首页 > 解决方案 > 宽松的 XML python 解析器:解决 xml 标签重叠

问题描述

我正在寻找“错误”XML 输入的友好错误(在 BeautifulSoup 的术语中是宽松的)python 解析器。问题是标签重叠。一个示例输入是:

<trn>choya - <i><b>a cholla cactus </i> lat. <i>Cylindropuntia</b></trn></i>

我想得到什么和符合 XML 的结果,例如(我希望的好结果)

<trn>choya - <b><i>a cholla cactus </i> lat. <i>Cylindropuntia</i></b></trn>

BeautifulSoupwith html.parserorhtml5lib给了我别的东西(我不想要的坏结果)

<trn>choya - <i><b>a cholla cactus </b></i> lat. <i>Cylindropuntia</i></trn>

注意<i><b>标签的顺序。如果我将标记<i>为斜体和<b>粗体,那么好的答案是

choya -一种 cholla 仙人掌 lat。 柱眼

不好的答案是

choya -一种 cholla 仙人掌lat。柱眼

我也试过 old tidyhtml,无法得到必要的结果。而对于新tidy-html5的找不到python接口。你能帮我吗?

谢谢!

标签: pythonxmlparsing

解决方案


html.parser.HTMLParser擅长解析标签汤,SAXXMLGenerator类有方便的 API 可以根据事件生成 XML。

并非所有位都在此处实现,尤其是标签的“刚性”/“重量”约束(现在我们所做的只是用我们期望的使嵌套正确的方式关闭标签),但基本的想法似乎有效。

输出是

<trn>choya - <i><com>a cholla cactus </com> lat. <i>Cylindropuntia</i></i> native to US</trn>

这是有效的 XML,嵌套方式。

祝你好运!


import html.parser
import io
from xml.sax.saxutils import XMLGenerator


class Reconstructor(html.parser.HTMLParser):

    def __init__(self):
        super().__init__()
        self.op_stream = []
        self.tag_stack = []

    def handle_startendtag(self, tag, attrs):
        self.op_stream.append(('startendtag', (tag, attrs)))

    def handle_starttag(self, tag, attrs):
        self.op_stream.append(('starttag', (tag, attrs)))
        self.tag_stack.append(tag)

    def handle_endtag(self, tag):
        expected_tag = self.tag_stack[-1]
        if tag != expected_tag:
            print('mismatch closing <{}>, expected <{}>'.format(tag, expected_tag))
            # TODO: implement logic to figure out the correct order for the tags here
            #       and reorder tag_stack accordingly.
        stack_tag = self.tag_stack.pop(-1)
        self.op_stream.append(('endtag', (stack_tag, tag)))

    def handle_charref(self, name):
        self.op_stream.append(('charref', (name,)))

    def handle_entityref(self, name):
        self.op_stream.append(('entityref', (name,)))

    def handle_data(self, data):
        self.op_stream.append(('data', (data,)))

    def handle_comment(self, data):
        self.op_stream.append(('comment', (data,)))

    def handle_decl(self, decl):
        self.op_stream.append(('decl', (decl,)))

    def handle_pi(self, data):
        self.op_stream.append(('pi', (data,)))

    def generate_xml(self):
        stream = io.StringIO()
        xg = XMLGenerator(stream, encoding='utf-8')
        for op, args in self.op_stream:
            if op in ('startendtag', 'starttag'):
                tag, attrib = args
                xg.startElement(tag, dict(attrib))
                if op == 'startendtag':
                    xg.endElement(tag)
            elif op == 'endtag':
                tag = args[0]
                xg.endElement(tag)
            elif op == 'data':
                xg.characters(args[0])
            else:
                raise NotImplementedError('Operator not implemented: %s' % op)
        xg.endDocument()
        return stream.getvalue()


xr = Reconstructor()
xr.feed('<trn>choya - <i><com>a cholla cactus </i> lat. <i>Cylindropuntia</com></trn> native to US</i>')
y = xr.generate_xml()
print(y)

推荐阅读