Шаг 107.
Python: сборник рецептов.
Кодирование и обработка данных. Парсинг XML-документов с пространствами имен

    На этом шаге мы рассмотрим, как можно решить эту задачу.

Задача

    Вам нужно распарсить XML-документ, но он использует пространства имен XML.

Решение

    Предположим, что у нас есть документ, использующий пространства имен:

<?xml version="1.0" encoding="utf-8"?>
<top>
  <author>David Beazley</author>
  <content>
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        <title>Hello World</title>
      </head>
      <body>
        <h1>Hello World!</h1>
      </body>
    </html>
  </content>
</top>

    Если вы парсите этот документ и пытаетесь выполнить обычные запросы, вы обнаружите, что это не работает так просто, поскольку все становится невероятно многословным:

>>> from xml.etree.ElementTree import parse, Element
>>> doc = parse('autor.xml')
>>> # Некоторые запросы, которые работают
>>> doc.findtext('author')
'David Beazley'
>>> doc.find('content')
<Element 'content' at 0x00000264BFA25CC0>
>>> # Запрос с использованием пространства имен (не работает)
>>> doc.find('content/html')
>>> # Работает при полном определении
>>> doc.find('content/{http://www.w3.org/1999/xhtml}html')
<Element '{http://www.w3.org/1999/xhtml}html' at 0x00000264BFA25BD0>
>>> # Не работает
>>> doc.findtext('content/{http://www.w3.org/1999/xhtml}html/head/title')
>>> # Полностью определен
>>> doc.findtext('content/{http://www.w3.org/1999/xhtml}html/' 
    '{http://www.w3.org/1999/xhtml}head/{http://www.w3.org/1999/xhtml}title')
'Hello World'
>>> 

    Часто вы можете упростить дело путем заворачивания работы с пространством имен во вспомогательный класс:

>>> class XMLNamespaces:
	def __init__(self, **kwargs):
		self.namespaces = {}
		for name, uri in kwargs.items():
			self.register(name, uri)
	def register(self, name, uri):
		self.namespaces[name] = '{' + uri + '}'
	def __call__(self, path):
		return path.format_map(self.namespaces)

	

    Чтобы использовать этот класс, вы можете поступить так:

>>> ns = XMLNamespaces(html='http://www.w3.org/1999/xhtml')
>>> doc.find(ns('content/{html}html'))
<Element '{http://www.w3.org/1999/xhtml}html' at 0x00000264BFA25BD0>
>>> doc.findtext(ns('content/{html}html/{html}head/{html}title'))
'Hello World'
>>> 


Обсуждение

    Парсинг XML-документов, содержащих пространства имен, может быть запутанным. Класс XMLNamespaces на самом деле предназначен для облегчения этой задачи: он позволяет использовать сокращенные имена для пространств имен в последующих операциях, а не полные URI.

    К несчастью, в базовом парсере ElementTree нет механизма для получения дополнительной информации о пространствах имен. Однако вы можете получить немного больше информации об области видимости обработки пространств имен, если будете использовать функцию iterparse(). Например:

>>> from xml.etree.ElementTree import iterparse
>>> for evt, elem in iterparse('autor.xml', ('end', 'start-ns', 'end-ns')):
	print(evt, elem)

	
end <Element 'author' at 0x00000264BFA76540>
start-ns ('', 'http://www.w3.org/1999/xhtml')
end <Element '{http://www.w3.org/1999/xhtml}title' at 0x00000264BFA767C0>
end <Element '{http://www.w3.org/1999/xhtml}head' at 0x00000264BFA76720>
end <Element '{http://www.w3.org/1999/xhtml}h1' at 0x00000264BFA768B0>
end <Element '{http://www.w3.org/1999/xhtml}body' at 0x00000264BFA76860>
end <Element '{http://www.w3.org/1999/xhtml}html' at 0x00000264BFA76680>
end-ns None
end <Element 'content' at 0x00000264BFA76590>
end <Element 'top' at 0x00000264BFA764F0>
>>> 

    Последнее замечание: если текст, который вы парсите, использует пространства имен в дополнение к другим продвинутым возможностям XML, вам лучше перейти с ElementTree на библиотеку lxml. Например, она предоставляет улучшенную поддержку валидации документов по DTD, более полную поддержку XPath и другие продвинутые возможности XML. А этот рецепт - просто небольшой фикс для облегчения парсинга.

    На следующем шаге мы рассмотрим взаимодействие с реляционной базой данных.




Предыдущий шаг Содержание