Я закончил тем, что извлек все пустые элементы, где конечный тег может быть опущен из DTD (например, <!ELEMENT elem_name - o EMPTY >
), создал список из этих элементов, а затем с помощью регулярных выражений закрыл все теги в списке. Полученный текст затем передается анализатору XML.
Вот урезанная версия того, что я делаю:
import re
from lxml.html import soupparser
from lxml import etree as ET
empty_tags = ['elem1', 'elem2', 'elem3']
markup = """
<elem1 attr="some value">
<elem2/>
<elem3></elem3>
"""
for t in empty_tags:
markup = re.sub(r'(<{0}(?:>|\s+[^>/]*))>\s*(?:</{0}>)?\n?'.format(t), r'\1/>\n', markup)
tree = soupparser.fromstring(markup)
print(ET.tostring(tree, pretty_print=True).decode("utf-8"))
Вывод должен быть:
<elem1 attr="some value"/>
<elem2/>
<elem3/>
(На самом деле это будет заключено в теги, но парсер добавляет их в.)
Он оставит атрибуты в покое и не будет касаться тегов, которые уже закрыты. Если у тега есть закрывающий тег, но он пуст, он удалит закрывающий тег и автоматически закроет тег, просто чтобы он был стандартизирован.
Это не очень общее решение, но, насколько я могу судить, нет другого способа сделать это, не зная, какие теги должны быть закрыты. Даже OpenSP нужен DTD, чтобы знать, какие теги он должен закрывать.