我希望能够根据子节点的值比较两个节点。使用=
运算符测试节点是否相等只是比较各自节点的字符串值。我想根据它们子节点中的值对它们进行比较。
更具体一点,我希望<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
首先,名为"equals"的关系不能命名为。
"Equals"表示该关系是等价关系。根据定义,任何等价关系~
必须是:
-
自反:
x ~ x
. -
对称:如果
x ~ y
则y ~ x
-
传递性:如果
x ~ y
和y ~ z
则x ~ z
.
这里有一个例子,表明所建议的"equals"关系不是传递的:
x
is:
<a>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-three" id="9088"/>
</a>
y
is:
<b>
<c type="type-one" id="5675"/>
<c type="type-two" id="3423"/>
<c type="type-four" id="1234"/>
</b>
z
is:
<b>
<c type="type-three" id="3333"/>
<c type="type-four" id="1234"/>
</b>
现在我们可以看到x ~ y
和y ~ 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
这是我想到的。给定一个像这样的玩具数据集:
<?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和其他处理器中可用。