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

Trying to parse XML, with ElementTree, that contains undefined entity (i.e.  ) raises:

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

14
nl ja de

2 ответы

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

<?xml version="1.1" ?>

    
]>

    
        1 >
        2008©
        141100
        
        
    

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

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

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

Решения:

  • If the data does not have an external DOCTYPE and has (X)HTML mnemonic entities, you are out of luck. It is not valid xml and expat is right to throw an error. You should add an external DOCTYPE.
  • If the data has an external DOCTYPE, you can just use your old syntax to map mnemonic names to characters. Note: you should use chr() in py3k - unichr() is not a valid name anymore
    • Alternatively, you could update XMLParser.entity with html.entities.html5 to map all valid HTML5 mnemonic entities to their characters.
  • If the data is XHTML, you could subclass HTMLParser to handle mnemonic entities but this won't return an ElementTree as desired.

Here is the snippet I used - it parses xml with an external DOCTYPE through HTMLParser (to demonstrate how to add entity handling by subclassing), ET.XMLParser with entity mappings and expat (which will just silently ignore undefined entities due to the external DOCTYPE). There is a valid xml entity (>) and an undefined entity (©) which I map to chr(0x24B4) with the 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"?>


    
        1>
        2008©
        141100
        
        
    
'''

# 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)
18
добавлено
Спасибо за изучение проблемы, но я почему-то сомневаюсь, что Python 3.x SPL не позволяет обновлять таблицу XML-сущностей. По крайней мере, я не нашел такого объявления. Извините, но использование regex для подготовки удаленных данных XHTML неприемлемо как идея.
добавлено автор theta, источник
Я хотел бы, чтобы этот вопрос был более общим, но давайте фильтруем проблему более «локально»: у меня есть данные XHTML с xhtml1-transitional.dtd , и я знаю, что только неопределенный XML-объект является & nbsp . Я использую lxml по умолчанию, а если недоступен, возвращаюсь к SPL, но возвращаю ET.
добавлено автор theta, источник
Это уже включено в мой вопрос: ParseError: undefined entity & nbsp;
добавлено автор theta, источник
Я не понимаю ваш комментарий. XML-парсер терпит неудачу, как ожидалось, потому что существует неопределенный объект. Попробуйте ET.parse() в файле xml с & nbsp; , чтобы увидеть его. В Python 2 я могу обновить сущности XML-парсера и проанализировать эти данные, но в Python 3 я не могу найти способ. Это моя проблема.
добавлено автор theta, источник
У меня было еще немного времени, чтобы поиграть с этим и выяснил, почему expat не перешел к методу _default() в XMLParser . См. Мое редактирование - вы можете отображать объекты, если задан внешний DOCTYPE.
добавлено автор m.brindley, источник
Как именно синтаксический анализ выйдет из строя? Если XMLParser получает неопределенный объект, переданный ему и не может сопоставить его, ошибка возникает как «неопределенная сущность% s: строка% d столбца% d», и если expat не передает ее,% s не является включен.
добавлено автор m.brindley, источник
В какой-то момент, когда я не знаю, это должно быть неправильно отображено или неудачно. Я использую ту же строку, что и в вашем вопросе (за исключением копии) - попробуйте поэкспериментировать с этим и увидеть, если это ошибки, а если нет, то какие различия.
добавлено автор m.brindley, источник
добавлено автор m.brindley, источник

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

2
добавлено
Python
Python
7 654 участник(ов)

Уютный чат для профессионалов, занимающихся поиском питоньих мудростей. Как не получить бан: https://t.me/ru_python/577926

Python beginners
Python beginners
4 449 участник(ов)

Вопросы про Python для чайников. Cпам и троллинг неприемлем. Не злоупотребляйте стикерами. Частозадаваемые вопросы: https://github.com/ru-python-beginners/faq/blob/master/README.md Статистика тут: https://grstats.me/chat/x4qym2k5uvfkr3al6at7

pro.python
pro.python
1 090 участник(ов)

Сообщество разработчиков под Python Создатель: @rodgelius

Rude Python
Rude Python
971 участник(ов)

Python без „девочек”, здесь матерятся и унижают Django. Not gay friendly. Правила: t.me/rudepython/114107 @rudepython | t.me/rudepython

rupython
rupython
509 участник(ов)

Группа создана с целью оперативного получения ответов на возникающие вопросы по разработке на яп python, смежные темы, а также человеческого общения. Приветствую!

Python-programming
Python-programming
266 участник(ов)

Чат группы вконтакте https://vk.com/python_community