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>
Related
When I'm in : <xsl:template match="listOfPerson/person">
for person of id "A", is it possible to retrieve his information that is stored in another element here it's inside the element data
xml :
<root>
<data>
<person id="A">
<name> Anna </name>
<age> 1 </age>
</person>
<person id="B">
<name> Banana </name>
<age> 1 </age>
</person>
</data>
<listOfPerson>
<person>
<id>A</id>
</person>
<person>
<id>B</id>
</person>
</listOfPerson>
</root>
my current xsl :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" />
<xsl:template match="root">
<xsl:apply-templates select="listOfPerson/person"/>
</xsl:template>
<xsl:template match="listOfPerson/person">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
current output :
A
B
desired output :
Anna 1
Banana 1
XSLT has a built-in key mechanism for resolving cross-references. Consider the following example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="person" match="data/person" use="#id" />
<xsl:template match="/root">
<xsl:for-each select="listOfPerson/person">
<xsl:variable name="data" select="key('person', id)" />
<xsl:value-of select="$data/name" />
<xsl:text> </xsl:text>
<xsl:value-of select="$data/age" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
Anna 1
Banana 1
I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is a variable that returns 12, which is what I expect:
<xsl:variable name="MM">
<xsl:value-of select="../BIRTH_MONTH"/>
</xsl:variable>
I want to base the select clause on a parameter. I figure something like this:
<!-- $which_date has the value "BIRTH" -->
<xsl:variable name="MM">
<xsl:value-of select="concat('../', $which_date, '_MONTH')"/>
</xsl:variable>
The above returns a value of ../BIRTH_MONTH.
I thought the problem might be with concat(), but below is a variant that also returns the un-evaluated result of ../BIRTH_MONTH:
<!-- $which_date has the value "../BIRTH_MONTH" -->
<xsl:variable name="MM">
<xsl:value-of select="$which_date"/>
</xsl:variable>
(Insert history here of dozens of attempts based on tweaks with quotation marks, braces, etc...)
How can I use $which_date in an expression that can be evaluated?
<xsl:variable name="MM">
<xsl:value-of select="concat('../', $which_date, '_MONTH')"/>
</xsl:variable>
The above returns a value of ../BIRTH_MONTH.
You want:
<xsl:variable name="MM" select="../*[name()=concat($which_date, '_MONTH')]"/>
Here is a complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="which_date" select="'BIRTH'"/>
<xsl:template match="x">
<xsl:variable name="MM" select=
"../*[name()=concat($which_date, '_MONTH')]"/>
<xsl:value-of select="$MM"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document (none was provided with the question):
<t>
<x>1</x>
<BIRTH_MONTH>12</BIRTH_MONTH>
</t>
the wanted, correct result is produced:
12
Update: Based on your other similar question, we see that you want a parameterized solution.
Here is one possible parameterized solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pdateComponents" select="'|BIRTH_MONTH|BIRTH_DAY|BIRTH_YEAR|'"/>
<xsl:param name="poutputDateName" select="'BIRTH_DATE'"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*[*[contains($pdateComponents, concat('|',name(),'|'))]]" mode="dateHolder"/>
</xsl:template>
<xsl:template match="*" mode="dateHolder">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:element name="{$poutputDateName}">
<xsl:apply-templates select=
"*[contains($pdateComponents, concat('|',name(),'|'))]" mode="date">
<xsl:sort select="substring-before($pdateComponents, concat('|',name(),'|'))"/>
</xsl:apply-templates>
</xsl:element>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="date">
<xsl:value-of select=
"concat(substring('/', 1 + (position() = 1)), normalize-space())"/>
</xsl:template>
</xsl:stylesheet>
When this XSLT 1.0 transformation is applied on the following XML document:
<RECORDS>
<PERSON name="A">
<BIRTH_YEAR> 1943 </BIRTH_YEAR>
<BIRTH_MONTH> 04 </BIRTH_MONTH>
<BIRTH_DAY> 01 </BIRTH_DAY>
</PERSON>
<PERSON name="B">
<BIRTH_YEAR> 1957 </BIRTH_YEAR>
<BIRTH_MONTH> 08 </BIRTH_MONTH>
<BIRTH_DAY> 29 </BIRTH_DAY>
</PERSON>
<PERSON name="C">
<BIRTH_YEAR> 1802 </BIRTH_YEAR>
<BIRTH_MONTH> 12 </BIRTH_MONTH>
<BIRTH_DAY> 14 </BIRTH_DAY>
</PERSON>
<PERSON name="D">
<BIRTH_YEAR> 2015 </BIRTH_YEAR>
<BIRTH_MONTH> 04 </BIRTH_MONTH>
<BIRTH_DAY> 30 </BIRTH_DAY>
</PERSON>
</RECORDS>
the result is:
<PERSON name="A">
<BIRTH_DATE>04/01/1943</BIRTH_DATE>
</PERSON>
<PERSON name="B">
<BIRTH_DATE>08/29/1957</BIRTH_DATE>
</PERSON>
<PERSON name="C">
<BIRTH_DATE>12/14/1802</BIRTH_DATE>
</PERSON>
<PERSON name="D">
<BIRTH_DATE>04/30/2015</BIRTH_DATE>
</PERSON>
Do note:
The names of the date components are provided in a global parameter to the transformation -- they are not statically known.
The name of the output element to contain the date is also provided as the value of another global parameter to the transformation.
Even the order of the date components, using which to construct the date, is provided in the first component!
Thus, the above supplied parameter results in American dates output.
But if we provide this parameter:
<xsl:param name="pdateComponents" select="'|BIRTH_DAY|BIRTH_MONTH|BIRTH_YEAR|'"/>
then the result of the transformation contains dates in European format:
<PERSON name="A">
<BIRTH_DATE>01/04/1943</BIRTH_DATE>
</PERSON>
<PERSON name="B">
<BIRTH_DATE>29/08/1957</BIRTH_DATE>
</PERSON>
<PERSON name="C">
<BIRTH_DATE>14/12/1802</BIRTH_DATE>
</PERSON>
<PERSON name="D">
<BIRTH_DATE>30/04/2015</BIRTH_DATE>
</PERSON>
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>
Let's assume, you have the xml below. The goal is to group by FirstName and export the Person into different xml files. Each output xml files should only contain up to X different FirstName.
Below is an example of the desired transformation with X = 3
XML input:
<People>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Person>
<Person>
<FirstName>Jack</FirstName>
<LastName>White</LastName>
</Person>
<Person>
<FirstName>Mark</FirstName>
<LastName>Wall</LastName>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Cyrus</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Megan</FirstName>
<LastName>Boing</LastName>
</Person>
</People>
XML output 1 with 3 different FirstName
<People>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Jack</FirstName>
<LastName>White</LastName>
</Person>
<Person>
<FirstName>Mark</FirstName>
<LastName>Wall</LastName>
</Person>
</People>
XML output 2 with the 2 remaining FirstName
<People>
<Person>
<FirstName>Cyrus</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Megan</FirstName>
<LastName>Boing</LastName>
</Person>
</People>
It seems to me that the muenchian grouping can be used along with the to produce multiple output files. However, the core question is where we can set a threshold in number of person before exporting to a new file?
Here is an example of doing it in two steps with XSLT 2.0:
<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:param name="n" as="xs:integer" select="3"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="People">
<xsl:variable name="groups" as="element(group)*">
<xsl:for-each-group select="Person" group-by="FirstName">
<group>
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each-group select="$groups" group-by="(position() - 1) idiv $n">
<xsl:result-document href="group{position()}.xml">
<People>
<xsl:copy-of select="current-group()"/>
</People>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I might try to convert to XSLT 1.0 and EXSLT later.
[edit]
Here is an attempt to translate into XSLT 1.0 and EXSLT:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:param name="n" select="3"/>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="person-by-firstname"
match="Person"
use="FirstName"/>
<xsl:template match="People">
<xsl:variable name="groups">
<xsl:for-each select="Person[generate-id() = generate-id(key('person-by-firstname', FirstName)[1])]">
<group>
<xsl:copy-of select="key('person-by-firstname', FirstName)"/>
</group>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="exsl:node-set($groups)/group[(position() - 1) mod $n = 0]">
<exsl:document href="groupTest{position()}.xml">
<People>
<xsl:copy-of select="Person | following-sibling::group[position() < $n]/Person"/>
</People>
</exsl:document>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>