Edgewall Software

source: tags/0.4.1/genshi/builder.py

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

More API docs.

  • Property svn:eol-style set to native
File size: 11.0 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006-2007 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
14"""Support for programmatically generating markup streams from Python code using
15a very simple syntax. The main entry point to this module is the `tag` object
16(which is actually an instance of the ``ElementFactory`` class). You should
17rarely (if ever) need to directly import and use any of the other classes in
18this module.
19
20Elements can be created using the `tag` object using attribute access. For
21example:
22
23>>> doc = tag.p('Some text and ', tag.a('a link', href='http://example.org/'), '.')
24>>> doc
25<Element "p">
26
27This produces an `Element` instance which can be further modified to add child
28nodes and attributes. This is done by "calling" the element: positional
29arguments are added as child nodes (alternatively, the `Element.append` method
30can be used for that purpose), whereas keywords arguments are added as
31attributes:
32
33>>> doc(tag.br)
34<Element "p">
35>>> print doc
36<p>Some text and <a href="http://example.org/">a link</a>.<br/></p>
37
38If an attribute name collides with a Python keyword, simply append an underscore
39to the name:
40
41>>> doc(class_='intro')
42<Element "p">
43>>> print doc
44<p class="intro">Some text and <a href="http://example.org/">a link</a>.<br/></p>
45
46As shown above, an `Element` can easily be directly rendered to XML text by
47printing it or using the Python ``str()`` function. This is basically a
48shortcut for converting the `Element` to a stream and serializing that
49stream:
50
51>>> stream = doc.generate()
52>>> stream #doctest: +ELLIPSIS
53<genshi.core.Stream object at ...>
54>>> print stream
55<p class="intro">Some text and <a href="http://example.org/">a link</a>.<br/></p>
56
57
58The `tag` object also allows creating "fragments", which are basically lists
59of nodes (elements or text) that don't have a parent element. This can be useful
60for creating snippets of markup that are attached to a parent element later (for
61example in a template). Fragments are created by calling the `tag` object, which
62returns an object of type `Fragment`:
63
64>>> fragment = tag('Hello, ', tag.em('world'), '!')
65>>> fragment
66<Fragment>
67>>> print fragment
68Hello, <em>world</em>!
69"""
70
71from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT
72
73__all__ = ['Fragment', 'Element', 'ElementFactory', 'tag']
74__docformat__ = 'restructuredtext en'
75
76
77class Fragment(object):
78    """Represents a markup fragment, which is basically just a list of element
79    or text nodes.
80    """
81    __slots__ = ['children']
82
83    def __init__(self):
84        """Create a new fragment."""
85        self.children = []
86
87    def __add__(self, other):
88        return Fragment()(self, other)
89
90    def __call__(self, *args):
91        """Append any positional arguments as child nodes.
92       
93        :see: `append`
94        """
95        map(self.append, args)
96        return self
97
98    def __iter__(self):
99        return self._generate()
100
101    def __repr__(self):
102        return '<%s>' % self.__class__.__name__
103
104    def __str__(self):
105        return str(self.generate())
106
107    def __unicode__(self):
108        return unicode(self.generate())
109
110    def append(self, node):
111        """Append an element or string as child node.
112       
113        :param node: the node to append; can be an `Element`, `Fragment`, or a
114                     `Stream`, or a Python string or number
115        """
116        if isinstance(node, (Stream, Element, basestring, int, float, long)):
117            # For objects of a known/primitive type, we avoid the check for
118            # whether it is iterable for better performance
119            self.children.append(node)
120        elif isinstance(node, Fragment):
121            self.children.extend(node.children)
122        elif node is not None:
123            try:
124                map(self.append, iter(node))
125            except TypeError:
126                self.children.append(node)
127
128    def _generate(self):
129        for child in self.children:
130            if isinstance(child, Fragment):
131                for event in child._generate():
132                    yield event
133            elif isinstance(child, Stream):
134                for event in child:
135                    yield event
136            else:
137                if not isinstance(child, basestring):
138                    child = unicode(child)
139                yield TEXT, child, (None, -1, -1)
140
141    def generate(self):
142        """Return a markup event stream for the fragment."""
143        return Stream(self._generate())
144
145
146def _value_to_unicode(value):
147    if isinstance(value, unicode):
148        return value
149    return unicode(value)
150
151def _kwargs_to_attrs(kwargs):
152    return [(QName(k.rstrip('_').replace('_', '-')), _value_to_unicode(v))
153            for k, v in kwargs.items() if v is not None]
154
155
156class Element(Fragment):
157    """Simple XML output generator based on the builder pattern.
158
159    Construct XML elements by passing the tag name to the constructor:
160
161    >>> print Element('strong')
162    <strong/>
163
164    Attributes can be specified using keyword arguments. The values of the
165    arguments will be converted to strings and any special XML characters
166    escaped:
167
168    >>> print Element('textarea', rows=10, cols=60)
169    <textarea rows="10" cols="60"/>
170    >>> print Element('span', title='1 < 2')
171    <span title="1 &lt; 2"/>
172    >>> print Element('span', title='"baz"')
173    <span title="&#34;baz&#34;"/>
174
175    The " character is escaped using a numerical entity.
176    The order in which attributes are rendered is undefined.
177
178    If an attribute value evaluates to `None`, that attribute is not included
179    in the output:
180
181    >>> print Element('a', name=None)
182    <a/>
183
184    Attribute names that conflict with Python keywords can be specified by
185    appending an underscore:
186
187    >>> print Element('div', class_='warning')
188    <div class="warning"/>
189
190    Nested elements can be added to an element using item access notation.
191    The call notation can also be used for this and for adding attributes
192    using keyword arguments, as one would do in the constructor.
193
194    >>> print Element('ul')(Element('li'), Element('li'))
195    <ul><li/><li/></ul>
196    >>> print Element('a')('Label')
197    <a>Label</a>
198    >>> print Element('a')('Label', href="target")
199    <a href="target">Label</a>
200
201    Text nodes can be nested in an element by adding strings instead of
202    elements. Any special characters in the strings are escaped automatically:
203
204    >>> print Element('em')('Hello world')
205    <em>Hello world</em>
206    >>> print Element('em')(42)
207    <em>42</em>
208    >>> print Element('em')('1 < 2')
209    <em>1 &lt; 2</em>
210
211    This technique also allows mixed content:
212
213    >>> print Element('p')('Hello ', Element('b')('world'))
214    <p>Hello <b>world</b></p>
215
216    Quotes are not escaped inside text nodes:
217    >>> print Element('p')('"Hello"')
218    <p>"Hello"</p>
219
220    Elements can also be combined with other elements or strings using the
221    addition operator, which results in a `Fragment` object that contains the
222    operands:
223   
224    >>> print Element('br') + 'some text' + Element('br')
225    <br/>some text<br/>
226   
227    Elements with a namespace can be generated using the `Namespace` and/or
228    `QName` classes:
229   
230    >>> from genshi.core import Namespace
231    >>> xhtml = Namespace('http://www.w3.org/1999/xhtml')
232    >>> print Element(xhtml.html, lang='en')
233    <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/>
234    """
235    __slots__ = ['tag', 'attrib']
236
237    def __init__(self, tag_, **attrib):
238        Fragment.__init__(self)
239        self.tag = QName(tag_)
240        self.attrib = Attrs(_kwargs_to_attrs(attrib))
241
242    def __call__(self, *args, **kwargs):
243        """Append any positional arguments as child nodes, and keyword arguments
244        as attributes.
245       
246        :see: `Fragment.append`
247        """
248        self.attrib |= Attrs(_kwargs_to_attrs(kwargs))
249        Fragment.__call__(self, *args)
250        return self
251
252    def __repr__(self):
253        return '<%s "%s">' % (self.__class__.__name__, self.tag)
254
255    def _generate(self):
256        yield START, (self.tag, self.attrib), (None, -1, -1)
257        for kind, data, pos in Fragment._generate(self):
258            yield kind, data, pos
259        yield END, self.tag, (None, -1, -1)
260
261    def generate(self):
262        """Return a markup event stream for the fragment."""
263        return Stream(self._generate())
264
265
266class ElementFactory(object):
267    """Factory for `Element` objects.
268   
269    A new element is created simply by accessing a correspondingly named
270    attribute of the factory object:
271   
272    >>> factory = ElementFactory()
273    >>> print factory.foo
274    <foo/>
275    >>> print factory.foo(id=2)
276    <foo id="2"/>
277   
278    Markup fragments (lists of nodes without a parent element) can be created
279    by calling the factory:
280   
281    >>> print factory('Hello, ', factory.em('world'), '!')
282    Hello, <em>world</em>!
283   
284    A factory can also be bound to a specific namespace:
285   
286    >>> factory = ElementFactory('http://www.w3.org/1999/xhtml')
287    >>> print factory.html(lang="en")
288    <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/>
289   
290    The namespace for a specific element can be altered on an existing factory
291    by specifying the new namespace using item access:
292   
293    >>> factory = ElementFactory()
294    >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3))
295    <html><g xmlns="http://www.w3.org/2000/svg" id="3"/></html>
296   
297    Usually, the `ElementFactory` class is not be used directly. Rather, the
298    `tag` instance should be used to create elements.
299    """
300
301    def __init__(self, namespace=None):
302        """Create the factory, optionally bound to the given namespace.
303       
304        :param namespace: the namespace URI for any created elements, or `None`
305                          for no namespace
306        """
307        if namespace and not isinstance(namespace, Namespace):
308            namespace = Namespace(namespace)
309        self.namespace = namespace
310
311    def __call__(self, *args):
312        """Create a fragment that has the given positional arguments as child
313        nodes.
314
315        :return: the created `Fragment`
316        """
317        return Fragment()(*args)
318
319    def __getitem__(self, namespace):
320        """Return a new factory that is bound to the specified namespace.
321       
322        :param namespace: the namespace URI or `Namespace` object
323        :return: an `ElementFactory` that produces elements bound to the given
324                 namespace
325        """
326        return ElementFactory(namespace)
327
328    def __getattr__(self, name):
329        """Create an `Element` with the given name.
330       
331        :param name: the tag name of the element to create
332        :return: an `Element` with the specified name
333        """
334        return Element(self.namespace and self.namespace[name] or name)
335
336
337tag = ElementFactory()
338"""Global `ElementFactory` bound to the default namespace."""
Note: See TracBrowser for help on using the repository browser.