Mercurial > genshi > genshi-test
annotate genshi/builder.py @ 430:e7a4d43c438a
Moved the `builder` document into the API docs.
author | cmlenz |
---|---|
date | Thu, 22 Mar 2007 17:14:09 +0000 |
parents | 5b248708bbed |
children | 747baa1cd597 |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
408 | 3 # Copyright (C) 2006-2007 Edgewall Software |
1 | 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 | |
230 | 8 # are also available at http://genshi.edgewall.org/wiki/License. |
1 | 9 # |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
230 | 12 # history and logs, available at http://genshi.edgewall.org/log/. |
1 | 13 |
430 | 14 """Support for programmatically generating markup streams from Python code using |
15 a 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 | |
17 rarely (if ever) need to directly import and use any of the other classes in | |
18 this module. | |
19 | |
20 Elements can be created using the `tag` object using attribute access. For | |
21 example: | |
22 | |
23 >>> doc = tag.p('Some text and ', tag.a('a link', href='http://example.org/'), '.') | |
24 >>> doc | |
25 <Element "p"> | |
26 | |
27 This produces an `Element` instance which can be further modified to add child | |
28 nodes and attributes. This is done by "calling" the element: positional | |
29 arguments are added as child nodes (alternatively, the `Element.append` method | |
30 can be used for that purpose), whereas keywords arguments are added as | |
31 attributes: | |
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 | |
38 If an attribute name collides with a Python keyword, simply append an underscore | |
39 to 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 | |
46 As shown above, an `Element` can easily be directly rendered to XML text by | |
47 printing it or using the Python ``str()`` function. This is basically a | |
48 shortcut for converting the `Element` to a stream and serializing that | |
49 stream: | |
50 | |
51 >>> stream = doc.generate() | |
52 >>> stream | |
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 | |
58 The `tag` object also allows creating "fragments", which are basically lists | |
59 of nodes (elements or text) that don't have a parent element. This can be useful | |
60 for creating snippets of markup that are attached to a parent element later (for | |
61 example in a template). Fragments are created by calling the `tag` object, which | |
62 returns an object of type `Fragment`: | |
63 | |
64 >>> fragment = tag('Hello, ', tag.em('world'), '!') | |
65 >>> fragment | |
66 <Fragment> | |
67 >>> print fragment | |
68 Hello, <em>world</em>! | |
69 """ | |
425
5b248708bbed
Try to use proper reStructuredText for docstrings throughout.
cmlenz
parents:
410
diff
changeset
|
70 |
230 | 71 from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT |
1 | 72 |
73 __all__ = ['Fragment', 'Element', 'tag'] | |
425
5b248708bbed
Try to use proper reStructuredText for docstrings throughout.
cmlenz
parents:
410
diff
changeset
|
74 __docformat__ = 'restructuredtext en' |
1 | 75 |
76 | |
77 class Fragment(object): | |
28 | 78 """Represents a markup fragment, which is basically just a list of element |
79 or text nodes. | |
80 """ | |
1 | 81 __slots__ = ['children'] |
82 | |
83 def __init__(self): | |
84 self.children = [] | |
85 | |
65
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
86 def __add__(self, other): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
87 return Fragment()(self, other) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
88 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
89 def __call__(self, *args): |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
90 map(self.append, args) |
65
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
91 return self |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
92 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
93 def __iter__(self): |
98
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
94 return self._generate() |
65
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
95 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
96 def __repr__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
97 return '<%s>' % self.__class__.__name__ |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
98 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
99 def __str__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
100 return str(self.generate()) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
101 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
102 def __unicode__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
103 return unicode(self.generate()) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
104 |
1 | 105 def append(self, node): |
106 """Append an element or string as child node.""" | |
379
d8c5045b547a
The builder API now accepts streams as children of elements and fragments.
cmlenz
parents:
345
diff
changeset
|
107 if isinstance(node, (Stream, Element, basestring, int, float, long)): |
1 | 108 # For objects of a known/primitive type, we avoid the check for |
109 # whether it is iterable for better performance | |
110 self.children.append(node) | |
111 elif isinstance(node, Fragment): | |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
112 self.children.extend(node.children) |
1 | 113 elif node is not None: |
114 try: | |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
115 map(self.append, iter(node)) |
1 | 116 except TypeError: |
117 self.children.append(node) | |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
118 |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
119 def _generate(self): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
120 for child in self.children: |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
121 if isinstance(child, Fragment): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
122 for event in child._generate(): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
123 yield event |
379
d8c5045b547a
The builder API now accepts streams as children of elements and fragments.
cmlenz
parents:
345
diff
changeset
|
124 elif isinstance(child, Stream): |
d8c5045b547a
The builder API now accepts streams as children of elements and fragments.
cmlenz
parents:
345
diff
changeset
|
125 for event in child: |
d8c5045b547a
The builder API now accepts streams as children of elements and fragments.
cmlenz
parents:
345
diff
changeset
|
126 yield event |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
127 else: |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
128 if not isinstance(child, basestring): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
129 child = unicode(child) |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
130 yield TEXT, child, (None, -1, -1) |
1 | 131 |
132 def generate(self): | |
28 | 133 """Return a markup event stream for the fragment.""" |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
134 return Stream(self._generate()) |
1 | 135 |
136 | |
345 | 137 def _value_to_unicode(value): |
138 if isinstance(value, unicode): | |
139 return value | |
140 return unicode(value) | |
141 | |
142 def _kwargs_to_attrs(kwargs): | |
403
32b283e1d310
Remove some magic/overhead from `Attrs` creation and manipulation by not automatically wrapping attribute names in `QName`.
cmlenz
parents:
379
diff
changeset
|
143 return [(QName(k.rstrip('_').replace('_', '-')), _value_to_unicode(v)) |
345 | 144 for k, v in kwargs.items() if v is not None] |
145 | |
146 | |
1 | 147 class Element(Fragment): |
148 """Simple XML output generator based on the builder pattern. | |
149 | |
150 Construct XML elements by passing the tag name to the constructor: | |
151 | |
152 >>> print Element('strong') | |
153 <strong/> | |
154 | |
155 Attributes can be specified using keyword arguments. The values of the | |
156 arguments will be converted to strings and any special XML characters | |
157 escaped: | |
158 | |
159 >>> print Element('textarea', rows=10, cols=60) | |
160 <textarea rows="10" cols="60"/> | |
161 >>> print Element('span', title='1 < 2') | |
162 <span title="1 < 2"/> | |
163 >>> print Element('span', title='"baz"') | |
164 <span title=""baz""/> | |
165 | |
166 The " character is escaped using a numerical entity. | |
167 The order in which attributes are rendered is undefined. | |
168 | |
169 If an attribute value evaluates to `None`, that attribute is not included | |
170 in the output: | |
171 | |
172 >>> print Element('a', name=None) | |
173 <a/> | |
174 | |
175 Attribute names that conflict with Python keywords can be specified by | |
176 appending an underscore: | |
177 | |
178 >>> print Element('div', class_='warning') | |
179 <div class="warning"/> | |
180 | |
181 Nested elements can be added to an element using item access notation. | |
182 The call notation can also be used for this and for adding attributes | |
183 using keyword arguments, as one would do in the constructor. | |
184 | |
185 >>> print Element('ul')(Element('li'), Element('li')) | |
186 <ul><li/><li/></ul> | |
187 >>> print Element('a')('Label') | |
188 <a>Label</a> | |
189 >>> print Element('a')('Label', href="target") | |
190 <a href="target">Label</a> | |
191 | |
192 Text nodes can be nested in an element by adding strings instead of | |
193 elements. Any special characters in the strings are escaped automatically: | |
194 | |
195 >>> print Element('em')('Hello world') | |
196 <em>Hello world</em> | |
197 >>> print Element('em')(42) | |
198 <em>42</em> | |
199 >>> print Element('em')('1 < 2') | |
200 <em>1 < 2</em> | |
201 | |
202 This technique also allows mixed content: | |
203 | |
204 >>> print Element('p')('Hello ', Element('b')('world')) | |
205 <p>Hello <b>world</b></p> | |
206 | |
34 | 207 Quotes are not escaped inside text nodes: |
208 >>> print Element('p')('"Hello"') | |
209 <p>"Hello"</p> | |
210 | |
1 | 211 Elements can also be combined with other elements or strings using the |
212 addition operator, which results in a `Fragment` object that contains the | |
213 operands: | |
214 | |
215 >>> print Element('br') + 'some text' + Element('br') | |
216 <br/>some text<br/> | |
217 | |
218 Elements with a namespace can be generated using the `Namespace` and/or | |
219 `QName` classes: | |
220 | |
230 | 221 >>> from genshi.core import Namespace |
1 | 222 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml') |
223 >>> print Element(xhtml.html, lang='en') | |
410
3460b04daeac
Improve the handling of namespaces in serialization.
cmlenz
parents:
408
diff
changeset
|
224 <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/> |
1 | 225 """ |
226 __slots__ = ['tag', 'attrib'] | |
227 | |
228 def __init__(self, tag_, **attrib): | |
229 Fragment.__init__(self) | |
230 self.tag = QName(tag_) | |
345 | 231 self.attrib = Attrs(_kwargs_to_attrs(attrib)) |
1 | 232 |
233 def __call__(self, *args, **kwargs): | |
345 | 234 self.attrib |= Attrs(_kwargs_to_attrs(kwargs)) |
98
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
235 Fragment.__call__(self, *args) |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
236 return self |
1 | 237 |
65
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
238 def __repr__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
239 return '<%s "%s">' % (self.__class__.__name__, self.tag) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
240 |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
241 def _generate(self): |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
242 yield START, (self.tag, self.attrib), (None, -1, -1) |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
243 for kind, data, pos in Fragment._generate(self): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
244 yield kind, data, pos |
133
b9a0031d4bbb
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
245 yield END, self.tag, (None, -1, -1) |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
246 |
1 | 247 def generate(self): |
28 | 248 """Return a markup event stream for the fragment.""" |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
249 return Stream(self._generate()) |
1 | 250 |
251 | |
252 class ElementFactory(object): | |
28 | 253 """Factory for `Element` objects. |
254 | |
255 A new element is created simply by accessing a correspondingly named | |
256 attribute of the factory object: | |
257 | |
258 >>> factory = ElementFactory() | |
259 >>> print factory.foo | |
260 <foo/> | |
261 >>> print factory.foo(id=2) | |
262 <foo id="2"/> | |
263 | |
119
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
264 Markup fragments (lists of nodes without a parent element) can be created |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
265 by calling the factory: |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
266 |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
267 >>> print factory('Hello, ', factory.em('world'), '!') |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
268 Hello, <em>world</em>! |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
269 |
28 | 270 A factory can also be bound to a specific namespace: |
271 | |
272 >>> factory = ElementFactory('http://www.w3.org/1999/xhtml') | |
273 >>> print factory.html(lang="en") | |
410
3460b04daeac
Improve the handling of namespaces in serialization.
cmlenz
parents:
408
diff
changeset
|
274 <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/> |
28 | 275 |
276 The namespace for a specific element can be altered on an existing factory | |
277 by specifying the new namespace using item access: | |
278 | |
279 >>> factory = ElementFactory() | |
280 >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3)) | |
410
3460b04daeac
Improve the handling of namespaces in serialization.
cmlenz
parents:
408
diff
changeset
|
281 <html><g xmlns="http://www.w3.org/2000/svg" id="3"/></html> |
28 | 282 |
283 Usually, the `ElementFactory` class is not be used directly. Rather, the | |
284 `tag` instance should be used to create elements. | |
285 """ | |
1 | 286 |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
287 def __init__(self, namespace=None): |
28 | 288 """Create the factory, optionally bound to the given namespace. |
289 | |
425
5b248708bbed
Try to use proper reStructuredText for docstrings throughout.
cmlenz
parents:
410
diff
changeset
|
290 :param namespace: the namespace URI for any created elements, or `None` |
5b248708bbed
Try to use proper reStructuredText for docstrings throughout.
cmlenz
parents:
410
diff
changeset
|
291 for no namespace |
28 | 292 """ |
20 | 293 if namespace and not isinstance(namespace, Namespace): |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
294 namespace = Namespace(namespace) |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
295 self.namespace = namespace |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
296 |
119
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
297 def __call__(self, *args): |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
298 return Fragment()(*args) |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
299 |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
300 def __getitem__(self, namespace): |
28 | 301 """Return a new factory that is bound to the specified namespace.""" |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
302 return ElementFactory(namespace) |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
303 |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
304 def __getattr__(self, name): |
28 | 305 """Create an `Element` with the given name.""" |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
306 return Element(self.namespace and self.namespace[name] or name) |
1 | 307 |
308 | |
309 tag = ElementFactory() |