【问题标题】:Parse XML file into Python object将 XML 文件解析为 Python 对象
【发布时间】:2011-07-28 16:49:39
【问题描述】:

我有一个如下所示的 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 中的正则表达式的情况下快速完成此操作。感谢您的帮助!

【问题讨论】:

  • 您可以查看lxml.de/objectify.html 或freenet.org.nz/python/xmlobject 之类的东西。XML 解析器有很多变体 - 请使用 PyPI。并且:忘记使用正则表达式编写解析器 - 只有不知道更好的傻瓜才会这样做。
  • stackoverflow.com/questions/1732348/… 我知道您在这里谈论的是 XML,但是 XML“不是常规语言,因此不能被正则表达式解析”的关键信息仍然存在。像回答者建议的那样使用 objectify 或 cElementTree。
  • 谢谢! lxml objectify 乍一看似乎有点困难,但我最终使用 xmlobject 得到了相当不错的结果(只是我必须手动将源代码更改为 3.x 语法)......但是我会测试 @ 987654328@建议如果我有时间然后决定我要接受哪个答案
  • 实际上,我给定的 XML 子集确实是常规的。我只建议使用正则表达式,因为它是固定的。

标签: python xml regex xml-parsing


【解决方案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()]

这会按文档顺序创建&lt;file&gt; 的 XML 子项的元组列表:

[('Name', 'some filename.mp3'), 
 ('Encoder', 'Gogo (after 3.0)'), 
 ('Bitrate', '131'), 
 ('Name', 'another filename.mp3'), 
 ('Encoder', 'iTunes'), 
 ('Bitrate', '128')]

显然,只要多几行和多一点思考,您就可以使用 ElementTree 从 XML 创建任何您想要的数据结构。它是 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

在Effbot.org有一个关于ElementTree的教程

【讨论】:

【解决方案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
    }

【讨论】:

  • 您的“编码高尔夫”字典会转换&lt;encspot&gt; 的所有孩子,对吗?如果有意义,您也可以取消项目订单
  • @drewk:(1)根据 OP,“encspot”的唯一子元素是“file”元素——如果不是,则在适当的位置插入 if el.tag == "file"。 (2) 丢失物品订单? OP想要一本字典。看起来每种物品都有 0 或 1 个,顺序无关紧要。
  • @drewk:如果您的意思是 converters 字典,那么是的,您将转换所有内容,包括字符串——例如x.strip()
  • 有时文档顺序在 XML 中是相关的。用户只需要知道他的数据和我猜的 Python 成语。感谢您的回复...
【解决方案3】:

我也一直在寻找一种在 XML 文档和 Python 数据结构之间转换数据的简单方法,类似于Golang's XML library,它允许您以声明方式指定如何从数据结构映射到 XML。

我无法为 Python 找到这样的库,因此我编写了一个名为 declxml 的用于声明式 XML 处理的库以满足我的需要。

使用 declxml,您可以创建 处理器,它以声明方式定义 XML 文档的结构。处理器用于执行解析和序列化以及基本级别​​的验证。

使用 declxml 将该 XML 数据解析为字典列表非常简单

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】:

    此代码来自my teacher's 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)
    

    【讨论】:

      【解决方案5】:

      如果你有一个将 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's why its classmethod
          return self(**d)
      

      【讨论】:

        猜你喜欢
        • 2015-09-12
        • 2014-07-30
        • 2012-07-14
        • 1970-01-01
        • 2021-05-16
        • 2016-12-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多