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
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()