近期做测试的朋友找我咨询操作xml文件的问题,但是由于鄙人不才,作为一个前端,对于python并不是怎么熟悉,但是好在在这个时代,互联网是发达的,我很快知道xml.etree.ElementTree
这个库可以对xml进行操作,同时xpath格式路径可以很容易做到我们想要的节点,并对其进行更改.既然找到了方法,那么直接尝试写吧,很不幸,遇到了不少未知问题。
import xml.etree.ElementTree as ET
# 解析 XML 文件
with open('input.xml', 'r', encoding='utf-8') as f:
tree = ET.parse(f)
root = tree.getroot()
# 查找所有符合条件的节点,修改属性值
for id_node in root.findall(".//id[@age='10']"):
id_node.set('age', '18')
# 将修改后的 XML 写回到文件中
tree.write('output.xml',encoding='utf-8',xml_declaration=True)
通过这段代码,我发觉python 真的简单,但是我们遇到了什么问题呢,一旦我们的xml文件是带命名空间的,那么这个xpath是无法定位到我们想要的节点的,也就是说我们通过findall 这个方法,什么都没有找到,可是实际xml 文件中那个标签节点,属性值对应的是存在的,是哪里出现了问题呢?
当我们的xml 文件是带命令空间的时候,那么我们的xpath就需要带上我们的命名空间(所谓xml的命名空间就是根节点中属性值为xmlns所代表的值),如下
例如我们的xml 文件如下
<a xmlns="urn:test-org:v1">
<id name="Anna" age="10" />
<id name="Bob" age="12" />
a>
# 其实我们的xpath路径规则是这样写的,这意思就是我们找的是id标签节点,属性值age=10的节点数据
".//{urn:test-org:v1}id[@age='10']"
# 我们修改上述错误代码变更为 ,修改age 为10的变更为18
for id_node in root.findall(".//{urn:test-org:v1}id[@age='10']"):
id_node.set('age', '18')
那么这又是我们不想要的 ,这种情况的发生,是因为什么呢?
# 这里就是将我们xml中自定义的命名空间转换为"",这样我们就能得到不带前缀的xml更新文件了
ET.register_namespace("",'urn:test-org:v1')
import xml.etree.ElementTree as ET
ET.register_namespace("",'urn:test-org:v1')
# 解析 XML 文件
with open('input.xml', 'r', encoding='utf-8') as f:
tree = ET.parse(f)
root = tree.getroot()
# 查找所有符合条件的节点,修改属性值
for id_node in root.findall(".//{urn:test-org:v1}id[@age='10']"):
id_node.set('age', '18')
# 将修改后的 XML 写回到文件中
tree.write('output.xml',encoding='utf-8',xml_declaration=True)
鉴于本人喜欢做点封装,所以对于xml的命名空间的获取,以及python中字典的遍历,字符串的变量传参,文件操作系统的方法都进行了一定的资料查询,以下是本人根据朋友所需封装的方法。我们只需要在意xml 文件路径以及变更传参字典
import xml.etree.ElementTree as ET
import os
def update_xml_data_by_xpath(xml_file_path, attr_dic):
"""局部更新xml文件,根据指定节点属性
Args:
xml_file_path (string): xml文件路径
attr_dic (dictionary): 所需更新的节点 参数格式为
{节点标签名:{attr:"所需更改的节点属性名",old_value:原本属性所对应的值,new_val:更新后的值}}
示例:{"id":{"attr":"extension","old_val":"00000001","new_val":"00000002"}}
Returns:
_type_: NONE
"""
if not xml_file_path:
return print("xml file path can't be empty")
if not attr_dic:
return print("attr_dic can't be empty")
# 读取xml 文件,这里主要是避免文件中存在中文,读取乱码现象
with open(xml_file_path, 'r', encoding='utf-8') as f:
# xml 文档转化为 节点元素树
tree = ET.parse(f)
# 获取树根元素
root = tree.getroot()
namespace = ""
# 避免存在xml 文件不存在命名空间的情况,导致数组超界发生
try:
namespace = root.tag.split('}')[0].split("{")[1]
# 命名空间前缀
ET.register_namespace("", namespace)
except IndexError:
print('该xml文件不存在命名空间,可不替换处理')
for key, value in attr_dic.items():
attr = value.get('attr')
old_val = value.get('old_val')
new_val = value.get('new_val')
if namespace:
xpath = f".//{{{namespace}}}{key}[@{attr}='{old_val}']"
else:
xpath = f".//{key}[@{attr}='{old_val}']"
# 通过xpath 匹配所需要找的节点所对应的属性值数据,并修改属性数据
for id_node in root.findall(xpath):
id_node.set(attr, new_val)
# 获取文件名
file_name = os.path.basename(xml_file_path)
# 获取文件所在目录
dir_path = os.path.dirname(xml_file_path)
# 更新后的文件路径
update_file_path = os.path.join(dir_path, f'update_{file_name}')
# 输出我们更改后的xml 文件
tree.write(update_file_path, encoding='utf-8',
xml_declaration=True, method="xml")
print(
f'The xml file is updated successfully and the file is output to{update_file_path}')
update_xml_data_by_xpath(
'./input.xml', {"id": {"attr": "age", "old_val": "18", "new_val": "20"}})
追加不存在命令空间xml 文件的判断处理,希望大家提供意见参考。