sax driver for the pyexpat c module this driver works with pyexpat version 2 22 if we re using a sufficiently recent version of python we can use weak references to avoid cycles between the parser and content handler otherwise we ll just have to pretend expatlocator locator for use with the expatparser class this uses a weak reference to the parser object to avoid creating a circular reference between the parser and the content handler expatparser sax driver for the pyexpat c module def initself namespacehandling0 bufsize21620 xmlreader incrementalparser initself bufsize self source xmlreader inputsource self parser none self namespaces namespacehandling self lexhandlerprop none self parsing false self entitystack self externalges 0 self interning none xmlreader methods def parseself source parse an xml document from a url or an inputsource source saxutils prepareinputsourcesource self source source try self reset self conthandler setdocumentlocatorexpatlocatorself xmlreader incrementalparser parseself source except bpo30264 close the source on error to not leak resources xml sax parse doesn t give access to the underlying parser to the caller self closesource raise def prepareparserself source if source getsystemid is not none self parser setbasesource getsystemid redefined setcontenthandler to allow changing handlers during parsing def setcontenthandlerself handler xmlreader incrementalparser setcontenthandlerself handler if self parsing self resetconthandler def getfeatureself name if name featurenamespaces return self namespaces elif name featurestringinterning return self interning is not none elif name in featurevalidation featureexternalpes featurenamespaceprefixes return 0 elif name featureexternalges return self externalges raise saxnotrecognizedexceptionfeature s not recognized name def setfeatureself name state if self parsing raise saxnotsupportedexceptioncannot set features while parsing if name featurenamespaces self namespaces state elif name featureexternalges self externalges state elif name featurestringinterning if state if self interning is none self interning else self interning none elif name featurevalidation if state raise saxnotsupportedexception expat does not support validation elif name featureexternalpes if state raise saxnotsupportedexception expat does not read external parameter entities elif name featurenamespaceprefixes if state raise saxnotsupportedexception expat does not report namespace prefixes else raise saxnotrecognizedexception feature s not recognized name def getpropertyself name if name handler propertylexicalhandler return self lexhandlerprop elif name propertyinterningdict return self interning elif name propertyxmlstring if self parser if hasattrself parser getinputcontext return self parser getinputcontext else raise saxnotrecognizedexception this version of expat does not support getting the xml string else raise saxnotsupportedexception xml string cannot be returned when not parsing raise saxnotrecognizedexceptionproperty s not recognized name def setpropertyself name value if name handler propertylexicalhandler self lexhandlerprop value if self parsing self resetlexhandlerprop elif name propertyinterningdict self interning value elif name propertyxmlstring raise saxnotsupportedexceptionproperty s cannot be set name else raise saxnotrecognizedexceptionproperty s not recognized name incrementalparser methods def feedself data isfinalfalse if not self parsing self reset self parsing true self conthandler startdocument try the isfinal parameter is internal to the expat reader if it is set to true expat will check validity of the entire document when feeding chunks they are not normally final except when invoked from close self parser parsedata isfinal except expat error as e exc saxparseexceptionexpat errorstringe code e self fixme when to invoke error self errhandler fatalerrorexc def closesourceself source self source try file source getcharacterstream if file is not none file close finally file source getbytestream if file is not none file close def closeself if self entitystack or self parser is none or isinstanceself parser closedparser if we are completing an external entity do nothing here return try self feedb isfinaltrue self conthandler enddocument self parsing false break cycle expat handlers pointing to our methods self parser none finally self parsing false if self parser is not none keep errorcolumnnumber and errorlinenumber after closing parser closedparser parser errorcolumnnumber self parser errorcolumnnumber parser errorlinenumber self parser errorlinenumber self parser parser self closesource def resetconthandlerself self parser processinginstructionhandler self conthandler processinginstruction self parser characterdatahandler self conthandler characters def resetlexhandlerpropself lex self lexhandlerprop parser self parser if lex is none parser commenthandler none parser startcdatasectionhandler none parser endcdatasectionhandler none parser startdoctypedeclhandler none parser enddoctypedeclhandler none else parser commenthandler lex comment parser startcdatasectionhandler lex startcdata parser endcdatasectionhandler lex endcdata parser startdoctypedeclhandler self startdoctypedecl parser enddoctypedeclhandler lex enddtd def resetself if self namespaces self parser expat parsercreateself source getencoding internself interning self parser namespaceprefixes 1 self parser startelementhandler self startelementns self parser endelementhandler self endelementns else self parser expat parsercreateself source getencoding intern self interning self parser startelementhandler self startelement self parser endelementhandler self endelement self resetconthandler self parser unparsedentitydeclhandler self unparsedentitydecl self parser notationdeclhandler self notationdecl self parser startnamespacedeclhandler self startnamespacedecl self parser endnamespacedeclhandler self endnamespacedecl self declhandlerprop none if self lexhandlerprop self resetlexhandlerprop self parser defaulthandler self parser defaulthandlerexpand self parser notstandalonehandler self parser externalentityrefhandler self externalentityref try self parser skippedentityhandler self skippedentityhandler except attributeerror this pyexpat does not support skippedentity pass self parser setparamentityparsing expat xmlparamentityparsingunlessstandalone self parsing false self entitystack locator methods def getcolumnnumberself if self parser is none return none return self parser errorcolumnnumber def getlinenumberself if self parser is none return 1 return self parser errorlinenumber def getpublicidself return self source getpublicid def getsystemidself return self source getsystemid event handlers def startelementself name attrs self conthandler startelementname attributesimplattrs def endelementself name self conthandler endelementname def startelementnsself name attrs pair name split if lenpair 1 no namespace pair none name elif lenpair 3 pair pair0 pair1 else default namespace pair tuplepair newattrs qnames for aname value in attrs items parts aname split length lenparts if length 1 no namespace qname aname apair none aname elif length 3 qname s s parts2 parts1 apair parts0 parts1 else default namespace qname parts1 apair tupleparts newattrsapair value qnamesapair qname self conthandler startelementnspair none attributesnsimplnewattrs qnames def endelementnsself name pair name split if lenpair 1 pair none name elif lenpair 3 pair pair0 pair1 else pair tuplepair self conthandler endelementnspair none this is not used call directly to contenthandler def processinginstructionself target data self conthandler processinginstructiontarget data this is not used call directly to contenthandler def characterdataself data self conthandler charactersdata def startnamespacedeclself prefix uri self conthandler startprefixmappingprefix uri def endnamespacedeclself prefix self conthandler endprefixmappingprefix def startdoctypedeclself name sysid pubid hasinternalsubset self lexhandlerprop startdtdname pubid sysid def unparsedentitydeclself name base sysid pubid notationname self dtdhandler unparsedentitydeclname pubid sysid notationname def notationdeclself name base sysid pubid self dtdhandler notationdeclname pubid sysid def externalentityrefself context base sysid pubid if not self externalges return 1 source self enthandler resolveentitypubid sysid source saxutils prepareinputsourcesource self source getsystemid or self entitystack appendself parser self source self parser self parser externalentityparsercreatecontext self source source try xmlreader incrementalparser parseself source except return 0 fixme save error info here self parser self source self entitystack1 del self entitystack1 return 1 def skippedentityhandlerself name ispe if ispe the sax spec requires to report skipped pes with a name name self conthandler skippedentityname def createparserargs kwargs return expatparserargs kwargs if name main import xml sax saxutils p createparser p setcontenthandlerxml sax saxutils xmlgenerator p seterrorhandlerxml sax errorhandler p parsehttp www ibiblio orgxmlexamplesshakespearehamlet xml if we re using a sufficiently recent version of python we can use weak references to avoid cycles between the parser and content handler otherwise we ll just have to pretend expatlocator locator for use with the expatparser class this uses a weak reference to the parser object to avoid creating a circular reference between the parser and the content handler expatparser sax driver for the pyexpat c module xmlreader methods bpo 30264 close the source on error to not leak resources xml sax parse doesn t give access to the underlying parser to the caller redefined setcontenthandler to allow changing handlers during parsing incrementalparser methods the isfinal parameter is internal to the expat reader if it is set to true expat will check validity of the entire document when feeding chunks they are not normally final except when invoked from close fixme when to invoke error if we are completing an external entity do nothing here break cycle expat handlers pointing to our methods keep errorcolumnnumber and errorlinenumber after closing self _parser defaulthandler self _parser defaulthandlerexpand self _parser notstandalonehandler this pyexpat does not support skippedentity locator methods event handlers no namespace default namespace no namespace default namespace this is not used call directly to contenthandler this is not used call directly to contenthandler fixme save error info here the sax spec requires to report skipped pes with a
version = "0.20"

from xml.sax._exceptions import *
from xml.sax.handler import feature_validation, feature_namespaces
from xml.sax.handler import feature_namespace_prefixes
from xml.sax.handler import feature_external_ges, feature_external_pes
from xml.sax.handler import feature_string_interning
from xml.sax.handler import property_xml_string, property_interning_dict

try:
    from xml.parsers import expat
except ImportError:
    raise SAXReaderNotAvailable("expat not supported", None)
else:
    if not hasattr(expat, "ParserCreate"):
        raise SAXReaderNotAvailable("expat not supported", None)
from xml.sax import xmlreader, saxutils, handler

AttributesImpl = xmlreader.AttributesImpl
AttributesNSImpl = xmlreader.AttributesNSImpl




try:
    import _weakref
except ImportError:
    def _mkproxy(o):
        return o
else:
    import weakref
    _mkproxy = weakref.proxy
    del weakref, _weakref

class _ClosedParser:
    pass



class ExpatLocator(xmlreader.Locator):
    
    def __init__(self, parser):
        self._ref = _mkproxy(parser)

    def getColumnNumber(self):
        parser = self._ref
        if parser._parser is None:
            return None
        return parser._parser.ErrorColumnNumber

    def getLineNumber(self):
        parser = self._ref
        if parser._parser is None:
            return 1
        return parser._parser.ErrorLineNumber

    def getPublicId(self):
        parser = self._ref
        if parser is None:
            return None
        return parser._source.getPublicId()

    def getSystemId(self):
        parser = self._ref
        if parser is None:
            return None
        return parser._source.getSystemId()




class ExpatParser(xmlreader.IncrementalParser, xmlreader.Locator):
    

    def __init__(self, namespaceHandling=0, bufsize=2**16-20):
        xmlreader.IncrementalParser.__init__(self, bufsize)
        self._source = xmlreader.InputSource()
        self._parser = None
        self._namespaces = namespaceHandling
        self._lex_handler_prop = None
        self._parsing = False
        self._entity_stack = []
        self._external_ges = 0
        self._interning = None

    

    def parse(self, source):
        "Parse an XML document from a URL or an InputSource."
        source = saxutils.prepare_input_source(source)

        self._source = source
        try:
            self.reset()
            self._cont_handler.setDocumentLocator(ExpatLocator(self))
            xmlreader.IncrementalParser.parse(self, source)
        except:
            
            
            
            self._close_source()
            raise

    def prepareParser(self, source):
        if source.getSystemId() is not None:
            self._parser.SetBase(source.getSystemId())

    

    def setContentHandler(self, handler):
        xmlreader.IncrementalParser.setContentHandler(self, handler)
        if self._parsing:
            self._reset_cont_handler()

    def getFeature(self, name):
        if name == feature_namespaces:
            return self._namespaces
        elif name == feature_string_interning:
            return self._interning is not None
        elif name in (feature_validation, feature_external_pes,
                      feature_namespace_prefixes):
            return 0
        elif name == feature_external_ges:
            return self._external_ges
        raise SAXNotRecognizedException("Feature '%s' not recognized" % name)

    def setFeature(self, name, state):
        if self._parsing:
            raise SAXNotSupportedException("Cannot set features while parsing")

        if name == feature_namespaces:
            self._namespaces = state
        elif name == feature_external_ges:
            self._external_ges = state
        elif name == feature_string_interning:
            if state:
                if self._interning is None:
                    self._interning = {}
            else:
                self._interning = None
        elif name == feature_validation:
            if state:
                raise SAXNotSupportedException(
                    "expat does not support validation")
        elif name == feature_external_pes:
            if state:
                raise SAXNotSupportedException(
                    "expat does not read external parameter entities")
        elif name == feature_namespace_prefixes:
            if state:
                raise SAXNotSupportedException(
                    "expat does not report namespace prefixes")
        else:
            raise SAXNotRecognizedException(
                "Feature '%s' not recognized" % name)

    def getProperty(self, name):
        if name == handler.property_lexical_handler:
            return self._lex_handler_prop
        elif name == property_interning_dict:
            return self._interning
        elif name == property_xml_string:
            if self._parser:
                if hasattr(self._parser, "GetInputContext"):
                    return self._parser.GetInputContext()
                else:
                    raise SAXNotRecognizedException(
                        "This version of expat does not support getting"
                        " the XML string")
            else:
                raise SAXNotSupportedException(
                    "XML string cannot be returned when not parsing")
        raise SAXNotRecognizedException("Property '%s' not recognized" % name)

    def setProperty(self, name, value):
        if name == handler.property_lexical_handler:
            self._lex_handler_prop = value
            if self._parsing:
                self._reset_lex_handler_prop()
        elif name == property_interning_dict:
            self._interning = value
        elif name == property_xml_string:
            raise SAXNotSupportedException("Property '%s' cannot be set" %
                                           name)
        else:
            raise SAXNotRecognizedException("Property '%s' not recognized" %
                                            name)

    

    def feed(self, data, isFinal=False):
        if not self._parsing:
            self.reset()
            self._parsing = True
            self._cont_handler.startDocument()

        try:
            
            
            
            
            self._parser.Parse(data, isFinal)
        except expat.error as e:
            exc = SAXParseException(expat.ErrorString(e.code), e, self)
            
            self._err_handler.fatalError(exc)

    def _close_source(self):
        source = self._source
        try:
            file = source.getCharacterStream()
            if file is not None:
                file.close()
        finally:
            file = source.getByteStream()
            if file is not None:
                file.close()

    def close(self):
        if (self._entity_stack or self._parser is None or
            isinstance(self._parser, _ClosedParser)):
            
            return
        try:
            self.feed(b"", isFinal=True)
            self._cont_handler.endDocument()
            self._parsing = False
            
            self._parser = None
        finally:
            self._parsing = False
            if self._parser is not None:
                
                parser = _ClosedParser()
                parser.ErrorColumnNumber = self._parser.ErrorColumnNumber
                parser.ErrorLineNumber = self._parser.ErrorLineNumber
                self._parser = parser
            self._close_source()

    def _reset_cont_handler(self):
        self._parser.ProcessingInstructionHandler = \
                                    self._cont_handler.processingInstruction
        self._parser.CharacterDataHandler = self._cont_handler.characters

    def _reset_lex_handler_prop(self):
        lex = self._lex_handler_prop
        parser = self._parser
        if lex is None:
            parser.CommentHandler = None
            parser.StartCdataSectionHandler = None
            parser.EndCdataSectionHandler = None
            parser.StartDoctypeDeclHandler = None
            parser.EndDoctypeDeclHandler = None
        else:
            parser.CommentHandler = lex.comment
            parser.StartCdataSectionHandler = lex.startCDATA
            parser.EndCdataSectionHandler = lex.endCDATA
            parser.StartDoctypeDeclHandler = self.start_doctype_decl
            parser.EndDoctypeDeclHandler = lex.endDTD

    def reset(self):
        if self._namespaces:
            self._parser = expat.ParserCreate(self._source.getEncoding(), " ",
                                              intern=self._interning)
            self._parser.namespace_prefixes = 1
            self._parser.StartElementHandler = self.start_element_ns
            self._parser.EndElementHandler = self.end_element_ns
        else:
            self._parser = expat.ParserCreate(self._source.getEncoding(),
                                              intern = self._interning)
            self._parser.StartElementHandler = self.start_element
            self._parser.EndElementHandler = self.end_element

        self._reset_cont_handler()
        self._parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl
        self._parser.NotationDeclHandler = self.notation_decl
        self._parser.StartNamespaceDeclHandler = self.start_namespace_decl
        self._parser.EndNamespaceDeclHandler = self.end_namespace_decl

        self._decl_handler_prop = None
        if self._lex_handler_prop:
            self._reset_lex_handler_prop()



        self._parser.ExternalEntityRefHandler = self.external_entity_ref
        try:
            self._parser.SkippedEntityHandler = self.skipped_entity_handler
        except AttributeError:
            
            pass
        self._parser.SetParamEntityParsing(
            expat.XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE)

        self._parsing = False
        self._entity_stack = []

    

    def getColumnNumber(self):
        if self._parser is None:
            return None
        return self._parser.ErrorColumnNumber

    def getLineNumber(self):
        if self._parser is None:
            return 1
        return self._parser.ErrorLineNumber

    def getPublicId(self):
        return self._source.getPublicId()

    def getSystemId(self):
        return self._source.getSystemId()

    
    def start_element(self, name, attrs):
        self._cont_handler.startElement(name, AttributesImpl(attrs))

    def end_element(self, name):
        self._cont_handler.endElement(name)

    def start_element_ns(self, name, attrs):
        pair = name.split()
        if len(pair) == 1:
            
            pair = (None, name)
        elif len(pair) == 3:
            pair = pair[0], pair[1]
        else:
            
            pair = tuple(pair)

        newattrs = {}
        qnames = {}
        for (aname, value) in attrs.items():
            parts = aname.split()
            length = len(parts)
            if length == 1:
                
                qname = aname
                apair = (None, aname)
            elif length == 3:
                qname = "%s:%s" % (parts[2], parts[1])
                apair = parts[0], parts[1]
            else:
                
                qname = parts[1]
                apair = tuple(parts)

            newattrs[apair] = value
            qnames[apair] = qname

        self._cont_handler.startElementNS(pair, None,
                                          AttributesNSImpl(newattrs, qnames))

    def end_element_ns(self, name):
        pair = name.split()
        if len(pair) == 1:
            pair = (None, name)
        elif len(pair) == 3:
            pair = pair[0], pair[1]
        else:
            pair = tuple(pair)

        self._cont_handler.endElementNS(pair, None)

    
    def processing_instruction(self, target, data):
        self._cont_handler.processingInstruction(target, data)

    
    def character_data(self, data):
        self._cont_handler.characters(data)

    def start_namespace_decl(self, prefix, uri):
        self._cont_handler.startPrefixMapping(prefix, uri)

    def end_namespace_decl(self, prefix):
        self._cont_handler.endPrefixMapping(prefix)

    def start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
        self._lex_handler_prop.startDTD(name, pubid, sysid)

    def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
        self._dtd_handler.unparsedEntityDecl(name, pubid, sysid, notation_name)

    def notation_decl(self, name, base, sysid, pubid):
        self._dtd_handler.notationDecl(name, pubid, sysid)

    def external_entity_ref(self, context, base, sysid, pubid):
        if not self._external_ges:
            return 1

        source = self._ent_handler.resolveEntity(pubid, sysid)
        source = saxutils.prepare_input_source(source,
                                               self._source.getSystemId() or
                                               "")

        self._entity_stack.append((self._parser, self._source))
        self._parser = self._parser.ExternalEntityParserCreate(context)
        self._source = source

        try:
            xmlreader.IncrementalParser.parse(self, source)
        except:
            return 0  

        (self._parser, self._source) = self._entity_stack[-1]
        del self._entity_stack[-1]
        return 1

    def skipped_entity_handler(self, name, is_pe):
        if is_pe:
            
            name = '%'+name
        self._cont_handler.skippedEntity(name)



def create_parser(*args, **kwargs):
    return ExpatParser(*args, **kwargs)



if __name__ == "__main__":
    import xml.sax.saxutils
    p = create_parser()
    p.setContentHandler(xml.sax.saxutils.XMLGenerator())
    p.setErrorHandler(xml.sax.ErrorHandler())
    p.parse("http://www.ibiblio.org/xml/examples/shakespeare/hamlet.xml")