【问题标题】:XSL: Comparing nodes by comparing their child nodesXSL:通过比较子节点来比较节点
【发布时间】:2013-01-19 05:28:09
【问题描述】:

我希望能够根据子节点的值比较两个节点。使用= 运算符测试节点相等性只是比较各个节点的字符串值。我想根据它们的子节点中的值来比较它们。

更具体一点,我希望<a><b>(如下)相等,因为@id 的值对于具有匹配@type 属性的<c> 元素也是相同的具有匹配的@id 属性。

<a>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-three" id="9088"/>
</a>
<b>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
</b>

但这些会有所不同:

<a>
    <c type="type-one" id="5675"/>
</a>
<b>
    <c type="type-one" id="2342"/>
</b>

我能开始看到的唯一解决方案是与 for-each 语句进行费力的比较,如果可能的话,我想避免这种情况。

如果可能的话,我想坚持使用 XSLT 1.0。我正在使用 xsltproc。

【问题讨论】:

  • a 的子元素&lt;c type="type-three" id="9088"/&gt; 没有b 的对应子元素时,第一个示例中的ab 如何“相等”? ?请编辑问题并更正。当前示例与“平等”的概念相矛盾。
  • 没有单一的相等定义...例如,具有相同内部文本的节点在某种意义上是相等的,但这不是我感兴趣的区别。或者在现实生活中,也许两个人在 Stack Overflow 中享有同等的声誉,但工资不等,等等等等......
  • user1447002,相等被严格定义为等价关系——如果~是相等,它必须是1.自反,2.对称和3.传递。你没有这些——你还没有定义你称之为“平等”的关系的所有属性。
  • user1447002,你的“平等绝对是传递的。
  • 我的定义不是自反的、对称的还是传递的?如果所有属性值都与 A/@* 和 B/@* 的交集匹配,则 A“等于”B。这对我来说似乎是自反的、对称的和传递的。

标签: xslt xslt-1.0


【解决方案1】:

首先,名为“equals”的关系不能有这个名字

“等于”表示该关系是等价关系。根据定义,任何等价关系~ 必须是:

  1. 反身:x ~ x

  2. 对称:如果x ~ y,那么y ~ x

  3. 传递:如果x ~ yy ~ z 然后x ~ z

这是一个示例,表明建议的“等于”关系不具有传递性:

x 是:

<a>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-three" id="9088"/>
</a>

y 是:

<b>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-four" id="1234"/>
</b>

z 是:

<b>
    <c type="type-three" id="3333"/>
    <c type="type-four" id="1234"/>
</b>

现在,我们可以看到x ~ yy ~ z。但是,显然这不成立:x ~ z

这就是说,我称关系为“匹配”,它是宽松的,而不是“等于”。

这是一个问题的解决方案,通过上述调整

请注意,这不能用单个 XPath 表达式来表达,因为 XPath 1.0(在 XSLT 1.0 转换中使用)没有范围变量

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

 <xsl:template match="/*">
     <xsl:call-template name="matches">
       <xsl:with-param name="pElem1" select="a"/>
       <xsl:with-param name="pElem2" select="b"/>
     </xsl:call-template>
 </xsl:template>

 <xsl:template name="matches">
   <xsl:param name="pElem1" select="/.."/>
   <xsl:param name="pElem2" select="/.."/>

   <xsl:variable name="vMisMatch">
       <xsl:for-each select="$pElem1/c[@type = $pElem2/c/@type]">
        <xsl:if test=
           "$pElem2/c[@type = current()/@type and not(@id = current()/@id)]">1</xsl:if>
       </xsl:for-each>
   </xsl:variable>

   <xsl:copy-of select="not(string($vMisMatch))"/>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时:

<t>
    <a>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
        <c type="type-three" id="9088"/>
    </a>
    <b>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
    </b>
</t>

产生了想要的正确结果:

true

当对这个 XML 文档应用相同的转换时:

<t>
    <a>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
        <c type="type-three" id="9088"/>
    </a>
    <b>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="9876"/>
    </b>
</t>

再次产生正确的结果:

false

【讨论】:

  • 不错的答案!你也可以检查my question 吗?我真的很高兴能详细了解如何实现这一目标!
  • @Dex'ter,我看到您的问题已得到解答(在我睡觉时)。您需要额外的帮助吗?
  • 如果您有什么要添加到该答案的任何其他信息会很棒:) 谢谢
【解决方案2】:

这是我想出的。给定一个像这样的玩具数据集:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a>
        <item key="x" value="123"/>
        <item key="y" value="456"/>
        <item key="z" value="789"/>
    </a>
    <b>
        <item key="x" value="123"/>
        <item key="z" value="789"/>
    </b>
</root>

此样式表显示如何测试问题中定义的相等性。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:set="http://exslt.org/sets"  xmlns:exsl="http://exslt.org/common" 
 extension-element-prefixes="set exsl">
<xsl:output method="text" version="1.0" encoding="UTF-8"/> 

<xsl:template match="/">
    <xsl:variable name="values-are-equal">
        <xsl:call-template name="equal">
            <xsl:with-param name="A" select="/root/a"/>
            <xsl:with-param name="B" select="/root/b"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="$values-are-equal = 1">Equal</xsl:when>
        <xsl:otherwise>Inequal</xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="equal">
    <xsl:param name="A" />
    <xsl:param name="B" />
    <xsl:variable name="common-keys" select="$A/item/@key[ count(set:distinct(  . | $B/item/@key )) = count( set:distinct( $B/item/@key ) ) ]"/>
    <xsl:variable name="differences">
        <xsl:for-each select="$common-keys">
            <xsl:if test="$A/item[@key = current()]/@value != $B/item[@key = current()]/@value">
                <different/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="count( exsl:node-set($differences)/* ) > 0">0</xsl:when>
        <xsl:otherwise>1</xsl:otherwise>
    </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

这使用了一些扩展,这些扩展在 xsltproc 和其他处理器中可用。

【讨论】:

  • 没有必要使用任何扩展,并且存在更简单/更短的解决方案 - 请参阅我的答案。
猜你喜欢
  • 2017-01-13
  • 2021-03-21
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多