• 6.7 利用命名空间解析XML文档
    • 问题
    • 解决方案
    • 讨论

    6.7 利用命名空间解析XML文档

    问题

    你想解析某个XML文档,文档中使用了XML命名空间。

    解决方案

    考虑下面这个使用了命名空间的文档:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <top>
    3. <author>David Beazley</author>
    4. <content>
    5. <html xmlns="http://www.w3.org/1999/xhtml">
    6. <head>
    7. <title>Hello World</title>
    8. </head>
    9. <body>
    10. <h1>Hello World!</h1>
    11. </body>
    12. </html>
    13. </content>
    14. </top>

    如果你解析这个文档并执行普通的查询,你会发现这个并不是那么容易,因为所有步骤都变得相当的繁琐。

    1. >>> # Some queries that work
    2. >>> doc.findtext('author')
    3. 'David Beazley'
    4. >>> doc.find('content')
    5. <Element 'content' at 0x100776ec0>
    6. >>> # A query involving a namespace (doesn't work)
    7. >>> doc.find('content/html')
    8. >>> # Works if fully qualified
    9. >>> doc.find('content/{http://www.w3.org/1999/xhtml}html')
    10. <Element '{http://www.w3.org/1999/xhtml}html' at 0x1007767e0>
    11. >>> # Doesn't work
    12. >>> doc.findtext('content/{http://www.w3.org/1999/xhtml}html/head/title')
    13. >>> # Fully qualified
    14. >>> doc.findtext('content/{http://www.w3.org/1999/xhtml}html/'
    15. ... '{http://www.w3.org/1999/xhtml}head/{http://www.w3.org/1999/xhtml}title')
    16. 'Hello World'
    17. >>>

    你可以通过将命名空间处理逻辑包装为一个工具类来简化这个过程:

    1. class XMLNamespaces:
    2. def __init__(self, **kwargs):
    3. self.namespaces = {}
    4. for name, uri in kwargs.items():
    5. self.register(name, uri)
    6. def register(self, name, uri):
    7. self.namespaces[name] = '{'+uri+'}'
    8. def __call__(self, path):
    9. return path.format_map(self.namespaces)

    通过下面的方式使用这个类:

    1. >>> ns = XMLNamespaces(html='http://www.w3.org/1999/xhtml')
    2. >>> doc.find(ns('content/{html}html'))
    3. <Element '{http://www.w3.org/1999/xhtml}html' at 0x1007767e0>
    4. >>> doc.findtext(ns('content/{html}html/{html}head/{html}title'))
    5. 'Hello World'
    6. >>>

    讨论

    解析含有命名空间的XML文档会比较繁琐。上面的 XMLNamespaces 仅仅是允许你使用缩略名代替完整的URI将其变得稍微简洁一点。

    很不幸的是,在基本的 ElementTree 解析中没有任何途径获取命名空间的信息。但是,如果你使用 iterparse() 函数的话就可以获取更多关于命名空间处理范围的信息。例如:

    1. >>> from xml.etree.ElementTree import iterparse
    2. >>> for evt, elem in iterparse('ns2.xml', ('end', 'start-ns', 'end-ns')):
    3. ... print(evt, elem)
    4. ...
    5. end <Element 'author' at 0x10110de10>
    6. start-ns ('', 'http://www.w3.org/1999/xhtml')
    7. end <Element '{http://www.w3.org/1999/xhtml}title' at 0x1011131b0>
    8. end <Element '{http://www.w3.org/1999/xhtml}head' at 0x1011130a8>
    9. end <Element '{http://www.w3.org/1999/xhtml}h1' at 0x101113310>
    10. end <Element '{http://www.w3.org/1999/xhtml}body' at 0x101113260>
    11. end <Element '{http://www.w3.org/1999/xhtml}html' at 0x10110df70>
    12. end-ns None
    13. end <Element 'content' at 0x10110de68>
    14. end <Element 'top' at 0x10110dd60>
    15. >>> elem # This is the topmost element
    16. <Element 'top' at 0x10110dd60>
    17. >>>

    最后一点,如果你要处理的XML文本除了要使用到其他高级XML特性外,还要使用到命名空间,建议你最好是使用 lxml 函数库来代替 ElementTree 。例如,lxml 对利用DTD验证文档、更好的XPath支持和一些其他高级XML特性等都提供了更好的支持。这一小节其实只是教你如何让XML解析稍微简单一点。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c06/p07_parse_xml_documents_with_namespaces.html