reportlab: start of doc programming
authorrgbecker
Thu, 21 Aug 2008 16:50:11 +0000
changeset 2955 cc16265295fb
parent 2954 5ec6485e810a
child 2956 5c17941635bd
reportlab: start of doc programming
reportlab/platypus/doctemplate.py
reportlab/platypus/flowables.py
reportlab/test/test_platypus_general.py
--- a/reportlab/platypus/doctemplate.py	Thu Aug 21 13:15:24 2008 +0000
+++ b/reportlab/platypus/doctemplate.py	Thu Aug 21 16:50:11 2008 +0000
@@ -35,6 +35,7 @@
 from reportlab.rl_config import defaultPageSize, verbose
 import reportlab.lib.sequencer
 from reportlab.pdfgen import canvas
+import tokenize
 
 from types import *
 import sys
@@ -338,6 +339,8 @@
     def __init__(self, filename, **kw):
         """create a document template bound to a filename (see class documentation for keyword arguments)"""
         self.filename = filename
+        self._nameSpace = dict(doc=self)
+        self._lifetimes = {}
 
         for k in self._initArgs.keys():
             if not kw.has_key(k):
@@ -434,6 +437,7 @@
             check the next page template
             hang a page begin
         '''
+        self._removeVars('page')
         #detect infinite loops...
         if self._curPageFlowableCount == 0:
             self._emptyPages += 1
@@ -488,6 +492,7 @@
         ''' Handles the semantics of the end of a frame. This includes the selection of
             the next frame or if this is the last frame then invoke pageEnd.
         '''
+        self._removeVars('frame')
         self._leftExtraIndent = self.frame._leftExtraIndent
         self._rightExtraIndent = self.frame._rightExtraIndent
 
@@ -557,7 +562,6 @@
 
     def handle_currentFrame(self,fx,resume=0):
         '''change to the frame with name or index fx'''
-
         self.handle_nextFrame(fx,resume)
         self.handle_frameEnd(resume)
 
@@ -694,7 +698,7 @@
 
         #each distinct pass gets a sequencer
         self.seq = reportlab.lib.sequencer.Sequencer()
-        
+
         self.canv = canvasmaker(filename or self.filename,
                                 pagesize=self.pagesize,
                                 invariant=self.invariant,
@@ -710,6 +714,7 @@
         self.handle_documentBegin()
 
     def _endBuild(self):
+        self._removeVars('build')
         if self._hanging!=[] and self._hanging[-1] is PageBegin:
             del self._hanging[-1]
             self.clean_hanging()
@@ -885,6 +890,64 @@
         '''called after a flowable has been rendered'''
         pass
 
+    _allowedLifetimes = 'page','frame','build','forever'
+    def docAssign(self,var,expr,lifetime):
+        var=var.strip()+'\n'
+        expr=expr.strip()
+        T=tokenize.generate_tokens(lambda :var)
+        tokens=[]
+        while 1:
+            t=T.next()
+            if t[0]==tokenize.NEWLINE: break
+            tokens.append(t)
+        simple = len(tokens)==1
+        del T
+        var=var.strip()
+        try:
+            if lifetime not in self._allowedLifetimes:
+                raise ValueError('bad lifetime %r not in %r'%(lifetime,self._allowedLifetimes))
+            exec '%s=(%s)' % (var,expr) in {},self._nameSpace
+        except:
+            exc = sys.exc_info()[1]
+            args = list(exc.args)
+            args[-1] += '\ndocAssign %s=(%s) lifetime=%r failed!' % (var,expr,lifetime)
+            exc.args = tuple(args)
+            raise
+        if simple:
+            for v in self._lifetimes.itervalues():
+                if var in v:
+                    v.remove(var)
+            self._lifetimes.setdefault(lifetime,set([])).add(var)
+
+    def docExec(self,stmt):
+        stmt=stmt.strip()
+        try:
+            exec stmt in {},self._nameSpace
+        except:
+            exc = sys.exc_info()[1]
+            args = list(exc.args)
+            args[-1] += '\ndocExec %s failed!' % stmt
+            exc.args = tuple(args)
+            raise
+
+    def _removeVars(self,lifetime):
+        for k in self._lifetimes.setdefault(lifetime,[]):
+            try:
+                del self._nameSpace[k]
+            except KeyError:
+                pass
+        del self._lifetimes[lifetime]
+
+    def docEval(self,expr):
+        try:
+            return eval(expr.strip(),{},self._nameSpace)
+        except:
+            exc = sys.exc_info()[1]
+            args = list(exc.args)
+            args[-1] += '\ndocEval %s failed!' % expr
+            exc.args = tuple(args)
+            raise
+
 class SimpleDocTemplate(BaseDocTemplate):
     """A special case document template that will handle many simple documents.
        See documentation for BaseDocTemplate.  No pageTemplates are required
--- a/reportlab/platypus/flowables.py	Thu Aug 21 13:15:24 2008 +0000
+++ b/reportlab/platypus/flowables.py	Thu Aug 21 16:50:11 2008 +0000
@@ -399,7 +399,11 @@
             r = "%s filename=%s>" % (r[:-4],self.filename)
         return r
 
-class Spacer(Flowable):
+class NullDraw(Flowable):
+    def draw(self):
+        pass
+
+class Spacer(NullDraw):
     """A spacer just takes up space and doesn't draw anything - it guarantees
        a gap between objects."""
     _fixedWidth = 1
@@ -411,10 +415,7 @@
     def __repr__(self):
         return "%s(%s, %s)" % (self.__class__.__name__,self.width, self.height)
 
-    def draw(self):
-        pass
-
-class UseUpSpace(Flowable):
+class UseUpSpace(NullDraw):
     def __init__(self):
         pass
 
@@ -426,9 +427,6 @@
         self.height = availHeight
         return (availWidth,availHeight-1e-8)  #step back a point
 
-    def draw(self):
-        pass
-
 class PageBreak(UseUpSpace):
     """Move on to the next page in the document.
        This works by consuming all remaining space in the frame!"""
@@ -642,16 +640,13 @@
             self.I.drawOn(canv,self.width-self.wI-self.xpad,self.height-self.hI)
             self.P.drawOn(canv,0,0)
 
-class FailOnWrap(Flowable):
+class FailOnWrap(NullDraw):
     def wrap(self, availWidth, availHeight):
         raise ValueError("FailOnWrap flowable wrapped and failing as ordered!")
 
-    def draw(self):
-        pass
-
 class FailOnDraw(Flowable):
     def wrap(self, availWidth, availHeight):
-        return (0,0)
+        return 0,0
 
     def draw(self):
         raise ValueError("FailOnDraw flowable drawn, and failing as ordered!")
@@ -1115,7 +1110,7 @@
     def draw(self):
         self.canv.bookmarkHorizontal(self._name,0,0)
 
-class FrameSplitter(Flowable):
+class FrameSplitter(NullDraw):
     '''When encountered this flowable should either switch directly to nextTemplate
     if remaining space in the current frame is less than gap+required or it should
     temporarily modify the current template to have the frames from nextTemplate
@@ -1165,5 +1160,56 @@
             G.append(CurrentFrameFlowable(F[0].id))
         frame.add_generated_content(*G)
         return 0,0
-    def draw(self):
-        pass
+
+class DocAssign(NullDraw):
+    '''At wrap time this flowable evaluates var=expr in the doctemplate namespace'''
+    _ZEROSIZE=1
+    def __init__(self,var,expr,life='frame'):
+        Flowable.__init__(self)
+        self.args = var,expr,life
+    def wrap(self,aW,aH):
+        NS=self._doctemplateAttr('_nameSpace')
+        NS.update(dict(availableWidth=aW,availableHeight=aH))
+        try:
+            self._doctemplateAttr('d'+self.__class__.__name__[1:])(*self.args)
+        finally:
+            for k in 'availableWidth','availableHeight':
+                try:
+                    del NS[k]
+                except:
+                    pass
+        return 0,0
+
+class DocExec(DocAssign):
+    '''at wrap time exec stmt in doc._nameSpace'''
+    def __init__(self,stmt):
+        Flowable.__init__(self)
+        self.args=stmt,
+
+class DocPara(DocAssign):
+    '''at wrap time create a paragraph with the value of expr as text
+    if paraTemplate is specified it should use %(value)s for string interpolation
+    suitable defaults will be used if style and klass are None
+    '''
+    def __init__(self,expr,paraTemplate=None,style=None,klass=None):
+        Flowable.__init__(self)
+        self.expr=expr
+        self.paraTemplate=paraTemplate
+        self.style=style
+        self.klass=klass
+
+    def wrap(self,aW,aH):
+        value=self._doctemplateAttr('docEval')(self.expr)
+        if self.paraTemplate:
+            value = self.paraTemplate % dict(value=value)
+        else:
+            value = str(value)
+        P = self.klass
+        if not P:
+            from reportlab.platypus.paragraph import Paragraph as P
+        style = self.style
+        if not style:
+            from reportlab.lib.styles import getSampleStyleSheet
+            style=getSampleStyleSheet()['Code']
+        self._doctemplateAttr('frame').add_generated_content(P(value,style=style))
+        return 0,0
--- a/reportlab/test/test_platypus_general.py	Thu Aug 21 13:15:24 2008 +0000
+++ b/reportlab/test/test_platypus_general.py	Thu Aug 21 16:50:11 2008 +0000
@@ -574,13 +574,21 @@
         from reportlab.lib.styles import ParagraphStyle
         from reportlab.graphics.shapes import Drawing, Rect
         from reportlab.platypus import SimpleDocTemplate
+        from reportlab.platypus.flowables import DocAssign, DocExec, DocPara
         normal = ParagraphStyle(name='Normal', fontName='Helvetica', fontSize=8.5, leading=11)
         header = ParagraphStyle(name='Heading1', parent=normal, fontSize=14, leading=19,
                     spaceAfter=6, keepWithNext=1)
         d = Drawing(400, 200)
         d.add(Rect(50, 50, 300, 100))
 
-        story = [Paragraph("The section header", header), d]
+        story = [Paragraph("The section header", header), d,
+                DocAssign('currentFrame','doc.frame.id'),
+                DocAssign('currentPageTemplate','doc.pageTemplate.id'),
+                DocAssign('aW','availableWidth'),
+                DocAssign('aH','availableHeight'),
+                DocAssign('aWH','availableWidth,availableHeight'),
+                DocPara('repr(doc._nameSpace)'),
+                ]
         doc = SimpleDocTemplate('test_drawing_keepwithnext.pdf')
         doc.build(story)