Overview
这篇博客内容将包括对 XML 文件的解析、追加新元素后写入到 XML,以及更新原 XML 文件中某结点的值。使用的是 python 的xml.dom.minidom
包,详情可见其官方文档:xml.dom.minidom 官方文档。
- 官方关于其他几种xml解析包的说明 XML Processing Modules
解析 XML 文件、字符串
导包
parse()和parseString()函数所做的是将一个XML解析器与一个DOM构建器连接起来,这个DOM构建器可以接受来自任何SAX解析器的解析事件,并将它们转换成一个DOM树。
from xml.dom.minidom import parse, parseString
解析
parse
和parseString
方法都会返回一个表示文档的内容的Document
对象。
解析文件
传入文件名,或者类似于文件的对象都可。
# 方式一:
dom1 = parse('c:\\temp\\mydata.xml')
# 方式二(推荐): 可自动释放资源
with parse('c:\\temp\\mydata.xml') as dom1:
pass
# 方式三:
datasource = open('c:\\temp\\mydata.xml')
dom2 = parse(datasource) # parse an open file
解析字符串
CDATA:在 XML 中,不会被解析器解析的部分数据。
message = '''
<!-- This is list of customers -->
<customers>
<customer ID="C001">
<name>Acme Inc.</name>
<phone>12345</phone>
<comments>
<![CDATA[Regular customer since 1995]]>
</comments>
</customer>
<customer ID="C002">
<name>Star Wars Inc.</name>
<phone>23456</phone>
<comments>
<![CDATA[A small but healthy company.]]>
</comments>
</customer>
</customers>
'''
# 方式一:
dom3 = parseString(message)
# 方式二:
# 将字符串转为 `io.String`对象
import io
msg = io.StringIO(message)
dom4 = parse(msg)
print(type(dom4))
<class 'xml.dom.minidom.Document'>
常用方法、属性
一旦有了DOM文档对象,就可以通过其属性和方法访问XML文档的各个部分。这些属性在DOM规范中定义。文档对象的主要属性是documentElement
属性。它提供了XML文档中的根元素。
在解析 XML 时,所有的文本都是储存在文本节点中的,且该文本节点被视为元素结点的子结点,例如:2005,元素节点 ,拥有一个值为 “2005” 的文本节点,“2005” 不是 元素的值,最常用的方法就是 getElementsByTagName() 方法了,获取到结点后再进一步根据文档结构解析即可。
具体的理论就不过多描述,配合上述 XML 文件和下面的代码,你将清楚的看到操作方法,下面的代码执行的工作是将所有的结点名称以及结点信息输出一下:
# 文档根元素
rootNode = dom4.documentElement
rootNode
<DOM Element: customers at 0x2cc4551d930>
# 获取所有子节点
rootNode.childNodes
[<DOM Text node "'\n '">,
<DOM Element: customer at 0x2cc456ab800>,
<DOM Text node "'\n '">,
<DOM Element: customer at 0x2cc456aba60>,
<DOM Text node "'\n'">]
# 获取第一个子节点
first_node = rootNode.firstChild
# 判断节点类型
first_node.nodeType == first_node.TEXT_NODE
True
# 兄弟节点
# 第一个节点的兄弟节点即父节点的第二个子节点
first_node.nextSibling == rootNode.childNodes[1]
True
# 根据节点名称查找,返回列表
rootNode.getElementsByTagName('customer')
[<DOM Element: customer at 0x2cc45f2aa60>,
<DOM Element: customer at 0x2cc45f2acc0>]
# 可以查找任意层级的节点
rootNode.getElementsByTagName('phone')
[<DOM Element: phone at 0x2cc45f2ab90>, <DOM Element: phone at 0x2cc45f2adf0>]
# 通过索引获取一个节点
rootNode.getElementsByTagName('customer')[1]
rootNode.childNodes[1]
<DOM Element: customer at 0x2cc45f2aa60>
customer = rootNode.getElementsByTagName('customer')[0]
# 节点名
print('tagName:', customer.tagName)
print('nodeName:', customer.nodeName)
# 节点属性
if customer.hasAttribute('ID'):
print("ID:", customer.getAttribute("ID"))
# 获取节点文本数据, 先获取一个叶子节点
name_node = customer.getElementsByTagName('name')[0]
# 再获取其文本节点
text_node = name_node.childNodes[0]
# 或者
# text_node = name_node.firstChild
print('Text data : ', text_node.data)
print('Text value: ', text_node.nodeValue)
print('Text xml :', text_node.toxml())
tagName: customer
nodeName: customer
ID: C001
Text data : Acme Inc.
Text value: Acme Inc.
Text xml : Acme Inc.
# 转字符串
print(customer.toxml(encoding=None))
<customer ID="C001">
<name>Acme Inc.</name>
<phone>12345</phone>
<comments>
<![CDATA[Regular customer since 1995]]>
</comments>
</customer>
# 显示缩进
print(customer.toprettyxml(indent=' ', newl=''))
<customer ID="C001">
<name>Acme Inc.</name>
<phone>12345</phone>
<comments>
<![CDATA[Regular customer since 1995]]>
</comments>
</customer>
# 写入文件
with open('added_customer.xml', 'w') as f:
# 缩进 - 换行 - 编码
dom4.writexml(f, indent=' ', addindent='', newl='', encoding='utf-8')
写入 XML 文件
在写入时,我觉得可分为两种方式:
- 新建一个全新的 XML 文件
- 在已有 XML 文件基础上追加一些元素信息
至于以上两种情况,其实创建元素结点的方法类似,你必须要做的都是先创建 / 得到一个 DOM 对象,再在 DOM 基础上创建 new 一个新的结点。
如果是第一种情况,你可以通过dom=minidom.Document()
来创建;如果是第二种情况,直接可以通过解析已有 XML 文件来得到 dom 对象,例如dom = parse("./customer.xml")
在具体创建元素 / 文本结点时,你大致会写出像以下这样的 “四部曲” 代码:
- ①创建一个新元素结点 createElement()
- ②创建一个文本节点 createTextNode()
- ③将文本节点挂载元素结点上
- ④将元素结点挂载到其父元素上。
现在,我需要新建一个 customer 节点,信息如下:
<customer>
<name ID='003'>蒂法</name>
<phone>32467</phone>
<comments>
<![CDATA[A small but healthy company.]]>
</comments>
</customer>
from xml.dom.minidom import getDOMImplementation
impl = getDOMImplementation()
newdoc = impl.createDocument(None, "customer", None)
top_element = newdoc.documentElement
split_text_node = newdoc.createTextNode('\n ')
name_node = newdoc.createElement('name')
name_node.setAttribute('ID', '003')
name_text = newdoc.createTextNode('蒂法')
name_node.appendChild(name_text)
phone_node = newdoc.createElement('phone')
phone_text = newdoc.createTextNode('32467')
phone_node.appendChild(phone_text)
comments = newdoc.createElement('comments')
comments_text = newdoc.createCDATASection('aaaa')
comments.appendChild(comments_text)
top_element.appendChild(name_node)
top_element.appendChild(phone_node)
top_element.appendChild(comments_node)
print(top_element.toprettyxml(indent=' '))
<customer>
<name ID="003">蒂法</name>
<phone>32467</phone>
<comments>
<![CDATA[A small but healthy company.]]> </comments>
</customer>
第二种方法代码如下:
# 已有文本
message = '''
<customers>
<customer ID="C001">
<name>Acme Inc.</name>
<phone>12345</phone>
<comments>
<![CDATA[Regular customer since 1995]]>
</comments>
</customer>
</customers>
'''
domTree = parseString(message)
# 文档根元素
rootNode = domTree.documentElement
# 新建一个customer节点
customer_node = domTree.createElement("customer")
customer_node.setAttribute("ID", "C003")
# 创建name节点,并设置textValue
name_node = domTree.createElement("name")
name_text_value = domTree.createTextNode("蒂法")
name_node.appendChild(name_text_value) # 把文本节点挂到name_node节点
customer_node.appendChild(name_node)
# 创建phone节点,并设置textValue
phone_node = domTree.createElement("phone")
phone_text_value = domTree.createTextNode("32467")
phone_node.appendChild(phone_text_value) # 把文本节点挂到name_node节点
customer_node.appendChild(phone_node)
# 创建comments节点,这里是CDATA
comments_node = domTree.createElement("comments")
cdata_text_value = domTree.createCDATASection(
"A small but healthy company.")
comments_node.appendChild(cdata_text_value)
customer_node.appendChild(comments_node)
rootNode.appendChild(customer_node)
print(rootNode.toprettyxml(indent=' ',newl='\n'))
<customers>
<customer ID="C001">
<name>Acme Inc.</name>
<phone>12345</phone>
<comments>
<![CDATA[Regular customer since 1995]]>
</comments>
</customer>
<customer ID="C003">
<name>蒂法</name>
<phone>32467</phone>
<comments>
<![CDATA[A small but healthy company.]]> </comments>
</customer>
</customers>
更新 XML 文件
在更新 XML 时,只需先找到对应的元素结点,然后将其下的文本结点或属性取值更新即可,然后保存到文件,具体我就不多说了,代码中我将思路都注释清楚了,如下:
domTree = parse("./customer.xml")
# 文档根元素
rootNode = domTree.documentElement
names = rootNode.getElementsByTagName("name")
for name in names:
if name.childNodes[0].data == "Acme Inc.":
# 获取到name节点的父节点
pn = name.parentNode
# 父节点的phone节点,其实也就是name的兄弟节点
# 可能有sibNode方法,我没试过,大家可以google一下
phone = pn.getElementsByTagName("phone")[0]
# 更新phone的取值
phone.childNodes[0].data = 99999
with open('updated_customer.xml', 'w') as f:
# 缩进 - 换行 - 编码
domTree.writexml(f, addindent=' ', encoding='utf-8')
参考:
- https://docs.python.org/3.8/library/xml.dom.minidom.html
- https://blog.csdn.net/qq_37174526/article/details/89489212
- https://docs.python.org/3.8/library/xml.html#xml-vulnerabilities