如何使用XMLStarlet / XMLint / XSLT / Xidel / Grep过滤XML文件并将过滤结果保存



我一直在寻找一个非常简单的任务的解决方案:根据多个条件过滤XML结果并将其另存为新的XML文件。通过过滤,我的意思是选择输出的值。因此,仅输出满足筛选器条件的 XML。不仅仅是筛选器的这些值(这没有意义),而是与筛选器匹配的 XML 的所有元素。应从 XML 中删除不符合筛选条件的用户。基本上,筛选器应从输入中删除数据,并将其保存到新的较小的输出 XML。

但直到现在我还没有设法找到任何解决方案。我确实看到了很多关于各种工具的文章,例如XMLstarlet(这是被放弃的吗?),XMLint,XSLT和Xidel。但老实说,我不知道从哪里开始。不知何故,大多数问题都是关于从 XML 中提取一个值而不是过滤它 - 通过丢弃与过滤器不匹配的元素 - 并制作一个新的、更紧凑的 XML 版本。

这就是我想要的:

  • 根据特定值筛选 XML - 如果它包含此值,请使用与该筛选器匹配的 XML 的所有元素的整个输出。

以示例 XML 的这个单个 XML 条目为例。

<item>
<g_id>5e4e8249-fb12-43e6-8f7e-ccef1b242097</g_id>
<g_title>A Bathing Ape Court Sta Beige</g_title>
<g_description>A Bathing Ape Court Sta Beige</g_description>
<g_google_product_category>Apparel & Accessories > Shoes</g_google_product_category>
<g_condition>new</g_condition>
<g_availability>in stock</g_availability>
<g_price>425.00 EUR</g_price>
<g_gtin>747883771947</g_gtin>
<g_brand>BAPE</g_brand>
<g_identifier_exists>TRUE</g_identifier_exists>
<g_gender>male</g_gender>
<g_age_group>adult</g_age_group>
<g_color>Beige/Light Brown/Blue</g_color>
<g_size>8</g_size>
<g_is_bundle>FALSE</g_is_bundle>
<g_adult>FALSE</g_adult>
<g_custom_label_0>sneakers</g_custom_label_0>
<g_custom_label_1>Other Brands A Bathing Ape</g_custom_label_1>
<g_custom_label_2>0ZXSHM191X30GBGK</g_custom_label_2>
</item>

假设XML有10.000个条目/记录,g_custom_label_0可以包含不同的值。g_custom_label_2也可能是空的。现在我希望整个 XML - 以及这 10.000 个条目 - 根据以下条件进行过滤:

g_custom_label_0 = 运动鞋 g_custom_label_3 = 不为空

如何使用 bash 脚本执行此操作并将过滤结果保存到新的 XML 文件中?

如果可能的话...g_gtin和g_custom_label_2之间存在关系(多:一)。是否可以在新 XML 中仅保存第一个唯一g_custom_label_2?–

XML文件太大了(200MB - 用GZ压缩),我只想使用相关的东西,这可能是减小XML文件大小的好方法。

帮助真的很感激!

马克

附言。我希望我可以在下载原始 XML 后使用 bash 脚本对其进行过滤并保存到新脚本。

---更新---

根据这些建议,XSLT 应该是要走的路。我创建了这个模板:


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|item()">
<xsl:copy>
<xsl:apply-templates select="@*|item()" />
</xsl:copy>
</xsl:template>
<xsl:template match="item[g_custom_label_0 = 'sneakers']" />
</xsl:stylesheet>

只需先尝试正确过滤g_custom_label_0即可。不幸的是,执行此命令后:

xsltproc --output output.xml template.xslt test.xml

我收到一个错误:

错误 xsltCompileIdKeyPattern : 期望 'key' 或 'id' 或节点类型 编译错误:文件模板.xslt 第 2 行元素模板 xsltCompilePattern : 无法编译 'item()'

原始 XML 如下所示:

<?xml version='1.0' encoding='UTF-8'?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
<channel>
<title>Some title</title>
<link></link>
<description>Some description</description>
<item>
<g:id>74cce63d-1523-460f-b59f-4a42a94be350</g:id>
<g:title>100 Thieves 2019 Hoodie Cream</g:title>
<g:description>Released in late 2019, this 100 Thieves 2019 Hoodie Cream is one of the hottest pieces of gaming merch out there. The clean, all white hoodie features a stitched 100 Thieves logo on the chest in black. This hoodie released on November 23, 2019, and sold out immediately upon it's release, generating considerable interest on StockX. It carried an original retail price of $90. Shop now by placing a Bid. Looking to get your gaming setup in check? Shop the coveted PS5 console &lt;a href= "  https://stockx.com/sony-ps5-playstation-5-blu-ray-edition-console-white">here&lt;/a> on StockX.</g:description>
<g:google_product_category>Apparel &amp; Accessories</g:google_product_category>
<g:condition>new</g:condition>
<g:availability>in stock</g:availability>
<g:price>179.00 GBP</g:price>
<g:gtin>765208957340</g:gtin>
<g:brand>100 Thieves</g:brand>
<g:age_group>adult</g:age_group>
<g:color>Cream</g:color>
<g:size>M</g:size>
<g:size_system>US</g:size_system>
<g:shipping>
<g:country>GB</g:country>
<g:price>11.41 GBP</g:price>
</g:shipping>
<g:is_bundle>FALSE</g:is_bundle>
<g:custom_label_0>streetwear</g:custom_label_0>
<g:custom_label_1>Other Brands 100 Thieves</g:custom_label_1>
</item>
<item>
<g:id>6576ba88-a874-48b2-a8a9-e4b82db0ac64</g:id>
<g:title>100% Soft Dumpster Fire Kawaii Galaxy Trash Vinyl Figure</g:title>
<g:google_product_category>Arts &amp; Entertainment > Hobbies &amp; Creative Arts > Collectibles</g:google_product_category>
<g:condition>new</g:condition>
<g:availability>in stock</g:availability>
<g:price>178.00 GBP</g:price>
<g:gtin>747883533613</g:gtin>
<g:brand>100% Soft</g:brand>
<g:gender>unisex</g:gender>
<g:age_group>adult</g:age_group>
<g:size_system>US</g:size_system>
<g:item_group_id>6060eb7c-a3aa-4b52-a4a3-834b5da1ba22</g:item_group_id>
<g:shipping>
<g:country>GB</g:country>
<g:price>11.41 GBP</g:price>
</g:shipping>
<g:is_bundle>FALSE</g:is_bundle>
<g:custom_label_0>collectibles</g:custom_label_0>
<g:custom_label_1>Other Artists Other</g:custom_label_1>
</item>
<item>
<g:id>c3885e7e-7cfc-426c-922c-c7fde307da73</g:id>
<g:title>100% Soft Dumpster Fire Figure Chrome</g:title>

---更新 2 ---

在这里取得了一些(糟糕的)进展。我将模板调整为:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="item[g_custom_label_0('sneaker')]" />
</xsl:stylesheet>

并得到一个不再包含 XML 标记的文件。完全错了。别这样。。


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="item[g_custom_label_0 = 'sneakers']">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

导致相同的结果..

---更新 3 ---

Yiztak提出的建议非常适合摆脱不符合"运动鞋"标准的元素。谢谢。

他提出了以下XSLT模板:


<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:g="http://base.google.com/ns/1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!--remove items where g:custom_label_0 != 'sneakers'-->
<xsl:template match="item[not(g:custom_label_0='sneakers')]"/>
</xsl:stylesheet>

下一步是删除g_custom_label_3值上的重复项(XML 示例中不存在),并删除g_custom_label_3的空值或缺失值。我将对此做一些实验。

<xsl:if>将是下一步。不知道如何与前面的陈述相结合。还有什么想法吗?

因此,g:custom_label_3 应该存在,并且新导出的 XML 中应具有值。

请尝试以下解决方案。

值得注意的一点:

  • 输入 XML 具有命名空间。它也应该在 XSLT 中声明和使用。
  • 第二个模板是通过谓词消除不需要[not(...)]内容。

输入 XML

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
<channel>
<title>Some title</title>
<link></link>
<description>Some description</description>
<item>
<g:id>74cce63d-1523-460f-b59f-4a42a94be350</g:id>
<g:title>100 Thieves 2019 Hoodie Cream</g:title>
<g:description>Released in late 2019, this 100 Thieves 2019 Hoodie Cream is one of the hottest pieces of gaming merch out there. The clean, all white hoodie features a stitched 100 Thieves logo on the chest in black. This hoodie released on November 23, 2019, and sold out immediately upon it's release, generating considerable interest on StockX. It carried an original retail price of $90. Shop now by placing a Bid. Looking to get your gaming setup in check? Shop the coveted PS5 console &lt;a href= "  https://stockx.com/sony-ps5-playstation-5-blu-ray-edition-console-white">here&lt;/a> on StockX.</g:description>
<g:google_product_category>Apparel &amp; Accessories</g:google_product_category>
<g:condition>new</g:condition>
<g:availability>in stock</g:availability>
<g:price>179.00 GBP</g:price>
<g:gtin>765208957340</g:gtin>
<g:brand>100 Thieves</g:brand>
<g:age_group>adult</g:age_group>
<g:color>Cream</g:color>
<g:size>M</g:size>
<g:size_system>US</g:size_system>
<g:shipping>
<g:country>GB</g:country>
<g:price>11.41 GBP</g:price>
</g:shipping>
<g:is_bundle>FALSE</g:is_bundle>
<g:custom_label_0>streetwear</g:custom_label_0>
<g:custom_label_1>Other Brands 100 Thieves</g:custom_label_1>
</item>
<item>
<g:id>6576ba88-a874-48b2-a8a9-e4b82db0ac64</g:id>
<g:title>100% Soft Dumpster Fire Kawaii Galaxy Trash Vinyl Figure</g:title>
<g:google_product_category>Arts &amp; Entertainment > Hobbies &amp; Creative Arts > Collectibles</g:google_product_category>
<g:condition>new</g:condition>
<g:availability>in stock</g:availability>
<g:price>178.00 GBP</g:price>
<g:gtin>747883533613</g:gtin>
<g:brand>100% Soft</g:brand>
<g:gender>unisex</g:gender>
<g:age_group>adult</g:age_group>
<g:size_system>US</g:size_system>
<g:item_group_id>6060eb7c-a3aa-4b52-a4a3-834b5da1ba22</g:item_group_id>
<g:shipping>
<g:country>GB</g:country>
<g:price>11.41 GBP</g:price>
</g:shipping>
<g:is_bundle>FALSE</g:is_bundle>
<g:custom_label_0>sneakers</g:custom_label_0>
<g:custom_label_1>Other Artists Other</g:custom_label_1>
</item>
</channel>
</rss>

XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:g="http://base.google.com/ns/1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!--remove items where g:custom_label_0 != 'sneakers'-->
<xsl:template match="item[not(g:custom_label_0='sneakers')]"/>
</xsl:stylesheet>

输出 XML

<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
<channel>
<title>Some title</title>
<link/>
<description>Some description</description>
<item>
<g:id>6576ba88-a874-48b2-a8a9-e4b82db0ac64</g:id>
<g:title>100% Soft Dumpster Fire Kawaii Galaxy Trash Vinyl Figure</g:title>
<g:google_product_category>Arts &amp; Entertainment &gt; Hobbies &amp; Creative Arts &gt; Collectibles</g:google_product_category>
<g:condition>new</g:condition>
<g:availability>in stock</g:availability>
<g:price>178.00 GBP</g:price>
<g:gtin>747883533613</g:gtin>
<g:brand>100% Soft</g:brand>
<g:gender>unisex</g:gender>
<g:age_group>adult</g:age_group>
<g:size_system>US</g:size_system>
<g:item_group_id>6060eb7c-a3aa-4b52-a4a3-834b5da1ba22</g:item_group_id>
<g:shipping>
<g:country>GB</g:country>
<g:price>11.41 GBP</g:price>
</g:shipping>
<g:is_bundle>FALSE</g:is_bundle>
<g:custom_label_0>sneakers</g:custom_label_0>
<g:custom_label_1>Other Artists Other</g:custom_label_1>
</item>
</channel>
</rss>

match="item[g_custom_label_0('sneaker')]"

不是有效的 XSLT 1.0 语法。我想你想要

match="item[g_custom_label_0 = 'sneakers'] 

我不知道你从哪里得到你的尝试的想法,但XSLT不是一种你可以通过反复试验来学习的语言。

> g_custom_label_0 = 运动鞋 g_custom_label_3 = 不空

使用xidel您可以使用x-replace-nodes

$ xidel -s input.xml -e '
x:replace-nodes(
//item[not(g:custom_label_0 = "sneakers" and g:custom_label_3 != "")],
()
)
' --output-format=xml --output-node-indent

是否可以在新 XML 中仅保存第一个唯一g_custom_label_2?

这是你的意思吗?

$ xidel -s input.xml -e '
(//item[g:custom_label_2])[1]
' --output-format=xml --output-node-indent

最新更新