Парсер xml.sax и номера строк и т. д.
Задача состоит в том, чтобы проанализировать простой 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
и использует его для определения номеров строк - создать
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."
Мартейн Питерс отмечает ниже другой подход с некоторыми преимуществами. Если инициализатор суперкласса ContentHandler
называется должным образом, то получается частный недокументированный член ._locator
установлен, который должен содержать надлежащий Locator
,
Преимущество: вам не нужно создавать свой собственный Locator
(или узнайте, как его создать). Недостаток: это нигде не задокументировано, а использование недокументированной закрытой переменной небрежно.
Спасибо Мартейн!
2 ответа
Предполагается, что сам синтаксический анализатор предоставляет вашему обработчику содержимого локатор. Локатор должен реализовывать определенные методы, но это может быть любой объект, если у него есть правильные методы. xml.sax.xmlreader.Locator
class - интерфейс, который локатор должен реализовать; если синтаксический анализатор предоставил объект локатора вашему обработчику, то вы можете рассчитывать на эти 4 метода, присутствующие в локаторе.
Парсеру рекомендуется только установить локатор, это не требуется. Синтаксический анализатор XML с экспатом предоставляет его.
Если вы подкласс xml.sax.handler.ContentHandler()
тогда это обеспечит стандарт 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, чтобы при вызове синтаксического анализатора подкласс обработчика контента пользователя может сохранить ссылку на переданный объект локатора (который был создан анализатором 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)
При таком подходе нет необходимости полагаться на недокументированные поля.