comparing nodes in xslt

Discussion in 'XML' started by inquirydog, Sep 29, 2004.

  1. inquirydog

    inquirydog Guest

    Hi-

    Does anyone know a way to compare whether two nodes contain
    the same information in xslt (the name, attributes, and all content
    recursivly should be the same. I am interested in the case where node
    ordering matters, and also the case where it doesn't, but perhaps that
    is an advanced topic). Ideally the method should be available to
    xpath expressions, so I think that creating new templates which
    compare nodes will not work (well, it will if you store the results of
    the apply-template in a variable and then use that in an expression,
    but this seems way too complex at times, especially since variables
    are fixed after they are evaluated).

    thanks
    -I
     
    inquirydog, Sep 29, 2004
    #1
    1. Advertising

  2. inquirydog

    Joris Gillis Guest

    Hi,

    I'm not quite sure this reply will be of any use to you.
    This solution only works with nodes with the same ordering.
    As far as I know, there are not (yet) Xpath expressions to deal with
    node comparison (or operations).

    to compare e.g. node[1] and node[2], use :

    <xsl:variable name="identical">
    <xsl:call-template name="check_identical">
    <xsl:with-param name="comp1"><xsl:copy-of
    select="//node[1]"/></xsl:with-param>
    <xsl:with-param name="comp2"><xsl:copy-of
    select="//node[2]"/></xsl:with-param>
    </xsl:call-template>
    </xsl:variable>
    <xsl:if test="$identical='true'">nodes completely are
    identical</xsl:if>
    <xsl:if test="$identical='false'">nodes differ in at least one
    aspect</xsl:if>

    These 2 templates should be included/inserted in the stylesheet:

    <xsl:template name="check_identical">
    <xsl:param name="comp1"/>
    <xsl:param name="comp2"/>
    <xsl:variable name="string1">
    <xsl:call-template name="stringify">
    <xsl:with-param name="node"><xsl:copy-of
    select="$comp1"/></xsl:with-param>
    </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="string2">
    <xsl:call-template name="stringify">
    <xsl:with-param name="node"><xsl:copy-of
    select="$comp2"/></xsl:with-param>
    </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$string1=$string2"/>
    </xsl:template>

    The following template 'stringifies' a given node i.e. convert it to
    the ascii equivalent like one would see when opening an xml file with
    a text editor.

    <xsl:template name="stringify">
    <xsl:param name="node"/>
    <xsl:for-each select="$node/*/*|$node/*/text()">
    <xsl:choose>
    <xsl:when test="boolean(local-name())">
    &lt;<xsl:value-of select="local-name()"/>
    <xsl:variable name="pos" select="position()"/>
    <xsl:for-each select="@*">
    <xsl:text> </xsl:text><xsl:value-of
    select="local-name()"/>="<xsl:value-of select="."/>"
    </xsl:for-each>
    <xsl:call-template name="stringify">
    <xsl:with-param name="node"><xsl:copy-of
    select="."/></xsl:with-param>
    </xsl:call-template>
    &lt;/<xsl:value-of select="local-name()"/>&gt;
    </xsl:when>
    <xsl:eek:therwise><xsl:value-of
    select="normalize-space(.)"/></xsl:eek:therwise>
    </xsl:choose>
    </xsl:for-each>
    </xsl:template>

    Joris Gillis
     
    Joris Gillis, Sep 29, 2004
    #2
    1. Advertising

  3. inquirydog

    inquirydog Guest

    Hi-

    Thanks for the reply. I looked into it a bit and found that
    in XSLT 2 you can define xpath functions using xslt elements. This
    certainly makes a solution like this more reasonable. It does still
    leave certain problems like counting repeat nodes in a list.... For
    instance, if you wanted to eliminate doubles in a list like this

    <list>
    <element name="1" />
    <element name="2" />
    <element name="1" />
    <element name="1" />
    <element name="1" />
    <element name="2" />
    </list>

    converting to

    <list>

    <element name"1" count="4" />
    <element name="2" count="2" />

    </list>

    you need to group by element type and then count the number of
    elements. Except for very constrained cases this seems to me to be
    impossible to do in xslt (yes I know about the muenchian method- it
    only works in a constrained case). To do this grouping you need to
    first be able to check for node equality (which I think you can do in
    xslt 2) and then do the grouping based on the equality (which is
    better supported in xslt 2 but still can't do what I describe in this
    email).

    thanks
    -I


    > Hi,
    >
    > I'm not quite sure this reply will be of any use to you.
    > This solution only works with nodes with the same ordering.
    > As far as I know, there are not (yet) Xpath expressions to deal with
    > node comparison (or operations).
    >
    > to compare e.g. node[1] and node[2], use :
    >
    > <xsl:variable name="identical">
    > <xsl:call-template name="check_identical">
    > <xsl:with-param name="comp1"><xsl:copy-of
    > select="//node[1]"/></xsl:with-param>
    > <xsl:with-param name="comp2"><xsl:copy-of
    > select="//node[2]"/></xsl:with-param>
    > </xsl:call-template>
    > </xsl:variable>
    > <xsl:if test="$identical='true'">nodes completely are
    > identical</xsl:if>
    > <xsl:if test="$identical='false'">nodes differ in at least one
    > aspect</xsl:if>
    >
    > These 2 templates should be included/inserted in the stylesheet:
    >
    > <xsl:template name="check_identical">
    > <xsl:param name="comp1"/>
    > <xsl:param name="comp2"/>
    > <xsl:variable name="string1">
    > <xsl:call-template name="stringify">
    > <xsl:with-param name="node"><xsl:copy-of
    > select="$comp1"/></xsl:with-param>
    > </xsl:call-template>
    > </xsl:variable>
    > <xsl:variable name="string2">
    > <xsl:call-template name="stringify">
    > <xsl:with-param name="node"><xsl:copy-of
    > select="$comp2"/></xsl:with-param>
    > </xsl:call-template>
    > </xsl:variable>
    > <xsl:value-of select="$string1=$string2"/>
    > </xsl:template>
    >
    > The following template 'stringifies' a given node i.e. convert it to
    > the ascii equivalent like one would see when opening an xml file with
    > a text editor.
    >
    > <xsl:template name="stringify">
    > <xsl:param name="node"/>
    > <xsl:for-each select="$node/*/*|$node/*/text()">
    > <xsl:choose>
    > <xsl:when test="boolean(local-name())">
    > &lt;<xsl:value-of select="local-name()"/>
    > <xsl:variable name="pos" select="position()"/>
    > <xsl:for-each select="@*">
    > <xsl:text> </xsl:text><xsl:value-of
    > select="local-name()"/>="<xsl:value-of select="."/>"
    > </xsl:for-each>
    > <xsl:call-template name="stringify">
    > <xsl:with-param name="node"><xsl:copy-of
    > select="."/></xsl:with-param>
    > </xsl:call-template>
    > &lt;/<xsl:value-of select="local-name()"/>&gt;
    > </xsl:when>
    > <xsl:eek:therwise><xsl:value-of
    > select="normalize-space(.)"/></xsl:eek:therwise>
    > </xsl:choose>
    > </xsl:for-each>
    > </xsl:template>
    >
    > Joris Gillis
     
    inquirydog, Oct 1, 2004
    #3
  4. (inquirydog) writes:

    > Hi-
    >
    > Thanks for the reply. I looked into it a bit and found that
    > in XSLT 2 you can define xpath functions using xslt elements. This
    > certainly makes a solution like this more reasonable. It does still
    > leave certain problems like counting repeat nodes in a list.... For
    > instance, if you wanted to eliminate doubles in a list like this
    >
    > <list>
    > <element name="1" />
    > <element name="2" />
    > <element name="1" />
    > <element name="1" />
    > <element name="1" />
    > <element name="2" />
    > </list>
    >
    > converting to
    >
    > <list>
    >
    > <element name"1" count="4" />
    > <element name="2" count="2" />
    >
    > </list>
    >
    > you need to group by element type and then count the number of
    > elements. Except for very constrained cases this seems to me to be
    > impossible to do in xslt (yes I know about the muenchian method- it
    > only works in a constrained case). To do this grouping you need to
    > first be able to check for node equality (which I think you can do in
    > xslt 2) and then do the grouping based on the equality (which is
    > better supported in xslt 2 but still can't do what I describe in this
    > email).
    >


    What do you mean by

    > but still can't do what I describe in this email.


    ?

    Your grouping example seems quite simple to do in either XSLT1 or XSLT2,
    it might loook a bit simpler in 2 using the explict xsl:for-each-group
    mechanism, but it doesn't take much code in either case.

    For example in 1.0 it would be:

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

    <xsl:key name="x" match="element" use="@name"/>

    <xsl:template match="list">
    <list>
    <xsl:for-each select="element[generate-id()=generate-id(key('x',@name))]">
    <element name="{@name}" count="{count(key('x',@name))}"/>
    </xsl:for-each>
    </list>
    </xsl:template>

    </xsl:stylesheet>



    David
     
    David Carlisle, Oct 4, 2004
    #4
  5. inquirydog

    inquirydog Guest

    Hi-

    The method you describe is the Muenchian Method that I
    mentioned in my posting.... And it only works for very simple cases,
    where 1). you know ahead of time where the list to be counted is, and
    2). where the list items are trivial content (all of the same type).
    For instance just change one of the elements by adding content to it

    <element name="1"> => <element name="1"><content /></element>

    the example counts this as the same type of element as the ones
    without content.

    I repeat what I stated before- XSLT 1.0 and 2.0 are (I am pretty sure)
    not capable of counting repeat nodes in a list in the general case,
    where the list can appear anywhere in the node tree and where the
    elements in the list are not of some predetermined type (they can
    contain arbitrary content). This is a limitation of xslt that I
    believe should be fixed in future revisions.

    thanks
    -I


    > Your grouping example seems quite simple to do in either XSLT1 or XSLT2,
    > it might loook a bit simpler in 2 using the explict xsl:for-each-group
    > mechanism, but it doesn't take much code in either case.
    >
    > For example in 1.0 it would be:
    >
    > <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    >
    > <xsl:key name="x" match="element" use="@name"/>
    >
    > <xsl:template match="list">
    > <list>
    > <xsl:for-each select="element[generate-id()=generate-id(key('x',@name))]">
    > <element name="{@name}" count="{count(key('x',@name))}"/>
    > </xsl:for-each>
    > </list>
    > </xsl:template>
    >
    > </xsl:stylesheet>
     
    inquirydog, Oct 7, 2004
    #5
  6. You say

    <element name="1"> => <element name="1"><content /></element>

    the example counts this as the same type of element as the ones
    without content.

    well yes of course because you (and so I) are only grouping on the name
    attribute. If you want to consider something else as being a
    distinguishing factor then just include it in the test eg
    use
    concat(@name,':',count(*))
    would distinguish nodes based on their name attribute and the number of
    children. I suspect that you want a test that is more like deep-equal
    (which is a predefined function in xpath2) so perhaps your request
    > This is a limitation of xslt that I
    > believe should be fixed in future revisions.


    has been granted in Xpath2. If that is the case you probably can't express that as
    a single Xpath in Xpath 1 (without using an extension function,) but you
    can certainly express it in xslt1 so you can do the grouping, you
    probably just can't use the optimisation of using a key as the key index
    needs to be expressed as a single xpath. But keys are only an
    optimisation feature anything expressable using keys is expressable
    without them (just may take longer to run)

    of course any xpath extension that allows xpath functions to be defined
    using xslt would allow this kind of key to be used even in an xslt1
    context (saxon and the exslt project have such function definition
    possibilitiesfor xslt1, and they are a standard part of xslt2 draft)

    David
     
    David Carlisle, Oct 8, 2004
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. asd
    Replies:
    3
    Views:
    440
    Arnaud Berger
    May 23, 2005
  2. gavnosis
    Replies:
    0
    Views:
    524
    gavnosis
    Aug 2, 2003
  3. Timo Nentwig

    selecting nodes between other nodes

    Timo Nentwig, Jun 16, 2004, in forum: XML
    Replies:
    1
    Views:
    409
    Patrick TJ McPhee
    Jun 17, 2004
  4. Johnny Ooi

    Looking A Nodes From Within Nodes

    Johnny Ooi, Nov 13, 2004, in forum: XML
    Replies:
    10
    Views:
    657
    Johnny Ooi
    Nov 14, 2004
  5. Ryan  Adler
    Replies:
    2
    Views:
    394
Loading...

Share This Page