comparison markup/builder.py @ 1:821114ec4f69

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