使用 lxml 解析包含多个 xml 文档的文件

Parse file with several xml documents using lxml

所以,我认为这是一个非常典型的用例,但我在 lxml 文档中找不到任何关于对此的支持。基本上我有一个 xml 文件,其中包含许多不同的 xml 文档(特别是评论)结构大约是:

<review>
    <!-- A bunch of metadata -->
</review>
<!-- The issue is here -->
<review>
    <!-- A bunch of metadata -->
</review>

基本上,我尝试像这样读取文件:

import lxml

document = lxml.etree.fromstring(open(xml_file).read())

但是当我这样做时出现错误:

lxml.etree.XMLSyntaxError: Extra content at the end of the document

完全合理的错误,实际上这是一个 xml 错误,应该这样对待,但我的问题是:如何让 lxml 认识到这是 xml 文档并进行相应的解析?

list_of_reviews = lxml.magic(open(xml_file).read())

magic 是一个真正的 lxml 函数吗?

XML 文档必须有一个根元素;否则,它们不是 well-formed,实际上也不是 XML。符合规范的解析器无法解析格式不正确的 "XML".

当您从多个文档构建单个 XML 文档时,只需将不同的根元素包装在一个根元素中即可。然后您就可以使用标准解析器,例如 lxml。

所以,它有点老套,但应该相对健壮。这里有两个主要的负面因素:

  • 重复调用 fromstring 意味着此代码不是非常快。与单独解析每个文档的速度大致相同,比所有文档慢得多
  • 相对于文档中的当前位置抛出错误。添加相对位置支持很容易(只需添加一个累加器来跟踪当前位置)

基本上,方法是找到抛出的错误,然后只解析错误上方的文件部分。如果抛出与根节点的最后一个无关的错误,则将其作为典型异常处理。

def fix_xml_list(test_file):
    documents = []
    finished = False
    while not finished:
        try:
            lxml.etree.fromstring(test_file)
        except XMLSyntaxError as e:
            if e.code == 5 and e.position[1] == 1:
                doc_end = e.position[0]
                end_char = find_nth(test_file, '\n', doc_end - 2)
                documents.append(lxml.etree.fromstring(test_file[:end_char]))
                if end_char == len(test_file):
                    finished = True
                test_file = test_file[end_char:]
            else:
                print e
                break
    return documents

def find_nth(doc, search, n=0):
    l = len(search)
    i = -l
    for c in xrange(n + 1):
        i = doc.find(search, i + l)
        if i < 0:
            break
    return i

find_nth 代码是无耻地从 this 问题中窃取的。这段代码可能在很多情况下都非常有用,但对于我有大量稍微不规则的文档(在学术数据中很常见)的我来说,它是无价的。