使用Python解析XML,以解析外部ENTITY引用

时间:2019-03-22 21:07:17

标签: python xml

在我的S1000D xml中,它指定DOCTYPE,并带有对公共URL的引用,该URL包含对许多其他文件的引用,这些文件包含所有有效字符实体。我已经使用xml.etree.ElementTree和lxml尝试对其进行解析,并得到一个解析错误,两者均指示:

undefined entity −: line 82, column 652

即使−是根据指定的ENTITY参考的有效实体。

xml顶部如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dmodule [
<!ENTITY % ISOEntities PUBLIC 'ISO 8879-1986//ENTITIES ISO Character Entities 20030531//EN//XML' 'http://www.s1000d.org/S1000D_4-1/ent/ISOEntities'>
%ISOEntities;]>

如果您出去获取http://www.s1000d.org/S1000D_4-1/ent/ISOEntities,它将包含其他20个ent文件,其中一个名为iso-tech.ent,其中包含以下行:

<!ENTITY minus "&#x2212;"> <!-- MINUS SIGN -->

列652附近的xml文件的第82行中的

是以下内容: ....请参阅70 &minus; 41 ....

如何在不获取未定义实体的情况下运行python脚本来解析此文件?

抱歉,我不想指定parser.entity['minus'] = chr(2212)。我这样做是为了快速解决问题,但是有很多字符实体引用。 我希望解析器检查xml中指定的实体引用。

我很惊讶,但是我已经绕着太阳转了转,还没有找到如何做的办法(或者也许我已经做到了,但是却无法遵循)。 如果我更新xml文件并添加 <!ENTITY minus "&#x2212;"> 它不会失败,所以它不是xml。

解析失败。这是我用于ElementTree的代码

 fl = os.path.join(pth, fn)
 try:
     root = ET.parse(fl)
 except ParseError as p:
     print("ParseError : ", p)

这是我用于lxml的代码

fl = os.path.join(pth, fn)
try:
    parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
    root = etree.parse(fl, parser=parser)
except etree.XMLSyntaxError as pe:
    print("lxml XMLSyntaxError: ", pe)

我希望解析器加载ENTITY引用,以便它知道-以及在所有文件中指定的所有其他字符实体都是有效的实体字符。

非常感谢您的建议和帮助。

1 个答案:

答案 0 :(得分:2)

我要回答lxml。如果可以使用lxml,没有理由考虑使用ElementTree。

我认为您缺少的部分是XMLParser中的no_network=False;是True by default

示例...

XML输入(test.xml)

<!DOCTYPE doc [
<!ENTITY % ISOEntities PUBLIC 'ISO 8879-1986//ENTITIES ISO Character Entities 20030531//EN//XML' 'http://www.s1000d.org/S1000D_4-1/ent/ISOEntities'>
%ISOEntities;]>
<doc>
    <test>Here's a test of minus: &minus;</test>
</doc>

Python

from lxml import etree

parser = etree.XMLParser(load_dtd=True,
                         no_network=False)

tree = etree.parse("test.xml", parser=parser)

etree.dump(tree.getroot())

输出

<doc>
    <test>Here's a test of minus: −</test>
</doc>

如果要保留实体引用,请将resolve_entities=False添加到XMLParser。


也可以考虑设置一个XML Catalog,而不是去外部解析参数实体。这将使您可以将公共和/或系统标识符解析为本地版本。

示例使用与上面相同的XML输入...

XML目录(“目录测试”目录中的“ catalog.xml”(用于测试的目录名中的空间))

<!DOCTYPE catalog PUBLIC "-//OASIS//DTD XML Catalogs V1.1//EN" "http://www.oasis-open.org/committees/entity/release/1.1/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
    <!-- The path in @uri is relative to this file (catalog.xml). -->
    <uri name="http://www.s1000d.org/S1000D_4-1/ent/ISOEntities" uri="./ents/ISOEntities_stackoverflow.ent"/>
</catalog>

实体文件(目录“ catalog test / ents”中的“ ISOEntities_stackoverflow.ent”。将值更改为“ BAM!”进行测试)

<!ENTITY minus "BAM!">

Python (将no_network的{​​{1}}更改为True,以获取使用本地版本http://www.s1000d.org/S1000D_4-1/ent/ISOEntities的更多证据。)

import os
from urllib.request import pathname2url
from lxml import etree

# The XML_CATALOG_FILES environment variable is used by libxml2 (which is used by lxml).
# See http://xmlsoft.org/catalog.html.
try:
    xcf_env = os.environ['XML_CATALOG_FILES']
except KeyError:
    # Path to catalog must be a url.
    catalog_path = f"file:{pathname2url(os.path.join(os.getcwd(), 'catalog test/catalog.xml'))}"
    # Temporarily set the environment variable.
    os.environ['XML_CATALOG_FILES'] = catalog_path

parser = etree.XMLParser(load_dtd=True,
                         no_network=True)

tree = etree.parse("test.xml", parser=parser)

etree.dump(tree.getroot())

输出

<doc>
    <test>Here's a test of minus: BAM!</test>
</doc>