XML已经成为数据传输存储使用越来越广泛的数据格式,本文讲述使用Python DOM处理XML文件的方法。
常见的 XML 编程接口有 DOM 和 SAX,这两种接口处理 XML 文件的方式不同,当然使用场合也不同。Python 有三种方法解析 XML,SAX,DOM,以及 ElementTree:
DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。HTML DOM 定义了访问和操作 HTML 文档的标准方法。将 XML 数据在内存中解析成一个树,通过对树的操作来操作XML。
Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。
ElementTree就像一个轻量级的DOM,具有方便友好的API。代码可用性好,速度快,消耗内存少。
<?xml version="1.0" encoding="utf-8"?>
<!--file name "test.xml" 2020.2.24 by VVD-->
<bookstore type="info science">
<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>
<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>
</bookstore>
一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里。

节点树中的节点彼此之间都有等级关系。
python 加载DOM解析XML,并输出整个XML
#加载DOM minidom模块
from xml.dom.minidom import parse
#解析XML文件
contents=parse('test.xml')
root=contents.documentElement
print(root.toxml())
>>><bookstore type="info science">
<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>
<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>
</bookstore>
此时root获取了XML文件中所有内容,组织成了一棵树,root就是该树的根节点
#获取节点名称
print(root.nodeName)
print(root.tagName)
>>> bookstore
bookstore
#获取子节点
children_Nodes=root.childNodes
print(f'子节点个数:{children_Nodes.length}')
for item in children_Nodes:
print (item.toxml())
>>> 子节点个数:5
<book category="Coding">
<title>C++Primer</title>
<author>StanleyB.Lippman</author>
<price>60.0</price>
<year>2009</year>
</book>
<book category="Computer">
<title>编译原理</title>
<author>AlfredV.Aho</author>
<price>70.0</price>
<year>2013</year>
</book>
为什么2本书却有5个节点? 因为两本书前中后各有一个回车,被划为文本节点。 DOM规定节点:

在 DOM 处理中一个普遍的错误是,认为元素节点包含文本。 不过,元素节点的文本是存储在文本节点中的。 在这个例子中:
<year>2005</year>,元素节点<year>,拥有一个值为 “2005” 的文本节点。 “2005” 不是<year>元素的值!d
#获取节点属性
print(root.attributes.items())
atr_root=root.attributes['type']
print(atr_root.name)
print(atr_root.value)
>>>[('type', 'info science')]
type
info science
#获取指定名称的节点
books=root.getElementsByTagName('book')
for book in books:
print (book.nodeName)
>>>book
book
#判断是否包含属性值
print(books[0].hasAttribute('nnn'))
print(books[0].hasAttribute('category'))
>>>False
True
#获取节点指定属性的属性值
print(books[0].getAttribute('category'))
print(books[1].getAttribute('category'))
>>>Coding
Computer
#获取节点文本值
node=books[0]
author=node.getElementsByTagName('author')
print(author[0].firstChild.data)
>>>StanleyB.Lippman
#判断是否有子节点
print(root.hasChildNodes())
print(author[0].hasChildNodes())
print(author[0].firstChild.hasChildNodes())
>>>True
True
False
from xml.dom import minidom
#1.创建DOM树对象
dom=minidom.Document()
# 2.创建根节点。每次都要用DOM对象来创建任何节点。
root_node=dom.createElement('root')
# 3.用DOM对象添加根节点
dom.appendChild(root_node)
# 用DOM对象创建元素子节点
book_node=dom.createElement('book')
# 用父节点对象添加元素子节点
root_node.appendChild(book_node)
# 设置该节点的属性
book_node.setAttribute('price','399')
name_node=dom.createElement('name')
book_node.appendChild(name_node)
# 也用DOM创建文本节点,把文本节点(文字内容)看成子节点
name_text=dom.createTextNode('C++ Primer 第1版')
# 用添加了文本的节点对象(看成文本节点的父节点)添加文本节点
name_node.appendChild(name_text)
print(dom.toxml()) #字符格式输出
print(dom.toprettyxml()) #美丽的输出
>>> <?xml version="1.0" ?><root><book price="399"><name>C++ Primer 第1版</name></book></root>
<?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
</root>
#增加要删除的节点
another_node=dom.createElement('delete')
another_node_child=dom.createElement('content')
another_node_child_text=dom.createTextNode('testing data')
another_node_child.appendChild(another_node_child_text)
another_node.appendChild(another_node_child)
root_node.appendChild(another_node)
print(dom.toprettyxml())
>>><?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
<delete>
<content>testing data</content>
</delete>
</root>#删除上文增加的节点
children_Nodes=root_node.childNodes
for i in range(children_Nodes.length-1,-1,-1):
if children_Nodes[i].tagName=='delete':
children_Nodes.remove(children_Nodes[i]) #删除root中的文本节点
print(dom.toprettyxml())
>>> <?xml version="1.0" ?>
<root>
<book price="399">
<name>C++ Primer 第1版</name>
</book>
</root>
#删除节点属性
book_node.setAttribute('price','199')
print(book_node.hasAttribute('price'))
print(book_node.getAttribute('price'))
book_node.removeAttribute('price')
print(book_node.hasAttribute('price'))
>>>True
199
False
#修改节点内容
children_Nodes=root_node.childNodes
name_Append = ' (中文版)'
for item in children_Nodes:
#print (item.toxml())
tag_name='name'
c_name = item.getElementsByTagName(tag_name)
print(c_name[0].firstChild.data)
c_name[0].firstChild.data+=name_Append
print(dom.toprettyxml())
>>>C++ Primer 第1版
<?xml version="1.0" ?>
<root>
<book>
<name>C++ Primer 第1版 (中文版)</name>
</book>
</root>
#修改节点属性
book_node.setAttribute('price','199')
print(book_node.hasAttribute('price'))
print(book_node.getAttribute('price'))
book_node.setAttribute('price','399')
print(book_node.getAttribute('price'))
book_node.setAttribute('price2','599')
print(book_node.attributes.items())
>>> True
199
399
[('price', '399'), ('price2', '599')]
#修改节点名称(可用、不安全)
name_node=dom.createElement('name')
name_node.nodeName='node name'
name_node.tagName='tag name'
root_node.appendChild(name_node)
print(dom.toxml())
print(name_node.nodeName)
print(name_node.localName)
#删掉刚刚增加的测试节点
children_Nodes=root_node.childNodes
children_Nodes.remove(children_Nodes[1])
>>><?xml version="1.0" ?><root><book price="399" price2="599"><name>C++ Primer 第1版 (中文版)</name></book><tag name/></root>
node name
tag name
可以看到,改变tagName在事实上实现了改变节点名称的效果,但nodeName并没有一并更新,使用时需要谨慎。
语法:writexml(writer, indent=“”, addindent=“”, newl=“”, encoding=None)
/t)/n)#Python DOM 写入XML
with open('write_test.xml','w',encoding='UTF-8') as writer:
dom.writexml(writer,indent='',addindent=' ',newl='/n',encoding='UTF-8')
得到xml文件write_test.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<book price="399" price2="599">
<name>C++ Primer 第1版 (中文版)</name>
</book>
</root>
文中测试环境与所有源码可在Github下载。