【问题标题】:Parse XML with Python resolving an external ENTITY reference使用 Python 解析 XML 解析外部实体引用
【发布时间】:2019-03-24 22:40:11
【问题描述】:

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

undefined entity −: line 82, column 652

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

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的文件包含以下行:

&lt;!ENTITY minus "&amp;#x2212;"&gt; &lt;!-- MINUS SIGN --&gt;

在 xml 文件的第 82 行靠近第 652 列的内容如下: ....参考70&amp;minus;41....

如何运行 python 脚本来解析这个文件而不获取未定义的实体?

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

我很惊讶,但我已经绕着太阳转了又回来,还没有找到如何做到这一点(或者我可能有但无法遵循它)。 如果我更新我的 xml 文件并添加 &lt;!ENTITY minus "&amp;#x2212;"&gt; 它不会失败,所以它不是 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)

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

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

【问题讨论】:

    标签: python xml


    【解决方案1】:

    我将回答 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(目录“catalog test”中的“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>
    

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

    <!ENTITY minus "BAM!">
    

    Python(将 no_network 更改为 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>
    

    【讨论】:

    • 很好的回应!感谢您如此完整,例如使用 lxml 的建议以及有关目录和出色示例的建议。目录解决方案工作正常,但我无法让网络工作。这一定是由于工作的代理服务器。我在我的家用电脑上对其进行了测试,no_network=False 就像你展示的那样工作得很好。我查找了如何在我的环境中添加代理信息但没有成功。但目录解决方案可以正常工作。
    • @beakerchi - 我的荣幸。真高兴你做到了。 :-)
    猜你喜欢
    • 2011-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-19
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多