用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()