Использование Python xml.etree для поиска начальных и конечных смещений символов - PullRequest
4 голосов
/ 13 ноября 2011

У меня есть данные XML, которые выглядят так:

<xml>
The captial of <place pid="1">South Africa</place> is <place>Pretoria</place>.
</xml>

Я хотел бы иметь возможность извлечь:

  1. Элементы XML в том виде, в котором они в настоящее время представлены в etree.
  2. Полный текст документа между начальным и конечным тегами.
  3. Расположение в текстовом формате каждого начального элемента в виде смещения символов.

(3) является наиболее важным требованием на данный момент; Этри предоставляет (1) штраф.

Я не вижу никакого способа сделать (3) напрямую, но надеялся, что итерация по элементам в дереве документа вернет много маленьких строк, которые могут быть повторно собраны, что обеспечит (2) и (3). Однако запрос .text корневого узла возвращает только текст между корневым узлом и первым элементом, например, "Столица ".

Выполнение (1) с SAX может включать в себя реализацию того, что уже много раз было написано, например, минидом и этри. Использование lxml не вариант для пакета, в который должен войти этот код. Кто-нибудь может помочь?

Ответы [ 4 ]

4 голосов
/ 14 ноября 2011

iterparse() функция доступна в xml.etree:

import xml.etree.cElementTree as etree

for event, elem in etree.iterparse(file, events=('start', 'end')):
    if event == 'start':
       print(elem.tag) # use only tag name and attributes here
    elif event == 'end':
       # elem children elements, elem.text, elem.tail are available
       if elem.text is not None and elem.tail is not None:
          print(repr(elem.tail))

Другой вариант заключается в переопределении start(), data(), end() методов etree.TreeBuilder():

from xml.etree.ElementTree import XMLParser, TreeBuilder

class MyTreeBuilder(TreeBuilder):

    def start(self, tag, attrs):
        print("&lt;%s>" % tag)
        return TreeBuilder.start(self, tag, attrs)

    def data(self, data):
        print(repr(data))
        TreeBuilder.data(self, data)

    def end(self, tag):
        return TreeBuilder.end(self, tag)

text = """<xml>
The captial of <place pid="1">South Africa</place> is <place>Pretoria</place>.
</xml>"""

# ElementTree.fromstring()
parser = XMLParser(target=MyTreeBuilder())
parser.feed(text)
root = parser.close() # return an ordinary Element

Выход

<xml>
'\nThe captial of '
<place>
'South Africa'
' is '
<place>
'Pretoria'
'.\n'
1 голос
/ 13 ноября 2011

Вам нужно посмотреть на свойство .tail, а также .text: .text дает вам текст непосредственно после начального тега, .tail дает вам текст непосредственно после конечного тега. Это предоставит вам ваши "много маленьких строк".

Совет: вы можете использовать etree.iterwalk(elem) (делает то же самое, что и с etree.iterparse(), но вместо существующего дерева), чтобы перебирать начальный и конечный теги. На идею:

for event, elem in etree.iterwalk(xml_elem, events=('start', 'end')):
    if event == 'start':
        # it's a start tag
        print 'starting element', elem.tag
        print elem.text
    elif event == 'end':
        # it's an end tag
        print 'ending element', elem.tag
        if elem is not xml_elem:
            # dont' want the text trailing xml_elem
            print elem.tail

Я полагаю, вы можете завершить остальное для себя? Предупреждение: .text и .tail могут быть None, поэтому, если вы хотите объединить, вы должны будете защититься от этого (используйте (elem.text or '') например)

Если вы знакомы с саксофоном (или имеете существующий саксофон-код, который делает то, что вам нужно), lxml позволяет создавать события саксофона из элемента или дерева :

lxml.sax.saxify(elem, handler)

Некоторые другие вещи, которые нужно искать при извлечении всего текста из элемента: метод .itertext(), выражение xpath .//text() (lxml позволяет вам возвращать «умные строки» из выражений xpath: они позволяют вам проверять, какой элемент они принадлежат и т.д ...).

0 голосов
/ 10 августа 2016

(3) можно сделать с помощью XMLParser.CurrentByteIndex , например:

import xml.etree.ElementTree as ET

class MyTreeBuilder(ET.TreeBuilder):
    def start(self, tag, attrs):
        print(parser.parser.CurrentByteIndex)
        ET.TreeBuilder.start(self, tag, attrs)

builder = MyTreeBuilder()
parser = ET.XMLParser(target=builder)
builder.parser = parser
tree = ET.parse('test.xml', parser=parser)

См. Также этот ответ для SAX альтернативы. Однако обратите внимание, что индекс байтов не совпадает с индексом символов, и в Python не может быть эффективного способа преобразования байта в индекс символов. (См. Также здесь .)

(по общему признанию, некрасивый) обходной путь для получения смещений символов вместо смещений байтов - это перекодирование байтов в символы. Предполагая фактическую кодировку utf8:

import xml.etree.ElementTree as ET

class MyTreeBuilder(ET.TreeBuilder):
    def start(self, tag, attrs):
        print(parser.parser.CurrentByteIndex)
        ET.TreeBuilder.start(self, tag, attrs)

builder = MyTreeBuilder()
parser = ET.XMLParser(target=builder)
builder.parser = parser
with open('test.xml', 'rb') as f:
    parser.feed(f.read().decode('latin1').encode('utf8'))
0 голосов
/ 13 ноября 2011

(2) легко с SAX, посмотрите этот фрагмент

from xml.sax.handler import ContentHandler
import xml.sax
import sys

class textHandler(ContentHandler):
    def characters(self, ch):
        sys.stdout.write(ch.encode("Latin-1"))

parser = xml.sax.make_parser()
handler = textHandler()
parser.setContentHandler(handler)
parser.parse("test.xml")

или пример 1-1: bookhandler.py в этой книге http://oreilly.com/catalog/pythonxml/chapter/ch01.html

(3) сложнееПосоветуйтесь с этим потоком, это Java, но в Python SAX должна быть похожая вещь. api Как получить правильные начальные / конечные местоположения тега xml с SAX?

...