annotate markup/builder.py @ 27:b4f78c05e5c9 trunk

* Fix the boilerplate in the Python source files. * Some more docstrings and cosmetic fixes.
author cmlenz
date Wed, 28 Jun 2006 09:28:09 +0000
parents cc92d74ce9e5
children 35956040ba6e
rev   line source
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
1 # -*- coding: utf-8 -*-
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
2 #
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
3 # Copyright (C) 2006 Christopher Lenz
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
4 # All rights reserved.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
5 #
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
6 # This software is licensed as described in the file COPYING, which
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
7 # you should have received as part of this distribution. The terms
27
b4f78c05e5c9 * Fix the boilerplate in the Python source files.
cmlenz
parents: 20
diff changeset
8 # are also available at http://markup.cmlenz.net/wiki/License.
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
9 #
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
10 # This software consists of voluntary contributions made by many
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
11 # individuals. For the exact contribution history, see the revision
27
b4f78c05e5c9 * Fix the boilerplate in the Python source files.
cmlenz
parents: 20
diff changeset
12 # history and logs, available at http://markup.cmlenz.net/log/.
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
13
19
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
14 from markup.core import Attributes, Namespace, QName, Stream
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
15
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
16 __all__ = ['Fragment', 'Element', 'tag']
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
17
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
18
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
19 class Fragment(object):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
20 __slots__ = ['children']
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
21
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
22 def __init__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
23 self.children = []
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
24
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
25 def append(self, node):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
26 """Append an element or string as child node."""
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
27 if isinstance(node, (Element, basestring, int, float, long)):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
28 # For objects of a known/primitive type, we avoid the check for
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
29 # whether it is iterable for better performance
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
30 self.children.append(node)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
31 elif isinstance(node, Fragment):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
32 self.children += node.children
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
33 elif node is not None:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
34 try:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
35 children = iter(node)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
36 except TypeError:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
37 self.children.append(node)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
38 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
39 for child in node:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
40 self.append(children)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
41
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
42 def __add__(self, other):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
43 return Fragment()(self, other)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
44
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
45 def __call__(self, *args):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
46 for arg in args:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
47 self.append(arg)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
48 return self
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
49
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
50 def generate(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
51 """Generator that yield tags and text nodes as strings."""
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
52 def _generate():
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
53 for child in self.children:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
54 if isinstance(child, Fragment):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
55 for event in child.generate():
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
56 yield event
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
57 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
58 yield Stream.TEXT, unicode(child), (-1, -1)
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
59 return Stream(_generate())
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
60
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
61 def __iter__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
62 return iter(self.generate())
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
63
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
64 def __str__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
65 return str(self.generate())
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
66
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
67 def __unicode__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
68 return unicode(self.generate())
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
69
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
70
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
71 class Element(Fragment):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
72 """Simple XML output generator based on the builder pattern.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
73
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
74 Construct XML elements by passing the tag name to the constructor:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
75
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
76 >>> print Element('strong')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
77 <strong/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
78
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
79 Attributes can be specified using keyword arguments. The values of the
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
80 arguments will be converted to strings and any special XML characters
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
81 escaped:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
82
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
83 >>> print Element('textarea', rows=10, cols=60)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
84 <textarea rows="10" cols="60"/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
85 >>> print Element('span', title='1 < 2')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
86 <span title="1 &lt; 2"/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
87 >>> print Element('span', title='"baz"')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
88 <span title="&#34;baz&#34;"/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
89
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
90 The " character is escaped using a numerical entity.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
91 The order in which attributes are rendered is undefined.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
92
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
93 If an attribute value evaluates to `None`, that attribute is not included
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
94 in the output:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
95
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
96 >>> print Element('a', name=None)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
97 <a/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
98
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
99 Attribute names that conflict with Python keywords can be specified by
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
100 appending an underscore:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
101
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
102 >>> print Element('div', class_='warning')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
103 <div class="warning"/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
104
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
105 Nested elements can be added to an element using item access notation.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
106 The call notation can also be used for this and for adding attributes
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
107 using keyword arguments, as one would do in the constructor.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
108
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
109 >>> print Element('ul')(Element('li'), Element('li'))
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
110 <ul><li/><li/></ul>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
111 >>> print Element('a')('Label')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
112 <a>Label</a>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
113 >>> print Element('a')('Label', href="target")
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
114 <a href="target">Label</a>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
115
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
116 Text nodes can be nested in an element by adding strings instead of
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
117 elements. Any special characters in the strings are escaped automatically:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
118
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
119 >>> print Element('em')('Hello world')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
120 <em>Hello world</em>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
121 >>> print Element('em')(42)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
122 <em>42</em>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
123 >>> print Element('em')('1 < 2')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
124 <em>1 &lt; 2</em>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
125
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
126 This technique also allows mixed content:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
127
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
128 >>> print Element('p')('Hello ', Element('b')('world'))
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
129 <p>Hello <b>world</b></p>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
130
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
131 Elements can also be combined with other elements or strings using the
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
132 addition operator, which results in a `Fragment` object that contains the
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
133 operands:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
134
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
135 >>> print Element('br') + 'some text' + Element('br')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
136 <br/>some text<br/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
137
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
138 Elements with a namespace can be generated using the `Namespace` and/or
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
139 `QName` classes:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
140
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
141 >>> from markup.core import Namespace
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
142 >>> xhtml = Namespace('http://www.w3.org/1999/xhtml')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
143 >>> print Element(xhtml.html, lang='en')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
144 <html lang="en" xmlns="http://www.w3.org/1999/xhtml"/>
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
145 """
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
146 __slots__ = ['tag', 'attrib']
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
147
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
148 def __init__(self, tag_, **attrib):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
149 Fragment.__init__(self)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
150 self.tag = QName(tag_)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
151 self.attrib = Attributes()
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
152 self(**attrib)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
153
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
154 def __call__(self, *args, **kwargs):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
155 for attr, value in kwargs.items():
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
156 if value is None:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
157 continue
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
158 attr = attr.rstrip('_').replace('_', '-')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
159 self.attrib.set(attr, value)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
160 return Fragment.__call__(self, *args)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
161
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
162 def generate(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
163 """Generator that yield tags and text nodes as strings."""
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
164 def _generate():
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
165 yield Stream.START, (self.tag, self.attrib), (-1, -1)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
166 for kind, data, pos in Fragment.generate(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
167 yield kind, data, pos
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
168 yield Stream.END, self.tag, (-1, -1)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
169 return Stream(_generate())
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
170
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
171
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
172 class ElementFactory(object):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
173
19
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
174 def __init__(self, namespace=None):
20
cc92d74ce9e5 Fix tests broken in [20].
cmlenz
parents: 19
diff changeset
175 if namespace and not isinstance(namespace, Namespace):
19
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
176 namespace = Namespace(namespace)
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
177 self.namespace = namespace
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
178
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
179 def __getitem__(self, namespace):
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
180 return ElementFactory(namespace)
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
181
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
182 def __getattr__(self, name):
704f10b06507 Enable `ElementFactory` to create namespaced elements.
cmlenz
parents: 17
diff changeset
183 return Element(self.namespace and self.namespace[name] or name)
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
184
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
185
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
186 tag = ElementFactory()
Copyright (C) 2012-2017 Edgewall Software