Mercurial > genshi > genshi-test
annotate markup/builder.py @ 124:9a2acebe84f7
Add Trove classifiers and download URL to `setup.py`.
author | cmlenz |
---|---|
date | Thu, 03 Aug 2006 17:08:35 +0000 |
parents | 61b133ed95b2 |
children | b9a0031d4bbb |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
66
822089ae65ce
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
822089ae65ce
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
822089ae65ce
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 |
98
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
14 from markup.core import Attributes, Namespace, QName, Stream, escape |
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
5c024cf58ecb
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): |
5c024cf58ecb
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) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
30 |
5c024cf58ecb
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): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
32 for arg in args: |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
33 self.append(arg) |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
34 return self |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
35 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
36 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
|
37 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
|
38 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
39 def __repr__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
40 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
|
41 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
42 def __str__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
43 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
|
44 |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
45 def __unicode__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
46 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
|
47 |
1 | 48 def append(self, node): |
49 """Append an element or string as child node.""" | |
50 if isinstance(node, (Element, basestring, int, float, long)): | |
51 # For objects of a known/primitive type, we avoid the check for | |
52 # whether it is iterable for better performance | |
53 self.children.append(node) | |
54 elif isinstance(node, Fragment): | |
55 self.children += node.children | |
56 elif node is not None: | |
57 try: | |
58 children = iter(node) | |
59 except TypeError: | |
60 self.children.append(node) | |
61 else: | |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
62 for child in children: |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
63 self.append(child) |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
64 |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
65 def _generate(self): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
66 for child in self.children: |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
67 if isinstance(child, Fragment): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
68 for event in child._generate(): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
69 yield event |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
70 else: |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
71 if not isinstance(child, basestring): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
72 child = unicode(child) |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
73 yield Stream.TEXT, child, (None, -1, -1) |
1 | 74 |
75 def generate(self): | |
28 | 76 """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
|
77 return Stream(self._generate()) |
1 | 78 |
79 | |
80 class Element(Fragment): | |
81 """Simple XML output generator based on the builder pattern. | |
82 | |
83 Construct XML elements by passing the tag name to the constructor: | |
84 | |
85 >>> print Element('strong') | |
86 <strong/> | |
87 | |
88 Attributes can be specified using keyword arguments. The values of the | |
89 arguments will be converted to strings and any special XML characters | |
90 escaped: | |
91 | |
92 >>> print Element('textarea', rows=10, cols=60) | |
93 <textarea rows="10" cols="60"/> | |
94 >>> print Element('span', title='1 < 2') | |
95 <span title="1 < 2"/> | |
96 >>> print Element('span', title='"baz"') | |
97 <span title=""baz""/> | |
98 | |
99 The " character is escaped using a numerical entity. | |
100 The order in which attributes are rendered is undefined. | |
101 | |
102 If an attribute value evaluates to `None`, that attribute is not included | |
103 in the output: | |
104 | |
105 >>> print Element('a', name=None) | |
106 <a/> | |
107 | |
108 Attribute names that conflict with Python keywords can be specified by | |
109 appending an underscore: | |
110 | |
111 >>> print Element('div', class_='warning') | |
112 <div class="warning"/> | |
113 | |
114 Nested elements can be added to an element using item access notation. | |
115 The call notation can also be used for this and for adding attributes | |
116 using keyword arguments, as one would do in the constructor. | |
117 | |
118 >>> print Element('ul')(Element('li'), Element('li')) | |
119 <ul><li/><li/></ul> | |
120 >>> print Element('a')('Label') | |
121 <a>Label</a> | |
122 >>> print Element('a')('Label', href="target") | |
123 <a href="target">Label</a> | |
124 | |
125 Text nodes can be nested in an element by adding strings instead of | |
126 elements. Any special characters in the strings are escaped automatically: | |
127 | |
128 >>> print Element('em')('Hello world') | |
129 <em>Hello world</em> | |
130 >>> print Element('em')(42) | |
131 <em>42</em> | |
132 >>> print Element('em')('1 < 2') | |
133 <em>1 < 2</em> | |
134 | |
135 This technique also allows mixed content: | |
136 | |
137 >>> print Element('p')('Hello ', Element('b')('world')) | |
138 <p>Hello <b>world</b></p> | |
139 | |
34 | 140 Quotes are not escaped inside text nodes: |
141 >>> print Element('p')('"Hello"') | |
142 <p>"Hello"</p> | |
143 | |
1 | 144 Elements can also be combined with other elements or strings using the |
145 addition operator, which results in a `Fragment` object that contains the | |
146 operands: | |
147 | |
148 >>> print Element('br') + 'some text' + Element('br') | |
149 <br/>some text<br/> | |
150 | |
151 Elements with a namespace can be generated using the `Namespace` and/or | |
152 `QName` classes: | |
153 | |
154 >>> from markup.core import Namespace | |
155 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml') | |
156 >>> print Element(xhtml.html, lang='en') | |
157 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | |
158 """ | |
159 __slots__ = ['tag', 'attrib'] | |
160 | |
161 def __init__(self, tag_, **attrib): | |
162 Fragment.__init__(self) | |
163 self.tag = QName(tag_) | |
164 self.attrib = Attributes() | |
165 self(**attrib) | |
166 | |
167 def __call__(self, *args, **kwargs): | |
168 for attr, value in kwargs.items(): | |
169 if value is None: | |
170 continue | |
171 attr = attr.rstrip('_').replace('_', '-') | |
98
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
172 self.attrib.set(attr, escape(value)) |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
173 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
|
174 return self |
1 | 175 |
65
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
176 def __repr__(self): |
5c024cf58ecb
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
34
diff
changeset
|
177 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
|
178 |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
179 def _generate(self): |
98
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
180 attrib = Attributes() |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
181 for attr, value in self.attrib: |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
182 if not isinstance(value, basestring): |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
183 value = unicode(value) |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
184 attrib.append((attr, value)) |
bc73d3ab823f
Bugfix in `builder` module: attribute values need to be converted to strings when generating streams.
cmlenz
parents:
94
diff
changeset
|
185 yield Stream.START, (self.tag, attrib), (None, -1, -1) |
94
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
186 for kind, data, pos in Fragment._generate(self): |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
187 yield kind, data, pos |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
188 yield Stream.END, self.tag, (None, -1, -1) |
80c72e936e72
Some bugfixes and minor performance improvements for the builder module.
cmlenz
parents:
91
diff
changeset
|
189 |
1 | 190 def generate(self): |
28 | 191 """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
|
192 return Stream(self._generate()) |
1 | 193 |
194 | |
195 class ElementFactory(object): | |
28 | 196 """Factory for `Element` objects. |
197 | |
198 A new element is created simply by accessing a correspondingly named | |
199 attribute of the factory object: | |
200 | |
201 >>> factory = ElementFactory() | |
202 >>> print factory.foo | |
203 <foo/> | |
204 >>> print factory.foo(id=2) | |
205 <foo id="2"/> | |
206 | |
119
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
207 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
|
208 by calling the factory: |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
209 |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
210 >>> print factory('Hello, ', factory.em('world'), '!') |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
211 Hello, <em>world</em>! |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
212 |
28 | 213 A factory can also be bound to a specific namespace: |
214 | |
215 >>> factory = ElementFactory('http://www.w3.org/1999/xhtml') | |
216 >>> print factory.html(lang="en") | |
217 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | |
218 | |
219 The namespace for a specific element can be altered on an existing factory | |
220 by specifying the new namespace using item access: | |
221 | |
222 >>> factory = ElementFactory() | |
223 >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3)) | |
224 <html><g id="3" xmlns="http://www.w3.org/2000/svg"/></html> | |
225 | |
226 Usually, the `ElementFactory` class is not be used directly. Rather, the | |
227 `tag` instance should be used to create elements. | |
228 """ | |
1 | 229 |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
230 def __init__(self, namespace=None): |
28 | 231 """Create the factory, optionally bound to the given namespace. |
232 | |
233 @param namespace: the namespace URI for any created elements, or `None` | |
234 for no namespace | |
235 """ | |
20 | 236 if namespace and not isinstance(namespace, Namespace): |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
237 namespace = Namespace(namespace) |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
238 self.namespace = namespace |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
239 |
119
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
240 def __call__(self, *args): |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
241 return Fragment()(*args) |
61b133ed95b2
Allow creating fragments from the `tag` object in `markup.builder`.
cmlenz
parents:
98
diff
changeset
|
242 |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
243 def __getitem__(self, namespace): |
28 | 244 """Return a new factory that is bound to the specified namespace.""" |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
245 return ElementFactory(namespace) |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
246 |
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
247 def __getattr__(self, name): |
28 | 248 """Create an `Element` with the given name.""" |
19
7b589453b797
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
249 return Element(self.namespace and self.namespace[name] or name) |
1 | 250 |
251 | |
252 tag = ElementFactory() |