--- a/degal/html.py Fri Jun 05 23:59:14 2009 +0300
+++ b/degal/html.py Wed Jun 10 21:59:49 2009 +0300
@@ -78,7 +78,70 @@
# default .render method
render = render_unicode
-class Tag (IRenderable) :
+class Container (IRenderable) :
+ """
+ A container holds a sequence of other renderable items.
+ """
+
+ @classmethod
+ def process_contents (cls, contents) :
+ """
+ Postprocess contents iterable to return new list.
+
+ Items that are None will be omitted from the return value.
+
+ >>> list(Container.process_contents([]))
+ []
+ >>> list(Container.process_contents([None]))
+ []
+ >>> list(Container.process_contents([u'foo']))
+ [u'foo']
+ """
+
+ for content in contents :
+ if content is None :
+ continue
+
+ else :
+ # normal, handle as unicode data
+ yield content
+
+ def __init__ (self, *contents) :
+ """
+ Construct this container with the given sub-items
+ """
+
+ # store postprocessed
+ self.contents = list(self.process_contents(contents))
+
+ def render_raw_lines (self, **render_opts) :
+ """
+ Render our contents as a series of non-indented lines, with the contents handling indentation themselves.
+
+ >>> list(Container(5).render_raw_lines())
+ [u'5']
+ >>> list(Container('line1', 'line2').render_raw_lines())
+ [u'line1', u'line2']
+ >>> list(Container('a', Tag('b', 'bb'), 'c').render_raw_lines())
+ [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
+ >>> list(Container(Tag('hr'), Tag('foo')('bar')).render_raw_lines())
+ [u'<hr />', u'<foo>', u'\\tbar', u'</foo>']
+ """
+
+ for content in self.contents :
+ if isinstance(content, IRenderable) :
+ # sub-items
+ for line in content.render_raw_lines(**render_opts) :
+ yield line
+
+ else :
+ # escape raw values
+ yield escape(unicode(content))
+
+ def __repr__ (self) :
+ return 'Container(%s)' % ', '.join(repr(c) for c in self.contents)
+
+class Tag (Container) :
"""
A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
@@ -86,25 +149,6 @@
"""
@staticmethod
- def process_contents (contents) :
- """
- Postprocess contents iterable to return new list.
-
- Items that are None will be omitted from the return value.
-
- XXX: flatten
-
- >>> Tag.process_contents([])
- []
- >>> Tag.process_contents([None])
- []
- >>> Tag.process_contents([u'foo'])
- [u'foo']
- """
-
- return [c for c in contents if c is not None]
-
- @staticmethod
def process_attrs (attrs) :
"""
Postprocess attributes.
@@ -113,15 +157,15 @@
TODO: only remove one underscore
- >>> Tag.process_attrs(dict())
+ >>> dict(Tag.process_attrs(dict()))
{}
- >>> Tag.process_attrs(dict(foo='bar'))
+ >>> dict(Tag.process_attrs(dict(foo='bar')))
{'foo': 'bar'}
- >>> Tag.process_attrs(dict(class_='bar', frob=None))
+ >>> dict(Tag.process_attrs(dict(class_='bar', frob=None)))
{'class': 'bar'}
"""
- return dict((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
+ return ((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
def __init__ (self, name, *contents, **attrs) :
"""
@@ -138,12 +182,13 @@
>>> Tag('foo', class_='ten')
Tag('foo', class='ten')
"""
-
+
+ # store contents as container
+ super(Tag, self).__init__(*contents)
+
+ # store postprocessed stuff
self.name = name
-
- # store filtered/processed versions
- self.contents = self.process_contents(contents)
- self.attrs = self.process_attrs(attrs)
+ self.attrs = dict(self.process_attrs(attrs))
def __call__ (self, *contents, **attrs) :
"""
@@ -196,28 +241,6 @@
return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems())
- def render_contents (self, **render_opts) :
- """
- Render the contents of the tag as a series of indented lines, with given render_lines options for subtags
-
- >>> list(Tag('x', 5).render_contents())
- [u'5']
- >>> list(Tag('x', 'line1', 'line2').render_contents())
- [u'line1', u'line2']
- >>> list(Tag('x', 'a', Tag('b', 'bb'), 'c').render_contents())
- [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
- """
-
- for content in self.contents :
- if isinstance(content, IRenderable) :
- # sub-tags
- for line in content.render_raw_lines(**render_opts) :
- yield line
-
- else :
- # escape raw values
- yield escape(unicode(content))
-
def render_raw_lines (self, indent=u'\t') :
"""
Render the tag and indented content
@@ -230,10 +253,11 @@
attrs_stuff = (" " + self.render_attrs()) if self.attrs else ""
if self.contents :
- # tag with content
+ # wrapping tags
yield u"<%s%s>" % (self.name, attrs_stuff)
-
- for line in self.render_contents(indent=indent) :
+
+ # subcontents
+ for line in super(Tag, self).render_raw_lines(indent=indent) :
yield indent + line
yield u"</%s>" % (self.name, )
@@ -271,6 +295,10 @@
class XHTMLDocument (IRenderable) :
"""
+ A full XHTML document with XML header, doctype, head and body.
+
+ XXX: current rendering is a bit of a kludge
+
<?xml version="..." encoding="..." ?>
<!DOCTYPE ...>
@@ -350,6 +378,7 @@
return Tag(name)
# pretty names
+container = Container
tag = Tag
tags = TagFactory()
raw = Text