Mercurial > genshi > mirror
annotate markup/builder.py @ 162:456039594db9 trunk
Implement the XPath relational operators and the `round()` function.
author | cmlenz |
---|---|
date | Wed, 16 Aug 2006 22:48:48 +0000 |
parents | 79f445396cd7 |
children | 2f30ce3fb85e |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
3 # Copyright (C) 2006 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 | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
8 # are also available at http://markup.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 | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
12 # history and logs, available at http://markup.edgewall.org/log/. |
1 | 13 |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
14 from markup.core import Attributes, Namespace, QName, Stream, START, END, TEXT |
1 | 15 |
16 __all__ = ['Fragment', 'Element', 'tag'] | |
17 | |
18 | |
19 class Fragment(object): | |
28 | 20 """Represents a markup fragment, which is basically just a list of element |
21 or text nodes. | |
22 """ | |
1 | 23 __slots__ = ['children'] |
24 | |
25 def __init__(self): | |
26 self.children = [] | |
27 | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
28 def __add__(self, other): |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
29 return Fragment()(self, other) |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
30 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
31 def __call__(self, *args): |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
32 map(self.append, args) |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
33 return self |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
34 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
35 def __iter__(self): |
98
44af12832c5a
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
36 return self._generate() |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
37 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
38 def __repr__(self): |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
39 return '<%s>' % self.__class__.__name__ |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
40 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
41 def __str__(self): |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
42 return str(self.generate()) |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
43 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
44 def __unicode__(self): |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
45 return unicode(self.generate()) |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
46 |
1 | 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): | |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
54 self.children.extend(node.children) |
1 | 55 elif node is not None: |
56 try: | |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
57 map(self.append, iter(node)) |
1 | 58 except TypeError: |
59 self.children.append(node) | |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
60 |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
61 def _generate(self): |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
62 for child in self.children: |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
63 if isinstance(child, Fragment): |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
64 for event in child._generate(): |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
65 yield event |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
66 else: |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
67 if not isinstance(child, basestring): |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
68 child = unicode(child) |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
69 yield TEXT, child, (None, -1, -1) |
1 | 70 |
71 def generate(self): | |
28 | 72 """Return a markup event stream for the fragment.""" |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
73 return Stream(self._generate()) |
1 | 74 |
75 | |
76 class 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 < 2"/> | |
92 >>> print Element('span', title='"baz"') | |
93 <span title=""baz""/> | |
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 < 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 | |
34 | 136 Quotes are not escaped inside text nodes: |
137 >>> print Element('p')('"Hello"') | |
138 <p>"Hello"</p> | |
139 | |
1 | 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 markup.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 = Attributes() | |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
161 for attr, value in attrib.items(): |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
162 if value is not None: |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
163 if not isinstance(value, basestring): |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
164 value = unicode(value) |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
165 self.attrib.append((QName(attr.rstrip('_').replace('_', '-')), |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
166 value)) |
1 | 167 |
168 def __call__(self, *args, **kwargs): | |
169 for attr, value in kwargs.items(): | |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
170 if value is not None: |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
171 if not isinstance(value, basestring): |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
172 value = unicode(value) |
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
173 self.attrib.set(attr.rstrip('_').replace('_', '-'), value) |
98
44af12832c5a
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
174 Fragment.__call__(self, *args) |
44af12832c5a
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
175 return self |
1 | 176 |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
177 def __repr__(self): |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
178 return '<%s "%s">' % (self.__class__.__name__, self.tag) |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
179 |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
180 def _generate(self): |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
181 yield START, (self.tag, self.attrib), (None, -1, -1) |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
182 for kind, data, pos in Fragment._generate(self): |
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
183 yield kind, data, pos |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
119
diff
changeset
|
184 yield END, self.tag, (None, -1, -1) |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
185 |
1 | 186 def generate(self): |
28 | 187 """Return a markup event stream for the fragment.""" |
94
0f8800c46e21
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
188 return Stream(self._generate()) |
1 | 189 |
190 | |
191 class ElementFactory(object): | |
28 | 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 | |
119
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
203 Markup fragments (lists of nodes without a parent element) can be created |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
204 by calling the factory: |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
205 |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
206 >>> print factory('Hello, ', factory.em('world'), '!') |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
207 Hello, <em>world</em>! |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
208 |
28 | 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 """ | |
1 | 225 |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
226 def __init__(self, namespace=None): |
28 | 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 """ | |
20 | 232 if namespace and not isinstance(namespace, Namespace): |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
233 namespace = Namespace(namespace) |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
234 self.namespace = namespace |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
235 |
119
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
236 def __call__(self, *args): |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
237 return Fragment()(*args) |
cc2aee07f53b
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
238 |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
239 def __getitem__(self, namespace): |
28 | 240 """Return a new factory that is bound to the specified namespace.""" |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
241 return ElementFactory(namespace) |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
242 |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
243 def __getattr__(self, name): |
28 | 244 """Create an `Element` with the given name.""" |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
245 return Element(self.namespace and self.namespace[name] or name) |
1 | 246 |
247 | |
248 tag = ElementFactory() |