comparison genshi/builder.py @ 230:24757b771651

Renamed Markup to Genshi in repository.
author cmlenz
date Mon, 11 Sep 2006 15:07:07 +0000
parents markup/builder.py@41db0260ebb1
children 8e75b83d3e71 676b78a0c5a0
comparison
equal deleted inserted replaced
229:110d69dbbda3 230:24757b771651
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2006 Edgewall Software
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
8 # are also available at http://genshi.edgewall.org/wiki/License.
9 #
10 # This software consists of voluntary contributions made by many
11 # individuals. For the exact contribution history, see the revision
12 # history and logs, available at http://genshi.edgewall.org/log/.
13
14 from genshi.core import Attrs, Namespace, QName, Stream, START, END, TEXT
15
16 __all__ = ['Fragment', 'Element', 'tag']
17
18
19 class Fragment(object):
20 """Represents a markup fragment, which is basically just a list of element
21 or text nodes.
22 """
23 __slots__ = ['children']
24
25 def __init__(self):
26 self.children = []
27
28 def __add__(self, other):
29 return Fragment()(self, other)
30
31 def __call__(self, *args):
32 map(self.append, args)
33 return self
34
35 def __iter__(self):
36 return self._generate()
37
38 def __repr__(self):
39 return '<%s>' % self.__class__.__name__
40
41 def __str__(self):
42 return str(self.generate())
43
44 def __unicode__(self):
45 return unicode(self.generate())
46
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):
54 self.children.extend(node.children)
55 elif node is not None:
56 try:
57 map(self.append, iter(node))
58 except TypeError:
59 self.children.append(node)
60
61 def _generate(self):
62 for child in self.children:
63 if isinstance(child, Fragment):
64 for event in child._generate():
65 yield event
66 else:
67 if not isinstance(child, basestring):
68 child = unicode(child)
69 yield TEXT, child, (None, -1, -1)
70
71 def generate(self):
72 """Return a markup event stream for the fragment."""
73 return Stream(self._generate())
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 &lt; 2"/>
92 >>> print Element('span', title='"baz"')
93 <span title="&#34;baz&#34;"/>
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 &lt; 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
136 Quotes are not escaped inside text nodes:
137 >>> print Element('p')('"Hello"')
138 <p>"Hello"</p>
139
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 genshi.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 = Attrs()
161 for attr, value in attrib.items():
162 if value is not None:
163 if not isinstance(value, basestring):
164 value = unicode(value)
165 self.attrib.append((QName(attr.rstrip('_').replace('_', '-')),
166 value))
167
168 def __call__(self, *args, **kwargs):
169 for attr, value in kwargs.items():
170 if value is not None:
171 if not isinstance(value, basestring):
172 value = unicode(value)
173 self.attrib.set(attr.rstrip('_').replace('_', '-'), value)
174 Fragment.__call__(self, *args)
175 return self
176
177 def __repr__(self):
178 return '<%s "%s">' % (self.__class__.__name__, self.tag)
179
180 def _generate(self):
181 yield START, (self.tag, self.attrib), (None, -1, -1)
182 for kind, data, pos in Fragment._generate(self):
183 yield kind, data, pos
184 yield END, self.tag, (None, -1, -1)
185
186 def generate(self):
187 """Return a markup event stream for the fragment."""
188 return Stream(self._generate())
189
190
191 class ElementFactory(object):
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
203 Markup fragments (lists of nodes without a parent element) can be created
204 by calling the factory:
205
206 >>> print factory('Hello, ', factory.em('world'), '!')
207 Hello, <em>world</em>!
208
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 """
225
226 def __init__(self, namespace=None):
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 """
232 if namespace and not isinstance(namespace, Namespace):
233 namespace = Namespace(namespace)
234 self.namespace = namespace
235
236 def __call__(self, *args):
237 return Fragment()(*args)
238
239 def __getitem__(self, namespace):
240 """Return a new factory that is bound to the specified namespace."""
241 return ElementFactory(namespace)
242
243 def __getattr__(self, name):
244 """Create an `Element` with the given name."""
245 return Element(self.namespace and self.namespace[name] or name)
246
247
248 tag = ElementFactory()
Copyright (C) 2012-2017 Edgewall Software