[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--