关于python 实现xml的读取以及标签节点的属性值变更

发表原由

近期做测试的朋友找我咨询操作xml文件的问题,但是由于鄙人不才,作为一个前端,对于python并不是怎么熟悉,但是好在在这个时代,互联网是发达的,我很快知道xml.etree.ElementTree 这个库可以对xml进行操作,同时xpath格式路径可以很容易做到我们想要的节点,并对其进行更改.既然找到了方法,那么直接尝试写吧,很不幸,遇到了不少未知问题。

牛刀小试(这里的编码记得要加,不然如果xml是中文,会导致乱码的情况)

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 文件中那个标签节点,属性值对应的是存在的,是哪里出现了问题呢?

xpath 问题校正

当我们的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 文件,每个节点自带前缀ns0

关于python 实现xml的读取以及标签节点的属性值变更_第1张图片

那么这又是我们不想要的 ,这种情况的发生,是因为什么呢?

解决前缀问题

# 这里就是将我们xml中自定义的命名空间转换为"",这样我们就能得到不带前缀的xml更新文件了 
ET.register_namespace("",'urn:test-org:v1')

实际运行效果如下
关于python 实现xml的读取以及标签节点的属性值变更_第2张图片
完整代码示例展示

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 文件的判断处理,希望大家提供意见参考。

你可能感兴趣的:(python,xml)