Mercurial > genshi > genshi-test
annotate markup/output.py @ 44:42bcb91bf025
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
author | mgood |
---|---|
date | Mon, 03 Jul 2006 23:55:03 +0000 |
parents | b8456279c444 |
children | 822089ae65ce |
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 |
14 """This module provides different kinds of serialization methods for XML event | |
15 streams. | |
16 """ | |
17 | |
18 try: | |
19 frozenset | |
20 except NameError: | |
21 from sets import ImmutableSet as frozenset | |
22 | |
18
4cbebb15a834
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
1
diff
changeset
|
23 from markup.core import Markup, Namespace, QName, Stream |
1 | 24 |
25 __all__ = ['Serializer', 'XMLSerializer', 'HTMLSerializer'] | |
26 | |
27 | |
28 class Serializer(object): | |
29 """Base class for serializers.""" | |
30 | |
31 def serialize(self, stream): | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
32 """Must be implemented by concrete subclasses to serialize the given |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
33 stream. |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
34 |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
35 This method must be implemented as a generator, producing the |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
36 serialized output incrementally as unicode strings. |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
37 """ |
1 | 38 raise NotImplementedError |
39 | |
40 | |
41 class XMLSerializer(Serializer): | |
42 """Produces XML text from an event stream. | |
43 | |
44 >>> from markup.builder import tag | |
20 | 45 >>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True)) |
1 | 46 >>> print ''.join(XMLSerializer().serialize(elem.generate())) |
47 <div><a href="foo"/><br/><hr noshade="True"/></div> | |
48 """ | |
49 | |
50 def serialize(self, stream): | |
51 ns_attrib = [] | |
52 ns_mapping = {} | |
53 | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
54 stream = _PushbackIterator(stream) |
1 | 55 for kind, data, pos in stream: |
56 | |
57 if kind is Stream.DOCTYPE: | |
58 # FIXME: what if there's no system or public ID in the input? | |
59 yield Markup('<!DOCTYPE %s "%s" "%s">\n' % data) | |
60 | |
61 elif kind is Stream.START_NS: | |
62 prefix, uri = data | |
63 if uri not in ns_mapping: | |
64 ns_mapping[uri] = prefix | |
65 if not prefix: | |
66 ns_attrib.append((QName('xmlns'), uri)) | |
67 else: | |
68 ns_attrib.append((QName('xmlns:%s' % prefix), uri)) | |
69 | |
70 elif kind is Stream.START: | |
71 tag, attrib = data | |
72 | |
73 tagname = tag.localname | |
74 if tag.namespace: | |
75 try: | |
76 prefix = ns_mapping[tag.namespace] | |
77 if prefix: | |
78 tagname = prefix + ':' + tag.localname | |
79 except KeyError: | |
80 ns_attrib.append((QName('xmlns'), tag.namespace)) | |
81 buf = ['<', tagname] | |
82 | |
83 if ns_attrib: | |
84 attrib.extend(ns_attrib) | |
85 ns_attrib = [] | |
86 for attr, value in attrib: | |
87 attrname = attr.localname | |
88 if attr.namespace: | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
89 prefix = ns_mapping.get(attr.namespace) |
1 | 90 if prefix: |
91 attrname = prefix + ':' + attrname | |
92 buf.append(' %s="%s"' % (attrname, Markup.escape(value))) | |
93 | |
94 kind, data, pos = stream.next() | |
95 if kind is Stream.END: | |
96 buf.append('/>') | |
97 else: | |
98 buf.append('>') | |
99 stream.pushback((kind, data, pos)) | |
100 | |
101 yield Markup(''.join(buf)) | |
102 | |
103 elif kind is Stream.END: | |
104 tag = data | |
105 tagname = tag.localname | |
106 if tag.namespace: | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
107 prefix = ns_mapping.get(tag.namespace) |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
108 if prefix: |
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
109 tagname = prefix + ':' + tag.localname |
1 | 110 yield Markup('</%s>' % tagname) |
111 | |
112 elif kind is Stream.TEXT: | |
113 yield Markup.escape(data, quotes=False) | |
114 | |
115 | |
116 class HTMLSerializer(Serializer): | |
117 """Produces HTML text from an event stream. | |
118 | |
119 >>> from markup.builder import tag | |
20 | 120 >>> elem = tag.div(tag.a(href='foo'), tag.br, tag.hr(noshade=True)) |
1 | 121 >>> print ''.join(HTMLSerializer().serialize(elem.generate())) |
122 <div><a href="foo"></a><br><hr noshade></div> | |
123 """ | |
124 | |
18
4cbebb15a834
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
1
diff
changeset
|
125 NAMESPACE = Namespace('http://www.w3.org/1999/xhtml') |
1 | 126 |
127 _EMPTY_ELEMS = frozenset(['area', 'base', 'basefont', 'br', 'col', 'frame', | |
128 'hr', 'img', 'input', 'isindex', 'link', 'meta', | |
129 'param']) | |
130 _BOOLEAN_ATTRS = frozenset(['selected', 'checked', 'compact', 'declare', | |
131 'defer', 'disabled', 'ismap', 'multiple', | |
132 'nohref', 'noresize', 'noshade', 'nowrap']) | |
133 | |
134 def serialize(self, stream): | |
135 ns_mapping = {} | |
136 | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
137 stream = _PushbackIterator(stream) |
1 | 138 for kind, data, pos in stream: |
139 | |
140 if kind is Stream.DOCTYPE: | |
141 yield Markup('<!DOCTYPE %s "%s" "%s">\n' % data) | |
142 | |
143 elif kind is Stream.START_NS: | |
144 prefix, uri = data | |
145 if uri not in ns_mapping: | |
146 ns_mapping[uri] = prefix | |
147 | |
148 elif kind is Stream.START: | |
149 tag, attrib = data | |
18
4cbebb15a834
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
1
diff
changeset
|
150 if tag.namespace and tag not in self.NAMESPACE: |
1 | 151 continue # not in the HTML namespace, so don't emit |
152 buf = ['<', tag.localname] | |
153 for attr, value in attrib: | |
18
4cbebb15a834
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
1
diff
changeset
|
154 if attr.namespace and attr not in self.NAMESPACE: |
1 | 155 continue # not in the HTML namespace, so don't emit |
156 if attr.localname in self._BOOLEAN_ATTRS: | |
157 if value: | |
158 buf.append(' %s' % attr.localname) | |
159 else: | |
160 buf.append(' %s="%s"' % (attr.localname, | |
161 Markup.escape(value))) | |
162 | |
163 if tag.localname in self._EMPTY_ELEMS: | |
164 kind, data, pos = stream.next() | |
165 if kind is not Stream.END: | |
166 stream.pushback((kind, data, pos)) | |
167 | |
168 yield Markup(''.join(buf + ['>'])) | |
169 | |
170 elif kind is Stream.END: | |
171 tag = data | |
18
4cbebb15a834
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
1
diff
changeset
|
172 if tag.namespace and tag not in self.NAMESPACE: |
1 | 173 continue # not in the HTML namespace, so don't emit |
174 yield Markup('</%s>' % tag.localname) | |
175 | |
176 elif kind is Stream.TEXT: | |
177 yield Markup.escape(data, quotes=False) | |
178 | |
179 | |
26
039fc5b87405
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
20
diff
changeset
|
180 class _PushbackIterator(object): |
1 | 181 """A simple wrapper for iterators that allows pushing items back on the |
182 queue via the `pushback()` method. | |
183 | |
184 That can effectively be used to peek at the next item.""" | |
185 __slots__ = ['iterable', 'buf'] | |
186 | |
187 def __init__(self, iterable): | |
188 self.iterable = iter(iterable) | |
189 self.buf = [] | |
190 | |
191 def __iter__(self): | |
192 return self | |
193 | |
194 def next(self): | |
195 if self.buf: | |
196 return self.buf.pop(0) | |
197 return self.iterable.next() | |
198 | |
199 def pushback(self, item): | |
200 self.buf.append(item) |