XPATH或XSL使用自定义比较匹配两个节点集

时间:2008-11-06 19:41:21

标签: xml xslt xpath

编辑:我还可以访问ESXLT个功能。

我有两个字符串令牌节点集。一组包含以下值:

/Geography/North America/California/San Francisco
/Geography/Asia/Japan/Tokyo/Shinjuku

另一组包含以下值:

/Geography/North America/
/Geography/Asia/Japan/

我的目标是找到两者之间的“匹配”。当集合1中的任何字符串以集合2中的字符串开头时进行匹配。例如,匹配将在 / Geography / North America / California / San Francisco / Geography之间进行/ North America / 因为第1集中的字符串以第2集中的字符串开头。

我可以使用第三方扩展程序使用通配符来比较字符串。我也可以在Xpath中使用正则表达式。

我的问题是如何构建Xpath以在两个集合的所有节点之间使用函数进行选择? XSL也是一个可行的选择。

这个XPATH:

count($set1[.=$set2])

会产生set1和set2之间的交集计数,但它是1比1的比较。是否可以使用其他一些比较节点的方法?

编辑:我确实让这个工作,但我通过使用其他一些第三方扩展来欺骗,以获得相同的结果。我仍然对其他方法感兴趣。

4 个答案:

答案 0 :(得分:2)

此:

<xsl:variable name="matches" select="$set1[starts-with(., $set2)]"/>

$matches设置为包含$set1中每个节点的节点集,该节点的文本值以$ set2中节点的文本值开头。这就是你要找的,对吗?

修改

嗯,我对此错了。这就是原因。

starts-with期望它们的两个参数都是字符串。如果它们不是,它会在评估函数之前将它们转换为字符串。

如果给它一个节点集作为其参数之一,它将使用节点集的字符串值,该值是集合中第一个节点的文本值。所以在上面,$set2永远不会被搜索;只检查列表中的第一个节点,因此谓词只会在$set1中找到以$set2中第一个节点的值开头的节点。

我被误导了,因为这种模式(我在过去几天里一直使用很多) 工作:

<xsl:variable name="hits" select="$set1[. = $set2]"/>

但该谓词使用的是节点集之间的比较,而不是文本值之间的比较。

执行此操作的理想方法是嵌套谓词。也就是说,“我想找到$set1中的每个节点,其中$set2中有一个节点,其值以......开头”,这就是XPath发生故障的地方。从什么开始?你想写的是:

<xsl:variable name="matches" select="$set1[$set2[starts-with(?, .)]]"/>

只有没有可以为?编写的表达式,它将返回外部谓词当前正在测试的节点。 (除非我遗漏了一些令人目眩的事情。)

要获得所需内容,您必须单独测试每个节点:

<xsl:variable name="matches">
  <xsl:for-each select="$set1">
    <xsl:if test="$set2[starts-with(current(), .)]">
      <xsl:copy-of select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:variable>

这不是一个非常令人满意的解决方案,因为它评估的是结果树片段,而不是节点集。如果要在XPath表达式中使用该变量,则必须使用扩展函数(如msxsl:node-set)将RTF转换为节点集。

答案 1 :(得分:1)

有一个简单而纯粹的XSLT 1.0解决方案(不需要扩展)来查找匹配数

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:variable name="vStars">
            <xsl:for-each select="*/regions/*">
                <xsl:for-each select="/*/cities/*[starts-with(.,current())]">
                    <xsl:value-of select="'*'"/>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:variable>

        <xsl:value-of select="string-length($vStars)"/>
    </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文档

<t>
    <cities>
        <city>/Geography/North America/California/San Francisco</city>
        <city>/Geography/Asia/Japan/Tokyo/Shinjuku</city>
    </cities>
    <regions>
        <region>/Geography/North America/</region>
        <region>/Geography/Asia/Japan/</region>
    </regions>
</t>

产生了正确的结果

<强> 2

请注意为找到的每个匹配生成一个字符(星号),所有这些星号构成$vStars变量的内容。然后我们只输出string-length()

答案 2 :(得分:0)

Robert的最后xsl:variable适用于获取包含匹配文本值的结果树片段,但除非(他建议)使用EXSLT或MS扩展到XSLT 1.0将RTF转换为节点集,无法计算匹配的文本节点。

这是我之前的响应中提到的XSLT样式表,它重复了我给出的样本输入文档,给出了集合1中的节点,其中集合2中的节点与其中的部分或全部匹配:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output indent="yes" method="text"/>

  <xsl:template match="/">
    <xsl:call-template name="count-matches">
      <xsl:with-param name="set1-node" select="sets/set[1]/text[1]"/>
      <xsl:with-param name="set2-node" select="sets/set[2]/text[1]"/>
      <xsl:with-param name="total-count" select="0"/>
    </xsl:call-template>
    <xsl:text>
</xsl:text>
  </xsl:template>

  <xsl:template name="count-matches">
    <xsl:param name="set1-node"/>
    <xsl:param name="set2-node"/>
    <xsl:param name="total-count" select="0"/>
    <xsl:variable name="this-count">
      <xsl:choose>
        <xsl:when test="contains($set1-node, $set2-node)">
          <xsl:value-of select="1"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="0"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$set2-node/following-sibling::text">
        <xsl:call-template name="count-matches">
          <xsl:with-param name="set1-node"
                          select="$set1-node"/>
          <xsl:with-param name="set2-node"
                          select="$set2-node/following-sibling::text[1]"/>
          <xsl:with-param name="total-count"
                          select="$total-count + $this-count"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$set1-node/following-sibling::text">
        <xsl:call-template name="count-matches">
          <xsl:with-param name="set1-node"
                          select="$set1-node/following-sibling::text[1]"/>
          <xsl:with-param name="set2-node"
                          select="$set2-node/preceding-sibling::text[last()]"/>
          <xsl:with-param name="total-count"
                          select="$total-count + $this-count"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$total-count + $this-count"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

不是特别简洁,但由于XSLT不允许程序员为已经定义的变量分配新值,因此通常需要递归。我没有在XSLT 1.0中看到使用xsl:for-each获取Zack请求的排序计数的方法。

答案 3 :(得分:-1)

我想我无法使XPath成功。我从以下XML文档开始初始化两个节点集:

<?xml version="1.0"?>
<sets>
  <set>
    <text>/Geography/North America/California/San Francisco</text>
    <text>/Geography/Asia/Japan/Tokyo/Shinjuku</text>
  </set>
  <set>
    <text>/Geography/North America/</text>
    <text>/Geography/Asia/Japan/</text>
  </set>
</sets>

我认为这个样式表应该实现Robert的解决方案,但我只计算'1':

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:template match="/">
    <xsl:variable name="set1" select="sets/set[1]/text/text()"/>
    <xsl:variable name="set2" select="sets/set[2]/text/text()"/>
    <xsl:value-of select="count($set1[starts-with(., $set2)])"/>
    <xsl:text>
</xsl:text>
  </xsl:template>

</xsl:stylesheet>

我确实编写了一个使用递归模板的样式表,并且使用给定的输入文档生成了正确的'2'计数,但它远不如Robert的答案那么优雅。如果我能让XPath工作 - 总是想学习。