Subject: Re: [xsl] compare two nodes (the child elements, not the string values) in XSLT 1.0 From: Mukul Gandhi <gandhi.mukul@xxxxxxxxx> Date: Thu, 7 Jul 2011 14:17:19 +0530 |
Not encouraging a 1.0 solution, but here's a solution I came up with, <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml" omit-xml-declaration="yes"/> <!-- main template --> <xsl:template match="outer"> <xsl:variable name="isElemNodesEqual"> <xsl:call-template name="areElemNodesEqual"> <xsl:with-param name="node1" select="*[1]"/> <xsl:with-param name="node2" select="*[2]"/> </xsl:call-template> </xsl:variable> <!-- print result --> <xsl:value-of select="normalize-space($isElemNodesEqual)"/> </xsl:template> <!-- check if two element nodes are deep equal --> <xsl:template name="areElemNodesEqual"> <xsl:param name="node1"/> <xsl:param name="node2"/> <xsl:if test="(local-name($node1) = local-name($node2)) and (namespace-uri($node1) = namespace-uri($node2))"> <!-- check equality of text content --> <xsl:variable name="isTextContentEqual"> <xsl:choose> <xsl:when test="count($node1/* | $node2/*) = 0"> <!-- both node1 & node2 do not contain element children --> <xsl:if test="$node1/text() = $node2/text()"> Equal </xsl:if> </xsl:when> <xsl:when test="(count($node1/*) > 0) and (count($node1/*) = count($node2/*))"> <!-- node1 & node2 have same numbers (gt 0) of element children --> <xsl:variable name="text1"> <xsl:call-template name="stringJoin"> <xsl:with-param name="strList" select="$node1/text()"/> </xsl:call-template> </xsl:variable> <xsl:variable name="text2"> <xsl:call-template name="stringJoin"> <xsl:with-param name="strList" select="$node2/text()"/> </xsl:call-template> </xsl:variable> <xsl:if test="normalize-space($text1) = normalize-space($text2)"> Equal </xsl:if> </xsl:when> </xsl:choose> </xsl:variable> <!-- check equality of attributes --> <xsl:variable name="isAttrSeqEqual"> <xsl:if test="normalize-space($isTextContentEqual) = 'Equal'"> <xsl:call-template name="areAttributeSeqEqual"> <xsl:with-param name="attrSeq1" select="$node1/@*"/> <xsl:with-param name="attrSeq2" select="$node2/@*"/> </xsl:call-template> </xsl:if> </xsl:variable> <xsl:if test="normalize-space($isAttrSeqEqual) = 'Equal'"> <!-- if attributes are identical, check equality of child element sequences --> <xsl:variable name="equalCount"> <xsl:if test="count($node1/*) = count($node2/*)"> <xsl:for-each select="$node1/*"> <xsl:variable name="nodeRef1" select="."/> <xsl:variable name="pos" select="position()"/> <xsl:variable name="nodeRef2" select="$node2/*[$pos]"/> <xsl:variable name="isElemNodesEqual"> <xsl:call-template name="areElemNodesEqual"> <xsl:with-param name="node1" select="$nodeRef1"/> <xsl:with-param name="node2" select="$nodeRef2"/> </xsl:call-template> </xsl:variable> <xsl:if test="normalize-space($isElemNodesEqual) = 'Equal'"> 1 </xsl:if> </xsl:for-each> </xsl:if> </xsl:variable> <xsl:if test="string-length(translate(normalize-space($equalCount), ' ', '')) = count($node1/*)"> Equal </xsl:if> </xsl:if> </xsl:if> </xsl:template> <!-- do a string-join operation on a sequence of strings --> <xsl:template name="stringJoin"> <xsl:param name="strList"/> <xsl:choose> <xsl:when test="count($strList) > 1"> <xsl:value-of select="$strList[1]"/> <xsl:call-template name="stringJoin"> <xsl:with-param name="strList" select="$strList[position() > 1]"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$strList[1]"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- check if two attribute sequences are equal --> <xsl:template name="areAttributeSeqEqual"> <xsl:param name="attrSeq1"/> <xsl:param name="attrSeq2"/> <xsl:variable name="equalCount"> <xsl:if test="count($attrSeq1) = count($attrSeq2)"> <xsl:for-each select="$attrSeq1"> <xsl:variable name="attr1ExpName" select="concat(local-name(.), ':', namespace-uri(.))"/> <xsl:variable name="attr1Val" select="."/> <xsl:for-each select="$attrSeq2"> <xsl:variable name="attr2ExpName" select="concat(local-name(.), ':', namespace-uri(.))"/> <xsl:variable name="attr2Val" select="."/> <xsl:if test="($attr1ExpName = $attr2ExpName) and ($attr1Val = $attr2Val)"> 1 </xsl:if> </xsl:for-each> </xsl:for-each> </xsl:if> </xsl:variable> <xsl:if test="string-length(translate(normalize-space($equalCount), ' ', '')) = count($attrSeq1)"> Equal </xsl:if> </xsl:template> </xsl:stylesheet> This needs an instance document like following (attributes are written only for illustration), <outer> <author at1="1" at2="2"> <first>John</first> <last>Doe</last> </author> <author at1="1" at2="2"> <first>John</first> <last>Doe</last> </author> </outer> Then the stylesheet would compare the sibling "author" nodes in this example. Here are the assumptions with which this stylesheet was written, 1. Mixed content is recognized 2. Non significant white spaces are discarded (e.g whitespace only nodes between adjacent start tags, or between adjacent end tags) 3. For string only element content, white spaces are significant 4. Sequence relationship between element children is honored 5. Attribute equality on elements are considered, and attribute order is insignificant 6. Only "local name" and "namespace name" of element or attribute nodes are significant to check for equality of node names. i.e namespace prefixes are not significant in this equality program. 7. The stylesheet caters to only element, attribute and text nodes (excluding non significant text nodes as described above[point 2]). But it should be possible to extend this stylesheet, for other XDM node types. 8. The comparison of text data is not type aware. i.e all the text is considered as string. If we had used the XPath 2.0 deep-equal function (ref, http://www.w3.org/TR/xpath-functions/#func-deep-equal), we would have got following benefits, 1. All XDM node types would be supported. Even a comparison of xs:anyAtomicType* sequences (which could be homogeneous or heterogeneous) would be supported. 2. It can be a type aware comparison if the instance document was validated by a schema. 3. deep-equal function supports pluggable collations. A side note: if you're constrained to XSLT 1.0 environment, and can invoke java extensions, you may consider invoking the W3C DOM method, "isEqualNode" (ref, http://xerces.apache.org/xerces2-j/javadocs/api/org/w3c/dom/Node.html. see method "isEqualNode"). the algorithm described in documentation of DOM "isEqualNode" method, is actually implemented in the above stylesheet (though you may notice discrepancies [oversight or any bugs in this stylesheet] between "isEqualNode" algorithm and this stylesheet, which you may report). Please feel free to report bugs in this stylesheet, and I would try to fix the errors! If you can use the deep-equal function or the "isEqualNode" method, then you don't need this stylesheet :) On Thu, Jul 7, 2011 at 10:25 AM, Wolfhart Totschnig <wolfhart@xxxxxxxxxxxxx> wrote: > Hi, > > Thanks to all who responded! > > For my particular case, I found a less complicated solution than Wendell's, > but it's a particular solution, not a general one: > > <xsl:variable name="last" select="last"/> > <xsl:if test="path/person/last=last and (not(first) or > path/person[last=$last]/first=first) and (not(middle) or > path/person[last=$last]/middle=middle)"> > > I am amazed that there is no simple general solution for this kind of > problem in XSLT 1.0. I thought I must be overlooking something obvious. XSLT > 2.0 indeed makes life easier! > > Thanks again, > Wolfhart -- Regards, Mukul Gandhi
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] compare two nodes (the ch, Wendell Piez | Thread | Re: [xsl] compare two nodes (the ch, Wendell Piez |
Re: [xsl] compare two nodes (the ch, Michel Hendriksen | Date | Re: [xsl] Find distinct nodes from , Wendell Piez |
Month |