root/livinglogic.python.xist/src/ll/xist/publishers.py @ 3156:ba916ca5d927

Revision 3156:ba916ca5d927, 8.6 KB (checked in by Walter Doerwald <walter@…>, 12 years ago)

Invert if logic.

Line 
1# -*- coding: utf-8 -*-
2
3## Copyright 1999-2008 by LivingLogic AG, Bayreuth/Germany
4## Copyright 1999-2008 by Walter Dörwald
5##
6## All Rights Reserved
7##
8## See xist/__init__.py for the license
9
10
11"""
12<p>This module contains classes that may be used as publishing
13handlers in <pyref module="ll.xist.xsc" class="Node" method="publish"><meth>publish</meth></pyref>.</p>
14"""
15
16
17import sys, codecs
18
19from ll import misc, url
20from ll import xml_codec # registers the "xml" encoding
21
22import xsc, helpers
23
24
25__docformat__ = "xist"
26
27
28def cssescapereplace(exc):
29    """
30    PEP 293 codec callback that escapes unencodable character for CSS output.
31    """
32    if not isinstance(exc, UnicodeEncodeError):
33        raise TypeError("don't know how to handle %r" % exc)
34    return (helpers.cssescapereplace(exc.object[exc.start:exc.end], exc.encoding), exc.end)
35codecs.register_error("cssescapereplace", cssescapereplace)
36
37
38class Publisher(object):
39    """
40    A <class>Publisher</class> object is used for serializing an &xist; tree into
41    a byte sequence.
42    """
43
44    def __init__(self, encoding=None, xhtml=1, validate=True, prefixes={}, prefixdefault=False, hidexmlns=()):
45        """
46        <p><arg>encoding</arg> specifies the encoding to be used for the byte sequence.
47        If <lit>None</lit> is used the encoding in the &xml; declaration will be
48        used. If there is none, UTF-8 will be used.</p>
49
50        <p>With the parameter <arg>xhtml</arg> you can specify if you want &html; output:</p>
51        <dl>
52        <dt>&html; (<lit><arg>xhtml</arg>==0</lit>)</dt>
53        <dd>Elements with a empty content model will be published as
54        <markup>&lt;foo&gt;</markup>.</dd>
55        <dt>&html; browser compatible &xml; (<lit><arg>xhtml</arg>==1</lit>)</dt>
56        <dd>Elements with an empty content model will be published as <markup>&lt;foo /&gt;</markup>
57        and others that just happen to be empty as <markup>&lt;foo&gt;&lt;/foo&gt;</markup>. This
58        is the default.</dd>
59        <dt>Pure &xml; (<lit><arg>xhtml</arg>==2</lit>)</dt>
60        <dd>All empty elements will be published as <markup>&lt;foo/&gt;</markup>.</dd>
61        </dl>
62
63        <p><arg>validate</arg> specifies whether validation should be done before
64        publishing.</p>
65
66        <p><arg>prefixes</arg> is a dictionary that specifies which namespace
67        prefixes should be used for publishing. Keys in the dictionary are either
68        namespace names or objects that have an <lit>xmlns</lit> attribute which
69        is the namespace name. Value can be:</p>
70
71        <dl>
72        <dt><lit>False</lit></dt>
73        <dd>Treat elements in this namespace as if they are not in any namespace
74        (if global attributes from this namespace are encountered, a prefix will
75        be used nonetheless).</dd>
76        <dt><lit>None</lit></dt>
77        <dd>Treat the namespace as the default namespaces (i.e. use unprefixed
78        element names). Global attributes will again result in a prefix.</dd>
79        <dt><lit>True</lit></dt>
80        <dd>The publisher uses a unique non-empty prefix for this namespace.</dd>
81        <dt>A string</dt>
82        <dd>Use this prefix for the namespace.</dd>
83        </dl>
84
85        <p>If an element or attribute is encountered whose namespace is not in
86        <arg>prefixes</arg> <arg>prefixdefault</arg> is used as the fallback.</p>
87
88        <p><arg>hidexmlns</arg> can be a list or set that contains namespace names
89        for which no <lit>xmlns</lit> attributes should be published. (This can be
90        used to hide the namespace declarations for e.g. Java taglibs.)</p>
91        """
92        self.base = None
93        self.encoding = encoding
94        self.encoder = None
95        self.xhtml = xhtml
96        self.validate = validate
97        self.prefixes = dict((xsc.nsname(xmlns), prefix) for (xmlns, prefix) in prefixes.iteritems())
98        self.prefixdefault = prefixdefault
99        self.hidexmlns = set(xsc.nsname(xmlns) for xmlns in hidexmlns)
100        self._ns2prefix = {}
101        self._prefix2ns = {}
102
103    def encode(self, text):
104        """
105        Encode <arg>text</arg> with the encoding and error handling currently
106        active and return the resulting byte string.
107        """
108        return self.encoder.encode(text)
109
110    def encodetext(self, text):
111        """
112        <p>Encode <arg>test</arg> as text data. <arg>text</arg> must
113        be a <class>unicode</class> object. The publisher will apply the configured
114        encoding, error handling and the current text filter (which escapes
115        characters that can't appear in text data (like <lit>&lt;</lit> etc.))
116        and return the resulting <class>str</class> object.
117        """
118        self.encoder.errors = self.__errors[-1]
119        result = self.encoder.encode(self.__textfilters[-1](text))
120        self.encoder.errors = "strict"
121        return result
122
123    def pushtextfilter(self, filter):
124        """
125        <p>pushes a new text filter function on the text filter stack.
126        This function is responsible for escaping characters that can't appear
127        in text data (like <lit>&lt;</lit>)). This is used to switch on escaping
128        of <lit>"</lit> inside attribute values.</p>
129        """
130        self.__textfilters.append(filter)
131
132    def poptextfilter(self):
133        """
134        <p>pops the current text filter function from the stack.</p>
135        """
136        self.__textfilters.pop()
137
138    def pusherrors(self, errors):
139        """
140        <p>pushes a new error handling scheme onto the error handling stack.</p>
141        """
142        self.__errors.append(errors)
143
144    def poperrors(self):
145        """
146        <p>pop the current error handling scheme from the error handling stack.</p>
147        """
148        self.__errors.pop()
149
150    def _newprefix(self):
151        prefix = "ns"
152        suffix = 2
153        while True:
154            if prefix not in self._prefix2ns:
155                return prefix
156            prefix = "ns%d" % suffix
157            suffix += 1
158
159    def getencoding(self):
160        """
161        Return the encoding currently in effect.
162        """
163        if self.encoding is not None:
164            # The encoding has been prescribed, so this *will* be used.
165            return self.encoding
166        elif self.encoder is not None:
167            # The encoding is determined by the XML declaration in the output,
168            # so use that if it has been determined already. If the encoder hasn't
169            # determined the encoding yet (e.g. because nothing has been output
170            # yet) use utf-8 (which will be what the encoder eventually will decide
171            # to use too). Note that this will not work if nothing has been output
172            # yet, but later an XML declaration (using a different encoding) will
173            # be output, but this shouldn't happen anyway.
174            return self.encoder.encoding or "utf-8"
175        return "utf-8"
176
177    def getprefix(self, object):
178        """
179        Can be used during publication by custom publish methods: Return the prefix
180        configured for object <arg>object</arg>.
181        """
182        xmlns = getattr(object, "xmlns")
183        if xmlns is None:
184            return None
185        emptyok = isinstance(object, xsc.Element) # If it's e.g. a procinst assume we need a non-empty prefix
186        try:
187            prefix = self._ns2prefix[xmlns]
188        except KeyError: # A namespace we haven't encountered yet
189            if xmlns == xsc.xml_xmlns: # We don't need a namespace mapping for the xml namespace
190                prefix = "xml"
191            else:
192                prefix = self.prefixes.get(xmlns, self.prefixdefault)
193                # global attributes always require prefixed names
194                if prefix is True or ((prefix is None or prefix is False) and not emptyok):
195                    prefix = self._newprefix()
196                if prefix is not False:
197                    try:
198                        oldxmlns = self._prefix2ns[prefix]
199                    except KeyError:
200                        pass
201                    else:
202                        # If this prefix has already been used for another namespace, we need a new one
203                        if oldxmlns != xmlns:
204                            prefix = self._newprefix()
205                    self._ns2prefix[xmlns] = prefix
206                    self._prefix2ns[prefix] = xmlns
207        else:
208            # We can't use the unprefixed names for global attributes
209            if (prefix is None or prefix is False) and not emptyok:
210                # Use a new one
211                prefix = self._newprefix()
212                self._ns2prefix[xmlns] = prefix
213                self._prefix2ns[prefix] = xmlns
214        return prefix
215
216    def publish(self, node, base=None):
217        """
218        <p>publish the node <arg>node</arg>. This method is a generator that
219        will yield the resulting &xml; byte sequence in fragments.</p>
220        """
221        self._ns2prefix.clear()
222        self._prefix2ns.clear()
223        # iterate through every node in the tree
224        for n in node.walknode(xsc.Node):
225            self.getprefix(n)
226
227        # Do we have to publish xmlns attributes?
228        self._publishxmlns = False
229        if self._ns2prefix:
230            # Determine if we have multiple roots
231            if isinstance(node, xsc.Frag):
232                count = 0
233                for child in node:
234                    if isinstance(node, xsc.Element) and node.xmlns not in self.hidexmlns:
235                        count += 1
236                if count > 1:
237                    raise xsc.MultipleRootsError()
238            self._publishxmlns = True
239
240        self.inattr = 0
241        self.__textfilters = [ helpers.escapetext ]
242
243        self.__errors = [ "xmlcharrefreplace" ]
244
245        self.base = url.URL(base)
246        self.node = node
247
248        self.encoder = codecs.getincrementalencoder("xml")(encoding=self.encoding)
249
250        for part in self.node.publish(self):
251            yield part
252        rest = self.encoder.encode(u"", True) # finish encoding and flush buffers
253        if rest:
254            yield rest
255   
256        self.inattr = 0
257        self.__textfilters = [ helpers.escapetext ]
258
259        self.__errors = [ "xmlcharrefreplace" ]
260
261        self.publishxmlns = False
262        self._ns2prefix.clear()
263        self._prefix2ns.clear()
264
265        self.encoder = None
Note: See TracBrowser for help on using the browser.