Link to home
Start Free TrialLog in
Avatar of Grant Rogers
Grant RogersFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Why is my xpath predicate not evaluating as I expect?

Hi guys,

I am trying to compare different parts of an xml document to work out if the nodes have the same value and return a boolean, to do this I am using recursion to go through the list of nodes so I can exit early when I get my match.  I have provided a simplified example below of the problem I am seeing:

XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
	<value>A</value>
	<value>B</value>
	<value>C</value>
	<value>D</value>
	<value>E</value>
	<value>F</value>
</root>

Open in new window


XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="xml" indent="yes"/>

	<xsl:template match="/">
		<xsl:apply-templates select="/root/value">
			<xsl:with-param name="value" select="/root/value[1]"/>
		</xsl:apply-templates>
	</xsl:template>

	<xsl:template match="/root/value">
		<xsl:param name="value" select = "false"/>
		<xsl:message>
			[<xsl:value-of select="."/>][<xsl:value-of select="$value"/>][<xsl:value-of select=". != $value"/>][<xsl:value-of select="following-sibling::value[. != $value][1]"/>]
		</xsl:message>
		<xsl:variable name="next" select="following-sibling::value[. != $value][1]"/>
		<xsl:choose>
			<xsl:when test="$next">
				<xsl:apply-templates select="$next">
					<xsl:with-param name="value" select="$value"/>
				</xsl:apply-templates>
			</xsl:when>
			<xsl:otherwise>
				<xsl:value-of select=". = $value"/>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>
</xsl:stylesheet>

Open in new window


The problem come from this line:
<xsl:variable name="next" select="following-sibling::value[. != $value][1]"/>

Open in new window



Why does is that when I print value-of [. != $value] (just above) it returns false on the first iteration and inside of the xpath predicate it evaluates to the next node "B"?  if the xpath is predicate is false I thought it should not get the node "B"..

Thanks for your help.

Thanks
Avatar of Badotz
Badotz
Flag of United States of America image

This might be part of the problem:
<xsl:variable name="next" select="following-sibling::value[. != $value][1]"/>

Open in new window

perhaps it should be:
<xsl:variable name="next" select="following-sibling::value[.] != $value][1]"/>

Open in new window

Might I suggest you pick up a copy of XSLT by Michael Kay? It is a first-rate compendium of all things XSLT, and an invaluable reference.
Avatar of Grant Rogers

ASKER

Hi Badotz,

Thanks for your reply, I have access to safari books so I have quite a lot of XSLT / XPATH reference material (Wrox, Oreilly and others).  Can you tell me what was wrong with the syntax I used?  In your reply the square brackets don't match up is that on purpose??

SystemId Unknown; Line #16; Column #81; Extra illegal tokens: ']', '[', '1', ']'
file:///home/rogergb/dev/rbam/roleproperties/test.xsl; Line #13; Column #16;
                        [A][A][false][B]

SystemId Unknown; Line #0; Column #0; Can not convert #BOOLEAN to a NodeList!

Open in new window


Thanks
Grant

Sorry, perhaps this:
<xsl:variable name="next" select="following-sibling::value[.] != $value[1]"/>

Open in new window

Hmm, that is not working either.  Does it work for you?
I have no way to test it, sorry.
Avatar of Gertone (Geert Bormans)
It would help if you explained what you are trying to achieve.
I have a feeling that you are trying a very hard way to achieve something simple.
So I would like to see some explanation of what you need.
Generally looping and escaping from a loop in XSLT is a bad idea,
and not often needed because of the set compares

I am not sure what you need , but the following program tests for duplicates... without looping
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" indent="yes"/>
    
    <xsl:template match="/">
        <xsl:apply-templates select="/root/value"/>
    </xsl:template>
    
    <xsl:template match="/root/value">
        <xsl:value-of select="."/>
        <xsl:text>: </xsl:text>
        <xsl:value-of select="boolean(following-sibling::value[. = current()])"/>
        <xsl:text>&#10;</xsl:text>
    </xsl:template>
</xsl:stylesheet>

Open in new window

If you only want to see the first distinct appear...
you should be using keys,
or use this simple method for small sets
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" indent="yes"/>
    
    <xsl:template match="/">
        <xsl:apply-templates select="/root/value[not(. = preceding-sibling::value)]"/>
    </xsl:template>
    
    <xsl:template match="/root/value">
        <xsl:value-of select="."/>
        <xsl:text>: </xsl:text>
        <xsl:value-of select="boolean(following-sibling::value[. = current()])"/>
        <xsl:text>&#10;</xsl:text>
    </xsl:template>
</xsl:stylesheet>

Open in new window

Hi Gertone,

Thanks for your replies, I will try to answer your questions here:

It would help if you explained what you are trying to achieve.
I am trying to compare 2 sets to see if it there is at least 1 match between them (which you have answered here: https://www.experts-exchange.com/questions/27233760/How-do-you-compare-2-node-sets-of-the-same-type-for-a-single-match-between-nodes.html I still need to test this though)

I have a feeling that you are trying a very hard way to achieve something simple.
Possibly, I am still learning :), I thought I was being efficient exiting early from the loop once a match had been found, and I thought I needed to do this for 2 sets (something I wasn't sure was possible using an xpath expression).

Generally looping and escaping from a loop in XSLT is a bad idea
Why is this?  Is it because xpath operations are just as efficient and shorter to type?

I appreciate your examples that you have given and you may well have fixed the overall problem in my other question, but I am still left with my original question why does the test ". != $value" differ from the xapth evaluation [. != $value][1] where one returns false and the other evaluates to true?  My best guess is that the 2 are evaluated differently so the first one compares strings and the second compares nodes (position, name, something else???).  Do you know where in the spec it says about this.

Thanks
ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Sorry for the late reply, I needed a little time to read your answer properly.  I think I understand it now, thanks a lot for your help Gertone.