I am looking to export from Filemaker using column names (instead of positions). Currently I export the following XSL stylesheet that exports by position with:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fm="http://www.filemaker.com/fmpxmlresult" exclude-result-prefixes="fm" >
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[01]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[02]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
</xsl:template>
</xsl:stylesheet>
Any ideas? Thanks.
If you just want to make the code more readable, then I'd suggest something simple, like:
<!-- expected columns -->
<xsl:variable name="NAME" value="1" />
<xsl:variable name="LOCATION" value="2" />
<!-- ... -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[$NAME]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[$LOCATION]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
BTW, with <xsl:value-of /> you can omit the fm:DATA, i.e. use:
<xsl:value-of select="fm:COL[$LOCATION] />
It will return the same result.
If you need something more sophisticated, please explain.
Update:
To refer to columns by column names is harder, but possible with something like that:
<!-- Define a key to get a field and all fields that precede it by the field name -->
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD" use="#NAME" />
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD"
use="following-sibling::fm:FIELD/#NAME" />
<!-- Then *count* them it in the code like that -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[count(key('N', 'name'))]" />
</name>
<location>
<xsl:value-of select="fm:COL[count(key('N', 'location'))]" />
</location>
</person>
</xsl:for-each>
</people>
Not utterly elegant, but works.
Related
I am a new to XSLT and I need to insert an xslt script into a third party software, which uses XSLT 1.0 to transform xml document.
My task is to take document A.xml and insert each element from document B.xml, but only if the text in A is not yet existing. The output should be generated as document C.xml.
Example A.xml:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Description/>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Example B.xml:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Citation>
<Title>Template Title</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<MetadataDate>20160131</MetadataDate>
</Table>
</metadata>
The expected output of C.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Three things are important:
B can contain elements, which are not present in A but must be copied to C (eg metadata/Table/Citation/Abstract)
Elements of A with text must not be overwritten in C with text from B. (eg metadata/Table/Citation/Title). Then again empty elements in A must be filled with text from B (eg metadata/Table/Citation/Description)
The xml is just a sample, there are more than hundred different tags in my real xml files, these are just samples depicting my problem. So any solution to my problem has to be applicable on more tags than the ones living in my sample xml.
I do not need a running solution, any hints how to solve this for a XSLT beginner would be nice.
The problem here is how to identify corresponding elements in both files. I assume that each element appears at most once, so we can identify corresponding elements simply by their element name.
My solution follows the idea of John Bollinger:
Create a template.xml file containing all possible elements in the desired output structure.
The XSL script runs through all elements of the template.
It lookups the values of this element in files A and B.
If value A is not empty, take it, else take the value from B.
This is the template I used:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name/>
<Location/>
<Citation>
<Title />
<Abstract />
<Description/>
</Citation>
<metadataDate/>
</Table>
</metadata>
The following XSL has the above template as input:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<!-- Parameters -->
<xsl:param name="aFile" select="'a.xml'" />
<xsl:param name="bFile" select="'b.xml'" />
<!-- Variables -->
<xsl:variable name="aDoc" select="document( $aFile, . )"/>
<xsl:variable name="bDoc" select="document( $bFile, . )"/>
<!-- Locate elements by name in both files -->
<xsl:key name="elementsByName" match="*" use="name()" />
<!-- Root-Template -->
<xsl:template match="/">
<xsl:comment>
<xsl:value-of select="concat( 'Merge of ', $aFile, ' with ', $bFile )" />
</xsl:comment>
<xsl:apply-templates />
</xsl:template>
<!-- Merge all elements -->
<xsl:template match="*">
<xsl:variable name="elemName" select="name()" />
<xsl:variable name="aValue" select="$aDoc/key('elementsByName', $elemName)/text()" />
<xsl:variable name="bValue" select="$bDoc/key('elementsByName', $elemName)/text()" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$aValue != ''">
<xsl:value-of select="$aValue" />
</xsl:when>
<xsl:when test="$bValue != ''">
<xsl:value-of select="$bValue" />
</xsl:when>
</xsl:choose>
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The result of the transformation is:
<?xml version="1.0" encoding="UTF-8"?>
<!--Merge of a.xml with b.xml-->
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
The only "trick" in this code is the use of xsl:key and the corresponding key() function. This allows us to find the corresponding elements (same name) in both files A and B.
This script copies all elements from the template to the output file C.
To change this behaviour, simple move the xsl:copy instructions inside of the xsl:when.
I try to give an improved answer separately to avoid confusion with the helpful comments there.
In addition to the "generic" approach there (match element names), we will now have specific templates as well.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<!-- Parameters -->
<xsl:param name="aFile" select="'a.xml'" />
<xsl:param name="bFile" select="'b.xml'" />
<!-- Variables -->
<xsl:variable name="aDoc" select="document( $aFile, . )"/>
<xsl:variable name="bDoc" select="document( $bFile, . )"/>
<!-- Locate elements by name in both files -->
<xsl:key name="elementsByName" match="*" use="name()" />
<!-- Root-Template -->
<xsl:template match="/">
<xsl:comment>
<xsl:value-of select="concat( 'Merge of ', $aFile, ' with ', $bFile )" />
</xsl:comment>
<xsl:apply-templates />
</xsl:template>
<!-- Merge specific elements -->
<xsl:template match="metadata/Table/Description">
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/metadata/Table/Description/text()" />
<xsl:with-param name="bValue" select="$bDoc/metadata/Table/Description/text()" />
</xsl:call-template>
</xsl:template>
<xsl:template match="metadata/Table/Citation/Description">
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/metadata/Table/Citation/Description/text()" />
<xsl:with-param name="bValue" select="$bDoc/metadata/Table/Citation/Description/text()" />
</xsl:call-template>
</xsl:template>
<!-- Merge unique elements -->
<xsl:template match="*" priority="-10">
<xsl:variable name="elemName" select="name()" />
<xsl:call-template name="mergeElement">
<xsl:with-param name="aValue" select="$aDoc/key('elementsByName', $elemName)/text()" />
<xsl:with-param name="bValue" select="$bDoc/key('elementsByName', $elemName)/text()" />
</xsl:call-template>
</xsl:template>
<!-- Use A or B -->
<xsl:template name="mergeElement">
<xsl:param name="aValue" />
<xsl:param name="bValue" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$aValue != ''">
<xsl:value-of select="$aValue" />
</xsl:when>
<xsl:when test="$bValue != ''">
<xsl:value-of select="$bValue" />
</xsl:when>
</xsl:choose>
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For the test, I changed the template and A and B and added another Description element directly inside of Table:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name />
<Description/> <!-- NEW: not unique element name -->
<Location/>
<Citation>
<Title />
<Abstract />
<Description/>
</Citation>
<metadataDate/>
</Table>
</metadata>
File A:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Location>oracle:TNS_1</Location>
<Description>Table description A</Description> <!-- NEW -->
<Citation>
<Title>Title 1</Title>
<Description/>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
File B:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<Table>
<Description>Table description B</Description> <!-- NEW -->
<Citation>
<Title>Template Title</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<MetadataDate>20160131</MetadataDate>
</Table>
</metadata>
Generated file C:
<?xml version="1.0" encoding="UTF-8"?>
<!--Merge of a.xml with b.xml-->
<metadata>
<Table>
<Name>SCHAME.table_name</Name>
<Description>Table description A</Description> <!-- NEW -->
<Location>oracle:TNS_1</Location>
<Citation>
<Title>Title 1</Title>
<Abstract>Template Abstract</Abstract>
<Description>Template Description</Description>
</Citation>
<metadataDate>20170418</metadataDate>
</Table>
</metadata>
Obviosly, this is not a generic solution.
But if the number of non-unique elements is low compared to the total number, you will benefit from the generical template. Only the non-unique elements must be "styled" individually.
I want to transfer a non-xml text file delimited by '|' characters into an xml using Datapower.
Following is file (sample1)
10|20003|24/23/25|23890
Now i have to break this into the following XML
<ResponseType>
<ResCode>10</ResCode>
<Id>20003</Id>
<SoftCode>24/23/25</SoftCode>
<StatusCode>23890</StatusCode>
</ResponseType>
What I did was following--
1>Create a Transform action in the service that will be receiving non-XML requests.
2>Select "Use XSLT specified in this action on a non-XML message" to specify that this is a Binary Transform.
3>Upload the following stylesheet as the Processing Control File.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
version="1.0">
<dp:input-mapping href="sample1.ffd" type="ffd"/>
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:copy-of select="ResponseType"/>
<xsl:call-template name="str:tokenize">
<xsl:with-param name="string" select="string" />
</xsl:call-template>
</xsl:template>
<xsl:template name="str:tokenize">
<xsl:with-param name="string" select="">
str:tokenize('string', '|')
</xsl:with param>
</xsl:template>
</xsl:stylesheet>
and here is my sample1.ffd(which I have uploaded in my local:// directory in Datapower
<File name="ResponseType">
<!-- capture all data into this tag -->
<Field name="ResCode/Id/SoftCode/StatusCode" />
</File>
But I am not getting desired output , I think my xslt is quite wrong
What can I do do to get desired output?
In DataPower using FFD the following should work:
1) Add the FFD file (below one of my old education samples):
<File name="CSVFILE">
<Group name="CSVLine" minOccurs="0" maxOccurs="unbounded" delim="\n">
<Field name="id"/>
<Field name="fname" delim=","/>
<Field name="lname" delim=","/>
<Field name="title" delim=","/>
<Field name="dept" delim=","/>
<Field name="org"/>
</Group>
</File>
2) Add the XSLT (this one simply copies the FFD transformed XML to output):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
version="1.0">
<dp:input-mapping href="CSVFILE.FFD" type="ffd"/>
<!-- This stylesheet copies the input to the output -->
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
3) Send a message:
1,Anders,Wasen,B2B Architect,DataPower Dev,Enfo Zystems
2,Jean-Luc,Piccard,Captain,USS Enterprise,Star Fleet
4) This will result in the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<CSVFILE>
<CSVLine>
<id>1</id>
<fname>Anders</fname>
<lname>Wasen</lname>
<title>B2B Architect</title>
<dept>DataPower Dev,Enfo Zystems</dept>
<org/>
</CSVLine>
<CSVLine>
<id>2</id>
<fname>Jean-Luc</fname>
<lname>Piccard</lname>
<title>Captain</title>
<dept>USS Enterprise,Star Fleet</dept>
<org/>
</CSVLine>
</CSVFILE>
Make sure that you change the XSLT Transform action into "Transform Binary" and set Request Type to "non-xml", else it will not work!
Hope this will help you! :)
I'm not sure how IBM Datapower might solve this problem, but for XSLT, you would at least wrap your input in a XML element:
<Whatever>
10|20003|24/23/25|23890
</Whatever>
And then you could go on with a transformation like follows. The hard part is splitting your text input. In XSLT 1.0, there is no function available for that, so you need a recursive template.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxml="urn:schemas-microsoft-com:xslt" version="1.0" exclude-result-prefixes="msxml">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<xsl:variable name="tokenized">
<items>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="//text()" />
</xsl:call-template>
</items>
</xsl:variable>
<ResponseType>
<ResCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[1]/text()" />
</ResCode>
<Id>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[2]/text()" />
</Id>
<SoftCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[3]/text()" />
</SoftCode>
<StatusCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[4]/text()" />
</StatusCode>
</ResponseType>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="string" />
<xsl:variable name="item" select="normalize-space( substring-before( concat( $string, '|'), '|'))" />
<xsl:if test="$item">
<item>
<xsl:value-of select="$item" />
</item>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="substring-after($string,'|')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I am new to xslt programming and xlm. I have created the code below this works fine, except that instead variable names for each column, it just shows "colno" How do I get the column names into the output?
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
exclude-result-prefixes="fmp"
>
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="kMetaData" select="fmp:METADATA/fmp:FIELD"/>
<xsl:variable name="colno"
select="count($kMetaData[following-sibling::fmp:FIELD/#NAME]) + 1" />
<xsl:template match="/fmp:FMPXMLRESULT">
<PERSON>
<xsl:apply-templates select="fmp:RESULTSET/fmp:ROW" />
</PERSON>
</xsl:template>
<xsl:template match="fmp:ROW">
<ELEMENTS>
<xsl:apply-templates select="fmp:COL" />
</ELEMENTS>
</xsl:template>
<xsl:template match="fmp:COL">
<xsl:element name="colno">
<xsl:value-of select="fmp:DATA" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is hard to make some suggestion without input xml. But at first sight this <xsl:element name="colno"> says "output an element <colno>". I think you should use something like <xsl:element name="{xpath/to/columnName}">
edit:
According to your input xml your template for "COL" element should look like
<xsl:template match="COL">
<xsl:variable name="colPosition" select="position()" />
<!-- Prevent spaces in NAME attribute of FIELD element -->
<xsl:variable name="colName" select="translate($kMetaData[$colPosition]/#NAME, ' ', '_')" />
<xsl:element name="{$colName}">
<xsl:value-of select="DATA"/>
</xsl:element>
</xsl:template>
Then the output looks like
<?xml version="1.0" encoding="utf-8"?>
<PERSON>
<ELEMENTS>
<FIRSTNAME>Richard</FIRSTNAME>
<LASTNAME>Katz</LASTNAME>
<MIDDLENAME>David</MIDDLENAME>
<REQUESTDT>1/1/2001</REQUESTDT>
<salutation>Mr</salutation>
<Bargaining_Unit>CSEA (02,03,04)</Bargaining_Unit>
<Field_134>b</Field_134>
</ELEMENTS>
</PERSON>
I need a little XSLT help. Couldn't figure out why the actual output is different from my expected output. Any help much appreciated!
XML
<?xml version="1.0"?>
<a>
<b c="d"/>
<b c="d"/>
<b c="d"/>
</a>
XSL
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="foo">
<xsl:param name="content"></xsl:param>
<xsl:value-of select="$content"></xsl:value-of>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="foo">
<xsl:with-param name="content">
<xsl:for-each select="a/b">
<e>
<xsl:value-of select="#c" />
</e>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
Actual Output
<?xml version="1.0"?>
ddd
Desired Output
<?xml version="1.0"?>
<e>d</e>
<e>d</e>
<e>d</e>
Note: Calling the template is mandatory. In my situation the template does more with extension functions.
Contrary to what ABach says, your xsl:param is fine. The only thing you need to change is your xsl:value-of. It should be a xsl:copy-of:
<xsl:template name="foo">
<xsl:param name="content"/>
<xsl:copy-of select="$content"/>
</xsl:template>
You're very close; you've just mixed up relative positioning and correct parameter usage within templates. Here's a slightly revised answer.
When this XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template name="foo">
<xsl:param name="pContent" />
<xsl:for-each select="$pContent">
<e>
<xsl:value-of select="#c" />
</e>
</xsl:for-each>
</xsl:template>
<xsl:template match="/*">
<xsl:call-template name="foo">
<xsl:with-param name="pContent" select="*" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<?xml version="1.0"?>
<a>
<b c="d" />
<b c="d" />
<b c="d" />
</a>
...the desired result is produced:
<?xml version="1.0"?>
<e>d</e>
<e>d</e>
<e>d</e>
In particular, notice the correct usage of <xsl:param> to include nodes based on their relative position. In your case, you are telling the XSLT parser to output the text values of the parameter that you're passing, rather than altering the nodes' contents in the way you want.
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>