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