XML 文档形成了一种树结构,它从"根部"开始,然后扩展到"枝叶"。
一个 XML 文档实例
XML 文档使用简单的具有自我描述性的语法:
第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码(UTF-8 : 万国码, 可显示各种语言)。
下一行描述文档的根元素(像在说:"本文档是一个便签"):
<note>
接下来 4 行描述根的 4 个子元素(to, from, heading 以及 body):
最后一行定义根元素的结尾:
</note>
您可以假设,从这个实例中,XML 文档包含了一张 Jani 写给 Tove 的便签。
XML 具有出色的自我描述性,您同意吗?
XML 文档形成一种树结构
XML 文档必须包含根元素。该元素是所有其他元素的父元素。
XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端。
所有的元素都可以有子元素:
父、子以及同胞等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素成为同胞(兄弟或姐妹)。
所有的元素都可以有文本内容和属性(类似 HTML 中)。
实例:
上图表示下面的 XML 中的一本书:
XML 文档实例
实例中的根元素是 <bookstore>。文档中的所有 <book> 元素都被包含在 <bookstore> 中。
<book> 元素有 4 个子元素:<title>、<author>、<year>、<price>。
XML 语法规则
XML 的语法规则很简单,且很有逻辑。这些规则很容易学习,也很容易使用。
XML 文档必须有根元素
XML 必须包含根元素,它是所有其他元素的父元素,比如以下实例中 root 就是根元素:
以下实例中 note 是根元素:
XML 声明
XML 声明文件的可选部分,如果存在需要放在文档的第一行,如下所示:
<?xml version="1.0" encoding="utf-8"?>
以上实例包含 XML 版本(<version="1.0"),甚至包含字符编码(encoding="utf-8")。< p="">
UTF-8 也是 HTML5, CSS, JavaScript, PHP, 和 SQL 的默认编码。
所有的 XML 元素都必须有一个关闭标签
在 HTML 中,某些元素不必有一个关闭标签:
<p>This is a paragraph.
<br>
在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签:
<p>This is a paragraph.</p>
<br />
注释:从上面的实例中,您也许已经注意到 XML 声明没有关闭标签。这不是错误。声明不是 XML 文档本身的一部分,它没有关闭标签。
XML 标签对大小写敏感
XML 标签对大小写敏感。标签 <Letter> 与标签 <letter> 是不同的。
必须使用相同的大小写来编写打开标签和关闭标签:
<Message>这是错误的</message>
<message>这是正确的</message>
注释:打开标签和关闭标签通常被称为开始标签和结束标签。不论您喜欢哪种术语,它们的概念都是相同的。
XML 必须正确嵌套
在 HTML 中,常会看到没有正确嵌套的元素:
<b><i>This text is bold and italic</b></i>
在 XML 中,所有元素都必须彼此正确地嵌套:
<b><i>This text is bold and italic</i></b>
在上面的实例中,正确嵌套的意思是:由于 <i> 元素是在 <b> 元素内打开的,那么它必须在 <b> 元素内关闭。
XML 属性值必须加引号
与 HTML 类似,XML 元素也可拥有属性(名称/值的对)。
在 XML 中,XML 的属性值必须加引号。
请研究下面的两个 XML 文档。 第一个是错误的,第二个是正确的:
<note date=12/11/2007>
<to>Tove</to>
<from>Jani</from>
</note>
<note date="12/11/2007">
<to>Tove</to>
<from>Jani</from>
</note>
在第一个文档中的错误是,note 元素中的 date 属性没有加引号。
实体引用
在 XML 中,一些字符拥有特殊的意义。
如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。
这样会产生 XML 错误:
<message>if salary < 1000 then</message>
为了避免这个错误,请用实体引用来代替 "<" 字符:
<message>if salary < 1000 then</message>
在 XML 中,有 5 个预定义的实体引用:
< | < | less than |
> | > | greater than |
& | & | ampersand |
' | ' | apostrophe |
" | " | quotation mark |
注释:在 XML 中,只有字符 "<" 和 "&" 确实是非法的。大于号是合法的,但是用实体引用来代替它是一个好习惯。
XML 中的注释
在 XML 中编写注释的语法与 HTML 的语法很相似。
<!-- This is a comment -->
在 XML 中,空格会被保留
HTML 会把多个连续的空格字符裁减(合并)为一个:
HTML: | Hello Tove |
Output: | Hello Tove |
在 XML 中,文档中的空格不会被删减。
XML 以 LF 存储换行
在 Windows 应用程序中,换行通常以一对字符来存储:回车符(CR)和换行符(LF)。
在 Unix 和 Mac OSX 中,使用 LF 来存储新行。
在旧的 Mac 系统中,使用 CR 来存储新行。
XML 以 LF 存储换行。
XML 元素
XML 文档包含 XML 元素。
什么是 XML 元素?
XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。
一个元素可以包含:
- 其他元素
- 文本
- 属性
- 或混合以上所有...
在上面的实例中,<bookstore> 和 <book> 都有 元素内容,因为他们包含其他元素。<book> 元素也有属性(category="CHILDREN")。<title>、<author>、<year> 和 <price> 有文本内容,因为他们包含文本。
XML 命名规则
XML 元素必须遵循以下命名规则:
- 名称可以包含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字母 xml(或者 XML、Xml 等等)开始
- 名称不能包含空格
可使用任何名称,没有保留的字词。
最佳命名习惯
使名称具有描述性。使用下划线的名称也很不错:<first_name>、<last_name>。
名称应简短和简单,比如:<book_title>,而不是:<the_title_of_the_book>。
避免 "-" 字符。如果您按照这样的方式进行命名:"first-name",一些软件会认为您想要从 first 里边减去 name。
避免 "." 字符。如果您按照这样的方式进行命名:"first.name",一些软件会认为 "name" 是对象 "first" 的属性。
避免 ":" 字符。冒号会被转换为命名空间来使用(稍后介绍)。
XML 文档经常有一个对应的数据库,其中的字段会对应 XML 文档中的元素。有一个实用的经验,即使用数据库的命名规则来命名 XML 文档中的元素。
在 XML 中,éòá 等非英语字母是完全合法的,不过需要留意,您的软件供应商不支持这些字符时可能出现的问题。
XML 元素是可扩展的
XML 元素是可扩展,以携带更多的信息。
请看下面的 XML 实例:
让我们设想一下,我们创建了一个应用程序,可将 <to>、<from> 以及 <body> 元素从 XML 文档中提取出来,并产生以下的输出:
MESSAGE
To: Tove Don't forget me this weekend! |
想象一下,XML 文档的作者添加的一些额外信息:
那么这个应用程序会中断或崩溃吗?
不会。这个应用程序仍然可以找到 XML 文档中的 <to>、<from> 以及 <body> 元素,并产生同样的输出。
XML 的优势之一,就是可以在不中断应用程序的情况下进行扩展。
XML 属性
XML元素具有属性,类似 HTML。
属性(Attribute)提供有关元素的额外信息。
XML 属性
在 HTML 中,属性提供有关元素的额外信息:
<a href="demo.html">
属性通常提供不属于数据组成部分的信息。在下面的实例中,文件类型与数据无关,但是对需要处理这个元素的软件来说却很重要:
XML 属性必须加引号
属性值必须被引号包围,不过单引号和双引号均可使用。比如一个人的性别,person 元素可以这样写:
或者这样也可以:
如果属性值本身包含双引号,您可以使用单引号,就像这个实例:
或者您可以使用字符实体:
XML 元素 vs. 属性
请看这些实例:
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>
<sex>female</sex>
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>
在第一个实例中,sex 是一个属性。在第二个实例中,sex 是一个元素。这两个实例都提供相同的信息。
没有什么规矩可以告诉我们什么时候该使用属性,而什么时候该使用元素。我的经验是在 HTML 中,属性用起来很便利,但是在 XML 中,您应该尽量避免使用属性。如果信息感觉起来很像数据,那么请使用元素吧。
我最喜欢的方式
下面的三个 XML 文档包含完全相同的信息:
第一个实例中使用了 date 属性:
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
第二个实例中使用了 date 元素:
<date>10/01/2008</date>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
第三个实例中使用了扩展的 date 元素(这是我的最爱):
<date>
<day>10</day>
<month>01</month>
<year>2008</year>
</date>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
避免 XML 属性?
因使用属性而引起的一些问题:
- 属性不能包含多个值(元素可以)
- 属性不能包含树结构(元素可以)
- 属性不容易扩展(为未来的变化)
属性难以阅读和维护。请尽量使用元素来描述数据。而仅仅使用属性来提供与数据无关的信息。
不要做这样的蠢事(这不是 XML 应该被使用的方式):
to="Tove" from="Jani" heading="Reminder"
body="Don't forget me this weekend!">
</note>
针对元数据的 XML 属性
有时候会向元素分配 ID 引用。这些 ID 索引可用于标识 XML 元素,它起作用的方式与 HTML 中 id 属性是一样的。这个实例向我们演示了这种情况:
<note id="501">
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
<note id="502">
<to>Jani</to>
<from>Tove</from>
<heading>Re: Reminder</heading>
<body>I will not</body>
</note>
</messages>
上面的 id 属性仅仅是一个标识符,用于标识不同的便签。它并不是便签数据的组成部分。
在此我们极力向您传递的理念是:元数据(有关数据的数据)应当存储为属性,而数据本身应当存储为元素。
XML 验证
拥有正确语法的 XML 被称为"形式良好"的 XML。
通过 DTD 验证的XML是"合法"的 XML。
形式良好的 XML 文档
"形式良好"的 XML 文档拥有正确的语法。
在前面的章节描述的语法规则:
- XML 文档必须有一个根元素
- XML元素都必须有一个关闭标签
- XML 标签对大小写敏感
- XML 元素必须被正确的嵌套
- XML 属性值必须加引号
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
验证 XML 文档
合法的 XML 文档是"形式良好"的 XML 文档,这也符合文档类型定义(DTD)的规则:
<!DOCTYPE note SYSTEM "Note.dtd">
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
在上面的实例中,DOCTYPE 声明是对外部 DTD 文件的引用。下面的段落展示了这个文件的内容。
XML DTD
DTD 的目的是定义 XML 文档的结构。它使用一系列合法的元素来定义文档结构:
[
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
如果您想要学习 DTD,请在我们的首页查找 DTD 教程。
XML Schema
W3C 支持一种基于 XML 的 DTD 代替者,它名为 XML Schema:
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
http://www.runoob.com/xml/xml-tutorial.html
使用xml.dom解析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
用 ElementTree 在 Python 中解析 XML
原文: http://eli.thegreenplace.net/2012/03/15/processing-xml-in-python-with-elementtree/
译者: TheLover_Z
当你需要解析和处理 XML 的时候,Python 表现出了它 “batteries included” 的一面。 标准库 中大量可用的模块和工具足以应对 Python 或者是 XML 的新手。
几个月前在 Python 核心开发者之间发生了一场 有趣的讨论 ,他们讨论了 Python 下可用的 XML 处理工具的优点,还有如何将它们最好的展示给用户看。这篇文章是我本人的拙作,我打算讲讲哪些工具比较好用还有为什么它们好用,当然,这篇文章也可以当作一个如何使用的基础教程来看。
这篇文章所使用的代码基于 Python 2.7,你稍微改动一下就可以在 Python 3.x 上面使用了。
应该使用哪个 XML 库?
Python 有非常非常多的工具来处理 XML。在这个部分我想对 Python 所提供的包进行一个简单的浏览,并且解释为什么 ElementTree
是你最应该用的那一个。
xml.dom.*
模块 - 是 W3C DOM API 的实现。如果你有处理 DOM API 的需要,那么这个模块适合你。注意:在 xml.dom 包里面有许多模块,注意它们之间的不同。
xml.sax.*
模块 - 是 SAX API 的实现。这个模块牺牲了便捷性来换取速度和内存占用。SAX 是一个基于事件的 API,这就意味着它可以“在空中”(on the fly)处理庞大数量的的文档,不用完全加载进内存(见注释1)。
xml.parser.expat
- 是一个直接的,低级一点的基于 C 的 expat
的语法分析器(见注释2)。 expat
接口基于事件反馈,有点像 SAX 但又不太像,因为它的接口并不是完全规范于 expat
库的。
最后,我们来看看 xml.etree.ElementTree
(以下简称 ET)。它提供了轻量级的 Python 式的 API ,它由一个 C 实现来提供。相对于 DOM 来说,ET 快了很多(见注释3)而且有很多令人愉悦的 API 可以使用。相对于 SAX 来说,ET 也有 ET.iterparse
提供了 “在空中” 的处理方式,没有必要加载整个文档到内存。ET 的性能的平均值和 SAX 差不多,但是 API 的效率更高一点而且使用起来很方便。我一会儿会给你们看演示。
我的建议 是尽可能的使用 ET 来处理 XML ,除非你有什么非常特别的需要。
ElementTree - 一个 API ,两种实现
ElementTree
生来就是为了处理 XML ,它在 Python 标准库中有两种实现。一种是纯 Python 实现例如 xml.etree.ElementTree
,另外一种是速度快一点的 xml.etree.cElementTree
。你要记住: 尽量使用 C 语言实现的那种,因为它速度更快,而且消耗的内存更少。如果你的电脑上没有 _elementtree
(见注释4) 那么你需要这样做:
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
这是一个让 Python 不同的库使用相同 API 的一个比较常用的办法。还是那句话,你的编译环境和别人的很可能不一样,所以这样做可以防止一些莫名其妙的小问题。注意:从 Python 3.3 开始,你没有必要这么做了,因为 ElementTree
模块会自动寻找可用的 C 库来加快速度。所以只需要 import xml.etree.ElementTree
就可以了。但是在 3.3 正式推出之前,你最好还是使用我上面提供的那段代码。
将 XML 解析为树的形式
我们来讲点基础的。XML 是一种分级的数据形式,所以最自然的表示方法是将它表示为一棵树。ET 有两个对象来实现这个目的 - ElementTree
将整个 XML 解析为一棵树, Element
将单个结点解析为树。如果是整个文档级别的操作(比如说读,写,找到一些有趣的元素)通常用 ElementTree
。单个 XML 元素和它的子元素通常用 Element
。下面的例子能说明我刚才啰嗦的一大堆。(见注释5)
我们用这个 XML 文件来做例子:
<?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>
让我们加载并且解析这个 XML :
>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')
然后抓根结点元素:
>>> tree.getroot()
<Element 'doc' at 0x11eb780>
和预期一样,root 是一个 Element
元素。我们可以来看看:
>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})
看吧,根元素没有任何状态(见注释6)。就像任何 Element
一样,它可以找到自己的子结点:
>>> for child_of_root in root:
... print child_of_root.tag, child_of_root.attrib
...
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
我们也可以进入一个指定的子结点:
>>> root[0].tag, root[0].text
('branch', '
text,source
')
找到我们感兴趣的元素
从上面的例子我们可以轻而易举的看到,我们可以用一个简单的递归获取 XML 中的任何元素。然而,因为这个操作比较普遍,ET 提供了一些有用的工具来简化操作.
Element
对象有一个 iter
方法可以对子结点进行深度优先遍历。 ElementTree
对象也有 iter
方法来提供便利。
>>> for elem in tree.iter():
... print elem.tag, elem.attrib
...
doc {}
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}
遍历所有的元素,然后检验有没有你想要的。ET 可以让这个过程更便捷。 iter
方法接受一个标签名字,然后只遍历那些有指定标签的元素:
>>> for elem in tree.iter(tag='branch'):
... print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
来自 XPath 的帮助
为了寻找我们感兴趣的元素,一个更加有效的办法是使用 XPath 支持。 Element
有一些关于寻找的方法可以接受 XPath 作为参数。 find
返回第一个匹配的子元素, findall
以列表的形式返回所有匹配的子元素, iterfind
为所有匹配项提供迭代器。这些方法在 ElementTree
里面也有。
给出一个例子:
>>> for elem in tree.iterfind('branch/sub-branch'):
... print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}
这个例子在 branch
下面找到所有标签为 sub-branch
的元素。然后给出如何找到所有的 branch
元素,用一个指定 name
的状态即可:
>>> for elem in tree.iterfind('branch[@name="release01"]'):
... print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}
想要深入学习 XPath 的话,请看 这里 。
建立 XML 文档
ET 提供了建立 XML 文档和写入文件的便捷方式。 ElementTree
对象提供了 write
方法。
现在,这儿有两个常用的写 XML 文档的脚本。
修改文档可以使用 Element
对象的方法:
>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
... print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
我们在这里删除了根元素的第三个子结点,然后为第一个子结点增加新状态。然后这个树可以写回到文件中。
>>> import sys
>>> tree.write(sys.stdout) # ET.dump can also serve this purpose
<doc>
<branch foo="bar" hash="1cdf045c" name="testing">
text,source
</branch>
<branch hash="f200013e" name="release01">
<sub-branch name="subrelease01">
xml,sgml
</sub-branch>
</branch>
</doc>
注意状态的顺序和原文档的顺序不太一样。这是因为 ET 讲状态保存在无序的字典中。语义上来说,XML 并不关心顺序。
建立一个全新的元素也很容易。ET 模块提供了 SubElement
函数来简化过程:
>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>
使用 iterparse 来处理 XML 流
就像我在文章一开头提到的那样,XML 文档通常比较大,所以将它们全部读入内存的库可能会有点儿小问题。这也是为什么我建议使用 SAX API 来替代 DOM 。
我们刚讲过如何使用 ET 来将 XML 读入内存并且处理。但它就不会碰到和 DOM 一样的内存问题么?当然会。这也是为什么这个包提供一个特殊的工具,用来处理大型文档,并且解决了内存问题,这个工具叫 iterparse
。
我给大家演示一个 iterparse
如何使用的例子。我用 自动生成 拿到了一个 XML 文档来进行说明。这只是开头的一小部分:
<?xml version="1.0" standalone="yes"?>
<site>
<regions>
<africa>
<item id="item0">
<location>United States</location> <!-- Counting locations -->
<quantity>1</quantity>
<name>duteous nine eighteen </name>
<payment>Creditcard</payment>
<description>
<parlist>
[...]
我已经用注释标出了我要处理的元素,我们用一个简单的脚本来计数有多少 location
元素并且文本内容为“Zimbabwe”。这是用 ET.parse
的一个标准的写法:
tree = ET.parse(sys.argv[2])
count = 0
for elem in tree.iter(tag='location'):
if elem.text == 'Zimbabwe':
count += 1
print count
所有 XML 树中的元素都会被检验。当处理一个大约 100MB 的 XML 文件时,占用的内存大约是 560MB ,耗时 2.9 秒。
注意:我们并不需要在内存中加载整颗树。它检测我们需要的带特定值的 location
元素。其他元素被丢弃。这是 iterparse
的来源:
count = 0
for event, elem in ET.iterparse(sys.argv[2]):
if event == 'end':
if elem.tag == 'location' and elem.text == 'Zimbabwe':
count += 1
elem.clear() # discard the element
print count
这个循环遍历 iterparse
事件,检测“闭合的”(end)事件并且寻找 location
标签和指定的值。在这里 elem.clear()
是关键 - iterparse
仍然建立一棵树,只不过不需要全部加载进内存,这样做可以有效的利用内存空间(见注释7)。
处理同样的文件,这个脚本占用内存只需要仅仅的 7MB ,耗时 2.5 秒。速度的提升归功于生成树的时候只遍历一次。相比较来说, parse
方法首先建立了整个树,然后再次遍历来寻找我们需要的元素(所以慢了一点)。
结论
在 Python 众多处理 XML 的模块中, ElementTree
真是屌爆了。它将轻量,符合 Python 哲学的 API ,出色的性能完美的结合在了一起。所以说如果要处理 XML ,果断地使用它吧!
这篇文章简略地谈了谈 ET 。我希望这篇拙作可以抛砖引玉。
注释
注释1:和 DOM 不一样,DOM 将整个 XML 加载进内存并且允许随机访问任何深度地元素。
注释2: expat 是一个开源的用于处理 XML 的 C 语言库。Python 将它融合进自身。
注释3:Fredrik Lundh,是 ElementTree 的原作者,他提到了一些 基准 。
注释4:当我提到 _elementtree
的时候,我意思是 C 语言的 cElementTree._elementtree
扩展模块。
注释5:确定你手边有 模块手册 然后可以随时查阅我提到的方法和函数。
注释6: 状态 是一个意义太多的术语。Python 对象有状态,XML 元素也有状态。希望我能将它们表达的更清楚一点。
注释7:准确来说,树的根元素仍然存活。在某些情况下根结点非常大,你也可以丢弃它,但那需要多一点点代码。
Python基于DOM的XML编程接口
Python自带支持XML DOM的模块:xml.dom和xml.dom.minidom。
http://blog.sina.com.cn/s/blog_da4487c40102v3jx.html
使用minidom来处理XML的示例(Python 学习)(转载)
一.XML的读取.
在 NewEdit 中有代码片段的功能,代码片段分为片段的分类和片段的内容。在缺省情况下都是用XML格式保存的。下面我讲述一下,如何使用minidom来读取和保存XML文件。
下面是片段分类的一个示例文件--catalog.xml
<?xml version="1.0" encoding="utf-8"?>
<catalog>
<maxid>4</maxid>
<item id="1">
<caption>Python</caption>
<item id="4">
<caption>测试</caption>
</item>
</item>
<item id="2">
<caption>Zope</caption>
</item>
</catalog>
分类是树状结构,显示出来可能为:
先简单介绍一下XML的知识,如果你已经知道了可以跳过去。
1. XML文档的编码
此XML文档的编码为utf-8,因此你看到的“测试”其实是UTF-8编码。在XML文档的处理中都是使用UTF-8编码进行的,因此,如果你不写明encoding的话,都是认为文件是UTF-8编码的。在Python中,好象只支持几种编码,象我们常用的GB2312码就不支持,因此建议大家在处理XML时使用UTF-8编码。
2. XML文档的结构
XML文档有XML头信息和XML信息体。头信息如:
<?xml version="1.0" encoding="utf-8"?>
它表明了此XML文档所用的版本,编码方式。有些复杂的还有一些文档类型的定义(DOCTYPE),用于定义此XML文档所用的DTD或Schema和一些实体的定义。这里并没有用到,而且我也不是专家,就不再细说了。
XML信息体是由树状元素组成。每个XML文档都有一个文档元素,也就是树的根元素,所有其它的元素和内容都包含在根元素中。
3. DOM
DOM是Document Object Model的简称,它是以对象树来表示一个XML文档的方法,使用它的好处就是你可以非常灵活的在对象中进行遍历。
4. 元素和结点
元素就是标记,它是成对出现的。XML文档就是由元素组成的,但元素与元素之间可以有文本,元素的内容也是文本。在minidom中有许多的结点,元素也属于结点的一种,它不是叶子结点,即它存在子结点;还存在一些叶子结点,如文本结点,它下面不再有子结点。
象catalog.xml中,文档元素是catalog,它下面有两种元素:maxid和item。maxid用来表示当前最大的item的id值。每一个item都有一个id属性,id属性是唯一的,在 NewEdit 中用来生成每个分类所对应的代码片段的XML文档名,因此不能重复,而且它是一个递增的值。item元素有一个caption子元素,用来表示此分类项的名称,它还可以包含item元素。这样,就定义了一个树状XML结构,下面让我们看一看如果把它们读出来。
一、得到dom对象
>>> import xml.dom.minidom
>>> dom = xml.dom.minidom.parse('d:/catalog.xml')
这样我们得到了一个dom对象,它的第一个元素应该是catalog。
二、得到文档元素对象
>>> root = dom.documentElement
这样我们得到了根元素(catalog)。
三、结点属性
每一个结点都有它的nodeName,nodeValue,nodeType属性。nodeName为结点名字。
>>> root.nodeName
u'catalog'
nodeValue是结点的值,只对文本结点有效。nodeType是结点的类型,现在有以下几种:
'ATTRIBUTE_NODE'
'CDATA_SECTION_NODE'
'COMMENT_NODE'
'DOCUMENT_FRAGMENT_NODE'
'DOCUMENT_NODE'
'DOCUMENT_TYPE_NODE'
'ELEMENT_NODE'
'ENTITY_NODE'
'ENTITY_REFERENCE_NODE'
'NOTATION_NODE'
'PROCESSING_INSTRUCTION_NODE'
'TEXT_NODE'
这些结点通过名字很好理解。catalog是ELEMENT_NODE类型。
>>> root.nodeType
1
>>> root.ELEMENT_NODE
1
四、子元素、子结点的访问
访问子元素、子结点的方法很多,对于知道元素名字的子元素,可以使用getElementsByTagName方法,如读取maxid子元素:
>>> root.getElementsByTagName('maxid')
[<DOM Element: maxid at 0xb6d0a8>]
这样返回一个列表,由于我们的例子中maxid只有一项,因此列表也只有一项。
如果想得到某个元素下的所有子结点(包括元素),可以使用childNodes属性:
>>> root.childNodes
[<DOM Text node " ">, <DOM Element: maxid at 0xb6d0a8>, <DOM Text node " ">, <DOM Element: item at 0xb6d918>, <DOM Text node " ">, <DOM Element: item at 0xb6de40>, <DOM Text node " ">, <DOM Element: item at 0xb6dfa8>, <DOM Text node " ">]
可以看出所有两个标记间的内容都被视为文本结点。象每行后面的回车,都被看到文本结点。从上面的结果我们可以看出每个结点的类型,本例中有文本结点和元素结点;结点的名字(元素结点);结点的值(文本结点)。每个结点都是一个对象,不同的结点对象有不同的属性和方法,更详细的要参见文档。由于本例比较简单,只涉及文本结点和元素结点。
getElementsByTagName可以搜索当前元素的所有子元素,包括所有层次的子元素。childNodes只保存了当前元素的第一层子结点。
这样我们可以遍历childNodes来访问每一个结点,判断它的nodeType来得到不同的内容。如,打印出所有元素的名字:
>>> for node in root.childNodes:
if node.nodeType == node.ELEMENT_NODE:
print node.nodeName
maxid
item
item
对于文本结点,想得到它的文本内容可以使用: .data属性。
对于简单的元素,如:<caption>Python</caption>,我们可以编写这样一个函数来得到它的内容(这里为Python)。
def getTagText(root, tag):
node = root.getElementsByTagName(tag)[0]
rc = ""
for node in node.childNodes:
if node.nodeType in ( node.TEXT_NODE, node.CDATA_SECTION_NODE):
rc = rc + node.data
return rc
这个函数只处理找到的第一个符合的子元素。它会将符合的第一个子元素中的所有文本结点拼在一起。当nodeType为文本类结点时,node.data为文本的内容。如果我们考查一下元素caption,我们可能看到:
[<DOM Text node "Python">]
说明caption元素只有一个文本结点。
如果一个元素有属性,那么可以使用getAttribute方法,如:
>>> itemlist = root.getElementsByTagName('item')
>>> item = itemlist[0]
>>> item.getAttribute('id')
u'1'
这样就得到了第一个item元素的属性值。
下面让我们简单地小结一下如何使用minidom来读取XML中的信息
1. 导入xml.dom.minidom模块,生成dom对象
2. 得到文档对象(根对象)
3. 通过getElementsByTagName()方法和childNodes属性(还有其它一些方法和属性)找到要处理的元素
4. 取得元素下文本结点的内容
二.写入.
下面我来演示一下如何从无到有生成象catalog.xml一样的XML文件。
一、生成dom对象
>>> import xml.dom.minidom
>>> impl = xml.dom.minidom.getDOMImplementation()
>>> dom = impl.createDocument(None, 'catalog', None)
这样就生成了一个空的dom对象。其中catalog为文档元素名,即根元素名。
二、显示生成的XML内容
每一个dom结点对象(包括dom对象本身)都有输出XML内容的方法,如:toxml(), toprettyxml()
toxml()输出紧凑格式的XML文本,如:
<catalog><item>test</item><item>test</item></catalog>
toprettyxml()输出美化后的XML文本,如:
<catalog>
<item>
test
</item>
<item>
test
</item>
</catalog>
可以看出,它是将每个结点后面都加入了回车符,并且自动处理缩近。但对于每一个元素,如果元素只有文本内容,则我希望元素的tag与文本是在一起的,如:
<item>test</item>
而不想是分开的格式,但minidom本身是不支持这样的处理。关于如何实现形如:
<catalog>
<item>test</item>
<item>test</item>
</catalog>
这样的XML格式,后面我们再说。
三、生成各种结点对象
dom对象拥有各种生成结点的方法,下面列出文本结点,CDATA结点和元素结点的生成过程。
1. 文本结点的生成
>>> text=dom.createTextNode('test')
test
要注意的是,在生成结点时,minidom并不对文本字符进行检查,象文本中如果出现了'<','&'之类的字符,应该转换为相应的实体符号'<','&'才可以,这里没有做这个处理。
2. CDATA结点的生成
>>> data = dom.createCDATASection('aaaaaa bbbbbb')
>>> data.toxml()
'<![CDATA[aaaaaa bbbbbb]]>'
CDATA是用于包括大块文本,同时可以不用转换'<','&'字符的标记,它是用<![CDATA[文本]]>来包括的。但文本中不可以有"]]>"这样的串存在。生成结点时minidom不作这些检查,只有当你输出时才有可能发现有错。
3. 元素结点的生成
>>> item = dom.createElement('caption')
>>> item.toxml()
'<caption/>'
对于象元素这样的结点,生成的元素结点其实是一个空元素,即不包含任何文本,如果要包含文本或其它的元素,我们需要使用appendChild()或insertBefore()之类的方法将子结点加就到元素结点中。如将上面生成的text结点加入到caption元素结点中:
>>> item.appendChild(text)
<DOM Text node "test">
>>> item.toxml()
'<caption>test</caption>'
使用元素对象的setAttribute()方法可以向元素中加入属性,如:
>>> item.setAttribute('id', 'idvalue')
>>> item.toxml()
'<caption id="idvalue">test</caption>'
四、生成dom对象树
我们有了dom对象,又知道了如何生成各种结点,包括叶子结点(不包含其它结点的结点,如文本结点)和非叶子结点(包含其它结点的结点,如元素结点)的生成,然后就需要利用结点对象本身的appendChild()或insertBefore()方法将各个结点根据在树中的位置连起来,串成一棵树。最后要串到文档结点上,即根结点上。如一个完整的示例为:
>>> import xml.dom.minidom
>>> impl = xml.dom.minidom.getDOMImplementation()
>>> dom = impl.createDocument(None, 'catalog', None)
>>> root = dom.documentElement
>>> item = dom.createElement('item')
>>> text = dom.createTextNode('test')
>>> item.appendChild(text)
<DOM Text node "test">
>>> root.appendChild(item)
<DOM Element: item at 0xb9cf80>
>>> print root.toxml()
<catalog><item>test</item></catalog>
五、简单生成元素结点的函数
下面是我写的一个小函数,用于简单的生成类似于:
<caption>test</caption>
或形如:
<item><![CDATA[test]]></item>
的元素结点
1 def makeEasyTag(dom, tagname, value, type='text'):
2 tag = dom.createElement(tagname)
3 if value.find(']]>') > -1:
4 type = 'text'
5 if type == 'text':
6 value = value.replace('&', '&')
7 value = value.replace('<', '<')
8 text = dom.createTextNode(value)
9 elif type == 'cdata':
10 text = dom.createCDATASection(value)
11 tag.appendChild(text)
12 return tag
参数说明:
- dom为dom对象
- tagname为要生成元素的名字,如'item'
- value为其文本内容,可以为多行
- type为文本结点的格式,'text'为一般Text结点,'cdata'为CDATA结点
函数处理说明:
- 首先创建元素结点
- 查找文本内容是否有']]>',如果找到,则此文本结点只可以是Text结点
- 如果结点类型为'text',则对文本内容中的'<'替换为'<','&'替换为'&',再生成文本结点
- 如果结点类型为'cdata',则生成CDATA结点
- 将生成的文本结点追加到元素结点上
因此这个小函数可以自动地处理字符转化及避免CDATA结点中出现']]>'串。
上面生成'item'结点的语句可以改为:
>>> item = makeEasyTag(dom, 'item', 'test')
>>> item.toxml()
'<item>test</item>'
六、写入到XML文件中
dom对象树已经生成好了,我们可以调用dom的writexml()方法来将内容写入文件中。writexml()方法语法格式为:
writexml(writer, indent, addindent, newl, encoding)
- writer是文件对象
- indent是每个tag前填充的字符,如:' ',则表示每个tag前有两个空格
- addindent是每个子结点的缩近字符
- newl是每个tag后填充的字符,如:' ',则表示每个tag后面有一个回车
- encoding是生成的XML信息头中的encoding属性值,在输出时minidom并不真正进行编码的处理,如果你保存的文本内容中有汉字,则需要自已进行编码转换。
writexml方法是除了writer参数必须要有外,其余可以省略。下面给出一个文本内容有汉字的示例:
1 >>> import xml.dom.minidom
2 >>> impl = xml.dom.minidom.getDOMImplementation()
3 >>> dom = impl.createDocument(None, 'catalog', None)
4 >>> root = dom.documentElement
5 >>> text = unicode('汉字示例', 'cp936')
6 >>> item = makeEasyTag(dom, 'item', text)
7 >>> root.appendChild(item)
8 <DOM Element: item at 0xb9ceb8>
9 >>> root.toxml()
10 u'<catalog><item>u6c49u5b57u793au4f8b</item></catalog>'
11 >>> f=file('d:/test.xml', 'w')
12 >>> import codecs
13 >>> writer = codecs.lookup('utf-8')[3](f)
14 >>> dom.writexml(writer, encoding='utf-8')
15 >>> writer.close()
5行 因为XML处理时内部使用Unicode编码,因此象汉字首先要转成Unicode,如果你不做这一步minicode并不检查,并且保存时可能不会出错。但读取时可能会出错。
12-13行 生成UTF-8编码的写入流对象,这样在保存时会自动将Unicode转换成UTF-8编码。
这样写XML文件就完成了。
三.美化.
对于dom对象的writexml()方法,虽然可以控制一些格式上的输出,但结果并不让人满意。比如我想实现:
<catalog>
<item>test</item>
<item>test</item>
</catalog>
而不是:
<catalog>
<item>
test
</item>
<item>
test
</item>
</catalog>
如果是象下面的输出结果我无法区分原来文本中是否带有空白,而上一种结果则不存在这一问题。好在我在wxPython自带的XML资源编辑器(xred)发现了美化的代码。代码如下:
1 def Indent(dom, node, indent = 0):
2 # Copy child list because it will change soon
3 children = node.childNodes[:]
4 # Main node doesn't need to be indented
5 if indent:
6 text = dom.createTextNode(' ' + ' ' * indent)
7 node.parentNode.insertBefore(text, node)
8 if children:
9 # Append newline after last child, except for text nodes
10 if children[-1].nodeType == node.ELEMENT_NODE:
11 text = dom.createTextNode(' ' + ' ' * indent)
12 node.appendChild(text)
13 # Indent children which are elements
14 for n in children:
15 if n.nodeType == node.ELEMENT_NODE:
16 Indent(dom, n, indent + 1)
参数说明:
dom为dom对象
node为要处理的元素结点
indent指明缩近的层数
函数说明:
Indent是一个递归函数,当一个结点有子元素时进行递归处理。主要是解决子元素的换行和缩近的处理。这里缩近是写死的,每一级缩近使用一个制表符。如果你愿意可以改为你想要的内容。就是把函数中的' '换替一下。或干脆写成一个全局变量,或参数以后改起来可能要容易的多。不过在 NewEdit 中,这样的处理足够了,就没有做这些工作。
Indent基本的想法就是递归遍历所有子结点,在所有需要加入回车和缩近的地方插入相应的文本结点。这样再使用writexml()输出时就是缩近好了的。具体程序不再细说,直接用就行了。
但这里要注意的是:
Indent()要修改原dom对象,因此在调用它之前最好先复制一个临时dom对象,使用完毕后再清除这个临时dom对象即可。下面是详细的调用过程:
1 domcopy = dom.cloneNode(True)
2 Indent(domcopy, domcopy.documentElement)
3 f = file(xmlfile, 'wb')
4 writer = codecs.lookup('utf-8')[3](f)
5 domcopy.writexml(writer, encoding = 'utf-8')
6 domcopy.unlink()
1行 克隆一个dom对象
2行 进行缩近处理
3-4行 进行UTF-8编码处理
5行 生成XML文件
6行 清除dom对象的内容
Python 使用 ElementTree 处理 XML
一、引用方法
ElementTree 所在文件保存在 Lib/xml/etree/ElementTree.py,所以我们通过下面的代码引用它,之后就可以使用 ET. 来访问 ElementTree 中的函数。
1
|
import xml.etree.ElementTree as ET
|
二、一个 XML 例子
下面所有的操作都将下面这段 XML 为例,我们将它保存为sample.xml。
1
|
<?xml version="1.0"?>
|
先对 XML 的格式做一些说明:
- Tag: 使用 < 和 > 包围的部分,如 成为 start-tag,是 end-tags;
- Element:被 Tag 包围的部分,如68,可以认为是一个节点,它可以有子节点;
- Attribute:在 Tag 中可能存在的 name/value 对,如 中的 name=”Liechtenstein”,一般表示属性。
三、解析 XML
读入 XML 数据
首先读入 XML,有两种途径,从文件读入和从字符串读入。
从文件读入:
1
|
import xml.etree.ElementTree as ET
|
从字符串读入:
1
|
root = ET.fromstring(sample_as_string)
|
tree 和 root 分布是 ElementTree 中两个很重要的类的对象:
- ElementTree
- Element
查看 Tag 和 Attribute
这时得到的 root 是一个指向Element 对象,我们可以通过查看 root 的 tag 和 attrib 来验证这一点:
1
|
|
上面的代码说明了查看一个 Element 的 Tag 和 Attribute 的方法,Tag 是一个 字符串 ,而 Attribute 得到的是一个 字典。
另外,还可以使用
- Element.get(AttributeName)
来代替 Element.attrib[AttributeName]来访问。
查看孩子
root.attrib 返回的是一个空字典,如果看 root 的孩子,可以得到非空的 attrib 字典。
1、使用 for…in…访问
1
|
for child in root:
|
得到
country {‘name’: ‘Liechtenstein’}
country {‘name’: ‘Singapore’}
country {‘name’: ‘Panama’}
2、使用下标访问
如:
1
|
|
3、使用 Tag 名称访问
下标访问的方法虽然简单,但是在未知 XML 具体结构的时候并不适用,通过 Tag 名称访问的方法更具有普适性。这里用到 Element 类的几个函数,分别是
- Element.iter()
- Element.findall()
- Element.find()
这两个函数使用的场景有所差异:
Element.iter()用来寻找 所有 符合要求的 Tag,注意,这里查找的范围 是所有孩子和孩子的孩子 and so on。如果查看所有的 year,可以使用下面的代码:
1
|
for neighbor in root.iter('year'):
|
返回
2008
2011
2011
Element.findall()只查找 直接的孩子 ,返回所有符合要求的 Tag 的 Element,而Element.find() 只返回符合要求的第一个 Element。如果查看 Singapore 的 year 的值,可以使用下面的代码:
1
|
for country in root.findall('country'):
|
1
|
for country in root.findall('country'):
|
查看 Element 的值
我们可以直接用 Element.text 来得到这个 Element 的值。
四、修改 XML
前面已经介绍了如何获取一个 Element 的对象,以及查看它的 Tag、Attribute、值和它的孩子。下面介绍如何修改一个 Element 并对 XML 文件进行保存
修改 Element
修改 Element 可以直接访问 Element.text。
修改 Element 的 Attribute,也可以用来新增 Attribute:
Element.set(‘AttributeName’,’AttributeValue’)
新增孩子节点:
Element.append(childElement)
删除孩子节点:
Element.remove(childElement)
保存 XML
我们从文件解析的时候,我们用了一个 ElementTree 的对象 tree,在完成修改之后,还用 tree 来保存 XML 文件。
1
|
tree.write('output.xml')
|
构建 XML
ElementTree 提供了两个静态函数(直接用类名访问,这里我们用的是 ET)可以很方便的构建一个 XML,如:
1
|
root = ET.Element('data')
|
就可以得到
12008
五、XPath 支持
XPath 表达式用来在 XML 中定位 Element,下面给一个例子来说明:
1
|
import xml.etree.ElementTree as ET
|
参考
ElementTree 主页
ElementTree 的函数与类介绍
【ElementTree解析】
两种实现
ElementTree生来就是为了处理XML ,它在python标准库中有两种实现。
一种是纯Python实现,例如: xml.etree.ElementTree
另外一种是速度快一点的: xml.etree.cElementTree
尽量使用C语言实现的那种,因为它速度更快,而且消耗的内存更少! 在程序中可以这样写:
- try:
- import xml.etree.cElementTree as ET
- except ImportError:
- import xml.etree.ElementTree as ET
常用方法
- # 当要获取属性值时,用attrib方法。
- # 当要获取节点值时,用text方法。
- # 当要获取节点名时,用tag方法。
示例XML
- <?xml version="1.0" encoding="utf-8"?>
- <info>
- <intro>Book message</intro>
- <list id='001'>
- <head>bookone</head>
- <name>python check</name>
- <number>001</number>
- <page>200</page>
- </list>
- <list id='002'>
- <head>booktwo</head>
- <name>python learn</name>
- <number>002</number>
- <page>300</page>
- </list>
- </info>
###########
## 加载XML
###########
方法一:加载文件
- root = ET.parse('book.xml')
方法二:加载字符串
- root = ET.fromstring(xmltext)
###########
## 获取节点
###########
方法一:获得指定节点->getiterator()方法
- book_node = root.getiterator('list')
方法二:获得指定节点->findall()方法
- book_node = root.findall('list')
方法三:获得指定节点->find()方法
- book_node = root.find('list')
方法四:获得儿子节点->getchildren()
- for node in book_node:
- book_node_child = node.getchildren()[0]
- print book_node_child.tag, '=> ', book_node_child.text
###########
## 例子01
###########
- # coding=utf-8
- try: # 导入模块
- import xml.etree.cElementTree as ET
- except ImportError:
- import xml.etree.ElementTree as ET
- root = ET.parse('book.xml') # 分析XML文件
- books = root.findall('/list') # 查找所有根目录下的list的子节点
- for book_list in books: # 对查找后的结果遍历
- print "=" * 30 # 输出格式
- for book in book_list: # 对每个子节点再进行遍历,找出里面你的属性及值
- if book.attrib.has_key('id'): # 一句id来做条件判断
- print "id:", book.attrib['id'] # 根据id打印出属性值
- print book.tag + '=> ' + book.text # 输出标签及文本内容
- print "=" * 30
输出结果:
- ==============================
- head=> bookone
- name=> python check
- number=> 001
- page=> 200
- ==============================
- head=> booktwo
- name=> python learn
- number=> 002
- page=> 300
- ==============================
Python 标准库之 xml.etree.ElementTree
简介
Element类型是一种灵活的容器对象,用于在内存中存储结构化数据。
[注意]xml.etree.ElementTree模块在应对恶意结构数据时显得并不安全。
每个element对象都具有以下属性:
1. tag:string对象,表示数据代表的种类。
2. attrib:dictionary对象,表示附有的属性。
3. text:string对象,表示element的内容。
4. tail:string对象,表示element闭合之后的尾迹。
5. 若干子元素(child elements)。
<tag attrib1=1>text</tag>tail
1 2 3 4
创建元素的方法有Element或者SubElement(),前者称作元素的构建函数(constructor),用以构建任一独存的元素;后者称作元素的制造函数(factory function),用以制造某一元素的子元素。
有了一串元素之后,使用ElementTree类来将其打包,把一串元素转换为xml文件或者从xml文件中解析出来。
若想加快速度,可以使用C语言编译的API xml.etree.cElementTree。
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank>1</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank>4</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> <country name="Panama"> <rank>68</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor name="Costa Rica" direction="W"/> <neighbor name="Colombia" direction="E"/> </country> </data>
XML操作
-
读取
#从变量读取,参数为XML段,返回的是一个根Element对象 root = ET.fromstring(country_data_as_string) #从xml文件中读取,用getroot获取根节点,根节点也是Element对象 tree = ET.parse('file.xml') root = tree.getroot()
-
访问
- 访问Element对象的标签、属性和值
tag = element.tag attrib = element.attrib value = element.text
-
- 访问子节点
#打印根节点的标签和属性,获取 for child in root: print(child.tag, child.attrib)
-
查找操作
- Element元素迭代子元素:Element.iter("tag"),可以罗列该节点所包含的所有其他节点(element对象)
#打印根节点中所有的neighbor对象的name属性 for neighbor in root.iter('neighbor'): print(neighbor.attrib['name'])
-
- Element.findall("tag"):查找当前元素为“tag”的直接子元素
#findall只能用来查找直接子元素,不能用来查找rank,neighbor等element for country in root.findall('country'): rank = country.find('rank').text name = country.find('rank').text neig = country.find('neighbor').attrib print(rank, name,neig)
-
- Element.find("tag"):查找为tag的第一个直接子元素
#返回第一个tag为country的element,如没有,返回None firstCountry = root.find("country") print(firstCountry)
-
创建xml文件
__author__ = 'xua' import xml.etree.ElementTree as ET #创建根节点 a = ET.Element("root") #创建子节点,并添加属性 b = ET.SubElement(a,"sub1") b.attrib = {"name":"name attribute"} #创建子节点,并添加数据 c = ET.SubElement(a,"sub2") c.text = "test" #创建elementtree对象,写文件 tree = ET.ElementTree(a) tree.write("test.xml")
创建的新文件内容为:<root><sub1 name="name attribute" /><sub2>test</sub2></root>
-
修改XML文件
- ElementTree.write("xmlfile"):更新xml文件
- Element.append():为当前element对象添加子元素(element)
- Element.set(key,value):为当前element的key属性设置value值
- Element.remove(element):删除为element的节点
#读取待修改文件 updateTree = ET.parse("test.xml") root = updateTree.getroot() #创建新节点并添加为root的子节点 newEle = ET.Element("NewElement") newEle.attrib = {"name":"NewElement","age":"20"} newEle.text = "This is a new element" root.append(newEle) #修改sub1的name属性 sub1 = root.find("sub1") sub1.set("name","New Name") #修改sub2的数据值 sub2 = root.find("sub2") sub2.text = "New Value" #写回原文件 updateTree.write("test.xml")
更新完的文件为:<root><sub1 name="New Name" /><sub2>New Value</sub2><NewElement age="20" name="NewElement">This is a new element</NewElement></root>
总结
XML的操作比较常见,当然也有很多第三方的库可以使用,所需要做的操作无非就是常用的读写xml文件、元素节点的增删改查,大家还可以在python官方文档上学习更多的操作。
https://docs.python.org/3.5/library/xml.etree.elementtree.html
xml源文件格式[例]
- <?xml version="1.0" encoding="UTF-8"?>
- <framework>
- <processers>
- <processer name="AProcesser" file="lib64/A.so"
- path="/tmp">
- </processer>
- <processer name="BProcesser" file="lib64/B.so" value="fordelete">
- </processer>
- <processer name="BProcesser" file="lib64/B.so2222222"/>
- <services>
- <service name="search" prefix="/bin/search?"
- output_formatter="OutPutFormatter:service_inc">
- <chain sequency="chain1"/>
- <chain sequency="chain2"></chain>
- </service>
- <service name="update" prefix="/bin/update?">
- <chain sequency="chain3" value="fordelete"/>
- </service>
- </services>
- </processers>
- </framework>
使用库:
xml.etree.ElementTree
官方文档地址:http://docs.python.org/library/xml.etree.elementtree.html
实现思想:
使用ElementTree,先将文件读入,解析成树,之后,根据路径,可以定位到树的每个节点,再对节点进行修改,最后直接将其输出
代码附文档:
- #!/usr/bin/python
- # -*- coding=utf-8 -*-
- # author : wklken@yeah.net
- # date: 2012-05-25
- # version: 0.1
- from xml.etree.ElementTree import ElementTree,Element
- def read_xml(in_path):
- '''''读取并解析xml文件
- in_path: xml路径
- return: ElementTree'''
- tree = ElementTree()
- tree.parse(in_path)
- return tree
- def write_xml(tree, out_path):
- '''''将xml文件写出
- tree: xml树
- out_path: 写出路径'''
- tree.write(out_path, encoding="utf-8",xml_declaration=True)
- def if_match(node, kv_map):
- '''''判断某个节点是否包含所有传入参数属性
- node: 节点
- kv_map: 属性及属性值组成的map'''
- for key in kv_map:
- if node.get(key) != kv_map.get(key):
- return False
- return True
- #---------------search -----
- def find_nodes(tree, path):
- '''''查找某个路径匹配的所有节点
- tree: xml树
- path: 节点路径'''
- return tree.findall(path)
- def get_node_by_keyvalue(nodelist, kv_map):
- '''''根据属性及属性值定位符合的节点,返回节点
- nodelist: 节点列表
- kv_map: 匹配属性及属性值map'''
- result_nodes = []
- for node in nodelist:
- if if_match(node, kv_map):
- result_nodes.append(node)
- return result_nodes
- #---------------change -----
- def change_node_properties(nodelist, kv_map, is_delete=False):
- '''''修改/增加 /删除 节点的属性及属性值
- nodelist: 节点列表
- kv_map:属性及属性值map'''
- for node in nodelist:
- for key in kv_map:
- if is_delete:
- if key in node.attrib:
- del node.attrib[key]
- else:
- node.set(key, kv_map.get(key))
- def change_node_text(nodelist, text, is_add=False, is_delete=False):
- '''''改变/增加/删除一个节点的文本
- nodelist:节点列表
- text : 更新后的文本'''
- for node in nodelist:
- if is_add:
- node.text += text
- elif is_delete:
- node.text = ""
- else:
- node.text = text
- def create_node(tag, property_map, content):
- '''''新造一个节点
- tag:节点标签
- property_map:属性及属性值map
- content: 节点闭合标签里的文本内容
- return 新节点'''
- element = Element(tag, property_map)
- element.text = content
- return element
- def add_child_node(nodelist, element):
- '''''给一个节点添加子节点
- nodelist: 节点列表
- element: 子节点'''
- for node in nodelist:
- node.append(element)
- def del_node_by_tagkeyvalue(nodelist, tag, kv_map):
- '''''同过属性及属性值定位一个节点,并删除之
- nodelist: 父节点列表
- tag:子节点标签
- kv_map: 属性及属性值列表'''
- for parent_node in nodelist:
- children = parent_node.getchildren()
- for child in children:
- if child.tag == tag and if_match(child, kv_map):
- parent_node.remove(child)
- if __name__ == "__main__":
- #1. 读取xml文件
- tree = read_xml("./test.xml")
- #2. 属性修改
- #A. 找到父节点
- nodes = find_nodes(tree, "processers/processer")
- #B. 通过属性准确定位子节点
- result_nodes = get_node_by_keyvalue(nodes, {"name":"BProcesser"})
- #C. 修改节点属性
- change_node_properties(result_nodes, {"age": "1"})
- #D. 删除节点属性
- change_node_properties(result_nodes, {"value":""}, True)
- #3. 节点修改
- #A.新建节点
- a = create_node("person", {"age":"15","money":"200000"}, "this is the firest content")
- #B.插入到父节点之下
- add_child_node(result_nodes, a)
- #4. 删除节点
- #定位父节点
- del_parent_nodes = find_nodes(tree, "processers/services/service")
- #准确定位子节点并删除之
- target_del_node = del_node_by_tagkeyvalue(del_parent_nodes, "chain", {"sequency" : "chain1"})
- #5. 修改节点文本
- #定位节点
- text_nodes = get_node_by_keyvalue(find_nodes(tree, "processers/services/service/chain"), {"sequency":"chain3"})
- change_node_text(text_nodes, "new text")
- #6. 输出到结果文件
- write_xml(tree, "./out.xml")
通过main处理后的结果文件:
- <?xml version='1.0' encoding='utf-8'?>
- <framework>
- <processers>
- <processer file="lib64/A.so" name="AProcesser" path="/tmp">
- </processer>
- <processer age="1" file="lib64/B.so" name="BProcesser">
- <person age="15" money="200000">this is the firest content</person>
- </processer>
- <processer age="1" file="lib64/B.so2222222" name="BProcesser">
- <person age="15" money="200000">this is the firest content</person>
- </processer>
- <services>
- <service name="search" output_formatter="OutPutFormatter:service_inc"
- prefix="/bin/search?">
- <chain sequency="chain2" />
- </service>
- <service name="update" prefix="/bin/update?">
- <chain sequency="chain3" value="fordelete">new text</chain>
- </service>
- </services>
- </processers>
- </framework>
导入ElementTree
在使用xml.etree.ElementTree时,一般都按如下导入:
try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET
XML是中结构化数据形式,在ET中使用ElementTree代表整个XML文档,并视其为一棵树,Element代表这个文档树中的单个节点。
ET对象具有多种方法从不同来源导入数据,如下:
#从硬盘的xml文件读取数据 import xml.etree.ElementTree as ET tree = ET.parse('country_data.xml') #载入数据 root = tree.getroot() #获取根节点 #从字符串读取数据 root = ET.fromstring(country_data_as_string)
[注意]fromstring()是直接获取string对象中的根节点,因此以上root其实是一个Element。
作为一个Element对象,本身是具有子元素,因此可以直接对Element进行迭代取值:
>>> for child in root: ... print child.tag, child.attrib ... country {'name': 'Liechtenstein'} country {'name': 'Singapore'} country {'name': 'Panama'}
或者直接使用索引寻找子节点:
>>> root[0][1].text
'2008'
Element中的遍历与查询
Element.iter(tag=None):遍历该Element所有后代,也可以指定tag进行遍历寻找。
Element.findall(path):查找当前元素下tag或path能够匹配的直系节点。
Element.find(path):查找当前元素下tag或path能够匹配的首个直系节点。
Element.text: 获取当前元素的text值。
Element.get(key, default=None):获取元素指定key对应的属性值,如果没有该属性,则返回default值。
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):删除子元素。
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")
模块方法
- 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实例。
New in version 2.7.
- 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的映射关系会被删除。
New in version 2.7.
- 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数据的字符串列表。
New in version 2.7.
- xml.etree.ElementTree.XML(text, parser=None)
-
从一个字符串常量中解析出xml片段。返回Element实例。
- xml.etree.ElementTree.XMLID(text, parser=None)
-
从字符串常量解析出xml片段,同时返回一个字典,用以映射element的id到其自身。