Mercurial > genshi > mirror
annotate markup/builder.py @ 48:a5d585dd38c4 trunk
convert the result of expressions in attributes to strings so that values like ints are output correctly
author | mgood |
---|---|
date | Tue, 04 Jul 2006 04:49:22 +0000 |
parents | 3421dd98f015 |
children | b3fdf93057ab |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2006 Christopher Lenz | |
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 | |
27 | 8 # are also available at http://markup.cmlenz.net/wiki/License. |
1 | 9 # |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
27 | 12 # history and logs, available at http://markup.cmlenz.net/log/. |
1 | 13 |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
14 from markup.core import Attributes, Namespace, QName, Stream |
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 | |
28 def append(self, node): | |
29 """Append an element or string as child node.""" | |
30 if isinstance(node, (Element, basestring, int, float, long)): | |
31 # For objects of a known/primitive type, we avoid the check for | |
32 # whether it is iterable for better performance | |
33 self.children.append(node) | |
34 elif isinstance(node, Fragment): | |
35 self.children += node.children | |
36 elif node is not None: | |
37 try: | |
38 children = iter(node) | |
39 except TypeError: | |
40 self.children.append(node) | |
41 else: | |
42 for child in node: | |
43 self.append(children) | |
44 | |
45 def __add__(self, other): | |
46 return Fragment()(self, other) | |
47 | |
48 def __call__(self, *args): | |
49 for arg in args: | |
50 self.append(arg) | |
51 return self | |
52 | |
53 def generate(self): | |
28 | 54 """Return a markup event stream for the fragment.""" |
1 | 55 def _generate(): |
56 for child in self.children: | |
57 if isinstance(child, Fragment): | |
58 for event in child.generate(): | |
59 yield event | |
60 else: | |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
1
diff
changeset
|
61 yield Stream.TEXT, unicode(child), (-1, -1) |
1 | 62 return Stream(_generate()) |
63 | |
64 def __iter__(self): | |
65 return iter(self.generate()) | |
66 | |
67 def __str__(self): | |
68 return str(self.generate()) | |
69 | |
70 def __unicode__(self): | |
71 return unicode(self.generate()) | |
72 | |
73 | |
74 class Element(Fragment): | |
75 """Simple XML output generator based on the builder pattern. | |
76 | |
77 Construct XML elements by passing the tag name to the constructor: | |
78 | |
79 >>> print Element('strong') | |
80 <strong/> | |
81 | |
82 Attributes can be specified using keyword arguments. The values of the | |
83 arguments will be converted to strings and any special XML characters | |
84 escaped: | |
85 | |
86 >>> print Element('textarea', rows=10, cols=60) | |
87 <textarea rows="10" cols="60"/> | |
88 >>> print Element('span', title='1 < 2') | |
89 <span title="1 < 2"/> | |
90 >>> print Element('span', title='"baz"') | |
91 <span title=""baz""/> | |
92 | |
93 The " character is escaped using a numerical entity. | |
94 The order in which attributes are rendered is undefined. | |
95 | |
96 If an attribute value evaluates to `None`, that attribute is not included | |
97 in the output: | |
98 | |
99 >>> print Element('a', name=None) | |
100 <a/> | |
101 | |
102 Attribute names that conflict with Python keywords can be specified by | |
103 appending an underscore: | |
104 | |
105 >>> print Element('div', class_='warning') | |
106 <div class="warning"/> | |
107 | |
108 Nested elements can be added to an element using item access notation. | |
109 The call notation can also be used for this and for adding attributes | |
110 using keyword arguments, as one would do in the constructor. | |
111 | |
112 >>> print Element('ul')(Element('li'), Element('li')) | |
113 <ul><li/><li/></ul> | |
114 >>> print Element('a')('Label') | |
115 <a>Label</a> | |
116 >>> print Element('a')('Label', href="target") | |
117 <a href="target">Label</a> | |
118 | |
119 Text nodes can be nested in an element by adding strings instead of | |
120 elements. Any special characters in the strings are escaped automatically: | |
121 | |
122 >>> print Element('em')('Hello world') | |
123 <em>Hello world</em> | |
124 >>> print Element('em')(42) | |
125 <em>42</em> | |
126 >>> print Element('em')('1 < 2') | |
127 <em>1 < 2</em> | |
128 | |
129 This technique also allows mixed content: | |
130 | |
131 >>> print Element('p')('Hello ', Element('b')('world')) | |
132 <p>Hello <b>world</b></p> | |
133 | |
34 | 134 Quotes are not escaped inside text nodes: |
135 >>> print Element('p')('"Hello"') | |
136 <p>"Hello"</p> | |
137 | |
1 | 138 Elements can also be combined with other elements or strings using the |
139 addition operator, which results in a `Fragment` object that contains the | |
140 operands: | |
141 | |
142 >>> print Element('br') + 'some text' + Element('br') | |
143 <br/>some text<br/> | |
144 | |
145 Elements with a namespace can be generated using the `Namespace` and/or | |
146 `QName` classes: | |
147 | |
148 >>> from markup.core import Namespace | |
149 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml') | |
150 >>> print Element(xhtml.html, lang='en') | |
151 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | |
152 """ | |
153 __slots__ = ['tag', 'attrib'] | |
154 | |
155 def __init__(self, tag_, **attrib): | |
156 Fragment.__init__(self) | |
157 self.tag = QName(tag_) | |
158 self.attrib = Attributes() | |
159 self(**attrib) | |
160 | |
161 def __call__(self, *args, **kwargs): | |
162 for attr, value in kwargs.items(): | |
163 if value is None: | |
164 continue | |
165 attr = attr.rstrip('_').replace('_', '-') | |
166 self.attrib.set(attr, value) | |
167 return Fragment.__call__(self, *args) | |
168 | |
169 def generate(self): | |
28 | 170 """Return a markup event stream for the fragment.""" |
1 | 171 def _generate(): |
172 yield Stream.START, (self.tag, self.attrib), (-1, -1) | |
173 for kind, data, pos in Fragment.generate(self): | |
174 yield kind, data, pos | |
175 yield Stream.END, self.tag, (-1, -1) | |
176 return Stream(_generate()) | |
177 | |
178 | |
179 class ElementFactory(object): | |
28 | 180 """Factory for `Element` objects. |
181 | |
182 A new element is created simply by accessing a correspondingly named | |
183 attribute of the factory object: | |
184 | |
185 >>> factory = ElementFactory() | |
186 >>> print factory.foo | |
187 <foo/> | |
188 >>> print factory.foo(id=2) | |
189 <foo id="2"/> | |
190 | |
191 A factory can also be bound to a specific namespace: | |
192 | |
193 >>> factory = ElementFactory('http://www.w3.org/1999/xhtml') | |
194 >>> print factory.html(lang="en") | |
195 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/> | |
196 | |
197 The namespace for a specific element can be altered on an existing factory | |
198 by specifying the new namespace using item access: | |
199 | |
200 >>> factory = ElementFactory() | |
201 >>> print factory.html(factory['http://www.w3.org/2000/svg'].g(id=3)) | |
202 <html><g id="3" xmlns="http://www.w3.org/2000/svg"/></html> | |
203 | |
204 Usually, the `ElementFactory` class is not be used directly. Rather, the | |
205 `tag` instance should be used to create elements. | |
206 """ | |
1 | 207 |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
208 def __init__(self, namespace=None): |
28 | 209 """Create the factory, optionally bound to the given namespace. |
210 | |
211 @param namespace: the namespace URI for any created elements, or `None` | |
212 for no namespace | |
213 """ | |
20 | 214 if namespace and not isinstance(namespace, Namespace): |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
215 namespace = Namespace(namespace) |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
216 self.namespace = namespace |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
217 |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
218 def __getitem__(self, namespace): |
28 | 219 """Return a new factory that is bound to the specified namespace.""" |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
220 return ElementFactory(namespace) |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
221 |
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
222 def __getattr__(self, name): |
28 | 223 """Create an `Element` with the given name.""" |
19
704f10b06507
Enable `ElementFactory` to create namespaced elements.
cmlenz
parents:
17
diff
changeset
|
224 return Element(self.namespace and self.namespace[name] or name) |
1 | 225 |
226 | |
227 tag = ElementFactory() |