Here is a simplified version of my source XML
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<ID>1234</ID>
<Locaitons>
<Location>
<Primary>N</Primary>
<Name>Oslo</Name>
</Location>
<Location>
<Primary>N</Primary>
<Name>Munich</Name>
</Location>
<Location>
<Primary>Y</Primary>
<Name>London</Name>
</Location>
<Location>
<Primary>Y</Primary>
<Name>New York</Name>
</Location>
</Locaitons>
</Worker>
<Worker>
<ID>6789</ID>
<Locaitons>
<Location>
<Primary>Y</Primary>
<Name>Zurich</Name>
</Location>
<Location>
<Primary>N</Primary>
<Name>Brisbane</Name>
</Location>
<Location>
<Primary>Y</Primary>
<Name>Paris</Name>
</Location>
<Location>
<Primary>Y</Primary>
<Name>Toronto</Name>
</Location>
</Locaitons>
</Worker>
</Workers>
Expected Output is
1234,Oslo,Default,Oslo
1234,Munich,Default,Munich
1234,London,01,,London <!-- 3rd field's position id is 01 -->
1234,New York,02,,New York <!-- 3rd field's position id is 02 -->
6789,Zurich,01,,Zurich <!-- 3rd field's position id is 01 -->
6789,Brisbane,Default,Brisbane
6789,Paris,02,,Paris <!-- 3rd field's position id is 02 -->
6789,Toronto,03,,Toronto <!-- 3rd field's position id is 03 -->
My attempt is
<?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="xs"
version="2.0">
<xsl:variable name="linefeed" select="'
'"/>
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:template match="Workers">
<xsl:for-each select="Worker/Locaitons/Location">
<xsl:call-template name="rec"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="rec">
<xsl:value-of select="../../ID"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="Name"/>
<xsl:value-of select="$delimiter"/>
<xsl:choose>
<xsl:when test="Primary='Y'">
<xsl:value-of select="format-number(position(),'00')"/>
<xsl:value-of select="$delimiter"/>
</xsl:when>
<xsl:when test="Primary='N'">
<xsl:text>Default</xsl:text>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="Name"/>
<xsl:value-of select="'
'"/>
</xsl:template>
</xsl:stylesheet>
Current Output is
1234,Oslo,Default,Oslo
1234,Munich,Default,Munich
1234,London,03,,London <!-- correct value for 3rd field should be 01 -->
1234,New York,04,,New York <!-- correct value for 3rd field should be 02 -->
6789,Zurich,05,,Zurich <!-- correct value for 3rd field should be 01 -->
6789,Brisbane,Default,Brisbane
6789,Paris,07,,Paris <!-- correct value for 3rd field should be 02 -->
6789,Toronto,08,,Toronto <!-- correct value for 3rd field should be 03 -->
The position id of the node 1,2,3 etc.. should be populated based on the value of element <Primary> = Y
. I cannot add condition Primary = Y
in <xsl:for-each select="Worker/Locaitons/Location[Primary='Y']">
because of several other validations that I’m performing inside rec
template.
Any help is appreciated to get the desired output?
Thank you!
Use xsl:number
instead of position()
:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/Workers">
<xsl:for-each select="Worker">
<xsl:variable name="id" select="ID" />
<xsl:for-each select="Locaitons/Location">
<xsl:value-of select="$id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>,</xsl:text>
<xsl:choose>
<xsl:when test="Primary='Y'">
<xsl:number count="Location[Primary='Y']" from="Worker" format="01"/>
<xsl:text>,</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Default,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="Name"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If it’s permissible to change the internal order of the locations, you could do:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Worker">
<xsl:apply-templates select="Locaitons/Location[Primary='Y']"/>
<xsl:apply-templates select="Locaitons/Location[Primary='N']"/>
</xsl:template>
<xsl:template match="Location" priority="1">
<xsl:value-of select="../../ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Name"/>
<xsl:text>,</xsl:text>
<xsl:next-match/>
<xsl:value-of select="Name"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="Location[Primary='Y']">
<xsl:value-of select="format-number(position(), '00')"/>
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="Location[Primary='N']">
<xsl:text>Default,</xsl:text>
</xsl:template>
</xsl:stylesheet>
1
When you call the position()
function, you are finding the position of the context node within the “dynamic context” established by the earlier <xsl:for-each select="Worker/Locaitons/Location">
. So the number reflects the position of the Location
within the sequence of all the Location
elements that are descendants of the Workers
element.
It seems to me you want the position of the Location
within its parent Locaitons
element (that element’s name is misspelled, by the way). That would be given by the expression 1 + count(preceding-sibling::Location)
. e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs=" http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="linefeed" select="'
'"/>
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:template match="Workers">
<xsl:for-each select="Worker/Locaitons/Location">
<xsl:call-template name="rec"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="rec">
<xsl:value-of select="../../ID"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="Name"/>
<xsl:value-of select="$delimiter"/>
<xsl:choose>
<xsl:when test="Primary='Y'">
<xsl:value-of select="format-number(1 + count(preceding-sibling::Location[Primary='Y']),'00')"/>
</xsl:when>
<xsl:when test="Primary='N'">
<xsl:text>Default</xsl:text>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="Name"/>
<xsl:value-of select="'
'"/>
</xsl:template>
</xsl:stylesheet>
Personally, though, my normal practice when writing XSLT that generates plain text, is to use <xsl:sequence select="some long XPath expression"/>
rather than a large number of <xsl:*>
statements of one kind or another. The resulting code is usually a lot shorter, and once you’re used to writing more of your logic in XPath, it is easier to read. And performance is better.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:template match="Workers">
<xsl:sequence select="
string-join(
Worker/Locaitons/Location/concat(
../../ID,
$delimiter,
Name,
$delimiter,
if (Primary='Y') then
format-number(1 + count(preceding-sibling::*[*[1]='Y']),'00')
else if (Primary='N') then
'Default'
else
(),
$delimiter,
Name,
'
'
)
)
"/>
</xsl:template>
</xsl:stylesheet>
Result:
1234,Oslo,Default,Oslo
1234,Munich,Default,Munich
1234,London,01,London
1234,New York,02,New York
6789,Zurich,01,Zurich
6789,Brisbane,Default,Brisbane
6789,Paris,02,Paris
6789,Toronto,03,Toronto
3