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
|
|
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 < 2"/>
|
|
87 >>> print Element('span', title='"baz"')
|
|
88 <span title=""baz""/>
|
|
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 < 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()
|