Рубрики
Без рубрики

Чтение и запись XML-файлов на Python

Автор оригинала: Scott Robinson.

XML, или расширяемый язык разметки,-это язык разметки, который обычно используется для структурирования, хранения и передачи данных между системами. Хотя он и не так распространен, как раньше, он все еще используется в таких сервисах, как RSS и SOAP, а также для структурирования файлов, таких как документы Microsoft Office.

Поскольку Python является популярным языком для Интернета и анализа данных, вполне вероятно, что в какой-то момент вам придется читать или записывать XML-данные, и в этом случае вам повезет.

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

Модули XML

minidom , или Минимальная реализация DOM, представляет собой упрощенную реализацию объектной модели документа (DOM). DOM – это интерфейс прикладного программирования, который рассматривает XML как древовидную структуру, где каждый узел в дереве является объектом. Таким образом, использование этого модуля требует, чтобы мы были знакомы с его функциональными возможностями.

Модуль ElementTree предоставляет более “питонический” интерфейс для обработки XML и является хорошим вариантом для тех, кто не знаком с DOM. Это также, вероятно, лучший кандидат для использования более начинающими программистами из-за его простого интерфейса, который вы увидите на протяжении всей этой статьи.

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

Пример XML-файла

В приведенных ниже примерах мы будем использовать следующий XML-файл, который мы сохраним как “items.xml”:


    
        item1abc
        item2abc
    

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

Чтение XML-документов

Использование minidom

Чтобы разобрать XML-документ с помощью minidom , мы должны сначала импортировать его из модуля xml.dom . Этот модуль использует функцию parse для создания DOM-объекта из нашего XML-файла. Функция parse имеет следующий синтаксис:

xml.dom.minidom.parse(filename_or_file[, parser[, bufsize]])

Здесь имя файла может быть строкой, содержащей путь к файлу, или объектом типа файла. Функция возвращает документ, который может быть обработан как тип XML. Таким образом, мы можем использовать функцию getelementsbytagname() для поиска определенного тега.

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

from xml.dom import minidom

# parse an xml file by name
mydoc = minidom.parse('items.xml')

items = mydoc.getElementsByTagName('item')

# one specific item attribute
print('Item #2 attribute:')
print(items[1].attributes['name'].value)

# all item attributes
print('\nAll attributes:')
for elem in items:
    print(elem.attributes['name'].value)

# one specific item's data
print('\nItem #2 data:')
print(items[1].firstChild.data)
print(items[1].childNodes[0].data)

# all items data
print('\nAll item data:')
for elem in items:
    print(elem.firstChild.data)

В результате получается следующее:

$ python minidomparser.py 
Item #2 attribute:
item2

All attributes:
item1
item2

Item #2 data:
item2abc
item2abc

All item data:
item1abc
item2abc

Рисунок 1

Если мы хотим использовать уже открытый файл, то можем просто передать наш файловый объект в parse вот так:

datasource = open('items.xml')

# parse an open file
mydoc = parse(datasource)

Кроме того, если XML-данные уже были загружены в виде строки, то вместо этого мы могли бы использовать функцию parseString () .

Использование ElementTree

ElementTree представляет нам очень простой способ обработки XML-файлов. Как всегда, чтобы использовать его, мы должны сначала импортировать модуль. В нашем коде мы используем команду import с ключевым словом as , что позволяет нам использовать упрощенное имя (в данном случае ET ) для модуля в коде.

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

Используя ElementTree , как и в предыдущем примере кода, мы получаем атрибуты узла и текст, используя объекты, связанные с каждым узлом.

Код выглядит следующим образом:

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()

# one specific item attribute
print('Item #2 attribute:')
print(root[0][1].attrib)

# all item attributes
print('\nAll attributes:')
for elem in root:
    for subelem in elem:
        print(subelem.attrib)

# one specific item's data
print('\nItem #2 data:')
print(root[0][1].text)

# all items data
print('\nAll item data:')
for elem in root:
    for subelem in elem:
        print(subelem.text)

Результат будет следующим:

$ python treeparser.py 
Item #2 attribute:
item2

All attributes:
item1
item2

Item #2 data:
item2abc

All item data:
item1abc
item2abc

Рисунок 2

Как видите, это очень похоже на пример minidom . Одно из главных отличий заключается в том, что объект attrib является просто объектом словаря, что делает его немного более совместимым с другим кодом Python. Нам также не нужно использовать value для доступа к значению атрибута элемента, как мы делали это раньше.

Возможно, вы заметили, что доступ к объектам и атрибутам с помощью ElementTree немного более питонен, как мы уже упоминали ранее. Это происходит потому, что XML-данные анализируются как простые списки и словари, в отличие от minidom , где элементы анализируются как пользовательские xml.dom.minidom.Attr и “Текстовые узлы DOM”.

Подсчет элементов XML-документа

Использование minidom

Как и в предыдущем случае, файл minidom должен быть импортирован из модуля dom . Этот модуль предоставляет функцию getElementsByTagName , которую мы будем использовать для поиска элемента тега. После получения мы используем встроенный метод len() для получения количества подэлементов, подключенных к узлу. Результат, полученный из приведенного ниже кода, показан на рисунке 3 .

from xml.dom import minidom

# parse an xml file by name
mydoc = minidom.parse('items.xml')

items = mydoc.getElementsByTagName('item')

# total amount of items
print(len(items))
$ python counterxmldom.py
2

Рисунок 3

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

Использование ElementTree

Аналогично, модуль ElementTree позволяет вычислить количество узлов, подключенных к узлу.

Пример кода:

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()

# total amount of items
print(len(root[0]))

В результате получается следующее:

$ python counterxml.py
2

Рисунок 4

Написание XML-документов

Использование ElementTree

ElementTree также отлично подходит для записи данных в XML-файлы. В приведенном ниже коде показано, как создать XML-файл с той же структурой, что и файл, который мы использовали в предыдущих примерах.

Шаги таковы:

  1. Создайте элемент, который будет действовать как наш корневой элемент. В нашем случае тег для этого элемента – “data”.
  2. Как только у нас есть корневой элемент, мы можем создавать подэлементы с помощью функции SubElement . Эта функция имеет следующий синтаксис:

ПодЭлемент(parent, tag, attrib={}, **extra)

Здесь parent – это родительский узел для подключения, attrib – словарь, содержащий атрибуты элемента, а extra – дополнительные аргументы ключевого слова. Эта функция возвращает нам элемент, который можно использовать для присоединения других подэлементов, как мы делаем в следующих строках, передавая элементы в конструктор SubElement . 3. Хотя мы можем добавить ваши атрибуты с помощью функции SubElement , мы также можем использовать функцию set () , как мы это делаем в следующем коде. Текст элемента создается с помощью свойства text объекта Element . 4. В последних 3 строках приведенного ниже кода мы создаем строку из XML-дерева и записываем эти данные в открытый файл.

Пример кода:

import xml.etree.ElementTree as ET

# create the file structure
data = ET.Element('data')
items = ET.SubElement(data, 'items')
item1 = ET.SubElement(items, 'item')
item2 = ET.SubElement(items, 'item')
item1.set('name','item1')
item2.set('name','item2')
item1.text = 'item1abc'
item2.text = 'item2abc'

# create a new XML file with the results
mydata = ET.tostring(data)
myfile = open("items2.xml", "w")
myfile.write(mydata)

Выполнение этого кода приведет к созданию нового файла”. items2.xml”, который должен быть эквивалентен оригиналу “items.xml – файл, по крайней мере, с точки зрения структуры данных XML. Вы, вероятно, заметите, что результирующая строка представляет собой только одну строку и не содержит отступов.

Поиск XML-элементов

Использование ElementTree

Модуль ElementTree предлагает функцию findall () , которая помогает нам находить определенные элементы в дереве. Он возвращает все элементы с заданным условием. Кроме того, модуль имеет функцию find() , которая возвращает только первый подэлемент, соответствующий заданным критериям. Синтаксис для обеих этих функций выглядит следующим образом:

findall(match, namespaces=None)
find(match, namespaces=None)

Для обеих этих функций параметр match может быть именем XML-тега или путем. Функция findall() возвращает список элементов, а find возвращает один объект типа Element .

Кроме того, существует еще одна вспомогательная функция, которая возвращает текст первого узла, соответствующего заданному критерию:

findtext(match, default=None, namespaces=None)

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

import xml.etree.ElementTree as ET
tree = ET.parse('items.xml')
root = tree.getroot()

# find the first 'item' object
for elem in root:
    print(elem.find('item').get('name'))

# find all "item" objects and print their "name" attribute
for elem in root:
    for subelem in elem.findall('item'):
    
        # if we don't need to know the name of the attribute(s), get the dict
        print(subelem.attrib)      
    
        # if we know the name of the attribute, access it directly
        print(subelem.get('name'))

И вот результат выполнения этого кода:

$ python findtree.py 
item1
{'name': 'item1'}
item1
{'name': 'item2'}
item2

Рисунок 5

Изменение XML-элементов

Использование ElementTree

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

Текст узла можно изменить, указав новое значение в текстовом поле объекта узла. Имя атрибута можно переопределить с помощью функции set(name, value) . Функция set не должна просто работать с существующим атрибутом, она также может быть использована для определения нового атрибута.

В приведенном ниже коде показано, как выполнять эти операции:

import xml.etree.ElementTree as ET

tree = ET.parse('items.xml')
root = tree.getroot()

# changing a field text
for elem in root.iter('item'):
    elem.text = 'new text'

# modifying an attribute
for elem in root.iter('item'):
    elem.set('name', 'newitem')

# adding an attribute
for elem in root.iter('item'):
    elem.set('name2', 'newitem2')

tree.write('newitems.xml')

После запуска кода полученный XML-файл “newitems.xml” будет иметь XML-дерево со следующими данными:


    
        new text
        new text
    

Как мы видим при сравнении с исходным XML-файлом, имена элементов item изменились на “new item”, текст-на “new text”, а атрибут “name2” был добавлен в оба узла.

Вы также можете заметить, что запись XML-данных таким образом (вызов three.write с именем файла) добавляет еще некоторое форматирование к XML-дереву, поэтому оно содержит новые строки и отступы.

Создание XML-подэлементов

Использование ElementTree

Модуль ElementTree имеет несколько способов добавления нового элемента. Первый способ, который мы рассмотрим, – это использование функции makeelement () , которая имеет имя узла и словарь с его атрибутами в качестве параметров.

Второй способ-через класс Sub Element () , который принимает в качестве входных данных родительский элемент и словарь атрибутов.

В нашем примере ниже мы покажем оба метода. В первом случае узел не имеет атрибутов, поэтому мы создали пустой словарь ( attrib = {} ). Во втором случае мы используем заполненный словарь для создания атрибутов.

import xml.etree.ElementTree as ET

tree = ET.parse('items.xml')
root = tree.getroot()

# adding an element to the root node
attrib = {}
element = root.makeelement('seconditems', attrib)
root.append(element)

# adding an element to the seconditem node
attrib = {'name2': 'secondname2'}
subelement = root[0][1].makeelement('seconditem', attrib)
ET.SubElement(root[1], 'seconditem', attrib)
root[1][0].text = 'seconditemabc'

# create a new XML file with the new element
tree.write('newitems2.xml')

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


    
        item1abc
        item2abc
    
    
         seconditemabc
    

Как мы видим при сравнении с исходным файлом, элемент “second items” и его подэлемент “second item” были добавлены. Кроме того, узел “второй элемент” имеет в качестве атрибута “имя 2”, а его текст – “seconditemabc”, как и ожидалось.

Удаление XML-элементов

Использование ElementTree

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

Удаление атрибута

В приведенном ниже коде показано, как удалить атрибут узла с помощью функции pop () . Функция применяется к параметру объекта attrib . Он задает имя атрибута и устанавливает его в None .

import xml.etree.ElementTree as ET

tree = ET.parse('items.xml')
root = tree.getroot()

# removing an attribute
root[0][0].attrib.pop('name', None)

# create a new XML file with the results
tree.write('newitems3.xml')

Результатом будет следующий XML-файл:


    
        item1abc
        item2abc
    

Как мы видим в приведенном выше XML-коде, первый элемент не имеет атрибута “name”.

Удаление одного подэлемента

Один конкретный подэлемент может быть удален с помощью функции remove . Эта функция должна указать узел, который мы хотим удалить.

Следующий пример показывает нам, как его использовать:

import xml.etree.ElementTree as ET

tree = ET.parse('items.xml')
root = tree.getroot()

# removing one sub-element
root[0].remove(root[0][0])

# create a new XML file with the results
tree.write('newitems4.xml')

Результатом будет следующий XML-файл:


    
        item2abc
    

Как мы видим из приведенного выше XML-кода, теперь существует только один узел “элемент”. Второй был удален из исходного дерева.

Удаление всех подэлементов

Модуль ElementTree представляет нам функцию clear () , которая может быть использована для удаления всех подэлементов данного элемента.

В приведенном ниже примере показано, как использовать clear() :

import xml.etree.ElementTree as ET

tree = ET.parse('items.xml')
root = tree.getroot()

# removing all sub-elements of an element
root[0].clear()

# create a new XML file with the results
tree.write('newitems5.xml')

Результатом будет следующий XML-файл:


    

Как мы видим в приведенном выше XML-коде, все подэлементы элемента “items” были удалены из дерева.

Обертывание

Python предлагает несколько вариантов обработки XML-файлов. В этой статье мы рассмотрели модуль ElementTree и использовали его для анализа, создания, изменения и удаления XML-файлов. Мы также использовали модель minidom для анализа XML-файлов. Лично я бы рекомендовал использовать модуль ElementTree , так как он гораздо проще в работе и является более современным модулем из двух.