#$Id: psionData.py,v 1.2 1996/10/07 14:52:39 connolly Exp $
#
# Documentation on psion database in:
#PSIONICS FILE - DBF.FMT
#=======================
#Format and use of Data files
#Last modified 1994-10-21
# http://www.cityscape.co.uk/~cdwf/psion/psionics/dbf.fmt

import struct, string
from string import index
import sys #@@ for debug

class T:
    def factory(self, ty):
	return Record

    def load(self, stream):
	self.loadHeader(stream)

	self.loadRecords(stream)

    def loadHeader(self, stream):
	h = Header(self)
	h.load(stream)
	self.header = h

class Header:
#A data file (also called a database file) begins with a 22 byte header of the
#following form:
#  Offset  0 (cstr): "SPREADSHEET"
#  Offset 16 (word): format version number
#  Offset 18 (word): offset value (meaning unknown)
#  Offset 20 (word): OPL runtime version number
#
#The version numbers and offset value are all zero.
    # struct module lacks string
    #fmt = '16chhh' 
    fmt = 'hhh'

    def __init__(self, parent):
	pass

    def load(self, stream):
	self.signature = stream.read(16)
	(self.formatVersion, offset, self.OPLruntimeVersion)=\
		   readStruct(self.fmt, stream)

	pad = offset - (16 + struct.calcsize(self.fmt))
	if pad > 0:
	    debug ("offset, header pad bytes", offset, pad)
	    stream.read(pad)
	debug("header: ", self.signature, offset, self.OPLruntimeVersion)


class Record:
    def __init__(self, parent, ty, siz):
	self.ty = ty
	self.siz = siz
	pass

    def load(self, stream):
	if self.ty > 0: debug("unknown record: ", self.ty, self.siz)
	stream.read(self.siz)

class DBF(T):
    def factory(self, ty):
	if ty == 2:
	    return Schema
	elif ty == 3:
	    return Descriptor
	elif ty == 1 or (ty > 4 and ty < 15):
	    return DataRecord

	return Record

    rec_fmt = 'h'
    hdQty = struct.calcsize(rec_fmt)

    def loadRecords(self, stream):
	fmt = self.rec_fmt

	while 1:
	    hd = stream.read(self.hdQty)
	    if not hd: break
	    (recHd,) = struct.unpack(fmt, hd)
	    (siz, ty) = (recHd & 0x0FFF, recHd >> 12)

	    fac = self.factory(ty)
	    r = fac(self, ty, siz)
	    r.load(stream)

    _formats = ['h', 'i', 'd', 's']

    def setSchema(self, raw):
	qty = len(raw)
	s = [None] * qty
	for i in range(0,qty):
	    (idx,) = struct.unpack('b', raw[i])
	    s[i] = self._formats[idx]

	self.schema = s

    def setLabels(self, labels):
	self.labels = labels
	debug('labels:', labels)

    def dataRecord(self, fields):
	print string.join(fields, '\t')

class Schema:
    def __init__(self, parent, ty, siz):
	self.siz = siz
	self.dbf = parent

    def load(self, stream):
	self.dbf.setSchema(stream.read(self.siz))

class DataRecord:
    def __init__(self, parent, ty, siz):
	self.siz = siz
	self.dbf = parent
    
    def load(self, stream):
	raw = stream.read(self.siz)

	rec = []
	for f in self.dbf.schema:
	    if not raw: break
	    if f == 's':
		(qty,) = struct.unpack('b', raw[0])
		v = raw[1:qty+1]
		raw = raw[qty+1:]
	    else:
		qty = struct.calcsize(f)
		(v,) = struct.unpack(f, raw[:qty])
		raw = raw[qty:]
	    rec.append(v)

	self.dbf.dataRecord(rec)


class Descriptor:
    def __init__(self, parent, ty, siz):
	self.siz = siz
	self.dbf = parent

    def load(self, stream):
	raw = stream.read(self.siz)

	while raw:
	    (recHd,) = struct.unpack('h', raw[:2])
	    (siz, ty) = (recHd & 0x0FFF, recHd >> 12)

	    subRec = raw[2:2+siz]
	    raw = raw[2+siz:]

	    if ty == 4:
		labels = []
		while subRec:
		    (qty,) = struct.unpack('b', subRec[0])
		    labels.append(subRec[1:1+qty])
		    subRec = subRec[1+qty:]


		self.dbf.setLabels(labels)


# Utility functions

def readStruct(fmt, stream):
    qty = struct.calcsize(fmt)
    data = stream.read(qty)
    ret = struct.unpack(fmt, data)
#    print "@#readStruct: ", fmt, qty, ret
    return ret


def readQstr(stream):
    (len,) = readStruct('b', stream)
    return stream.read(len)

def cstr(s):
    return s[:index(s, '\000')]

# test harness

def test():
    import sys

    d = DBF()
    d.load(sys.stdin)

def debug(*args):
    sys.stderr.write(`args`+'\n')

if __name__ == '__main__': test()
