xml.sax文件解析器和行号

2024-09-30 06:29:31 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

在对文档进行了一番挖掘之后,我发现:

  • xmlreader.Locator接口包含以下信息:getLineNumber()。在
  • handler.ContentHandler接口有setDocumentHandler()。在

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

但是,xmlreader.Locator只是一个框架接口,并且只能从它的任何方法返回-1。 作为一个可怜的用户,我该怎么办,除了自己写一个完整的Parser和{}呢??在

我马上回答我自己的问题。在


(好吧,我本来可以的,除非有个武断、恼人的规定说我不能。)


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

默认情况下,xml.sax函数make_parser()创建一个实的Parser,但这是什么样的东西呢?
在源代码中,我们发现它是一个ExpatParser,在expatreader.py. 而且…它有自己的Locator,一个ExpatLocator。但是,这东西是没有办法的。 在这和一个解决方案之间出现了许多令人头疼的问题。在

  1. 编写您自己的ContentHandler,它知道Locator,并使用它来确定行号
  2. xml.sax.make_parser()创建一个ExpatParser
  3. 创建一个ExpatLocator,将其传递给ExpatParser实例。在
  4. 使ContentHandler,给它这个ExpatLocator
  5. ContentHandler传递给解析器的setContentHandler()
  6. Parser上调用parse()。在

例如:

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(或了解如何创建它)。 缺点:它没有文档记录,使用未记录的私有变量是草率的。在

谢谢马蒂金!在


Tags: 方法文档selfparserdefsysxmlhandler
2条回答

sax解析器本身应该为内容处理程序提供定位器。定位器必须实现某些方法,但它可以是任何对象,只要它有正确的方法。^{} class是定位器应该实现的接口;如果解析器向处理程序提供了一个locator对象,那么就可以指望定位器上存在这4个方法了。在

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

如果您子类^{},那么它将为您提供一个标准的setDocumentHandler()方法,并且在调用处理程序上的.startDocument()时,您的内容处理程序实例将设置self._locator

from xml.sax.handler import ContentHandler

class MyContentHandler(ContentHandler):
    def __init__(self):
        ContentHandler.__init__(self)
        # initialize your handler

    def startElement(self, name, attrs):
        loc = self._locator
        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)

这是一个老问题,但我认为有一个比给出的答案更好的答案,所以我还是要再加一个答案。在

虽然在ContentHandler超类中可能确实存在一个名为_locator的未记录的私有数据成员,正如Martijn在上面的回答中所描述的,但是在我看来,使用这个数据成员访问位置信息并不是定位工具的预期用途。在

在我看来,史蒂夫·怀特提出了一些很好的问题,为什么这个成员没有被记录在案。我认为这些问题的答案是,它可能不是为了供公众使用。它似乎是ContentHandler超类的私有实现细节。由于它是一个没有文档记录的私有实现细节,它可能会随着SAX库的任何未来版本而消失,因此依赖它可能是危险的。在

从阅读ContentHandler类的文档,特别是ContentHandler.setDocumentLocator,设计人员希望用户改为重写ContentHandler.setDocumentLocator函数,以便当解析器调用它时,用户的contenthandler子类可以保存对传入的locator对象(由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)

有了这种方法,就没有必要依赖于未记录的字段。在

相关问题 更多 >

    热门问题