Mercurial > genshi > mirror
comparison genshi/builder.py @ 500:3eb30e4ece8c experimental-inline
Merged revisions 487-603 via svnmerge from
http://svn.edgewall.org/repos/genshi/trunk
author | cmlenz |
---|---|
date | Fri, 01 Jun 2007 17:21:47 +0000 |
parents | a81675590258 |
children | 9755836bb396 |
comparison
equal
deleted
inserted
replaced
499:b2704e935eb2 | 500:3eb30e4ece8c |
---|---|
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 # | 2 # |
3 # Copyright (C) 2006 Edgewall Software | 3 # Copyright (C) 2006-2007 Edgewall Software |
4 # All rights reserved. | 4 # All rights reserved. |
5 # | 5 # |
6 # This software is licensed as described in the file COPYING, which | 6 # This software is licensed as described in the file COPYING, which |
7 # you should have received as part of this distribution. The terms | 7 # you should have received as part of this distribution. The terms |
8 # are also available at http://genshi.edgewall.org/wiki/License. | 8 # are also available at http://genshi.edgewall.org/wiki/License. |
9 # | 9 # |
10 # This software consists of voluntary contributions made by many | 10 # This software consists of voluntary contributions made by many |
11 # individuals. For the exact contribution history, see the revision | 11 # individuals. For the exact contribution history, see the revision |
12 # history and logs, available at http://genshi.edgewall.org/log/. | 12 # history and logs, available at http://genshi.edgewall.org/log/. |
13 | 13 |
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 #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 | |
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 """ | |
70 | |
14 from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT | 71 from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT |
15 | 72 |
16 __all__ = ['Fragment', 'Element', 'tag'] | 73 __all__ = ['Fragment', 'Element', 'ElementFactory', 'tag'] |
74 __docformat__ = 'restructuredtext en' | |
17 | 75 |
18 | 76 |
19 class Fragment(object): | 77 class Fragment(object): |
20 """Represents a markup fragment, which is basically just a list of element | 78 """Represents a markup fragment, which is basically just a list of element |
21 or text nodes. | 79 or text nodes. |
22 """ | 80 """ |
23 __slots__ = ['children'] | 81 __slots__ = ['children'] |
24 | 82 |
25 def __init__(self): | 83 def __init__(self): |
84 """Create a new fragment.""" | |
26 self.children = [] | 85 self.children = [] |
27 | 86 |
28 def __add__(self, other): | 87 def __add__(self, other): |
29 return Fragment()(self, other) | 88 return Fragment()(self, other) |
30 | 89 |
31 def __call__(self, *args): | 90 def __call__(self, *args): |
91 """Append any positional arguments as child nodes. | |
92 | |
93 :see: `append` | |
94 """ | |
32 map(self.append, args) | 95 map(self.append, args) |
33 return self | 96 return self |
34 | 97 |
35 def __iter__(self): | 98 def __iter__(self): |
36 return self._generate() | 99 return self._generate() |
43 | 106 |
44 def __unicode__(self): | 107 def __unicode__(self): |
45 return unicode(self.generate()) | 108 return unicode(self.generate()) |
46 | 109 |
47 def append(self, node): | 110 def append(self, node): |
48 """Append an element or string as child 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 """ | |
49 if isinstance(node, (Stream, Element, basestring, int, float, long)): | 116 if isinstance(node, (Stream, Element, basestring, int, float, long)): |
50 # For objects of a known/primitive type, we avoid the check for | 117 # For objects of a known/primitive type, we avoid the check for |
51 # whether it is iterable for better performance | 118 # whether it is iterable for better performance |
52 self.children.append(node) | 119 self.children.append(node) |
53 elif isinstance(node, Fragment): | 120 elif isinstance(node, Fragment): |
70 if not isinstance(child, basestring): | 137 if not isinstance(child, basestring): |
71 child = unicode(child) | 138 child = unicode(child) |
72 yield TEXT, child, (None, -1, -1) | 139 yield TEXT, child, (None, -1, -1) |
73 | 140 |
74 def generate(self): | 141 def generate(self): |
75 """Return a markup event stream for the fragment.""" | 142 """Return a markup event stream for the fragment. |
143 | |
144 :rtype: `Stream` | |
145 """ | |
76 return Stream(self._generate()) | 146 return Stream(self._generate()) |
77 | 147 |
78 | 148 |
79 def _value_to_unicode(value): | 149 def _value_to_unicode(value): |
80 if isinstance(value, unicode): | 150 if isinstance(value, unicode): |
81 return value | 151 return value |
82 return unicode(value) | 152 return unicode(value) |
83 | 153 |
84 def _kwargs_to_attrs(kwargs): | 154 def _kwargs_to_attrs(kwargs): |
85 return [(k.rstrip('_').replace('_', '-'), _value_to_unicode(v)) | 155 return [(QName(k.rstrip('_').replace('_', '-')), _value_to_unicode(v)) |
86 for k, v in kwargs.items() if v is not None] | 156 for k, v in kwargs.items() if v is not None] |
87 | 157 |
88 | 158 |
89 class Element(Fragment): | 159 class Element(Fragment): |
90 """Simple XML output generator based on the builder pattern. | 160 """Simple XML output generator based on the builder pattern. |
161 `QName` classes: | 231 `QName` classes: |
162 | 232 |
163 >>> from genshi.core import Namespace | 233 >>> from genshi.core import Namespace |
164 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml') | 234 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml') |
165 >>> print Element(xhtml.html, lang='en') | 235 >>> print Element(xhtml.html, lang='en') |
166 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | 236 <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/> |
167 """ | 237 """ |
168 __slots__ = ['tag', 'attrib'] | 238 __slots__ = ['tag', 'attrib'] |
169 | 239 |
170 def __init__(self, tag_, **attrib): | 240 def __init__(self, tag_, **attrib): |
171 Fragment.__init__(self) | 241 Fragment.__init__(self) |
172 self.tag = QName(tag_) | 242 self.tag = QName(tag_) |
173 self.attrib = Attrs(_kwargs_to_attrs(attrib)) | 243 self.attrib = Attrs(_kwargs_to_attrs(attrib)) |
174 | 244 |
175 def __call__(self, *args, **kwargs): | 245 def __call__(self, *args, **kwargs): |
246 """Append any positional arguments as child nodes, and keyword arguments | |
247 as attributes. | |
248 | |
249 :return: the element itself so that calls can be chained | |
250 :rtype: `Element` | |
251 :see: `Fragment.append` | |
252 """ | |
176 self.attrib |= Attrs(_kwargs_to_attrs(kwargs)) | 253 self.attrib |= Attrs(_kwargs_to_attrs(kwargs)) |
177 Fragment.__call__(self, *args) | 254 Fragment.__call__(self, *args) |
178 return self | 255 return self |
179 | 256 |
180 def __repr__(self): | 257 def __repr__(self): |
185 for kind, data, pos in Fragment._generate(self): | 262 for kind, data, pos in Fragment._generate(self): |
186 yield kind, data, pos | 263 yield kind, data, pos |
187 yield END, self.tag, (None, -1, -1) | 264 yield END, self.tag, (None, -1, -1) |
188 | 265 |
189 def generate(self): | 266 def generate(self): |
190 """Return a markup event stream for the fragment.""" | 267 """Return a markup event stream for the fragment. |
268 | |
269 :rtype: `Stream` | |
270 """ | |
191 return Stream(self._generate()) | 271 return Stream(self._generate()) |
192 | 272 |
193 | 273 |
194 class ElementFactory(object): | 274 class ElementFactory(object): |
195 """Factory for `Element` objects. | 275 """Factory for `Element` objects. |
211 | 291 |
212 A factory can also be bound to a specific namespace: | 292 A factory can also be bound to a specific namespace: |
213 | 293 |
214 >>> factory = ElementFactory('http://www.w3.org/1999/xhtml') | 294 >>> factory = ElementFactory('http://www.w3.org/1999/xhtml') |
215 >>> print factory.html(lang="en") | 295 >>> print factory.html(lang="en") |
216 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | 296 <html xmlns="http://www.w3.org/1999/xhtml" lang="en"/> |
217 | 297 |
218 The namespace for a specific element can be altered on an existing factory | 298 The namespace for a specific element can be altered on an existing factory |
219 by specifying the new namespace using item access: | 299 by specifying the new namespace using item access: |
220 | 300 |
221 >>> factory = ElementFactory() | 301 >>> factory = ElementFactory() |
222 >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3)) | 302 >>> 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> | 303 <html><g xmlns="http://www.w3.org/2000/svg" id="3"/></html> |
224 | 304 |
225 Usually, the `ElementFactory` class is not be used directly. Rather, the | 305 Usually, the `ElementFactory` class is not be used directly. Rather, the |
226 `tag` instance should be used to create elements. | 306 `tag` instance should be used to create elements. |
227 """ | 307 """ |
228 | 308 |
229 def __init__(self, namespace=None): | 309 def __init__(self, namespace=None): |
230 """Create the factory, optionally bound to the given namespace. | 310 """Create the factory, optionally bound to the given namespace. |
231 | 311 |
232 @param namespace: the namespace URI for any created elements, or `None` | 312 :param namespace: the namespace URI for any created elements, or `None` |
233 for no namespace | 313 for no namespace |
234 """ | 314 """ |
235 if namespace and not isinstance(namespace, Namespace): | 315 if namespace and not isinstance(namespace, Namespace): |
236 namespace = Namespace(namespace) | 316 namespace = Namespace(namespace) |
237 self.namespace = namespace | 317 self.namespace = namespace |
238 | 318 |
239 def __call__(self, *args): | 319 def __call__(self, *args): |
320 """Create a fragment that has the given positional arguments as child | |
321 nodes. | |
322 | |
323 :return: the created `Fragment` | |
324 :rtype: `Fragment` | |
325 """ | |
240 return Fragment()(*args) | 326 return Fragment()(*args) |
241 | 327 |
242 def __getitem__(self, namespace): | 328 def __getitem__(self, namespace): |
243 """Return a new factory that is bound to the specified namespace.""" | 329 """Return a new factory that is bound to the specified namespace. |
330 | |
331 :param namespace: the namespace URI or `Namespace` object | |
332 :return: an `ElementFactory` that produces elements bound to the given | |
333 namespace | |
334 :rtype: `ElementFactory` | |
335 """ | |
244 return ElementFactory(namespace) | 336 return ElementFactory(namespace) |
245 | 337 |
246 def __getattr__(self, name): | 338 def __getattr__(self, name): |
247 """Create an `Element` with the given name.""" | 339 """Create an `Element` with the given name. |
340 | |
341 :param name: the tag name of the element to create | |
342 :return: an `Element` with the specified name | |
343 :rtype: `Element` | |
344 """ | |
248 return Element(self.namespace and self.namespace[name] or name) | 345 return Element(self.namespace and self.namespace[name] or name) |
249 | 346 |
250 | 347 |
251 tag = ElementFactory() | 348 tag = ElementFactory() |
349 """Global `ElementFactory` bound to the default namespace. | |
350 | |
351 :type: `ElementFactory` | |
352 """ |