Разобрать файл XML в объект Python - PullRequest
16 голосов
/ 03 апреля 2011

У меня есть 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. Спасибо за вашу помощь!

Ответы [ 5 ]

25 голосов
/ 04 апреля 2011

Моя любимая шапка 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.

Редактировать

Код гольф включен!

[{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

8 голосов
/ 03 апреля 2011

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

Другой возможностью является lxml , который является сторонним пакетом, который реализует интерфейс ElementTree и многое другое.

Обновление Кто-то начал играть в code-golf;вот моя запись, которая фактически создает структуру данных, которую вы запрашивали:

# 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'}]
>>>

Возможно, вы захотите иметь dict, отображающий имена «атрибутов» в функции преобразования:

converters = {
    'Frames': int,
    'Size': lambda x: int(x.replace(',', '')),
    # etc
    }
4 голосов
/ 04 сентября 2017

Я также искал простой способ преобразования данных между документами XML и структурами данных Python, похожий на 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)]
0 голосов
/ 12 июня 2019

Этот код от моего учителя 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)
0 голосов
/ 03 марта 2016

Если у вас есть статическая функция, которая преобразует XML в объект, это будет что-то вроде этого

@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's why its classmethod
    return self(**d)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...