Edgewall Software

source: tags/0.3.1/genshi/builder.py

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

Fixed EOL style.

  • Property svn:eol-style set to native
File size: 8.0 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, (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            else:
67                if not isinstance(child, basestring):
68                    child = unicode(child)
69                yield TEXT, child, (None, -1, -1)
70
71    def generate(self):
72        """Return a markup event stream for the fragment."""
73        return Stream(self._generate())
74
75
76class Element(Fragment):
77    """Simple XML output generator based on the builder pattern.
78
79    Construct XML elements by passing the tag name to the constructor:
80
81    >>> print Element('strong')
82    <strong/>
83
84    Attributes can be specified using keyword arguments. The values of the
85    arguments will be converted to strings and any special XML characters
86    escaped:
87
88    >>> print Element('textarea', rows=10, cols=60)
89    <textarea rows="10" cols="60"/>
90    >>> print Element('span', title='1 < 2')
91    <span title="1 &lt; 2"/>
92    >>> print Element('span', title='"baz"')
93    <span title="&#34;baz&#34;"/>
94
95    The " character is escaped using a numerical entity.
96    The order in which attributes are rendered is undefined.
97
98    If an attribute value evaluates to `None`, that attribute is not included
99    in the output:
100
101    >>> print Element('a', name=None)
102    <a/>
103
104    Attribute names that conflict with Python keywords can be specified by
105    appending an underscore:
106
107    >>> print Element('div', class_='warning')
108    <div class="warning"/>
109
110    Nested elements can be added to an element using item access notation.
111    The call notation can also be used for this and for adding attributes
112    using keyword arguments, as one would do in the constructor.
113
114    >>> print Element('ul')(Element('li'), Element('li'))
115    <ul><li/><li/></ul>
116    >>> print Element('a')('Label')
117    <a>Label</a>
118    >>> print Element('a')('Label', href="target")
119    <a href="target">Label</a>
120
121    Text nodes can be nested in an element by adding strings instead of
122    elements. Any special characters in the strings are escaped automatically:
123
124    >>> print Element('em')('Hello world')
125    <em>Hello world</em>
126    >>> print Element('em')(42)
127    <em>42</em>
128    >>> print Element('em')('1 < 2')
129    <em>1 &lt; 2</em>
130
131    This technique also allows mixed content:
132
133    >>> print Element('p')('Hello ', Element('b')('world'))
134    <p>Hello <b>world</b></p>
135
136    Quotes are not escaped inside text nodes:
137    >>> print Element('p')('"Hello"')
138    <p>"Hello"</p>
139
140    Elements can also be combined with other elements or strings using the
141    addition operator, which results in a `Fragment` object that contains the
142    operands:
143   
144    >>> print Element('br') + 'some text' + Element('br')
145    <br/>some text<br/>
146   
147    Elements with a namespace can be generated using the `Namespace` and/or
148    `QName` classes:
149   
150    >>> from genshi.core import Namespace
151    >>> xhtml = Namespace('http://www.w3.org/1999/xhtml')
152    >>> print Element(xhtml.html, lang='en')
153    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
154    """
155    __slots__ = ['tag', 'attrib']
156
157    def __init__(self, tag_, **attrib):
158        Fragment.__init__(self)
159        self.tag = QName(tag_)
160        self.attrib = Attrs()
161        for attr, value in attrib.items():
162            if value is not None:
163                if not isinstance(value, basestring):
164                    value = unicode(value)
165                self.attrib.append((QName(attr.rstrip('_').replace('_', '-')),
166                                    value))
167
168    def __call__(self, *args, **kwargs):
169        for attr, value in kwargs.items():
170            if value is not None:
171                if not isinstance(value, basestring):
172                    value = unicode(value)
173                self.attrib.set(attr.rstrip('_').replace('_', '-'), value)
174        Fragment.__call__(self, *args)
175        return self
176
177    def __repr__(self):
178        return '<%s "%s">' % (self.__class__.__name__, self.tag)
179
180    def _generate(self):
181        yield START, (self.tag, self.attrib), (None, -1, -1)
182        for kind, data, pos in Fragment._generate(self):
183            yield kind, data, pos
184        yield END, self.tag, (None, -1, -1)
185
186    def generate(self):
187        """Return a markup event stream for the fragment."""
188        return Stream(self._generate())
189
190
191class ElementFactory(object):
192    """Factory for `Element` objects.
193   
194    A new element is created simply by accessing a correspondingly named
195    attribute of the factory object:
196   
197    >>> factory = ElementFactory()
198    >>> print factory.foo
199    <foo/>
200    >>> print factory.foo(id=2)
201    <foo id="2"/>
202   
203    Markup fragments (lists of nodes without a parent element) can be created
204    by calling the factory:
205   
206    >>> print factory('Hello, ', factory.em('world'), '!')
207    Hello, <em>world</em>!
208   
209    A factory can also be bound to a specific namespace:
210   
211    >>> factory = ElementFactory('http://www.w3.org/1999/xhtml')
212    >>> print factory.html(lang="en")
213    <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
214   
215    The namespace for a specific element can be altered on an existing factory
216    by specifying the new namespace using item access:
217   
218    >>> factory = ElementFactory()
219    >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3))
220    <html><g id="3" xmlns="http://www.w3.org/2000/svg"/></html>
221   
222    Usually, the `ElementFactory` class is not be used directly. Rather, the
223    `tag` instance should be used to create elements.
224    """
225
226    def __init__(self, namespace=None):
227        """Create the factory, optionally bound to the given namespace.
228       
229        @param namespace: the namespace URI for any created elements, or `None`
230            for no namespace
231        """
232        if namespace and not isinstance(namespace, Namespace):
233            namespace = Namespace(namespace)
234        self.namespace = namespace
235
236    def __call__(self, *args):
237        return Fragment()(*args)
238
239    def __getitem__(self, namespace):
240        """Return a new factory that is bound to the specified namespace."""
241        return ElementFactory(namespace)
242
243    def __getattr__(self, name):
244        """Create an `Element` with the given name."""
245        return Element(self.namespace and self.namespace[name] or name)
246
247
248tag = ElementFactory()
Note: See TracBrowser for help on using the repository browser.