xml.sax解析器和行号等

时间:2013-03-18 12:55:45

标签: python xml-parsing saxparser

任务是解析一个简单的XML文档,并按行号分析内容。

正确的Python包似乎是xml.sax。但是我该如何使用呢?

在对文档进行一些挖掘后,我发现:

  • xmlreader.Locator界面包含以下信息:getLineNumber()
  • handler.ContentHandler界面有setDocumentHandler()

首先想到的是创建一个Locator,将其传递给ContentHandler,并在调用其character()方法等时从定位器中读取信息。

但是,xmlreader.Locator只是一个骨架接口,只能从其任何方法返回-1。 所以作为一个糟糕的用户,我应该做什么,除了编写我自己的整个ParserLocator之外?

我现在回答我自己的问题。


(好吧,除了任意,烦人的规则,我说我不会这样做。)


我无法使用现有文档(或通过网络搜索)解决这个问题,并被迫阅读xml.sax的源代码(在/usr/lib/python2.7/xml/sax/下)在我的系统上。)

默认情况下,xml.sax函数make_parser()会创建一个真实的Parser,但这是什么类型的?
在源代码中,我发现它是ExpatParser,在expatreader.py中定义。 而且......它有自己的LocatorExpatLocator。但是,没有机会接触到这个东西。 这和解决方案之间存在许多令人头疼的问题。

  1. 编写自己的ContentHandler,它知道Locato r,并使用它来确定行号
  2. 使用ExpatParser
  3. 创建xml.sax.make_parser()
  4. 创建ExpatLocator,并将ExpatParser实例传递给它。
  5. 制作ContentHandler,将其ExpatLocator
  6. ContentHandler传递给解析器' setContentHandler()
  7. 致电parse()上的Parser
  8. 例如:

    import sys
    import xml.sax
    
    class EltHandler( xml.sax.handler.ContentHandler ):
        def __init__( self, locator ):
            xml.sax.handler.ContentHandler.__init__( self )
            self.loc = locator
            self.setDocumentLocator( self.loc )
    
        def startElement( self, name, attrs ): pass
    
        def endElement( self, name ): pass
    
        def characters( self, data ):
            lineNo = self.loc.getLineNumber()
            print >> sys.stdout, "LINE", lineNo, data
    
    def spit_lines( filepath ):
        try:
            parser = xml.sax.make_parser()
            locator = xml.sax.expatreader.ExpatLocator( parser )
            handler = EltHandler( locator )
            parser.setContentHandler( handler )
            parser.parse( filepath )
        except IOError as e:
            print >> sys.stderr, e
    
    if len( sys.argv ) > 1:
        filepath = sys.argv[1]
        spit_lines( filepath )
    else:
        print >> sys.stderr, "Try providing a path to an XML file."
    

    Martijn Pieters在下面指出了另一种有一些优势的方法。 如果正确调用ContentHandler的超类初始值设定项, 然后它发现了一个看起来很私人,没有文档的成员._locator 设置,应该包含适当的Locator

    优势:您不必创建自己的Locator(或了解如何创建它)。 缺点:它没有记录,使用未记录的私有变量是草率的。

    谢谢Martijn!

2 个答案:

答案 0 :(得分:4)

sax解析器本身应该为您的内容处理程序提供定位器。定位器必须实现某些方法,但只要它具有正确的方法,它就可以是任何对象。 xml.sax.xmlreader.Locator class是定位器应该实现的接口;如果解析器为您的处理程序提供了一个定位器对象,那么您可以依赖定位器上存在的那4种方法。

解析器仅鼓励设置定位器,不需要这样做。 expat XML解析器确实提供了它。

如果您继承xml.sax.handler.ContentHandler(),那么它将为您提供标准的setDocumentHandler()方法,并且在调用处理程序时.startDocument(),您的内容处理程序实例将具有{{1设置:

self._locator

答案 1 :(得分:0)

这是一个古老的问题,但我认为对此问题的答案比给出的答案更好,因此我还是要添加另一个答案。

尽管如Martijn的上述回答所述,在ContentHandler超类中确实确实存在一个未记录的私有数据成员_locator,但在我看来,使用该数据成员访问位置信息并不是定位设施的预期用途

我认为,史蒂夫·怀特(Steve White)提出了有关为何未记录此成员的好问题。我认为这些问题的答案是它可能不打算供公众使用。它似乎是ContentHandler超类的私有实现细节。由于它是未公开的私有实现细节,因此在以后发布SAX库时可能会在没有警告的情况下消失,因此依靠它可能很危险。

从阅读ContentHandler类的文档,特别是ContentHandler.setDocumentLocator的文档,对我来说,设计人员打算让用户替代ContentHandler.setDocumentLocator函数,以便在解析器调用它时,用户的内容处理程序子类可以保存对传入的定位器对象(由SAX解析器创建)的引用,并且以后可以使用该保存的对象来获取位置信息。例如:

class MyContentHandler(ContentHandler):
    def __init__(self):
        super().__init__()
        self._mylocator = None
        # initialize your handler

    def setDocumentLocator(self, locator):
        self._mylocator = locator

    def startElement(self, name, attrs):
        loc = self._mylocator
        if loc is not None:
            line, col = loc.getLineNumber(), loc.getColumnNumber()
        else:
            line, col = 'unknown', 'unknown'
        print 'start of {} element at line {}, column {}'.format(name, line, col)

使用这种方法,无需依赖未记录的字段。