python xml(ElementTree)

发布时间 2023-10-08 20:57:50作者: 流翎

python xml处理(ElementTree)

1. 模块导入

from xml.etree.ElementTree import ElementTree,Element,SubElement

2. 对象概述

  • ElementTree:表示整个xml层级结构
  • Element:表示树形结构中的父节点
  • SubElement:表示树形结构中的所有子节点,有些节点既可以是父节点,也可以是子节点

3. ElementTree

3.1. API

getroot(self)
用途:返回xml树的根节点;
返回值:Element;

parse(self, source, parser=None)
用途:解析xml文件;
参数:source为xml文件名或文件对象,parser为xml解析器,默认为XMLParser;
返回值:返回xml树的根节点,Element;

find(self, path, namespaces=None)
用途:根据tag name或path查找首个匹配的Element对象,同getroot().find(path);
参数:path为包含tag或path的字符串,namespaces为命名空间前缀到全名的可选映射;
返回值:返回首个匹配的Element对象,查找失败则返回None;

findall(self, path, namespaces=None)
用途:根据tag name或path查找全部匹配的Element对象,同getroot().findall(path);
参数:path为包含tag或path的字符串,namespaces为命名空间前缀到全名的可选映射;
返回值:返回list,按xml文档顺序,包含所有匹配的Element对象;

findtext(self, path, default=None, namespaces=None)
用途:根据tag name或path查找首个匹配的Element对象的text值,同getroot().findtext(path);
参数:path为包含tag或path的字符串,namespaces为命名空间前缀到全名的可选映射;
返回值:返回首个匹配的Element对象的text值,查找失败则返回None;

write(self, file_or_filename, encoding=None, xml_declaration=None, default_namespace=None, method=None, *, short_empty_elements=True)
用途:将element树写入为xml文件
参数

  • file_or_filename为文件名或已打开的文件对象;
  • encoding为编码方式,默认为US-ASCII;
  • xml_declaration bool值,指示输出文件时是否需要添加xml文件头部声明,为None时,则如果编码不是US-ASCII、UTF-8或Unicode中的任何一种,则会添加XML声明;
  • default_namespace,设置默认xml命名空间;
  • method为xml、html、text或c14n,默认为xml;
  • short_empty_elements,控制不包含内容的元素的格式。

4. Element

xml示例:

<field name="ack" type="uint16_t">响应码</field>

Element中类对象的值

  • tag:对应xml文件Tag部分,以字符串结构存储,如上示例的'filed';
  • attrib:对应xml文件Attribute部分,以字典结构存储,如上示例的'name="ack"';
  • text:对应xml文件Element部分,以字符串结构存储,xml数据标签包裹的内容,如上示例的'响应码';

4.1. API

append(self, subelement, /)
用途:添加子对象;
参数:subelement为子对象;

clear(self, /)
用途:清空元素的后代、属性、text和tail也设置为None;

extend(self, elements, /)
用途:增加一串元素对象作为子元素
参数:elements为子对象;

find(self, /, path, namespaces=None)
用途:根据tag name或path查找首个匹配的Element对象;

findall(self, /, path, namespaces=None)
用途:根据tag name或path查找全部匹配的Element对象;
返回值:返回list;

findtext(self, /, path, default=None, namespaces=None)
用途:根据tag name或path查找全部匹配的Element对象的text值;

get(self, /, key, default=None)
用途:获取key对应的属性值,如该属性不存在则返回default值;

set(self, key, value, /)
用途:设置新的属性键与值;

insert(self, index, subelement, /)
用途:在指定位置插入子对象;
参数:subelement为子对象;

items(self, /)
用途:根据属性字典返回一个列表,列表元素为(key, value);

keys(self, /)
用途:返回包含所有元素属性键的列表;

remove(self, subelement, /)
用途:删除子对象;
参数:subelement为子对象;

5. 使用示例

以下代码示例均使用如下xml文件:

<?xml version='1.0' encoding='utf-8'?>
<messages>
    <message name="SetDebugParam" id="100">
        <description>调试配置</description>
        <field name="opc" type="uint8_t">操作码</field>
        <field name="parameter1" type="uint32_t">参数1</field>
        <field name="parameter2" type="uint32_t">参数2</field>
        <field name="parameter3" type="uint32_t">参数3</field>
    </message>
</messages>

5.1. 文件解析

#!/usr/bin/env python
from xml.etree.ElementTree import ElementTree,Element,SubElement

# 解析field
def parse_field(message):
    field_info = []
    for item in message:
        field_node = {}
        if item.tag == "field":
            field_node["name"] = item.attrib["name"]
            field_node["type"] = item.attrib["type"]
            field_node["text"] = item.text
            field_info.append(field_node)
    return field_info


# 解析文件
def parse_msg(xml_name):
    node_table = {}

    # 读取并解析xml文件
    tree = ElementTree()
    tree.parse(xml_name)

    # 查找某个路径匹配的所有节点
    msg_blocks = tree.findall("message")

    for msg in msg_blocks:
        # 获取name对应的值
        node_table["name"] = msg.get("name")
        # 获取id对应的值
        node_table["id"] = msg.get("id")
        # 解析fields
        node_table["fields"] = parse_field(msg) 

    return node_table

node_table = parse_msg("./test.xml")

# 打印
print(node_table)

输出结果:

{'name': 'SetDebugParam', 'id': '100', 'fields': [{'name': 'opc', 'type': 'uint8_t', 'text': '操作码'}, {'name': 'parameter1', 'type': 'uint32_t', 'text': '参数1'}, {'name': 'parameter2', 'type': 'uint32_t', 'text': '参数2'}, {'name': 'parameter3', 'type': 'uint32_t', 'text': '参数3'}]}

5.2. 创建文件

#!/usr/bin/env python
from xml.etree.ElementTree import ElementTree,Element,SubElement

# 新建一个节点
def create_node(tag, property_map, text):
    element = Element(tag, property_map)
    element.text = text
    return element

def make_xml(output_xml):
    root = Element("messages")
    
    # 新建节点
    message_node = create_node("message", {"name": "SetDebugParam", "id": "100"}, "")
    root.append(message_node)

    description_node = create_node("description", {}, u'调试配置')
    message_node.append(description_node)

    field_opc = create_node("field", {"name": "opc", "type": "uint8_t"}, u'操作码')
    field_p1 = create_node("field", {"name": "parameter1", "type": "uint32_t"}, u'参数1')
    field_p2 = create_node("field", {"name": "parameter2", "type": "uint32_t"}, u'参数2')
    field_p3 = create_node("field", {"name": "parameter3", "type": "uint32_t"}, u'参数3')

    message_node.append(field_opc)
    message_node.append(field_p1)
    message_node.append(field_p2)
    message_node.append(field_p3)

    # 创建ElementTree对象
    root_tree = ElementTree(root)

    # 创建xml文件
    root_tree.write(output_xml, encoding="utf-8", xml_declaration=True)
    
make_xml("./output.xml")

输出结果:

<?xml version='1.0' encoding='utf-8'?>
<messages><message name="SetDebugParam" id="100"><description>调试配置</description><field name="opc" type="uint8_t">操作码</field><field name="parameter1" type="uint32_t">参数1</field><field name="parameter2" type="uint32_t">参数2</field><field name="parameter3" type="uint32_t">参数3</field></message></messages>

5.3. 格式化xml文件

# xml格式化,调整缩进和换行; indent:缩进 newline: 换行 level: 缩进级别
def pretty_xml(element, indent, newline, level=0):
    if element is not None:  # 判断element是否有子元素    
        if (element.text is None) or element.text.isspace():  # 如果element的text没有内容
            element.text = newline + indent * (level + 1)
        # else:  # 此处两行如果把注释去掉,Element的text也会另起一行
        #     element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level
    temp = list(element)  # 将element转成list
    for subelement in temp:
        # 如果不是list的最后一个元素,说明下一个行是同级别元素的起始,缩进应一致
        if temp.index(subelement) < (len(temp) - 1):  
            subelement.tail = newline + indent * (level + 1)
        # 如果是list的最后一个元素, 说明下一行是母元素的结束,缩进应该少一个   
        else:   
            subelement.tail = newline + indent * level
        pretty_xml(subelement, indent, newline, level=level + 1)  # 对子元素进行递归操作

def make_xml():
    ...

    # 格式化xml树
    pretty_xml(root, '\t', '\n')

    # 创建ElementTree对象
    root_tree = ElementTree(root)
    
    # 创建xml文件
    root_tree.write(output_xml, encoding="utf-8", xml_declaration=True)

输出结果:

<?xml version='1.0' encoding='utf-8'?>
<messages>
        <message name="SetDebugParam" id="100"><description>调试配置</description>
                <field name="opc" type="uint8_t">操作码</field>
                <field name="parameter1" type="uint32_t">参数1</field>
                <field name="parameter2" type="uint32_t">参数2</field>
                <field name="parameter3" type="uint32_t">参数3</field>
        </message>
</messages>

5.4. 修改节点

#!/usr/bin/env python

from xml.etree.ElementTree import ElementTree,Element,SubElement

# 修改节点text
def change_node_text_by_tag(nodelist, tag, text):
    for node in nodelist:
        if node.tag == tag:
            node.text = text

# 修改/新增/删除节点属性及属性值
def change_node_arrtib(nodelist, kv_map, is_delete=False):
    for node in nodelist:
        for key in kv_map:
            if is_delete:
                if key in node.attrib:
                    del node.attrib[key]
            else:
                node.set(key, kv_map.get(key))

# 修改节点属性,10进制数字改为16进制
def change_node_attrib_dec_to_hex(nodelist, kv_map):
    for node in nodelist:
        for key in kv_map:
            node.set(key, str(hex(int(node.attrib[key], 10))))

# 删除子对象
def del_node_by_tag(nodelist, tag):
    for node in nodelist:
            if node.tag == tag:
                nodelist.remove(node)

# 修改文件
def modify_msg(input_xml, output_xml):
    node_item = {}

    # 读取并解析xml文件
    tree = ElementTree()
    tree.parse(input_xml)

    # 查找某个路径匹配的所有节点
    msg_list = tree.findall("message")

    # 修改message id 为16进制
    change_node_attrib_dec_to_hex(msg_list, {"id": ""})
    
    for msg in msg_list:
        # 删除description子对象
        del_node_by_tag(msg, "description")

        # 修改field type 为 int32_t
        change_node_arrtib(msg, {"type": "int32_t"})
        # 新增属性
        change_node_arrtib(msg, {"direction": "down"})
        # 删除属性
        change_node_arrtib(msg, {"name":""}, True)

    # 创建xml文件
    tree.write(output_xml, encoding="utf-8", xml_declaration=True)

modify_msg("./test.xml", "./test_new.xml")

输出结果:

<?xml version='1.0' encoding='utf-8'?>
<messages>
        <message name="SetDebugParam" id="0x64">
                <field type="int32_t" direction="down">操作码</field>
                <field type="int32_t" direction="down">参数1</field>
                <field type="int32_t" direction="down">参数2</field>
                <field type="int32_t" direction="down">参数3</field>
        </message>
</messages>