用python的minidom解析xml(转载)
1 xml规则概述
以下是一个博客网站的xml的例子:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>pilgrim</title>
<link>http://blog.xxx.com</link>
<description>博客描述</description>
<generator>Terac Miracle 3.8</generator>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_42678.html</link>
<description>文章内容</description>
<pubDate>Sun, 23 Sep 2007 23:32:00 +0800</pubDate>
</item>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_39749.html</link>
<description>文章内容</description>
<pubDate>Mon, 27 Aug 2007 23:58:00 +0800</pubDate>
</item>
</channel>
</rss>
其中,第一行为文档声明,它向解析器提供了关于文档的基本信息。建议使用 XML 声明,但它不是必需的。如果有的话,那么它一定是文档的第一样东
西。声明最多可以包含三个名称-值对。version 是使用的 XML 版本;目前该值必须是 1.0。encoding 是该文档所使用的字符集。如没有指定 encoding,XML 解析器会假定字符在 UTF-8 字符集中。最后,还有一个standalone(可以是 yes 或 no)
定义了是否可以在不读取任何其它文件的情况下处理该文档。例如,如果 XML 文档没有引用任何其它文件,则可以指定standalone="yes"。如果 XML 文档引用其它描述该文档的文件,则可以指定 standalone="no"。
standalone="no" 是缺省值。
例:<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
接下来,定义了xml文档的根元素,一个xml文档必须包含在一个单一的根元素中,在本例中元素标签名为rss,它包含文档中所有文本和所有其它元
素。version是rss元素的一个属性,其值为字符串"2.0"。属性必须有用引号括起的值,可以是单引号或双引号。</rss>是rss元素结束
标志,每个元素的结束标记是必须的。如果元素没有子节点,可以结束标记和开始标记合二为一:<tag attr='value'/>。
xml元素不能重叠,xml区分大小写。
三种 XML 文档:
* 无效文档没有遵守 XML 规范定义的语法规则。如果开发人员已经在 DTD 或模式中定义了文档能够包含什么,而某个文档没有遵守那些规
则,那么这个文档也是无效的。(请参阅定义文档内容以获得对 XML 文档 的 DTD 和模式的专门介绍。)
* 有效文档既遵守 XML 语法规则也遵守在其 DTD 或模式中定义的规则。
* 格式良好的文档遵守 XML 语法,但没有 DTD 或模式。
2 解析器种类
要让计算机读懂xml文档,就需要解析器(parser)。
有不同的方法来划分解析器种类:
1.验证或非验证解析器
2.支持 Document Object Model (DOM) 的解析器
3.支持 Simple API for XML (SAX) 的解析器
4.特定语言编写的解析器 (Java, C++, Perl 等)
如我们在前面所提及的,XML 文档如果使用一个 DTD 并符合 DTD中的规则将被称为有效文档(valid document)。符合基本标记规则的 XML 文档被称为格式正确文档(well-formed document)。XML 规范要求所有的解析器当其发现一个文档不是格式正确时要报错。
验证解析器(Validating parser)在解析 XML 文档同时进行验证(检查是否是有效文档)。非验证解析器(Non-validating parser) 忽略所有的验证错误。换而言之,如果一个 XML 文档是格式正确的时,一个非验证解析器并不关注文档是否符合其对应 DTD 所指定的规则(如果有的话)。
文档对象模型(Document Object Model)是 World Wide Web Consortium(W3C)的正式推荐。它定义了一个接口使得程序可以存取和更新 XML 文档的风格、结构和内容。支持 DOM 的 XML 解析器实现该接口。
一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后你可以利用DOM 提供的不同的函数来读取或修改文档的内容和结构,也可以把修改过的内容写入xml文件。
SAX API 是另一种处理 XML 文档内容的方法。一个既成事实的标准,它由David Megginson 和 XML-Dev 邮件列表
其它成员所开发。不同于DOM,SAX不是一次性读取整个文档,而是以数据流的形式处理文档,解析器在文档的不同处将产生事件。由您来决定对每个事件如
何处理。
DOM解析器适合处理短小的文档,并且适合对文档进行随机访问和修改,SAX适合按顺序读取大型文档。用SAX修改文档是很麻烦的。
3 DOM与 python 库xml.dom.minidom
DOM把xml每个元素、属性、文本等信息储存在称为节点的数据类型中,xml.dom中Node(节点)是xml文档中每一个component
的父类。XML 中最常见的节点类型包括:
*元素:元素是 XML 的基本构造模块。通常,元素拥有子元素、文本节点,或两者的组合。元素节点也是能够拥有属性的唯一节点类型。
*属性:属性节点包含关于元素节点的信息,但是并不实际认为是元素的孩子,比如在下面的例子中:
*文本:文本节点就是名副其实的文本。它可以由更多信息组成,也可以只包含空白。
*文档:文档节点是文档中其他所有节点的父亲。
其他节点类型不太常用,但是在某些场合下仍然是必需的。它们包括:
*CDATA:字符数据(Character Data)的缩写,这是一个特殊的节点,它包含不应该被解析器分析的信息。相反,它包含的信息应该以纯文本传递。例如,可能会为了特殊目的而存储 HTML 标签。在通常情形下,处理器可能尝试为所存储的每个标签创建元素,而这样可能导致文档不是格式良好
的。这些问题可用通过使用 CDATA 节(section)来避免。这些节使用特殊的符号来编写:
<[CDATA[<b>
Important: Please keep head and hands inside ride at <i>all
times</i>.
</b>]]>
*注释:注释包括关于数据的信息,通常被应用程序忽略。它们写为如下形式:
<!-- This is a comment. -->
*处理指令:处理指令是专门针对应用程序的信息。其中一些例子包括要执行的代码或者关于从何处寻找样式表的信息。例如:
<?xml-stylesheet type="text/xsl" href="foo.xsl"?>
python xml.dom中节点类型由节点中nodeType属性得到,该属性取如下值:ELEMENT_NODE,
ATTRIBUTE_NODE, TEXT_NODE, CDATA_SECTION_NODE, ENTITY_NODE,
PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, DOCUMENT_NODE,
DOCUMENT_TYPE_NODE, NOTATION_NODE
示例:把上面的xml例子保存为test-utf8.xml,一定用utf8的编码保存。然后在python提示符下输入:
>>> from xml.dom import minidom
>>> xmldoc=minidom.parse('test-utf8.xml')
得到一个Document类型变量xmldoc, 它是一个保存文档所有信息的树结构。用Node的toxml()函数可以得到节点中保存的xml字符串。因为Document是Node的子类,所以可以应用toxml函数:
>>> print xmldoc.toxml()
<?xml version="1.0" ?><rss version="2.0">
<channel>
<title>pilgrim</title>
<link>http://blog.xxx.com</link>
<description>博客描述</description>
<generator>Terac Miracle 3.8</generator>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_42678.html</link>
<description>文章内容</description>
<pubDate>Sun, 23 Sep 2007 23:32:00 +0800</pubDate>
</item>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_39749.html</link>
<description>文章内容</description>
<pubDate>Mon, 27 Aug 2007 23:58:00 +0800</pubDate>
</item>
</channel>
</rss>
要得到文档的根节点,用Document的documentElement属性:
>>> root=xmldoc.documentElement
>>> root
<DOM Element: rss at 0x14f29e0>
>>> print root.toxml()
<rss version="2.0">
<channel>
<title>pilgrim</title>
<link>http://blog.xxx.com</link>
<description>博客描述</description>
<generator>Terac Miracle 3.8</generator>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_42678.html</link>
<description>文章内容</description>
<pubDate>Sun, 23 Sep 2007 23:32:00 +0800</pubDate>
</item>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_39749.html</link>
<description>文章内容</description>
<pubDate>Mon, 27 Aug 2007 23:58:00 +0800</pubDate>
</item>
</channel>
</rss>
root有一个子元素channel,这些子元素由root的childNodes保存,其中第一个子节点由root.firstChild引用,最后一个子节点由root.lastChild引用:
>>> print root.firstChild.toxml()
>>> print root.lastChild.toxml()
>>> print root.childNodes[1].toxml()
<channel>
<title>pilgrim</title>
<link>http://blog.xxx.com</link>
<description>博客描述</description>
<generator>Terac Miracle 3.8</generator>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_42678.html</link>
<description>文章内容</description>
<pubDate>Sun, 23 Sep 2007 23:32:00 +0800</pubDate>
</item>
<item>
<title>文章标题</title>
<link>http://blog.xxx.com/e_39749.html</link>
<description>文章内容</description>
<pubDate>Mon, 27 Aug 2007 23:58:00 +0800</pubDate>
</item>
</channel>
因为firstChild和lastChild是由channel前后的空白和换行形成的文本型节点,所以会打印出空行。
访问xmldoc:
xml各部分与minidom关系对应:
<Node.tagName Node.attributes.keys() = Node.attributes['key'].value
attributes可以像dict一样使用,用keys()得到属性列表,用Node.attributes['key'].value得到属性值
Node.childNodes保存个子节点,第一个节点Node.firstChild,最后一个Node.lastChild,可以像list一样使用。
<Node.tagName>TextNode.data</Node.tagName>
</Node.tagName>
修改xmldoc:
添加节点:首先创建节点:Document.createElement(tagName)
Document.createTextNode(data)再把节点添加到Node下面:Node.appendChild()
node.insertBefore(new,ref)
删除节点Node.removeChild()
替换节点 Node.replaceChild(new,old)
添加删除属性 更改属性
Element.setAttribute(name,value) Element.removeAttribute(name) 也可以用
Element.attributes['key']=value来直接指定,value是unicode字符串。
4 例子
dir2xml.py是由目录结构生成xml文件的例子,xml2dir.py是根据生成的xml文件内容重建目录,当然建立出的文件都是没有内容的空文件。
文件dir2xml.py:
#!/usr/bin/env python
#-*- coding: gbk -*-
"""遍历目录,根据目录结构生成xml文件。
dir2xml dirname xmlfilename
"""
import os
from xml.dom import minidom as pydom
import sys
def usage():
print "usage:",sys.argv[0],"dirname xmlfilename"
def dir2xml(dirname):
"""遍历目录,根据目录内容生成xml Document对象
dirname是路径名,必须是标准的路径名,前后没有空格,后面不带/或\\。
"""
impl=pydom.getDOMImplementation()
newdoc=impl.createDocument(None,"dir",None)
rootdir=newdoc.documentElement
rootdir.attributes['name']=os.path.basename(dirname)
def walkdir(dirname,node,document): #对目录递归遍历,这里用os.path.walk更简单
for file in os.listdir(dirname):
if os.path.isfile(os.path.join(dirname,file)):
newFileEl=document.createElement('file')
newFileEl.attributes['name']=file.encode('utf8')
node.appendChild(newFileEl)
elif os.path.isdir(os.path.join(dirname,file)):
newFileEl=document.createElement('dir')
newFileEl.attributes['name']=file.encode('utf8')
node.appendChild(newFileEl)
walkdir(os.path.join(dirname,file),newFileEl,document)
walkdir(dirname,rootdir,newdoc)
return newdoc
if __name__ == '__main__':
if len(sys.argv)<3:
usage()
sys.exit()
if not os.path.isdir(sys.argv[1]):
print 'Error:',sys.argv[1],'is not a directory.'
sys.exit()
xmlfile=file(sys.argv[2],'w')
newdoc=dir2xml(unicode(os.path.normpath(sys.argv[1].strip()),'gb2312'))
newdoc.writexml(xmlfile,'\n',' ')
xmlfile.close()
文件xml2dir.py:
#!/usr/bin/env python
# -*- coding: cp936 -*-
"""由xml文档生成目录
xml2dir xmlfilename dirname
"""
import os
from xml.dom import minidom as pydom
import sys
def usage():
print "usage:",sys.argv[0],"dirname xmlfilename"
def xml2dir(xmlElement,dirname):
"""由xml文档生成目录
xml2dir(xmlElement,dirname)
xmlElement是xml文档元素,标签名file表示文件,dir表示目录。属性name表示文件或目录名
dirname是保存xmlElement整个节点的目录名,将在dirname目录中开始建立xmlElement目录树。"""
if not os.path.exists(dirname):
os.mkdir(dirname)
cwd=os.getcwd()
os.chdir(dirname)
for childNode in xmlElement.childNodes:
if childNode.nodeType not in
(childNode.ELEMENT_NODE,childNode.DOCUMENT_NODE):
continue
if childNode.tagName == u'file':
file(childNode.attributes['name'].value,'w').close()
elif childNode.tagName == u'dir':
if not os.path.exists(childNode.attributes['name'].value):
os.mkdir(childNode.getAttribute('name'))
xml2dir(childNode,childNode.getAttribute('name'))
os.chdir(cwd)
if __name__ == '__main__':
if len(sys.argv)<3:
usage()
sys.exit()
try:
xmlfile=file(sys.argv[1],'r')
except:
sys.stderr.write("XML file not found or cannot access.")
sys.exit()
xmldoc=pydom.parse(xmlfile)
xml2dir(xmldoc,os.path.normpath(sys.argv[2].strip()))
xmlfile.close()