[reportlab-users] Processor for kugar definitions

Dick Kniep reportlab-users@reportlab.com
Sat, 17 Jan 2004 01:06:24 +0100


--Boundary-00=_AyHCAR/7Ms8wb3C
Content-Type: text/plain;
  charset="us-ascii"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Hi list,

Enclosed is a first version of a processor that takes a definition of Kugar 
and creates a PDF based on that.

Using kudesigner you can create an XML file that contains a formdefinition. 
This form definition is then used to create a PDF.

I hope it is useful, and would like to see that it is included in the 
Reportlab as an example of how to use Reportab.

Functionally it is not complete yet. However, in my opinion it is very usable, 
so if anyone feels the same, please let me know. Any patches are also very 
welcome.

Cheers to you all, and ReportLab, thank you for a wonderful product.

Dick Kniep
Lindix BV

--Boundary-00=_AyHCAR/7Ms8wb3C
Content-Type: text/x-python;
  charset="us-ascii";
  name="GenXMLReport.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="GenXMLReport.py"

#-----------------------------------------------------------------------------
# Name:        GenXMLReport.py
# Purpose:     Generate PDF output using Reportlab with definition taken from
#              Kugar
#
# Author:      Dick Kniep
#
# Created:     2003/28/06
# RCS-ID:      $Id: GenReport.py,v 1.1.1.1 2003/07/09 09:23:36 dick Exp $
# Copyright:   (c) 2002
# Licence:     <your licence>
#-----------------------------------------------------------------------------
from xml.dom.ext.reader import Sax2
from xml.dom.ext import PrettyPrint
from xml.dom.DOMImplementation import implementation
from xml.dom import EMPTY_NAMESPACE
from os import tempnam, system, remove, chmod, popen
from math import floor
from types import *
from string import split, upper
from reportlab.platypus import BaseDocTemplate, TableStyle, Paragraph, Spacer, Table
from reportlab.platypus.doctemplate import *
from reportlab.lib.styles import getSampleStyleSheet,ParagraphStyle
from reportlab.lib import pagesizes, colors 
from reportlab.rl_config import defaultPageSize
from reportlab.lib.units import cm
from string import strip
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
import Opendb, cStringIO, copy

Formsize = ('A4','B5','LETTER','LEGAL','EXECUTIVE','A0','A1','A2','A3','A5','A6','A7','A8','A9','B0','B1','B10','B2','B3','B4','B6',
            'B7','B8','B9','C5E','COMM10E','DLE','FOLIO','LEDGER','TABLOID','NPAGESIZE')


POINTSCM = 28.34

FontConversion = {'Times New Roman':'Times-Roman'}

KUGARPOINTSX = 912.0
REPLABPOINTSX = 595.2
KUGARPOINTSY = 2100.0
REPLABPOINTSY = 1296.0

FACTORX = REPLABPOINTSX/KUGARPOINTSX
FACTORY = REPLABPOINTSY/KUGARPOINTSY

ALIGNMENT=[TA_LEFT,TA_CENTER,TA_RIGHT]

# from xml.dom import EMPTYNAMESPACE


Testdef =   (   ('veld1','kopveld1',"int",None),
                ('veld2','header2','chr',None),
                ('veld3','datumkop','dat',None),
                ('veld4','tekstkop','txt',None),
                ('veld5','numeriek','num',2),
                ('kortvld1','teller',"int",None))

TestData =  [   [123423,'veld2tekst kort','12-04-2003','lange tekst die gewrapped moet worden... Ik weet niet of dat goed lukt, maar ik ga het in ieder geval proberen, bovendien moet ik maar kijken wat de grootste lengte is die de routine aankan',1232300,2],
                [543211,'Tekst van veld2 tamellijk lang','17-12-2002','Nog een kop, die niet gewrapped moet worden',9830,4],
                [54232,'Tekst van veld2 tamellijk lang','17-12-2002','Nog een kop, die niet gewrapped moet worden',9830,6]]

            
class XMLReplab:
    """
    Class to define pages in ReportLab based on XML definition in Kugar
    
    Based on the name of the Kugar definitionfile, the XML definition is opened
    and read. Depending on the version this is a compressed file or a plain
    XML file.
    
    Kugar definitions are based on points, and defined based on the upper left corner
    reportlab definitions are based on points (fixed 72/inch) and the lower left corner.
    
    Conversion of points to centimeters is done based on the fixed rate of 72 points
    per inch, so 72 points per 2.54 cm is 28.34 points per cm.
    
    self.PgSize is a tuple that contains the x and y 
    
    If the InMemory switch is used, the pdf document is generated in memory and
    returned as a variable. This can be handy if it has to be stored in a database!
    """
    def __init__(self, kutfile, filenm = '/tmp/tmpout.pdf', InMemory=False, title=None):
        
        if InMemory:
            self.filenm = cStringIO.StringIO()
        else:
            self.filenm = filenm
#            tmpdir = Opendb.TableObjs.config.Option('config','tmpdir', False)
#            if tmpdir is None: tmpdir = '/tmp/'
#            if not tmpdir[len(tmpdir)-1:] == '/':
#                tmpdir = tmpdir + '/'

        
        reader = Sax2.Reader()
        k = open(kutfile)
        self.prtdef = reader.fromStream(k)
        k.close()

        self.count = 0
        self.tmpl = self.prtdef.getElementsByTagName('KugarTemplate')
        PageSize = int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'PageSize'))
        try:
            self.PgSize= pagesizes.__dict__[Formsize[PageSize]]
        except: 
            raise "Given pagesize not supported by reportlab....."
            return
        
        self.BottomMargin = int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'BottomMargin'))*FACTORY
        self.RightMargin = int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'RightMargin'))*FACTORX
        self.LeftMargin = int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'LeftMargin'))*FACTORX 
        self.TopMargin = int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'TopMargin'))*FACTORY 
        
        if int(self.tmpl[0].getAttributeNS(EMPTY_NAMESPACE,'PageOrientation')): self.PgSize = pagesizes.landscape(self.PgSize)
        else: self.PgSize = pagesizes.portrait(self.PgSize)

        self.Width = self.PgSize[0] - self.LeftMargin - self.RightMargin
        
#        self.fonts = getAvailableFonts()

        self.hdr = self.RepHdr(self)
        self.pag = self.RepPag(self)
        self.det = self.Detail(self)
        self.dft = self.DetailFooter(self)
        self.pft = self.PageFooter(self)
        self.rft = self.RepFooter(self)

        self.__defRepLabCanvas()
        self.flowables = []
        self.Section_Height = []
    
    def __defRepLabCanvas(self):
        """
        Build the frames and pagetemplates specific for this report,
        
        Note:
            Currently printfrequency is ignored, reportheader is printed ONCE,
            pageheader is printed every page, calculation of height of 
            detailframe is based on the pageheader and the pagefooter
            
        ReportFooter is also printed only once, but is concidered as a normal 
        flowable, and processed as a detailrow. This also means that it has to be 
        called!
        """
        self.document = BaseDocTemplate(self.filenm, pagesize=self.PgSize, author="Lindix BV")                
        PageTempls = []
        DetFrames = []
        Dpt = None
        if self.hdr.Available:
            Ypos = self.PgSize[1]-self.TopMargin-self.hdr.Height
            HdrFrame = Frame(self.LeftMargin, Ypos, self.PgSize[0]-self.LeftMargin-self.RightMargin, \
                    self.hdr.Height, id='rephdr', showBoundary=0)
            HPt = PageTemplate("FirstPage",HdrFrame,self.__FirstHdrPage, onPageEnd=self.__DetPages)
            PageTempls.append(HPt)
            
        if self.pag.Available:
            Ypos = self.PgSize[1]-self.TopMargin-self.pag.Height
#            PageHdrFrame = Frame(self.LeftMargin, Ypos, self.PgSize[0]-self.LeftMargin-self.RightMargin, \
#                    self.pag.Height, id='paghdr', showBoundary=0)
#            DetFrames.append(PageHdrFrame)

        if self.pft.Available:
            Ypos = self.BottomMargin
#            PageFtrFrame = Frame(self.LeftMargin, Ypos, self.PgSize[0]-self.LeftMargin-self.RightMargin, \
#                    self.pag.Height, id='pagftr', showBoundary=0)
#            DetFrames.append(PageFtrFrame)

        if self.rft.Available:
            Ypos = self.BottomMargin
#            PageFtrFrame = Frame(self.LeftMargin, Ypos, self.PgSize[0]-self.LeftMargin-self.RightMargin, \
#                    self.pag.Height, id='pagftr', showBoundary=0)
#            DetFrames.append(PageFtrFrame)

        if self.det.Available:
            Ypos = self.BottomMargin + self.pft.Height
            DetailFrame = Frame(self.LeftMargin, Ypos, self.PgSize[0]-self.LeftMargin-self.RightMargin, \
                    self.PgSize[1]-self.pag.Height, id='detail', showBoundary=0)
            DetFrames.append(DetailFrame)

        if len(DetFrames) > 0:
            DPt = PageTemplate("DetailPage",DetFrames,onPageEnd=self.__NextPage)
            PageTempls.append(DPt)

        self.document = BaseDocTemplate(self.filenm, pagesize=self.PgSize, pageTemplates=PageTempls, author="Lindix BV") 

    def __DetPages(self, canvas, doc):
        self.document.handle_currentFrame('detail')

    def __FirstHdrPage(self, canvas, doc):
        print "__FirstHdrPage"
        canvas.saveState()
        if self.hdr.Available:
            Ypos = self.PgSize[1]-self.TopMargin-self.hdr.Height
            self.__outputFixed(canvas, self.hdr.DetDefs, Ypos)
        canvas.restoreState()
        
    def __NextPage(self, canvas, doc):
        print "__NextPage"
        canvas.saveState()
        if self.pag.Available:
            Ypos = self.PgSize[1]-self.TopMargin-self.pag.Height
            self.__outputFixed(canvas, self.pag.DetDefs, Ypos)
        canvas.restoreState()

    def __outputFixed(self, canvas, DetDefs, Ypos):
#        print "__outputfixed"
        
        for n in range(len(DetDefs)):
            try:
                canvas.setFont(DetDefs[n].FontFamily,float(DetDefs[n].FontSize))
            except KeyError:
                try:
                    DetDefs[n].FontFamily = FontConversion[DetDefs[n].FontFamily]
                except:
                    DetDefs[n].FontFamily = 'Times-Roman'
                canvas.setFont(DetDefs[n].FontFamily,float(DetDefs[n].FontSize))
                    
            Ydefpos = int(Ypos + float(DetDefs[n].Y)*FACTORY + int(DetDefs[n].Height) + 0.5)
            Xdefpos = int(float(DetDefs[n].X)*FACTORX + self.LeftMargin + 0.5)
            canvas.drawString(Xdefpos , Ydefpos, DetDefs[n].Text)
    
    def __defRepLabTemplate(self):
        pass
    
    def __defRepLabFlowables(self):
        pass
    
    def Row(self, data={}, lvl=0, DetTot=False, RepTot=False):
        """
        This public method can be used to enter data into the report. It contains
        the data itself, which is a list of dictionaries where the fieldname 
        references the data, the detaillevel, a switch which determins whether it 
        is a levelbreak line, and a switch that determines whether this is an end
        of report line.
        
        If it is a subtotal, the switch DetTot must be True. If it is a grand-Total,
        the switch RepTot must be True, if there are still Detail Totals to be
        processed, these will NOT be processed, and effectively be lost.
        
        If calculated fields are not processed, they are NOT reset either. This 
        means that subtotals are unreliable if not processed correctly from the 
        calling program.
        
        This means that levelprocessing MUST be done in the calling program.
        
        Example:
            {fielda:1, fieldb:'Jan Klaassen', fieldc:5}
        
        if a field is not there that is defined in the layout, it is ignored, and
        the original value defined in kudesigner is printed.
        
        Note, possibly in the future, also an XML variant will become available
        
        The level is used to determine which record should be written.
        
        Fields and labels that are to be placed in different areas are generated
        using tables.
        """
        if RepTot:
            Rh, result = self.rft.ProcessRow(data, lvl) 
        elif DetTot:
            Rh, result = self.dft.ProcessRow(data, lvl) 
        else:
            Rh, result = self.det.ProcessRow(data, lvl) 
        self.flowables += [result]
        self.Section_Height.append(Rh)
        
    def Generate(self):
        """
        Routine that the actual output generates
        """
        
        print "Generate"
        
        self.document.build([Table(self.flowables, colWidths=[self.Width], rowHeights=self.Section_Height)])
        
        return self.filenm
        
        
    class KugarEntity:
        def __init__(self, fld, fldsatt):
            for att in fldsatt:
                setattr(self,att,fld.getAttributeNS(EMPTY_NAMESPACE,att))
            self.X = int(self.X)
            self.Y = int(self.Y)
            self.Width = int(self.Width)
            self.Height = int(self.Height)
            self.Row = []
            self.Col = []
            self.indent=0

        
        def bldParagraph(self, stylesheet):
            setattr(self,'Paragraph',Paragraph(self.Text, self.__getParagraphStyle(stylesheet)))

        def __getParagraphStyle(self, stylesheet):
            return ParagraphStyle(name='1',parent=stylesheet['Normal'],leftIndent=self.indent,alignment=ALIGNMENT[int(self.HAlignment)])

        def OverlapY(self, Entity):
            YlowerEntity = Entity.Y + Entity.Height
            YlowerSelf = self.Y + self.Height
            return (Entity.Y >= self.Y and Entity.Y < YlowerSelf) or \
                (self.Y >= Entity.Y and self.Y < YlowerEntity)

        def OverlapX(self, Entity):
            XrightEntity = Entity.X + Entity.Width
            XrightSelf = self.X + self.Width
            return (XrightEntity >= self.X and XrightEntity <= XrightSelf) or \
                (Entity.X >= self.X and Entity.X <= XrightSelf)

    class KugarField(KugarEntity):
        pass
            
    class KugarLabel(KugarEntity):
        def __init__(self, fld, fldsatt):
            XMLReplab.KugarEntity.__init__(self, fld, fldsatt)

    class KugarCalcField(KugarEntity):
        pass
        
    class KugarReport:
        lblsatt = ("BackgroundColor","BorderColor","BorderStyle","BorderWidth","FontFamily",
               "FontItalic","FontSize","FontWeight","ForegroundColor","HAlignment","Height","Text",
               "VAlignment","Width","WordWrap","X","Y")
        fldsatt = lblsatt + ("CommaSeparator","Currency","DataType","DateFormat","NegValueColor",
                "Precision","Field")
        calcatt = fldsatt + ("CalculationType",)

        def __init__(self, parent, type):
            self.Available = False
            self.Height = 0
            self.repnode = parent.prtdef.getElementsByTagName(type)
            self.xpos = []
            self.cols = []
            self.style = ""
            self.calcfldidx = {}
            self.stylesheet=getSampleStyleSheet()
            self.next_id=1
            self.fldidx = {}
            self.parent = parent
            self.Width_replabPoints = int(self.parent.Width)
            self.Width = int(self.Width_replabPoints/FACTORX)
            self.RowHeight = []
            self.RowData = []
            self.Calculations = (lambda x,y: x+1, lambda x,y: x+y, lambda x,y: x+y, 'not supported', 'not supported')
            

        def UpdFldIdx(self, fld, fname=None):
            if fname is None: fname = fld.getAttributeNS(EMPTY_NAMESPACE,"Field")
            self.fldidx[fname]=self.next_id
            self.next_id += 1
            return fname

        def ProcessLbls(self, lbls):
            lb = []
            for lbl in lbls:
                p = XMLReplab.KugarLabel(lbl, self.lblsatt)
                fname = '__lbl__#'+strip(`self.next_id`)
                setattr(p,'Field',fname)
                self.UpdFldIdx(lbl, fname)
                lb.append(p)
            return lb

        def ProcessCalcFlds(self, flds, det, dd):
            fl = []
            for fld in flds:
                p = XMLReplab.KugarCalcField(fld, self.calcatt)
                fldnam = self.UpdFldIdx(fld)
                fl.append(p)
                det.CalcFld(fldnam, p.CalculationType)
            return fl
    
        def ProcessFlds(self, flds):
            fl = []
            for fld in flds:
                fl.append(XMLReplab.KugarField(fld, self.fldsatt))
                self.UpdFldIdx(fld)
            return fl

        def DefineOutputTable(self, DetDefs, SectionHeight):

            self.SectionHeight = SectionHeight
            self.recursive_call_lvl = 0
            RowData, DetDefs = self.__NextOutputTable(DetDefs, self.Width)
            n = 0
            while n < len(DetDefs):
                if DetDefs[n].__class__.__name__ == "KugarLabel":
                    DetDefs[n].bldParagraph(self.stylesheet)
                n += 1
            
            return RowData, DetDefs
            
        def __NextOutputTable(self, DetDefs, Width, RowDataHigher=None):
            """
            Recursive routine to define the rows and columns of the grid that
            will be output
            
            The first time the routine is run without the rows. When it is run
            recursively, the rows are filled, and so only the rows that are
            part of overlapping fields will be processed
            
            The dimensions of the tables are also generated, based on the actual
            height and width of the table.
            """

            if RowDataHigher is None:
                ColumnNr = 1
                a = [None]
                ColumnPos = 0
            else:
                a = []
                n = 0
                while n < len(RowDataHigher[2]):
                    if len(RowDataHigher[2][n][0]) > 1:
                        a.append(RowDataHigher[2][n][0])
                    n += 1
                ColumnPos = RowDataHigher[2][0][1]
                            
                ColumnNr = len(a)
                
            while ColumnNr:
                savXY = self.__bldHlpArray(DetDefs, a[ColumnNr-1])
                
                RowData = self.__defRows(DetDefs, savXY, Width)

                row = 0
                for DD_indxs in RowData:
                    if len(DD_indxs[0]) > 1:        # if there is more than one field in the same column
                        RowData[row][2], DetDefs = self.__defCols(DetDefs, savXY, DD_indxs[0], Width)
                    else:
                        DetDefs = self.__DefidxIndent(DD_indxs[0], ColumnPos, DetDefs)
                    row += 1

                row = 0
                while row < len(RowData):
                    col = 0
                    if len(RowData[row][0]) > 1:
                        while col < len(RowData[row][2]):
                            if len(RowData[row][2][col][0]) > 1: # more than one field in the row/column
                                self.recursive_call_lvl += 1
                                RowData[row][2][col][3], DetDefs = self.__NextOutputTable(DetDefs, RowData[row][2][col][2], RowDataHigher=RowData[row])
                            col += 1
                    row += 1
                ColumnNr -= 1
            
            return RowData, DetDefs
                
        def __defRows(self, DetDefs, savXY, ColWidth):
            """
            Check whether there is overlap in the Y positions. 
            
            Always start the rownr with 0. 
            """
            INDEX=0
            ROWHEIGT=1
            COLUMNS=2
            ROWWIDTH=3
            DD_row = []
            row = -1
            n = 0
            savRowYpos = int(self.SectionHeight)
            for Ypos, Height, Xpos, Width, m in savXY:
                if n == 0 or (len(savXY)-1) > n:
                    if n == 0 or not compDetDefs.OverlapY(DetDefs[m]): 
                        row += 1
                        DD_row.append([[m], int((savRowYpos - Ypos)*FACTORY+0.5), [m], int(ColWidth*FACTORX+0.5)])
                        savRowYpos = Ypos
                        compDetDefs = DetDefs[m]
                        
                    else:
                        if compDetDefs.Y > Ypos:
                            compDetDefs = DetDefs[m]

                        DD_row[row][0].append(m)
                        DD_row[row][2].append(m)

                elif len(savXY)-1 == n:
                    row += 1
                    DD_row.append([[m], int((savRowYpos - Ypos)*FACTORY+0.5), [m], int(ColWidth*FACTORX+0.5)])

                n += 1

            DD_row.reverse()
            
            return DD_row
            
        def __defCols(self, DetDefs, savXY, DD_indxs, Width):
            """
            Define columns. If X positions overlap, an extra column is added. 
            At the same time the width of the column is calculated, and 
            based on that width the indent is calculated.
            
            Note that this routine is called recursively!
            
            Also the columns are not intuitively numbered. Therefor, after the
            generation of the columns they are repositioned, so they reflect
            the real print order. This is done thru self.ColXpos. In that list
            the X-positions of the columns are stored, and based on that
            list the final order is determined.
            """
            INDEX=0
            XPOS=1
            WIDTH=2
            SUBCOL=3
            self.DDsort = DetDefs
            DD_indxs.sort(self.sortcol3)

            c = []
            col = -1
            n = 0
            while n < len(DD_indxs):
                Xpos = int(DetDefs[DD_indxs[n]].X)
                idx = DD_indxs[n]
                if n == 0 or not compDetDefs.OverlapX(DetDefs[idx]): 
                    col += 1
                    if n == 0:
                        if self.recursive_call_lvl == 0: Xpos = 0
                        ColXpos = Xpos
                    c.append([[idx], Xpos, 0, None])
                    compDetDefs = DetDefs[idx]
                    if n == 1:
                        c[col-1][WIDTH] = int((Xpos - int(ColXpos))*FACTORX+0.5)
                    elif n > 1:
                        c[col-1][WIDTH] = int((Xpos - int(DetDefs[DD_indxs[n-1]].X))*FACTORX+0.5)
                    if n == len(DD_indxs)-1:
                        c[col][WIDTH] = int((Width + ColXpos - Xpos)*FACTORX+0.5)
                    
                else:

                    c[col][INDEX].append(idx)
#                    if n > 1:
#                        c[col-1][WIDTH] = int((Xpos - int(DetDefs[DD_indxs[n-1]].X))*FACTORX+0.5)
                    if n == len(DD_indxs)-1:
                        c[col][WIDTH] = int((Width + ColXpos - Xpos)*FACTORX+0.5)
#                    c[col][WIDTH] = int((Xpos-c[col][XPOS])*FACTORX+0.5)

                n += 1
            
#            c = self.__colWidthAdjust(c, WIDTH, int(Width*FACTORX+0.5))
               
            DetDefs = self.__DefineIndent(c, DetDefs)
                
            return c, DetDefs


        def __colWidthAdjust(self, c, WIDTHIDX, Width):
            TotWidth = 0
            MaxWidth = 0
            n = 0
            while n < len(c): 
                if MaxWidth < c[n][WIDTHIDX]: 
                    MaxWidth = c[n][WIDTHIDX]
                    MaxN = n
                TotWidth += c[n][WIDTHIDX]
                n += 1
            
            if TotWidth <> Width:
                WDiff = Width - TotWidth
                c[MaxN][WIDTHIDX] += WDiff
                
            return c

        def sortcol3(self, a, b):
            if self.DDsort[a].X > self.DDsort[b].X: return 1
            elif self.DDsort[a].X < self.DDsort[b].X: return -1
            else: return 0

        def __DefineIndent(self, cols, DetDefs):
            """
            Define leftindent within a column
            """
            for idxs, Xpos, XWidth, subCol in cols:
                DetDefs = self.__DefidxIndent(idxs, Xpos, DetDefs)
            return DetDefs
        
        def __DefidxIndent(self, idxs, Xpos, DetDefs):
            for idx in idxs:
                DetDefs[idx].indent = int((int(DetDefs[idx].X) - Xpos)*FACTORX +0.5)
            return DetDefs

        def __bldHlpArray(self, DetDefs, rows=None):
            """
            Builds an array that is sorted, and can be processed easily
            
            Note that the Kugar position is the top left position of a field/label.
            Therefore the bottom left position is calculated by adding the Height
            """
            savXY=[]
            for m in range(len(DetDefs)):
                if rows is None or m in rows:
                    savXY.append([int(DetDefs[m].Y), int(DetDefs[m].Height), int(DetDefs[m].X), int(DetDefs[m].Width), m])

            savXY.sort(self.sortcol)
            savXY.reverse()
            return savXY

        def FillStandards(self, parent, Footer=False):
            if len(self.repnode):
                self.Available = True
                self.PrintFrequency = int(self.repnode[0].getAttributeNS(EMPTY_NAMESPACE,'PrintFrequency'))
                self.Height= int(self.repnode[0].getAttributeNS(EMPTY_NAMESPACE,'Height'))
                if Footer:
                    self.DetDefs = self.ProcessFlds(self.repnode[0].getElementsByTagName('Field')) + \
                            self.ProcessLbls(self.repnode[0].getElementsByTagName('Label'))
                    self.DetDefs += self.ProcessCalcFlds(self.repnode[0].getElementsByTagName('CalculatedField'), 
                            parent.det, self.DetDefs)
                else:
                    self.DetDefs = self.ProcessLbls(self.repnode[0].getElementsByTagName('Label'))
                self.RowData, self.DetDefs = self.DefineOutputTable(self.DetDefs,self.Height)
        
        def sortcol(self, a, b):
            if a[0] > b[0]: return 1
            elif a[0] < b[0]: return -1
            elif a[1] > b[1]: return 1
            elif a[1] < b[1]: return -1
            elif a[2] < b[2]: return 1
            elif a[2] > b[2]: return -1
            else: return 0


    class RepHdr(KugarReport):
        """
        Print the reportheader, currently the PrintFrequency directive is ignored
        and the reportheader is only printed once on the first page
        """
        def __init__(self, parent):
            print "* * * Bouwen RepHdrdefinities"
            XMLReplab.KugarReport.__init__(self, parent, 'ReportHeader')
            self.FillStandards(parent)
            
    

    class RepPag(KugarReport):
        """
        Print the Page header, currently, the PrintFrequency directive is ignored
        and the page header is printed on every page, except the first (if there
        is a reportheader)
        """
        def __init__(self, parent):
            print "* * * Bouwen RepPagdefinities"
            XMLReplab.KugarReport.__init__(self, parent, 'PageHeader')
            self.FillStandards(parent)

    class FlowDetTot(KugarReport):
        def __init__(self, parent):
            XMLReplab.KugarReport.__init__(self, parent, 'FlowDetTot')
            self.DetDefs = []
            self.DD = {}
            self.cols = []
            self.columndata = []
            self.xposlvl = []
            self.calcfldidx = {}
            self.table_elmslvl = []
            self.fldlvlidx = []
            self.DetData = []
            self.Det_Height = []
            self.stylesheet=getSampleStyleSheet()
            self.detlvlheight = []
        
        def bldDefs(self, type, prtdef, width, pdef=None, CalcFlds=False):
            detlines = prtdef.getElementsByTagName(type)
            n = 0
            for det in detlines:
                self.next_id=0
                self.xpos = []
                self.Available = True
                try:
                    lvl = int(det.getAttributeNS(EMPTY_NAMESPACE,'Level'))
                except ValueError:
                    lvl=0
                print "Detaillvl " + `lvl` + " is arraynr " + `n`
                
                self.DD[lvl] = n
                self.Det_Height.append( float(det.getAttributeNS(EMPTY_NAMESPACE,'Height')))
                p = self.ProcessFlds(det.getElementsByTagName('Field')) + \
                        self.ProcessLbls(det.getElementsByTagName('Label'))
                
                if CalcFlds:
                    p += self.ProcessCalcFlds(det.getElementsByTagName('CalculatedField'), 
                        pdef, p)
                self.DetDefs.append(p)
                Rd, self.DetDefs[n] = self.DefineOutputTable(self.DetDefs[n], self.Det_Height[n])
                self.RowData.append(Rd)
                self.fldlvlidx.append(self.fldidx)
                n += 1

        def ProcessRow(self, data, lvl):
            print "ProcessRow"
            self.DetData = []
            lvlidx = self.DD[lvl]
            return self.__ProcessTableInput(data, lvlidx)
            
        def __ProcessTableInput(self, data, lvlidx):
            print "ProcessTable"
            result = []
            H = []
            if type(data) is DictType:
                Rh, t = self.__SingleRow(data, lvlidx)
                result = [t]
                H = Rh
            elif type(data) is TupleType or type(data) is ListType:
                for rec in data:
                    Rh, t = self.__SingleRow(data, lvlidx)
                    result.append(t)
                    H.append(Rh)
            else:
                raise ValueError % "Error in calling Row, dictionary, tuple or list of dictionaries expected...."
            
            return H, result

        def __SingleRow(self, data, lvlidx):
            """ 
            Processing of a single ENTERED row. This may yield several
            output rows, so maybe the name is a little confusing.
            
            The data that is received from the calling application is transferred
            and processed as a Paragraph.
            
            As there can be more than one detaillevel, this is also passed.
            """
            print "------------------------------------- SingleRow ----------------------------------"
                
            n = 0
            while n < len(self.DetDefs[lvlidx]):
                if self.DetDefs[lvlidx][n].__class__.__name__ == 'KugarField':
                    try:
                        value = data[self.DetDefs[lvlidx][n].Field]
                    except:
                        value = ''
                    self.DetDefs[lvlidx][n].Paragraph = self.__FillinField(self.DetDefs[lvlidx][n], value)
                    if hasattr(self.DetDefs[lvlidx][n],'CalcResult'):
                        m = 0
                        while m < len(self.DetDefs[lvlidx][n].CalcResult):
                            self.DetDefs[lvlidx][n].CalcResult[m] = self.Calculations[self.DetDefs[lvlidx][n].CalcType[m]](self.DetDefs[lvlidx][n].CalcResult[m], float(value))
                            m += 1
                elif self.DetDefs[lvlidx][n].__class__.__name__ == 'KugarCalcField':
                    pass
                n += 1
            
            return self.__ProcessOutpTable(self.RowData[lvlidx], lvlidx)
                            
            print "EXit #### SingleRow"

        def __ProcessOutpTable(self, RowData, lvlidx):
            OutData = []
            prim_rowHeight = []
            tabStyle = TableStyle ([('VALIGN',(0,0),(-1,-1),'TOP'),
                                    ('ALIGN',(0,0),(-1,-1),'LEFT'),
                                    ('LEFTPADDING',(0,0),(-1,-1),0),
                                    ('RIGHTPADDING',(0,0),(-1,-1),0),
                                    ('TOPPADDING',(0,0),(-1,-1),0),
                                    ('BOTTOMPADDING',(0,0),(-1,-1),0)] )
            for idxs, height, cols, width in RowData:
                prim_rowHeight.append(height)
                if len(idxs) == 1:                      # Single column
                    OutData.append([self.DetDefs[lvlidx][idxs[0]].Paragraph])
                else:
                    ColWidth = []
                    OutRCol = []
                    XW = 0
                    for colidxs, Xpos, XWidth, subcol in cols:
                        if subcol is None:
                            t = Table([[self.DetDefs[lvlidx][colidxs[0]].Paragraph]],colWidths=[XWidth],rowHeights=[height])
                            t.setStyle(tabStyle)
                            OutRCol.append(t)
                        else:
                            height, t = self.__ProcessOutpTable(subcol, lvlidx)
                            OutRCol.append([t])
                        ColWidth.append(XWidth)
                    t = Table([OutRCol], colWidths=ColWidth, rowHeights=[height])
                    t.setStyle(tabStyle)
                    OutData.append([t])
            t = Table(OutData, rowHeights=prim_rowHeight, colWidths=[width])
            Rh = 0 
            for H in prim_rowHeight: Rh += H
            t.setStyle(tabStyle)
            return Rh, t
                    
        def __FillinField(self, DetDef, value):
            q = self.__getParagraphStyle(DetDef)
            return Paragraph(text=value, style=q)

        def __getParagraphStyle(self, lbl):
            return ParagraphStyle(name='1',parent=self.stylesheet['Normal'],leftIndent=lbl.indent,alignment=ALIGNMENT[int(lbl.HAlignment)])

        def __getTxtValue(self, lbl, data):
            try:
                txt = data[lbl['Field']]
            except:
                txt = lbl['Text']
            return txt
    
    class Detail(FlowDetTot):
        """
        Only details, detailfooters and reportfooters can process rows with fields
        """
        def __init__(self, parent):
            self.parent = parent
            self.fldidx = {}
            XMLReplab.FlowDetTot.__init__(self, parent)
            self.bldDefs('Detail', parent.prtdef, parent.Width)
                
        def CalcFld(self, fldnam, CalcType=1):
            for lvl in range(len(self.DetDefs)):
                try:
                    fldnr = self.fldlvlidx[lvl][fldnam]
                    break
                except KeyError: continue
            
            if not hasattr(self.DetDefs[lvl][fldnr],'CalcType'):
                self.DetDefs[lvl][fldnr].CalcType = [CalcType]
                self.DetDefs[lvl][fldnr].CalcResult = [0]
            else:
                self.DetDefs[lvl][fldnr].CalcType.append(CalcType)
                self.DetDefs[lvl][fldnr].CalcResult.append(0)

    class DetailFooter(FlowDetTot):
        def __init__(self, parent):
            print "* * * Bouwen DetailFooterdefinities"
            self.parent = parent
            XMLReplab.FlowDetTot.__init__(self, parent)
            self.bldDefs('DetailFooter', parent.prtdef, parent.Width, parent.det, True)

    class PageFooter(KugarReport):
        def __init__(self, parent):
            print "* * * Bouwen PageFooterdefinities"
            self.parent = parent
            XMLReplab.KugarReport.__init__(self, parent, 'PageFooter')
            self.FillStandards(parent,True)

    class RepFooter(FlowDetTot):
        def __init__(self, parent):
            print "* * * Bouwen RepFooterdefinities"
            self.parent = parent
            XMLReplab.FlowDetTot.__init__(self, parent)
            self.bldDefs('ReportFooter', parent.prtdef, parent.Width, parent.det, True)
            

if __name__=="__main__":
    p = XMLReplab('../config/demooffertenw.kut',InMemory=False)
    p.Row({"Naam1":"dddddd", "Naam2":"eeeeeeee","Postbus":"Postbus 900","Postcode":"1213 DS","Woonplaats":"Vlaardingen"},lvl=0)
    p.Row({"omschrijving":"EERSTE omschrijving"},lvl=1)
    p.Row({"omschrijving":"Omschrijving","bedrag":"12,35"},lvl=2)
    p.Row({"omschrijving":"Omschrijving-2","bedrag":"13,35"},lvl=2)
    p.Row({"omschrijving":"Omschrijving-3","bedrag":"14,35"},lvl=2)
    p.Row({"namens":"Van Puffelen","opdrachtgever":"Putje Puek"},RepTot=True)
    
    p.Generate()
    
--Boundary-00=_AyHCAR/7Ms8wb3C--