degal/html.py
changeset 61 fad360dd01da
parent 53 14d73f544764
child 69 5b53fe294034
equal deleted inserted replaced
60:406da27a4be2 61:fad360dd01da
     1 """
     1 """
     2     Generating HTML tags
     2     Generating XHTML output
       
     3 
       
     4     XXX: use a 'real' XML builder?
     3 """
     5 """
     4 
     6 
     5 from cgi import escape
     7 from cgi import escape
     6 import itertools as _itertools
     8 import itertools as _itertools
     7 
     9 
    15             Render the indented lines for tag and contents, without newlines
    17             Render the indented lines for tag and contents, without newlines
    16         """
    18         """
    17 
    19 
    18         abstract
    20         abstract
    19 
    21 
       
    22     def render_lines (self, indent=u'\t', newline=u'\n') :
       
    23         """
       
    24             Render full output lines with given newlines
       
    25 
       
    26             >>> list(Tag('xx', 'yy').render_lines())
       
    27             [u'<xx>\\n', u'\\tyy\\n', u'</xx>\\n']
       
    28         """
       
    29 
       
    30         for line in self.render_raw_lines(indent) :
       
    31             yield line + newline
       
    32 
       
    33     def render_unicode (self, **render_opts) :
       
    34         """
       
    35             Render full tag as a single unicode string
       
    36 
       
    37             >>> Tag('xx', 'yy').render_unicode()
       
    38             u'<xx>\\n\\tyy\\n</xx>\\n'
       
    39         """
       
    40 
       
    41         return "".join(self.render_lines(**render_opts))
       
    42 
       
    43     def render_str (self, encoding='ascii', **render_opts) :
       
    44         """
       
    45             Render full tag as an encoded string
       
    46 
       
    47             >>> Tag('xx', 'yy').render_str()
       
    48             '<xx>\\n\\tyy\\n</xx>\\n'
       
    49         """
       
    50 
       
    51         return self.render_unicode(**render_opts).encode(encoding)
       
    52 
       
    53     def render_out (self, stream, encoding, **render_opts) :
       
    54         """
       
    55             Render output into the given stream, encoding using the given encoding
       
    56 
       
    57             >>> from StringIO import StringIO; buf = StringIO(); Tag('xx', 'yy').render_out(buf, 'ascii'); buf.getvalue()
       
    58             '<xx>\\n\\tyy\\n</xx>\\n'
       
    59         """
       
    60 
       
    61         for line in self.render_lines(**render_opts) :
       
    62             stream.write(line.encode(encoding))
       
    63     
       
    64     # default output
       
    65     __str__ = render_str
       
    66     __unicode__ = render_unicode
       
    67 
       
    68     # default .render method
       
    69     render = render_unicode
       
    70 
    20 class Tag (IRenderable) :
    71 class Tag (IRenderable) :
    21     """
    72     """
    22         A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
    73         A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
    23 
    74 
    24         Provides various kinds of rendering output
    75         Provides various kinds of rendering output
    28     def process_contents (contents) :
    79     def process_contents (contents) :
    29         """
    80         """
    30             Postprocess contents iterable to return new list.
    81             Postprocess contents iterable to return new list.
    31 
    82 
    32             Items that are None will be omitted from the return value.
    83             Items that are None will be omitted from the return value.
       
    84 
       
    85             XXX: flatten
    33 
    86 
    34             >>> Tag.process_contents([])
    87             >>> Tag.process_contents([])
    35             []
    88             []
    36             >>> Tag.process_contents([None])
    89             >>> Tag.process_contents([None])
    37             []
    90             []
   177 
   230 
   178         else :
   231         else :
   179             # singleton tag
   232             # singleton tag
   180             yield u"<%s%s />" % (self.name, attrs_stuff)
   233             yield u"<%s%s />" % (self.name, attrs_stuff)
   181     
   234     
   182     def render_lines (self, indent=u'\t', newline=u'\n') :
       
   183         """
       
   184             Render full output lines with given newlines
       
   185 
       
   186             >>> list(Tag('xx', 'yy').render_lines())
       
   187             [u'<xx>\\n', u'\\tyy\\n', u'</xx>\\n']
       
   188         """
       
   189 
       
   190         for line in self.render_raw_lines(indent) :
       
   191             yield line + newline
       
   192 
       
   193     def render_unicode (self, **render_opts) :
       
   194         """
       
   195             Render full tag as a single unicode string
       
   196 
       
   197             >>> Tag('xx', 'yy').render_unicode()
       
   198             u'<xx>\\n\\tyy\\n</xx>\\n'
       
   199         """
       
   200 
       
   201         return "".join(self.render_lines(**render_opts))
       
   202 
       
   203     def render_str (self, charset='ascii', **render_opts) :
       
   204         """
       
   205             Render full tag as an encoded string
       
   206 
       
   207             >>> Tag('xx', 'yy').render_str()
       
   208             '<xx>\\n\\tyy\\n</xx>\\n'
       
   209         """
       
   210 
       
   211         return self.render_unicode(**render_opts).encode(charset)
       
   212 
       
   213     def render_out (self, stream, charset, **render_opts) :
       
   214         """
       
   215             Render output into the given stream, encoding using the given charset
       
   216 
       
   217             >>> from StringIO import StringIO; buf = StringIO(); Tag('xx', 'yy').render_out(buf, 'ascii'); buf.getvalue()
       
   218             '<xx>\\n\\tyy\\n</xx>\\n'
       
   219         """
       
   220 
       
   221         for line in self.render_lines(**render_opts) :
       
   222             stream.write(line.encode(charset))
       
   223     
       
   224     # default output
       
   225     __str__ = render_str
       
   226     __unicode__ = render_unicode
       
   227 
       
   228     # default .render method
       
   229     render = render_unicode
       
   230 
   235 
   231     def __repr__ (self) :
   236     def __repr__ (self) :
   232         return 'Tag(%s)' % ', '.join(
   237         return 'Tag(%s)' % ', '.join(
   233             [
   238             [
   234                 repr(self.name)
   239                 repr(self.name)
   252         self.lines = [line]
   257         self.lines = [line]
   253     
   258     
   254     def render_raw_lines (self, indent=u'\t') :
   259     def render_raw_lines (self, indent=u'\t') :
   255         return self.lines
   260         return self.lines
   256 
   261 
       
   262 class XHTMLDocument (IRenderable) :
       
   263     """
       
   264         <?xml version="..." encoding="..." ?>
       
   265         <!DOCTYPE ...>
       
   266         
       
   267         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
       
   268             <head>
       
   269                 ...
       
   270             </head>
       
   271             <body>
       
   272                 ...
       
   273             </body>
       
   274         </html>
       
   275     """
       
   276 
       
   277     def __init__ (self, 
       
   278         head, body,
       
   279         xml_version='1.0', xml_encoding='utf-8', 
       
   280         doctype='html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"',
       
   281         html_xmlns='http://www.w3.org/1999/xhtml', html_lang='en'
       
   282     ) :
       
   283         # store
       
   284         self.xml_version = xml_version
       
   285         self.xml_encoding = xml_encoding
       
   286         self.doctype = doctype
       
   287         
       
   288         # build the document
       
   289         self.document = Tag('html', **{'xmlns': html_xmlns, 'xml:lang': html_lang})(
       
   290             Tag('head', head),
       
   291             Tag('body', body),
       
   292         )
       
   293 
       
   294     def render_raw_lines (self, **render_opts) :
       
   295         """
       
   296             Render the two header lines, and then the document
       
   297         """
       
   298 
       
   299         yield '<?xml version="%s" encoding="%s" ?>' % (self.xml_version, self.xml_encoding)
       
   300         yield '<!DOCTYPE %s>' % (self.doctype)
       
   301 
       
   302         for line in self.document.render_raw_lines(**render_opts) :
       
   303             yield line
       
   304     
       
   305     def _check_encoding (self, encoding) :
       
   306         if encoding and encoding != self.xml_encoding :
       
   307             raise ValueError("encoding mismatch: %r should be %r" % (encoding, self.xml_encoding)
       
   308 
       
   309     def render_str (self, encoding=None, **render_opts) :
       
   310         """
       
   311             Wrap render_str to verify that the right encoding is used
       
   312         """
       
   313 
       
   314         self._check_encoding(encoding)
       
   315         
       
   316         return super(XHTMLDocument, self).render_str(self.xml_encoding, **render_opts)
       
   317 
       
   318     def render_out (self, stream, encoding=None, **render_opts) :
       
   319         """
       
   320             Wrap render_out to verify that the right encoding is used
       
   321         """
       
   322 
       
   323         self._check_encoding(encoding)
       
   324         
       
   325         return super(XHTMLDocument, self).render_out(stream, self.xml_encoding, **render_opts)
       
   326 
   257 class TagFactory (object) :
   327 class TagFactory (object) :
   258     """
   328     """
   259         Build Tags with names give as attribute names
   329         Build Tags with names give as attribute names
   260     """
   330     """
   261 
   331