0001"""
0002Kind of like htmlgen, only much simpler. The only important symbol
0003that is exported is ``html``.
0004
0005This builds ElementTree nodes, but with some extra useful methods.
0006(Open issue: should it use ``ElementTree`` more, and the raw
0007``Element`` stuff less?)
0008
0009You create tags with attribute access. I.e., the ``A`` anchor tag is
0010``html.a``. The attributes of the HTML tag are done with keyword
0011arguments. The contents of the tag are the non-keyword arguments
0012(concatenated). You can also use the special ``c`` keyword, passing a
0013list, tuple, or single tag, and it will make up the contents (this is
0014useful because keywords have to come after all non-keyword arguments,
0015which is non-intuitive). Or you can chain them, adding the keywords
0016with one call, then the body with a second call, like::
0017
0018 >>> print html.a(href='http://yahoo.com')('<Yahoo>')
0019 <a href=\"http://yahoo.com\"><Yahoo></a>
0020
0021Note that strings will be quoted; only tags given explicitly will
0022remain unquoted.
0023
0024If the value of an attribute is None, then no attribute
0025will be inserted. So::
0026
0027 >>> print html.a(href='http://www.yahoo.com', name=None,
0028 ... c='Click Here')
0029 <a href=\"http://www.yahoo.com\">Click Here</a>
0030
0031If the value is None, then the empty string is used. Otherwise str()
0032is called on the value.
0033
0034``html`` can also be called, and it will produce a special list from
0035its arguments, which adds a ``__str__`` method that does ``html.str``
0036(which handles quoting, flattening these lists recursively, and using
0037'' for ``None``).
0038
0039``html.comment`` will generate an HTML comment, like
0040``html.comment('comment text')`` -- note that it cannot take keyword
0041arguments (because they wouldn't mean anything).
0042
0043Examples::
0044
0045 >>> print html.html(
0046 ... html.head(html.title(\"Page Title\")),
0047 ... html.body(
0048 ... bgcolor='#000066',
0049 ... text='#ffffff',
0050 ... c=[html.h1('Page Title'),
0051 ... html.p('Hello world!')],
0052 ... ))
0053 <html><head><title>Page Title</title></head><body bgcolor=\"#000066\" text=\"#ffffff\"><h1>Page Title</h1><p>Hello world!</p></body></html>
0054 >>> print html.a(href='#top')('return to top')
0055 <a href=\"#top\">return to top</a>
0056
0057"""
0058
0059from __future__ import generators
0060
0061from cgi import escape
0062try:
0063 import xml.etree.ElementTree as ET
0064except ImportError:
0065 import elementtree.ElementTree as ET
0066
0067default_encoding = 'utf-8'
0068
0069class _HTML:
0070
0071 def __getattr__(self, attr):
0072 if attr.startswith('_'):
0073 raise AttributeError
0074 attr = attr.lower()
0075 if attr.endswith('_'):
0076 attr = attr[:-1]
0077 if attr.find('__') != -1:
0078 attr = attr.replace('__', ':')
0079 if attr == 'comment':
0080 return Element(ET.Comment, {})
0081 else:
0082 return Element(attr, {})
0083
0084 def __call__(self, *args):
0085 return ElementList(args)
0086
0087 def quote(self, arg):
0088 if arg is None:
0089 return ''
0090 return escape(unicode(arg).encode(default_encoding), 1)
0091
0092 def str(self, arg, encoding=None):
0093 if isinstance(arg, str):
0094 return arg
0095 elif arg is None:
0096 return ''
0097 elif isinstance(arg, unicode):
0098 return arg.encode(default_encoding)
0099 elif isinstance(arg, (list, tuple)):
0100 return ''.join(map(self.str, arg))
0101 elif isinstance(arg, Element):
0102 return str(arg)
0103 else:
0104 return unicode(arg).encode(default_encoding)
0105
0106html = _HTML()
0107
0108class Element(ET._ElementInterface):
0109
0110 def __call__(self, *args, **kw):
0111 el = self.__class__(self.tag, self.attrib)
0112 if kw.has_key('c'):
0113 if args:
0114 raise ValueError(
0115 "You may either provide positional arguments or a "
0116 "'c' keyword argument, but not both")
0117 args = kw['c']
0118 del kw['c']
0119 if not isinstance(args, (list, tuple)):
0120 args = (args,)
0121 for name, value in kw.items():
0122 if value is None:
0123 del kw[name]
0124 continue
0125 kw[name] = unicode(value)
0126 if name.endswith('_'):
0127 kw[name[:-1]] = value
0128 del kw[name]
0129 if name.find('__') != -1:
0130 new_name = name.replace('__', ':')
0131 kw[new_name] = value
0132 del kw[name]
0133 el.attrib.update(kw)
0134 el.text = self.text
0135 last = None
0136 for item in self.getchildren():
0137 last = item
0138 el.append(item)
0139 for arg in flatten(args):
0140 if arg is None:
0141 continue
0142 if not ET.iselement(arg):
0143 if last is None:
0144 if el.text is None:
0145 el.text = unicode(arg)
0146 else:
0147 el.text += unicode(arg)
0148 else:
0149 if last.tail is None:
0150 last.tail = unicode(arg)
0151 else:
0152 last.tail += unicode(arg)
0153 else:
0154 last = arg
0155 el.append(last)
0156 return el
0157
0158 def __str__(self):
0159 return ET.tostring(self, default_encoding)
0160
0161 def __unicode__(self):
0162
0163 return str(self).decode(default_encoding)
0164
0165 def __repr__(self):
0166 content = str(self)
0167 if len(content) > 25:
0168 content = repr(content[:25]) + '...'
0169 else:
0170 content = repr(content)
0171 return '<Element %r>' % content
0172
0173class ElementList(list):
0174
0175 def __str__(self):
0176 return html.str(self)
0177
0178 def __repr__(self):
0179 return 'ElementList(%s)' % list.__repr__(self)
0180
0181def flatten(items):
0182 for item in items:
0183 if isinstance(item, (list, tuple)):
0184 for sub in flatten(item):
0185 yield sub
0186 else:
0187 yield item
0188
0189__all__ = ['html']