Парсер 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, Но нет доступа к этой вещи. Между этим и решением возникло много царапин.

  1. написать свой ContentHandler, который знает о Locatoи использует его для определения номеров строк
  2. создать ExpatParser с xml.sax.make_parser()
  3. создать ExpatLocatorпередавая это ExpatParser пример.
  4. сделать ContentHandlerдавая это ExpatLocator
  5. пройти ContentHandler парсеру setContentHandler()
  6. вызов 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)

При таком подходе нет необходимости полагаться на недокументированные поля.

Другие вопросы по тегам