Edgewall Software

source: branches/stable/0.3.x/genshi/builder.py

Last change on this file was 471, checked in by cmlenz, 17 years ago

0.3.x branch: ported [461] and [469].

  • Property svn:eol-style set to native
File size: 8.1 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006 Edgewall Software
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://genshi.edgewall.org/wiki/License.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://genshi.edgewall.org/log/.
13
14from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT
15
16__all__ = ['Fragment', 'Element', 'tag']
17
18
19class Fragment(object):
20    """Represents a markup fragment, which is basically just a list of element
21    or text nodes.
22    """
23    __slots__ = ['children']
24
25    def __init__(self):
26        self.children = []
27
28    def __add__(self, other):
29        return Fragment()(self, other)
30
31    def __call__(self, *args):
32        map(self.append, args)
33        return self
34
35    def __iter__(self):
36        return self._generate()
37
38    def __repr__(self):
39        return '<%s>' % self.__class__.__name__
40
41    def __str__(self):
42        return str(self.generate())
43
44    def __unicode__(self):
45        return unicode(self.generate())
46
47    def append(self, node):
48        """Append an element or string as child node."""
49        if isinstance(node, (Stream, Element, basestring, int, float, long)):
50            # For objects of a known/primitive type, we avoid the check for
51            # whether it is iterable for better performance
52            self.children.append(node)
53        elif isinstance(node, Fragment):
54            self.children.extend(node.children)
55        elif node is not None:
56            try:
57                map(self.append, iter(node))
58            except TypeError:
59                self.children.append(node)
60
61    def _generate(self):
62        for child in self.children:
63            if isinstance(child, Fragment):
64                for event in child._generate():
65                    yield event
66            elif isinstance(child, Stream):
67                for event in child:
68                    yield event
69            else:
70                if not isinstance(child, basestring):
71                    child = unicode(child)
72                yield TEXT, child, (None, -1, -1)
73
74    def generate(self):
75        """Return a markup event stream for the fragment."""
76        return Stream(self._generate())
77
78
79class Element(Fragment):
80    """Simple XML output generator based on the builder pattern.
81
82    Construct XML elements by passing the tag name to the constructor:
83
84    >>> print Element('strong')
85    <strong/>
86
87    Attributes can be specified using keyword arguments. The values of the
88    arguments will be converted to strings and any special XML characters
89    escaped:
90
91    >>> print Element('textarea', rows=10, cols=60)
92    <textarea rows="10" cols="60"/>
93    >>> print Element('span', title='1 < 2')
94    <span title="1 &lt; 2"/>
95    >>> print Element('span', title='"baz"')
96    <span title="&#34;baz&#34;"/>
97
98    The " character is escaped using a numerical entity.
99    The order in which attributes are rendered is undefined.
100
101    If an attribute value evaluates to `None`, that attribute is not included
102    in the output:
103
104    >>> print Element('a', name=None)
105    <a/>
106
107    Attribute names that conflict with Python keywords can be specified by
108    appending an underscore:
109
110    >>> print Element('div', class_='warning')
111    <div class="warning"/>
112
113    Nested elements can be added to an element using item access notation.
114    The call notation can also be used for this and for adding attributes
115    using keyword arguments, as one would do in the constructor.
116
117    >>> print Element('ul')(Element('li'), Element('li'))
118    <ul><li/><li/></ul>
119    >>> print Element('a')('Label')
120    <a>Label</a>
121    >>> print Element('a')('Label', href="target")
122    <a href="target">Label</a>
123
124    Text nodes can be nested in an element by adding strings instead of
125    elements. Any special characters in the strings are escaped automatically:
126
127    >>> print Element('em')('Hello world')
128    <em>Hello world</em>
129    >>> print Element('em')(42)
130    <em>42</em>
131    >>> print Element('em')('1 < 2')
132    <em>1 &lt; 2</em>
133
134    This technique also allows mixed content:
135
136    >>> print Element('p')('Hello ', Element('b')('world'))
137    <p>Hello <b>world</b></p>
138
139    Quotes are not escaped inside text nodes:
140    >>> print Element('p')('"Hello"')
141    <p>"Hello"</p>
142
143    Elements can also be combined with other elements or strings using the
144    addition operator, which results in a `Fragment` object that contains the
145    operands:
146   
147    >>> print Element('br') + 'some text' + Element('br')
148    <br/>some text<br/>
149   
150    Elements with a namespace can be generated using the `Namespace` and/or
151    `QName` classes:
152   
153    >>> from genshi.core import Namespace
154    >>> xhtml = Namespace('http://www.w3.org/1999/xhtml')
155    >>> print Element(xhtml.html, lang='en')
156    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
157    """
158    __slots__ = ['tag', 'attrib']
159
160    def __init__(self, tag_, **attrib):
161        Fragment.__init__(self)
162        self.tag = QName(tag_)
163        self.attrib = Attrs()
164        for attr, value in attrib.items():
165            if value is not None:
166                if not isinstance(value, basestring):
167                    value = unicode(value)
168                self.attrib.append((QName(attr.rstrip('_').replace('_', '-')),
169                                    value))
170
171    def __call__(self, *args, **kwargs):
172        for attr, value in kwargs.items():
173            if value is not None:
174                if not isinstance(value, basestring):
175                    value = unicode(value)
176                self.attrib.set(attr.rstrip('_').replace('_', '-'), value)
177        Fragment.__call__(self, *args)
178        return self
179
180    def __repr__(self):
181        return '<%s "%s">' % (self.__class__.__name__, self.tag)
182
183    def _generate(self):
184        yield START, (self.tag, self.attrib), (None, -1, -1)
185        for kind, data, pos in Fragment._generate(self):
186            yield kind, data, pos
187        yield END, self.tag, (None, -1, -1)
188
189    def generate(self):
190        """Return a markup event stream for the fragment."""
191        return Stream(self._generate())
192
193
194class ElementFactory(object):
195    """Factory for `Element` objects.
196   
197    A new element is created simply by accessing a correspondingly named
198    attribute of the factory object:
199   
200    >>> factory = ElementFactory()
201    >>> print factory.foo
202    <foo/>
203    >>> print factory.foo(id=2)
204    <foo id="2"/>
205   
206    Markup fragments (lists of nodes without a parent element) can be created
207    by calling the factory:
208   
209    >>> print factory('Hello, ', factory.em('world'), '!')
210    Hello, <em>world</em>!
211   
212    A factory can also be bound to a specific namespace:
213   
214    >>> factory = ElementFactory('http://www.w3.org/1999/xhtml')
215    >>> print factory.html(lang="en")
216    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
217   
218    The namespace for a specific element can be altered on an existing factory
219    by specifying the new namespace using item access:
220   
221    >>> factory = ElementFactory()
222    >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3))
223    <html><g id="3" xmlns="http://www.w3.org/2000/svg"/></html>
224   
225    Usually, the `ElementFactory` class is not be used directly. Rather, the
226    `tag` instance should be used to create elements.
227    """
228
229    def __init__(self, namespace=None):
230        """Create the factory, optionally bound to the given namespace.
231       
232        @param namespace: the namespace URI for any created elements, or `None`
233            for no namespace
234        """
235        if namespace and not isinstance(namespace, Namespace):
236            namespace = Namespace(namespace)
237        self.namespace = namespace
238
239    def __call__(self, *args):
240        return Fragment()(*args)
241
242    def __getitem__(self, namespace):
243        """Return a new factory that is bound to the specified namespace."""
244        return ElementFactory(namespace)
245
246    def __getattr__(self, name):
247        """Create an `Element` with the given name."""
248        return Element(self.namespace and self.namespace[name] or name)
249
250
251tag = ElementFactory()
Note: See TracBrowser for help on using the repository browser.