XSLT - Problems changing the node context in conjunction with the identity pattern - xslt

I have a given source XML document with a structure like this:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
</override>
</sample>
The characteristic of the source XML is:
Each <assignment> element within the <override> element corresponds to exactly one <variable> element within the <definition> element. This 1:1 relationship is established by the content of their <name> element.
The requirements of the transformation and the target XML are:
Depending on the pattern of the content of the <path> elements, within the <assignment> elements in an <override> element, I like to add a new <assignment> element. Important to note is, that an <assignment> element is the leading information. Therefore always at first, a new <assignment> element with its <path> and <name> content has to be created and in conjunction with that a corresponding new <variable> element with the same <name> content and a specific <value> content has to be created and inserted at last position in the <definition> element. For example, for adding param03, the right result should look like:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>param00_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</definition>
<override>
<assignment>
<name>param00_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<xpath>module01/object01/param03</xpath>
</assignment>
</override>
</sample>
My XSL 2.0 stylesheet for transformation:
For identity transformation, I have choosen to use the fine-grained control identity rule, recommended by [Dimitre Novatchev]. Applying the processing param03 template, I create a new <assignment> element with its specific <path> and <name> content. Within that template, I like to change the node context by using for-each, to the <definition> element and add at last position a new <variable> element with the corresponding <name> content and a specific <value> content. This stylesheet has been tested with Saxon HE 9.5.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- randomid here is just a fake for sake of simplification -->
<xsl:variable name="randomid" select="138368350261919623"/>
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - variable assignment ===============================================
-->
<xsl:template name="variable_assignment">
<xsl:param name="value_node_name"/>
<xsl:param name="value_node_path"/>
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying path: ', $value_node_path)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<xpath>
<xsl:value-of select="$value_node_path"/>
</xpath>
</assignment>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment
/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- setting params -->
<xsl:param name="value_node_name_target">
<xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
</xsl:param>
<xsl:param name="value_node_path_target">
<xsl:value-of select="concat(./path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value_target" select="'1000'"/>
<!-- processing variable assignment -->
<xsl:call-template name="variable_assignment">
<xsl:with-param name="value_node_name" select="$value_node_name_target"/>
<xsl:with-param name="value_node_path" select="$value_node_path_target"/>
</xsl:call-template>
<!-- processing variable definition -->
<xsl:for-each select="/sample/definition/*[position()=last()]">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying value: ', $value_node_value_target)"/>
<xsl:call-template name="identity"/>
<variable>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<value>
<xsl:value-of select="$value_node_value_target"/>
</value>
</variable>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The resulting wrong XML is:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<xpath>module01/object01/param03</xpath>
</assignment>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</override>
</sample>
The problems I got are:
The node context becomes not changed. The new <variable> element becomes added at last position into the <override> element, instead into the <definition> element, as wanted.
Additionally the last <variable> element from <definition> element becomes copied into the <override> element. That is not what I want.
Help needed!
I really would appreciate if somebody could advice me, in which way I would have to adapt my XSLT in order to get rid of the problems and the right behavior as delineated above.
Many thanks.
The XSLT 2.0 proposed by you, adapted by me:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- baserandom here is just a fake for sake of simplification -->
<xsl:param name="baserandom" select="138368350261919623"/>
<!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
<!--xsl:param name="value_node_path"-->
<!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
<!--xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[1]/path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value" select="'1000'"/-->
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - definition ========================================================
-->
<!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
<xsl:template match="definition/*[last()]">
<xsl:param name="value_node_name"/>
<xsl:param name="value_node_value"/>
<xsl:call-template name="identity"/>
<xsl:if test="$value_node_name">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying value: ', $value_node_value)"/>
<variable>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<value>
<xsl:value-of select="$value_node_value"/>
</value>
</variable>
</xsl:if>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- name -->
<xsl:param name="value_node_name">
<xsl:value-of select="concat('param03_ID', '_', $baserandom)"/>
</xsl:param>
<!-- path -->
<xsl:param name="value_node_path">
<xsl:value-of select="concat(./path, '/param03')"/>
</xsl:param>
<!-- value -->
<xsl:param name="value_node_value" select="'1000'"/>
<!-- processing definition -->
<xsl:apply-templates select="/sample/definition/*[last()]">
<xsl:with-param name="value_node_name" select="$value_node_name"/>
<xsl:with-param name="value_node_value" select="$value_node_value"/>
</xsl:apply-templates>
<!-- processing assignment -->
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name)"/>
<xsl:message select="concat('applying path: ', $value_node_path)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name"/>
</name>
<path>
<xsl:value-of select="$value_node_path"/>
</path>
</assignment>
</xsl:template>
</xsl:stylesheet>
The resulting XML (still wrong):
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
</definition>
<override>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>param03_ID_138368350261919623</name>
<path>module01/object01/param03</path>
</assignment>
</override>
</sample>

The issue you are having is that xsl:for-each isn't changing the output context. It's only changing the iteration context.
You are still outputting in the context of assignment (template match) when you're iterating over xsl:for-each select="/sample/definition/*[position()=last()].
You'll need to output the new variable from the context of definition.
I've modified your XSLT to produce what you want. It might not be the final solution, but should get you closer. I added comments (all uppercase) to try to explain what I changed. Please let me know if there are questions.
Modified XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="fo xs fn">
<!--
global declarations ==========================================================
-->
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- randomid here is just a fake for sake of simplification -->
<xsl:param name="randomid" select="138368350261919623"/>
<!--MOVED PARAMS FROM ORIGINAL TEMPLATE HERE SO THEY CAN BE USED BY MULTIPLE TEMPLATES -->
<xsl:param name="value_node_name_target">
<xsl:value-of select="concat('Param03_ID', '_', $randomid)"/>
</xsl:param>
<xsl:param name="value_node_path_target">
<!--I LEFT THE PREDICATE BECAUSE IT APPEARS THAT THERE COULD BE MORE THAN ONE override ELEMENT.-->
<xsl:value-of select="concat(/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[1]/path, '/param03')"/>
</xsl:param>
<xsl:param name="value_node_value_target" select="'1000'"/>
<!--
template - identity ==========================================================
-->
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!--
template - variable assignment ===============================================
-->
<!--MOVED CODE FROM THIS TEMPLATE INTO THE assignment TEMPLATE-->
<!--REPLACES THE xsl:for-each THAT PROCESSES THE VARIABLE DEFINITION-->
<xsl:template match="definition[../override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]]/*[last()]">
<xsl:message select="'processing: variable definition'"/>
<xsl:message select="concat('Here we are: ', .)"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying value: ', $value_node_value_target)"/>
<xsl:call-template name="identity"/>
<variable>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<value>
<xsl:value-of select="$value_node_value_target"/>
</value>
</variable>
</xsl:template>
<!--
template - processing param03 =============================================
-->
<xsl:template match="/sample/override[not(assignment/path[matches(text(), '.*/object01/param03$')])]
/assignment[path[matches(text(), '.*/object01$')]]">
<!-- setting params -->
<!--MOVED TEMPLATE PARAMS TO GLOBAL PARAMS-->
<!-- processing variable assignment -->
<!--REPLACED UNNECESSARY xsl:call-template WITH ACTUAL CODE-->
<xsl:message select="'processing: variable assignment'"/>
<xsl:message select="concat('applying name: ', $value_node_name_target)"/>
<xsl:message select="concat('applying path: ', $value_node_path_target)"/>
<xsl:call-template name="identity"/>
<assignment>
<name>
<xsl:value-of select="$value_node_name_target"/>
</name>
<!--CHANGED FROM xpath TO path (APPEARED TO BE A TYPO)-->
<path>
<xsl:value-of select="$value_node_path_target"/>
</path>
</assignment> <!-- processing variable definition -->
<!--THIS IS NOW DONE BY A SEPARATE MATCHING TEMPLATE-->
</xsl:template>
</xsl:stylesheet>
Output
<sample>
<definition>
<variable>
<name>object01_ID_138368350261919620</name>
<value>NUL</value>
</variable>
<variable>
<name>param01_ID_138368350261919621</name>
<value>10</value>
</variable>
<variable>
<name>param02_ID_138368350261919622</name>
<value>100</value>
</variable>
<variable>
<name>Param03_ID_138368350261919623</name>
<value>1000</value>
</variable>
</definition>
<override>
<assignment>
<name>object01_ID_138368350261919620</name>
<path>module01/object01</path>
</assignment>
<assignment>
<name>param01_ID_138368350261919621</name>
<path>module01/object01/param01</path>
</assignment>
<assignment>
<name>param02_ID_138368350261919622</name>
<path>module01/object01/param02</path>
</assignment>
<assignment>
<name>Param03_ID_138368350261919623</name>
<path>module01/object01/param03</path>
</assignment>
</override>
</sample>

Related

How to improve/refactor this xslt?

Given the following xml :
<tree>
<val>0</val>
<tree>
<val>1</val>
<tree>
<val>3</val>
</tree>
<tree>
<val>4</val>
</tree>
</tree>
<tree>
<val>2</val>
<tree>
<val>5</val>
</tree>
<tree>
<val>6</val>
</tree>
</tree>
</tree>
I need to transform it into this xml:
<root>0
<frst>1
<leaf>3</leaf>
<leaf>4</leaf>
</frst>
<second>2
<leaf>5</leaf>
<leaf>6</leaf>
</second>
</root>
This is my attempt that gives the same result, I recently started learning XSLT i'm not sure what other option I have, can this be improved or done in another way ?
Thank you for your help
This is my attempt :
<?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" encoding="UTF-8"/>
<xsl:template match="/tree">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="val">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="tree">
<xsl:choose>
<!-- IF HAS CHILDREN -->
<xsl:when test="child::tree">
<xsl:if test="(count(preceding-sibling::tree)+1) = 1">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:if>
<xsl:if test="(count(preceding-sibling::tree)+1) = 2">
<second>
<xsl:apply-templates/>
</second>
</xsl:if>
</xsl:when>
<!-- ELSE IS A LEAF -->
<xsl:otherwise>
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
test test
I would think the conditions can be written in match patterns:
<xsl:template match="tree[tree][1]">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:template>
<xsl:template match="tree[tree][2]">
<second>
<xsl:apply-templates/>
</second>
</xsl:template>
<xsl:template match="tree[not(tree)]">
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:template>
although I guess your current code doesn't process the third/fourth/fifth.. tree children so an additional <xsl:template match="tree[tree][position() > 2]"/> might be necessary.

XSLT transform a document with group element

I have a document like this:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<data attribute1="12" attribute2="1" attribute3="1">Director</data>
<data attribute1="12" attribute2="1" attribute3="5">James</data>
<data attribute1="12" attribute2="1" attribute3="8">Male</data>
<data attribute1="12" attribute2="1" attribute3="9">10-Dec-1965</data>
<data attribute1="12" attribute2="2" attribute3="18">James#gmail.com</data>
<data attribute1="12" attribute2="2" attribute3="1">Chief Account</data>
<data attribute1="12" attribute2="2" attribute3="5">Anna</data>
<data attribute1="12" attribute2="2" attribute3="8">Female</data>
<data attribute1="12" attribute2="1" attribute3="9">5-Aug-1980</data>
<data attribute1="12" attribute2="2" attribute3="18">Anna#gmail.com</data>
</document>
I want to transform it to this:
<Person>
<Title>Director</Title>
<FullName>James</FullName>
<Gender>Male</Gender>
<DateOfBirth>10-Dec-1965</DateOfBirth>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
<FullName>Anna</FullName>
<Gender>Female</Gender>
<DateOfBirth>5-Aug-1980</DateOfBirth>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
I am using this xslt:
<xsl:for-each select="document/data[#attribute1=12]">
<Person>
<xsl:choose>
<xsl:when test="boolean(./#attribute3 = '1')">
<Title>
<xsl:value-of select="./."/>
</Title>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '5')">
<FullName>
<xsl:value-of select="./."/>
</FullName>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '8')">
<Gender>
<xsl:value-of select="./."/>
</Gender>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '9')">
<DateOfBirth>
<xsl:value-of select="./."/>
</DateOfBirth>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '18')">
<EmailAddress>
<xsl:value-of select="./."/>
</EmailAddress>
</xsl:when>
</xsl:choose>
</Person>
</xsl:for-each>
The problem is that I get the following output, where the <Person> tag is duplicated.
<Person>
<Title>Director</Title>
</Person>
<Person>
<FullName>James</FullName>
</Person>
<Person>
<Gender>Male</Gender>
</Person>
<Person>
<DateOfBirth>10-Dec-1965</DateOfBirth>
</Person>
<Person>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
</Person>
<Person>
<FullName>Anna</FullName>
</Person>
<Person>
<Gender>Female</Gender>
</Person>
<Person>
<DateOfBirth>5-Aug-1980</DateOfBirth>
</Person>
<Person>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
Could anyone help me solving this problem? Thank you!
It looks like you wish to create a new Person element for each data element with an attribute3 value of 1. Rather than iterate over all data elements as you are currently doing, just select the elements with the relevant attribute
<xsl:apply-templates select="data[#attribute3='1']"/>
Then, you would have a template to output the Person element only for these data elements.
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<!-- Select other elements here -->
</Person>
</xsl:template>
Now, to get the other elements, another way to achieve this is to make use of a key. Effectively, you will group the data elements by the first most preceding data element with an attribute3 value of "1".
<xsl:key name="data"
match="data[#attribute3 != '1']"
use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
Then, to select the other elements, you can use this key, using the unique ID of the current data element as the lookup.
<xsl:apply-templates select="key('data', generate-id())" />
This would only select the data elements that make up that particular person element. You would then have template to match the other data elements for each possible attribute value:
Try this XSLT :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="data" match="data[#attribute3 != '1']" use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
<xsl:template match="document">
<xsl:apply-templates select="data[#attribute3='1']"/>
</xsl:template>
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<xsl:apply-templates select="key('data', generate-id())" />
</Person>
</xsl:template>
<xsl:template match="data[#attribute3='5']">
<FullName><xsl:value-of select="." /></FullName>
</xsl:template>
<xsl:template match="data[#attribute3='8']">
<Gender><xsl:value-of select="." /></Gender>
</xsl:template>
<xsl:template match="data[#attribute3='9']">
<DateOfBirth><xsl:value-of select="." /></DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3='18']">
<EmailAddress><xsl:value-of select="." /></EmailAddress>
</xsl:template>
</xsl:stylesheet>
Here is my suggestion based on the position of elements:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="size" select="5"/>
<xsl:template match="document">
<Persons>
<xsl:apply-templates select="data[position() mod $size = 1]" mode="group"/>
</Persons>
</xsl:template>
<xsl:template match="data" mode="group">
<Person>
<xsl:apply-templates select=". | following-sibling::data[position() < $size]"/>
</Person>
</xsl:template>
<xsl:template match="data[#attribute3 = '1']">
<Title>
<xsl:value-of select="."/>
</Title>
</xsl:template>
<xsl:template match="data[#attribute3 = '5']">
<FullName>
<xsl:value-of select="."/>
</FullName>
</xsl:template>
<xsl:template match="data[#attribute3 = '8']">
<Gender>
<xsl:value-of select="."/>
</Gender>
</xsl:template>
<xsl:template match="data[#attribute3 = '9']">
<DateOfBirth>
<xsl:value-of select="."/>
</DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3 = '18']">
<EmailAddress>
<xsl:value-of select="."/>
</EmailAddress>
</xsl:template>
</xsl:stylesheet>

xslt following group

Old Source XML:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>...
</Employees>
New Source XML:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>
<Person>
<FirstName>Joe</FirstName>
<DetailsArray>
<Details1>
<IsManager>N</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details1>
<Details2>
<IsManager>Y</IsManager>
<IsSuperviser>N</IsSuperviser>
</Details2>
</DetailsArray>
</Person>...
</Employees>
output should be:
<Names>
<Name num='1'>Joe</Name>
<Name num='2'>Joy</Name>
<Name num='3'>Joyce</Name>
....
</Names>
This source XML has some adjustments when compared to previous XML. Here the new condition is "The person may be linked to 2projects or 2tasks", so that i need the output to start from the person with IsManager='Y' even if IsManager is 'y' in Details2 tag of DetailsArray. The output should not have duplications of Names. For suppose if we sort The names will be duplicated..
Thanks for the Previous answers..
EDIT. As lwburk points out, the original solution of this answer just sorts the nodes by IsManager.
Here is a solution that finds the first manager, prints it out, then cycles through the remaining people (cycling back to the beginning, if needed).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Employees">
<xsl:variable name="position" select="count(Person) - count(Person/IsManager[. = 'Y'][1]/../following-sibling::*)" />
<xsl:call-template name="person">
<xsl:with-param name="name" select="Person/IsManager[. = 'Y'][1]/../FirstName" />
<xsl:with-param name="position" select="'1'" />
</xsl:call-template>
<xsl:for-each select="Person[position() > $position]">
<xsl:call-template name="person" />
</xsl:for-each>
<xsl:for-each select="Person[position() < $position]">
<xsl:call-template name="person" />
</xsl:for-each>
</xsl:template>
<xsl:template name="person">
<xsl:param name="name" select="FirstName" />
<xsl:param name="position" select="position() + 1" />
<Name>
<xsl:attribute name="num"><xsl:value-of select="$position" /></xsl:attribute>
<xsl:value-of select="$name" />
</Name>
</xsl:template>
</xsl:stylesheet>
Old answer.
I'm not sure about your question, but I think you want to get all the names starting from the person with IsManager = Y. You can use <xsl:sort> by the IsManager value. Don't forget to specify "descending" in the attribute "order" (otherwise, the person with IsManager = Y will be the last one).
I wrote an example that works with your input data:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Employees">
<xsl:for-each select="Person">
<xsl:sort select="IsManager" order="descending" />
<Name>
<xsl:attribute name="num">
<xsl:value-of select="position()" />
</xsl:attribute>
<xsl:value-of select="FirstName" />
</Name>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This short and simple transformation (no modes, no variables, and only three templates):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<Names>
<xsl:apply-templates select="*/Person[IsManager='Y'][1]"/>
</Names>
</xsl:template>
<xsl:template match="Person[IsManager='Y']">
<xsl:apply-templates select=
"FirstName |../Person[not(generate-id()=generate-id(current()))]
/FirstName
">
<xsl:sort select=
"generate-id(..) = generate-id(/*/*[IsManager = 'Y'][1])"
order="descending"/>
<xsl:sort select=
"boolean(../preceding-sibling::Person[IsManager='Y'])"
order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="FirstName">
<Name num="{position()}"><xsl:value-of select="."/></Name>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML (the same one as provided by #lwburk):
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>
<Person>
<FirstName>Professor X</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Songey</FirstName>
<IsManager>Y</IsManager>
</Person>
</Employees>
produces the wanted, correct result:
<Names>
<Name num="1">Joe</Name>
<Name num="2">Professor X</Name>
<Name num="3">Songey</Name>
<Name num="4">Joy</Name>
<Name num="5">Joyce</Name>
</Names>
Explanation:
This is a typical case of sorting using multiple keys.
The highest priority sorting criteria is whether the Person parent is the first manager.
The second priority sorting criteria is whether the parent Person is following a manager.
We use the fact that when sorting booleans false() comes before true(), therefore we are processing the sorted nodelist in descending order.
It sounds like you're trying to start at the the first manager and then processes all Person elements in order, cycling back around to the beginning to get all elements before the partition element.
The following stylesheet achieves the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="Employees/Person"/>
</xsl:template>
<xsl:template match="Person[IsManager='Y'][1]">
<Name num="1">
<xsl:apply-templates select="FirstName"/>
</Name>
<!-- partition -->
<xsl:apply-templates select="following-sibling::Person" mode="after"/>
<xsl:apply-templates select="../Person" mode="before">
<xsl:with-param name="pos" select="last() - position() + 1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Person" mode="after">
<Name num="{position() + 1}">
<xsl:apply-templates select="FirstName"/>
</Name>
</xsl:template>
<xsl:template match="Person[not(IsManager='Y') and
not(preceding-sibling::Person[IsManager='Y'])]" mode="before">
<xsl:param name="pos" select="0"/>
<Name num="{position() + $pos}">
<xsl:apply-templates select="FirstName"/>
</Name>
</xsl:template>
<xsl:template match="Person"/>
<xsl:template match="Person" mode="before"/>
</xsl:stylesheet>
Note: 1) This solution requires there be at least one manager present in the source; 2) This might not be a very efficient solution because it requires multiple passes and uses preceding-sibling to test group membership (for elements before the partition element).
Example input:
<Employees>
<Person>
<FirstName>Joy</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joyce</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Joe</FirstName>
<IsManager>Y</IsManager>
</Person>
<Person>
<FirstName>Professor X</FirstName>
<IsManager>N</IsManager>
</Person>
<Person>
<FirstName>Songey</FirstName>
<IsManager>Y</IsManager>
</Person>
</Employees>
Output:
<Name num="1">Joe</Name>
<Name num="2">Professor X</Name>
<Name num="3">Songey</Name>
<Name num="4">Joy</Name>
<Name num="5">Joyce</Name>

XSL conditional formatting

I am using xsl to transform xml to kml format. I would like to add conditional logic to the xsl to switch the styleUrl based on part of an attribute value. The attribute name is FROM_SYSTEM_ID. The format of the attribute value is "A-123-CAM-1" where "CAM" is part of the string to determine which style definition to use (in this case CAM stands for Camera, CAB stands for Cabinet, etc).
How can I parse this attribute to perform the needed style definition switch?
Following is my xsl template:
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>#msn_open-diamond00</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
Following is a sample of the XML:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
<!-- additional rows here -->
</Line>
You can extract the CAM or CAB portion of the FROM_SYSTEM_ID attribute using a combination of substring-after and substring-before:
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
Putting this together with your stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
</Line>
Produces the following result:
<Folder>
<name>Lines</name>
<Placemark>
<name>A-123-CAB-1</name>
<description>A-123-CAM-3</description>
<styleUrl>CAB</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.107221652500,42.624948852000,0
-83.107353167000,42.624940325900,0
</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>A-123-CAM-1</name>
<description>A-123-HH-16</description>
<styleUrl>CAM</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.151500129600,42.641662528600,0
-83.151552587600,42.641709802200,0
</coordinates>
</LineString>
</Placemark>
</Folder>

Counting distinct items in XSLT

I have XML like this:
<assessment>
<variables>
<variable>
<attributes>
<variable_name value="FRED"/>
</attributes>
</variable>
</variables>
<variables>
<variable>
<attributes>
<variable_name value="MORTIMER"/>
</attributes>
</variable>
</variables>
<variables>
<variable>
<attributes>
<variable_name value="FRED"/>
</attributes>
</variable>
</variables>
</assessment>
I know that with this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html"/>
<xsl:template match="assessment">
<xsl:for-each select=".//variables/variable/attributes/variable_name">
<xsl:value-of select="#value"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I can output the following:
FRED
MORTIMER
FRED
But what I really want to output is this:
FRED: 2
MORTIMER: 1
That is, I want to list the distinct elements and how many times each occurs. Note that I want the elements to appear in the order of their first appearance (which might rule out some solutions that use sorting).
How do I do this?
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="variable_name/#value"
use="."/>
<xsl:template match="/">
<xsl:for-each select="
/*/*/variable/attributes/variable_name/#value
[generate-id()
=
generate-id(key('kValueByVal', .)[1])
]
">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document, produces the wanted, correct result:
FRED 2
MORTIMER 1