Разбить XML файл на объект Python
У меня есть XML файл, который выглядит следующим образом:
<encspot>
<file>
<Name>some filename.mp3</Name>
<Encoder>Gogo (after 3.0)</Encoder>
<Bitrate>131</Bitrate>
<Mode>joint stereo</Mode>
<Length>00:02:43</Length>
<Size>5,236,644</Size>
<Frame>no</Frame>
<Quality>good</Quality>
<Freq.>44100</Freq.>
<Frames>6255</Frames>
..... and so forth ......
</file>
<file>....</file>
</encspot>
Я хочу прочитать его в объекте python, что-то вроде списка словарей. Поскольку разметка абсолютно фиксирована, у меня возникает соблазн использовать регулярное выражение (я неплохо использую их). Тем не менее, я думал, что проверю, знает ли кто, как легко избежать регулярных выражений здесь. Однако у меня нет большого опыта работы с SAX или другим синтаксическим разбором, но я хочу учиться.
Я с нетерпением жду, чтобы показать, как это делается быстро, без регулярных выражений в Python. Спасибо за вашу помощь!
Ответы
Ответ 1
Моя любимая шляпа SD Chargers для вас, если вы думаете, что регулярное выражение проще, чем это:
#!/usr/bin/env python
import xml.etree.cElementTree as et
sxml="""
<encspot>
<file>
<Name>some filename.mp3</Name>
<Encoder>Gogo (after 3.0)</Encoder>
<Bitrate>131</Bitrate>
</file>
<file>
<Name>another filename.mp3</Name>
<Encoder>iTunes</Encoder>
<Bitrate>128</Bitrate>
</file>
</encspot>
"""
tree=et.fromstring(sxml)
for el in tree.findall('file'):
print '-------------------'
for ch in el.getchildren():
print '{:>15}: {:<30}'.format(ch.tag, ch.text)
print "\nan alternate way:"
el=tree.find('file[2]/Name') # xpath
print '{:>15}: {:<30}'.format(el.tag, el.text)
Вывод:
-------------------
Name: some filename.mp3
Encoder: Gogo (after 3.0)
Bitrate: 131
-------------------
Name: another filename.mp3
Encoder: iTunes
Bitrate: 128
an alternate way:
Name: another filename.mp3
Если ваше притяжение к регулярному выражению является кратким, вот и непостижимый бит понимания списка для создания структуры данных:
[(ch.tag,ch.text) for e in tree.findall('file') for ch in e.getchildren()]
Который создает список кортежей для детей XML <file>
в порядке документа:
[('Name', 'some filename.mp3'),
('Encoder', 'Gogo (after 3.0)'),
('Bitrate', '131'),
('Name', 'another filename.mp3'),
('Encoder', 'iTunes'),
('Bitrate', '128')]
С еще несколькими строками и немного более продуманным, очевидно, вы можете создать любую структуру данных, которую хотите от XML, с помощью ElementTree. Это часть дистрибутива Python.
Edit
Код для гольфа включен!
[{item.tag: item.text for item in ch} for ch in tree.findall('file')]
[ {'Bitrate': '131',
'Name': 'some filename.mp3',
'Encoder': 'Gogo (after 3.0)'},
{'Bitrate': '128',
'Name': 'another filename.mp3',
'Encoder': 'iTunes'}]
Если ваш XML имеет только раздел file
, вы можете выбрать свой гольф. Если ваш XML имеет другие теги, другие разделы, вам нужно учесть раздел, в котором находятся дети, и вам нужно будет использовать findall
В ElementTree есть учебник по Effbot.org
Ответ 2
Используйте ElementTree. Вам не нужно/нужно гадать с помощью только синтаксического анализа, такого как pyexpat
... вы только в конечном итоге будете повторно изобретать ElementTree частично и плохо.
Другая возможность - lxml, которая является сторонним пакетом, который реализует интерфейс ElementTree плюс больше.
Обновление Кто-то начал играть в кодекс-гольф; здесь моя запись, которая фактически создает структуру данных, которую вы просили:
# xs = """<encspot> etc etc </encspot"""
>>> import xml.etree.cElementTree as et
>>> from pprint import pprint as pp
>>> pp([dict((attr.tag, attr.text) for attr in el) for el in et.fromstring(xs)])
[{'Bitrate': '131',
'Encoder': 'Gogo (after 3.0)',
'Frame': 'no',
'Frames': '6255',
'Freq.': '44100',
'Length': '00:02:43',
'Mode': 'joint stereo',
'Name': 'some filename.mp3',
'Quality': 'good',
'Size': '5,236,644'},
{'Bitrate': '0', 'Name': 'foo.mp3'}]
>>>
Вероятно, вы захотите присвоить именам атрибутов "атрибут" для преобразования:
converters = {
'Frames': int,
'Size': lambda x: int(x.replace(',', '')),
# etc
}
Ответ 3
Я также искал простой способ преобразования данных между документами XML и структурами данных Python, что-то похожее на библиотеку Golang XML, которая позволяет декларативно указывать, как сопоставлять структуры данных с XML.
Мне не удалось найти такую библиотеку для Python, поэтому я написал ее для удовлетворения моей потребности declxml для декларативной обработки XML.
С помощью declxml вы создаете процессоры, которые декларативно определяют структуру вашего XML-документа. Процессоры используются для синтаксического анализа и сериализации, а также для базового уровня проверки.
Разбор данных XML в список словарей с помощью declxml прост.
import declxml as xml
xml_string = """
<encspot>
<file>
<Name>some filename.mp3</Name>
<Encoder>Gogo (after 3.0)</Encoder>
<Bitrate>131</Bitrate>
</file>
<file>
<Name>another filename.mp3</Name>
<Encoder>iTunes</Encoder>
<Bitrate>128</Bitrate>
</file>
</encspot>
"""
processor = xml.dictionary('encspot', [
xml.array(xml.dictionary('file', [
xml.string('Name'),
xml.string('Encoder'),
xml.integer('Bitrate')
]), alias='files')
])
xml.parse_from_string(processor, xml_string)
Что дает следующий результат
{'files': [
{'Bitrate': 131, 'Encoder': 'Gogo (after 3.0)', 'Name': 'some filename.mp3'},
{'Bitrate': 128, 'Encoder': 'iTunes', 'Name': 'another filename.mp3'}
]}
Хотите проанализировать данные в объектах вместо словарей? Вы также можете сделать это
import declxml as xml
class AudioFile:
def __init__(self):
self.name = None
self.encoder = None
self.bit_rate = None
def __repr__(self):
return 'AudioFile(name={}, encoder={}, bit_rate={})'.format(
self.name, self.encoder, self.bit_rate)
processor = xml.array(xml.user_object('file', AudioFile, [
xml.string('Name', alias='name'),
xml.string('Encoder', alias='encoder'),
xml.integer('Bitrate', alias='bit_rate')
]), nested='encspot')
xml.parse_from_string(processor, xml_string)
Что производит вывод
[AudioFile(name=some filename.mp3, encoder=Gogo (after 3.0), bit_rate=131),
AudioFile(name=another filename.mp3, encoder=iTunes, bit_rate=128)]
Ответ 4
Если у вас есть статическая функция, которая преобразует XML в Object, это будет что-то вроде этого
@classmethod
def from_xml(self,xml_str):
#create XML Element
root = ET.fromstring(xml_str)
# create a dict from it
d = {ch.tag: ch.text for ch in root.getchildren()}
# return the object, created with **kwargs called from the Class, that why its classmethod
return self(**d)
Ответ 5
Этот код от моего учителя Github. Он преобразует строку XML в объект Python. Преимущество этого подхода в том, что он работает с любым XML.
Реализуйте логику:
def xml2py(node):
"""
convert xml to python object
node: xml.etree.ElementTree object
"""
name = node.tag
pytype = type(name, (object, ), {})
pyobj = pytype()
for attr in node.attrib.keys():
setattr(pyobj, attr, node.get(attr))
if node.text and node.text != '' and node.text != ' ' and node.text != '\n':
setattr(pyobj, 'text', node.text)
for cn in node:
if not hasattr(pyobj, cn.tag):
setattr(pyobj, cn.tag, [])
getattr(pyobj, cn.tag).append(xml2py(cn))
return pyobj
Определить данные:
xml_str = '''<?xml version="1.0" encoding="UTF-8"?>
<menza>
<date day="Monday">
<meal name="Potato flat cakes">
<ingredient name="potatoes"/>
<ingredient name="flour"/>
<ingredient name="eggs"/>
</meal>
<meal name="Pancakes">
<ingredient name="milk"/>
<ingredient name="flour"/>
<ingredient name="eggs"/>
</meal>
</date>
</menza>'''
Загрузить объект из XML:
import xml.etree.ElementTree as ET
menza_xml_tree = ET.fromstring(xml_str)
obj = xml2py(menza_xml_tree)
Тестовое задание:
for date in obj.date:
print(date.day)
for meal in date.meal:
print('\t', meal.name)
for ingredient in meal.ingredient:
print('\t\t', ingredient.name)