这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
XML已经成为数据传输存储使用越来越广泛的数据格式,本文讲述使用Python DOM处理XML文件的方法。
准备工作
- 安装Python
- XML基础知识
Python常用处理XML库
常见的 XML 编程接口有 DOM 和 SAX,这两种接口处理 XML 文件的方式不同,当然使用场合也不同。Python 有三种方法解析 XML,SAX,DOM,以及 ElementTree:
DOM(Document Object Model) - 本文介绍
DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。HTML DOM 定义了访问和操作 HTML 文档的标准方法。将 XML 数据在内存中解析成一个树,通过对树的操作来操作XML。
SAX (simple API for XML )
Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。
ElementTree(元素树)
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>
Python DOM 解析XML
DOM节点树
一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里。
节点级别
节点树中的节点彼此之间都有等级关系。
- 在节点树中,顶端的节点成为根节点
- 根节点之外的每个节点都有一个父节点
- 节点可以有任何数量的子节点
- 叶子是没有子节点的节点
- 同级节点是拥有相同父节点的节点
解析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规定节点:
- 整个文档是一个文档节点
- 每个 XML 标签是一个元素节点
- 包含在 XML 元素中的文本是文本节点
- 每一个 XML 属性是一个属性节点
- 注释属于注释节点
文本总是存储在文本节点中
在 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
DOM解析XML方法总结
- contents = xml.dom.minidom.parse(filename):加载读取XML文件
- node = contents.documentElement:获取XML文档对象
- node.getAttribute(AttributeName):获取XML节点属性值
- node.getElementsByTagName(TagName):获取XML节点对象集合
- node.childNodes :返回子节点列表。
- node.childNodes[index].nodeValue:获取XML节点值
- node.firstChild:访问第一个节点,等价于pagexml.childNodes[0]
- node.toxml(‘UTF-8’)返回Node节点的xml表示的文本:
- 访问元素属性: atr=Node.attributes[“id”] atr.name # “id” atr.value #属性的值
- node.nodeName/node.tagName:节点的名称
- node.nodeValue:节点的值,文本节点才有值,其它节点返回的是None
- node.nodeType:节点的类型
Python DOM 修改XML
生成XML树、添加属性
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并没有一并更新,使用时需要谨慎。
DOM修改XML方法总结
- dom=minidom.Document() #创建DOM树对象
- root_node=dom.createElement('root') #用DOM对象创建元素节点
- name_text=dom.createTextNode('计算机程序设计语言 第1版')#用DOM对象创建文本节点
- dom.appendChild(root_node) # 用DOM对象添加节点
- book_node.setAttribute('price','199') #添加节点属性(可多个),也可以用来覆盖修改原有的属性
- children_Nodes.remove(children_Nodes[i]) #删除节点
- book_node.removeAttribute('price') #删除节点属性
- price[0].firstChild.data=str(price_float) #直接修改文本节点完成对xml保存内容的修改
- name_node.tagName='tag name' #修改节点名称
Python DOM 写入XML
写入XML
语法:writexml(writer, indent="", addindent="", newl="", encoding=None)
- writer:文件对象
- indent:根节点缩进格式(一般根节点不缩进,所以一般为空)
- addindent:子节点缩进格式(一般为空格
或制表符\t) - newl:换行格式(一般为换行回车
\n) - encoding:编码方式
#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下载。