Разбирайте XML с (X) объектами HTML - программирование
Подтвердить что ты не робот

Разбирайте XML с (X) объектами HTML

Попытка разобрать XML с помощью ElementTree, содержащего объект undefined (т.е.  ), вызывает:

ParseError: undefined entity  

В Python 2.x XML-сущность dict может быть обновлена ​​путем создания парсера (документация):

parser = ET.XMLParser()
parser.entity["nbsp"] = unichr(160)

но как сделать то же самое с Python 3.x?


Обновление: с моей стороны было непонимание, потому что я забыл, что я вызывал parser.parser.UseForeignDTD(1), прежде чем пытаться обновить XML-сущность dict, что вызывало ошибку в синтаксическом анализаторе. К счастью, @m.brindley был терпелив и указал, что XML-сущность dict все еще существует в Python 3.x и может быть обновлена ​​так же, как в Python 2.x

4b9b3361

Ответ 1

Проблема в том, что единственными действительными мнемическими объектами в XML являются quot, amp, apos, lt и gt. Это означает, что почти все (X) HTML-названные сущности должны быть определены в DTD с помощью декларации объявления объекта, определенной в XML 1.1 spec. Если документ должен быть автономным, это должно быть сделано с помощью встроенного DTD, например:

<?xml version="1.1" ?>
<!DOCTYPE naughtyxml [
    <!ENTITY nbsp "&#0160;">
    <!ENTITY copy "&#0169;">
]>
<data>
    <country name="Liechtenstein">
        <rank>1&nbsp;&gt;</rank>
        <year>2008&copy;</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
</data>

XMLParser xml.etree.ElementTree использует xml.parsers.expat для фактического разбора. В аргументах init для XMLParser существует пространство для < предопределенных объектов HTML ', но этот аргумент еще не реализован. В методе init создается пустой dict с именем entity, и это то, что используется для поиска объектов undefined.

Я не думаю, что expat (по расширению, ET XMLParser) способен обрабатывать переключение пространств имен на что-то вроде XHMTL, чтобы обойти это. Возможно потому, что он не будет определять внешние определения пространства имен (я попытался сделать xmlns="http://www.w3.org/1999/xhtml" пространство имен по умолчанию для элемента данных, но оно не получилось хорошо), но я не могу это подтвердить. По умолчанию expat будет вызывать ошибку в отношении не XML-объектов, но вы можете обойти это, указав внешний DOCTYPE - это приведет к тому, что парсер expat передаст записи объектов undefined обратно в метод ET.XMLParser _default().

Метод _default() выполняет поиск entity dict в экземпляре XMLParser, и если он найдет соответствующий ключ, он заменит объект на соответствующее значение. Это поддерживает синтаксис Python-2.x, упомянутый в вопросе.

Решения:

  • Если данные не имеют внешнего DOCTYPE и имеют (X) HTML-мнемонические объекты, вам не повезло. Недействительно XML и expat имеет право выбросить ошибку. Вы должны добавить внешний DOCTYPE.
  • Если у данных есть внешний DOCTYPE, вы можете просто использовать свой старый синтаксис для сопоставления мнемонических имен с символами. Примечание: вы должны использовать chr() в py3k - unichr() больше не является допустимым именем
    • В качестве альтернативы вы можете обновить XMLParser.entity с помощью html.entities.html5, чтобы сопоставить все допустимые мнемонические сущности HTML5 с их символами.
  • Если данные XHTML, вы можете подклассифицировать HTMLParser для обработки мнемонических объектов, но это не вернет ElementTree по желанию.

Вот фрагмент, который я использовал, - он анализирует XML с внешним DOCTYPE через HTMLParser (чтобы продемонстрировать, как добавить обработку объектов путем подкласса), ET.XMLParser с сопоставлениями объектов и expat (которые будут просто игнорировать undefined из-за внешнего DOCTYPE). Существует допустимый объект XML (&gt;) и объект undefined (&copy;), который я сопоставляю с chr(0x24B4) с ET.XMLParser.

from html.parser import HTMLParser
from html.entities import name2codepoint
import xml.etree.ElementTree as ET
import xml.parsers.expat as expat

xml = '''<?xml version="1.0"?>
<!DOCTYPE data PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<data>
    <country name="Liechtenstein">
        <rank>1&gt;</rank>
        <year>2008&copy;</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
</data>'''

# HTMLParser subclass which handles entities
print('=== HTMLParser')
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, name, attrs):
        print('Start element:', name, attrs)
    def handle_endtag(self, name):
        print('End element:', name)
    def handle_data(self, data):
        print('Character data:', repr(data))
    def handle_entityref(self, name):
        self.handle_data(chr(name2codepoint[name]))

htmlparser = MyHTMLParser()
htmlparser.feed(xml)


# ET.XMLParser parse
print('=== XMLParser')
parser = ET.XMLParser()
parser.entity['copy'] = chr(0x24B8)
root = ET.fromstring(xml, parser)
print(ET.tostring(root))
for elem in root:
    print(elem.tag, ' - ', elem.attrib)
    for subelem in elem:
        print(subelem.tag, ' - ', subelem.attrib, ' - ', subelem.text)

# Expat parse
def start_element(name, attrs):
    print('Start element:', name, attrs)
def end_element(name):
    print('End element:', name)
def char_data(data):
    print('Character data:', repr(data))
print('=== Expat')
expatparser = expat.ParserCreate()
expatparser.StartElementHandler = start_element
expatparser.EndElementHandler = end_element
expatparser.CharacterDataHandler = char_data
expatparser.Parse(xml)

Ответ 2

У меня была аналогичная проблема и обошел ее, используя lxml. Его etree.XMLParser имеет аргумент ключевого слова recover, который заставляет его пытаться игнорировать сломанный XML.