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\">&lt;Yahoo&gt;</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        # This is lame!
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']