使用 xmlstarlet 将包含重复字段和缺失字段的 XML 转换为制表符分隔



我有一个大型的复杂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:distinctstr: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'

列出输入文件中的元素xmlstarletel命令可以是 有用,-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,输出是相同的(因为字段名称和字段值相同)。

最新更新