degal/html.py
changeset 78 d580323b4bfa
parent 77 2a53c5ade434
child 80 f4b637ae775c
equal deleted inserted replaced
77:2a53c5ade434 78:d580323b4bfa
    76     __unicode__ = render_unicode
    76     __unicode__ = render_unicode
    77 
    77 
    78     # default .render method
    78     # default .render method
    79     render = render_unicode
    79     render = render_unicode
    80 
    80 
    81 class Tag (IRenderable) :
    81 class Container (IRenderable) :
       
    82     """
       
    83         A container holds a sequence of other renderable items.
       
    84     """
       
    85 
       
    86     @classmethod
       
    87     def process_contents (cls, contents) :
       
    88         """
       
    89             Postprocess contents iterable to return new list.
       
    90 
       
    91             Items that are None will be omitted from the return value.
       
    92 
       
    93             >>> list(Container.process_contents([]))
       
    94             []
       
    95             >>> list(Container.process_contents([None]))
       
    96             []
       
    97             >>> list(Container.process_contents([u'foo']))
       
    98             [u'foo']
       
    99         """
       
   100         
       
   101         for content in contents :
       
   102             if content is None :
       
   103                 continue
       
   104                         
       
   105             else :
       
   106                 # normal, handle as unicode data
       
   107                 yield content
       
   108 
       
   109     def __init__ (self, *contents) :
       
   110         """
       
   111             Construct this container with the given sub-items
       
   112         """
       
   113         
       
   114         # store postprocessed
       
   115         self.contents = list(self.process_contents(contents))
       
   116     
       
   117     def render_raw_lines (self, **render_opts) :
       
   118         """
       
   119             Render our contents as a series of non-indented lines, with the contents handling indentation themselves.
       
   120 
       
   121             >>> list(Container(5).render_raw_lines())
       
   122             [u'5']
       
   123             >>> list(Container('line1', 'line2').render_raw_lines())
       
   124             [u'line1', u'line2']
       
   125             >>> list(Container('a', Tag('b', 'bb'), 'c').render_raw_lines())
       
   126             [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
       
   127             >>> list(Container(Tag('hr'), Tag('foo')('bar')).render_raw_lines())
       
   128             [u'<hr />', u'<foo>', u'\\tbar', u'</foo>']
       
   129         """
       
   130 
       
   131         for content in self.contents :
       
   132             if isinstance(content, IRenderable) :
       
   133                 # sub-items
       
   134                 for line in content.render_raw_lines(**render_opts) :
       
   135                     yield line
       
   136             
       
   137             else :
       
   138                 # escape raw values
       
   139                 yield escape(unicode(content))
       
   140 
       
   141     def __repr__ (self) :
       
   142         return 'Container(%s)' % ', '.join(repr(c) for c in self.contents)
       
   143 
       
   144 class Tag (Container) :
    82     """
   145     """
    83         A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
   146         A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
    84 
   147 
    85         Provides various kinds of rendering output
   148         Provides various kinds of rendering output
    86     """
   149     """
    87 
       
    88     @staticmethod
       
    89     def process_contents (contents) :
       
    90         """
       
    91             Postprocess contents iterable to return new list.
       
    92 
       
    93             Items that are None will be omitted from the return value.
       
    94 
       
    95             XXX: flatten
       
    96 
       
    97             >>> Tag.process_contents([])
       
    98             []
       
    99             >>> Tag.process_contents([None])
       
   100             []
       
   101             >>> Tag.process_contents([u'foo'])
       
   102             [u'foo']
       
   103         """
       
   104 
       
   105         return [c for c in contents if c is not None]
       
   106 
   150 
   107     @staticmethod
   151     @staticmethod
   108     def process_attrs (attrs) :
   152     def process_attrs (attrs) :
   109         """
   153         """
   110             Postprocess attributes.
   154             Postprocess attributes.
   111 
   155 
   112             Key-value pairs where the value is None will be ommitted, and any trailing underscores in the key omitted.
   156             Key-value pairs where the value is None will be ommitted, and any trailing underscores in the key omitted.
   113 
   157 
   114             TODO: only remove one underscore
   158             TODO: only remove one underscore
   115 
   159 
   116             >>> Tag.process_attrs(dict())
   160             >>> dict(Tag.process_attrs(dict()))
   117             {}
   161             {}
   118             >>> Tag.process_attrs(dict(foo='bar'))
   162             >>> dict(Tag.process_attrs(dict(foo='bar')))
   119             {'foo': 'bar'}
   163             {'foo': 'bar'}
   120             >>> Tag.process_attrs(dict(class_='bar', frob=None))
   164             >>> dict(Tag.process_attrs(dict(class_='bar', frob=None)))
   121             {'class': 'bar'}
   165             {'class': 'bar'}
   122         """
   166         """
   123 
   167 
   124         return dict((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
   168         return ((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
   125 
   169 
   126     def __init__ (self, name, *contents, **attrs) :
   170     def __init__ (self, name, *contents, **attrs) :
   127         """
   171         """
   128             Construct tag with given name/attributes or contents.
   172             Construct tag with given name/attributes or contents.
   129 
   173 
   136             >>> Tag('foo', 'quux', bar=5)
   180             >>> Tag('foo', 'quux', bar=5)
   137             Tag('foo', 'quux', bar=5)
   181             Tag('foo', 'quux', bar=5)
   138             >>> Tag('foo', class_='ten')
   182             >>> Tag('foo', class_='ten')
   139             Tag('foo', class='ten')
   183             Tag('foo', class='ten')
   140         """
   184         """
   141 
   185         
       
   186         # store contents as container
       
   187         super(Tag, self).__init__(*contents)
       
   188         
       
   189         # store postprocessed stuff
   142         self.name = name
   190         self.name = name
   143 
   191         self.attrs = dict(self.process_attrs(attrs))
   144         # store filtered/processed versions
       
   145         self.contents = self.process_contents(contents)
       
   146         self.attrs = self.process_attrs(attrs)
       
   147 
   192 
   148     def __call__ (self, *contents, **attrs) :
   193     def __call__ (self, *contents, **attrs) :
   149         """
   194         """
   150             Return a new Tag with this tag's attributes and contents, as well as the given attributes/contents.
   195             Return a new Tag with this tag's attributes and contents, as well as the given attributes/contents.
   151 
   196 
   194             u'foo="5" bar="&lt;"'
   239             u'foo="5" bar="&lt;"'
   195         """
   240         """
   196 
   241 
   197         return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems())
   242         return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems())
   198 
   243 
   199     def render_contents (self, **render_opts) :
       
   200         """
       
   201             Render the contents of the tag as a series of indented lines, with given render_lines options for subtags
       
   202 
       
   203             >>> list(Tag('x', 5).render_contents())
       
   204             [u'5']
       
   205             >>> list(Tag('x', 'line1', 'line2').render_contents())
       
   206             [u'line1', u'line2']
       
   207             >>> list(Tag('x', 'a', Tag('b', 'bb'), 'c').render_contents())
       
   208             [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
       
   209         """
       
   210 
       
   211         for content in self.contents :
       
   212             if isinstance(content, IRenderable) :
       
   213                 # sub-tags
       
   214                 for line in content.render_raw_lines(**render_opts) :
       
   215                     yield line
       
   216             
       
   217             else :
       
   218                 # escape raw values
       
   219                 yield escape(unicode(content))
       
   220 
       
   221     def render_raw_lines (self, indent=u'\t') :
   244     def render_raw_lines (self, indent=u'\t') :
   222         """
   245         """
   223             Render the tag and indented content
   246             Render the tag and indented content
   224 
   247 
   225             >>> list(Tag('xx', 'yy', zz='foo').render_raw_lines(indent=' '))
   248             >>> list(Tag('xx', 'yy', zz='foo').render_raw_lines(indent=' '))
   228 
   251 
   229         # render attr string, including preceding space
   252         # render attr string, including preceding space
   230         attrs_stuff = (" " + self.render_attrs()) if self.attrs else ""
   253         attrs_stuff = (" " + self.render_attrs()) if self.attrs else ""
   231 
   254 
   232         if self.contents :
   255         if self.contents :
   233             # tag with content
   256             # wrapping tags
   234             yield u"<%s%s>" % (self.name, attrs_stuff)
   257             yield u"<%s%s>" % (self.name, attrs_stuff)
   235 
   258             
   236             for line in self.render_contents(indent=indent) :
   259             # subcontents
       
   260             for line in super(Tag, self).render_raw_lines(indent=indent) :
   237                 yield indent + line
   261                 yield indent + line
   238 
   262 
   239             yield u"</%s>" % (self.name, )
   263             yield u"</%s>" % (self.name, )
   240 
   264 
   241         else :
   265         else :
   269     def render_raw_lines (self, indent=u'\t') :
   293     def render_raw_lines (self, indent=u'\t') :
   270         return self.lines
   294         return self.lines
   271 
   295 
   272 class XHTMLDocument (IRenderable) :
   296 class XHTMLDocument (IRenderable) :
   273     """
   297     """
       
   298         A full XHTML document with XML header, doctype, head and body.
       
   299 
       
   300         XXX: current rendering is a bit of a kludge
       
   301 
   274         <?xml version="..." encoding="..." ?>
   302         <?xml version="..." encoding="..." ?>
   275         <!DOCTYPE ...>
   303         <!DOCTYPE ...>
   276         
   304         
   277         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   305         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   278             <head>
   306             <head>
   348         """
   376         """
   349 
   377 
   350         return Tag(name)
   378         return Tag(name)
   351 
   379 
   352 # pretty names
   380 # pretty names
       
   381 container = Container
   353 tag = Tag
   382 tag = Tag
   354 tags = TagFactory()
   383 tags = TagFactory()
   355 raw = Text
   384 raw = Text
   356 
   385 
   357 # testing
   386 # testing