1. 概述
XML解析是软件开发中经常会遇到的事情,包括C++、Java,以及Python等(此外的语言基本无涉及)。C/C++比较经典的就是libxml2(http://www.xmlsoft.org/),Java有SDK(参考Core Java),而Python有几个库,但通常使用xml.dom.minidom就基本可以满足日常工作了。
本文就是讨论xml.dom.minidom的常用方法。
1.1 libxml2
Linux下面也有libxml2库可用,而在Windows上面,就需要单独去下载libxml2。
2. 参考资料
minidom的参考资料主要如下:
- https://docs.python.org/3/library/xml.dom.minidom.html:使用方法及示例
- hg.python.org/cpython/file/3.4/Lib/xml/dom/minidom.py:minidom的源代码,可以查到所有的方法和属性
3. 常用操作步骤
无论哪种语言,对于xml的解析基本上步骤都是类似的,——毕竟处理的是完全相同的XML语法。
3.1 参考步骤
对于minidom来讲,通常是如下几个步骤:
1. 调用parse()方法把xml文件加载到内存,形成DOM对象(树);
2. 调用documentElement()获取根元素,或称整个文档;
3. 调用getElementsByTagName()获取某个节点下面所有特定tag的所有子节点;或者,
4. 调用childNodes来处理子节点;
5. 调用getAttribute()获取节点的特定属性;或者,
6. 如果该节点无任何属性,已到数据层,则调用.data属性取具体的文本串。
3.2 调试手段
当xml层次太多的时候,可能会不确定当前处理的节点是什么,为此可以借助minidom的一些方法进行调试,比如:
- cur_node.toxml(): 打印当前处理的节点的xml字符串
- cur_node.nodeName: 打印当前节点的名称
- 等等
简单地讲,就是从minidom的源代码中去遍历其方法和属性,根据名称即可大致猜测其具体含义。——当然,进一步通过示例代码进一步验证。
4. 示例
在第一个参考资料中,即给出了一个示例代码。这里结合日常学习的实践,再给出一个例子阐述minidom的使用方法。
4.1 下载Android源码的xml解析
4.1.1 需求
这里的例子来源于优化的下载Android源码的Python脚本一文,即通过Android repo/git库的default.xml文件,自动构造git clone命令,从而下载Android的每个git库。
为了提高页面浏览的流畅性,并便于读者调试,default.xml提供的下载池:http://download.csdn.net/detail/u013344915/7347711
首先抛开Android的领域知识,我们简化成如下的(设计)需求:
- 获取xml文件中的每一个project节点,获取其path属性和name属性的值,即path_attribute和name_attribute;
- 根据path和name属性,构造一条git命令,即字符串格式为git clone http://android.googlesource.com/name_attribute.gitdownload_dir/path_attribute
- 其中download_dir是Android下载的目标目录,每个project下载到这个目标目录下面的某个子目录(即path_attribute中);
因为本文首先是阐述xml解析的方法,所以以上需求进一步可以简化为:
- 获取所有project的path属性和name属性的值
至于如何利用这些属性去构造git命令,以及下载策略等则不再本文讨论。如此通过彻底的简化来突出xml本身的解析方法。
4.1.2 设计
——这里用词“设计”显然有夸大之嫌,但我们姑且用之。
根据前面描述的xml解析的步骤,我们给出如下的设计思路:
1. 把xml加载到内存:dom = minidom.parse("default.xml")
2. 获取根目录:manifest = dom.documentElement ——通过xml文件看到,顶级节点名为manifest,所以这里定义了manifest对象
3. 遍历manifest阶段下面的每一个project节点:projects = manifest.getElementsByTagName("project") ——这里返回的是一个list
4. 或者调用projects = manifest.childNodes属性获取manifest的所有字节点。但因为除了project,还包括remote和default等节点,所以需要剔除掉它们。——因为本文阐述xml解析的方法、讨论可能的属性和方法,所以我们会通过一些bad smell的代码说明问题,但这并不意味着正式项目中需要采用这种策略。
5. 获取project的path和name属性:path_attribute = project.getAttribute("path"), name_attribute = project.getAttribute("name")
4.1.3 Sprint1
作为初始迭代,我们只需要把所有project的两个属性取到、打印到屏幕上即可。
——因为是示例,我们这里并没有考虑可测试性,特别是没有考虑自动化测试的需求,对于打印格式也没有那么严格。
代码如下:
#!/usr/bin/env python
import xml.dom.minidom
import os
default_xml = "./default.xml"
dom = xml.dom.minidom.parse(default_xml)
manifest = dom.documentElement
projects = manifest.getElementsByTagName("project")
for project in projects:
path = project.getAttribute("path")
name = project.getAttribute("name")
print path, name
运行结果:
flying-bird@flyingbird:~/examples/python/minidom$ ./sprint1.py
build platform/build
abi/cpp platform/abi/cpp
。。。。。。
tools/studio/cloud platform/tools/studio/cloud
tools/swt platform/tools/swt
flying-bird@flyingbird:~/examples/python/minidom$
4.1.4 sprint1 with bad smell
下面再用node.childNodes属性完成上述任务:
#!/usr/bin/env python
import xml.dom.minidom
import os
default_xml = "./default.xml"
dom = xml.dom.minidom.parse(default_xml)
manifest = dom.documentElement
children = manifest.childNodes
for child in children:
tag = child.nodeName
if tag == "project":
path = child.getAttribute("path")
name = child.getAttribute("name")
print path, name
4.1.5 拼接git命令
如果只关心xml解析,则可以跳过本节。
在利用xml得到了关键信息之后,我们可以逐步再增加功能,这一步即实现git命令。即根据path和name,拼接成可以执行的git命令。根据前面的需求描述,其实就是一个字符串连接而已,代码如下:
#!/usr/bin/env python
import xml.dom.minidom
import os
default_xml = "./default.xml"
dom = xml.dom.minidom.parse(default_xml)
manifest = dom.documentElement
projects = manifest.getElementsByTagName("project")
for project in projects:
path = project.getAttribute("path")
name = project.getAttribute("name")
git_command = "git clone http://android.googlesource.com/" + name + " ./android_source/" + path
print git_command
运行结果:
flying-bird@flyingbird:~/examples/python/minidom$ ./sprint2.py
git clone http://android.googlesource.com/platform/build ./android_source/build
。。。。。。。。。。。。。。
git clone http://android.googlesource.com/platform/tools/swt ./android_source/tools/swt
flying-bird@flyingbird:~/examples/python/minidom$
5. 局限性
minidom是使用有限的一个xml库,有些功能无法获取CDATA,此时就要选用功能更全的dom库了。