Синтаксический разбор нецелых XML

XML спецификация предписывает, что все XML синтаксические анализаторы должны выполнять «драконову (строгую) обработку ошибок». То есть, при обнаружении в XML документе формальной ошибки или не«правильнопостроенности» (wellformedness) анализаторы должны сразу же прервать анализ и «вспыхнуть». Ошибки правильнопостроенности включают несогласованность открывающих и закрывающих тэгов, неопределённые элементы, неправильные символы Юникод и другие эзотерические ситуации. Такая обработка ошибок сильно контрастирует на фоне других известных форматов, например, HTML — браузер не останавливается отрисовывать web-страницу если в странице забыт закрывающий HTML тэг или значение атрибута тэга содержит неэкранированный амперсанд. (Существует распространённое заблуждение, что в формате HTML не оговорена обработка ошибок. На самом деле, обработка HTML ошибок отлично документирована, но она гораздо сложнее чем просто «остановиться и загореться на первой ошибке».)

Некоторые считают (и я в том числе), что это было ошибкой со стороны разработчиков формата XML заставлять так строго обрабатывать ошибки. Не поймите меня неправильно, я конечно же за упрощение правил обработки ошибок. Однако, на практике понятие «правильнопостроенности» оказывается коварнее чем кажется, особенно для XML документов которые публикуются в интернете и передаются по протоколу HTTP (например, фиды Atom). Несмотря на зрелость XML, который стандартизовал драконову обработку ошибок в 1997, исследования постоянно показывают, что значительная часть фидов Atom в интернете содержат ошибки правильнопостроенности.

Итак, у меня есть и теоретические и практические причины обрабатывать XML документы «любой ценой», то есть не останавливаться и взрываться при первой ошибке. Если Вы окажетесь в похожей ситуации, то lxml может помочь.

Ниже приведён фрагмент «битого» XML документа.

<?xml version='1.0' encoding='utf-8'?><feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into &hellip;</title>...</feed>

В фиде ошибка, так как последовательность &hellip; не определена в формате XML (она определена в HTML). Если попробовать разобрать битый фид с настройками по умолчанию, то lxml споткнётся на неопределённом вхождении hellip.

>>> import lxml.etree>>> tree = lxml.etree.parse('examples/feed-broken.xml')Traceback (most recent call last): File "<stdin>", line 1, in <module> File "lxml.etree.pyx", line 2693, in lxml.etree.parse (src/lxml/lxml.etree.c:52591) File "parser.pxi", line 1478, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:75665) File "parser.pxi", line 1507, in lxml.etree._parseDocumentFromURL (src/lxml/lxml.etree.c:75993) File "parser.pxi", line 1407, in lxml.etree._parseDocFromFile (src/lxml/lxml.etree.c:75002) File "parser.pxi", line 965, in lxml.etree._BaseParser._parseDocFromFile (src/lxml/lxml.etree.c:72023) File "parser.pxi", line 539, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:67830) File "parser.pxi", line 625, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:68877) File "parser.pxi", line 565, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:68125)lxml.etree.XMLSyntaxError: Entity 'hellip' not defined, line 3, column 28

Для того чтобы обрабатывать XML документ с ошибками, необходимо создать новый синтаксический анализатор XML.

>> parser = lxml.etree.XMLParser(recover=True) ①>>> tree = lxml.etree.parse('examples/feed-broken.xml', parser) ②>>> parser.error_log ③examples/feed-broken.xml:3:28:FATAL:PARSER:ERR_UNDECLARED_ENTITY: Entity 'hellip' not defined>>> tree.findall('{http://www.w3.org/2005/Atom}title')[<Element {http://www.w3.org/2005/Atom}title at ead510>]>>> title = tree.findall('{http://www.w3.org/2005/Atom}title')[0]>>> title.text ④'dive into '>>> print(lxml.etree.tounicode(tree.getroot())) ⑤<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> <title>dive into </title>.. [остальной вывод сериализации пропущен для краткости].

① Для того чтобы создать новый анализатор мы создаём новый класс lxml.etree.XMLParser. Хотя он может принимать много разных параметров, для нас представляет интерес только один — аргумент восстановления recover. При присвоении аргументу значения True lxml будет из кожи вон лезть чтобы восстановить ошибки правильнопостроенности.

② Для того чтобы разобрать XML документ новым анализатором мы передаём объект parser в качестве второго аргумента в функцию parse(). На этот раз lxml не выбрасывает исключительную ситуацию при неопределённой последовательности &hellip;.

③ Анализатор содержит сообщения обо всех найденных ошибках. (На самом деле эти сообщения сохраняются независимо от параметра recover.)

④ Так как анализатор не знает что делать с неопределённым &hellip;, то он просто выбрасывает слово. Текстовое содержание элемента title превращается в 'dive into '.

⑤ И ещё раз: после сериализации последовательность &hellip; исчезла, lxml её выбросил.

Важно отметить, что нет никакой гарантии переносимости восстановления ошибок у XML анализаторов. Другой анализатор может быть умнее и распознать что &hellip; является валидной последовательностью HTML и восстановить её как амперсанд. «Лучше» ли это? Возможно. Является ли это «более правильным»? Нет, так как оба решения с точки зрения формата XML неверны. Правильное поведение (согласно XML спецификации) прекратить обработку и загореться. Если же необходимо не следовать спецификации, то Вы делаете это на свой страх и риск.