Python STL xml

Posted onetoinf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python STL xml相关的知识,希望对你有一定的参考价值。

python对XML的解析

常见的XML编程接口有DOM和SAX,这两种接口处理XML文件的方式不同,当然使用场合也不同。

python有三种方法解析XML,SAX,DOM,以及ElementTree:

SAX (simple API for XML )

python 标准库包含SAX解析器,SAX用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。

DOM(Document Object Model)

将XML数据在内存中解析成一个树,通过对树的操作来操作XML。

ElementTree(元素树)

ElementTree就像一个轻量级的DOM,具有方便友好的API。代码可用性好,速度快,消耗内存少。

注:因DOM需要将XML数据映射到内存中的树,一是比较慢,二是比较耗内存,而SAX流式读取XML文件,比较快,占用内存少,但需要用户实现回调函数(handler)。

数据

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

使用SAX

SAX是一种基于事件驱动的API。

利用SAX解析XML文档牵涉到两个部分:解析器和事件处理器。

解析器负责读取XML文档,并向事件处理器发送事件,如元素开始跟元素结束事件;

而事件处理器则负责对事件作出相应,对传递的XML数据进行处理。

  • 1、对大型文件进行处理;
  • 2、只需要文件的部分内容,或者只需从文件中得到特定信息。
  • 3、想建立自己的对象模型的时候。

在python中使用sax方式处理xml要先引入xml.sax中的parse函数,还有xml.sax.handler中的ContentHandler。

ContentHandler类方法介绍

characters(content)方法

调用时机:

从行开始,遇到标签之前,存在字符,content的值为这些字符串。

从一个标签,遇到下一个标签之前, 存在字符,content的值为这些字符串。

从一个标签,遇到行结束符之前,存在字符,content的值为这些字符串。

标签可以是开始标签,也可以是结束标签。

startDocument()方法

文档启动的时候调用。

endDocument()方法

解析器到达文档结尾时调用。

startElement(name, attrs)方法

遇到XML开始标签时调用,name是标签的名字,attrs是标签的属性值字典。

endElement(name)方法

遇到XML结束标签时调用。

make_parser方法

以下方法创建一个新的解析器对象并返回。

xml.sax.make_parser( [parser_list] )

参数说明:

  • parser_list - 可选参数,解析器列表

parser方法

以下方法创建一个 SAX 解析器并解析xml文档:

xml.sax.parse( xmlfile, contenthandler[, errorhandler])

参数说明:

  • xmlfile - xml文件名
  • contenthandler - 必须是一个ContentHandler的对象
  • errorhandler - 如果指定该参数,errorhandler必须是一个SAX ErrorHandler对象

parseString方法

parseString方法创建一个XML解析器并解析xml字符串:

xml.sax.parseString(xmlstring, contenthandler[, errorhandler])

参数说明:

  • xmlstring - xml字符串
  • contenthandler - 必须是一个ContentHandler的对象
  • errorhandler - 如果指定该参数,errorhandler必须是一个SAX ErrorHandler对象

Python 解析XML实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
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")

执行结果如下:

*****Movie*****
Title: Enemy Behind
Type: War, Thriller
Format: DVD
Year: 2003
Rating: PG
Stars: 10
Description: Talk about a US-Japan war
*****Movie*****
Title: Transformers
Type: Anime, Science Fiction
Format: DVD
Year: 1989
Rating: R
Stars: 8
Description: A schientific fiction
*****Movie*****
Title: Trigun
Type: Anime, Action
Format: DVD
Rating: PG
Stars: 10
Description: Vash the Stampede!
*****Movie*****
Title: Ishtar
Type: Comedy
Format: VHS
Rating: PG
Stars: 2
Description: Viewable boredom

完整的 SAX API 文档请查阅Python SAX APIs

使用xml

文件对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。

一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后你可以利用DOM 提供的不同的函数来读取或修改文档的内容和结构,也可以把修改过的内容写入xml文件。

python中用xml.dom.minidom来解析xml文件,实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
from xml.dom.minidom import parse
import xml.dom.minidom
 
# 使用minidom解析器打开 XML 文档
DOMTree = xml.dom.minidom.parse("movies.xml")
collection = DOMTree.documentElement
if collection.hasAttribute("shelf"):
   print "Root element : %s" % collection.getAttribute("shelf")
 
# 在集合中获取所有电影
movies = collection.getElementsByTagName("movie")
 
# 打印每部电影的详细信息
for movie in movies:
   print "*****Movie*****"
   if movie.hasAttribute("title"):
      print "Title: %s" % movie.getAttribute("title")
 
   type = movie.getElementsByTagName('type')[0]
   print "Type: %s" % type.childNodes[0].data
   format = movie.getElementsByTagName('format')[0]
   print "Format: %s" % format.childNodes[0].data
   rating = movie.getElementsByTagName('rating')[0]
   print "Rating: %s" % rating.childNodes[0].data
   description = movie.getElementsByTagName('description')[0]
   print "Description: %s" % description.childNodes[0].data

执行结果如下:

Root element : New Arrivals
*****Movie*****
Title: Enemy Behind
Type: War, Thriller
Format: DVD
Rating: PG
Description: Talk about a US-Japan war
*****Movie*****
Title: Transformers
Type: Anime, Science Fiction
Format: DVD
Rating: R
Description: A schientific fiction
*****Movie*****
Title: Trigun
Type: Anime, Action
Format: DVD
Rating: PG
Description: Vash the Stampede!
*****Movie*****
Title: Ishtar
Type: Comedy
Format: VHS
Rating: PG
Description: Viewable boredom

完整的 DOM API 文档请查阅Python DOM APIs

使用ElementTree

解析XML文档

已解析的XML文档在内存中由ElementTree和Element对象表示,这些对象基于XML文 档中节点嵌套的方式以树结构相互连接。

用parse()解析一个完整的文档时,会返回一个ElementTree实例。这个树了解输入文档中的所有数据,另外可以原地搜索或操纵树中的节点。基于这种灵活性,可以更为方便地处理已 解析文档,不过与基于亊件的解析方法相比,这样往往需要更多的内存,因为整个文档必须 —次全部加栽。

对于简单的小文档(如以下播客列表,表示为一个OPML大纲),其内存需求并不大:

<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
<head>
    <title>My Podcasts</title>
    <dateCreated>Sun, 07 Mar 2010 15:53:26 GMT</dateCreated>
    <dateModified>Sun, 07 Mar 2010 15:53:26 GMT</dateModified>
</head>
<body>
  <outline text="Fiction">
    <outline 
       text="tor.com / category / tordotstories" type="rss" 
       xmlUrl="http://www.tor.com/rss/category/TorDotStories" 
       htmlUrl="http://www.tor.com/" />
  </outline>
  <outline text="Python">
    <outline 
       text="PyCon Podcast" type="rss" 
       xmlUrl="http://advocacy.python.org/podcasts/pycon.rss" 
       htmlUrl="http://advocacy.python.org/podcasts/" />
    <outline 
       text="A Little Bit of Python" type="rss" 
       xmlUrl="http://advocacy.python.org/podcasts/littlebit.rss" 
       htmlUrl="http://advocacy.python.org/podcasts/" />
  </outline>
</body>
</opml>

要解析这个文档,需要向parse传递一个打开的文件句

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

print tree

它会读取数据、解析XML,并返回一个ElementTree对象。

<xml.etree.ElementTree.ElementTree object at 0x000000000517A518>
[Finished in 0.1s]

遍历解析树

要按顺序访问所有子节点,可以使用iter创建一个生成器,迭代处理这个ElementTree实例。

from xml.etree import ElementTree
import pprint

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.iter():
    print node.tag

这个例子会打印整个树,一次打印一个标记

opml
head
title
dateCreated
dateModified
body
outline
outline
outline
outline
outline
[Finished in 0.1s]

如果只是打印播客的名字组和提要URL,可以只迭代处理outline节点(而不考虑首部中的所有数据),通过査找attrib字典中的值来打印text和xmlUrl属性

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.iter('outline'):
    name = node.attrib.get('text')
    url = node.attrib.get('xmlUrl')
    if name and url:
        print '  %s' % name
        print '    %s' % url
    else:
        print name

iter()的outline参数意味着只处理标记为outline的节点。

Fiction
  tor.com / category / tordotstories
    http://www.tor.com/rss/category/TorDotStories
Python
  PyCon Podcast
    http://advocacy.python.org/podcasts/pycon.rss
  A Little Bit of Python
    http://advocacy.python.org/podcasts/littlebit.rss
[Finished in 0.1s]

查找文档中的节点

如果像这样査看整个树并捜索有关的节点,可能很容易出错。前面的例子必须査看每一个outline节点,来确定这是一个组(只有一个text属性的节点)还是一个播客(包含text和xmlUrl的节点)。要生成一个简单的播客提要URL列表,而不包括名字或组,可以简化逻辑, 使用findall来査找包含更多描述性搜索特性的节点。

对于以上的第一个版本,作为第一次转换,可以用一个XPath参数来査找所有outline节点。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.findall('.//outline'):
    url = node.attrib.get('xmlUrl')
    if url:
        print url

这个版本中的逻辑与使用getiterator的版本并没有显著区别。这里仍然必须检査URL是否存在,只不过如果没有发现URL,它不会打印组名

http://www.tor.com/rss/category/TorDotStories
http://advocacy.python.org/podcasts/pycon.rss
http://advocacy.python.org/podcasts/littlebit.rss
[Finished in 0.1s]

outline节点只有2层嵌套,可以利用这一点,把搜索路径修改为7/outHneAmtiine,这意味着只处理outline节点的第2层。

from xml.etree import ElementTree

with open('podcasts.opml', 'rt') as f:
    tree = ElementTree.parse(f)

for node in tree.findall('.//outline/outline'):
    url = node.attrib.get('xmlUrl')
    print url

输入中所有嵌套深度为2层的outline节点都认为有一个xmlURL属性指向播客提要,所以循环在使用这个属性之前可以不再先做检査。

http://www.tor.com/rss/category/TorDotStories
http://advocacy.python.org/podcasts/pycon.rss
http://advocacy.python.org/podcasts/littlebit.rss
[Finished in 0.1s]

不过,这个版本仅限干当前的这个结构,所以如果outline节点重新组织为一个更深的树, 这个版本就无法正常工作。

解析节点属性

fmdall和iter返回的元素是Element对象,各个对象分別表示XML解析树中的一个节点。每个Element有一些属性来访问XML中的数据。可以用一个稍有些牵强的示例输入文件data.xml来说明。

<?xml version="1.0" encoding="UTF-8"?>
<top>
  <child>Regular text.</child>
  <child_with_tail>Regular text.</child_with_tail>"Tail" text.
  <with_attributes name="value" foo="bar" />
  <entity_expansion attribute="This &#38; That">
    That &#38; This
  </entity_expansion>
</top>

节点的属性可以由attrib属性得到,它就像是一个字典

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

node = tree.find('./with_attributes')
print node.tag
for name, value in sorted(node.attrib.items()):
    print '  %-4s = "%s"' % (name, value)

输入文件第5行上的节点有两个属性name和foo。

with_attributes
  foo  = "bar"
  name = "value"
[Finished in 0.1s]

还可以得到节点的文本内容,以及结束标记后面的tail文本。

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

for path in [ './child', './child_with_tail' ]:
    node = tree.find(path)
    print node.tag
    print '  child node text:', node.text
    print '  and tail text  :', node.tail

第3行的child节点包含嵌人文本,第4行的节点除了文本还有tail文本(包括空白符),

child
  child node text: Regular text.
  and tail text  : 
  
child_with_tail
  child node text: Regular text.
  and tail text  : "Tail" text.
  
[Finished in 0.1s]

返回值之前.文档中嵌入的XML实体引用会转换为适当的字符。

from xml.etree import ElementTree

with open('data.xml', 'rt') as f:
    tree = ElementTree.parse(f)

node = tree.find('entity_expansion')
print node.tag
print '  in attribute:', node.attrib['attribute']
print '  in text     :', node.text.strip()

这个自动转换意味着可以忽略表示XML文档中某些字符的实现细节。

entity_expansion
  in attribute: This & That
  in text     : That & This
[Finished in 0.1s]

解析时监视事件

另一个处理XML文档的API是基于事件的。解析器为开始标记生成start亊件,为结束标 记生成end事件。解析阶段中可以通过迭代处理事件流从文档抽取数据,如果以后没有必要处 理整个文档,或者没有必要将整个解析文档都保存在内存中,基于事件的API就会很方便。

有以下事件类型:

  • start遇到一个新标记。会处理标记的结束尖括号,但不处理内容。

  • end已经处理结束标记的结束尖括号。所有子节点都已经处理,

  • start-ns开始一个命名空间声明。

  • end-ns结束一个命名空间声明。

  • iterparse()返回一个可迭代的对象,它将生成元组,其中包含事件名和触发事件的节点。

from xml.etree.ElementTree import iterparse

depth = 0
prefix_width = 8
prefix_dots = '.' * prefix_width
line_template = ''.join([ '{prefix:<0.{prefix_len}}',
                          '{event:<8}',
                          '{suffix:<{suffix_len}} ',
                          '{node.tag:<12} ',
                          '{node_id}',
                          ])

EVENT_NAMES = ['start', 'end', 'start-ns', 'end-ns']

for (event, node) in iterparse('podcasts.opml', EVENT_NAMES):
    if event == 'end':
        depth -= 1

    prefix_len = depth * 2
    
    print line_template.format(
        prefix=prefix_dots,
        prefix_len=prefix_len,
        suffix='',
        suffix_len=(prefix_width - prefix_len),
        node=node,
        node_id=id(node),
        event=event,
        )
    
    if event == 'start':
        depth += 1

默认情况下,只会生成end事件。要査看其他事件,可以将所需的事件名列表传入 iterparse,如下例所示。

start            opml         91004768
..start          head         91021664
....start        title        91021440
....end          title        91021440
....start        dateCreated  91022056
....end          dateCreated  91022056
....start        dateModified 91022112
....end          dateModified 91022112
..end            head         91021664
..start          body         91022168
....start        outline      91022224
......start      outline      91022280
......end        outline      91022280
....end          outline      91022224
....start        outline      91022392
......start      outline      91022448
......end        outline      91022448
......start      outline      91022504
......end        outline      91022504
....end          outline      91022392
..end            body         91022168
end              opml         91004768
[Finished in 0.1s]

以亊件方式进行处理对于某些操作来说更为自然,如将XML输入转换为另外某种格式,可以使用这个技术将播客列表(来自前面的例子)由XML文件转换为一个CSV文件,从而可以将它们加载到一个电子表格或数据库应用。

import csv
from xml.etree.ElementTree import iterparse
import sys

writer = csv.writer(sys.stdout, quoting=csv.QUOTE_NONNUMERIC)

group_name = ''

for (event, node) in iterparse('podcasts.opml', events=['start']):
    if node.tag != 'outline':
        # Ignore anything not part of the outline
        continue
    if not node.attrib.get('xmlUrl'):
        # Remember the current group
        group_name = node.attrib['text']
    else:
        # Output a podcast entry
        writer.writerow( (group_name, node.attrib['text'],
                          node.attrib['xmlUrl'],
                          node.attrib.get('htmlUrl', ''),
                          )
                         )

这个转换程序并不需要将整个已解析的输入文件保存在内存中,而且遇到输入中的各个节点时才进行处理,这样做也更为髙效。

"Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/"
"Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/"
"Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/"
[Finished in 0.1s]

创建一个定制树构造器

要处理解析亊件,一种可能更为髙效的方法是将标准的树构造器行为替换为一种定制行为。ElementTree解析器使用一个XMLTreeBuilder处理XML,并调用目标类的方法来保存结果。通常输出是由默认TreeBuilder类创建的一个ElementTree实例。可以将TreeBuilder替换为另 一个类,使它在实例化Element节点之前接收亊件,从而节省这部分开销。

可以将上一节的XML-CSV转换器重新实现为一个树构造器。

import csv
from xml.etree.ElementTree import XMLTreeBuilder
import sys

class PodcastListToCSV(object):

    def __init__(self, outputFile):
        self.writer = csv.writer(outputFile,
                                 quoting=csv.QUOTE_NONNUMERIC)
        self.group_name = ''
        return

    def start(self, tag, attrib):
        if tag != 'outline':
            # Ignore anything not part of the outline
            return
        if not attrib.get('xmlUrl'):
            # Remember the current group
            self.group_name = attrib['text']
        else:
            # Output a podcast entry
            self.writer.writerow( (self.group_name, attrib['text'],
                                   attrib['xmlUrl'],
                                   attrib.get('htmlUrl', ''),
                                   )
                                  )

    def end(self, tag):
        # Ignore closing tags
        pass
    def data(self, data):
        # Ignore data inside nodes
        pass
    def close(self):
        # Nothing special to do here
        return


target = PodcastListToCSV(sys.stdout)
parser = XMLTreeBuilder(target=target)
with open('podcasts.opml', 'rt') as f:
    for line in f:
        parser.feed(line)
parser.close()

PodcastListToCSV实现了TreeBuilder协议。每次遇到一个新的XML标记时,会调用start并提供标记名和属性。看到一个结束标记时,则会根据标记名调用油啦)。在这二者之间,如果一个节点有内容会调用data(—般认为树构造器会跟踪“当前”节点)。所有输入都 已处理时,将调用close()。它可能返回一个值,这会返回给XMLTreeBuilder用户,

"Fiction","tor.com / category / tordotstories","http://www.tor.com/rss/category/TorDotStories","http://www.tor.com/"
"Python","PyCon Podcast","http://advocacy.python.org/podcasts/pycon.rss","http://advocacy.python.org/podcasts/"
"Python","A Little Bit of Python","http://advocacy.python.org/podcasts/littlebit.rss","http://advocacy.python.org/podcasts/"
[Finished in 0.1s]

解析串

如果只是处理少量的XML文本,特别是可能嵌入在程序源代码中的字符串字面量,可以使用XML(),将包含待解析XML的字符串作为其惟一参数。

from xml.etree.ElementTree import XML

parsed = XML('''
<root>
  <group>
    <child id="a">This is child "a".</child>
    <child id="b">This is child "b".</child>
  </group>
  <group>
    <child id="c">This is child "c".</child>
  </group>
</root>
''')

print 'parsed =', parsed

def show_node(node):
    print node.tag
    if node.text is not None and node.text.strip():
        print '  text: "%s"' % node.text
    if node.tail is not None and node.tail.strip():
        print '  tail: "%s"' % node.tail
    for name, value in sorted(node.attrib.items()):
        print '  %-4s = "%s"' % (name, value)
    for child in node:
        show_node(child)
    return

for elem in parsed:
    show_node(elem)

与parse不同,这个函数的返回值是一个Element实例而不是ElementTree。Element直接支持迭代器协议,所以没有必要调用getiterator。

parsed = <Element 'root' at 0x5c6af28>
group
child
  text: "This is child "a"."
  id   = "a"
child
  text: "This is child "b"."
  id   = "b"
group
child
  text: "This is child "c"."
  id   = "c"
[Finished in 0.1s]

对于使用id属性来标识相应惟一节点的结构化XML,可以使用便利方法XMLIDO来访问解析结果。

from xml.etree.ElementTree import XMLID

tree, id_map = XMLID('''
<root>
  <group>
    <child id="a">This is child "a".</child>
    <child id="b">This is child "b".</child>
  </group>
  <group>
    <child id="c">This is child "c".</child>
  </group>
</root>
''')

for key, value in sorted(id_map.items()):
    print '%s = %s' % (key, value)

XMLIDO将解析树作为一个Element对象返回,并提供一个字典,将id属性串映射到树中的单个节点。

a = <Element 'child' at 0x4e8e0b8>
b = <Element 'child' at 0x4e8e278>
c = <Element 'child' at 0x4e8e320>
[Finished in 0.1s]

用元素节点构造文档

除解析功能外,xml.etree.ElementTree还支持由应用中构造的Element对象创建良构的XML文档。解析文档时使用的Element类还知道如何生成其内容的一个串行化形式,然后可以将这个串行化内容写至一个文件或另一个数据流。

有3个辅助函数对于创建Element节点层次结构很有用。

Element创建一个标准节点,

SubElement将一个新节点关联到一个父节点,

Comment创建一个节点,它会使用XML的注释语法串行化。

from xml.etree.ElementTree import ( Element,
                                    SubElement,
                                    Comment,
                                    tostring,
                                    )

top = Element('top')

comment = Comment('Generated for PyMOTW')
top.append(comment)

child = SubElement(top, 'child')
child.text = 'This child contains text.'

child_with_tail = SubElement(top, 'child_with_tail')
child_with_tail.text = 'This child has regular text.'
child_with_tail.tail = 'And "tail" text.'

child_with_entity_ref = SubElement(top, 'child_with_entity_ref')
child_with_entity_ref.text = 'This & that'

print tostring(top)

这个输出只包含树中的XML节点,而不包括含版本和编码的XML声明。

<top><!--Generated for PyMOTW--><child>This child contains text.</child><child_with_tail>This child has regular text.</child_with_tail>And "tail" text.<child_with_entity_ref>This &amp; that</child_with_entity_ref></top>
[Finished in 0.1s]

美观打印XML

ElementTree不会格式化tostring的输出,所以很易读,因为增加额外的空白符会改变文档的内容。为了让输出更易懂,后面的例子将使用xml.dom.minidom重新解析XML,然后使 用其toprettyxml方法。

from xml.etree.ElementTree import Element, SubElement, Comment
from ElementTree_pretty import prettify

top = Element('top')

comment = Comment('Generated for PyMOTW')
top.append(comment)

child = SubElement(top, 'child')
child.text = 'This child contains text.'

child_with_tail = SubElement(top, 'child_with_tail')
child_with_tail.text = 'This child has regular text.'
child_with_tail.tail = 'And "tail" text.'

child_with_entity_ref = SubElement(top, 'child_with_entity_ref')
child_with_entity_ref.text = 'This & that'

print prettify(top)

输出将更易读。

<?xml version="1.0" ?>
<top>
  <!--Generated for PyMOTW-->
  <child>This child contains text.</child>
  <child_with_tail>This child has regular text.</child_with_tail>
  And &quot;tail&quot; text.
  <child_with_entity_ref>This &amp; that</child_with_entity_ref>
</top>

[Finished in 0.2s]

除了添加用于格式化的额外空白符,xmLdom.minidom美观打印器还会向输出添加一个XML声明。

设置元素厲性

前面的例子都由标记和文本内容来创建节点,但是没有设置节点的任何属性。前面的很多例子都在处理一个列举播客及其提要的OPML文件。树中的outline节点使用了对应组名和播客特性的属性。可以用mementTree由一个CSV输入文件构造一个类似的XML文件,并在构造树时设置所有元素厲性。

import csv
from xml.etree.ElementTree import ( Element,
                                    SubElement,
                                    Comment,
                                    tostring,
                                    )
import datetime
from ElementTree_pretty import prettify

generated_on = str(datetime.datetime.now())

# Configure one attribute with set()
root = Element('opml')
root.set('version', '1.0')

root.append(
    Comment('Generated by ElementTree_csv_to_xml.py for PyMOTW')
    )

head = SubElement(root, 'head')
title = SubElement(head, 'title')
title.text = 'My Podcasts'
dc = SubElement(head, 'dateCreated')
dc.text = generated_on
dm = SubElement(head, 'dateModified')
dm.text = generated_on

body = SubElement(root, 'body')

with open('podcasts.csv', 'rt') as f:
    current_group = None
    reader = csv.reader(f)
    for row in reader:
        group_name, podcast_name, xml_url, html_url = row
        if current_group is None or group_name != current_group.text:
            # Start a new group
            current_group = SubElement(body, 'outline',
                                       {'text':group_name})
        # Add this podcast to the group,
        # setting all its attributes at
        # once.
        podcast = SubElement(current_group, 'outline',
                             {'text':podcast_name,
                              'xmlUrl':xml_url,
                              'htmlUrl':html_url,
                              })

print prettify(root)

这个例子使用两种技术来设置新节点的属性值。根节点用set()配置,一次修改一个属性。另外通过向节点工厂传入一个字典,对播客节点一次给定所有属性。

<?xml version="1.0" ?>
<opml version="1.0">
  <!--Generated by ElementTree_csv_to_xml.py for PyMOTW-->
  <head>
    <title>My Podcasts</title>
    <dateCreated>2018-02-23 19:42:22.829000</dateCreated>
    <dateModified>2018-02-23 19:42:22.829000</dateModified>
  </head>
  <body>
    <outline text="Books and Fiction">
      <outline htmlUrl="http://www.tor.com/" text="tor.com / category / tordotstories" xmlUrl="http://www.tor.com/rss/category/TorDotStories"/>
    </outline>
    <outline text="Python">
      <outline htmlUrl="http://advocacy.python.org/podcasts/" text="PyCon Podcast" xmlUrl="http://advocacy.python.org/podcasts/pycon.rss"/>
    </outline>
    <outline text="Python">
      <outline htmlUrl="http://advocacy.python.org/podcasts/" text="A Little Bit of Python" xmlUrl="http://advocacy.python.org/podcasts/littlebit.rss"/>
    </outline>
    <outline text="Python">
      <outline htmlUrl="" text="Django Dose Everything Feed" xmlUrl="http://djangodose.com/everything/feed/"/>
    </outline>
  </body>
</opml>

[Finished in 0.1s]

由节点列表构造树

利用extend方法可以将多个子节点一同添加到一个Element实例。extend的参数可以是任意可迭代对象,包括list或另一个Element实例。

from xml.etree.ElementTree import Element, tostring
from ElementTree_pretty import prettify

top = Element('top')

children = [
    Element('child', num=str(i))
    for i in xrange(3)
    ]

top.extend(children)

print prettify(top)

给定一个list时,列表中的节点会直接添加到新的父节点中。

<?xml version="1.0" ?>
<top>
  <child num="0"/>
  <child num="1"/>
  <child num="2"/>
</top>

[Finished in 0.1s]

给定另一个Element实例时,该节点的子节点会添加到新的父节点中。

from xml.etree.ElementTree import Element, SubElement, tostring, XML
from ElementTree_pretty import prettify

top = Element('top')

parent = SubElement(top, 'parent')

children = XML(
   '<root><child num="0" /><child num="1" /><child num="2" /></root>'
   )
parent.extend(children)

print prettify(top)

在这个例子中,通过解折XML串创建的root节点有3个子节点,它们都添加到parent节点中。root节点不是输出树的一部分,

<?xml version="1.0" ?>
<top>
  <parent>
    <child num="0"/>
    <child num="1"/>
    <child num="2"/>
  </parent>
</top>

[Finished in 0.1s]

要了解的重要一点是,extend并不改变节点现有的父子关系。如果传入extend的值已经存在于树中的某个位置,它们将仍在原处,并在输出中重复。

from xml.etree.ElementTree import Element, SubElement, tostring, XML
from ElementTree_pretty import prettify

top = Element('top')

parent_a = SubElement(top, 'parent', id='A')
parent_b = SubElement(top, 'parent', id='B')

# Create children
children = XML(
   '<root><child num="0" /><child num="1" /><child num="2" /></root>'
   )

# Set the id to the Python object id of the node
# to make duplicates easier to spot.
for c in children:
    c.set('id', str(id(c)))

# Add to first parent
parent_a.extend(children)

print 'A:'
print prettify(top)
print

# Copy nodes to second parent
parent_b.extend(children)

print 'B:'
print prettify(top)
print

这里将子节点的id属性设置为Python惟一对象标识符,由此强调同样的节点对象可以在输出树中出现多次。

A:
<?xml version="1.0" ?>
<top>
  <parent id="A">
    <child id="82789264" num="0"/>
    <child id="82789040" num="1"/>
    <child id="82789600" num="2"/>
  </parent>
  <parent id="B"/>
</top>


B:
<?xml version="1.0" ?>
<top>
  <parent id="A">
    <child id="82789264" num="0"/>
    <child id="82789040" num="1"/>
    <child id="82789600" num="2"/>
  </parent>
  <parent id="B">
    <child id="82789264" num="0"/>
    <child id="82789040" num="1"/>
    <child id="82789600" num="2"/>
  </parent>
</top>


[Finished in 0.1s]

将XML串行化至一个流

tostring实现为将内容写至一个类文件的内存中对象,然后返回一个表示整个元素树的串。处理大量数据时,这种做法需要的内存较少,而且可以更髙效地利用I/O库,使用ElementTree的write()方法直接写至一个文件句柄。

import sys
from xml.etree.ElementTree import ( Element,
                                    SubElement,
                                    Comment,
                                    ElementTree,
                                    )

top = Element('top')

comment = Comment('Generated for PyMOTW')
top.append(comment)

child = SubElement(top, 'child')
child.text = 'This child contains text.'

child_with_tail = SubElement(top, 'child_with_tail')
child_with_tail.text = 'This child has regular text.'
child_with_tail.tail = 'And "tail" text.'

child_with_entity_ref = SubElement(top, 'child_with_entity_ref')
child_with_entity_ref.text = 'This & that'

empty_child = SubElement(top, 'empty_child')

ElementTree(top).write(sys.stdout)

这个例子使用sys.stdout写至控制台,不过也可以写至一个打开的文件或套接字。

<top><!--Generated for PyMOTW--><child>This child contains text.</child><child_with_tail>This child has regular text.</child_with_tail>And "tail" text.<child_with_entity_ref>This &amp; that</child_with_entity_ref><empty_child /></top>[Finished in 0.1s]

树中最后一个节点不包含文本或子节点,所以它写为一个空标记<empty_child />,write()有一个方法(method)参数,用来控制空节点的处理。

import sys
from xml.etree.ElementTree import Element, SubElement, ElementTree

top = Element('top')

child = SubElement(top, 'child')
child.text = 'Contains text.'

empty_child = SubElement(top, 'empty_child')

for method in [ 'xml', 'html', 'text' ]:
    print method
    ElementTree(top).write(sys.stdout, method=method)
    print '\n'

这里支持3个方法。

xml 默认方法,生成 <empty_child />,

html生成标记对,HTML文档要求必须采用这种方法(<empty_childx/empty_child>),

text只打印节点的文本,完全跳过空标记。

xml
<top><child>Contains text.</child><empty_child /></top>

html
<top><child>Contains text.</child><empty_child></empty_child></top>

text
Contains text.

[Finished in 0.1s]

以上是关于Python STL xml的主要内容,如果未能解决你的问题,请参考以下文章

xml Eclipse模板(代码片段)检查参数并最终抛出IllegalArgumentException

需要示例代码片段帮助

比给定值最小的最大元素的 STL 算法

从 XML 声明片段获取 XML 编码:部分内容解析不支持 XmlDeclaration

使用非utf-8编码在Python中解析XML

STL容器自定义内存分配器