我有一个大型的复杂XML文件,其中包含如下所示的模式
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<field1>field1</field1>
<field2>field2</field2>
<field2>field2</field2>
<field3>field3</field3>
<field4>field4</field4>
<field4>field4</field4>
</record>
<record>
<field1>field1</field1>
<field1>field1</field1>
<field3>field3</field3>
<field4>field4</field4>
<field4>field4</field4>
</record>
</records>
我想使用 xmlstarlet 将其转换为制表符分隔,重复字段用分号分隔,例如
field1tfield2;field2tfield3tfield4;field4
field1;field1ttfield3tfield4;field4
我可以通过在将文件提供给 xmlstarlet 之前使用字符串处理例程折叠重复字段来做我需要的事情,但这感觉很黑客。有没有一种优雅的方法可以在 xmlstarlet 中完成这一切?
你问已经有一段时间了。不过。。。
使用 xmlstarlet 版本 1.6.1 提取和排序字段名称 要确定字段顺序,然后生成制表符分隔值,请执行以下操作:
xmlstarlet sel
-N set="http://exslt.org/sets" -N str="http://exslt.org/strings"
-T -t
--var fldsep="'$(printf "t")'"
--var subsep="';'"
--var allfld
-m '*/record/*'
-v 'name()' --nl
-b
-b
--var uniqfld='set:distinct(str:split(normalize-space($allfld)))'
-m '*/record'
--var rec='.'
-m '$uniqfld'
--sort 'A:T:-' '.'
-v 'substring($fldsep,1,position()!=1)'
-m '$rec/*[local-name() = current()]'
-v 'substring($subsep,1,position()!=1)'
-v '.'
-b
-b
--nl < file.xml
编辑:--sort
从$allfld
移动到-m $uniqfld
。
哪里:
- 输入中的所有字段名称都收集在
$allfld
变量中 - exslt 函数
set:distinct
和str:split
用于从$allfld
创建唯一字段名称的节点集 $uniqfld
节点集确定字段输出顺序- 此处按文档顺序输出重复字段,但
-m '$rec/*[…]'
接受--sort
条款 substring
表达式在position() != 1
时发出分隔符
给定以下输入,与OP 的输入不同,
<?xml version="1.0" encoding="UTF-8"?>
<records>
<record>
<field2>r1-f2A</field2>
<field2>r1-f2B</field2>
<field3>r1-f3</field3>
<field4>r1-f4A</field4>
<field4>r1-f4B</field4>
<field6>r1-f6</field6>
</record>
<record>
<field1>r2-f1A</field1>
<field1>r2-f1B</field1>
<field3/>
<field4>r2-f4A</field4>
<field4>r2-f4B</field4>
<field5>r2-f5</field5>
<field5>r2-f5</field5>
</record>
<record>
<field6>r3-f6</field6>
<field4>r3-f4</field4>
<field2>r3-f2B</field2>
<field2>r3-f2A</field2>
</record>
</records>
输出变为:
r1-f2A;r1-f2B r1-f3 r1-f4A;r1-f4B r1-f6
r2-f1A;r2-f1B r2-f4A;r2-f4B r2-f5;r2-f5
r3-f2B;r3-f2A r3-f4 r3-f6
或者,通过管道sed -n l
显示不可打印的内容,
tr1-f2A;r1-f2Btr1-f3tr1-f4A;r1-f4Bttr1-f6$
r2-f1A;r2-f1Btttr2-f4A;r2-f4Btr2-f5;r2-f5t$
tr3-f2B;r3-f2Attr3-f4ttr3-f6$
使用自定义字段输出顺序可以简化为:
xmlstarlet sel -T -t
--var fldsep="'$(printf "t")'"
--var subsep="';'"
-m '*/record'
--var rec='.'
-m 'str:split("field6 field4 field2 field1 field3 field5")'
-v 'substring($fldsep,1,position()!=1)'
-m '$rec/*[local-name() = current()]'
-v 'substring($subsep,1,position()!=1)'
-v '.'
-b
-b
--nl < file.xml
在没有--sort
的情况下,再次按文档顺序发出重复字段。
请注意,在--var
子句中使用 EXSLT 函数需要 要显式声明的相应命名空间,-N
以避免 运行时错误(为什么?
要列出生成的 XSLT 1.0/EXSLT 代码,请在-t
选项之前添加-C
。
要制作上述格式化xmlstarlet
命令的单行 - 剥离线延续字符和前导空格 - 管道 他们通过这个sed
命令:
sed -e ':1' -e 's/^[[:blank:]]*//;/\$/!b;$b;N;s/\n[[:blank:]]*//;b1'
列出输入文件中的元素xmlstarlet
的el
命令可以是 有用,-u
独特:
xmlstarlet el -u file.xml
输出:
records
records/record
records/record/field1
records/record/field2
records/record/field3
records/record/field4
records/record/field5
records/record/field6
您可以使用以下 xmlstarlet 命令:
xmlstarlet sel -t -m "/records/record" -m "*[starts-with(local-name(),field)]" -i "position()=1" -v "." --else -i "local-name() = local-name(preceding-sibling::*[1])" -v "concat(';',.)" --else -v "concat('t',.)" -b -b -b -n input.xml
在伪代码中,它表示类似的东西
- 适用于每个
/records/record
- 对于以
field
开头的每个元素名称 - 如果是第一个元素,则输出该项
- 还
- 如果检查当前元素名称是否等于前一个元素名称
- 然后输出
;Item
- 否则输出
tItem
b
确实意味着"中断"出圈或 if 子句n
输出换行符
它的输出是
field1tfield2;field2tfield3tfield4;field4
field1;field1tfield3tfield4;field4
上面的代码根据元素名称的基础进行区分。如果要改为根据元素值进行区分,请使用以下命令:
xmlstarlet sel -t -m "/records/record" -m "*[starts-with(local-name(),field)]" -i "position()=1" -v "." --else -i ". = preceding-sibling::*[1]" -v "concat(';',.)" --else -v "concat('t',.)" -b -b -b -n a.xml
对于示例 XML,输出是相同的(因为字段名称和字段值相同)。