9

Python XML文件格式的解析

 3 years ago
source link: https://www.biaodianfu.com/python-xml.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

术→技巧, 研发

Python XML文件格式的解析

钱魏Way · 2020-11-24 · 0 次浏览

XML 指可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。XML 被设计用来传输和存储数据。

Python 有三种常见的 XML 解析方式:SAX(simple API for XML)、DOM(Document Object Model)、ElementTree。

  • DOM 方式:DOM 中文译为文档对象模型,是 W3C 组织推荐的标准编程接口,它将 XML 数据在内存中解析成一个树,通过对树的操作来操作 XML。
  • SAX 方式:SAX 是一个用于处理 XML 事件驱动的模型,它逐行扫描文档,一边扫描一边解析,对于大型文档的解析拥有巨大优势,尽管不是 W3C 标准,但它却得到了广泛认可。
  • ElementTree 方式:ElementTree 相对于 DOM 来说拥有更好的性能,与 SAX 性能差不多,API 使用也很方便。

Python除了内建的xml解析器外,还要非常多其他的解析工具。哪个工具易用性更好,性能更佳?一起来探索下。

python-xml.jpg

xml.dom.* 模块

xml.dom实现的是W3C制定的DOM API。如果你习惯于使用DOM API,可以使用这个包。xml.dom将XML数据在内存中解析成一个树,通过对树的操作来操作XML。一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后你可以利用DOM 提供的不同的函数来读取或修改文档的内容和结构,也可以把修改过的内容写入xml文件。

注意:在 xml.dom 包里面有许多模块,注意它们之间的不同。

  • minidom是DOM API的极简化实现,比完整版的DOM要简单的多,而且这个包也小的多。
  • pulldom模块提供的是一个“pull解析器”,其背后的基本概念指的是从XML流中pull事件,然后进行处理。
# <?xml version="1.0" encoding="UTF-8"?>
# <employees>
# <employee>
# <name>linux</name>
# <age>30</age>
# </employee>
# <employee>
# <name>windows</name>
# <age>20</age>
# </employee>
# </employees>
from xml.dom import minidom
doc = minidom.parse("employees.xml")
root = doc.documentElement
employees = root.getElementsByTagName("employee")
for employee in employees:
print (employee.nodeName)
print (employee.toxml())
nameNode = employee.getElementsByTagName("name")[0]
print (nameNode.childNodes)
print (nameNode.nodeName + ":" + nameNode.childNodes[0].nodeValue)
ageNode = employee.getElementsByTagName("age")[0]
print (ageNode.childNodes)
print (ageNode.nodeName + ":" + ageNode.childNodes[0].nodeValue)
for n in employee.childNodes:
print (n)
# <?xml version="1.0" encoding="UTF-8"?>
#  <employees>
#   <employee>
#     <name>linux</name>
#     <age>30</age>
#   </employee>
#   <employee>
#     <name>windows</name>
#     <age>20</age>
#   </employee>
#  </employees>

from xml.dom import minidom
doc = minidom.parse("employees.xml")
root = doc.documentElement
employees = root.getElementsByTagName("employee")
for employee in employees:
    print (employee.nodeName)
    print (employee.toxml())
    nameNode = employee.getElementsByTagName("name")[0]
    print (nameNode.childNodes)
    print (nameNode.nodeName + ":" + nameNode.childNodes[0].nodeValue)
    ageNode = employee.getElementsByTagName("age")[0]
    print (ageNode.childNodes)
    print (ageNode.nodeName + ":" + ageNode.childNodes[0].nodeValue)
    for n in employee.childNodes:
        print (n)

xml.sax.* 模块

xml.sax.* 模块是 SAX API 的实现。这个模块牺牲了便捷性来换取速度和内存占用。SAX(simple API for XML),是基于事件处理的,当XML文档顺序地读入时,每次遇到一个元素会触发相应的事件处理函数来处理。

SAX的特点:

  • 是基于事件的 API
  • 在一个比 DOM 低的级别上操作
  • 为您提供比 DOM 更多的控制
  • 几乎总是比 DOM 更有效率
  • 但不幸的是,需要比 DOM 更多的工作

使用Python解析XML的时候,需要 import xml.sax 和 xml.sax.handler

# <?xml version="1.0"?>
# <collection shelf="New Arrivals">
# <movie title="Enemy Behind">
# <type>War, Thriller</type>
# <format>DVD</format>
# <year>2003</year>
# <rating>PG</rating>
# <stars>10</stars>
# <description>Talk about a US-Japan war</description>
# </movie>
# <movie title="Transformers">
# <type>Anime, Science Fiction</type>
# <format>DVD</format>
# <year>1989</year>
# <rating>R</rating>
# <stars>8</stars>
# <description>A schientific fiction</description>
# </movie>
# <movie title="Trigun">
# <type>Anime, Action</type>
# <format>DVD</format>
# <episodes>4</episodes>
# <rating>PG</rating>
# <stars>10</stars>
# <description>Vash the Stampede!</description>
# </movie>
# <movie title="Ishtar">
# <type>Comedy</type>
# <format>VHS</format>
# <rating>PG</rating>
# <stars>2</stars>
# <description>Viewable boredom</description>
# </movie>
# </collection>
import xml.sax
class MovieHandler( xml.sax.ContentHandler):
def __init__(self):
self.CurrentData = ""
self.type = ""
self.format = ""
self.year = ""
self.rating = ""
self.stars = ""
self.description = ""
# 元素开始事件处理
def startElement(self, tag, attributes):
self.CurrentData = tag
if tag == "movie":
print "*****Movie*****"
title = attributes["title"]
print "Title:", title
# 元素结束事件处理
def endElement(self, tag):
if self.CurrentData == "type":
print "Type:", self.type
elif self.CurrentData == "format":
print "Format:", self.format
elif self.CurrentData == "year":
print "Year:", self.year
elif self.CurrentData == "rating":
print "Rating:", self.rating
elif self.CurrentData == "stars":
print "Stars:", self.stars
elif self.CurrentData == "description":
print "Description:", self.description
self.CurrentData = ""
# 内容事件处理
def characters(self, content):
if self.CurrentData == "type":
self.type = content
elif self.CurrentData == "format":
self.format = content
elif self.CurrentData == "year":
self.year = content
elif self.CurrentData == "rating":
self.rating = content
elif self.CurrentData == "stars":
self.stars = content
elif self.CurrentData == "description":
self.description = content
if ( __name__ == "__main__"):
# 创建一个 XMLReader
parser = xml.sax.make_parser()
# turn off namepsaces
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
# 重写 ContextHandler
Handler = MovieHandler()
parser.setContentHandler( Handler )
parser.parse("movies.xml")
# <?xml version="1.0"?>
# <collection shelf="New Arrivals">
#     <movie title="Enemy Behind">
#        <type>War, Thriller</type>
#        <format>DVD</format>
#        <year>2003</year>
#        <rating>PG</rating>
#        <stars>10</stars>
#        <description>Talk about a US-Japan war</description>
#     </movie>
#     <movie title="Transformers">
#        <type>Anime, Science Fiction</type>
#        <format>DVD</format>
#        <year>1989</year>
#        <rating>R</rating>
#        <stars>8</stars>
#        <description>A schientific fiction</description>
#     </movie>
#        <movie title="Trigun">
#        <type>Anime, Action</type>
#        <format>DVD</format>
#        <episodes>4</episodes>
#        <rating>PG</rating>
#        <stars>10</stars>
#        <description>Vash the Stampede!</description>
#     </movie>
#     <movie title="Ishtar">
#        <type>Comedy</type>
#        <format>VHS</format>
#        <rating>PG</rating>
#        <stars>2</stars>
#        <description>Viewable boredom</description>
#     </movie>
# </collection>

import xml.sax

class MovieHandler( xml.sax.ContentHandler):
   def __init__(self):
      self.CurrentData = ""
      self.type = ""
      self.format = ""
      self.year = ""
      self.rating = ""
      self.stars = ""
      self.description = ""

   # 元素开始事件处理
   def startElement(self, tag, attributes):
      self.CurrentData = tag
      if tag == "movie":
         print "*****Movie*****"
         title = attributes["title"]
         print "Title:", title

   # 元素结束事件处理
   def endElement(self, tag):
      if self.CurrentData == "type":
         print "Type:", self.type
      elif self.CurrentData == "format":
         print "Format:", self.format
      elif self.CurrentData == "year":
         print "Year:", self.year
      elif self.CurrentData == "rating":
         print "Rating:", self.rating
      elif self.CurrentData == "stars":
         print "Stars:", self.stars
      elif self.CurrentData == "description":
         print "Description:", self.description
      self.CurrentData = ""

   # 内容事件处理
   def characters(self, content):
      if self.CurrentData == "type":
         self.type = content
      elif self.CurrentData == "format":
         self.format = content
      elif self.CurrentData == "year":
         self.year = content
      elif self.CurrentData == "rating":
         self.rating = content
      elif self.CurrentData == "stars":
         self.stars = content
      elif self.CurrentData == "description":
         self.description = content

if ( __name__ == "__main__"):
   # 创建一个 XMLReader
   parser = xml.sax.make_parser()
   # turn off namepsaces
   parser.setFeature(xml.sax.handler.feature_namespaces, 0)
   # 重写 ContextHandler
   Handler = MovieHandler()
   parser.setContentHandler( Handler )
   parser.parse("movies.xml")

xml.parser.expat

xml.parser.expat提供了对C语言编写的expat解析器的一个直接的、底层API接口。expat接口与SAX类似,也是基于事件回调机制,但是这个接口并不是标准化的,只适用于expat库。

import xml.parsers.expat
class ExParser(object):
'''Parse roster xml'''
def __init__(self, xml_raw):
'''init parser and setup handlers'''
self.parser = xml.parsers.expat.ParserCreate()
#connect handlers
self.parser.StartElementHandler = self.start_element
self.parser.EndElementHandler = self.end_element
self.parser.CharacterDataHandler = self.char_data
self.parser.Parse(xml_raw)
del(xml_raw)
def start_element(self, name, attrs):
'''Start xml element handler'''
print('start:'+name)
def end_element(self, name):
'''End xml element handler'''
print('end:'+name)
def char_data(self, data):
'''Char xml element handler'''
print('data is '+data)
import xml.parsers.expat

class ExParser(object):
    '''Parse roster xml'''
    def __init__(self, xml_raw):
        '''init parser and setup handlers'''
        self.parser = xml.parsers.expat.ParserCreate()

        #connect handlers
        self.parser.StartElementHandler = self.start_element
        self.parser.EndElementHandler = self.end_element
        self.parser.CharacterDataHandler = self.char_data
        self.parser.Parse(xml_raw)
        del(xml_raw)

    def start_element(self, name, attrs):
        '''Start xml element handler'''
        print('start:'+name)

    def end_element(self, name):
        '''End xml element handler'''
        print('end:'+name)

    def char_data(self, data):
        '''Char xml element handler'''
        print('data is '+data)

ElementTree

xml.etree.ElementTree模块提供了一个轻量级、Pythonic的API,同时还有一个高效的C语言实现,即xml.etree.cElementTree。与DOM相比,ET的速度更快,API使用更直接、方便。与SAX相比,ET.iterparse函数同样提供了按需解析的功能,不会一次性在内存中读入整个文档。ET的性能与SAX模块大致相仿,但是它的API更加高层次,用户使用起来更加便捷。

ElementTree在 Python 标准库中有两种实现。一种是纯 Python 实现例如 xml.etree.ElementTree ,另外一种是速度快一点的 xml.etree.cElementTree 。你要记住: 尽量使用 C 语言实现的那种,因为它速度更快,而且消耗的内存更少。

# <?xml version="1.0"?>
# <doc>
# <branch name="testing" hash="1cdf045c">
# text,source
# </branch>
# <branch name="release01" hash="f200013e">
# <sub-branch name="subrelease01">
# xml,sgml
# </sub-branch>
# </branch>
# <branch name="invalid">
# </branch>
# </doc>
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='doc1.xml')
root = tree.getroot()
print root.tag, root.attrib
for child_of_root in root:
print child_of_root.tag, child_of_root.attrib
for elem in tree.iter():
print elem.tag, elem.attrib
for elem in tree.iter(tag='branch'):
print elem.tag, elem.attrib
for elem in tree.iterfind('branch/sub-branch'):
print elem.tag, elem.attrib
for elem in tree.iterfind('branch[@name="release01"]'):
print elem.tag, elem.attrib
# <?xml version="1.0"?>
# <doc>
#     <branch name="testing" hash="1cdf045c">
#         text,source
#     </branch>
#     <branch name="release01" hash="f200013e">
#         <sub-branch name="subrelease01">
#             xml,sgml
#         </sub-branch>
#     </branch>
#     <branch name="invalid">
#     </branch>
# </doc>

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

tree = ET.ElementTree(file='doc1.xml')
root = tree.getroot()
print root.tag, root.attrib
for child_of_root in root:
    print child_of_root.tag, child_of_root.attrib
for elem in tree.iter():
    print elem.tag, elem.attrib
for elem in tree.iter(tag='branch'):
    print elem.tag, elem.attrib
for elem in tree.iterfind('branch/sub-branch'):
    print elem.tag, elem.attrib
for elem in tree.iterfind('branch[@name="release01"]'):
    print elem.tag, elem.attrib

Element对象

class xml.etree.ElementTree.Element(tag, attrib={}, **extra)
  tag:string,元素代表的数据种类。
  text:string,元素的内容。
  tail:string,元素的尾形。
  attrib:dictionary,元素的属性字典。
  #针对属性的操作
  clear():清空元素的后代、属性、text和tail也设置为None。
  get(key, default=None):获取key对应的属性值,如该属性不存在则返回default值。
  items():根据属性字典返回一个列表,列表元素为(key, value)。
  keys():返回包含所有元素属性键的列表。
  set(key, value):设置新的属性键与值。
  #针对后代的操作
  append(subelement):添加直系子元素。
  extend(subelements):增加一串元素对象作为子元素。#python2.7新特性
  find(match):寻找第一个匹配子元素,匹配对象可以为tag或path。
  findall(match):寻找所有匹配子元素,匹配对象可以为tag或path。
  findtext(match):寻找第一个匹配子元素,返回其text值。匹配对象可以为tag或path。
  insert(index, element):在指定位置插入子元素。
  iter(tag=None):生成遍历当前元素所有后代或者给定tag的后代的迭代器。#python2.7新特性
  iterfind(match):根据tag或path查找所有的后代。
  itertext():遍历所有后代并返回text值。
  remove(subelement):删除子元素。
class xml.etree.ElementTree.Element(tag, attrib={}, **extra)

  tag:string,元素代表的数据种类。
  text:string,元素的内容。
  tail:string,元素的尾形。
  attrib:dictionary,元素的属性字典。
  
  #针对属性的操作
  clear():清空元素的后代、属性、text和tail也设置为None。
  get(key, default=None):获取key对应的属性值,如该属性不存在则返回default值。
  items():根据属性字典返回一个列表,列表元素为(key, value)。
  keys():返回包含所有元素属性键的列表。
  set(key, value):设置新的属性键与值。

  #针对后代的操作
  append(subelement):添加直系子元素。
  extend(subelements):增加一串元素对象作为子元素。#python2.7新特性
  find(match):寻找第一个匹配子元素,匹配对象可以为tag或path。
  findall(match):寻找所有匹配子元素,匹配对象可以为tag或path。
  findtext(match):寻找第一个匹配子元素,返回其text值。匹配对象可以为tag或path。
  insert(index, element):在指定位置插入子元素。
  iter(tag=None):生成遍历当前元素所有后代或者给定tag的后代的迭代器。#python2.7新特性
  iterfind(match):根据tag或path查找所有的后代。
  itertext():遍历所有后代并返回text值。
  remove(subelement):删除子元素。

ElementTree对象

class xml.etree.ElementTree.ElementTree(element=None, file=None)
  element如果给定,则为新的ElementTree的根节点。
  _setroot(element):用给定的element替换当前的根节点。慎用。
  # 以下方法与Element类中同名方法近似,区别在于它们指定以根节点作为操作对象。
  find(match)
  findall(match)
  findtext(match, default=None)
  getroot():获取根节点.
  iter(tag=None)
  iterfind(match)
  parse(source, parser=None):装载xml对象,source可以为文件名或文件类型对象.
  write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml")
class xml.etree.ElementTree.ElementTree(element=None, file=None)
  element如果给定,则为新的ElementTree的根节点。

  _setroot(element):用给定的element替换当前的根节点。慎用。
  
  # 以下方法与Element类中同名方法近似,区别在于它们指定以根节点作为操作对象。
  find(match)
  findall(match)
  findtext(match, default=None)
  getroot():获取根节点.
  iter(tag=None)
  iterfind(match)
  parse(source, parser=None):装载xml对象,source可以为文件名或文件类型对象.
  write(file, encoding="us-ascii", xml_declaration=None, default_namespace=None,method="xml")

模块方法

xml.etree.ElementTree.Comment(text=None)
创建一个特别的element,通过标准序列化使其代表了一个comment。comment可以为bytestring或unicode。
xml.etree.ElementTree.dump(elem)
生成一个element tree,通过sys.stdout输出,elem可以是元素树或单个元素。这个方法最好只用于debug。
xml.etree.ElementTree.fromstring(text)
text是一个包含XML数据的字符串,与XML()方法类似,返回一个Element实例。
xml.etree.ElementTree.fromstringlist(sequence, parser=None)
从字符串的序列对象中解析xml文档。缺省parser为XMLParser,返回Element实例。
xml.etree.ElementTree.iselement(element)
检查是否是一个element对象。
xml.etree.ElementTree.iterparse(source, events=None, parser=None)
将文件或包含xml数据的文件对象递增解析为element tree,并且报告进度。events是一个汇报列表,如果忽略,将只有end事件会汇报出来。
注意,iterparse()只会在看见开始标签的">"符号时才会抛出start事件,因此届时属性是已经定义了,但是text和tail属性在那时还没有定义,同样子元素也没有定义,因此他们可能不能被显示出来。如果你想要完整的元素,请查找end事件。
xml.etree.ElementTree.parse(source, parser=None)
将一个文件或者字符串解析为element tree。
xml.etree.ElementTree.ProcessingInstruction(target, text=None)
这个方法会创建一个特别的element,该element被序列化为一个xml处理命令。
xml.etree.ElementTree.register_namespace(prefix, uri)
注册命名空间前缀。这个注册是全局有效,任何已经给出的前缀或者命名空间uri的映射关系会被删除。
xml.etree.ElementTree.SubElement(parent, tag, attrib={}, **extra)
子元素工厂,创建一个Element实例并追加到已知的节点。
xml.etree.ElementTree.tostring(element, encoding="us-ascii", method="xml")
生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为"xml","html","text"。返回包含了xml数据的字符串。
xml.etree.ElementTree.tostringlist(element, encoding="us-ascii", method="xml")
生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为"xml","html","text"。返回包含了xml数据的字符串列表。
xml.etree.ElementTree.XML(text, parser=None)
从一个字符串常量中解析出xml片段。返回Element实例。
xml.etree.ElementTree.XMLID(text, parser=None)
从字符串常量解析出xml片段,同时返回一个字典,用以映射element的id到其自身。
xml.etree.ElementTree.Comment(text=None)
创建一个特别的element,通过标准序列化使其代表了一个comment。comment可以为bytestring或unicode。

xml.etree.ElementTree.dump(elem)
生成一个element tree,通过sys.stdout输出,elem可以是元素树或单个元素。这个方法最好只用于debug。

xml.etree.ElementTree.fromstring(text)
text是一个包含XML数据的字符串,与XML()方法类似,返回一个Element实例。

xml.etree.ElementTree.fromstringlist(sequence, parser=None)
从字符串的序列对象中解析xml文档。缺省parser为XMLParser,返回Element实例。

xml.etree.ElementTree.iselement(element)
检查是否是一个element对象。

xml.etree.ElementTree.iterparse(source, events=None, parser=None)
将文件或包含xml数据的文件对象递增解析为element tree,并且报告进度。events是一个汇报列表,如果忽略,将只有end事件会汇报出来。
注意,iterparse()只会在看见开始标签的">"符号时才会抛出start事件,因此届时属性是已经定义了,但是text和tail属性在那时还没有定义,同样子元素也没有定义,因此他们可能不能被显示出来。如果你想要完整的元素,请查找end事件。

xml.etree.ElementTree.parse(source, parser=None)
将一个文件或者字符串解析为element tree。

xml.etree.ElementTree.ProcessingInstruction(target, text=None)
这个方法会创建一个特别的element,该element被序列化为一个xml处理命令。

xml.etree.ElementTree.register_namespace(prefix, uri)
注册命名空间前缀。这个注册是全局有效,任何已经给出的前缀或者命名空间uri的映射关系会被删除。

xml.etree.ElementTree.SubElement(parent, tag, attrib={}, **extra)
子元素工厂,创建一个Element实例并追加到已知的节点。

xml.etree.ElementTree.tostring(element, encoding="us-ascii", method="xml")
生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为"xml","html","text"。返回包含了xml数据的字符串。

xml.etree.ElementTree.tostringlist(element, encoding="us-ascii", method="xml")
生成一个字符串来表示表示xml的element,包括所有子元素。element是Element实例,method为"xml","html","text"。返回包含了xml数据的字符串列表。

xml.etree.ElementTree.XML(text, parser=None)
从一个字符串常量中解析出xml片段。返回Element实例。

xml.etree.ElementTree.XMLID(text, parser=None)
从字符串常量解析出xml片段,同时返回一个字典,用以映射element的id到其自身。

xmltodict

xmltodict是一个可以让你在处理XML时感觉像在处理JSON一样的Python模块。

对于一个像这样的XML文件:

<mydocument has="an attribute">
<and>
<many>elements</many>
<many>more elements</many>
</and>
<plus a="complex">
element as well
</plus>
</mydocument>
<mydocument has="an attribute">
  <and>
    <many>elements</many>
    <many>more elements</many>
  </and>
  <plus a="complex">
    element as well
  </plus>
</mydocument>

可以装载进一个Python字典里:

import xmltodict
with open('path/to/file.xml') as fd:
obj = xmltodict.parse(fd.read())
import xmltodict

with open('path/to/file.xml') as fd:
    obj = xmltodict.parse(fd.read())

你可以访问元素,属性以及值:

doc['mydocument']['@has'] # == u'an attribute'
doc['mydocument']['and']['many'] # == [u'elements', u'more elements']
doc['mydocument']['plus']['@a'] # == u'complex'
doc['mydocument']['plus']['#text'] # == u'element as well'
doc['mydocument']['@has'] # == u'an attribute'
doc['mydocument']['and']['many'] # == [u'elements', u'more elements']
doc['mydocument']['plus']['@a'] # == u'complex'
doc['mydocument']['plus']['#text'] # == u'element as well'

xmltodict 也有unparse函数让你可以转回XML。该函数有一个streaming模式适合用来 处理不能放入内存的文件,它还支持命名空间。

dicttoxml是一个将字典转化为xml的工具,感兴趣的可以研究下。

untangle

untangle库可以将XML文档映射为一个Python 对象,该对象于其结构中包含了原文档的节点与属性信息。

<?xml version="1.0"?>
<root>
<child name="child1">
</root>
<?xml version="1.0"?>
<root>
    <child name="child1">
</root>

可以被这样载入:

import untangle
obj = untangle.parse('path/to/file.xml')
import untangle

obj = untangle.parse('path/to/file.xml')

然后你可以像这样获取child元素名称:

obj.root.child['name']
obj.root.child['name']

untangle也支持从字符串或URL中载入XML。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK