formencode.htmlgen
Covered: 136 lines
Missed: 21 lines
Skipped 33 lines
Percent: 86 %
  1
"""
  2
Kind of like htmlgen, only much simpler.  The only important symbol
  3
that is exported is ``html``.
  5
This builds ElementTree nodes, but with some extra useful methods.
  6
(Open issue: should it use ``ElementTree`` more, and the raw
  7
``Element`` stuff less?)
  9
You create tags with attribute access.  I.e., the ``A`` anchor tag is
 10
``html.a``.  The attributes of the HTML tag are done with keyword
 11
arguments.  The contents of the tag are the non-keyword arguments
 12
(concatenated).  You can also use the special ``c`` keyword, passing a
 13
list, tuple, or single tag, and it will make up the contents (this is
 14
useful because keywords have to come after all non-keyword arguments,
 15
which is non-intuitive).  Or you can chain them, adding the keywords
 16
with one call, then the body with a second call, like::
 18
    >>> print html.a(href='http://yahoo.com')('<Yahoo>')
 19
    <a href=\"http://yahoo.com\">&lt;Yahoo&gt;</a>
 21
Note that strings will be quoted; only tags given explicitly will
 22
remain unquoted.
 24
If the value of an attribute is None, then no attribute
 25
will be inserted.  So::
 27
    >>> print html.a(href='http://www.yahoo.com', name=None,
 28
    ...              c='Click Here')
 29
    <a href=\"http://www.yahoo.com\">Click Here</a>
 31
If the value is None, then the empty string is used.  Otherwise str()
 32
is called on the value.
 34
``html`` can also be called, and it will produce a special list from
 35
its arguments, which adds a ``__str__`` method that does ``html.str``
 36
(which handles quoting, flattening these lists recursively, and using
 37
'' for ``None``).
 39
``html.comment`` will generate an HTML comment, like
 40
``html.comment('comment text')`` -- note that it cannot take keyword
 41
arguments (because they wouldn't mean anything).
 43
Examples::
 45
    >>> print html.html(
 46
    ...    html.head(html.title(\"Page Title\")),
 47
    ...    html.body(
 48
    ...    bgcolor='#000066',
 49
    ...    text='#ffffff',
 50
    ...    c=[html.h1('Page Title'),
 51
    ...       html.p('Hello world!')],
 52
    ...    ))
 53
    <html><head><title>Page Title</title></head><body bgcolor=\"#000066\" text=\"#ffffff\"><h1>Page Title</h1><p>Hello world!</p></body></html>
 54
    >>> print html.a(href='#top')('return to top')
 55
    <a href=\"#top\">return to top</a>
 57
"""
 59
from __future__ import generators
 61
from cgi import escape
 62
try:
 63
    import xml.etree.ElementTree as ET
 64
except ImportError:
 65
    import elementtree.ElementTree as ET
 67
default_encoding = 'utf-8'
 69
class _HTML:
 71
    def __getattr__(self, attr):
 72
        if attr.startswith('_'):
 73
            raise AttributeError
 74
        attr = attr.lower()
 75
        if attr.endswith('_'):
 76
            attr = attr[:-1]
 77
        if attr.find('__') != -1:
 78
            attr = attr.replace('__', ':')
 79
        if attr == 'comment':
 80
            return Element(ET.Comment, {})
 81
        else:
 82
            return Element(attr, {})
 84
    def __call__(self, *args):
 85
        return ElementList(args)
 87
    def quote(self, arg):
 88
        if arg is None:
 89
            return ''
 90
        return escape(unicode(arg).encode(default_encoding), 1)
 92
    def str(self, arg, encoding=None):
 93
        if isinstance(arg, str):
 94
            return arg
 95
        elif arg is None:
 96
            return ''
 97
        elif isinstance(arg, unicode):
 98
            return arg.encode(default_encoding)
 99
        elif isinstance(arg, (list, tuple)):
100
            return ''.join(map(self.str, arg))
101
        elif isinstance(arg, Element):
102
            return str(arg)
103
        else:
104
            return unicode(arg).encode(default_encoding)
106
html = _HTML()
108
class Element(ET._ElementInterface):
110
    def __call__(self, *args, **kw):
111
        el = self.__class__(self.tag, self.attrib)
112
        if kw.has_key('c'):
113
            if args:
114
                raise ValueError(
115
                    "You may either provide positional arguments or a "
116
                    "'c' keyword argument, but not both")
117
            args = kw['c']
118
            del kw['c']
119
            if not isinstance(args, (list, tuple)):
120
                args = (args,)
121
        for name, value in kw.items():
122
            if value is None:
123
                del kw[name]
124
                continue
125
            kw[name] = unicode(value)
126
            if name.endswith('_'):
127
                kw[name[:-1]] = value
128
                del kw[name]
129
            if name.find('__') != -1:
130
                new_name = name.replace('__', ':')
131
                kw[new_name] = value
132
                del kw[name]
133
        el.attrib.update(kw)
134
        el.text = self.text
135
        last = None
136
        for item in self.getchildren():
137
            last = item
138
            el.append(item)
139
        for arg in flatten(args):
140
            if arg is None:
141
                continue
142
            if not ET.iselement(arg):
143
                if last is None:
144
                    if el.text is None:
145
                        el.text = unicode(arg)
146
                    else:
147
                        el.text += unicode(arg)
148
                else:
149
                    if last.tail is None:
150
                        last.tail = unicode(arg)
151
                    else:
152
                        last.tail += unicode(arg)
153
            else:
154
                last = arg
155
                el.append(last)
156
        return el
158
    def __str__(self):
159
        return ET.tostring(self, default_encoding)
161
    def __unicode__(self):
163
        return str(self).decode(default_encoding)
165
    def __repr__(self):
166
        content = str(self)
167
        if len(content) > 25:
168
            content = repr(content[:25]) + '...'
169
        else:
170
            content = repr(content)
171
        return '<Element %r>' % content
173
class ElementList(list):
175
    def __str__(self):
176
        return html.str(self)
178
    def __repr__(self):
179
        return 'ElementList(%s)' % list.__repr__(self)
181
def flatten(items):
182
    for item in items:
183
        if isinstance(item, (list, tuple)):
184
            for sub in flatten(item):
185
                yield sub
186
        else:
187
            yield item
189
__all__ = ['html']