Reputation: 49
In my XML document, I have the following kind of nodes:
<parent>
<value id="value1" name="Y" type="number"/>
<value id="value2" name="X" type="number"/>
<value id="value3" name="Z" type="operation" op="-" args="value1;value2"/>
</parent>
And I want to transform this to get the full operation looking like this :
<parent>
<value id="value1" name="Y" type="number" />
<value id="value2" name="X" type="number" />
<operation>
<name>Z = Y - X</name>
</operation>
</parent>
I'm struggling with my xsl template. Here is the full XSL code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings">
<xsl:template match="@*|node()" priority="0">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="value[@type='operation']" priority="1">
<xsl:variable name="name">
<xsl:value-of select="concat(@name, ' = ')" />
<xsl:for-each select="str:tokenize(@args, ';')">
<xsl:choose>
<xsl:when test="//value[@id=current()]">
<xsl:value-of
select="concat(//value[@id=current()]/@name, ' ', @op, ' ')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(current(), ' ', @op, ' ')" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<operation>
<name>
<xsl:value-of select="$name" />
</name>
</operation>
</xsl:template>
</xsl:stylesheet>
Inside the fore-each I am checking if it can find the corrent node because sometimes, the @args of the operation can be something like args="2.00;value1"
for example.
There is obviously something wrong with my test inside the for-each because the result I am getting for the input file shown above is
<?xml version="1.0" encoding="UTF-8"?>
<parent>
<value id="value1" name="Y" type="number" />
<value id="value2" name="X" type="number" />
<operation xmlns:str="http://exslt.org/strings">
<name>Z = value1 value2 </name>
</operation>
</parent>
What should the test be to get the name of the correct value ?
Upvotes: 2
Views: 55
Reputation: 167571
With XSLT 1 and the EXSLT str:tokenize
you can use
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
exclude-result-prefixes="str"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings">
<xsl:template match="@*|node()" priority="0">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:key name="val-ref" match="value[@id]" use="@id"/>
<xsl:template match="value[@type='operation']" priority="1">
<xsl:variable name="op" select="."/>
<xsl:variable name="name">
<xsl:value-of select="concat(@name, ' = ')" />
<xsl:for-each select="str:tokenize(@args, ';')">
<xsl:if test="position() > 1">
<xsl:value-of select="concat(' ', $op/@op, ' ')"/>
</xsl:if>
<xsl:variable name="value" select="."/>
<xsl:for-each select="$op">
<xsl:choose>
<xsl:when test="key('val-ref', $value)">
<xsl:value-of
select="key('val-ref', $value)/@name" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<operation>
<name>
<xsl:value-of select="$name" />
</name>
</operation>
</xsl:template>
</xsl:stylesheet>
http://xsltransform.net/3MP2uCm
With XSLT 3 it becomes way more compact:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:key name="val-ref" match="value[@id]" use="@id"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="value[@op]">
<operation>
<name>
<xsl:value-of select="@name || ' = '"/>
<xsl:value-of select="tokenize(@args, ';') ! (key('val-ref', ., current()/ancestor::parent)/@name, .)[1]"
separator=" {@op} "/>
</name>
</operation>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jz1PuP6
Upvotes: 2
Reputation: 116993
AFAICT, you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="arg-val" match="value" use="@id" />
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="value[@type='operation']">
<xsl:variable name="arg1" select="substring-before(@args, ';')"/>
<xsl:variable name="arg2" select="substring-after(@args, ';')"/>
<xsl:variable name="val1" select="key('arg-val', $arg1)/@name" />
<xsl:variable name="val2" select="key('arg-val', $arg2)/@name" />
<operation>
<name>
<xsl:value-of select="@name"/>
<xsl:text> = </xsl:text>
<xsl:choose>
<xsl:when test="$val1">
<xsl:value-of select="$val1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$arg1"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<xsl:value-of select="@op"/>
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="$val2">
<xsl:value-of select="$val2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$arg2"/>
</xsl:otherwise>
</xsl:choose>
</name>
</operation>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2