XML的解析与反解析

Universal Parser 基于 xml.parsers.expat Python内置库编写,expat 相比其它库就一个字 !


本页用到的XML数据

(点击此处展开)查看XML数据
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:aq="aquatic">
    <animals>
        <animal type="dog" id="1">
            <!-- 大黄是人类的朋友 -->
            <name>大黄</name>
            <age>11</age>
            <sex>male</sex>
            <desc><![CDATA[<大黄><一只狗>]]></desc>
        </animal>
        <animal type="dog" id="2">
            <!-- 小白是个傻狗 -->
            <name>小白</name>
            <age>1</age>
            <sex>female</sex>
            <desc>小白身上有斑点</desc>
        </animal>
        <animal type="cat" id="01">
            <name>小卡</name>
            <age>3</age>
            <sex>female</sex>
            <desc>小卡是只咖啡猫</desc>
        </animal>
    </animals>
    <version>0.0.1</version>
    <aq:halobios>
        <!-- 海洋生物 -->
        <desc>美丽的大海</desc>
    </aq:halobios>
    <text name="test_text">123456</text>
    <text2 name="test_text2"><![CDATA[我是CDATA]]></text2>
</root>

取代xmltodict

如果您不需要此框架提供的丰富操作接口,只需要 XMLPyton字典 的功能,则仅需调用 XmlParser.parse_odict 即可。

您将获得一个纯 OrderedDict 数据包,不包含任何操作。数据包的结构类似于 xmltodict 解析出来的内容,但会比 xmltodict 更加完整一点 ,同样会记住所有节点的位置。

from UniversalParser import XmlParser
from UniversalParser.xml.unparser import unparse_dict
import UniversalParser as UP

docs, xml_declare = XmlParser.parse_odict(
    xml_data
    , encoding = None # 编码,默认 None
    , namespace_separator = None # 命名空间解析(暂时不建议使用)
    , attr_prefix = UP.ATTR_PREFIX # 属性前缀,默认 '@'
    , cdata_key = UP.CDATA_KEY # 解析时文本域索引名,默认 '#text'
    , cdata_self_key = UP.CDATA_SELF_KEY # 索引时文本域索引名,默认 'text_'
    , comment_key = UP.COMMENT_KEY # 注释索引名,默认 '#comment'
    , combine_cdata = True # 是否合并 文本 和 CDATA
    , include_comment = False # 是否解析注释
    , cdata_separator = UP.CDATA_SEPARATOR # 同一节点多行文本地连接符,默认空格
    , loc_key = UP.LOC_KEY # 位置属性名,默认 '__loc__'
    , include_loc = False # 是否解析时包含位置信息,默认不包含,即默认没有'__loc__'属性
)

import json
print(json.dumps(docs, ensure_ascii=False, indent=4))

'''反向解析(即 JSON 转 XML 功能)

请确保 `XmlParser.parse_odict` 和 `unparse_dict` 参数传递的一致性,只有保证了一致性,才能够 1:1 还原初始XML。
'''
print(unparse_dict(docs, xml_declare, out_stream=None))

读XML

import UniversalParser as UP

# 假设上面的 XML 数据存放在 xml_data 变量中
xmlManager = UP.parse_xml(xml_data)

# 假设上面的 XML 数据存放在 data.xml 文件中
xmlManager = UP.parse('data.xml', encoding='utf-8')
其它所有的关键字参数说明:

  • analysis_text - 是否对文本域建立索引,默认 True 。设置为 True ,可以使用与文本域相关的所有方法,但在不需要文本域索引的情况下,建议设置为 False
  • combine_cdata - 是否将 CDATA 视为文本域处理,默认 True 。设置为 False 后,文本域将不包含 CDATA 内容,CDATA 内容将被独立解析;
  • open_cdata - 是否对 CDATA 单独建立索引,默认 False 。如需开启,必须同时设定 open_cdata=Truecombine_cdata=False
  • include_comment - 是否包含注释,默认 False
  • open_comment - 是否对注释建立索引,默认 False。如需启用,必须同时设定 include_comment=Trueopen_comment=True
  • cdata_separator - 当节点内文本域被拆成多行时的连接符,默认空格连接;
  • analysis_mode - 选择解析引擎,默认非递归算法,如需使用递归算法请赋值为 AnalysisMode.RECURSION_OLD
  • include_loc - 是否需要包含原XML中各个节点的位置信息,默认 True。设置为 False 将导致反向解析生成的 XML 是乱序的;
  • attr_prefix - 属性解析前缀,默认"@",非必要不修改;
  • namespace_separator - 启用命名空间解析,默认 None (当前版本不建议使用);
  • cdata_key - 文本域解析名,默认"#text",非必要不修改;
  • real_cdata_key - 文本域索引名,默认"text_",非必要不修改;
  • cdata_self_key - CDATA 的解析及索引名,默认"#CDATA",非必要不修改;
  • comment_key - 注释的解析及索引名,默认"#comment",非必要不修改;

注意: 若XML中存在 CDATA,且其中存在需要在XML中转义的字符时,必须同时开启参数:combine_cdata=Falseopen_cdata=True,必须!!!。

链式属性

【我更喜换用此功能获取属性值或者文本域,而不是用于路径查找。】

XML 中的每一个 标签名属性名 均会被视为 UniversalParser对象的属性。因此可以通过 . 运算符获取数据。但也因此产生了一定的局限性,当同一个节点的 孩子节点标签名该节点的属性名 冲突时,该节点的属性将会丢失,此问题会在未来的版本中做适当处理。

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
    , open_comment = True
    , include_comment = True
)

'''旧版本中 xmlManager.xml 和 xmlManager.document 完全等价
'''
root = xmlManager.document.root



'''text_ 来自于 real_cdata_key 传入的值,默认是 text_
'''
print(root.text_) # 每一个节点均有文本域,为空就是空字符串



'''任何一个节点都可以用属性的方式进行访问

但注意,类似 copy 之类的标签名,会引发内部变量名冲突,此时需要用 node['copy'] 获取
'''
animals = root.animals.animal



'''严格按照原来 XML 中的顺序进行输出
'''
print([_a.name for _a in animals]) # ['大黄', '小白', '小卡']
print([_a.type for _a in animals]) # ['dog', 'dog', 'cat']

使用链式属性查找的几个注意点

【情形一】:

<a>
    <b>0</b>
</a>
情形一的情况下,通过 xmlManager.document.a.b 获取到的是 ChainDict 类型。
【情形二】:
<a>
    <b>0</b>
    <b>1</b>
    <b>2</b>
</a>
情形二的情况下,通过 xmlManager.document.a.b 获取到的是 List[ChainDict] 类型。

文本域

链式属性查找 下的文本域获取

import UniversalParser as UP
from UniversalParser import SM

xmlManager = UP.parse_xml(xml_data)
root = xmlManager.document.root

'''当节点只有文本域没有属性时,获取的将直接是文本域内容
'''
print(root.version) # 0.0.1

# 当唯一标签名称节点存在属性时,获取的值是 ChainDict 类型
# ChainDict 类型有下列四种获取文本域的方式
'''一、通过 find_text 方法获取
'''
print(xmlManager.find_text(root.text)) # 123456

'''二、内置属性名获取(不推荐,可能会藏有版本迁移失效的问题)
'''
print(root.text.text_) # 123456。text_ 来自于 real_cdata_key 设置的值

'''三、魔法操作符获取
'''
print(root.text & SM.text) # 123456

'''四、格式化获取
'''
def format_func(s):
    return '二次处理:' + s
print(xmlManager.find_text(root.text, format_func)) # 二次处理:123456
快速定位方法 查找后的文本域获取
# 快速定位查找获取到的类型只有两种:List[ChainDict] 和 ChainDict
import UniversalParser as UP
from UniversalParser import SM

xmlManager = UP.parse_xml(xml_data)

# 下面两种获取节点的方式完全等价(内部均调用了 find_nodes_by_tag 方法)
version = (xmlManager | 'version') ^ 1 # | 限制标签名,^ 限制输出个数(具体请看【魔法定位】)
version = xmlManager.find_nodes_by_tag('version', one_=True)

'''和链式查找的节点唯一不同的就是:

【链式查找】的节点:当节点只有文本域没有属性时,获取的将直接是文本域内容。  
而【快速定位方法】查找到的节点没有这种特性,其它获取方式均一致。
'''
print(xmlManager.find_text(version)) # 0.0.1
print(version.text_) # 0.0.1
print(version & SM.text) # 0.0.1
def format_func(s):
    return '二次处理:' + s
print(xmlManager.find_text(version, format_func)) # 二次处理:0.0.1


'''特别地,可以直接通过属性获取文本域
'''
text = xmlManager.find_text_by_attrs(name="test_text")
print(text) # 123456
find_text_by_attrs 方法还接受两个关键字参数:text_typeformat_func

  1. text_type 目前有三种取值:TextType.STR、TextType.FLOAT、TextType.INT。
  2. format_func 参数的用法,参考 find_text 函数。

单独获取 CDATA 的值

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, open_cdata=True, combine_cdata=False)
animals = xmlManager | 'animal' # ChainManager 内部实现了 __getitem__ 方法
print(animals[0].desc.text_) # 输出空字符串
print(animals[0].desc[UP.CDATA_SELF_KEY]) # ['<大黄><一只狗>']

获取 注释 内容

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, include_comment=True, open_comment=True)
animals = list(xmlManager | 'animal')
print(animals[1][UP.COMMENT_KEY]) # ['小白是个傻狗']

快速定位查找

属性定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

'''根据属性获取唯一的节点

若无任何符合条件的节点,抛出 NoNodesFound 异常
若超过一个符合条件的节点,抛出 MoreNodesFound 异常
'''
node = xmlManager.find_nodes_by_attrs(name="test_text", one_=True)
node = xmlManager.find_node_by_attrs(name="test_text") # 完全等价于上一行语句
print(node & UP.SM.text) # 123456

'''根据属性获取多个符合条件节点
'''
nodes = xmlManager.find_nodes_by_attrs(name="test_text")
print(nodes[0] & UP.SM.text) # 123456

'''多个属性同时限制查找(且的关系,必须同时满足)
'''
nodes = xmlManager.find_nodes_by_attrs(name="test_text", other="ttt")
print(len(nodes)) # 0

属性定位+获取指定位置

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

'''第一个参数是要获取的索引集,非法的索引将被忽略
'''
dogs = xmlManager.find_nodes_by_indexs([1,], type="dog")
print(dogs[0].name) # 小白

标签名定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

nodes = xmlManager.find_nodes_by_tag('animal', one_=False)
print(len(nodes)) # 3

文本域定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

'''注意

当 open_cdata=False 且 combine_cdata=True 时,CDATA将出现在排查范围内
'''
node = xmlManager.find_nodes_by_text('123456', one_=True)
print(node.name) # test_text

CDATA定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, open_cdata=True, combine_cdata=False)

node = xmlManager.find_nodes_by_cdata('<大黄><一只狗>', one_=True)
print((node & UP.SM.parent).name) # 大黄
ChainDict 类型用 & 运算符,后面跟 UP.SM.parent 表示获取其父节点。

注释内容定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, open_comment=True, include_comment=True)

node = xmlManager.find_nodes_by_comment('海洋生物', one_=True)
print(node.desc) # 美丽的大海

树结构关系定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

# 获取 animals 标签下的 'animal' 标签,限制 'animal' 标签文本域为空
# 有名为 id 的属性,且属性值为 '01'

# 方法一:
limit_node = (xmlManager | 'animals') ^ 1
animal = xmlManager.find_nodes_with_ancestor(
    unique_node = limit_node
    , tag_ = 'animal' # 带查找节点的标签名限制
    , text_ = '' # 待查找节点的文本域限制
    , find_func = xmlManager.find_nodes_by_tag_text_attrs # 指定内部查找的函数类型
    , constraint = None # 限制节点的传参数据包
    , one_ = True # 限制有且仅有一个输出
    , id = '01' # 待查找节点的属性限制,find_func 参数指向的方法必须要支持属性查找
)
print(animal.name) # 小卡

# 方法二:(完全等价于方法一)
animal = xmlManager.find_nodes_with_ancestor(
    constraint = {
        'args': [], 'kwargs': {'tag_': 'animals'}
    }
    , tag_ = 'animal'
    , text_ = ''
    , find_func = xmlManager.find_nodes_by_tag_text_attrs
    , one_ = True
    , id = '01'
)
print(animal.name) # 小卡
注意:constraintunique_node 只允许选其一使用。

find_nodes_with_ancestor - 用祖先节点限制查找,换句话说。unique_nodeconstraint 所指定的节点只要存在于待查找节点的 祖先节点范围内,该节点就会被找到。

同样的方法,目前还有两个:
find_nodes_with_descendantsfind_nodes_with_sibling_ancestor,前者是子孙节点限制查找,后者是祖先及其兄弟节点限制查找。 参数和注意点完全和 find_nodes_with_ancestor 一致。

祖先及其兄弟节点】一词的解释:
a节点有一个父节点为bb有两个兄弟节点(c, d)。

  • 假设b为根节点,则a的祖先及其兄弟节点就是(b, c, d)。
  • 假设b不是根节点,b有一个父节点mm为根节点),c的父节点为hh有一个兄弟节点rm有两个兄弟节点(u, k)。则a节点的祖先及其兄弟节点 就是[(b, c, d), (m, u, k)]。如您所见,它是一层一层往上搜索的,只要在某一层找到符合条件的节点,就停止搜索,否则会遍历完所有的相关节点。

混合定位

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data)

'''标签 + 属性定位
'''
nodes = xmlManager.find_nodes_by_tag_and_attrs(
    tag_ = 'text'
    , name = 'test_text'
    , one_= False
)
print(nodes[0] & UP.SM.text) # 123456

'''标签 + 文本域 + 属性定位
'''
nodes = xmlManager.find_nodes_by_tag_text_attrs(
    tag_ = 'text'
    , text_ = '1234567'
    , name = 'test_text'
    , one_= False
)
print(len(nodes)) # 0
为了区别于有可能存在的 tag 标签名,这里使用 tag_ 作为参数名称,其它参数名称取名均有此约束。(仅混合定位时需要注意)

增删改操作

插入新节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animals = (xmlManager | 'animals') ^ 1

insert_node = xmlManager.insert(
    animals
    , tag = 'animal'
    , attrs = {'type':'cat'}
    , text = ''
)
print(insert_node.type) # cat

xmlManager.save_as_xml()
这种插入方式只允许一个节点一个节点的插入,如果需要插入一个结构数据就需要使用者自己编写结构逻辑。

插入XML字符串

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animals = (xmlManager | 'animals') ^ 1

insert_xml = '''
<animal type="cat" id="02">
    <name>小蝶一号</name>
    <age>0.5</age>
    <sex>Unkown</sex>
    <desc>啥子</desc>
</animal>
'''

xmlManager.insert_xmlstring(animals, insert_xml) # 尾插到该节点

xmlManager.save_as_xml()
特别的,通过XML字符串插入的对象,暂时不支持 '&' 运算符。

复制/拷贝节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

root = xmlManager.xml.root
animals = (xmlManager | 'animals') ^ 1

# 将 animals 拷贝一份到 root.text 中
xmlManager.copy_to(root.text, animals)

xmlManager.save_as_xml()
除了支持拷贝 ChainDictList[ChainDict] 之外,还支持任意 dict 类型和 list 类型的组合。

插入新的属性

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, open_cdata=True, combine_cdata=False)
node = xmlManager.find_nodes_by_tag('animals', one_=True)
print(node & UP.SM.attr_names) # ['__loc__']

xmlManager.insert_attrs(node, name='插入')

print(node & UP.SM.attr_names) # ['__loc__', 'name']
__loc__ 属性每一个节点都会在解析时添加进去,目的是为了记录节点的位置信息。

插入注释

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
    , open_comment = True
    , include_comment = True
)
xmlManager.insert_comment(xmlManager.xml.root, '新的注释')
xmlManager.save_as_xml()
启用注释功能,请务必开启 include_comment=Trueopen_comment=True

插入CDATA

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)
xmlManager.insert_cdata(xmlManager.xml.root, '新的CDATA')
xmlManager.save_as_xml()
必须将 CDATA 参数开启,即:combine_cdata=Falseopen_cdata=True

移动节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

root = xmlManager.xml.root
animals = (xmlManager | 'animals') ^ 1

# 将 animals 从 root 移动到 root.text
xmlManager.move(animals, root.text)

xmlManager.save_as_xml()

交换节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

root = xmlManager.xml.root
animals = (xmlManager | 'animals') ^ 1

# 将两个节点互换(包括位置信息)
xmlManager.swap(animals, root.text)

xmlManager.save_as_xml()

平移节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

root = xmlManager.xml.root
animals = (xmlManager | 'animals') ^ 1

# 向上平移
xmlManager.pan_up(animals.animal[2], 2) # 向上平移2个单位
# 平移到最顶部
xmlManager.pan_up(animals.animal[2], top=True)

# 向下平移
xmlManager.pan_down(animals.animal[0], 2) # 向下平移2个单位
# 平移到最底部
xmlManager.pan_down(animals.animal[0], bottom=True)

xmlManager.save_as_xml()
注意:平移只对该节点的兄弟节点有效。特别的,如果<a></a>节点有三个孩子节点<b>1</b><b>2</b><c>1</c>。则 b1 向下平移2个单位后,顺序不会变成 <b>2</b><c>1</c><b>1</b>,而是 <b>2</b><b>1</b><c>1</c>。这是因为同类型的节点平移,只会在用类型节点下生效(若您需要此方法,请您务必了解此细节)。

平移的步数必须 >= 0,如果步数超过了节点的总数,则默认移动到端点,向上平移就是移动到最上方,向下平移就是移动到最下方。

清空文本域

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)
name = ((xmlManager @ {'id': '1'}) | 'name') ^ 1
xmlManager.clear_text(name)

'''批量清空
'''
xmlManager.batch_clear_text([name, ])

xmlManager.save_as_xml()
因为存在文本域索引的原因,暂时不支持 name='' 这样的清空方式。

清空节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

root = xmlManager.xml.root

# 清空属性
xmlManager.clear_node_attrs(root.text)

# 清空文本域及所有的孩子节点
xmlManager.clear_node_content(root.text)

# 上述两种方法的结合
xmlManager.clear_node(root.text)

xmlManager.save_as_xml()

删除节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data, open_cdata=True, combine_cdata=False)

'''方法一
'''
animal = (xmlManager @ {'id': '01'}) ^ 1 # @ 表示属性限制
xmlManager.popitem(animal) # 删除 id = '01' 的 animal 节点

'''方法二
'''
xmlManager.pop_node_by_attrs(id="01") # 该节点必须只能被唯一查找
xmlManager.pop_nodes_by_attrs(type="dog")

'''输出为 XML
'''
xmlManager.save_as_xml('ttt.xml')
删除一个节点并不是简单的 del,还包括删除自身的所有相关索引,以及子孙节点的相关索引。请务必使用本工具提供的方法删除,否则数据无法在运行时及时更新,严重可能导致数据关系混乱。

删除节点属性

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)
animal = xmlManager.find_nodes_by_tag('animal')[0]
print(animal & UP.SM.attr_names) # ['__loc__', 'type', 'id']
print(animal.type) # dog

del_attr: Tuple[str, str] = xmlManager.del_attr(animal, 'type')

print(animal & UP.SM.attr_names) # ['__loc__', 'id']

'''批量删除属性

删除不存在的属性时会报错。
'''
del_attrs: List[Tuple[str, str]] = xmlManager.batch_del_attr(animal, 'type', 'id')

xmlManager.save_as_xml()

修改节点属性值

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)
animal = xmlManager.find_nodes_by_tag('animal')[0]
print(animal.type) # dog

old_attr: Tuple[str, str] = xmlManager.update_attr(animal, "type", "old_dog")

print(animal.type) # old_dog

'''批量修改属性值

下面两个方式功能上一模一样。
'''
old_attrs: List[Tuple[str, str]] = xmlManager.batch_update_attrs(animal, type="old_dog")
old_attrs: List[Tuple[str, str]] = xmlManager.batch_update_attrs(animal, ("type", "old_dog"))

xmlManager.save_as_xml()

修改节点文本域

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)
name = ((xmlManager @ {'id': '1'}) | 'name') ^ 1
print(name & UP.SM.text) # 大黄

old_text = xmlManager.update_text(name, '大黄二号')

print(name & UP.SM.text) # 大黄二号

xmlManager.save_as_xml()
后面的版本会实现直接用属性赋值的方式修改文本域,目前因为文本域索引的存在,不容易实现。

树结构操作

注意: 当节点是拷贝的或者通过字符串插入的,请勿使用 & 运算符。

获取父节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animal = list(xmlManager | 'animal')[1]

# 下面三行完全等价
print(xmlManager.get_parent(animal))
print(xmlManager.objects.get_parent(animal))
print(animal & UP.SM.parent)

print(xmlManager.get_parent(animal) == animal & UP.SM.parent) # True

# 另类获取方式
# 先取父节点的 id,然后再取父节点
p_id = xmlManager.objects.parent(id(animal))
print(xmlManager._id_nodes[p_id] == animal & UP.SM.parent) # True

获取祖先节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animal = list(xmlManager | 'animal')[1]

# 下面两行完全等价
print(xmlManager.get_ancestor(animal))
print(animal & UP.SM.ancestor)

获取孩子节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animal = list(xmlManager | 'animal')[1]

# 下面两行完全等价
print(xmlManager.get_children(animal))
print(animal & UP.SM.children)

获取子孙节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animal = list(xmlManager | 'animal')[1]

# 下面两行完全等价
print(xmlManager.get_descendants(animal))
print(animal & UP.SM.descendants)

获取兄弟节点

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

animal = list(xmlManager | 'animal')[1]

# 下面两行完全等价
print(xmlManager.get_siblings(animal))
print(animal & UP.SM.siblings)

获取运行时数据

获取初始XMLOrderedDict的字典数据

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

print(xmlManager.objects.doc)

获取运行时的最新XML字符串

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

print(xmlManager.get_xml_data())

获取运行时的JSON字典数据

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

print(xmlManager.document)
print(xmlManager.xml) # 旧版本变量,尽量保持向后兼容
print(xmlManager.document == xmlManager.xml) # True

反向解析/另存为

另存为XML

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

xmlManager.save_as_xml(
    path = 'output.xml' # 输出路径
    , encoding = 'utf-8' # 编码格式
    , distinct = True # 是否对同一节点内的 注释 和 CDATA 去重
)

另存为JSON

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

xmlManager.save_as_json(
    path = 'output.json' # 输出路径
    , encoding = 'utf-8' # 编码格式
    , ensure_ascii = True # 输出 ASCII 编码
    , indent = None # 缩进
    , ori = False # 是否保留初始标记,如 #text 等
)

魔法定位(替换XPath)

魔法操作符

操作符 对应方法 操作对象 参数 返回值类型
& - ChainDict UniversalParser.SM对象 Union[str, ChainDict, List[ChainDict]]
@ find_nodes_by_attrs ChainManager Union[dict, str] ChainManager
| find_nodes_by_tag ChainManager str ChainManager
^ - ChainManager int Union[ChainDict, List[ChainDict]]
/ find_nodes_by_text ChainManager Any ChainManager
// find_nodes_by_comment ChainManager str ChainManager
% find_nodes_by_cdata ChainManager str ChainManager

【注意】:ChainManager 本身就是一个可迭代对象,认识这点,将帮助您减少部分代码量。

从上表可以很轻易的掌握一些高级用法,比如操作符可以连续使用,但需要注意的是,原先的操作符是有 优先级的,所以进行运算时,必须要在两侧加括号,如:(manager @ {'id': '01'}) | 'tag',表示查找id属性为 01且标签为tag的节点。

^ 运算符的作用相当于切片,右操作数必须为大于0的整数。特别地,右操作数为1时,将限制有且仅有一个输出,否则报错。 如:manager ^ 1当有且仅有一个输出时等价于 list(manager)[0]

| 运算符的右操作数是 标签名 字符串。比如 <a>123</a>,要查找的话就是 manager | 'a'

/ 运算符的右操作数是 文本域 字符串。

// 运算符的右操作数是 注释 字符串,使用此运算符必须要开启注释功能!

% 运算符的右操作数是 CDATA 字符串,必须开启CDATA功能后才能使用!

特别地,对 @ 运算符,当限制属性有且仅有一个时,Universal Parser 提供了一种简写方式:

import UniversalParser as UP

xmlManager = UP.parse_xml(xml_data
    , combine_cdata = False
    , open_cdata = True
)

xmlManager.SEARCH_ATTR_KEY = 'id' # 默认就是 'id'
animal1 = list(xmlManager @ '01')[0] # 简写,可以忽略键
animal2 = list(xmlManager @ {'id': '01'})[0]
print(animal1 == animal2) # True
当然,您也可以在任意地方重置 xmlManager.SEARCH_ATTR_KEY,它是灵活的。