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 """
Copyright (C) 2012-2017 Edgewall Software