Reputation: 399
I want to thank's to this community for helping me so much lately. You are my saviours :)
I'm trying to verify when a number (x) is different from the previous one
So my list was sorted, first I have:
<xsl:apply-templates select="house">
<xsl:sort select="./point/x"/>
</xsl:apply-templates>
the important part is:
<xsl:template match="house">
<xsl:if test="./point/x != preceding-sibling::house[1]/point/x">
<br/>
<xsl:value-of select="preceding-sibling::house[1]/point/x"/>
value changed to <xsl:value-of select="./point/x"/>
</xsl:if>
<!-- Just printing -->
<br/>
<td><xsl:value-of select="./point/x"/></td>
<td><xsl:value-of select="./point/y"/></td>
</xsl:template>
Note I have 2 values in print, x and y, but Im only working with x
I want output to be something like:
xy
00
01
0 value change to 1
10
11
somehow my result goes get the wrong previous number
00
1 value change to 0
01
0 value change to 1
10
0 value change to 1
11
Progress: I found out that by removing the sort the output is correct
00
0 value change to 1
10
1 value change to 0
01
0 value change to 1
11
So my question now is... how can I do this procedure?
Upvotes: 2
Views: 1523
Reputation: 243449
Here is a much more efficient (linear vs O(N^2*log(N)) XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kHouseById" match="house" use="generate-id()"/>
<xsl:variable name="vIdsOfSorted">
<xsl:for-each select="/*/house">
<xsl:sort select="point/x" data-type="number"/>
<xsl:copy-of select="concat(generate-id(), ' ')"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/*">
<xsl:call-template name="traverseIds">
<xsl:with-param name="pIds" select="concat($vIdsOfSorted, ' ')"/>
<xsl:with-param name="pLength" select="count(house)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="traverseIds">
<xsl:param name="pIds"/>
<xsl:param name="pLength"/>
<xsl:param name="pPos" select="1"/>
<xsl:param name="pPrevId" select="''"/>
<xsl:if test="not($pPos > $pLength)">
<xsl:variable name="vId" select="substring-before($pIds, ' ')"/>
<xsl:variable name="vHouse" select="key('kHouseById', $vId)"/>
<xsl:value-of select=
"concat('
(', $vHouse/point/x, ',', $vHouse/point/y, ')')"/>
<xsl:if test="not($pPos >= $pLength)">
<xsl:variable name="vNextId" select=
"substring-before(substring-after($pIds, ' '), ' ')"/>
<xsl:variable name="vNextHouse" select="key('kHouseById', $vNextId)"/>
<xsl:if test="not($vHouse/point/x = $vNextHouse/point/x)">
<xsl:value-of select=
"concat('
value changed to ', $vNextHouse/point/x)"/>
</xsl:if>
<xsl:call-template name="traverseIds">
<xsl:with-param name="pIds" select="substring-after($pIds, ' ')"/>
<xsl:with-param name="pLength" select="$pLength"/>
<xsl:with-param name="pPos" select="$pPos+1"/>
<xsl:with-param name="pPrevId" select="$vId"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<house>
<point>
<x>1</x><y>0</y>
</point>
</house>
<house>
<point>
<x>0</x><y>1</y>
</point>
</house>
<house>
<point>
<x>0</x><y>0</y>
</point>
</house>
<house>
<point>
<x>1</x><y>1</y>
</point>
</house>
</t>
the wanted, correct result is produced:
(0,1)
(0,0)
value changed to 1
(1,0)
(1,1)
In case you don't love recursion, here is a non-recursive XSLT 1.0 solution that uses the xxx:node-set()
extension function:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output method="text"/>
<xsl:variable name="vrtfSorted">
<xsl:for-each select="/*/house">
<xsl:sort select="point/x" data-type="number"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vSorted" select="ext:node-set($vrtfSorted)/*"/>
<xsl:template match="/*">
<xsl:for-each select="$vSorted">
<xsl:variable name="vPos" select="position()"/>
<xsl:if test=
"$vPos > 1
and
not(point/x = $vSorted[position()=$vPos -1]/point/x)">
<xsl:text>
</xsl:text>
<xsl:value-of select="concat('value changed to ', point/x)"/>
</xsl:if>
<xsl:text>
</xsl:text>
<xsl:value-of select="concat('(',point/x,',',point/y, ')')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
produces exactly the same, correct result.
II. XSLT 2.0 solution
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:variable name="vSorted">
<xsl:perform-sort select="house">
<xsl:sort select="xs:integer(point/x)"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:sequence select=
"(for $n in 2 to count($vSorted/*),
$n-1 in $n -1,
$vNewX in $vSorted/*[$n]/point/x,
$vOldX in $vSorted/*[$n-1]/point/x,
$vNewY in $vSorted/*[$n]/point/y,
$vOldY in $vSorted/*[$n-1]/point/y,
$remark in
if($vNewX ne $vOldX)
then concat('
value changed to ', $vNewX, '
')
else '
'
return
concat('(',$vOldX,',',$vOldY, ')', $remark)
),
concat('(',$vSorted/*[last()]/point/x,',',
$vSorted/*[last()]/point/y, ')'
)
"/>
</xsl:template>
</xsl:stylesheet>
When applied to the same XML document (above), the same correct result is produced:
(0,1)
(0,0)
value changed to 1
(1,0)
(1,1)
Upvotes: 3
Reputation: 70618
Here is an alternate approach. You might want to think in terms of groups. As you have tagged the question xslt 2.0, this does come with some useful grouping functions (In xslt 1.0 you would have to make use of the Meunchian Grouping technique)
Effectively you are grouping houses by the point/x value, so you get each distinct group like so:
<xsl:for-each-group select="house" group-by="point/x">
<xsl:sort select="point/x"/>
Then you could iterate over the houses within the group like so:
<xsl:for-each select="current-group()">
<xsl:sort select="point/y"/>
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text"/>
<xsl:template match="houses">
<xsl:text>xy </xsl:text>
<xsl:for-each-group select="house" group-by="point/x">
<xsl:sort select="point/x"/>
<xsl:if test="position() > 1">
<xsl:value-of select="concat(' value changed to ', point/x, ' ')" />
</xsl:if>
<xsl:for-each select="current-group()">
<xsl:sort select="point/y"/>
<xsl:value-of select="concat(point/x, point/y, ' ')" />
</xsl:for-each>
<xsl:if test="position() < last()">
<xsl:value-of select="point/x" />
</xsl:if>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When applied to the following XML....
<houses>
<house><point><x>1</x><y>0</y></point></house>
<house><point><x>2</x><y>1</y></point></house>
<house><point><x>0</x><y>1</y></point></house>
<house><point><x>1</x><y>1</y></point></house>
<house><point><x>0</x><y>0</y></point></house>
<house><point><x>2</x><y>0</y></point></house>
</houses>
The following is output:
xy
00
01
0 value changed to 1
10
11
1 value changed to 2
20
21
Upvotes: 2
Reputation: 338208
Not very nice, but if you must know "the previous value of x according to a certain order", then you must order the nodes again and fetch the right one:
<xsl:template match="house">
<xsl:variable name="current-position" select="position()" />
<xsl:variable name="preceding-x-ordered">
<xsl:for-each select="../house">
<xsl:sort select="point/x" />
<xsl:if test="position() = $current-position - 1">
<xsl:value-of select="point/x" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="position() > 1 and point/x != $preceding-x-ordered">
<br/>
<xsl:value-of select="$preceding-x-ordered"/>
<xsl:text> value changed to </xsl:text>
<xsl:value-of select="point/x"/>
</xsl:if>
<!-- Just printing -->
<br/>
<td><xsl:value-of select="point/x"/></td>
<td><xsl:value-of select="point/y"/></td>
</xsl:template>
XSLT is supposed to be side-effects free, which means that the processor can process templates in any order it wishes without changing the overall result. Not "knowing" what it did in the last iteration of a loop is a consequence of that requirement. Hence there is no way that the preceding-sibling
axis can know what node was processed earlier.
Upvotes: 3