如何使用 xslt 从 xml 文档中过滤掉任意节点

Posted

技术标签:

【中文标题】如何使用 xslt 从 xml 文档中过滤掉任意节点【英文标题】:how to filter out arbitrary nodes from xml document using xslt 【发布时间】:2018-04-19 10:18:00 【问题描述】:

我有一个结构如下的 XML 文件:

<?xml version = '1.0' encoding="ISO-8859-1"?>
<!DOCTYPE stuff PUBLIC "stuff" "stuff.dtd">
<stuff>
  <level1>
    <type>foo</type>
    <name>name1_A</name>
    <junk1>garbage</junk1>
    <junk2>garbage</junk2>
    <level2>
      <name>name2_A</name>
      <junk3>garbage</junk3>
      <junk4>garbage</junk4>
      <level3>
        <name>name3_A</name>
        <junk5>garbage</junk5>
        <junk6>garbage</junk6>
      </level3>
      <level3>
        <name>name3_B</name>
        <junk5>garbage</junk5>
        <junk6>garbage</junk6>
      </level3>
    </level2>
    <level2>
      <name>name2_B</name>
      <junk>garbage</junk>
      <level3>
        <name>name3_A</name>
        <junk>garbage</junk>
      </level3>
      <level3>
        <name>name3_B</name>
        <junk>garbage</junk>
      </level3>
    </level2>
  </level1>
  <level1>
    <type>foo</type>
    <name>name1_B</name>
    <junk1>garbage</junk1>
    <junk2>garbage</junk2>
    <level2>
      <name>name2_A</name>
      <junk3>garbage</junk3>
      <junk4>garbage</junk4>
      <level3>
        <name>name3_A</name>
        <junk5>garbage</junk5>
        <junk6>garbage</junk6>
      </level3>
      <level3>
        <name>name3_B</name>
        <junk5>garbage</junk5>
        <junk6>garbage</junk6>
      </level3>
    </level2>
    <level2>
      <name>name2_B</name>
      <junk>garbage</junk>
      <level3>
        <name>name3_A</name>
        <junk>garbage</junk>
      </level3>
      <level3>
        <name>name3_B</name>
        <junk>garbage</junk>
      </level3>
    </level2>
  </level1>
</stuff>

我想编写一个 XSLT 来过滤掉所有名为 junk* 的元素。也就是说,我知道我想要保留的元素名称并想要摆脱其他所有内容。上述起点的预期最终结果将如下所示,去掉所有垃圾元素:

<?xml version = '1.0' encoding="ISO-8859-1"?>
<!DOCTYPE stuff PUBLIC "stuff" "stuff.dtd">
<stuff>
  <level1>
    <type>foo</type>
    <name>name1_A</name>
    <level2>
      <name>name2_A</name>
      <level3>
        <name>name3_A</name>
      </level3>
      <level3>
        <name>name3_B</name>
      </level3>
    </level2>
    <level2>
      <name>name2_B</name>
      <level3>
        <name>name3_A</name>
      </level3>
      <level3>
        <name>name3_B</name>
      </level3>
    </level2>
  </level1>
  <level1>
    <type>foo</type>
    <name>name1_B</name>
    <level2>
      <name>name2_A</name>
      <level3>
        <name>name3_A</name>
      </level3>
      <level3>
        <name>name3_B</name>
      </level3>
    </level2>
    <level2>
      <name>name2_B</name>
      <level3>
        <name>name3_A</name>
      </level3>
      <level3>
        <name>name3_B</name>
      </level3>
    </level2>
  </level1>
</stuff>

请记住,我的示例中的各种垃圾元素可以命名为任何名称 - 我有我想要保留的元素名称列表(例如 level1/type、level1/name、level1/level2/name、level1/level2 /level3/name 等)并想放弃其他所有内容。

到目前为止我得到的最好的是这个 XSLT,但在这里我必须明确列出我想要删除的所有元素名称,而不是我想要保留的元素名称,所以它不太理想:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="no"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="junk1 | junk2 | junk3 | junk4 | junk5 | junk6"/>

</xsl:stylesheet>

【问题讨论】:

然后你必须明确地命名你想要保留的所有元素。由于垃圾元素可以出现在任何级别,因此编写一个模板来保留&lt;level1&gt; 但丢弃其中包含的任何垃圾元素将更加困难。你所拥有的已经是最佳方法,为什么你认为你可以改进它? 如果您认为这已经是最佳选择...我不会争论!我花了一段时间寻找更好的方法无济于事。正如您所指出的,我的问题似乎是垃圾元素可以出现在任何级别。我找到了许多解决方案,可以处理他们都是特定级别的孩子,但不像我那样分散。 【参考方案1】:

如果它们在名称中具有一些共同特征,则可以将它们重新组合到不同的类别中,而不是枚举所有要忽略的节点名称:

所有以//*[starts-with(name(), 'junk')]开头的标签 所有以//*[ends-with(name(), 'junk')]结尾的标签 包含特定子字符串的所有标签。 //*[contains(.,'junk')]

如果您不确切知道要删除的标签的名称,您可以更改 XSLT 的逻辑并仅应用于您要保留的节点名称和复制操作。

如果您只知道要忽略的标签的名称,请使用以下逻辑:

如果“节点”是指元素,则使用:

<xsl:template match="*[not(self::ServiceNode)]">

如果“节点”是指任何节点(类型元素、文本、注释、处理指令):使用

<xsl:template match="node()[not(self::ServiceNode)]">

如果您只想匹配 Document 的子项,请使用:

<xsl:template match="Document/node()[not(self::ServiceNode)]">

如果您只想匹配顶部元素的子元素,请使用:

<xsl:template match="/*/node()[not(self::ServiceNode)]">

How to write a xpath to match all elements except a particular element

【讨论】:

确实知道我想保留的节点的名称。我想扔掉所有除了我已知的节点名称列表。我的问题一直是理解当要保留的节点名称出现在层次结构的不同级别时如何做到这一点。

以上是关于如何使用 xslt 从 xml 文档中过滤掉任意节点的主要内容,如果未能解决你的问题,请参考以下文章

XSLT 过滤掉节点并生成新的 xml

使用 XSLT 翻译功能或更好的方法删除单引号和双引号

如何在 XSLT 中启用文档功能(从 ASP.NET 调用)?

如何使用 xslt 获取 XML 的属性值和代码作为 html 的值

如何使用 XSLT 将 base64 编码的文本加载到 XML 文档中?

包含 ∈ 的 XML 文档无效 - 如何使用 XSLT 输出?