degal/html.py
changeset 78 d580323b4bfa
parent 77 2a53c5ade434
child 80 f4b637ae775c
--- 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