xsl-list
[Top] [All Lists]

Re: [xsl] compare two nodes (the child elements, not the string values) in XSLT 1.0

2011-07-07 03:47:52
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/*) &gt; 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) &gt; 1">
                        <xsl:value-of select="$strList[1]"/>
                        <xsl:call-template name="stringJoin">
                               <xsl:with-param name="strList" 
select="$strList[position() &gt; 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(_at_)totschnig(_dot_)org> 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

--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--