pvl.web.html: support arbitrary contents for Text
authorTero Marttila <terom@paivola.fi>
Mon, 21 Jan 2013 18:39:23 +0200
changeset 162 80c91d019a13
parent 161 cc10eb9d0055
child 163 96d551b90734
pvl.web.html: support arbitrary contents for Text
pvl/web/html.py
--- a/pvl/web/html.py	Mon Jan 21 18:13:06 2013 +0200
+++ b/pvl/web/html.py	Mon Jan 21 18:39:23 2013 +0200
@@ -6,6 +6,8 @@
     u'<a href="http://www.google.com">\\n\\tGoogle &lt;this&gt;!\\n</a>'
 """
 
+# XXX: needs some refactoring for Text vs Tag now
+
 import itertools as itertools
 import types as types
 from xml.sax import saxutils
@@ -15,6 +17,75 @@
         Structured data that's flattened into indented lines of text.
     """
 
+    # types of nested items to flatten
+    CONTAINER_TYPES = (types.TupleType, types.ListType, types.GeneratorType)
+
+    @classmethod
+    def process_contents (cls, *args) :
+        """
+            Yield the HTML tag's contents from the given sequence of positional arguments as a series of flattened
+            items, eagerly converting them to unicode.
+
+            If no arguments are given, we don't have any children:
+            
+            >>> bool(list(Tag.process_contents()))
+            False
+            
+            Items that are None will be ignored:
+
+            >>> list(Tag.process_contents(None))
+            []
+
+            Various Python container types are recursively flattened:
+
+            >>> list(Tag.process_contents([1, 2]))
+            [u'1', u'2']
+            >>> list(Tag.process_contents([1], [2]))
+            [u'1', u'2']
+            >>> list(Tag.process_contents([1, [2]]))
+            [u'1', u'2']
+            >>> list(Tag.process_contents(n + 1 for n in xrange(2)))
+            [u'1', u'2']
+            >>> list(Tag.process_contents((1, 2)))
+            [u'1', u'2']
+            >>> list(Tag.process_contents((1), (2, )))
+            [u'1', u'2']
+
+            Our own HTML-aware objects are returned as-is:
+            
+            >>> list(Tag.process_contents(Tag.build('foo')))
+            [tag('foo')]
+            >>> list(Tag.process_contents(Text(u'bar')))
+            [Text(u'bar')]
+            
+            All other objects are converted to unicode:
+            
+            >>> list(Tag.process_contents('foo', u'bar', 0.123, False))
+            [u'foo', u'bar', u'0.123', u'False']
+
+        """
+
+        for arg in args :
+            if arg is None :
+                # skip null: None
+                continue
+            
+            elif isinstance(arg, cls.CONTAINER_TYPES) :
+                # flatten nested container: tuple/list/generator
+                for node in arg :
+                    # recurse
+                    for item in cls.process_contents(node) :
+                        yield item
+
+            elif isinstance(arg, Renderable) :
+                # yield item: Renderable
+                yield arg
+
+            else :
+                # as unicode
+                yield unicode(arg)
+
+
     def flatten (self) :
         """
             Flatten this object into a series of (identlevel, line) tuples.
@@ -60,99 +131,42 @@
     """
         Plain un-structured/un-processed HTML text for output
         
-        >>> Text('foo')
+        >>> Text(u'foo')
         Text(u'foo')
         >>> list(Text('<foo>'))
         [u'<foo>']
+        >>> list(Text('<foo>', tag('p', 'test')))
+        [u'<foo>', u'<p>', u'\\ttest', u'</p>']
         >>> list(tag('a', Text('<foo>')))
         [u'<a>', u'\\t<foo>', u'</a>']
+        >>> list(Text(range(2)))
+        [u'0', u'1']
+
     """
 
-    def __init__ (self, text) :
-        self.text = unicode(text)
+    def __init__ (self, *contents) :
+        self.contents = self.process_contents(*contents)
     
-    def flatten (self) :
-        yield (0, self.text)
+    def flatten (self, indent=0) :
+        for item in self.contents :
+            if isinstance(item, Renderable) :
+                # recursively flatten items
+                for line_indent, line in item.flatten() :
+                    # indented
+                    yield indent + line_indent, line
 
+            else :
+                # render raw value
+                yield indent, unicode(item)
+ 
     def __repr__ (self) :
-        return "Text(%r)" % (self.text, )
+        return "Text(%s)" % (', '.join(repr(item) for item in self.contents))
 
 class Tag (Renderable) :
     """
         An immutable HTML tag structure, with the tag's name, attributes and contents.
     """
     
-    # types of nested items to flatten
-    CONTAINER_TYPES = (types.TupleType, types.ListType, types.GeneratorType)
-
-    # types of items to keep as-is
-    ITEM_TYPES = (Renderable, )
-
-    @classmethod
-    def process_contents (cls, *args) :
-        """
-            Yield the HTML tag's contents from the given sequence of positional arguments as a series of flattened
-            items, eagerly converting them to unicode.
-
-            If no arguments are given, we don't have any children:
-            
-            >>> bool(list(Tag.process_contents()))
-            False
-            
-            Items that are None will be ignored:
-
-            >>> list(Tag.process_contents(None))
-            []
-
-            Various Python container types are recursively flattened:
-
-            >>> list(Tag.process_contents([1, 2]))
-            [u'1', u'2']
-            >>> list(Tag.process_contents([1], [2]))
-            [u'1', u'2']
-            >>> list(Tag.process_contents([1, [2]]))
-            [u'1', u'2']
-            >>> list(Tag.process_contents(n + 1 for n in xrange(2)))
-            [u'1', u'2']
-            >>> list(Tag.process_contents((1, 2)))
-            [u'1', u'2']
-            >>> list(Tag.process_contents((1), (2, )))
-            [u'1', u'2']
-
-            Our own HTML-aware objects are returned as-is:
-            
-            >>> list(Tag.process_contents(Tag.build('foo')))
-            [tag('foo')]
-            >>> list(Tag.process_contents(Text('bar')))
-            [Text(u'bar')]
-            
-            All other objects are converted to unicode:
-            
-            >>> list(Tag.process_contents('foo', u'bar', 0.123, False))
-            [u'foo', u'bar', u'0.123', u'False']
-
-        """
-
-        for arg in args :
-            if arg is None :
-                # skip null: None
-                continue
-            
-            elif isinstance(arg, cls.CONTAINER_TYPES) :
-                # flatten nested container: tuple/list/generator
-                for node in arg :
-                    # recurse
-                    for item in cls.process_contents(node) :
-                        yield item
-
-            elif isinstance(arg, cls.ITEM_TYPES) :
-                # yield item: Renderable
-                yield arg
-
-            else :
-                # as unicode
-                yield unicode(arg)
-
     @classmethod
     def process_attrs (cls, **kwargs) :
         """
@@ -275,7 +289,7 @@
 
         return cls(_name, contents, attrs, **options)
 
-    def __init__ (self, name, contents, attrs, selfclosing=None, whitespace_sensitive=None) :
+    def __init__ (self, name, contents, attrs, selfclosing=None, whitespace_sensitive=None, escape=True) :
         """
             Initialize internal Tag state with the given tag identifier, flattened list of content items, dict of
             attributes and dict of options.
@@ -286,6 +300,8 @@
                 whitespace_sensitive    - do not indent tag content onto separate rows, render the full tag as a single
                                           row
 
+                escape                  - html-escape non-Renderable's (text)
+
             Use the build() factory function to build Tag objects using Python's function call argument semantics.
             
             The tag name is used a pure string identifier:
@@ -322,6 +338,7 @@
         # options
         self.selfclosing = selfclosing
         self.whitespace_sensitive = whitespace_sensitive
+        self.escape = escape
 
     def __call__ (self, *args, **kwargs) :
         """
@@ -416,10 +433,14 @@
                     # indented
                     yield indent + line_indent, line
 
-            else :
+            elif self.escape :
                 # render HTML-escaped raw value
                 # escape raw values
                 yield indent, saxutils.escape(item)
+
+            else :
+                # render raw value
+                yield indent, unicode(item)
    
     def flatten (self) :
         """
@@ -589,12 +610,12 @@
 
         return Tag(name, [], {})
 
-    def __call__ (self, value) :
+    def __call__ (self, *values) :
         """
             Raw HTML.
         """
 
-        return Text(value)
+        return Text(*values)
 
 # static instance
 tags = TagFactory()