XSLT过滤元件(XSLT filtering elements)

2019-10-19 08:09发布

例如,让我们假定输入XML有以下结构:

<root>
  <a>
    <aa>1</aa>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <b>
    <ba>4</ba>
    <bb>5</bb>
  <b>
  <c>
    <ca>
      <caa>6</caa>
      <cab>7</cab>
    </ca>
  </c>
</root>

鉴于组XPath的通过过滤元素:

/root/a/ab,
/root/a/ac,
/root/c/ca/cab

生成的XML应该是:

<root>
  <a>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>

如何这可以通过XSLT来表达?

先感谢您

Answer 1:

在XSLT 1.0实现这一点(与EXSLT可能一些小的帮助)或2.0,你可以通过破坏每个给定的路径为自己和祖先路径,使启动:

/root/c/ca/cab

例如,变为:

<path>/root/c/ca/cab</path>
<path>/root/c/ca</path>
<path>/root/c</path>
<path>/root</path>

这应该不是太困难的一个名为递归模板来完成。

一旦你在的地方,你可以使用身份变换加修饰的“直通”参数使每个处理单元可以计算路径本身,比较它的路径给出的列表,并确定它是否应该加入结果树与否。

在下面的样式表中,第1步中已跳过,结果被用作如果给出。

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="paths">
    <path>/root/a/ab</path>
    <path>/root/a</path>
    <path>/root</path>

    <path>/root/a/ac</path>
    <path>/root/a</path>
    <path>/root</path>

    <path>/root/c/ca/cab</path>
    <path>/root/c/ca</path>
    <path>/root/c</path>
    <path>/root</path>
</xsl:param>

<xsl:template match="@* | node()">
<xsl:param name="pathtrain" />
<xsl:variable name="path" select="concat($pathtrain, '/', name())" />
<xsl:if test="$path=exsl:node-set($paths)/path or not(self::*)">
    <xsl:copy>
         <xsl:apply-templates select="@* | node()">
            <xsl:with-param name="pathtrain" select="$path"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

适用于你的(校正)输入:

<root>
  <a>
    <aa>1</aa>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <b>
    <ba>4</ba>
    <bb>5</bb>
  </b>
  <c>
    <ca>
      <caa>6</caa>
      <cab>7</cab>
    </ca>
  </c>
</root>

获得以下结果:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>

编辑:

请注意,使用上述基于字符串的测试时,重复的分支可能产生误报。 例如,适用于以下输入时:

<root>
  <a>
    <aa>1</aa>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <b>
    <ba>4</ba>
    <bb>5</bb>
  </b>
  <c>
    <ca>
      <caa>6</caa>
    </ca>
  </c>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>

上述样式表将产生:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <c>
    <ca/>
  </c>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>

如果这是一个问题,我会发布另一个(更复杂)XSLT 1.0的答案,通过测试唯一的ID,而不是消除了问题。



Answer 2:

下面是使用撒克逊9.5 PE或EE和XSLT 3.0(工作草案版本目前在这些撒克逊版本中实现)的例子:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

<xsl:param name="paths" as="xs:string">
/root/a/ab,
/root/a/ac,
/root/c/ca/cab
</xsl:param>

<xsl:variable name="nodes" as="node()*">
  <xsl:evaluate xpath="$paths" context-item="/"/>
</xsl:variable>

<xsl:output indent="yes"/>

<xsl:template match="*[(.//node(), .//@*) intersect $nodes]">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()[(., .//node(), .//@*) intersect $nodes]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="node()[. intersect $nodes]">
  <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

这里是一个不同的版本,其利用新的XSLT 3.0特性为具有可变参考作为匹配图案的,我认为这种方式,代码是更有效(与读取):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

<xsl:param name="paths" as="xs:string">
/root/a/ab,
/root/a/ac,
/root/c/ca/cab
</xsl:param>

<xsl:variable name="nodes" as="node()*">
  <xsl:evaluate xpath="$paths" context-item="/"/>
</xsl:variable>

<xsl:variable name="ancestors" as="node()*" select="$nodes/ancestor::node()"/>

<xsl:output indent="yes"/>

<xsl:template match="$ancestors">
  <xsl:copy>
    <xsl:apply-templates select="@* , node()[. intersect $ancestors or . intersect $nodes]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="$nodes">
  <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>


Answer 3:

这是一个更复杂的XSLT 1.0答案(还需要EXSLT节点集()函数),通过执​​行变换的三道次重复解决分支的问题:

在第一阶段,给定的元素的ID被收集,使用恒等变换模板与“直通”参数,以确定他们 - 类似我以前的答案;

在第二遍中,每一个给定元素“收集”的本身及其祖先的ID;

在第三和最终道次,一个恒等变换模板再次使用去在整个源树和仅输出元件,其IDS已收集在步骤2中。

需要注意的是特定的路径并不需要在此版本进行预处理。

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="paths">
    <path>/root/a/ab</path>
    <path>/root/a/ac</path>
    <path>/root/c/ca/cab</path>
</xsl:param>

<!-- first pass: get ids of given nodes -->
<xsl:variable name="ids">
    <xsl:apply-templates select="/" mode="getids"/>
</xsl:variable>

<xsl:template match="*" mode="getids">
<xsl:param name="pathtrain" />
<xsl:variable name="path" select="concat($pathtrain, '/', name())" />
<xsl:if test="$path=exsl:node-set($paths)/path">
    <id><xsl:value-of select="generate-id()" /></id>
    </xsl:if>
    <xsl:apply-templates select="*" mode="getids">
        <xsl:with-param name="pathtrain" select="$path"/>
    </xsl:apply-templates>
</xsl:template>

<!-- second pass: extend the list of ids to given nodes and their ancestors-->
<xsl:variable name="extids">
    <xsl:for-each select="//*[generate-id(.)=exsl:node-set($ids)/id]">
        <xsl:for-each select="ancestor-or-self::*">
            <id><xsl:value-of select="generate-id()" /></id>
        </xsl:for-each>
    </xsl:for-each>
</xsl:variable>

<!-- third pass: output the nodes whose ids are in the extended list -->
<xsl:template match="@* | node()">
    <xsl:if test="generate-id(.)=exsl:node-set($extids)/id or not(self::*)">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

上述样式表,当施加到下面的“重复的分支”输入:

<root>
  <a>
    <aa>1</aa>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <b>
    <ba>4</ba>
    <bb>5</bb>
  </b>
  <c>
    <ca>
      <caa>6</caa>
    </ca>
  </c>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>

产生以下结果:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a>
    <ab>2</ab>
    <ac>3</ac>
  </a>
  <c>
    <ca>
      <cab>7</cab>
    </ca>
  </c>
</root>


文章来源: XSLT filtering elements
标签: xml xslt