# HG changeset patch # User Tero Marttila # Date 1358786363 -7200 # Node ID 80c91d019a139505a19075ed78e3762a75ce0a24 # Parent cc10eb9d0055110579134c7a59dfcc3dbee090dd pvl.web.html: support arbitrary contents for Text diff -r cc10eb9d0055 -r 80c91d019a13 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'\\n\\tGoogle <this>!\\n' """ +# 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('')) [u''] + >>> list(Text('', tag('p', 'test'))) + [u'', u'

', u'\\ttest', u'

'] >>> list(tag('a', Text(''))) [u'', u'\\t', u''] + >>> 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()