svv/html.py
author Tero Marttila <terom@fixme.fi>
Thu, 20 Jan 2011 23:14:07 +0200
changeset 58 4f4150296cd3
parent 50 c3ae4c448248
child 60 b364279347d9
permissions -rw-r--r--
controllers: tidy up PageHandler a little
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     1
"""
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     2
    Generating XHTML output from nested python objects
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     3
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     4
    XXX: use a 'real' XML builder?
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     5
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     6
    To use:
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     7
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     8
    >>> from html import tags
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
     9
    >>> str(tags.a(href="http://www.google.com")("Google <this>!"))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    10
    '<a href="http://www.google.com">\\n\\tGoogle &lt;this&gt;!\\n</a>\\n'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    11
"""
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    12
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    13
from cgi import escape
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    14
import itertools as _itertools, types as _types
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    15
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    16
class IRenderable (object) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    17
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    18
        Something that's renderable as the contents of a HTML tag.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    19
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    20
        This is just used by Container for rendering Tags as actual HTML, vs just plain strs as escaped data.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    21
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    22
        Additionally, some str vs unicode vs file stuff..
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    23
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    24
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    25
    def render_raw_lines (self, indent=u'\t') :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    26
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    27
            Render the indented lines for tag and contents, without newlines
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    28
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    29
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    30
        abstract
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    31
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    32
    def render_lines (self, indent=u'\t', newline=u'\n') :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    33
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    34
            Render full output lines with given newlines
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    35
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    36
            >>> list(Tag('xx', 'yy').render_lines())
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    37
            [u'<xx>\\n', u'\\tyy\\n', u'</xx>\\n']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    38
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    39
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    40
        for line in self.render_raw_lines(indent=indent) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    41
            yield line + newline
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    42
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    43
    def render_unicode (self, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    44
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    45
            Render full tag as a single unicode string
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    46
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    47
            >>> Tag('xx', 'yy').render_unicode()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    48
            u'<xx>\\n\\tyy\\n</xx>\\n'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    49
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    50
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    51
        return "".join(self.render_lines(**render_opts))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    52
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    53
    def render_str (self, encoding='ascii', **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    54
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    55
            Render full tag as an encoded string
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    56
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    57
            >>> Tag('xx', 'yy').render_str()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    58
            '<xx>\\n\\tyy\\n</xx>\\n'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    59
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    60
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    61
        return self.render_unicode(**render_opts).encode(encoding)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    62
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    63
    def render_out (self, stream, encoding=None, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    64
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    65
            Render output into the given stream, encoding using the given encoding if given.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    66
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    67
            >>> from StringIO import StringIO; buf = StringIO(); Tag('xx', 'yy').render_out(buf, 'ascii'); buf.getvalue()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    68
            '<xx>\\n\\tyy\\n</xx>\\n'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    69
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    70
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    71
        for line in self.render_lines(**render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    72
            if encoding :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    73
                line = line.encode(encoding)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    74
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    75
            stream.write(line)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    76
    
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    77
    def render_file (self, file, encoding=None, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    78
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    79
            Render output to given file, overwriteing anything already there
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    80
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    81
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    82
        self.render_out(file.open_write(encoding), **render_opts)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    83
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    84
    # default output
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    85
    __str__ = render_str
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    86
    __unicode__ = render_unicode
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    87
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    88
    # default .render method
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    89
    render = render_unicode
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    90
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    91
class Container (IRenderable) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    92
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    93
        A container holds a sequence of other renderable items.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    94
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    95
        This is just used as the superclass for Tag, and just serves to gives us useful handling for e.g. generators as
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    96
        tag contents (iterate through them instead of repr'ing them).
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    97
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    98
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
    99
    @classmethod
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   100
    def process_contents (cls, contents) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   101
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   102
            Postprocess contents iterable to return new list.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   103
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   104
            Items that are None will be omitted from the return value.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   105
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   106
            Certain core sequence types will be recognized and flattened for output: tuples, lists, and generators.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   107
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   108
            >>> list(Container.process_contents([]))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   109
            []
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   110
            >>> list(Container.process_contents([None]))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   111
            []
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   112
            >>> list(Container.process_contents([u'foo']))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   113
            [u'foo']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   114
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   115
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   116
        for content in contents :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   117
            if content is None :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   118
                continue
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   119
            
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   120
            # Hardcoded list of special-case nested contents
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   121
            elif isinstance(content, (_types.TupleType, _types.ListType, _types.GeneratorType)) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   122
                for subcontent in cls.process_contents(content) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   123
                    yield subcontent
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   124
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   125
            else :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   126
                # normal, handle as IRenderable/unicode data
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   127
                yield content
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   128
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   129
    def __init__ (self, *contents) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   130
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   131
            Construct this container with the given sub-items
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   132
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   133
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   134
        # store postprocessed
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   135
        self.contents = list(self.process_contents(contents))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   136
    
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   137
    def render_raw_lines (self, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   138
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   139
            Render our contents as a series of non-indented lines, with the contents handling indentation themselves.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   140
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   141
            >>> list(Container(5).render_raw_lines())
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   142
            [u'5']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   143
            >>> list(Container('line1', 'line2').render_raw_lines())
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   144
            [u'line1', u'line2']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   145
            >>> list(Container('a', Tag('b', 'bb'), 'c').render_raw_lines())
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   146
            [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   147
            >>> list(Container(Tag('hr'), Tag('foo')('bar')).render_raw_lines())
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   148
            [u'<hr />', u'<foo>', u'\\tbar', u'</foo>']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   149
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   150
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   151
        for content in self.contents :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   152
            if isinstance(content, IRenderable) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   153
                # sub-items
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   154
                for line in content.render_raw_lines(**render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   155
                    yield line
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   156
            
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   157
            else :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   158
                # escape raw values
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   159
                yield escape(unicode(content))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   160
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   161
    def __repr__ (self) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   162
        return 'Container(%s)' % ', '.join(repr(c) for c in self.contents)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   163
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   164
class Tag (Container) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   165
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   166
        A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   167
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   168
        This is the core object, and the ~only one you really need to pay attention to.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   169
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   170
        Provides various kinds of rendering output via IRenderable.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   171
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   172
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   173
    @staticmethod
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   174
    def process_attrs (attrs) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   175
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   176
            Postprocess attributes.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   177
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   178
            Key-value pairs where the value is None will be ommitted, and any trailing underscores in the key omitted.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   179
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   180
            TODO: only remove one underscore
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   181
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   182
            >>> dict(Tag.process_attrs(dict()))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   183
            {}
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   184
            >>> dict(Tag.process_attrs(dict(foo='bar')))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   185
            {'foo': 'bar'}
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   186
            >>> dict(Tag.process_attrs(dict(class_='bar', frob=None)))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   187
            {'class': 'bar'}
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   188
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   189
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   190
        return ((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   191
5
c72e0314b930 Fix html module bugs
Tero Marttila <terom@fixme.fi>
parents: 0
diff changeset
   192
    def __init__ (self, _name, *contents, **attrs) :
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   193
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   194
            Construct tag with given name/attributes or contents.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   195
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   196
            The filtering rules desribed in process_contents/process_attrs apply.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   197
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   198
            >>> Tag('foo')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   199
            Tag('foo')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   200
            >>> Tag('foo', 'quux')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   201
            Tag('foo', 'quux')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   202
            >>> Tag('foo', 'quux', bar=5)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   203
            Tag('foo', 'quux', bar=5)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   204
            >>> Tag('foo', class_='ten')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   205
            Tag('foo', class='ten')
5
c72e0314b930 Fix html module bugs
Tero Marttila <terom@fixme.fi>
parents: 0
diff changeset
   206
            >>> Tag('bar', name='foo')
c72e0314b930 Fix html module bugs
Tero Marttila <terom@fixme.fi>
parents: 0
diff changeset
   207
            Tag('bar', name='foo')
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   208
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   209
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   210
        # store contents as container
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   211
        super(Tag, self).__init__(*contents)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   212
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   213
        # store postprocessed stuff
5
c72e0314b930 Fix html module bugs
Tero Marttila <terom@fixme.fi>
parents: 0
diff changeset
   214
        self.name = _name
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   215
        self.attrs = dict(self.process_attrs(attrs))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   216
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   217
    def __call__ (self, *contents, **attrs) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   218
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   219
            Return a new Tag with this tag's attributes and contents, as well as the given attributes/contents.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   220
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   221
            The filtering rules desribed in process_contents/process_attrs apply.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   222
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   223
            >>> Tag('foo')('bar')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   224
            Tag('foo', 'bar')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   225
            >>> Tag('a', href='index.html')("Home")
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   226
            Tag('a', 'Home', href='index.html')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   227
            >>> Tag('bar', None)(5, foo=None, class_='bar')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   228
            Tag('bar', 5, class='bar')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   229
            >>> Tag('a')('b')('c')(asdf=5)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   230
            Tag('a', 'b', 'c', asdf=5)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   231
            >>> t1 = Tag('a'); t2 = t1('b'); t1
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   232
            Tag('a')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   233
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   234
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   235
        # merge attrs/contents
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   236
        # XXX: new_attrs is not an iterator...
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   237
        new_attrs = dict(_itertools.chain(self.attrs.iteritems(), attrs.iteritems()))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   238
        new_contents = _itertools.chain(self.contents, contents)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   239
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   240
        # build new tag
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   241
        return Tag(self.name, *new_contents, **new_attrs)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   242
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   243
    @staticmethod
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   244
    def format_attr (name, value) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   245
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   246
            Format a single HTML tag attribute
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   247
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   248
            >>> Tag.format_attr('name', 'value')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   249
            u'name="value"'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   250
            >>> Tag.format_attr('this', '<a"b>')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   251
            u'this="&lt;a&quot;b&gt;"'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   252
            >>> Tag.format_attr('xx', 1337)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   253
            u'xx="1337"'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   254
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   255
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   256
        return u'%s="%s"' % (name, escape(unicode(value), True))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   257
 
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   258
    def render_attrs (self) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   259
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   260
            Return the HTML attributes string
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   261
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   262
            >>> Tag('x', foo=5, bar='<').render_attrs()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   263
            u'foo="5" bar="&lt;"'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   264
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   265
8
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   266
        return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems() if not n.startswith('_'))
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   267
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   268
    def render_raw_lines (self, indent=u'\t') :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   269
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   270
            Render the tag and indented content
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   271
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   272
            >>> list(Tag('xx', 'yy', zz='foo').render_raw_lines(indent=' '))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   273
            [u'<xx zz="foo">', u' yy', u'</xx>']
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   274
        """
8
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   275
        
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   276
        # opts
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   277
        selfclosing = self.attrs.get('_selfclosing')
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   278
        whitespace_sensitive = self.attrs.get('_whitespace_sensitive')
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   279
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   280
        # render attr string, including preceding space
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   281
        attrs_stuff = (" " + self.render_attrs()) if self.attrs else ""
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   282
8
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   283
        if self.contents or selfclosing is False:
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   284
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   285
            if not whitespace_sensitive :
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   286
                # wrapping tags
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   287
                yield u"<%s%s>" % (self.name, attrs_stuff)
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   288
                
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   289
                # subcontents
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   290
                for line in super(Tag, self).render_raw_lines(indent=indent) :
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   291
                    yield indent + line
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   292
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   293
                yield u"</%s>" % (self.name, )
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   294
            
8
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   295
            else :
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   296
                # whole tag
27e37082625e html: Implement selfclosing and whitespace_sensitive options (for <textarea>s)
Tero Marttila <terom@fixme.fi>
parents: 5
diff changeset
   297
                yield u"<%s%s>%s</%s>" % (self.name, attrs_stuff, ''.join(super(Tag, self).render_raw_lines(indent=indent)), self.name)
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   298
        else :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   299
            # singleton tag
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   300
            yield u"<%s%s />" % (self.name, attrs_stuff)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   301
    
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   302
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   303
    def __repr__ (self) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   304
        return 'Tag(%s)' % ', '.join(
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   305
            [
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   306
                repr(self.name)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   307
            ] + [
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   308
                repr(c) for c in self.contents
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   309
            ] + [
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   310
                '%s=%r' % (name, value) for name, value in self.attrs.iteritems()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   311
            ]
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   312
        )
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   313
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   314
class Text (IRenderable) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   315
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   316
        Raw HTML text
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   317
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   318
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   319
    def __init__ (self, line) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   320
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   321
            Initialize to render as the given lines
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   322
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   323
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   324
        self.lines = [line]
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   325
    
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   326
    def render_raw_lines (self, indent=u'\t') :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   327
        return self.lines
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   328
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   329
class Document (IRenderable) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   330
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   331
        A full XHTML document with XML header, doctype, head and body.
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   332
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   333
        XXX: current rendering is a bit of a kludge
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   334
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   335
        <?xml version="..." encoding="..." ?>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   336
        <!DOCTYPE ...>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   337
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   338
        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   339
            <head>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   340
                ...
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   341
            </head>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   342
            <body>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   343
                ...
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   344
            </body>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   345
        </html>
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   346
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   347
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   348
    def __init__ (self, 
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   349
        head, body,
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   350
        xml_version='1.0', xml_encoding='utf-8', 
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   351
        doctype='html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"',
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   352
        html_xmlns='http://www.w3.org/1999/xhtml', html_lang='en'
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   353
    ) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   354
        # store
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   355
        self.xml_version = xml_version
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   356
        self.xml_encoding = xml_encoding
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   357
        self.doctype = doctype
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   358
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   359
        # build the document
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   360
        self.document = Tag('html', **{'xmlns': html_xmlns, 'xml:lang': html_lang})(
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   361
            Tag('head', head),
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   362
            Tag('body', body),
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   363
        )
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   364
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   365
    def render_raw_lines (self, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   366
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   367
            Render the two header lines, and then the document
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   368
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   369
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   370
        yield '<?xml version="%s" encoding="%s" ?>' % (self.xml_version, self.xml_encoding)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   371
        yield '<!DOCTYPE %s>' % (self.doctype)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   372
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   373
        for line in self.document.render_raw_lines(**render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   374
            yield line
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   375
    
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   376
    def _check_encoding (self, encoding) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   377
        if encoding and encoding != self.xml_encoding :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   378
            raise ValueError("encoding mismatch: %r should be %r" % (encoding, self.xml_encoding))
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   379
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   380
    def render_str (self, encoding=None, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   381
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   382
            Wrap render_str to verify that the right encoding is used
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   383
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   384
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   385
        self._check_encoding(encoding)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   386
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   387
        return super(XHTMLDocument, self).render_str(self.xml_encoding, **render_opts)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   388
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   389
    def render_out (self, stream, encoding=None, **render_opts) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   390
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   391
            Wrap render_out to verify that the right encoding is used
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   392
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   393
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   394
        self._check_encoding(encoding)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   395
        
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   396
        return super(XHTMLDocument, self).render_out(stream, self.xml_encoding, **render_opts)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   397
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   398
class TagFactory (object) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   399
    """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   400
        Build Tags with names give as attribute names
50
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   401
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   402
        >>> str(TagFactory().raw("><")
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   403
        '><'
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   404
    """
58
4f4150296cd3 controllers: tidy up PageHandler a little
Tero Marttila <terom@fixme.fi>
parents: 50
diff changeset
   405
4f4150296cd3 controllers: tidy up PageHandler a little
Tero Marttila <terom@fixme.fi>
parents: 50
diff changeset
   406
    # full XHTML document
4f4150296cd3 controllers: tidy up PageHandler a little
Tero Marttila <terom@fixme.fi>
parents: 50
diff changeset
   407
    document = Document
50
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   408
    
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   409
    # raw HTML
c3ae4c448248 html: add tags.raw pseudo-tag for raw HTML processing
Tero Marttila <terom@fixme.fi>
parents: 8
diff changeset
   410
    raw = Text
0
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   411
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   412
    def __getattr__ (self, name) :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   413
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   414
            Get a Tag object with the given name, but no contents
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   415
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   416
            >>> TagFactory().a(href='bar')('quux')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   417
            Tag('a', 'quux', href='bar')
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   418
        """
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   419
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   420
        return Tag(name)
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   421
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   422
# pretty names
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   423
container = Container
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   424
tag = Tag
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   425
tags = TagFactory()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   426
raw = Text
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   427
document = Document
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   428
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   429
# testing
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   430
if __name__ == '__main__' :
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   431
    import doctest
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   432
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   433
    doctest.testmod()
b28a1681e79b Initial layout, with hello-world
Tero Marttila <terom@fixme.fi>
parents:
diff changeset
   434