XML排序小工具——xmlabit

简介

这是小黑去年写的一个小公举工具,日常工作中有需要比较两个系统生成的XML文件是否相同,但是XML中的节点顺序并不是固定的。当时没有找到合适的工具,于是就自己写了这个工具。

工具名叫xmlabit,是xml alphabet it的缩写。代码很简单,主要依赖pugixml库对XML文件节点进行重排序,仓库地址:https://github.com/wuruofan/xmlabit

pugixml是一个C++实现的轻量级XML操作库,支持XPath路径表达式,仓库地址:https://github.com/zeux/pugixml

功能简介

1
xmlabit [options] -t </xpath/to/parent_node@target_node#attributes_name> -o <output_xml_file> <input_xml_file>

通常情况,xmlabit命令基本格式如上,[]方括号中表示可选参数,<>表示必选参数。支持参数如下:

  • -v/--version:打印程序版本信息。
  • -h/--help:打印帮助信息。
  • -t/--target:类似XPath的路径表达式来定位需要排序的节点,格式/xpath/of/parent_node@node#attribute,其中#attribute可以省略。
  • -o <output_xml_file>/--output <output_xml_file>:输入排序后文件。若没有使用此参数,则排序后字符串默认输出到屏幕.
  • -d/--desecend:降序排序。默认将待排序属性或节点名按A-Z字母顺序排序。
  • -i/--ignore-case:忽略大小写,比较时忽略大小写。
  • -n/--numeric:将待排序属性或节点名当作数字进行比较,默认XML解析时会把数字当作字符串处理。

编译方法

目前编译使用CMake,仅支持Linux/MacOS,Windows请使用WSL环境编译。

在工程目录,运行如下命令:

1
2
3
4
5
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make install

如果想编译调试版本,使用 cmake -B build -DCMAKE_BUILD_TYPE=Debug

默认情况make installxmlabit复制到 /usr/local/bin 目录,如需卸载直接rm -i /usr/local/bin即可。

使用示例

示例1:按某子节点值排序

这里有一个books.xml如下,保存了书架、书籍的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<magzine>
<title lang="eng">Beauty</title>
<price>129.29</price>
</magzine>
<book>
<title lang="eng">Harry Potter3</title>
<price>29.29</price>
</book>
<book>
<title lang="eng">Harry Potter2</title>
<price>39.99</price>
</book>
<book>
<title lang="eng">Harry Potter</title>
<price>29.19</price>
</book>
<book>
<title lang="eng">readme</title>
<price>0.95</price>
</book>
<book>
<title lang="eng">readme2</title>
<price>-0.955</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
<book>
<title lang="eng">readme3</title>
<price>0.95</price>
</book>
</bookstore>

现在如果想对书架上的书籍按书名排序,那么,-t参数后面的表达式应该是/bookstore@book#title,其中/表示根节点,/bookstore@book表示待排序的节点名都是book,其XPath路径为/bookstore/book,排序依据的子节点值或者属性值名为title

进入编译build目录,运行./xmlabit -t /bookstore@book#title books.xml -o output.xml即可。排序后的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0"?>
<bookstore>
<magzine>
<title lang="eng">Beauty</title>
<price>129.29</price>
</magzine>
<book>
<title lang="eng">Harry Potter</title>
<price>29.19</price>
</book>
<book>
<title lang="eng">Harry Potter2</title>
<price>39.99</price>
</book>
<book>
<title lang="eng">Harry Potter3</title>
<price>29.29</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
<book>
<title lang="eng">readme</title>
<price>0.95</price>
</book>
<book>
<title lang="eng">readme2</title>
<price>-0.955</price>
</book>
<book>
<title lang="eng">readme3</title>
<price>0.95</price>
</book>
</bookstore>

示例2

这里又有一个book2.xml,也保存了一些书籍信息,不同的是XML节点构成不太一样。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<books>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="us" price="10">harry potter2</book>
<book lang="fr" price="2">Harry Potter</book>
<book lang="eng" price="5">readme</book>
<book lang="zh-CN" price="0.1">readme2</book>
<book lang="eng" price="92">Learning XML</book>
<book lang="eng" price="0.01">readme3</book>
</books>
</bookstore>

2.1 对books节点下的所有书籍按名称降序排序

使用命令./xmlabit -t /bookstore/books@book -d books2.xml即可:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<bookstore>
<books>
<book lang="eng" price="0.01">readme3</book>
<book lang="zh-CN" price="0.1">readme2</book>
<book lang="eng" price="5">readme</book>
<book lang="us" price="10">harry potter2</book>
<book lang="eng" price="92">Learning XML</book>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="fr" price="2">Harry Potter</book>
</books>
</bookstore>

这里看到有一本小写名称的《harry potter2》排在了《Learning XML》前面,因为小写字符的ASCII数值要比大写字符大。

2.2 对books节点下的所有书籍按名称、无视大小写、降序排序

使用命令./xmlabit -t /bookstore/books@book -di books2.xml即可:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<bookstore>
<books>
<book lang="eng" price="0.01">readme3</book>
<book lang="zh-CN" price="0.1">readme2</book>
<book lang="eng" price="5">readme</book>
<book lang="eng" price="92">Learning XML</book>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="us" price="10">harry potter2</book>
<book lang="fr" price="2">Harry Potter</book>
</books>
</bookstore>

2.3 对books节点下的所有书籍按属性lang排序

使用命令./xmlabit -t /bookstore/books@book#lang books2.xml即可:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<bookstore>
<books>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="eng" price="5">readme</book>
<book lang="eng" price="92">Learning XML</book>
<book lang="eng" price="0.01">readme3</book>
<book lang="fr" price="2">Harry Potter</book>
<book lang="us" price="10">harry potter2</book>
<book lang="zh-CN" price="0.1">readme2</book>
</books>
</bookstore>

2.4 对books节点下的所有书籍按属性price进行排序

使用命令./xmlabit -t /bookstore/books@book#price books2.xml即可:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<bookstore>
<books>
<book lang="eng" price="0.01">readme3</book>
<book lang="zh-CN" price="0.1">readme2</book>
<book lang="us" price="10">harry potter2</book>
<book lang="fr" price="2">Harry Potter</book>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="eng" price="5">readme</book>
<book lang="eng" price="92">Learning XML</book>
</books>
</bookstore>

因为price属性都是字符串,排序也是按照字符串字符进行排序的。

2.5 对books节点下的所有书籍按属性price以数字模式进行排序

使用命令./xmlabit -t /bookstore/books@book#price books2.xml -n即可:

1
2
3
4
5
6
7
8
9
10
11
<bookstore>
<books>
<book lang="eng" price="0.01">readme3</book>
<book lang="zh-CN" price="0.1">readme2</book>
<book lang="fr" price="2">Harry Potter</book>
<book lang="eng" price="5">readme</book>
<book lang="us" price="10">harry potter2</book>
<book lang="eng" price="29.99">Harry Potter3</book>
<book lang="eng" price="92">Learning XML</book>
</books>
</bookstore>

Git子模块(submodule)

由于xml解析逻辑完全依赖于pugixml,所以不想在代码里直接放入pugixml的源码文件,Git本身其实提供了submodule子模块组件,用来管理项目中用到的其他Git项目。

添加子模块

使用命令git submodule add https://github.com/zeux/pugixml.gitpugixml仓库添加为xmlabit的子模块。

这时运行git status会发现本地仓库里多了一个.gitmodules文件,里面内容记录了当前仓库包含的子模块信息。

1
2
3
[submodule "pugixml"]
path = pugixml
url = https://github.com/zeux/pugixml.git

初始化并检出子模块

1
git submodule update --init --recursive

这一条命令相当于运行了git submodule init以及git submodule update各个嵌套子模块。

CMakeLists增加Git submodule支持

现在虽然不用在仓库里添加pugixml的源代码了,但是需要用户手动克隆仓库时记得使用git submodule相关命令,这样不太好。

好在Cmake可以解决,在编译时自动执行git submodule相关命令,需要在CMakeLists增加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()

if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${PUGIXML_REPO}/CMakeLists.txt")
message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
endif()

GitHub Actions

之前在搭建GitHub Pages时候使用了Travis CI进行持续集成,在每次提交代码到GitHub时自动构建并执行部署与发布任务,这次尝试一下GitHub自家的Actions功能。

GitHub仓库界面上有一个名为Actions的Tab,点击之后会自动推荐此仓库适合使用的workflow,也就是工作流程脚本。GitHub自身提供了一个市场,可以搜索到他人提交的Actions,也可以自己上传。

GitHub Actions

xmlabit使用CMake进行编译,就自动推荐了一个CMake相关的workflow,点击添加即可。以后每次提交代码,就会触发CMake自动编译的workflow。

现在可以使用如下Markdown链接在README中添加一个小徽章来显示当前workflow的状态,自动编译成功之后,会显示一个小绿标编译成功徽章

1
![GitHub Action](https://github.com/wuruofan/xmlabit/actions/workflows/cmake.yml/badge.svg)

End

就这样,很简单的代码,欢迎Star和PR。


XML排序小工具——xmlabit
https://wuruofan.com/2021/05/20/xmlabit-a-xml-node-sort-tool/
作者
rf.w
发布于
2021年5月20日
许可协议