任务是解析一个简单的XML文档,并按行号分析内容。
正确的Python包似乎是xml.sax
。但是我该如何使用呢?
在对文档进行一些挖掘后,我发现:
xmlreader.Locator
界面包含以下信息:getLineNumber()
。handler.ContentHandler
界面有setDocumentHandler()
。首先想到的是创建一个Locator
,将其传递给ContentHandler
,并在调用其character()
方法等时从定位器中读取信息。
但是,xmlreader.Locator
只是一个骨架接口,只能从其任何方法返回-1。
所以作为一个糟糕的用户,我应该做什么,除了编写我自己的整个Parser
和Locator
之外?
我现在回答我自己的问题。
(好吧,除了任意,烦人的规则,我说我不会这样做。)
我无法使用现有文档(或通过网络搜索)解决这个问题,并被迫阅读xml.sax
的源代码(在/usr/lib/python2.7/xml/sax/下)在我的系统上。)
默认情况下,xml.sax
函数make_parser()
会创建一个真实的Parser
,但这是什么类型的?
在源代码中,我发现它是ExpatParser
,在expatreader.py中定义。
而且......它有自己的Locator
,ExpatLocator
。但是,没有机会接触到这个东西。
这和解决方案之间存在许多令人头疼的问题。
ContentHandler
,它知道Locato
r,并使用它来确定行号ExpatParser
xml.sax.make_parser()
ExpatLocator
,并将ExpatParser
实例传递给它。ContentHandler
,将其ExpatLocator
ContentHandler
传递给解析器' setContentHandler()
parse()
上的Parser
。例如:
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!
答案 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)
使用这种方法,无需依赖未记录的字段。