Welcome to xlml’s documentation!

1. Введение: Python и XML

Благодаря постоянному развитию как Python, так и XML, существует множество пакетов, которые помогают вам читать, генерировать и изменять XML-файлы из сценариев Python. По сравнению с большинством из них пакет lxml имеет два больших преимущества:

  • Производительность. Чтение и запись даже довольно больших файлов XML занимает почти незаметное количество времени.
  • Простота программирования. Пакет lxml основан на ElementTree, который Fredrik Lundh изобрел, чтобы упростить и оптимизировать обработку XML.

lxml во многом похож на два других, более ранних пакета:

  • Fredrik Lundh продолжает поддерживать свою первоначальную версию ElementTree.
  • xml.etree.ElementTree теперь является официальной частью библиотеки Python. Существует C-языковая версия cElementTree, которая может быть даже быстрее, чем lxml для некоторых приложений.

Однако автор предпочитает lxml из-за наличия ряда дополнительных функций, которые облегчают жизнь. В частности, поддержка XPath значительно упрощает управление более сложными структурами XML.

2. Как ElementTree представляет XML

Если вы работали с XML с помощью Document Object Model (DOM), вы обнаружите, что пакет lxml имеет совершенно другой способ представления документов в виде деревьев. В DOM деревья построены из узлов, представленных как экземпляры узлов. Некоторые узлы представляют собой экземпляры Element, представляющие целые элементы. Каждый элемент имеет набор дочерних узлов различных типов: Узлы элементов для дочерних элементов; Узлы атрибутов для атрибутов; и Текстовые узлы для текстового содержимого.

Вот небольшой фрагмент XHTML и его представление как дерева DOM:

Альтернативный текст
<p>To find out <em>more</em>, see the
<a href="http://www.w3.org/XML">standard</a>.</p>

На приведенной выше диаграмме показана концептуальная структура XML. В отличие от этого представление lxml документа XML создает дерево только одного типа узла: Element.

Основное различие между представлением ElementTree, используемым в lxml, и классическим представлением, - это объединение текста с элементами: оно очень отличается в lxml.

Экземпляр класса Element lxml содержит следующие свойства:

.tag
Имя элемента, например «p» для абзаца или «em» для emphasis.
.text
Текст внутри элемента, если он есть, до первого дочернего элемента. Этот атрибут None, если элемент пуст или не имеет текста перед первым дочерним элементом.
.tail
Текст, следующий за элементом. Это самый необычный обход. В модели DOM любой текст, следующий за элементом E, связан с родителем E; в lxml этот текст считается “tail” E.
.attrib
Словарь Python, содержащий имена атрибутов XML элемента и их соответствующие значения. Например, для элемента “<h2 class =” arch “id =” N15 “>”, атрибуты этого элемента будут словарем “{” class ”:” arch ”,” id ”:” N15 “}”.

(element children)

Чтобы получить доступ к подэлементам, рассматривайте элемент как список. Например, если узел является экземпляром элемента, узел [0] является первым подэлементом узла. Если узел не имеет каких-либо подэлементов, эта операция вызовет исключение IndexError.

Вы можете узнать количество подэлементов, используя функцию len (). Например, если узел имеет пять дочерних элементов, len (node) вернет значение 5.

Одним из преимуществ представления lxml является то, что теперь дерево состоит только из одного типа узлов: каждый узел является экземпляром элемента. Вот наш XML-фрагмент снова и изображение его представления в lxml.

Альтернативный текст
<p>To find out <em>more</em>, see the
<a href="http://www.w3.org/XML">standard</a>.</p>

Обратите внимание, что в представлении lxml текст «, see then» (который включает в себя новую строку) содержится в атрибуте .tail элемента em, не связанного с элементом p, как это было бы в представлении DOM. Так же ”.” в конце абзаца находится в атрибуте .tail элемента a (link).

Теперь, когда вы знаете, как XML представлен в lxml, есть три общие области применения.

3. Чтение XML-документа

Предположим, вы хотите извлечь некоторую информацию из документа XML. Вот общая методика:

  1. Вам нужно будет импортировать пакет lxml. Вот один из способов как сделать это:
from lxml import etree
  1. Обычно ваш XML-документ находится в файле. Предположим, что ваш файл называется test.xml; чтобы прочитать документ, вы можете сделать что-то вроде:
doc = etree.parse('test.xml')

Возвращаемое значение doc - это экземпляр класса ElementTree, который представляет ваш XML-документ в виде дерева.

После того, как у вас есть документ в этой форме, обратитесь к разделу 8. Класс ElementTree: XML-документ, чтобы узнать, как перемещаться по дереву и извлекать различные части его структуры.

Для других методов создания ElementTree см. 7. Особенности модуля etree.

4. Обработка нескольких пространств имен

Пространство имен в XML представляет собой набор имен элементов и атрибутов. Например, в пространстве имен XHTML мы находим имена элементов, такие как body, link и h1, а также имена атрибутов, такие как href и align.

Для простых документов все имена элементов и атрибутов документа могут находиться в одном пространстве имен. В целом, однако, XML-документ может включать имена элементов и атрибутов из многих пространств имен.

4.1. Глоссарий терминов пространства имен

4.1.1. URI: Universal Resource Identifier

Формально каждое пространство имен называется URI или универсальным идентификатором ресурса. Хотя URI часто выглядит как URL-адрес, существует важная разница:

  • URL (Universal Resource Locator) соответствует более или менее фактической веб-странице. Если вы вставляете URL-адрес в свой браузер, вы ожидаете получить какую-либо веб-страницу.
  • URI - это просто уникальное имя, которое идентифицирует конкретную концептуальную сущность. Если вы вставляете его в браузер, вы можете получить веб-страницу или не можете; не требуется, чтобы URI, который определяет данное пространство имен, также является URL-адресом.

4.1.2. NSURI: Namespace URI

Не все URIs определяют пространства имен.

Термин NSURI для NameSpace URI - это URI, который используется для уникальной идентификации определенного пространства имен XML.

Примечание

The W3C Recommendation Namespaces in XML 1.0 предпочитают термин имя пространства имен для более широко используемого NSURI.

Например, здесь есть NSURI, который идентифицирует «XHTML 1.0 Strict» диалект XHTML:

http://www.w3.org/1999/xhtml

4.1.3. Пустое пространство имен

В рамках данного документа ни один набор имен элементов и атрибутов не может быть отнесен к определенному пространству имен и соответствующему NSURI. Эти элементы и атрибуты, как говорят, находятся в пустом пространстве имен.

Это удобно для документов, чьи имена элементов и атрибутов находятся в одном пространстве имен. Это также характерно для неформальных и экспериментальных приложений, где разработчик не хочет беспокоиться о том, чтобы определить NSURI для пространства имен или еще не создал его.

Например, многие страницы XHTML используют пустое пространство имен, потому что все имена находятся в одном пространстве имен и потому, что браузеру не требуется NSURI, чтобы отображать их правильно.

4.1.4. Clark notation

Каждый элемент и имя атрибута в документе связаны с определенным пространством имен и соответствующим им NSURI, иначе он находится в пустом пространстве имен. В общем случае документ может указывать NSURI для каждого пространства имен; см. раздел 4.2. Синтаксис документов с несколькими пространствами имен.

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

Когда ваш документ представлен как ElementTree, атрибут .tag, который указывает на Element , содержит как NSURI, так и имя элемента с использованием обозначения Кларка, названного в честь его изобретателя Джеймса Кларка.

Когда NSURI элемента известен, атрибут .tag содержит строку этой формы:

"{NSURI}name"

Например, когда правильно построенный документ XHTML 1.0 Strict обрабатывается в ElementTree, атрибут .tag корневого элемента документа будет выглядеть следующим образом:

"{http://www.w3.org/1999/xhtml}html"

Примечание

Нотация Кларка фактически не появляется в исходном файле XML. Она используется только в представлении ElementTree документа.

Для имен элементов и атрибутов в пустом пространстве имен нотация Кларка - это просто имя без префикса «{NSURI}».

4.1.5. Предок (Ancestor)

В понятие предок элемента включают его непосредственного родителя, родителя родительского элемента и т.д. до корня дерева. У корневого узла нет предков.

4.1.6. Потомок (Descendant)

Потомки элемента включают его прямых детей, детей его детей и т.д. до листьев дерева документа.

4.2. Синтаксис документов с несколькими пространствами имен

Внешняя форма XML-документа использует префиксы пространства имен для различения имен из разных пространств имен. Каждый NSURI каждого префикса должен быть определен внутри документа, за исключением пустого пространства имен, если оно есть.

Вот небольшой фрагмент, чтобы показать вам общую идею:

<fo:inline font-style='italic' font-family='sans-serif'>
<xsl:copy-of select="$content"/>
</fo:inline>

inline элемент находится в пространстве имен XSL-FO, которое в этом документе использует префикс пространства имен fo:. Элемент copy-of находится в пространстве имен XSLT, префиксом которого является «xsl:».

В вашем документе вы должны определить NSURI, соответствующий каждому префиксу пространства имен. Это можно сделать несколькими способами.

  • Любой элемент может содержать атрибут «xmlns: P =« NSURI »», где P - префикс пространства имен для этого NSURI.
  • Любой элемент может содержать атрибут «xmlns =» NSURI ». Это определяет NSURI, связанный с пустым пространством имен.
  • Если элемент или атрибут не содержит префикс пространства имен, он наследует NSURI ближайшего элемента-предка, который имеет префикс.
  • Некоторые атрибуты могут встречаться где угодно в любом документе в пространстве имен «xml:», которое всегда определяется.

Например, любой элемент может иметь атрибут «xml: id», который служит для идентификации уникального элемента в документе.

Вот небольшой правильный XHTML-файл со всеми украшениями, рекомендованными организацией W3C:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
  <title>My page title</title>
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

Атрибут xmlns элемента html указывает, что все его дочерние элементы находятся в пространстве имен XHTML 1.0 Strict.

Атрибут xml: lang = “en” указывает, что документ использует английский язык.

Вот более подробный пример. Это корневой элемент таблицы стилей XSLT. Префикс «xsl:» используется для элементов XSLT; префикс «fo:» используется для элементов XSL-FO; и третье пространство имен с префиксом «date:» также включено. В этом документе не используется пустое пространство имен.

    <xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:date="http://exslt.org/dates-and-times">

4.3. Карты пространства имен

Карта пространства имен - это словарь Python, который связывает префиксы пространства имен с пространствами имен. Ключи словаря являются префиксами пространства имен, и каждое связанное значение является именем пространства имен как NSURI.

Карты пространства имен используются в нескольких целях.

  • При чтении XML-файла с несколькими пространствами имен вы можете использовать карту пространства имен в процессе поиска и извлечения элементов и атрибутов из ElementTree. См., Например, раздел 9.5, «Element.find (): найти соответствующий подэлемент».
  • При создании нового документа XML, содержащего элементы в нескольких пространствах имен, вы можете использовать карту пространства имен, чтобы указать, какие префиксы пространства имен появятся, когда ElementTree будет сериализован в XML-форму. См. Раздел 7.2, «Конструктор Element ()» и раздел 7.8 «Конструктор SubElement ()» для подробностей.

Например, в конце раздела 4.2 «Синтаксис документов с несколькими именами» есть тег начала xsl: stylesheet, который определяет xsl: как префикс для пространства имен XSLT, fo: для пространства имен XSL-FO и date : для date-and-time. Вот карта пространства имен, которая описывает те же отношения префиксов к NSURI:

nsm = {"xsl":  "http://www.w3.org/1999/XSL/Transform",
"fo":   "http://www.w3.org/1999/XSL/Format",
"date": "http://exslt.org/dates-and-times"}

Чтобы определить NSURI пустого пространства имен , используйте запись с ключом None. Например, эта карта пространства имен будет определять элементы без пространства имен как принадлежащие XHTML, а элементы с префиксом пространства имен «xl:» относятся к пространству имен XLink:

nsm = {None: "http://www.w3.org/1999/xhtml",
"xl": "http://www.w3.org/1999/xlink"}

5. Создание нового документа XML

Если вашей программе необходимо написать какой-то вывод в виде XML-документа, пакет lxml упрощает эту операцию.

  1. Сначала импортируйте пакет lxml. Вот один из способов:
from lxml import etree
  1. Создайте корневой элемент. Например, предположим, что вы создаете веб-страницу; корневым элементом является html. Используйте конструктор etree.Element () для создания этого элемента.
page = etree.Element('html')
  1. Затем используйте конструктор etree.ElementTree (), чтобы создать новое дерево документов, используя наш html-элемент в качестве его корня:
doc = etree.ElementTree(page)
  1. Конструктор etree.SubElement () идеально подходит для добавления новых дочерних элементов в наш документ. Вот код для добавления элемента head, а затем тело как элемент, как новые дети элемента html:
headElt = etree.SubElement(page, 'head')
bodyElt = etree.SubElement(page, 'body')
  1. На странице будет нужен элемент title ребенок элемента head. Добавьте текст в этот элемент, сохранив строку в свой .text атрибут:
title = etree.SubElement(headElt, 'title')
title.text = 'Your page title here'
  1. Чтобы указать значения атрибута, используйте аргументы ключевого слова для конструктора SubElement (). Например, предположим, что вам нужна ссылка на таблицу стилей внутри элемента head, которая выглядит так:
<link rel='stylesheet' href='mystyle.css' type='text/css'>

Этот код сделает это:

linkElt = etree.SubElement(headElt, 'link', rel='stylesheet',
href='mystyle.css', type='text/css')
  1. Продолжайте создавать новый документ, используя различные функции, описанные в разделе 7. Особенности модуля etree и раздел 9. Класс Element: один элемент дерева.
  1. Когда документ полностью построен, запишите его в файл с помощью метода .write () экземпляра ElementTree, который принимает файловую переменную.
outFile = open('homemade.xml', 'w')
doc.write(outFile)

6. Изменение существующего XML-документа

Если ваша программа нуждается в чтении документа XML, изменении и записи, эта операция выполняется с помощью lxml.

  1. Начните с чтения документа, используя методы из раздела 3. Чтение XML-документа.
  1. Измените дерево документа, добавив, удалив или заменив элементы, атрибуты, текст и другие функции.

Например, предположим, что ваша программа имеет переменную linkNode, которая содержит экземпляр Element, представляющий элемент HTML «a» (гиперссылку), и вы хотите изменить значение его атрибута href, чтобы указать на другой URL-адрес, например http: // www.nmt.edu/. Этот код будет делать это:

linkNode.attrib['href'] = 'http://www.nmt.edu/'
  1. Наконец, запишите документ обратно в файл, как описано в разделе 5. Создание нового документа XML.

7. Особенности модуля etree

etree содержит множество функций и конструкторов классов.

7.1. Конструктор Comment()

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

etree.Comment(text=None)
text
Текст, который будет помещен в комментарий. После сериализации в формате XML этому тексту будет предшествовать «<! -», а затем «->».

Возвращаемое значение является экземпляром класса Comment. Используйте метод .append() для родительского элемента, чтобы поместить комментарий в ваш документ.

Например, предположим, что bodyElt является элементом HTML. Чтобы добавить комментарий к этому элементу, содержащему строку s, вы должны использовать этот код:

newComment = etree.Comment(s)
bodyElt.append(newComment)

7.2. Конструктор Element()

Этот конструктор создает и возвращает новый экземпляр Element.

etree.Element(tag, attrib={}, nsmap=None, **extras)
tag
Строка, содержащая имя создаваемого элемента.
attrib
Словарь, содержащий имена и значения атрибутов, которые необходимо добавить в элемент. Значение по умолчанию - отсутствие атрибутов.
nsmap
Если ваш документ содержит несколько пространств имен XML, вы можете указать карту пространства имен, которая определяет префиксы пространства имен, которые вы хотели бы использовать, когда этот документ преобразуется в XML. См. Раздел 4.3 «Карты пространства имен».
extras
Любые аргументы в виде name = value, которое вы поставляете конструктору, добавляются к атрибутам элемента. Например, этот код:
newReed = etree.Element('reed', pitch='440', id='a4')

создаст элемент, который выглядит так:

<reed pitch='440' id='a4'/>

Ниже приведен пример создания документа с несколькими пространствами имен с использованием аргумента ключевого слова nsmap.

#!/usr/bin/env python
import sys
from lxml import etree as et

HTML_NS  =  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
XSL_NS   =  "http://www.w3.org/1999/XSL/Transform"
NS_MAP = {None:  HTML_NS,
  "xsl": XSL_NS}

rootName = et.QName(XSL_NS, 'stylesheet')
root = et.Element(rootName, nsmap=NS_MAP)
sheet = et.ElementTree(root)

top = et.SubElement(root, et.QName(XSL_NS, "template"), match='/')
html = et.SubElement(top, et.QName(HTML_NS, "html"))
head = et.SubElement(html, "head")
title = et.SubElement(head, "title")
title.text = "Heading title"
body = et.SubElement(html, "body")
h1 = et.SubElement(body, "h1")
h1.text = "Body heading"
p = et.SubElement(body, "p")
p.text = "Paragraph text"
sheet.write(sys.stdout, pretty_print=True)

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

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <xsl:template match="/">
        <html>
                <head>
                        <title>Heading title</title>
                </head>
                <body>
                        <h1>Body heading</h1>
                        <p>Paragraph text</p>
                </body>
        </html>
  </xsl:template>
</xsl:stylesheet>

Существует одна незначительная патология этого конструктора. Если вы передадите предварительно построенный словарь в качестве аргумента атрибута, а также укажите аргументы ключевого слова, значения аргументов ключевого слова будут добавлены в этот словарь, как если бы вы использовали метод .update () в словаре атрибутов. Вот пример, показывающий этот побочный эффект:

>>> from lxml import etree
>>> d = { 'name': 'Clem', 'clan': 'bozo' }
>>> clownElt = etree.Element('clown', d, attitude='bad')
>>> d
{'clan': 'bozo', 'attitude': 'bad', 'name': 'Clem'}
>>> etree.tostring(clownElt)
'<clown clan="bozo" attitude="bad" name="Clem"/>'
>>>

7.3. Конструктор ElementTree()

Чтобы создать новый пустой документ, используйте этот конструктор. Он возвращает новый экземпляр ElementTree.

etree.ElementTree(element=None, file=None)
element
Экземпляр элемента, который будет использоваться в качестве корневого элемента.
file
Чтобы создать ElementTree, который представляет существующий файл, передайте либо записываемый файловый объект, либо строку, содержащую имя файла. Не используйте аргумент element; если вы это сделаете, аргумент файла будет проигнорирован.

Например, чтобы преобразовать файл с именем balrog.xml в ElementTree, используйте это выражение:

balrogTree = etree.ElementTree(file='balrog.xml')

Исключения, которые могут быть вызваны этим конструктором, включают:

IOError
Если файл несуществующий или нечитаемый.
etree.XMLSyntaxError
Если файл доступен для чтения, но его содержимое не является корректным XML.

Возвращаемое значение исключения имеет атрибут .error log, который вы можете отобразить, чтобы узнать, где произошли ошибки файла. Вот пример:

>>> try:
...   bad = etree.fromstring("<a>\n<<oops>\n</a>")
... except etree.XMLSyntaxError, detail:
...   pass
...
>>> detail
<etree.XMLSyntaxError instance at 0xb7eba10c>
>>> detail.error_log
<string>:2:FATAL:PARSER:ERR_NAME_REQUIRED: StartTag: invalid element name
<string>:3:FATAL:PARSER:ERR_TAG_NAME_MISMATCH: Opening and ending tag mismatch: oops line 2 and a
<string>:3:FATAL:PARSER:ERR_TAG_NOT_FINISHED: Premature end of data in tag a line 1
>>>

7.4. Функция fromstring(): создать элемент из строки

Вы можете создать элемент или дерево элементов из строки, содержащей XML, с помощью этой функции; он возвращает новый экземпляр Element, представляющий весь этот XML.

etree.fromstring(s)

где s - строка.

Вот пример:

>>> milne = '''<monster name='Heffalump'>
...   <trail>Woozle</trail>
...   <eeyore mood='boggy'/>
... </monster>'''
>>> doc = etree.fromstring(milne)
>>> print etree.tostring(doc)
<monster name="Heffalump">
  <trail>Woozle</trail>
  <eeyore mood="boggy"/>
</monster>
>>>

7.5. Функция parse(): постройте ElementTree из файла

Самый быстрый способ преобразования XML-файла в ElementTree - использовать эту функцию:

etree.parse(source)

где source - это имя файла или файл, содержащий XML. Если файл корректно сформирован, функция возвращает экземпляр ElementTree.

Исключения включают:

IOError
Файл отсутствует или не читается.
etree.XMLSyntaxError
Файл читается, но не содержит хорошо сформированного XML. Возвращенное исключение содержит атрибут .error_log, который вы можете распечатать, чтобы увидеть, где произошла ошибка. Пример отображения error_log см. В разделе 7.3 «Конструктор ElementTree ()».

7.6. Конструктор ProcessingInstruction()

Чтобы добавить инструкцию XML в документ, используйте этот конструктор. Он возвращает новый экземпляр ProcessingInstruction; чтобы поместить это в дерево, передать этот экземпляр методу .append() родительского элемента.

etree.ProcessingInstruction(target, text=None):
target
Строка, содержащая целевую часть инструкции обработки.
text
Необязательная строка, содержащая остальную часть инструкции обработки. Значение по умолчанию пуст.

Вот пример:

pi = etree.ProcessingInstruction('decor', 'danish,modern,ducksOnWall')

После преобразования в XML эта инструкция обработки будет выглядеть так:

<?decor danish,modern,ducksOnWall?>

7.7. Конструктор QName()

Когда вы работаете с несколькими пространствами имен, объект QName полезен для объединения части «URI пространства имен» с частью «локального имени». Экземпляр QName может использоваться для части имени атрибутов, которые находятся в другом пространстве имен, чем их содержащий элемент.

Хотя это не является законным в именах XML-элементов, существует конвенция под названием «обозначение Кларка» (после Джеймса Кларка), которая объединяет эти две части в строке этой формы:

{nsURI}local

Чтобы создать новый экземпляр QName, используйте инструкцию этого общего вида:

etree.QName(text, tag=none)
  • Если полное имя элемента уже указано в обозначении Кларка, вызовите конструктор QName только с этим аргументом.
  • Если вы хотите передать URI пространства имен и локальное имя отдельно, вызовите QName с URI пространства имен в качестве текстового аргумента и локальное имя в качестве аргумента тега.

Вот два примера создания экземпляра QName, представляющего квалифицированное имя в пространстве имен XSLT с локальным именем шаблона:

  • В нотации Кларка:
qn = etree.QName("{http://www.w3.org/1999/XSL/Transform}template")
  • Если URI пространства имен и локальное имя указаны отдельно:
qn = etree.QName("http://www.w3.org/1999/XSL/Transform", "template")

7.8. Конструктор SubElement()

Это удобный конструктор, который выполняет две основные операции при добавлении элемента в дерево:

  • создание нового экземпляра элемента и
  • добавление нового элемента который является следующим дочерним элементом его родительского элемента.

Вот общая форма:

SubElement(parent, tag, attrib={}, nsmap=None, **extras):

Первый аргумент parent - это экземпляр Element, в котором новый экземпляр Element должен быть добавлен в качестве следующего дочернего элемента. Параметры тега, атрибута, nsmap и **extras работают точно так же, как и при вызове Element (), описанном в разделе 7.2, «Конструктор Element ()».

Возвращаемое значение - это вновь созданный элемент.

Вот пример. Предположим, вы хотите построить этот XML:

<state name="New Mexico">
  <county name="Socorro">
    <ppl name="Luis Lopez"/>
  </county>
</state>

Вот код для его создания, а затем отображать его в интерактивном режиме:

>>> st=etree.Element('state', name='New Mexico')
>>> co=etree.SubElement(st, 'county', name='Socorro')
>>> ppl=etree.SubElement(co, 'ppl', name='Luis Lopez')
>>> print etree.tostring(st)
<state name="New Mexico"><county name="Socorro"><ppl name="Luis Lopez"/>
</county></state>
>>>

7.9. Функция tostring(): Сериализовать как XML

Чтобы преобразовать Element и его содержимое обратно в XML, используйте вызов функции этой формы:

etree.tostring(elt, pretty_print=False, encoding=None)

где elt - экземпляр элемента. Функция возвращает строку, содержащую XML. Например, см. Раздел 7.8, «Конструктор SubElement ()».

Если вы установите опциональный аргумент pretty_print в значение True, метод попытается вставить разрывы строк, если возможно, чтобы длина строк была коротка.

Чтобы вывести Unicode, используйте ключевое слово encoding = unicode.

7.10. Функция XMLID(): преобразование текста в XML со словарем значений id

..To convert XML in the form of a string into an Element structure, use Section 7.4, “The fromstring() function: Create an element from a string”. However, there is a similar function named etree.XMLID() that does this and also provides a dictionary that allows you to find elements in a tree by their unique id attribute values.

Чтобы преобразовать XML в виде строки в структуру элемента, используйте раздел 7.4, «Функция fromstring (): создайте элемент из строки». Тем не менее, существует аналогичная функция с именем etree.XMLID (), которая делает это, а также предоставляет словарь, который позволяет находить элементы в дереве по их уникальным значениям атрибута id.

Стандарт XML предусматривает, что любой элемент в любом документе может иметь атрибут id, но каждое значение этого атрибута должно быть уникальным в документе. Цель этой функции заключается в том, что приложения могут ссылаться на любой элемент, используя его значение id.

Вот общий вид этой функции:

etree.XMLID(text)

Возвращаемое значение представляет собой кортеж (E, D), где:

  • E - это преобразованный XML как экземпляр элемента, укореняющий преобразованное дерево, так же, как если бы вы вызвали etree.fromstring (текст).
  • D - словарь, ключи которого являются значениями атрибутов id в преобразованном дереве, и каждое соответствующее значение является экземпляром элемента, который несет это значение id.

Вот небольшой пример скрипта:

#!/usr/bin/env python
from lxml import etree

SOURCE = '''<dog id="Fido">
Woof!
<cat id="Fluff">Mao?</cat>
<rhino id="ZR"/>
</dog>'''
tree, idMap = etree.XMLID(SOURCE)

for id in sorted(idMap.keys()):
        elt = idMap[id].text or "(none)"
        print "Tag {0}, text is '{1}'".format(id, elt.strip())

И его вывод:

Tag Fido, text is 'Woof!'
Tag Fluff, text is 'Mao?'
Tag ZR, text is '(none)'

8. Класс ElementTree: XML-документ

После того, как вы использовали конструктор etree.ElementTree для создания XML-документа, вы можете использовать эти методы в этом экземпляре.

8.1. ElementTree.find()

ET.find(path[, namespaces=D])

Этот метод используется для поиска конкретного элемента в документе. Это по существу эквивалентно вызову метода .find() в корневом элементе документа; см. раздел 9.5, «Element.find(): найдите соответствующий подэлемент».

Например, если doc является экземпляром ElementTree, этот вызов:

doc.find('h1')

эквивалентно:

doc.getroot().find('h1')

8.2. ElementTree.findall(): поиск соответствующих элементов

Учитывая некоторый экземпляр ElementTree, этот метод вернет последовательность из нуля или более элементов, которые соответствуют шаблону, указанному аргументом path.

ET.findall(path[, namespaces=N])

Этот метод работает точно так же, как вызов метода .findall() в корневом элементе документа. См. Раздел 9.6, «Element.findall(): найти все соответствующие подэлементы».

8.3. ElementTree.findtext(): получение текстового содержимого из элемента

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

ET.findtext(path[, default=None][, namespaces=N])

Этот метод по существу такой же, как вызов метода .findtext() в корневом элементе документа; см. раздел 9.7, «Element.findtext(): Извлечь текстовый контент».

8.4. ElementTree.getiterator(): создать итератор

Во многих приложениях вы захотите посетить каждый элемент документа или, возможно, получить информацию из всех тегов определенного типа. Этот метод, на некоторых экземплярах ElementTree ET, вернет итератор, который посещает все соответствующие теги.

ET.getiterator(tag=None)

Если вы опустите аргумент, вы получите итератор, который генерирует каждый элемент в дереве в порядке документа.

Если вы хотите посетить только теги с определенным именем, передайте это имя в качестве аргумента.

Вот несколько примеров. В этих примерах предположим, что page - это экземпляр ElementTree, содержащий страницу XHTML. В первом примере будет напечатано каждое имя тега на странице в порядке документа.

for  elt in page.getiterator():
print elt.tag

Второй пример будет рассматривать каждый элемент div на странице, а для тех, у кого есть атрибут class, он печатает эти атрибуты.

for  elt in page.getiterator('div'):
        if  elt.attrib.has_key('class'):
                print elt.get('class')

8.5. ElementTree.getroot(): найдите корневой элемент

ET.getroot()

Возвращаемое значение обычно будет экземпляром корневого элемента. Однако, если вы создали свой экземпляр ElementTree без указания корневого элемента или входного файла, этот метод вернет None.

8.6. ElementTree.xpath(): оценить выражение XPath

Для экземпляра ElementTree ET используйте этот вызов метода для оценки выражения XPath s, используя корневой элемент дерева как узел контекста.

ET.xpath(s)

Эти методы возвращают результат выражения XPath. Общие обсуждения XPath см. В разделе 10 «Обработка XPath».

8.7. ElementTree.write(): Записать обратно в XML

Чтобы сериализовать (конвертировать в XML) содержимое документа, содержащегося в некотором экземпляре ElementTree, применяйте этот метод:

ET.write(file, pretty_print=False)

Вы должны указать записываемый файловый объект или имя файла, который нужно записать. Если вы установите аргумент pretty_print = True, метод попытается свернуть длинные строки и отступы XML для удобочитаемости.

Например, если у вас есть экземпляр ElementTree на странице переменных, содержащей страницу XHTML, и вы хотите записать ее в стандартный выходной поток, это заявление будет делать это:

import sys
page.write(sys.stdout)

9. Класс Element: один элемент дерева

Каждый элемент XML представлен экземпляром класса Element.

9.1. Атрибуты экземпляра элемента

Каждый экземпляр класса Element имеет эти атрибуты.

.attrib
Словарь, содержащий атрибуты элемента. Ключами являются имена атрибутов, и каждое соответствующее значение является значением атрибута.
.base
Базовый URI из атрибута xml: base, который этот элемент содержит или наследует, если таковой имеется. В противном случае None.
.prefix
Префикс пространства имен этого элемента, если он есть, в противном случае None.
.sourceline
Номер строки этого элемента при анализе, если известен, в противном случае Нет.
.tag
Имя элемента.
.tail
Текст, следующий за закрывающим тегом этого элемента, вплоть до начального тега следующего элемента sibling. Если там не было текста, этот атрибут будет иметь значение None.

    Этот способ связывания текста с элементами не является типичным для большинства моделей обработки XML; см. раздел 2 «Как ElementTree представляет XML».

.text
Текст внутри элемента, вплоть до начального тега первого дочернего элемента. Если там не было текста, этот атрибут будет иметь значение None.

9.2. Доступ к списку дочерних элементов

Во многих случаях экземпляр Element действует как список Python, а его дочерние элементы XML действуют как члены этого списка.

Вы можете использовать функцию len() Python, чтобы определить, сколько детей имеет элемент. Например, если узел является экземпляром Element с пятью дочерними элементами элемента, len(node) вернет значение 5.

Вы можете добавлять, заменять или удалять дочерние элементы элемента, используя регулярные операции с списком Python. Например, если узел экземпляра Element имеет три дочерних элемента, узел [0] является первым потомком, а узел [2] является третьим.

В следующих примерах предположим, что E является экземпляром элемента.

  • E [i] возвращает дочерний элемент E в позиции i, если он есть. Если в этой позиции нет дочернего элемента, эта операция вызывает исключение IndexError.
  • E [i: j] возвращает список дочерних элементов между позициями i и j.

    Например, node[2: 4] возвращает список, содержащий третий и четвертый дочерние узлы узла.

  • Вы можете заменить один дочерний элемент E новым элементом c, используя инструкцию этого вида:
E[i] = c

Если i не является существующим дочерним элементом, эта операция вызовет исключение IndexError.

  • Вы можете заменить последовательность соседних дочерних элементов элемента E, используя назначение среза:
E[i:j] = seq

где seq - последовательность экземпляров Element.

Если срез [i:j] не указывает существующий набор дочерних элементов, эта операция вызывает исключение IndexError.

  • Вы можете удалить один из таких элементов:
del E[i]

где i - индекс этого ребенка.

  • Вы можете удалить фрагмент из списка дочерних элементов элемента следующим образом:
del E[i:j]
  • Вы можете перебирать дочерние элементы элемента с циклом for. Например, если узел является экземпляром элемента, этот код будет печатать теги всех его дочерних элементов:
for kid in node:
        print kid.tag

Не все дети элемента сами по себе являются элементами.

  • Инструкции по обработке - это примеры класса etree._ProcessingInstruction.
  • Комментарии - это примеры класса etree._Comment.

Если вам нужно проверить, является ли данный дочерний узел инструкцией обработки или комментарием, вы можете использовать встроенную функцию Python isinstance(I, C), которая проверяет, является ли объект I экземпляром класса или подкласса класса C.

Например, чтобы проверить, является ли узел комментарием, вы можете использовать этот тест, который возвращает True, если узел является комментарием, False в противном случае.

issubclass(node, etree._Comment)

9.3. Element.append(): добавить новый дочерний элемент

Чтобы добавить нового элемента c в элемент E, используйте этот метод:

E.append(c)

Вы можете использовать этот метод для добавления экземпляров Comment и ProcessingInstruction в качестве дочерних элементов элемента, а также экземпляров Element.

Вот пример:

>>> st = etree.Element("state", name="New Mexico")
>>> etree.tostring(st)
'<state name="New Mexico"/>'
>>> co = etree.Element("county", name="Socorro")
>>> st.append(co)
>>> etree.tostring(st)
'<state name="New Mexico"><county name="Socorro"/></state>'
>>> rem = etree.Comment("Just another day in paradise.")
>>> st.append(rem)
>>> etree.tostring(st)
'<state name="New Mexico"><county name="Socorro"/><!-- Just another day in
paradise. --></state>'
>>>

9.4. Element.clear(): Сделать элемент пустым

Вызов метода .clear() на экземпляре Element удаляет все его содержимое:

  • Все значения удаляются из словаря .attrib.
  • У атрибутов .text и .tail устанавливаются значение None.
  • Любые дочерние элементы удаляются.

9.5. Element.find(): найдите соответствующий подэлемент

Вы можете искать подэлементы экземпляра Element E , используя этот метод:

E.find(path[, namespaces=D])

Этот метод выполняет поиск элемента и его потомков для одного элемента, который соответствует шаблону, описываемому аргументом path.

  • Если имеется ровно один соответствующий элемент, этот метод возвращает этот элемент как экземпляр элемента.
  • Если имеется несколько совпадающих элементов, метод возвращает тот, который появляется первым в порядке документа.
  • Если нет соответствующих элементов, он возвращает None.

Аргумент path - это строка, описывающая элемент, для которого вы ищете. Возможные значения:

“tag”
Найдите первый дочерний элемент, чье имя является «tag».

“tag1/tag2/.../tagn”

    Найдите первый дочерний элемент, чье имя - tag1; затем под этим дочерним элементом найдите первый дочерний элемент с именем tag2; и так далее.

Например, если узел является экземпляром Element, у которого есть дочерний элемент с тегом «county», и этот ребенок, в свою очередь, имеет дочерний элемент с тегом «seat», это выражение вернет элемент, соответствующий элементу «seat»:

node.find("county/seat")

Необязательный аргумент namespaces - это карта пространства имен; см. раздел 4.3 «Карты пространства имен». Если он указан, эта карта используется для интерпретации префиксов пространства имен в аргументе path.

Например, предположим, что у вас есть элемент someNode, и вы хотите найти дочерний элемент с именем roundtable в пространстве имен с именем http://example.com/mphg/, и под ним вы хотите найти дочерний элемент с именем knight в пространстве имен с именем http://example.org/sirs/ns/. Это сделает вызов :

nsd = {'mp': 'http://example.com/mphg/',
   'k':  'http://example.org/sirs/ns/'}
someNode.find('mp:roundtable/k:knight', namespaces=nsd}

Обратите внимание, что префиксы пространства имен, которые вы определяете таким образом, не должны иметь какого-либо определенного значения или соответствовать префиксам пространства имен, которые могут использоваться для этих NSURI в внешней форме документа.

Предупреждение

Аргумент ключевого слова namespaces для метода .find() доступен только для версии 2.3.0 или более поздней версии etree.

9.6. Element.findall(): найти все соответствующие подэлементы

Этот метод возвращает список потомков элемента, который соответствует шаблону, описываемому аргументом path.

E.findall(path[, namespaces=N])

Способ, которым аргумент пути описывает желаемый набор узлов, работает так же, как аргумент пути, описанный в разделе 9.5. Element.find(): найдите соответствующий подэлемент.

Например, если элемент статьи с именем root имеет ноль или более детей с именем section, этот вызов будет формировать список sectionList, содержащий экземпляры Element, представляющие этих детей.

sectionList = root.findall('section')

Необязательный аргумент ключевого слова namespaces позволяет указать карту пространства имен. Если указано, эта карта пространства имен используется для интерпретации префиксов пространства имен в пути; в разделе 9.5. Element.find(): найдите соответствующий подэлемент для получения более подробной информации.

Предупреждение

Аргумент ключевого слова namespaces доступен только с версии 2.3.0 версии lxml.etree.

9.7. Element.findtext(): извлечение текстового содержимого

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

E.findtext(path, default=None, namespaces=N)

Аргумент path указывает желаемый элемент таким же образом, как и аргумент path в разделе 9.5. Element.find(): найдите соответствующий подэлемент.

  • Если существуют какие-либо потомки E, которые соответствуют данному пути, этот метод возвращает текстовое содержимое первого элемента соответствия.
  • Если есть хотя бы один соответствующий элемент, но он не имеет текстового содержимого, возвращаемое значение будет пустой строкой.
  • Если никакие элементы не соответствуют указанному пути, метод возвращает None или значение аргумента ключевого слова default =, если вы его предоставили.

Вот пример.

>>> from lxml import etree
>>> node=etree.fromstring('<a><b>bum</b><b>ear</b><c/></a>')
>>> node.findtext('b')
'bum'
>>> node.findtext('c')
''
>>> node.findtext('c', default='Huh?')
''
>>> print node.findtext('x')
None
>>> node.findtext('x', default='Huh?')
'Huh?'

Необязательный аргумент ключевого слова namespaces позволяет указать префиксы пространства имен для документов с несколькими именами; для получения дополнительной информации см. раздел 9.5. Element.find(): найдите соответствующий подэлемент.

9.8. Element.get(): получить значение атрибута с дефолтным значением

Существует два способа получить значение атрибута из экземпляра элемента. См. Также словарь .attrib в разделе 9.1. Атрибуты экземпляра элемента.

Метод .get() в экземпляре ``Element` также пытается получить значение атрибута. Преимущество этого метода заключается в том, что вы можете предоставить значение по умолчанию, которое возвращается, если данный элемент фактически не имеет атрибута по данному имени.

Вот общий вид, для некоторого экземпляра Element.

E.get(key, default=None)

Аргументом key является имя атрибута, значение которого вы хотите получить.

  • Если E имеет атрибут по этому имени, метод возвращает значение этого атрибута в виде строки.
  • Если E не имеет такого атрибута, метод возвращает аргумент по умолчанию, который сам по умолчанию имеет значение None.

Вот пример:

>>> from lxml import etree
>>> node = etree.fromstring('<mount species="Jackalope"/>')
>>> print node.get('species')
Jackalope
>>> print node.get('source')
None
>>> print node.get('source', 'Unknown')
Unknown
>>>

9.9. Element.getchildren(): получить дочерние элементы

Для экземпляра E Element этот метод возвращает список всех дочерних элементов E:

E.getchildren()

Вот пример:

>>> xml = '''<corral><horse n="2"/><cow n="17"/>
...  <cowboy n="2"/></corral>'''
>>> pen = etree.fromstring(xml)
>>> penContents = pen.getchildren()
>>> for  content in penContents:
...     print "%-10s %3s" % (content.tag, content.get("n", "0"))
...
horse        2
cow         17
cowboy       2
>>>

9.10. Element.getiterator(): Сделать итератор для перемещения поддерева

Иногда вы хотите пройти через весь или часть документа, глядя на все элементы в порядке документа. Аналогичным образом, вы можете пройти через весь или часть документа и искать все вхождения определенного типа элемента.

Метод .getiterator() на экземпляре Element создает итератор Python, который сообщает Python, как посещать элементы этими способами. Вот общий вид для экземпляра Element E:

E.getiterator(tag=None)
  • Если вы опустите аргумент, вы получите итератор, который сначала посещает E, а затем все его дочерние элементы и их дочерние элементы в обходе порядка в этом поддереве.
  • Если вы хотите посетить только элементы с определенным именем тега, передайте имя тега в качестве аргумента.

Предпросмотр обхода дерева означает, что мы сначала посещаем корень, затем поддеревья слева направо (то есть в порядке документа). Это также называется переходом по глубине: мы посещаем корень, потом его первый ребенок, потом первый его первый ребенок и т. Д., Пока не закончим потомков. Затем мы возвращаемся к последнему элементу с большим количеством детей и повторяем.

Вот пример, показывающий обход всего дерева. Во-первых, диаграмма, показывающая древовидную структуру:

Альтернативный текст


Обход этого дерева происходит в следующем порядке: a, b, c, d, e.

>>> xml = '''<a><b><c/><d/></b><e/></a>'''
>>> tree = etree.fromstring(xml)
>>> walkAll = tree.getiterator()
>>> for  elt in walkAll:
...     print elt.tag,
...
a b c d e
>>>

В этом примере мы посещаем только bird узлы .

>>> xml = '''<bio>
...   <bird type="Bushtit"/>
...   <butterfly type="Mourning Cloak"/>
...   <bird type="Mew Gull"/>
...   <group site="Water Canyon">
...     <snake type="Sidewinder"/>
...     <bird type="Verdin"/>
...   </group>
...   <bird type="Pygmy Nuthatch"/>
... </bio>'''
>>> root = etree.fromstring(xml)
>>> for  elt in root.getiterator('bird'):
...     print elt.get('type', 'Unknown')
...
Bushtit
Mew Gull
Verdin
Pygmy Nuthatch
>>>

В приведенном выше примере обратите внимание, что итератор посещает элемент Verdin, даже если он не является прямым дочерним элементом корневого элемента.

9.11. Element.getroottree(): найдите элемент ElementTree, содержащий этот элемент

Для любого экземпляра E тот вызов метода возвращает экземпляр ElementTree, который содержит E:

E.getroottree()

9.12. Element.insert(): вставить новый дочерний элемент

Используйте метод .insert() в экземпляре E элемента, чтобы добавить новый дочерний элемент elt в произвольное положение. (Чтобы добавить новый дочерний элемент элемента в последнюю позицию, см. Раздел 9.3, «Element.append (): Добавить новый дочерний элемент».)

E.insert(index, elt)

Аргумент index указывает позицию, в которую вставлен элемент elt. Например, если вы укажете индекс 0, новый ребенок будет вставлен перед любыми другими дочерними элементами E.

Модуль lxml вполне разрешителен относительно значений аргумента index: если он отрицательный или больше, чем позиция последнего существующего дочернего элемента, новый ребенок добавляется после всех существующих дочерних элементов.

Вот пример, показывающий вставки в позициях 0 и 2.

>>> node = etree.fromstring('<a><c0/><c1/><c2/></a>')
>>> newKid = etree.Element('c-1', laugh="Hi!")
>>> node.insert(0, newKid)
>>> etree.tostring(node)
'<a><c-1 laugh="Hi!"/><c0/><c1/><c2/></a>'
>>> newerKid = etree.Element('cn')
>>> node.insert(2, newerKid)
>>> etree.tostring(node)
'<a><c-1 laugh="Hi!"/><c0/><cn/><c1/><c2/></a>'
>>>

9.13. Element.items(): создавать имена атрибутов и значения

Для любого экземпляра E метод .items() возвращает атрибуты, как если бы они были словарем, и вы вызывали метод .items() в этом словаре: результатом является список двухэлементных кортежей (имя, значение), по одному для каждого атрибута XML E.

Значения атрибутов возвращаются в определенном порядке.

Вот пример.

>>> node = etree.fromstring("<event time='1830' cost='3.50' rating='nc-03'/>")
>>> node.items()
[('cost', '3.50'), ('time', '1830'), ('rating', 'nc-03')]
>>>

9.14. Element.iterancestors(): Найдите предков элемента

Предками элемента являются его родительский элемент, родительский родительский элемент и т. Д. Вплоть до корневого элемента дерева. Для любого экземпляра E элемента этот метод возвращает итератор, который посещает предков E в обратном порядке документа:

E.iterancestors(tag=None)

Если вы опустите аргумент, итератор посетит всех предков. Если вы хотите посетить только предков с определенным именем тега, передайте это имя тега в качестве аргумента.

Примеры:

>>> xml = '''<class sci='Aves' eng='Birds'>
...   <order sci='Strigiformes' eng='Owls'>
...     <family sci='Tytonidae' eng='Barn-Owls'>
...       <genus sci='Tyto'>
...         <species sci='Tyto alba' eng='Barn Owl'/>
...       </genus>
...     </family>
...   </order>
... </class>'''
>>> root = etree.fromstring(xml)
>>> barney = root.xpath('//species') [0]
>>> print "%s: %s" % (barney.get('sci'), barney.get('eng'))
Tyto alba: Barn Owl
>>> for  ancestor in barney.iterancestors():
...     print ancestor.tag,
genus family order class
>>> for  fam in barney.iterancestors('family'):
...    print "%s: %s" % (fam.get('sci'), fam.get('eng'))
Tytonidae: Barn-Owls

9.15. Element.iterchildren(): Найти всех детей

Для экземпляра E Element этот метод возвращает итератор, который выполняет итерацию по всем дочерним элементам E.

E.iterchildren(reversed=False, tag=None)

Обычно результирующий итератор будет посещать детей в порядке документа. Однако, если вы пройдете обратный = True, он будет посещать их в обратном порядке.

Если вы хотите, чтобы итератор посещал только детей с определенным именем N, передайте аргумент tag = N.

Пример:

>>> root=et.fromstring("<mom><aaron/><betty/><clarence/><dana/></mom>")
>>> for kid in root.getchildren():
...     print kid.tag
aaron
betty
clarence
dana
>>> for kid in root.iterchildren(reversed=True):
...     print kid.tag
...
dana
clarence
betty
aaron
>>>

9.16. Element.iterdescendants(): Найти всех потомков

Термин descendants относится к детям элемента, их детям и т. Д. Вплоть до листьев дерева документов.

Для экземпляра E этот метод возвращает итератор, который посещает всех потомков E в порядке документа.

E.iterdescendants(tag=None)

Если вы хотите, чтобы итератор посещал только элементы с определенным именем тега N, передайте аргумент tag = N.

Пример:

>>> xml = '''<root>
...   <grandpa>
...     <dad>
...       <yuo/>
...     </dad>
...   </grandpa>
... </root>'''
>>> root = etree.fromstring(xml)
>>> you = root.xpath('.//yuo')[0]
>>> for  anc in you.iterancestors():
...    print anc.tag,
dad grandpa root
>>>

9.17. Element.itersiblings(): Найти других детей одного и того же родителя

Для любого экземпляра E этот метод возвращает итератор, который посещает всех родных братьев E, то есть дочерние элементы его родителя, в порядке документа, но не указывая E.

E.itersiblings(preceding=False)

Если preceding аргумент имеет значение false, итератор будет посещать братьев и сестер, следующих за E в порядке документа. Если preceding=True, итератор посетит братьев и сестер, которые предшествуют E в порядке документа.

Пример:

>>> root=etree.fromstring(
...   "<mom><aaron/><betty/><clarence/><dana/></mom>")
>>> betty=root.find('betty')
>>> for sib in betty.itersiblings(preceding=True):
...     print sib.tag
...
aaron
>>> for sib in betty.itersiblings():
...     print sib.tag
...
clarence
dana
>>>

9.18. Element.keys(): найти все имена атрибутов

Для любого экземпляра E этот метод возвращает список имен атрибутов XML элемента, в определенном порядке.

E.keys()

Вот пример:

>>> node = etree.fromstring("<event time='1830' cost='3.50' rating='nc-03'/>")
>>> node.keys()
['time', 'rating', 'cost']
>>>

9.19. Element.remove(): удалить дочерний элемент

Чтобы удалить дочерний элемент C из экземпляра E, используйте этот вызов метода.

E.remove(C)

Если C не является дочерним элементом E, этот метод вызывает исключение ValueError.

9.20. Element.set(): установить значение атрибута

Чтобы создать или изменить атрибут с именем A для значения V в экземпляре E элемента, используйте этот метод:

E.set(A, V)

Вот пример.

>>> node = etree.Element('div', id='u401')
>>> etree.tostring(node)
'<div id="u401"/>'
>>> node.set('class', 'flyer')
>>> etree.tostring(node)
'<div id="u401" class="flyer"/>'
>>> node.set('class', 'broadside')
>>> etree.tostring(node)
'<div id="u401" class="broadside"/>'
>>>

Этот метод является одним из двух способов создания или изменения значения атрибута. Другой метод - изменять значения в словаре .attrib экземпляра Element.

9.21. Element.xpath(): оценка выражения XPath

Чтобы оценить выражение XPath с использованием некоторого экземпляра Element как контекстного узла:

E.xpath(s[, namespaces=N][, var=value][, ...])

Для общего обсуждения использования XPath см. Раздел 10 «Обработка XPath».

s
Выражение XPath для оценки.
N
Карта пространства имен, которая связывает префиксы пространства имен с NSURI; см. раздел 4.3 «Карты пространства имен». Карта пространства имен используется для интерпретации префиксов пространства имен в выражении XPath.
var=value
Вы можете использовать дополнительные аргументы ключевых слов для определения значений переменных XPath, которые будут использоваться при оценке s. Например, если вы передадите аргумент count = 17, значение переменной $ count в выражении XPath будет равно 17.

Возвращаемое значение может быть любым:

  • Список нулевых или более выбранных экземпляров элементов.
  • Значение bool Python для тестов true/false.
  • Значение float Python для числовых результатов.
  • Строка для строкового результата.

10. Обработка XPath

Одним из наиболее важных преимуществ пакета lxml над другими пакетами в стиле ElementTree является поддержка полного языка XPath. Выражения XPath дают вам гораздо более мощный механизм для выбора и извлечения частей документа по сравнению с относительно простым синтаксисом «path», используемым в разделе 8.1. ElementTree.find().

Если вы не знакомы с XPath, см. Эти источники:

  • XSLT reference, в частности раздел «Ссылка XPath».
  • Стандарт XML Path Language (XPath), Version 1.0.

Имейте в виду, что каждое выражение XPath оценивается с использованием трех элементов контекста:

  • Контекстный узел является отправной точкой для любых операций, значение которых относительно некоторой точки в дереве.
  • Размер контекста - это число элементов, которые являются дочерними элементами родительского узла контекста, то есть узлом контекста и всеми его братьями и сестрами.
  • Позиция контекста - это позиция контекстного узла относительно его братьев и сестер, считая первого брата как позицию 1.

Вы можете оценить выражение XPath с помощью метода .xpath(s) на экземпляре Element или экземпляре ElementTree. См. Раздел 9.21. Element.xpath(): оценка выражения XPath и раздел 8.6. ElementTree.xpath(): оценить выражение XPath.

В зависимости от используемого выражения XPath эти методы .xpath() могут возвращать один из нескольких значений:

  • Для выражений, возвращающих логическое значение, метод .xpath() возвращает True или False.
  • Выражения с числовым результатом всегда возвращают тип float.
  • Выражения со строковым результатом возвращают строку (string) Python или строку unicode.
  • Выражения, которые производят список значений, таких как узлы, возвращают список Python. Элементами этого списка могут быть, в свою очередь, любой из нескольких типов:
    • Элементы, комментарии и инструкции по обработке будут представлены как экземпляры элементов lxml, Comment и ProcessingInstruction.
    • Текстовый контент и значения атрибутов возвращаются в виде экземпляров Python str (string).
    • Объявления пространства имен возвращаются как два кортежа (префикс, namespaceURI).

Для получения дополнительной информации о функциях XPath от lxml см. XML Path Language (XPath).

10.1. Пример XPath

Ниже приведен пример ситуации, когда выражение XPath поможет вам сократить много работы. Предположим, у вас есть документ с элементом, который называется para, который представляет абзац текста. Далее предположим, что ваш para имеет модель смешанного контента, поэтому ее содержимое представляет собой бесплатную смесь текста и несколько видов встроенной разметки. Однако ваше приложение должно извлекать только текст в абзаце, отбрасывая все теги.

Используя только классический интерфейс ElementTree, вам потребуется написать какую-то функцию, которая рекурсивно перемещает элемент para и его поддерево, извлекает атрибуты .text и .tail на каждом уровне и в конечном итоге склеивает их все вместе.

Тем не менее, существует относительно простое выражение XPath, которое делает все это для вас:

descendant-or-self::text()

«Descendant-or-self ::» - это селектор оси, который ограничивает поиск узлом контекста, его дочерними элементами, их дочерними элементами и т. д. к листьям дерева. Функция «text()» выбирает только текстовые узлы, отбрасывая любые элементы, комментарии и другое нетекстовое содержимое. Возвращаемое значение представляет собой список строк.

Вот пример этого выражения на практике.

>>> node=etree.fromstring('''<a>
...   a-text <b>b-text</b> b-tail <c>c-text</c> c-tail
... </a>''')
>>> alltext = node.xpath('descendant-or-self::text()')
>>> alltext
['\n  a-text ', 'b-text', ' b-tail ', 'c-text', ' c-tail\n']
>>> clump = "".join(alltext)
>>> clump
'\n  a-text b-text b-tail c-text c-tail\n'
>>>

11. Искусство Web-scraping: анализ HTML с помощью Beautiful Soup

Веб-скрапинг - это метод для извлечения данных с веб-страниц. Если бы все в World Wide Web использовали действительный XHTML, это было бы легко. Однако в реальном мире подавляющее большинство веб-страниц использует то, что вы можете назвать tag soup - теоретически HTML, но на самом деле часто представляет собой неструктурированную смесь тегов и текста.

К счастью, модуль lxml включает пакет BeautifulSoup, который пытается перевести tag soup в дерево так же, как если бы он исходил с действующей страницы XHTML. Естественно, этот процесс не является совершенным, но есть очень хороший шанс, что полученное дерево будет иметь достаточно предсказуемую структуру, чтобы обеспечить автоматическое извлечение информации в нем.

Импортируйте модуль BeautifulSoup следующим образом:

from lxml.html import soupparser

В этом модуле есть две функции.

soupparser.parse(input)
Аргумент ввода указывает источник HTML веб-страницы как имя файла или файл-подобный объект. Возвращаемое значение представляет собой экземпляр ElementTree, корневым элементом которого является элемент html как экземпляр элемента.
soupparser.fromstring(s)
Аргумент s - это строка, содержащая tag soup. Возвращаемое значение представляет собой дерево узлов, представляющих s. Корневой узел этого дерева всегда будет элементом html как экземпляр элемента.

12. Автоматическая проверка входных файлов

Что происходит с вашим приложением, если вы читаете файл, который не соответствует схеме? Существует два способа обработки ошибок.

Если вы являетесь осторожным и защищенным программистом, вы всегда будете проверять наличие и достоверность каждой части XML-документа, который вы читаете, и выпустить соответствующее сообщение об ошибке. Если вы недостаточно осторожны или достаточно защищены, ваше приложение может потерпеть крах.

Это может сделать ваше приложение намного проще, если вы механически проверяете входной файл на схему, которая определяет документ.

В модуле lxml последний подход является недорогим как в процессе программирования, так и во время выполнения. Вы можете проверить документ, используя любой из этих основных языков схемы:

  • Раздел 12.1, «Проверка с помощью схемы Relax NG».
  • Раздел 12.2, «Проверка с помощью схемы XSchema (XSD)».

12.1. Проверка с помощью схемы Relax NG

Модуль lxml может проверять документ в виде ElementTree на схему, выраженную в нотации Relax NG. Для получения дополнительной информации о Relax NG см. Relax NG Compact Syntax (RNC).

Схема Relax NG может использовать две формы: компактный синтаксис (RNC) или тип документа XML (RNG). Если ваша схема использует RNC, вы должны перевести ее в формат RNG. Утилита trang делает это преобразование для вас. Используйте команду этой формы:

trang file.rnc file.rng

После того, как у вас есть схема, доступная как файл .rng, используйте эти шаги для правильного дерева элементов ET.

  1. Разберите файл .rng в свой собственный ElementTree, как описано в разделе 7.3 «Конструктор ElementTree ()».
  1. Используйте конструктор etree.RelaxNG(S) для преобразования этого дерева в «экземпляр схемы», где S - это экземпляр ElementTree, содержащий схему, с предыдущего шага.

    Если дерево не является допустимой схемой Relax NG, конструктор будет вызывать исключение etree.RelaxNGParseError.

  1. Используйте метод .validate(ET) экземпляра схемы для проверки ET.

    Этот метод возвращает 1, если ET проверяет схему, или 0, если это не так.

    Если метод возвращает 0, экземпляр схемы имеет атрибут с именем .error_log, содержащий все ошибки, обнаруженные экземпляром схемы. Вы можете распечатать .error_log.last_error, чтобы увидеть самую последнюю обнаруженную ошибку.

Ниже в этом документе представлены два примера использования этой методики валидации:

  • Раздел 15, «rnc_validate: модуль для проверки XML по схеме Relax NG».
  • Раздел 16, «rnck: автономный скрипт для проверки XML по схеме Relax NG».

12.2. Проверка с помощью схемы XSchema(XSD)

Чтобы проверить документ на схему, написанную на языке XSchema, выполните шаги, показанные в разделе 12.1 «Проверка с помощью схемы Relax NG», с одним изменением.

Вместо использования etree.RelaxNG() для анализа дерева схемы S используйте команду etree.XMLSchema(S).

13. etbuilder.py: упрощенный модуль построителя XML

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

mainTitle  =  et.Element('h1')
mainTitle.text  =  "Welcome to Your Title Here!"

Блестящий и продуктивный Fredrik Lundh написал очень хороший модуль под названием builder.py, который значительно упрощает создание XML.

  • См. Оригинальную страницу Lundh, ElementTree Builder, для более старой версии его модуля, с документацией и примерами.
  • Вы можете использовать текущую версию builder.py со страницы репозитория SVN Lundh.
  • Автор написал модифицированную версию, основанную в основном на версии Lundh. Источник для этого модуля etbuilder.py доступен в Интернете.

    Инструкции по использованию авторской версии см. В разделе 13.1 «Использование модуля etbuilder».

    Для фактической реализации в форме легкого грамотного программирования см. Раздел 14 «Внедрение etbuilder».

13.1. Использование модуля etbuilder

Вместо импорта пакета ElementTree в качестве et используйте этот импорт:

from etbuilder import et, E

Имя E является фабричным объектом, который создает экземпляры et.Element.

Вот вызывающая последовательность для E:

E(tag, \*p, \**kw)

Первый аргумент, tag, является именем элемента в виде строки. Возвращаемое значение представляет собой новый экземпляр et.Element.

Вы можете предоставить любое количество позиционных аргументов p, за которыми следует любое количество аргументов ключевого слова. Интерпретация каждого аргумента зависит от его типа. Экран с подсказками «>>>» - это интерактивные примеры.

  • Любой аргумент ключевого слова формы «name = value» становится атрибутом XML «name = ‘value’» нового элемента.
>>> colElt=E('col', valign='top', align='left')
>>> et.tostring(colElt)
'<col align="left" valign="top" />'
  • Строковые аргументы добавляются в содержимое тега.
>>> p14 = E("p", "Welcome to ", "Your Paragraph Here.")
>>> et.tostring(p14)
'<p>Welcome to Your Paragraph Here.</p>'
  • Аргумент типа int преобразуется в строку и добавляется в содержимое тега.
  • Если вы передадите словарь в функцию, его члены также станут атрибутами XML. Например, вы можете создать элемент ячейки таблицы XHTML следующим образом:

    >>> cell = E('td', {'valign': 'top', 'align': 'right'}, 14)
    >>> et.tostring(cell)
    '<td align="right" valign="top">14</td>'
    
  • You can pass in an et.Element instance, and it becomes a child element of the element being built. This allows you to nest calls within calls, like this:

  • Вы можете передать экземпляр et.Element и стать дочерним элементом создаваемого элемента. Это позволяет вам встраивать вызовы в вызовы, например:

    >>> head = E('head',
    ...    E('title', 'Your Page Title Here'),
    ...    E('link', rel='stylesheet', href='/tcc/style.css'))
    >>> print et.tostring(head, pretty_print=True)
    <head>
    <title>Your Page Title Here</title>
    <link href="/tcc/style.css" rel="stylesheet" />
    </head>
    

У этого модуля есть еще одна приятная морщина. Если имя создаваемого тега также является допустимым именем Python, вы можете использовать это имя как имя вызова метода в экземпляре E. То есть,

E.name(...)

функционально эквивалентно

E("name", ...)

Вот пример:

>>> head = E.head(
...    E.title('Your title'),
...    E.link(rel='stylesheet', href='/tcc/style.css'))
>>> print et.tostring(head, pretty_print=True)
<head>
  <title>Your title</title>
  <link href="/tcc/style.css" rel="stylesheet" />
</head>

13.2. CLASS (): добавление атрибутов класса

Одной из наиболее распространенных операций является привязка атрибута класса к тегу XML. Например, предположим, что вы хотите создать этот контент:

<div class='warning'>
  Your brain may not be the boss!
</div>

Очевидный способ сделать это не работает:

E.div("Your brain may not be the boss!", class='warning')  # Fails!

Поскольку класс является зарезервированным словом в Python, вы не можете использовать его как ключевое слово аргумента. Поэтому пакет включает вспомогательную функцию с именем CLASS(), которая принимает одно или несколько имен в качестве аргументов и возвращает словарь, который может быть передан конструктору E(), чтобы добавить атрибут class= attribute. Этот пример действительно работает для создания вышеприведенного XML:

E.div("Your brain may not be the boss!", CLASS('warning'))  # Works.

Вот еще один пример, на этот раз с несколькими именами классов.

E.span(CLASS('ref', 'index'), "Pie, whole.")

Это генерирует:

<span class='ref index'>Pie, whole.</span>

13.3. FOR(): добавление для атрибутов

Эта функция аналогична той, которая определена в разделе 13.2, «CLASS (): добавление атрибутов класса»: он используется для присоединения атрибута к элементу, чье имя (for) является зарезервированным словом на Python. Такие атрибуты обычно используются для связывания элемента метки XHTML с элементом формы.

13.4. subElement(): добавление дочернего элемента

Эта функция объединяет две общие операции создания элемента и добавления его в качестве следующего дочернего элемента какого-либо родительского узла. Общая последовательность вызовов:

subNode = subElement(parent, child)

Эта функция добавляет дочерние элементы в качестве следующего дочернего элемента родителя и возвращает дочерний элемент.

13.5. addText(): добавление текстового содержимого в элемент

Эта удобная функция обрабатывает специальную логику, используемую для добавления текстового содержимого в узел стиля ElementTree. Проблема в том, что если узел не имеет дочерних элементов, новый текст добавляется к атрибуту .text, но если есть дети, новый текст должен быть добавлен к атрибуту .tail последнего дочернего элемента. Обратитесь к разделу 2 «Как ElementTree представляет XML» для обсуждения того, почему это необходимо.

Вот общая последовательность вызовов, чтобы добавить некоторую текстовую строку s к существующему узлу:

addText(node, s)

14. Реализация etbuilder

Вот авторский модуль etbuilder.py с описанием.

14.1. Особенности, отличные от оригинала Лунда

Версия автора отличается от версии Лунда в этом отношении:

  • Для этого требуется пакет lxml. Версия Lundh не использовала lxml; он использует cElementTree или elementtree, если первый недоступен.
  • Для этого требуется Python 2.5 или новее. Версия Lundh будет работать с более ранними версиями, возможно, вернется, по крайней мере, к 2.2.
  • Версия автора также допускает значения int в вызове экземпляра E.

14.2. Пролог

Модуль начинается с комментария, указывающего на эту документацию, и подтверждения работы Фредрика Лунда.

"""etbuilder.py: An element builder for lxml.etree
================================================================
    $Revision: 1.55 $  $Date: 2012/08/11 21:44:19 $
================================================================
For documentation, see:
    http://www.nmt.edu/tcc/help/pubs/pylxml/
Borrows heavily from the work of Fredrik Lundh; see:
    http://effbot.org/zone/
"""

et в модуле lxml.etree.

#================================================================
# Imports
#----------------------------------------------------------------

from lxml import etree as et

Функция functools.partial() используется для выполнения вызова функции в разделе 14.11, «ElementMaker .__ getattr __ (): обрабатывать вызовы произвольных методов».

Однако модуль functools является новым в Python 2.5. Чтобы этот модуль работал в установке Python 2.4, мы ожидаем возможного отказа от импорта functools, предоставляя эту функциональность с помощью функции partial() substitute. Эта функция украдена непосредственно из справочника библиотеки Python.

try:
        from functools import partial
except ImportError:
        def partial(func, *args, **keywords):
        def newfunc(*fargs, **fkeywords):
                newkeywords = keywords.copy()
                newkeywords.update(fkeywords)
                return func(*(args + fargs), **newkeywords)
        newfunc.func  =  func
        newfunc.args  =  args
        newfunc.keywords  =  keywords
        return newfunc

14.3. CLASS(): вспомогательная функция для добавления атрибутов класса CSS

Далее идет определение вспомогательной функции CLASS(), обсуждаемой в разделе 13.2, «CLASS (): добавление атрибутов класса».

# - - -   C L A S S

def CLASS(*names):
'''Helper function for adding 'class=...' attributes to tags.

[ names is a list of strings ->
        return a dictionary with one key 'class' and the related
        value the concatenation of (names) with one space between
        them ]
'''
return {'class': ' '.join(names)}

14.4. FOR(): вспомогательная функция для добавления XHTML для атрибутов

# - - -   F O R

def FOR(id):
'''Helper function for adding 'for=ID' attributes to tags.
'''
return {'for': id}

14.5. subElement(): добавить дочерний элемент

См. Раздел 13.4. subElement(): добавление дочернего элемента

    # - - -   s u b E l e m e n t

def subElement(parent, child):

    '''Add a child node to the parent and return the child.

[ (parent is an Element) and
(child is an Element with no parent) ->
parent  :=  parent with child added as its new last child
return child ]
'''
#-- 1 --
parent.append(child)

#-- 2 --
return child

14.6. addText(): добавление текстового содержимого в элемент

См. Раздел 13.5. addText(): добавление текстового содержимого в элемент. Чтобы упростить работу вызывающего, мы ничего не делаем, если s равно None, как это может быть в случае с атрибутом .text или .tail для элемента et.Element.

# - - -   a d d T e x t

def addText(node, s):
    '''Add text content to an element.

    [ (node is an Element) and (s is a string) ->
    if node has any children ->
        last child's .tail  +:=  s
    else ->
        node.text +:= s ]
    '''
    #-- 1 --
    if not s:
        return

    #-- 2 --
    if len(node) == 0:
        node.text = (node.text or "") + s
    else:
        lastChild = node[-1]
        lastChild.tail = (lastChild.tail or "") + s

14.7. class ElementMaker: The factory class

# - - - - -   c l a s s   E l e m e n t M a k e r

class ElementMaker(object):
'''ElementTree element factory class

Exports:
    ElementMaker(typeMap=None):
        [ (typeMap is an optional dictionary whose keys are
        type objects T, and each corresponding value is a
        function with calling sequence
        f(elt, item)
        and generic intended function
        [ (elt is an et.Element) and
        (item has type T) ->
        elt  :=  elt with item added ]) ->
        return a new ElementMaker instance that has
        calling sequence
        E(*p, **kw)
        and intended function
        [ p[0] exists and is a str ->
                return a new et.Element instance whose name
                is p[0], and remaining elements of p become
                string content of that element (for types
                str, unicode, and int) or attributes (for
                type dict, and members of kw) or children
                (for type et.Element), plus additional
                handling from typeMap if it is provided ]
          and allows arbitrary method calls of the form
            E.tag(*p, **kw)
          with intended function
            [ return a new et.Element instance whose name
              is (tag), and elements of p and kw have
              the same effects as E(*(p[1:]), **kw) ]
'''