root/livinglogic.python.xist/src/ll/xist/parsers.py @ 2559:8d4552168c39

Revision 2559:8d4552168c39, 29.5 KB (checked in by Walter Doerwald <walter@…>, 13 years ago)

Add setuptools to the list of requirements.

Update NEWS items.

parsed() can now return a different node (This is used by specials.url)

The PI specials.url has been added that does the same URL handling as
xsc.URLAttr.

Unused stuff (encode, Queue, Esc*Text) has been removed from presenters.
NormalPresenter? has been dropped.

Fixed a small bug in CodePresenter?.presentProcInst().

Exceptions and warning no longer have to output the node location
themselves as it is included in the normal repr() output.

Fixed a few str() methods for warnings that still used astyle.color().

The Node methods repr() and asrepr() have been removed.

Renamed venom to detox.

Line 
1#! /usr/bin/env python
2# -*- coding: iso-8859-1 -*-
3
4## Copyright 1999-2006 by LivingLogic AG, Bayreuth/Germany.
5## Copyright 1999-2006 by Walter Dörwald
6##
7## All Rights Reserved
8##
9## See xist/__init__.py for the license
10
11"""
12<par>This file contains everything you need to parse &xist; objects from files, strings, &url;s etc.</par>
13
14<par>It contains different &sax;2 parser driver classes (mostly for sgmlop, everything else
15is from <app moreinfo="http://pyxml.sf.net/">PyXML</app>). It includes a
16<pyref class="HTMLParser"><class>HTMLParser</class></pyref> that uses sgmlop
17to parse &html; and emit &sax;2 events.</par>
18"""
19
20import sys, os, os.path, warnings, cStringIO
21
22from xml import sax
23from xml.parsers import sgmlop
24from xml.sax import expatreader
25from xml.sax import saxlib
26from xml.sax import handler
27from xml.dom import html as htmldtd
28
29from ll import url
30
31import xsc, utils, cssparsers
32from ns import html
33
34
35class SGMLOPParser(sax.xmlreader.XMLReader, sax.xmlreader.Locator):
36    """
37    This is a rudimentary, buggy, halfworking, untested SAX2 drivers for sgmlop that
38    only works in the context of &xist;. And I didn't even know, what I was doing.
39    """
40    _whichparser = sgmlop.XMLParser
41
42    def __init__(self, bufsize=8000):
43        sax.xmlreader.XMLReader.__init__(self)
44        self.encoding = None
45        self._parser = None
46        self._bufsize = bufsize
47
48    def _makestring(self, data):
49        return unicode(data, self.encoding).replace(u"\r\n", u"\n").replace(u"\r", u"\n")
50
51    def reset(self):
52        self.close()
53        self.source = None
54        self.lineNumber = -1
55
56    def feed(self, data):
57        if self._parser is None:
58            self._parser = self._whichparser()
59            self._parser.register(self)
60            self._cont_handler.startDocument()
61        self._parser.feed(data)
62
63    def close(self):
64        if self._parser is not None:
65            self._cont_handler.endDocument()
66            self._parser.close()
67            self._parser.register(None)
68            self._parser = None
69
70    def parse(self, source):
71        self.source = source
72        stream = source.getByteStream()
73        encoding = source.getEncoding()
74        if encoding is None:
75            encoding = "utf-8"
76        self.encoding = encoding
77        self._cont_handler.setDocumentLocator(self)
78        self._cont_handler.startDocument()
79        self.lineNumber = 1
80        # we don't keep a column number, because otherwise parsing would be much to slow
81        self.headerJustRead = False # will be used for skipping whitespace after the XML header
82
83        parsed = False
84        try:
85            while True:
86                data = stream.read(self._bufsize)
87                if not data:
88                    if not parsed:
89                        self.feed("")
90                    break
91                while True:
92                    pos = data.find("\n")
93                    if pos==-1:
94                        break
95                    self.feed(data[:pos+1])
96                    self.parsed = True
97                    data = data[pos+1:]
98                    self.lineNumber += 1
99                self.feed(data)
100                self.parsed = True
101        except (SystemExit, KeyboardInterrupt):
102            self.close()
103            self.source = None
104            self.encoding = None
105            raise
106        except Exception, exc:
107            try:
108                self.close()
109            except SystemExit:
110                raise
111            except KeyboardInterrupt:
112                raise
113            except Exception, exc2:
114                self.source = None
115                self.encoding = None
116            errhandler = self.getErrorHandler()
117            if errhandler is not None:
118                errhandler.fatalError(exc)
119            else:
120                raise
121        self.close()
122        self.source = None
123        self.encoding = None
124
125    # Locator methods will be called by the application
126    def getColumnNumber(self):
127        return -1
128
129    def getLineNumber(self):
130        if self._parser is None:
131            return -1
132        return self.lineNumber
133
134    def getPublicId(self):
135        if self.source is None:
136            return None
137        return self.source.getPublicId()
138
139    def getSystemId(self):
140        if self.source is None:
141            return None
142        return self.source.getSystemId()
143
144    def setFeature(self, name, state):
145        if name == handler.feature_namespaces:
146            if state:
147                raise sax.SAXNotSupportedException("no namespace processing available")
148        elif name == handler.feature_external_ges:
149            if state:
150                raise sax.SAXNotSupportedException("processing of external general entities not available")
151        else:
152            sax.xmlreader.IncrementalParser.setFeature(self, name, state)
153
154    def getFeature(self, name):
155        if name == handler.feature_namespaces:
156            return 0
157        else:
158            return sax.xmlreader.IncrementalParser.setFeature(self, name)
159
160    def handle_comment(self, data):
161        self.getContentHandler().comment(self._makestring(data))
162        self.headerJustRead = False
163
164    # don't define handle_charref or handle_cdata, so we will get those through handle_data
165    # but unfortunately we have to define handle_charref here, because of a bug in
166    # sgmlop: unicode characters i.e. "&#8364;" don't work.
167
168    def handle_charref(self, data):
169        data = self._makestring(data)
170        if data.startswith(u"x"):
171            data = unichr(int(data[1:], 16))
172        else:
173            data = unichr(int(data))
174        if not self.headerJustRead or not data.isspace():
175            self.getContentHandler().characters(data)
176            self.headerJustRead = False
177
178    def handle_data(self, data):
179        data = self._makestring(data)
180        if not self.headerJustRead or not data.isspace():
181            self.getContentHandler().characters(data)
182            self.headerJustRead = False
183
184    def handle_proc(self, target, data):
185        target = self._makestring(target)
186        data = self._makestring(data)
187        if target != u'xml': # Don't report <?xml?> as a processing instruction
188            self.getContentHandler().processingInstruction(target, data)
189            self.headerJustRead = False
190        else: # extract the encoding
191            encoding = utils.findattr(data, u"encoding")
192            if encoding is not None:
193                self.encoding = encoding
194            self.headerJustRead = True
195
196    def handle_entityref(self, name):
197        try:
198            c = {"lt": u"<", "gt": u">", "amp": u"&", "quot": u'"', "apos": u"'"}[name]
199        except KeyError:
200            self.getContentHandler().skippedEntity(self._makestring(name))
201        else:
202            self.getContentHandler().characters(c)
203        self.headerJustRead = False
204
205    def finish_starttag(self, name, attrs):
206        newattrs = sax.xmlreader.AttributesImpl({})
207        for (attrname, attrvalue) in attrs.items():
208            if attrvalue is None:
209                attrvalue = attrname
210            else:
211                attrvalue = self._string2fragment(self._makestring(attrvalue))
212            newattrs._attrs[self._makestring(attrname)] = attrvalue
213        self.getContentHandler().startElement(self._makestring(name), newattrs)
214        self.headerJustRead = False
215
216    def finish_endtag(self, name):
217        self.getContentHandler().endElement(self._makestring(name))
218        self.headerJustRead = False
219
220    def _string2fragment(self, text):
221        """
222        parses a string that might contain entities into a fragment
223        with text nodes, entities and character references.
224        """
225        if text is None:
226            return xsc.Null
227        ct = getattr(self.getContentHandler(), "createText", xsc.Text)
228        node = xsc.Frag()
229        while True:
230            texts = text.split(u"&", 1)
231            text = texts[0]
232            if text:
233                node.append(ct(text))
234            if len(texts)==1:
235                break
236            texts = texts[1].split(u";", 1)
237            name = texts[0]
238            if len(texts)==1:
239                raise xsc.MalformedCharRefWarning(name)
240            if name.startswith(u"#"):
241                try:
242                    if name.startswith(u"#x"):
243                        node.append(ct(unichr(int(name[2:], 16))))
244                    else:
245                        node.append(ct(unichr(int(name[1:]))))
246                except ValueError:
247                    raise xsc.MalformedCharRefWarning(name)
248            else:
249                try:
250                    c = {"lt": u"<", "gt": u">", "amp": u"&", "quot": u'"', "apos": u"'"}[name]
251                except KeyError:
252                    node.append(self.getContentHandler().createEntity(name))
253                else:
254                    node.append(ct(c))
255            text = texts[1]
256        if not node:
257            node.append(ct(u""))
258        return node
259
260
261class BadEntityParser(SGMLOPParser):
262    """
263    <par>A &sax;2 parser that recognizes the character entities
264    defined in &html; and tries to pass on unknown or malformed
265    entities to the handler literally.</par>
266    """
267
268    def handle_entityref(self, name):
269        try:
270            c = {"lt": u"<", "gt": u">", "amp": u"&", "quot": u'"', "apos": u"'"}[name]
271        except KeyError:
272            name = self._makestring(name)
273            try:
274                self.getContentHandler().skippedEntity(name)
275            except xsc.IllegalEntityError:
276                try:
277                    entity = html.entity(name, xml=True)
278                except xsc.IllegalEntityError:
279                    self.getContentHandler().characters(u"&%s;" % name)
280                else:
281                    if issubclass(entity, xsc.CharRef):
282                        self.getContentHandler().characters(unichr(entity.codepoint))
283                    else:
284                        self.getContentHandler().characters(u"&%s;" % name)
285        else:
286            self.getContentHandler().characters(c)
287        self.headerJustRead = False
288
289    def _string2fragment(self, text):
290        """
291        This version tries to pass illegal content literally.
292        """
293        if text is None:
294            return xsc.Null
295        node = xsc.Frag()
296        ct = self.getContentHandler().createText
297        while True:
298            texts = text.split(u"&", 1)
299            text = texts[0]
300            if text:
301                node.append(ct(text))
302            if len(texts)==1:
303                break
304            texts = texts[1].split(u";", 1)
305            name = texts[0]
306            if len(texts)==1: # no ; found, so it's no entity => append it literally
307                name = u"&" + name
308                warnings.warn(xsc.MalformedCharRefWarning(name))
309                node.append(ct(name))
310                break
311            else:
312                if name.startswith(u"#"): # character reference
313                    try:
314                        if name.startswith(u"#x"): # hexadecimal character reference
315                            node.append(ct(unichr(int(name[2:], 16))))
316                        else: # decimal character reference
317                            node.append(ct(unichr(int(name[1:]))))
318                    except (ValueError, OverflowError): # illegal format => append it literally
319                        name = u"&%s;" % name
320                        warnings.warn(xsc.MalformedCharRefWarning(name))
321                        node.append(ct(name))
322                else: # entity reference
323                    try:
324                        entity = {"lt": u"<", "gt": u">", "amp": u"&", "quot": u'"', "apos": u"'"}[name]
325                    except KeyError:
326                        try:
327                            entity = self.getContentHandler().createEntity(name)
328                        except xsc.IllegalEntityError:
329                            try:
330                                entity = html.entity(name, xml=True)
331                                if issubclass(entity, xsc.CharRef):
332                                    entity = ct(unichr(entity.codepoint))
333                                else:
334                                    entity = entity()
335                            except xsc.IllegalEntityError:
336                                name = u"&%s;" % name
337                                warnings.warn(xsc.MalformedCharRefWarning(name))
338                                entity = ct(name)
339                    else:
340                        entity = ct(entity)
341                    node.append(entity)
342            text = texts[1]
343        return node
344
345    def handle_charref(self, data):
346        data = self._makestring(data)
347        try:
348            if data.startswith("x"):
349                data = unichr(int(data[1:], 16))
350            else:
351                data = unichr(int(data))
352        except (ValueError, OverflowError):
353            data = u"&#%s;" % data
354        if not self.headerJustRead or not data.isspace():
355            self.getContentHandler().characters(data)
356            self.headerJustRead = False
357
358
359class HTMLParser(BadEntityParser):
360    """
361    <par>A &sax;2 parser that can parse &html;.</par>
362    """
363
364    _whichparser = sgmlop.SGMLParser
365
366    def __init__(self, bufsize=2**16-20):
367        BadEntityParser.__init__(self, bufsize)
368        self._stack = []
369
370    def reset(self):
371        self._stack = []
372        BadEntityParser.reset(self)
373
374    def close(self):
375        while self._stack: # close all open elements
376            self.finish_endtag(self._stack[-1])
377        BadEntityParser.close(self)
378
379    def finish_starttag(self, name, attrs):
380        name = name.lower()
381
382        # guess omitted close tags
383        while self._stack and self._stack[-1].upper() in htmldtd.HTML_OPT_END and name not in htmldtd.HTML_DTD.get(self._stack[-1], []):
384            BadEntityParser.finish_endtag(self, self._stack[-1])
385            del self._stack[-1]
386
387        # Check whether this element is allowed in the current context
388        if self._stack and name not in htmldtd.HTML_DTD.get(self._stack[-1], []):
389            warnings.warn(xsc.IllegalDTDChildWarning(name, self._stack[-1]))
390
391        # Skip unknown attributes (but warn about them)
392        newattrs = {}
393        element = html.element(name, xml=True)
394        for (attrname, attrvalue) in attrs:
395            if attrname=="xmlns" or ":" in attrname or element.Attrs.isallowed(attrname.lower(), xml=True):
396                newattrs[attrname.lower()] = attrvalue
397            else:
398                warnings.warn(xsc.IllegalAttrError(element.Attrs, attrname.lower(), xml=True))
399        BadEntityParser.finish_starttag(self, name, newattrs)
400
401        if name.upper() in htmldtd.HTML_FORBIDDEN_END:
402            # close tags immediately for which we won't get an end
403            BadEntityParser.finish_endtag(self, name)
404            return 0
405        else:
406            self._stack.append(name)
407        return 1
408
409    def finish_endtag(self, name):
410        name = name.lower()
411        if name.upper() in htmldtd.HTML_FORBIDDEN_END:
412            # do nothing: we've already closed it
413            return
414        if name in self._stack:
415            # close any open elements that were not closed explicitely
416            while self._stack and self._stack[-1] != name:
417                BadEntityParser.finish_endtag(self, self._stack[-1])
418                del self._stack[-1]
419            BadEntityParser.finish_endtag(self, name)
420            del self._stack[-1]
421        else:
422            warnings.warn(xsc.IllegalCloseTagWarning(name))
423
424
425class ExpatParser(expatreader.ExpatParser):
426    def reset(self):
427        expatreader.ExpatParser.reset(self)
428        self._parser.UseForeignDTD(True)
429
430
431class Parser(object):
432    """
433    <par>It is the job of a <class>Parser</class> to create the object tree from the
434    &sax; events generated by the underlying &sax; parser.</par>
435    """
436
437    def __init__(self, saxparser=SGMLOPParser, nspool=None, prefixes=None, tidy=False, loc=True, validate=True, encoding=None):
438        """
439        <par>Create a new <class>Parser</class> instance.</par>
440
441        <par>Arguments have the following meaning:</par>
442        <dlist>
443        <term><arg>saxparser</arg></term><item><par>a callable that returns an instance of a &sax;2 compatible parser.
444        &xist; itself provides several &sax;2 parsers
445        (all based on Fredrik Lundh's <app>sgmlop</app> from <app moreinfo="http://pyxml.sf.net/">PyXML</app>):</par>
446        <ulist>
447        <item><pyref module="ll.xist.parsers" class="SGMLOPParser"><class>SGMLOPParser</class></pyref>
448        (which is the default if the <arg>saxparser</arg> argument is not given);</item>
449        <item><pyref module="ll.xist.parsers" class="BadEntityParser"><class>BadEntityParser</class></pyref>
450        (which is based on <class>SGMLOPParser</class> and tries to pass on unknown entity references as literal content);</item>
451        <item><pyref module="ll.xist.parsers" class="HTMLParser"><class>HTMLParser</class></pyref> (which is
452        based on BadEntityParser and tries to make sense of &html; sources).</item>
453        </ulist>
454        </item>
455
456        <term><arg>tidy</arg></term><item>If <arg>tidy</arg> is true, <link href="http://xmlsoft.org/">libxml2</link>'s
457        &html; parser will be used for parsing broken &html;.</item>
458        <term><arg>nspool</arg></term><item>an instance of <pyref module="ll.xist.xsc" class="NSPool"><class>ll.xist.xsc.NSPool</class></pyref>;
459        From this namespace pool namespaces will be taken when the parser
460        encounters <lit>xmlns</lit> attributes.</item>
461
462        <term><arg>prefixes</arg></term><item>an instance of <pyref module="ll.xist.xsc" class="Prefixes"><class>ll.xist.xsc.Prefixes</class></pyref>;
463        Specifies which namespace modules should be available during parsing
464        and to which prefixes they are mapped (this mapping can change during
465        parsing by <lit>xmlns</lit> attributes are encountered).</item>
466
467        <term><arg>loc</arg></term><item>Should location information be attached to the generated nodes?</item>
468
469        <term><arg>validate</arg></term><item>Should the parsed &xml; nodes be validated after parsing?</item>
470
471        <term><arg>encoding</arg></term><item>The default encoding to use, when the
472        source doesn't provide an encoding. The default <lit>None</lit> results in
473        <lit>"utf-8"</lit> for parsing &xml; and <lit>"iso-8859-1"</lit> when parsing
474        broken &html; (when <lit><arg>tidy</arg></lit> is true).</item>
475        </dlist>
476        """
477        self.saxparser = saxparser
478
479        if nspool is None:
480            nspool = xsc.defaultnspool
481        self.nspool = nspool
482
483        if prefixes is None:
484            prefixes = xsc.defaultPrefixes.clone()
485        self.prefixes = prefixes # the currently active prefix mapping (will be replaced once xmlns attributes are encountered)
486
487        self._locator = None
488        self.tidy = tidy
489        self.loc = loc
490        self.validate = validate
491        self.encoding = encoding
492
493    def _last(self):
494        """
495        Return the newest node from the stack that is a real node.
496        (There might be fake nodes on the stack, because we are inside
497        of illegal elements).
498        """
499        for (node, prefixes) in reversed(self._nesting):
500            if node is not None:
501                return node
502
503    def _parseHTML(self, stream, base, sysid, encoding):
504        """
505        Internal helper method for parsing &html; via <module>libxml2</module>.
506        """
507        import libxml2 # This requires libxml2 (see http://www.xmlsoft.org/)
508
509        def decode(s):
510            try:
511                return s.decode("utf-8")
512            except UnicodeDecodeError:
513                return s.decode("iso-8859-1")
514
515        def toxsc(node):
516            if node.type == "document_html":
517                newnode = xsc.Frag()
518                child = node.children
519                while child is not None:
520                    newnode.append(toxsc(child))
521                    child = child.next
522            elif node.type == "element":
523                name = decode(node.name).lower()
524                try:
525                    newnode = ns.element(name, xml=True)()
526                    if self.loc:
527                        newnode.startloc = xsc.Location(sysid=sysid, line=node.lineNo())
528                except xsc.IllegalElementError:
529                    newnode = xsc.Frag()
530                else:
531                    attr = node.properties
532                    while attr is not None:
533                        name = decode(attr.name).lower()
534                        if attr.content is None:
535                            content = u""
536                        else:
537                            content = decode(attr.content)
538                        try:
539                            attrnode = newnode.attrs.set(name, content, xml=True)
540                        except xsc.IllegalAttrError:
541                            pass
542                        else:
543                            attrnode = attrnode.parsed(self)
544                        attr = attr.next
545                    newnode.attrs = newnode.attrs.parsed(self)
546                    newnode = newnode.parsed(self, start=True)
547                child = node.children
548                while child is not None:
549                    newnode.append(toxsc(child))
550                    child = child.next
551                if isinstance(node, xsc.Element): # if we did recognize the element, otherwise we're in a Frag
552                    newnode = newnode.parsed(self, start=False)
553            elif node.type in ("text", "cdata"):
554                newnode = xsc.Text(decode(node.content))
555                if self.loc:
556                    newnode.startloc = xsc.Location(sysid=sysid, line=node.lineNo())
557            elif node.type == "comment":
558                newnode = xsc.Comment(decode(node.content))
559                if self.loc:
560                    newnode.startloc = xsc.Location(sysid=sysid, line=node.lineNo())
561            else:
562                newnode = xsc.Null
563            return newnode
564
565        data = stream.read()
566        ns = self.nspool.get(html.xmlname, html)
567        try:
568            olddefault = libxml2.lineNumbersDefault(1)
569            doc = libxml2.htmlReadMemory(data, len(data), sysid, encoding, 0x160)
570            try:
571                node = toxsc(doc)
572            finally:
573                doc.freeDoc()
574        finally:
575            libxml2.lineNumbersDefault(olddefault)
576        return node
577
578    def _parse(self, stream, base, sysid, encoding):
579        self.base = url.URL(base)
580
581        parser = self.saxparser()
582        # register us for callbacks
583        parser.setErrorHandler(self)
584        parser.setContentHandler(self)
585        parser.setDTDHandler(self)
586        parser.setEntityResolver(self)
587
588        # Configure the parser
589        try:
590            parser.setFeature(handler.feature_namespaces, False) # We do our own namespace processing
591        except sax.SAXNotSupportedException:
592            pass
593        try:
594            parser.setFeature(handler.feature_external_ges, False) # Don't process external entities, but pass them to skippedEntity
595        except sax.SAXNotSupportedException:
596            pass
597
598        self.skippingwhitespace = False
599
600        if self.tidy:
601            if encoding is None:
602                encoding = "iso-8859-1"
603            return self._parseHTML(stream, base, sysid, encoding)
604
605        if encoding is None:
606            encoding = "utf-8"
607
608        source = sax.xmlreader.InputSource(sysid)
609        source.setByteStream(stream)
610        source.setEncoding(encoding)
611
612        # XIST nodes do not have a parent link, therefore we have to store the
613        # active path through the tree in a stack (which we call _nesting)
614        # together with the namespace prefixes defined by each element.
615        #
616        # After we've finished parsing, the Frag that we put at the bottom of the
617        # stack will be our document root.
618        #
619        # The parser provides the ability to skip illegal elements, attributes,
620        # processing instructions or entity references, but for illegal elements,
621        # it must still record the new namespaces defined by the illegal element.
622        # In this case None is stored in the stack instead of the element node.
623
624        self._nesting = [ (xsc.Frag(), self.prefixes) ]
625        try:
626            parser.parse(source)
627            root = self._nesting[0][0]
628        finally:
629            self._nesting = None
630        return root
631
632    def parse(self, stream, base=None, sysid=None):
633        """
634        Parse &xml; from the stream <arg>stream</arg> into an &xist; tree.
635        <arg>base</arg> is the base &url; for the parsing process, <arg>sysid</arg>
636        is the &xml; system identifier (defaulting to <arg>base</arg> if it is <lit>None</lit>).
637        """
638        if sysid is None:
639            sysid = base
640        return self._parse(stream, base, sysid, self.encoding)
641
642    def parseString(self, text, base=None, sysid=None):
643        """
644        Parse the string <arg>text</arg> (<class>str</class> or <class>unicode</class>)
645        into an &xist; tree. <arg>base</arg> is the base &url; for the parsing process, <arg>sysid</arg>
646        is the &xml; system identifier (defaulting to <arg>base</arg> if it is <lit>None</lit>).
647        """
648        if isinstance(text, unicode):
649            encoding = "utf-8"
650            text = text.encode(encoding)
651        else:
652            encoding = self.encoding
653        stream = cStringIO.StringIO(text)
654        if base is None:
655            base = url.URL("STRING")
656        if sysid is None:
657            sysid = str(base)
658        return self._parse(stream, base, sysid, encoding)
659
660    def parseURL(self, name, base=None, sysid=None, headers=None, data=None):
661        """
662        Parse &xml; input from the &url; <arg>name</arg> which might be a string
663        or an <pyref module="ll.url" class="URL"><class>URL</class></pyref> object
664        into an &xist; tree. <arg>base</arg> is the base &url; for the parsing process
665        (defaulting to the final &url; of the response (i.e. including redirects)),
666        <arg>sysid</arg> is the &xml; system identifier (defaulting to <arg>base</arg>
667        if it is <lit>None</lit>). <arg>headers</arg> is a dictionary with additional
668        headers used in the &http; request. If <arg>data</arg> is not <lit>None</lit>
669        it must be a dictionary. In this case <arg>data</arg> will be used as the data
670        for a <lit>POST</lit> request.
671        """
672        name = url.URL(name)
673        stream = name.openread(headers=headers, data=data)
674        if base is None:
675            base = stream.finalurl
676        if sysid is None:
677            sysid = str(base)
678        encoding = self.encoding
679        if encoding is None:
680            encoding = stream.encoding
681        return self._parse(stream, base, sysid, encoding)
682
683    def parseFile(self, filename, base=None, sysid=None):
684        """
685        Parse &xml; input from the file named <arg>filename</arg>. <arg>base</arg> is
686        the base &url; for the parsing process (defaulting to <arg>filename</arg>),
687        <arg>sysid</arg> is the &xml; system identifier (defaulting to <arg>base</arg>).
688        """
689        filename = os.path.expanduser(filename)
690        stream = open(filename, "rb")
691        if base is None:
692            base = url.File(filename)
693        if sysid is None:
694            sysid = str(base)
695        return self._parse(stream, base, sysid, self.encoding)
696
697    def setDocumentLocator(self, locator):
698        self._locator = locator
699
700    def startDocument(self):
701        pass
702
703    def endDocument(self):
704        pass
705
706    def startElement(self, name, attrs):
707        oldprefixes = self.prefixes
708
709        newprefixes = {}
710        for (attrname, attrvalue) in attrs.items():
711            if attrname==u"xmlns" or attrname.startswith(u"xmlns:"):
712                ns = self.nspool[unicode(attrvalue)]
713                newprefixes[attrname[6:] or None] = [ns]
714
715        if newprefixes:
716            prefixes = oldprefixes.clone()
717            prefixes.update(newprefixes)
718            self.prefixes = newprefixes = prefixes
719        else:
720            newprefixes = oldprefixes
721
722        node = self.createElement(name)
723        if node is not None:
724            for (attrname, attrvalue) in attrs.items():
725                if attrname != u"xmlns" and not attrname.startswith(u"xmlns:"):
726                    attrname = newprefixes.attrnamefromqname(node, attrname)
727                    if attrname is not None: # None means an illegal attribute
728                        node[attrname] = attrvalue
729                        node[attrname] = node[attrname].parsed(self)
730            node.attrs = node.attrs.parsed(self)
731            node = node.parsed(self, start=True)
732            self.__appendNode(node)
733        # push new innermost element onto the stack, together with the list of prefix mappings to which we have to return when we leave this element
734        # If the element is bad (i.e. createElement returned None), we push None as the node
735        self._nesting.append((node, oldprefixes))
736        self.skippingwhitespace = False
737
738    def endElement(self, name):
739        currentelement = self._last()
740        if currentelement is not None: # we're not in an bad element
741            currentelement.parsed(self, start=False) # ignore return value
742            if self.validate:
743                currentelement.checkvalid()
744            if self.loc:
745                currentelement.endloc = self.getLocation()
746
747        # We have to check with the currently active prefixes
748        element = self.createElement(name) # Unfortunately this creates the element a second time.
749        if element is not None and element.__class__ is not currentelement.__class__:
750            raise xsc.ElementNestingError(currentelement.__class__, element.__class__)
751
752        self.prefixes = self._nesting.pop()[1] # pop the innermost element off the stack and restore the old prefixes mapping (from outside this element)
753
754        self.skippingwhitespace = False
755
756    def characters(self, content):
757        if self.skippingwhitespace:
758            # the following could be content = content.lstrip(), but this would remove nbsps
759            while content and content[0].isspace() and content[0] != u"\xa0":
760                content = content[1:]
761        if content:
762            node = self.createText(content)
763            node = node.parsed(self)
764            last = self._last()
765            if len(last) and isinstance(last[-1], xsc.Text):
766                node = last[-1] + unicode(node) # join consecutive Text nodes
767                node.startloc = last[-1].startloc # make sure the replacement node has the original location
768                last[-1] = node # replace it
769            else:
770                self.__appendNode(node)
771            self.skippingwhitespace = False
772
773    def comment(self, content):
774        node = self.createComment(content)
775        node = node.parsed(self)
776        self.__appendNode(node)
777        self.skippingwhitespace = False
778
779    def processingInstruction(self, target, data):
780        if target=="x":
781            self.skippingwhitespace = True
782        else:
783            node = self.createProcInst(target, data)
784            if node is not None:
785                node = node.parsed(self)
786                self.__appendNode(node)
787            self.skippingwhitespace = False
788
789    def skippedEntity(self, name):
790        node = self.createEntity(name)
791        if node is not None:
792            if isinstance(node, xsc.CharRef):
793                self.characters(unichr(node.codepoint))
794            else:
795                node = node.parsed(self)
796                self.__appendNode(node)
797        self.skippingwhitespace = False
798
799    def __decorateException(self, exception):
800        if not isinstance(exception, saxlib.SAXParseException):
801            msg = exception.__class__.__name__
802            msg2 = str(exception)
803            if msg2:
804                msg += ": " + msg2
805            exception = saxlib.SAXParseException(msg, exception, self._locator)
806        return exception
807
808    def error(self, exception):
809        "Handle a recoverable error."
810        # This doesn't work properly with expat, as expat fiddles with the traceback
811        raise self.__decorateException(exception)
812
813    def fatalError(self, exception):
814        "Handle a non-recoverable error."
815        # This doesn't work properly with expat, as expat fiddles with the traceback
816        raise self.__decorateException(exception)
817
818    def warning(self, exception):
819        "Handle a warning."
820        print self.__decorateException(exception)
821
822    def getLocation(self):
823        return xsc.Location(self._locator)
824
825    def __appendNode(self, node):
826        if self.loc:
827            node.startloc = self.getLocation()
828        self._last().append(node) # add the new node to the content of the innermost element (or fragment)
829
830    def createText(self, content):
831        return xsc.Text(content)
832
833    def createComment(self, content):
834        return xsc.Comment(content)
835
836    def createElement(self, name):
837        element = self.prefixes.element(name)
838        if element is not None: # None means that the element should be ignored
839            element = element()
840        return element
841
842    def createProcInst(self, target, data):
843        procinst = self.prefixes.procinst(target)
844        if procinst is not None: # None means that the procinst should be ignored
845            procinst = procinst(data)
846        return procinst
847
848    def createEntity(self, name):
849        entity = self.prefixes.entity(name)
850        if entity is not None: # None means that the entity should be ignored
851            entity = entity()
852        return entity
853
854
855def parse(stream, base=None, sysid=None, **parserargs):
856    """
857    Parse &xml; from the stream <arg>stream</arg> into an &xist; tree.
858    For the arguments <arg>base</arg> and <arg>sysid</arg> see the method
859    <pyref class="Parser" method="parse"><method>parse</method></pyref>
860    in the <class>Parser</class> class. You can pass any other argument that the
861    <pyref class="Parser" method="__init__"><class>Parser</class> constructor</pyref>
862    takes as keyword arguments via <arg>parserargs</arg>.
863    """
864    parser = Parser(**parserargs)
865    return parser.parse(stream, base, sysid)
866
867
868def parseString(text, base=None, sysid=None, **parserargs):
869    """
870    Parse the string <arg>text</arg> (<class>str</class> or <class>unicode</class>) into an
871    &xist; tree. For the arguments <arg>base</arg> and <arg>sysid</arg> see the method
872    <pyref class="Parser" method="parseString"><method>parseString</method></pyref>
873    in the <class>Parser</class> class. You can pass any other argument that the
874    <pyref class="Parser" method="__init__"><class>Parser</class> constructor</pyref>
875    takes as keyword arguments via <arg>parserargs</arg>.
876    """
877    parser = Parser(**parserargs)
878    return parser.parseString(text, base, sysid)
879
880
881def parseURL(url, base=None, sysid=None, headers=None, data=None, **parserargs):
882    """
883    Parse &xml; input from the &url; <arg>name</arg> which might be a string
884    or an <pyref module="ll.url" class="URL"><class>URL</class></pyref> object
885    into an &xist; tree. For the arguments <arg>base</arg>, <arg>sysid</arg>,
886    <arg>headers</arg> and <arg>data</arg> see the method
887    <pyref class="Parser" method="parseURL"><method>parseURL</method></pyref>
888    in the <class>Parser</class> class. You can pass any other argument that the
889    <pyref class="Parser" method="__init__"><class>Parser</class> constructor</pyref>
890    takes as keyword arguments via <arg>parserargs</arg>.
891    """
892    parser = Parser(**parserargs)
893    return parser.parseURL(url, base, sysid, headers, data)
894
895
896def parseFile(filename, base=None, sysid=None, **parserargs):
897    """
898    Parse &xml; input from the file named <arg>filename</arg>. For the arguments
899    <arg>base</arg> and <arg>sysid</arg> see the method
900    <pyref class="Parser" method="parseFile"><method>parseFile</method></pyref>
901    in the <class>Parser</class> class. You can pass any other argument that the
902    <pyref class="Parser" method="__init__"><class>Parser</class> constructor</pyref>
903    takes as keyword arguments via <arg>parserargs</arg>.
904    """
905    parser = Parser(**parserargs)
906    return parser.parseFile(filename, base, sysid)
Note: See TracBrowser for help on using the browser.