0001"""
0002htmlgen
0003
0004Kind of like HTMLGen, only much simpler.  Like stan, only not.  The
0005only important symbol that is exported is ``html``.
0006
0007You create tags with attribute access.  I.e., the ``A`` anchor tag is
0008``html.a``.  The attributes of the HTML tag are done with keyword
0009arguments.  The contents of the tag are the non-keyword arguments
0010(concatenated).  You can also use the special ``c`` keyword, passing a
0011list, tuple, or single tag, and it will make up the contents (this is
0012useful because keywords have to come after all non-keyword arguments,
0013which is non-intuitive).
0014
0015If the value of an attribute is None, then no attribute will be
0016inserted.  So::
0017
0018    >>> html.a(href='http://www.yahoo.com', name=None, c='Click Here')
0019    '<a href=\"http://www.yahoo.com\">Click Here</a>'
0020
0021If a non-string is passed in, then ``webhelpers.escapes.html_escape``
0022is called on the value.
0023
0024``html`` can also be called, and it will concatenate the string
0025representations of its arguments.
0026
0027``html.comment`` will generate an HTML comment, like
0028``html.comment('comment text', 'and some more text')`` -- note that it
0029cannot take keyword arguments (because they wouldn't mean anything).
0030
0031For cases where you cannot use a name (e.g., for the ``class``
0032attribute) you can append an underscore to the name, like
0033``html.span(class_='alert')``.
0034
0035Examples::
0036
0037    >>> html.html(
0038    ...    html.head(html.title(\"Page Title\")),
0039    ...    html.body(
0040    ...    bgcolor='#000066',
0041    ...    text='#ffffff',
0042    ...    c=[html.h1('Page Title'),
0043    ...       html.p('Hello world!')],
0044    ...    ))
0045    '<html><head><title>Page Title</title></head><body text="#ffffff" bgcolor="#000066"><h1>Page Title</h1><p>Hello world!</p></body></html>'
0046    >>> html.a(href='#top', c='return to top')
0047    '<a href=\"#top\">return to top</a>'
0048
0049.. note::
0050
0051   Should this return objects instead of strings?  That would allow
0052   things like ``html.a(href='foo')('title')``.  Also, the objects
0053   could have a method that shows that they are trully HTML, and thus
0054   should not be further quoted.
0055
0056   However, in some contexts you can't use objects, you need actual
0057   strings.  But maybe we can just make sure those contexts don't
0058   happen in webhelpers.
0059"""
0060
0061from util import html_escape
0062
0063__all__ = ['html']
0064
0065def strify(s):
0066    if s is None:
0067        return ''
0068    if not isinstance(s, basestring):
0069        s = unicode(s)
0070    if isinstance(s, unicode):
0071        s = s.encode('ascii', 'xmlcharrefreplace')
0072    return s
0073
0074class UnfinishedComment:
0075
0076    def __call__(self, *args):
0077        return '<!--%s-->' % '\n'.join(map(strify, args))
0078
0079class Base:
0080
0081    comment = UnfinishedComment()
0082
0083    def __getattr__(self, attr):
0084        if attr.startswith('__'):
0085            raise AttributeError
0086        attr = attr.lower()
0087        return UnfinishedTag(attr)
0088
0089    def __call__(self, *args):
0090        return ''.join(map(str, args))
0091
0092    def escape(self, *args):
0093        return ''.join(map(html_escape, args))
0094
0095    def str(self, arg):
0096        return strify(arg)
0097
0098class UnfinishedTag:
0099
0100    def __init__(self, tag):
0101        self._tag = tag
0102
0103    def __call__(self, *args, **kw):
0104        return tag(self._tag, *args, **kw)
0105
0106    def __str__(self):
0107        if self._tag in empty_tags:
0108            return '<%s />' % self._tag
0109        else:
0110            return '<%s></%s>' % (self._tag, self._tag)
0111
0112def tag(tag, *args, **kw):
0113    if kw.has_key("c"):
0114        if args:
0115            raise TypeError(
0116                "The special 'c' keyword argument cannot be used in "
0117                "conjunction with non-keyword arguments")
0118        args = kw["c"]
0119        del kw["c"]
0120    attrargs = []
0121    for attr, value in kw.items():
0122        if value is None:
0123            continue
0124        if attr.endswith('_'):
0125            attr = attr[:-1]
0126        attrargs.append(' %s="%s"' % (attr, html_escape(value)))
0127    if not args and tag in empty_tags:
0128        return '<%s%s />' % (tag, ''.join(attrargs))
0129    else:
0130        return '<%s%s>%s</%s>' % (
0131            tag, ''.join(attrargs), ''.join(map(strify, args)),
0132            tag)
0133
0134# Taken from: http://www.w3.org/TR/REC-html40/index/elements.html
0135empty_tags = {}
0136for _t in ("area base basefont br col frame hr img input isindex "
0137           "link meta param".split()):
0138    empty_tags[_t] = None
0139
0140block_level_tags = {}
0141for _t in ("applet blockquote body br dd div dl dt fieldset "
0142           "form frameset head hr html iframe map menu noframes "
0143           "noscript object ol optgroup p param script select "
0144           "table tbody tfoot thead tr ul var"):
0145    block_level_tags[_t] = None
0146
0147html = Base()