simple support for acrobat forms in reportlab documents. Minor optimizations to metrics
authoraaron_watters
Thu, 20 Jun 2002 15:05:58 +0000
changeset 1660 c93476060b9d
parent 1659 0711987437b6
child 1661 94d09f7b4c9a
simple support for acrobat forms in reportlab documents. Minor optimizations to metrics
reportlab/pdfbase/pdfform.py
reportlab/pdfbase/pdfmetrics.py
reportlab/pdfbase/pdfpattern.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/reportlab/pdfbase/pdfform.py	Thu Jun 20 15:05:58 2002 +0000
@@ -0,0 +1,632 @@
+
+"""Support for Acrobat Forms in ReportLab documents
+
+This module is somewhat experimental at this time.
+
+Includes basic support for
+    textfields,
+    select fields (drop down lists), and
+    check buttons.
+
+The public interface consists of functions at the moment.
+At some later date these operations may be made into canvas
+methods. (comments?)
+
+The ...Absolute(...) functions position the fields with respect
+to the absolute canvas coordinate space -- that is, they do not
+respect any coordinate transforms in effect for the canvas.
+
+The ...Relative(...) functions position the ONLY THE LOWER LEFT
+CORNER of the field using the coordinate transform in effect for
+the canvas.  THIS WILL ONLY WORK CORRECTLY FOR TRANSLATED COORDINATES
+-- THE SHAPE, SIZE, FONTSIZE, AND ORIENTATION OF THE FIELD WILL NOT BE EFFECTED
+BY SCALING, ROTATION, SKEWING OR OTHER NON-TRANSLATION COORDINATE
+TRANSFORMS.
+
+Please note that all field names (titles) in a given document must be unique.
+Textfields and select fields only support the "base 14" canvas fonts
+at this time.
+
+See individual function docstrings below for more information.
+
+The function test1(...) generates a simple test file.
+
+THIS CONTRIBUTION WAS COMMISSIONED BY REPORTLAB USERS
+WHO WISH TO REMAIN ANONYMOUS.
+"""
+
+### NOTE: MAKE THE STRING FORMATS DYNAMIC IN PATTERNS TO SUPPORT ENCRYPTION XXXX
+
+import string
+from reportlab.pdfbase.pdfdoc import LINEEND, PDFString, PDFStream, PDFDictionary, PDFName
+
+#==========================public interfaces
+
+def textFieldAbsolute(canvas, title, x, y, width, height, value="", maxlen=1000000, multiline=0):
+    """Place a text field on the current page
+        with name title at ABSOLUTE position (x,y) with
+        dimensions (width, height), using value as the default value and
+        maxlen as the maximum permissible length.  If multiline is set make
+        it a multiline field.
+    """
+    theform = getForm(canvas)
+    return theform.textField(canvas, title, x, y, x+width, y+height, value, maxlen, multiline)
+
+def textFieldRelative(canvas, title, xR, yR, width, height, value="", maxlen=1000000, multiline=0):
+    "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
+    (xA, yA) = canvas.absolutePosition(xR,yR)
+    return textFieldAbsolute(canvas, title, xA, yA, width, height, value, maxlen, multiline)
+
+def buttonFieldAbsolute(canvas, title, value, x, y):
+    """Place a check button field on the current page
+        with name title and default value value (one of "Yes" or "Off")
+        at ABSOLUTE position (x,y).
+    """
+    theform = getForm(canvas)
+    return theform.buttonField(canvas, title, value, x, y)
+
+def buttonFieldRelative(canvas, title, value, xR, yR):
+    "same as buttonFieldAbsolute except the x and y are relative to the canvas coordinate transform"
+    (xA, yA) = canvas.absolutePosition(xR,yR)
+    return buttonFieldAbsolute(canvas, title, value, xA, yA)
+
+def selectFieldAbsolute(canvas, title, value, options, x, y, width, height):
+    """Place a select field (drop down list) on the current page
+        with name title and 
+        with options listed in the sequence options
+        default value value (must be one of options)
+        at ABSOLUTE position (x,y) with dimensions (width, height)."""
+    theform = getForm(canvas)
+    theform.selectField(canvas, title, value, options, x, y, x+width, y+height)
+    
+def selectFieldRelative(canvas, title, value, options, xR, yR, width, height):
+    "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
+    (xA, yA) = canvas.absolutePosition(xR,yR)
+    return selectFieldAbsolute(canvas, title, value, options, xA, yA, width, height)
+
+def test1():
+    from reportlab.pdfgen import canvas
+    fn = "formtest1.pdf"
+    c = canvas.Canvas(fn)
+    # first page
+    c.setFont("Courier", 10)
+    c.drawString(100, 500, "hello world")
+    textFieldAbsolute(c, "fieldA", 100, 600, 100, 20, "default value")
+    textFieldAbsolute(c, "fieldB", 100, 300, 100, 50, "another default value", multiline=1)
+    selectFieldAbsolute(c, "fieldC", "France", ["Canada", "France", "China"], 100, 200, 100, 20)
+    c.rect(100, 600, 100, 20)
+    buttonFieldAbsolute(c, "field2", "Yes", 100, 700)
+    c.rect(100, 700, 20, 20)
+    buttonFieldAbsolute(c, "field3", "Off", 100, 800)
+    c.rect(100, 800, 20, 20)
+    # second page
+    c.showPage()
+    c.setFont("Helvetica", 7)
+    c.translate(50, 20)
+    c.drawString(100, 500, "hello world")
+    textFieldRelative(c, "fieldA_1", 100, 600, 100, 20, "default value 2")
+    c.setStrokeColorRGB(1,0,0)
+    c.setFillColorRGB(0,1,0.5)
+    textFieldRelative(c, "fieldB_1", 100, 300, 100, 50, "another default value 2", multiline=1)
+    selectFieldRelative(c, "fieldC_1", "France 1", ["Canada 0", "France 1", "China 2"], 100, 200, 100, 20)
+    c.rect(100, 600, 100, 20)
+    buttonFieldRelative(c, "field2_1", "Yes", 100, 700)
+    c.rect(100, 700, 20, 20)
+    buttonFieldRelative(c, "field3_1", "Off", 100, 800)
+    c.rect(100, 800, 20, 20)
+    c.save()
+    print "wrote", fn
+
+#==========================end of public interfaces
+
+from pdfpattern import PDFPattern
+
+def getForm(canvas):
+    "get form from canvas, create the form if needed"
+    try:
+        return canvas.AcroForm
+    except AttributeError:
+        theform = canvas.AcroForm = AcroForm()
+        # install the form in the document
+        d = canvas._doc
+        cat = d._catalog
+        cat.AcroForm = theform
+        return theform
+    
+class AcroForm:
+    def __init__(self):
+        self.fields = []
+    def textField(self, canvas, title, xmin, ymin, xmax, ymax, value="", maxlen=1000000, multiline=0):
+        # determine the page ref
+        doc = canvas._doc
+        page = doc.thisPageRef()
+        # determine text info
+        (R,G,B) = canvas._fillColorRGB
+        #print "rgb", (R,G,B)
+        font = canvas. _fontname
+        fontsize = canvas. _fontsize
+        field = TextField(title, value, xmin, ymin, xmax, ymax, page, maxlen,
+                          font, fontsize, R, G, B, multiline)
+        self.fields.append(field)
+        canvas._addAnnotation(field)
+    def selectField(self, canvas, title, value, options, xmin, ymin, xmax, ymax):
+        # determine the page ref
+        doc = canvas._doc
+        page = doc.thisPageRef()
+        # determine text info
+        (R,G,B) = canvas._fillColorRGB
+        #print "rgb", (R,G,B)
+        font = canvas. _fontname
+        fontsize = canvas. _fontsize
+        field = SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
+              font=font, fontsize=fontsize, R=R, G=G, B=B)
+        self.fields.append(field)
+        canvas._addAnnotation(field)
+    def buttonField(self, canvas, title, value, xmin, ymin):
+        # determine the page ref
+        doc = canvas._doc
+        page = doc.thisPageRef()
+        field = ButtonField(title, value, xmin, ymin, page)
+        self.fields.append(field)
+        canvas._addAnnotation(field)
+    def format(self, document):
+        from reportlab.pdfbase.pdfdoc import PDFArray
+        proxy = PDFPattern(FormPattern, Resources=GLOBALRESOURCES, fields=PDFArray(self.fields))
+        return proxy.format(document)
+
+FormPattern = [
+'<<', LINEEND,
+' /NeedAppearances true ', LINEEND,
+' /DA ', PDFString('/Helv 0 Tf 0 g '), LINEEND,
+' /DR ', LINEEND,
+["Resources"],
+' /Fields ', LINEEND,
+["fields"],
+'>>'
+]
+
+def FormFontsDictionary():
+    from reportlab.pdfbase.pdfdoc import PDFDictionary
+    fontsdictionary = PDFDictionary()
+    fontsdictionary.__RefOnly__ = 1
+    for (fullname, shortname) in FORMFONTNAMES.items():
+        fontsdictionary[shortname] = FormFont(fullname, shortname)
+    fontsdictionary["ZaDb"] = ZADB
+    return fontsdictionary
+
+def FormResources():
+    return PDFPattern(FormResourcesDictionaryPattern,
+                      Encoding=ENCODING, Font=GLOBALFONTSDICTIONARY)
+
+ZaDbPattern = [
+' <<'
+' /BaseFont'
+'    /ZapfDingbats'
+' /Name'
+'    /ZaDb'
+' /Subtype'
+'    /Type1'
+' /Type'
+'    /Font'
+'>>']
+
+ZADB = PDFPattern(ZaDbPattern)
+
+FormResourcesDictionaryPattern = [
+'<<',
+' /Encoding ',
+["Encoding"], LINEEND,
+' /Font ',
+["Font"], LINEEND,
+'>>'
+]
+
+FORMFONTNAMES = {
+    "Helvetica": "Helv",
+    "Helvetica-Bold": "HeBo",
+    'Courier': "Cour",
+    'Courier-Bold': "CoBo",
+    'Courier-Oblique': "CoOb",
+    'Courier-BoldOblique': "CoBO",
+    'Helvetica-Oblique': "HeOb",
+    'Helvetica-BoldOblique': "HeBO",
+    'Times-Roman': "Time",
+    'Times-Bold': "TiBo",
+    'Times-Italic': "TiIt",
+    'Times-BoldItalic': "TiBI",
+    }
+
+EncodingPattern = [
+'<<',
+' /PDFDocEncoding ',
+["PDFDocEncoding"], LINEEND,
+'>>',
+]
+
+PDFDocEncodingPattern = [
+'<<'
+' /Differences'
+'    ['
+' 24'
+' /breve'
+' /caron'
+' /circumflex'
+' /dotaccent'
+' /hungarumlaut'
+' /ogonek'
+' /ring'
+' /tilde'
+' 39'
+' /quotesingle'
+' 96'
+' /grave'
+' 128'
+' /bullet'
+' /dagger'
+' /daggerdbl'
+' /ellipsis'
+' /emdash'
+' /endash'
+' /florin'
+' /fraction'
+' /guilsinglleft'
+' /guilsinglright'
+' /minus'
+' /perthousand'
+' /quotedblbase'
+' /quotedblleft'
+' /quotedblright'
+' /quoteleft'
+' /quoteright'
+' /quotesinglbase'
+' /trademark'
+' /fi'
+' /fl'
+' /Lslash'
+' /OE'
+' /Scaron'
+' /Ydieresis'
+' /Zcaron'
+' /dotlessi'
+' /lslash'
+' /oe'
+' /scaron'
+' /zcaron'
+' 160'
+' /Euro'
+' 164'
+' /currency'
+' 166'
+' /brokenbar'
+' 168'
+' /dieresis'
+' /copyright'
+' /ordfeminine'
+' 172'
+' /logicalnot'
+' /.notdef'
+' /registered'
+' /macron'
+' /degree'
+' /plusminus'
+' /twosuperior'
+' /threesuperior'
+' /acute'
+' /mu'
+' 183'
+' /periodcentered'
+' /cedilla'
+' /onesuperior'
+' /ordmasculine'
+' 188'
+' /onequarter'
+' /onehalf'
+' /threequarters'
+' 192'
+' /Agrave'
+' /Aacute'
+' /Acircumflex'
+' /Atilde'
+' /Adieresis'
+' /Aring'
+' /AE'
+' /Ccedilla'
+' /Egrave'
+' /Eacute'
+' /Ecircumflex'
+' /Edieresis'
+' /Igrave'
+' /Iacute'
+' /Icircumflex'
+' /Idieresis'
+' /Eth'
+' /Ntilde'
+' /Ograve'
+' /Oacute'
+' /Ocircumflex'
+' /Otilde'
+' /Odieresis'
+' /multiply'
+' /Oslash'
+' /Ugrave'
+' /Uacute'
+' /Ucircumflex'
+' /Udieresis'
+' /Yacute'
+' /Thorn'
+' /germandbls'
+' /agrave'
+' /aacute'
+' /acircumflex'
+' /atilde'
+' /adieresis'
+' /aring'
+' /ae'
+' /ccedilla'
+' /egrave'
+' /eacute'
+' /ecircumflex'
+' /edieresis'
+' /igrave'
+' /iacute'
+' /icircumflex'
+' /idieresis'
+' /eth'
+' /ntilde'
+' /ograve'
+' /oacute'
+' /ocircumflex'
+' /otilde'
+' /odieresis'
+' /divide'
+' /oslash'
+' /ugrave'
+' /uacute'
+' /ucircumflex'
+' /udieresis'
+' /yacute'
+' /thorn'
+' /ydieresis'
+'    ]'
+' /Type'
+' /Encoding'
+'>>']
+
+# global constant
+PDFDOCENC = PDFPattern(PDFDocEncodingPattern)
+# global constant
+ENCODING = PDFPattern(EncodingPattern, PDFDocEncoding=PDFDOCENC)
+
+
+def FormFont(BaseFont, Name):
+    from reportlab.pdfbase.pdfdoc import PDFName
+    return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFDOCENC)
+
+FormFontPattern = [
+'<<',
+' /BaseFont ',
+["BaseFont"], LINEEND,
+' /Encoding ',
+["Encoding"], LINEEND,
+' /Name ',
+["Name"], LINEEND,
+' /Subtype '
+' /Type1 '
+' /Type '
+' /Font '
+'>>' ]
+
+# global constants
+GLOBALFONTSDICTIONARY = FormFontsDictionary()
+GLOBALRESOURCES = FormResources()
+
+
+def TextField(title, value, xmin, ymin, xmax, ymax, page,
+              maxlen=1000000, font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627, multiline=0):
+    from reportlab.pdfbase.pdfdoc import PDFString, PDFName
+    Flags = 0
+    if multiline:
+        Flags = Flags | (1<<12) # bit 13 is at position 12 :)
+    fontname = FORMFONTNAMES[font]
+    return PDFPattern(TextFieldPattern,
+                      value=PDFString(value), maxlen=maxlen, page=page,
+                      title=PDFString(title),
+                      xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
+                      fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B, Flags=Flags)
+
+
+TextFieldPattern = [
+'<<'
+' /DA'
+' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)'
+' /DV ',
+["value"], LINEEND,
+' /F 4 /FT /Tx'
+'/MK << /BC [ 0 0 0 ] >>'
+' /MaxLen ',
+["maxlen"], LINEEND,
+' /P ',
+["page"], LINEEND,
+' /Rect '
+'    [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]'
+'/Subtype /Widget'
+' /T ',
+["title"], LINEEND,
+' /Type'
+'    /Annot'
+' /V ',
+["value"], LINEEND,
+' /Ff ',
+["Flags"],LINEEND,
+'>>']
+
+def SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
+              font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627):
+    #print "ARGS", (title, value, options, xmin, ymin, xmax, ymax, page, font, fontsize, R, G, B)
+    from reportlab.pdfbase.pdfdoc import PDFString, PDFName, PDFArray
+    if value not in options:
+        raise ValueError, "value %s must be one of options %s" % (repr(value), repr(options))
+    fontname = FORMFONTNAMES[font]
+    optionstrings = map(PDFString, options)
+    optionarray = PDFArray(optionstrings)
+    return PDFPattern(SelectFieldPattern,
+                      Options=optionarray,
+                      Selected=PDFString(value), Page=page,
+                      Name=PDFString(title),
+                      xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
+                      fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B)
+
+SelectFieldPattern = [
+'<< % a select list',LINEEND,
+' /DA ',
+' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)',LINEEND,
+#'    (/Helv 12 Tf 0 g)',LINEEND,
+' /DV ',
+["Selected"],LINEEND,
+' /F ',
+'    4',LINEEND,
+' /FT ',
+'    /Ch',LINEEND,
+' /MK ',
+'    <<',
+'    /BC',
+'        [',
+'            0',
+'            0',
+'            0',
+'        ]',
+'    /BG',
+'        [',
+'            1',
+'            1',
+'            1',
+'        ]',
+'    >>',LINEEND,
+' /Opt ',
+["Options"],LINEEND,
+' /P ',
+["Page"],LINEEND,
+'/Rect',
+'    [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
+'    ] ',LINEEND,
+'/Subtype',
+'    /Widget',LINEEND,
+' /T ',
+["Name"],LINEEND,
+' /Type ',
+'    /Annot',
+' /V ',
+["Selected"],LINEEND,
+'>>']
+
+def ButtonField(title, value, xmin, ymin, page):
+    if value not in ("Yes", "Off"):
+        raise ValueError, "button value must be 'Yes' or 'Off': "+repr(value)
+    (dx, dy) = (16.77036, 14.90698)
+    return PDFPattern(ButtonFieldPattern,
+                      Name=PDFString(title),
+                      xmin=xmin, ymin=ymin, xmax=xmin+dx, ymax=ymin+dy,
+                      Hide=HIDE,
+                      APDOff=APDOFF,
+                      APDYes=APDYES,
+                      APNYes=APNYES,
+                      Value=PDFName(value),
+                      Page=page)
+
+ButtonFieldPattern = ['<< ',
+'/AA',
+'    <<',
+'    /D ',
+["Hide"], LINEEND,
+#'        %(imported.18.0)s',
+'    >> ',
+'/AP ',
+'    <<',
+'    /D',
+'        <<',
+'        /Off ',
+#'            %(imported.40.0)s',
+["APDOff"], LINEEND,
+'        /Yes ',
+#'            %(imported.39.0)s',
+["APDYes"], LINEEND,
+'        >>', LINEEND,
+'    /N',
+'        << ',
+'        /Yes ',
+#'            %(imported.38.0)s',
+["APNYes"],  LINEEND,
+'        >>',
+'    >>', LINEEND,
+' /AS ',
+["Value"], LINEEND,
+' /DA ',
+PDFString('/ZaDb 0 Tf 0 g'), LINEEND,
+'/DV ',
+["Value"], LINEEND,
+'/F ',
+'    4 ',
+'/FT ',
+'    /Btn ',
+'/H ',
+'    /T ',
+'/MK ',
+'    <<',
+'    /AC (\\376\\377)',
+#PDFString('\376\377'),
+'    /CA ',
+PDFString('4'),
+'    /RC ',
+PDFString('\376\377'),
+'    >> ',LINEEND,
+'/P ',
+["Page"], LINEEND,
+'/Rect',
+'    [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
+'    ] ',LINEEND,
+'/Subtype',
+'    /Widget ',
+'/T ',
+["Name"], LINEEND,
+'/Type',
+'    /Annot ',
+'/V ',
+["Value"], LINEEND,
+' >>']
+
+HIDE = PDFPattern([
+'<< '
+'/S '
+' /Hide '
+'>>'])
+
+def buttonStreamDictionary():
+    "everything except the length for the button appearance streams"
+    result = PDFDictionary()
+    result["SubType"] = "/Form"
+    result["BBox"] = "[0 0 16.77036 14.90698]"
+    font = PDFDictionary()
+    font["ZaDb"] = ZADB
+    resources = PDFDictionary()
+    resources["ProcSet"] = "[ /PDF /Text ]"
+    resources["Font"] = font
+    result["Resources"] = resources
+    return result
+
+def ButtonStream(content):
+    dict = buttonStreamDictionary()
+    result = PDFStream(dict, content)
+    result.filters = []
+    return result
+
+APDOFF = ButtonStream('0.749 g 0 0 16.7704 14.907 re f'+LINEEND)
+APDYES = ButtonStream( 
+'0.749 g 0 0 16.7704 14.907 re f q 1 1 14.7704 12.907 re W '+
+'n BT /ZaDb 11.3086 Tf 0 g  1 0 0 1 3.6017 3.3881 Tm (4) Tj ET'+LINEEND)
+APNYES = ButtonStream(
+'q 1 1 14.7704 12.907 re W n BT /ZaDb 11.3086 Tf 0 g  1 0 0 1 3.6017 3.3881 Tm (4) Tj ET Q'+LINEEND)
+
+#==== script interpretation
+
+if __name__=="__main__":
+    test1()
--- a/reportlab/pdfbase/pdfmetrics.py	Thu Jun 20 10:26:58 2002 +0000
+++ b/reportlab/pdfbase/pdfmetrics.py	Thu Jun 20 15:05:58 2002 +0000
@@ -2,7 +2,7 @@
 #see license.txt for license details
 #history http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/pdfbase/pdfmetrics.py?cvsroot=reportlab
 #$Header $
-__version__=''' $Id: pdfmetrics.py,v 1.53 2002/05/24 17:06:57 dinu_gherman Exp $ '''
+__version__=''' $Id: pdfmetrics.py,v 1.54 2002/06/20 15:05:58 aaron_watters Exp $ '''
 __doc__="""
 This provides a database of font metric information and
 efines Font, Encoding and TypeFace classes aimed at end users.
@@ -231,6 +231,7 @@
 
     def __getitem__(self, index):
         "Return glyph name for that code point, or None"
+        # THIS SHOULD BE INLINED FOR SPEED
         return self.vector[index]
 
     def __setitem__(self, index, value):
@@ -268,8 +269,8 @@
         ranges = []
         curRange = None
         for i in xrange(len(self.vector)):
-            glyph = self[i]
-            if glyph==otherEnc[i]:
+            glyph = self.vector[i]
+            if glyph==otherEnc.vector[i]:
                 if curRange:
                     ranges.append(curRange)
                     curRange = []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/reportlab/pdfbase/pdfpattern.py	Thu Jun 20 15:05:58 2002 +0000
@@ -0,0 +1,60 @@
+"""
+helper for importing pdf structures into a ReportLab generated document
+"""
+from reportlab.pdfbase.pdfdoc import format
+
+import string
+
+class PDFPattern:
+    __RefOnly__ = 1
+    def __init__(self, pattern_sequence, **keywordargs):
+        """
+        Description of a kind of PDF object using a pattern.
+        
+        Pattern sequence should contain strings or singletons of form [string].
+        Strings are literal strings to be used in the object.
+        Singletons are names of keyword arguments to include.
+        Keyword arguments can be non-instances which are substituted directly in string conversion,
+        or they can be object instances in which case they should be pdfdoc.* style
+        objects with a x.format(doc) method.
+        Keyword arguments may be set on initialization or subsequently using __setitem__, before format.
+        "constant object" instances can also be inserted in the patterns.
+        """
+        self.pattern = pattern_sequence
+        self.arguments = keywordargs
+        from types import StringType, InstanceType
+        toptypes = (StringType, InstanceType)
+        for x in pattern_sequence:
+            if type(x) not in toptypes:
+                if len(x)!=1:
+                    raise ValueError, "sequence elts must be strings or singletons containing strings: "+repr(x)
+                if type(x[0]) is not StringType:
+                    raise ValueError, "Singletons must contain strings or instances only: "+repr(x[0])
+    def __setitem__(self, item, value):
+        self.arguments[item] = value
+    def __getitem__(self, item):
+        return self.arguments[item]
+    def format(self, document):
+        L = []
+        arguments = self.arguments
+        from types import StringType, InstanceType
+        for x in self.pattern:
+            tx = type(x)
+            if tx is StringType:
+                L.append(x)
+            elif tx is InstanceType:
+                L.append( x.format(document) )
+            else:
+                name = x[0]
+                value = arguments.get(name, None)
+                if value is None:
+                    raise ValueError, "%s value not defined" % repr(name)
+                if type(value) is InstanceType:
+                    #L.append( value.format(document) )
+                    L.append(format(value, document))
+                else:
+                    L.append( str(value) )
+        return string.join(L, "")
+
+    
+                
\ No newline at end of file