XSLT - Recursive grouping / count based on attribute values - xslt

I'm struggling with a simple recursive count problem based on distinct values. From a list such as:
<List>
<Place continent="Asia" country="India" region="UP">Lucknow</Place>
<Place continent="Europe" country="France" region="Provence">Marseille</Place>
<Place continent="Europe" country="Greece" region="Arcadia">Dimitsana</Place>
<Place continent="Europe" country="Italy" region="Sicily">Sicily</Place>
<Place continent="Asia" country="India" region="Maharastra">Pune</Place>
<Place continent="Asia" country="China">China</Place>
<Place continent="Europe" country="France" region="Provence">Marseille</Place>
<Place continent="America" country="Brasil" region="Pará">Belém</Place>
</List>
I'd like to retrieve an output based on distinct values for the continent, country and region attributes (in some cases optional), that would count recursively each token, as follows:
<Places>
<continent name="America" count="1">
<country name="Brasil" count="1">
<region name="Pará" count="1">
<city name="Belém" count="1"/>
</region>
</country>
</continent>
<continent name="Asia" count="3">
<country name="India" count="2">
<region name="Maharastra" count="1">
<city name="Pune" count="1"/>
</region>
<region name="UP" count="1">
<city name="Lucknow" count="1"/>
</region>
</country>
<country name="China" count="1"/>
</continent>
<continent name="Europe" count="4">
<country name="France" count="2">
<region name="Provence" count="2">
<city name="Marseille" count="2"/>
</region>
</country>
<country name="Greece" count="1">
<region name="Arcadia" count="1">
<city name="Dimitsana" count="1"/>
</region>
</country>
<country name="Italy" count="1">
<region name="Sicily" count="1"/>
</country>
</continent>
I can manage to retrieve each set of distinct values (continents, countries, regions) independently, with, e.g.:
<xsl:template match="List">
<Places>
<xsl:for-each-group select="Place" group-by="#continent">
<continent>
<xsl:attribute name="name">
<xsl:value-of select="#continent"/>
</xsl:attribute>
<xsl:attribute name="count">
<xsl:value-of select="count(current-group())"/>
</xsl:attribute>
</continent>
</xsl:for-each-group>
</Places>
— but, although I suppose I'm missing something very basic, I can't manage to get the nested grouping and count for countries inside continents, etc. Many thanks in advance.

Related

Uncaught TypeError: Cannot read properties of null (reading 'hashCode') [SaxonJS]

I'm getting this error in Chrome console:
Seems to be a problem with XSLT or SEF as other SEF works.
I complie SEF with the xslt3 tool on Node.js version v14.17.3.
I can run the XSLT with Saxon 9.8.12-EE in oXygen with no problem.
Maybe the problem with maps, xsl:include or xsl:import?
Here is the XSLT code:
<stylesheet exclude-result-prefixes="xs xd dme functx dita mei map array" extension-element-prefixes="ixsl" version="3.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:array="http://www.w3.org/2005/xpath-functions/array" xmlns:dita="http://dita-ot.sourceforge.net" xmlns:dme="http://www.mozarteum.at/ns/dme" xmlns:functx="http://www.functx.com" xmlns:ixsl="http://saxonica.com/ns/interactiveXSLT" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:mei="http://www.music-encoding.org/ns/mei" xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.music-encoding.org/ns/mei">
<xi:include href="docs.xsl" xpointer="element(/1/1)"/>
<import href="changeLog.xsl"/>
<!--This parameter is a needed for XSpec. Cf. https://github.com/xspec/xspec/wiki/Global-Context-Item-->
<param name="global-context-item" select="."/>
<param name="source"/>
<include href="../modules/identity-transform.xsl"/>
<xd:doc>
<xd:desc>
<xd:p>Replace current scoreDef with the alternative. Note that this should be defined in the following-sibling <choice>.</xd:p>
</xd:desc>
</xd:doc>
<template match="scoreDef[not(#corresp = concat('#', $source))]">
<copy-of select="following-sibling::choice[#type = 'scoring']/*[#corresp = concat('#', $source)]/scoreDef"/>
</template>
<xd:doc>
<xd:desc/>
</xd:doc>
<template match="choice[#type = 'scoring']"/>
<xd:doc>
<xd:desc>
<xd:p>Map staffDef#n of the new scoreDef and #dme.parts.</xd:p>
</xd:desc>
</xd:doc>
<variable as="map(xs:string, item())*" name="map_staves_order">
<for-each select="$global-context-item//choice[#type = 'scoring']/*[#corresp = concat('#', $source)]/scoreDef">
<map>
<for-each select="descendant::staffDef">
<map-entry key="string(#n)" select="#dme.parts"/>
</for-each>
</map>
</for-each>
</variable>
<xd:doc>
<xd:desc>
<xd:p>Get the maximum number of staves.</xd:p>
</xd:desc>
</xd:doc>
<variable as="xs:integer" name="count_staves" select="
max(for $a in map:keys($map_staves_order)
return
if (matches($a, '\d')) then
xs:integer($a)
else
())"/>
<xd:doc>
<xd:desc>
<xd:p>Key: old #n.</xd:p>
<xd:p>Value: new #n</xd:p>
</xd:desc>
</xd:doc>
<variable as="map(xs:string, xs:string)*" name="map_new_old_staves">
<map>
<for-each select="map:keys($map_staves_order)">
<variable name="current_key" select="."/>
<map-entry key="$global-context-item//measure[#n = 1]/staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>
</for-each>
</map>
</variable>
<xd:doc>
<xd:desc>
<xd:p>Copies elements before first staff without changes, e.g. <tempo></xd:p>
<xd:p>Copies staves accordingly to the new score order. Changes the #n-attribute accordingly</xd:p>
<xd:p>Copies ControlEvents and updates the #staff attribute if neccessary.</xd:p>
</xd:desc>
</xd:doc>
<template match="measure">
<copy>
<apply-templates select="#*"/>
<apply-templates select="child::staff[1]/preceding-sibling::*"/>
<variable as="element()" name="currentMeasure" select="."/>
<for-each select="1 to $count_staves">
<variable as="xs:string" name="currentItem" select="string()"/>
<for-each select="$currentMeasure/child::staff[map:get($map_staves_order, $currentItem) = #dme.parts]">
<copy>
<attribute name="n" select="$currentItem"/>
<apply-templates select="(#* except #n) | node()"/>
</copy>
</for-each>
</for-each>
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="child::staff[last()]/following-sibling::*"/>
</call-template>
</copy>
</template>
<xd:doc>
<xd:desc>
<xd:p>Receives one or more elements (controlEvents) and updates their #staff according to the $map_new_old_staves variable.</xd:p>
<xd:p>Recursive template</xd:p>
</xd:desc>
<xd:param name="elements"/>
</xd:doc>
<template name="update_staff_controlEvents">
<param name="elements"/>
<for-each select="$elements">
<choose>
<when test="#staff">
<copy>
<attribute name="staff" select="map:get($map_new_old_staves, #staff)"/>
<apply-templates select="#* except #staff"/>
<if test="node()">
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="node()"/>
</call-template>
</if>
</copy>
</when>
<otherwise>
<copy>
<apply-templates select="#*"/>
<if test="node()">
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="node()"/>
</call-template>
</if>
</copy>
</otherwise>
</choose>
</for-each>
</template>
</stylesheet>
JS which I run on Console:
var options = {
stylesheetLocation: "./assets/xsl/origScoring.sef.json",
sourceText: xmlString,
stylesheetParams: {"source": "source_A"},
destination: "document"
}
var result = SaxonJS.transform(options);
result.principalResult
Owing to the Saxon developers I could easily solve my issue by adding namespace prefixes in the XPath expressions when creating maps. Originally, I used xpath-default-namespace. Fo instance, here on the elements measure and staff should be:
<map-entry key="$global-context-item//mei:measure[#n = 1]/mei:staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>
instead of
<map-entry key="$global-context-item//measure[#n = 1]/staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>

xsl get node that not compare in key

In this example i have this xml
<CityStates>
<States>
<State Abbr="AL">Alabama</State>
<State Abbr="AK">Alaska</State>
<State Abbr="AZ">Arizona</State>
<State Abbr="AR">Arkansas</State>
</States>
<Cities>
<City State="NY" >New York</City>
<City State="CA" >Los Angeles</City>
<City State="AZ" >Chicago</City>
<City State="AR" >Houston</City>
<City State="AR" >Philadelphia</City>
</Cities>
</CityStates>
I would like to view only the nodes that do not have the State with the Key()
xsl is this:
<xsl:key name="keyState" match="State" use="#Abbr"/>
<xsl:template match="/">
<xsl:for-each select="//City">
<xsl:value-of select="City"/>
</xsl:for-each>
</xsl:template>
Use a predicate City[not(key('keyState', #State))] i.e.
<xsl:for-each select="//City[not(key('keyState', #State))]">
<xsl:value-of select="."/>
</xsl:for-each>
Try something like:
<xsl:template match="/CityStates">
<xsl:for-each select="Cities/City[not(key('keyState', #State))]" >
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
You haven't posted the expected result. You will probably want to add some kind of delimiter after the xsl:value-of instruction.

XSLT Generating incremental values

I have the following xml:
<Details>
<Head>
<pageid>123</pageid> <!-- Needs to be sequential starting with 0000000001 -->
</Head>
<Start>
<pageid>124</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>12</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>45</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>459</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>65</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>78</pageid>
</END>
</Details>
I want to the value pageid to be incremental with 10 digits.
Sample o/p
<Details>
<Head>
<pageid>0000000001</pageid>
</Head>
<Start>
<pageid>0000000002</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>0000000003</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>0000000004</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>0000000005</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>0000000006</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>0000000007</pageid>
</END>
</Details>
I tried using the following construct:
<xsl:variable name="counter" select="0000000000" saxon:assignable="yes"/>
<xsl:template match="//*[local-name()='pageid']">
<saxon:assign name="counter" select="$counter+0000000001"/>
<imp1:Line_id>
<xsl:value-of select="$counter"></xsl:value-of>
</imp1:Line_id>
But this wasnt helpful. Can u suggest a easier way to do it?
Instead of trying to use a variable counter, you could just make use of the xsl:number element here:
<xsl:template match="//*[local-name()='pageid']">
<imp1:Line_id>
<xsl:number level="any" format="0000000000" />
</imp1:Line_id>
</xsl:template>

How to count the xml node inside xml element using xslt

I am using XSLT to generate my HTML.
I am having below xml and I want to write condition if there is only one city node inside a country node I want to write some condition, please see the below xml.
There are two xmls.
1) destinations.xml
<?xml version="1.0"?>
<list type="Destinations">
<resources location="include/xml/locations.xml">
<publication>232</publication>
</resources>
<destination id="594904" title="Maldives" url="/destinations_offers/destinations/asiapacific/maldives/maldives.aspx" thumbnail="/99/english/images/square_tcm481-594879.jpg" FeaturedDestination="true">
<city id="192513" />
</destination>
<destination id="594089" title="New Delhi" url="/destinations_offers/destinations/asiapacific/india/newdelhi.aspx" thumbnail="/99/english/images/sydney_tcm481-594346.jpg" FeaturedDestination="true" NewestDestination="true">
<city id="192460" />
</destination>
</list>
For eample In the above xml there is city id = 192513 for maldives and it is alone node in locations.xml this will be checked in below locations.xml and if that id is alone in that country node then I need to call specific condition.
<?xml version="1.0"?>
<list type="Locations">
<region id="192393" code="ASIA" name="Asia & the Pacific" shortname="Asia & the Pacific">
<country id="192395" code="AU" name="Australia" shortname="Australia">
<city id="192397" code="BNE" name="Brisbane" shortname="Brisbane">
<airport id="192399" code="BNE" name="Brisbane International Airport" shortname="Brisbane"></airport>
</city>
<city id="192409" code="SYD" name="Sydney" shortname="Sydney">
<airport id="192411" code="SYD" name="Kingsford Smith Airport" shortname="Sydney"></airport>
</city>
</country>
<country id="192511" code="MV" name="Maldives" shortname="Maldives">
<city id="192513" code="MLE" name="Male" shortname="Male">
<airport id="192515" code="MLE" name="Male International Airport" shortname="Male"></airport>
</city>
</country>
</region>
</list>
Please suggest!
Thanks.
Use:
count($vLocations/*/*/country[city[#id = $vDestCity/#id]]/city) = 1
In this expression $vLocations is the XML document with top element <list type="Locations"> and $vDestCity is the <city> element we are interested in from the XML document with top element <list type="Destinations">
To see this in action:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:locations>
<list type="Locations">
<region id="192393" code="ASIA"
name="Asia & the Pacific"
shortname="Asia & the Pacific">
<country id="192395" code="AU" name="Australia"
shortname="Australia">
<city id="192397" code="BNE" name="Brisbane"
shortname="Brisbane">
<airport id="192399" code="BNE"
name="Brisbane International Airport"
shortname="Brisbane">
</airport>
</city>
<city id="192409" code="SYD" name="Sydney"
shortname="Sydney">
<airport id="192411" code="SYD"
name="Kingsford Smith Airport"
shortname="Sydney">
</airport>
</city>
</country>
<country id="192511" code="MV" name="Maldives"
shortname="Maldives">
<city id="192513" code="MLE" name="Male"
shortname="Male">
<airport id="192515" code="MLE"
name="Male International Airport"
shortname="Male">
</airport>
</city>
</country>
</region>
</list>
</my:locations>
<xsl:variable name="vLocations"
select="document('')/*/my:locations"/>
<xsl:variable name="vDestCity1"
select="/*/destination/city[#id=192513]"/>
<xsl:variable name="vDestCity2"
select="/*/destination/city[#id=192397]"/>
<xsl:template match="/">
<xsl:value-of select=
"count($vLocations/*/*/country
[city[#id = $vDestCity1/#id]]/city
) = 1
"/>
:
<xsl:text/>
<xsl:value-of select=
"count($vLocations/*/*/country
[city[#id = $vDestCity2/#id]]/city
) = 1
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided destinations.xml:
<list type="Destinations">
<resources location="include/xml/locations.xml">
<publication>232</publication>
</resources>
<destination id="594904" title="Maldives" url="/destinations_offers/destinations/asiapacific/maldives/maldives.aspx" thumbnail="/99/english/images/square_tcm481-594879.jpg" FeaturedDestination="true">
<city id="192513" />
</destination>
<destination id="594089" title="New Delhi" url="/destinations_offers/destinations/asiapacific/india/newdelhi.aspx" thumbnail="/99/english/images/sydney_tcm481-594346.jpg" FeaturedDestination="true" NewestDestination="true">
<city id="192460" />
</destination>
</list>
The wanted, correct result is produced:
true
:
false
for example Australia: count(//country[#id='192395']/city)

How to enumerate indexed (xsl:key) items?

I'm new in xml, sorry for the dumb question. I'm trying to create xsl template to convert Source xml to Destination. Actually it's almost done but I don't know how to enumerate <creator>'s correctly (creator#affil in Destination xml).
Source:
<?xml version="1.0" encoding="iso-8859-1"?>
<CREATORS>
<PERSON ROLE="CONTACT">
<PREFIX>Prof</PREFIX>
<FIRST_NAME>Mike</FIRST_NAME>
<MIDDLE_INITIALS>J</MIDDLE_INITIALS>
<LAST_NAME>Petrov</LAST_NAME>
<SUFFIX/>
<POSITION>Director</POSITION>
<EMAIL_1>pontorez#pontorez.ru</EMAIL_1>
<EMAIL_2>webmaster#pontorez.ru</EMAIL_2>
<URL>www.pontorez.ru</URL>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT/>
<ORGANISATION>Example</ORGANISATION>
<ADDRESS_1>Finch Pavilion</ADDRESS_1>
<ADDRESS_2>Middle Way</ADDRESS_2>
<CITY>Oxford</CITY>
<ZIP>OX9 7LG</ZIP>
<REGION/>
<COUNTRY CODE="GB">UK</COUNTRY>
<PHONE_1>+380 6245 716300</PHONE_1>
<PHONE_2/>
<FAX_1>+380 6245 716311</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE/>
</PERSON>
<PERSON ID="7091" ROLE="AUTHOR">
<PREFIX>Prof</PREFIX>
<FIRST_NAME>Mike</FIRST_NAME>
<MIDDLE_INITIALS>J</MIDDLE_INITIALS>
<LAST_NAME>Petrov</LAST_NAME>
<SUFFIX/>
<POSITION>Director</POSITION>
<EMAIL_1>pontorez#pontorez.ru</EMAIL_1>
<EMAIL_2>webmaster#pontorez.ru</EMAIL_2>
<URL>www.pontorez.ru</URL>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT/>
<ORGANISATION>Example</ORGANISATION>
<ADDRESS_1>Finch Pavilion</ADDRESS_1>
<ADDRESS_2>Middle Way</ADDRESS_2>
<CITY>Oxford</CITY>
<ZIP>OX9 7LG</ZIP>
<REGION/>
<COUNTRY CODE="GB">UK</COUNTRY>
<PHONE_1>+380 6245 716300</PHONE_1>
<PHONE_2/>
<FAX_1>+380 6245 716311</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE>Author footnote</FOOTNOTE>
</PERSON>
<PERSON ID="7094" ROLE="AUTHOR">
<PREFIX>Mrs</PREFIX>
<FIRST_NAME>Anne</FIRST_NAME>
<MIDDLE_INITIALS/>
<LAST_NAME>Spencer</LAST_NAME>
<SUFFIX/>
<POSITION/>
<EMAIL_1>aspencer#Hardware.co.uk</EMAIL_1>
<EMAIL_2/>
<URL/>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT/>
<ORGANISATION>Example</ORGANISATION>
<ADDRESS_1>SEO R&D Programme</ADDRESS_1>
<ADDRESS_2>Finch Pavilion, Middle Way</ADDRESS_2>
<CITY>Oxford</CITY>
<ZIP>OX9 7LG</ZIP>
<REGION>Oxfordshire</REGION>
<COUNTRY CODE="GB">UK</COUNTRY>
<PHONE_1>+380 6245 716300</PHONE_1>
<PHONE_2/>
<FAX_1>+380 6245 716311</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE/>
</PERSON>
<PERSON ID="15756" ROLE="AUTHOR">
<PREFIX>Mr</PREFIX>
<FIRST_NAME>Ed</FIRST_NAME>
<MIDDLE_INITIALS/>
<LAST_NAME>Gantos</LAST_NAME>
<SUFFIX/>
<POSITION>Senior Medical Statistician</POSITION>
<EMAIL_1>santos#xxx.org.uk</EMAIL_1>
<EMAIL_2/>
<URL>http://www.isds.sxo.ac.uk/</URL>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT>Head of SEO Support</DEPARTMENT>
<ORGANISATION>Centre for Statistics in Software</ORGANISATION>
<ADDRESS_1>Pearson College</ADDRESS_1>
<ADDRESS_2>Linton Road</ADDRESS_2>
<CITY>Oxford</CITY>
<ZIP>OX9 6UD</ZIP>
<REGION/>
<COUNTRY CODE="GB">UK</COUNTRY>
<PHONE_1>+380 6245 112404</PHONE_1>
<PHONE_2/>
<FAX_1>+380 6245 112424</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE/>
</PERSON>
<PERSON ID="7092" ROLE="AUTHOR">
<PREFIX>Dr</PREFIX>
<FIRST_NAME>Sherry</FIRST_NAME>
<MIDDLE_INITIALS/>
<LAST_NAME>Wilson</LAST_NAME>
<SUFFIX/>
<POSITION/>
<EMAIL_1>wilson#Hardware.co.uk</EMAIL_1>
<EMAIL_2/>
<URL/>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT/>
<ORGANISATION>Example</ORGANISATION>
<ADDRESS_1>SEO R&D Programme</ADDRESS_1>
<ADDRESS_2>Finch Pavilion, Middle Way</ADDRESS_2>
<CITY>Oxford</CITY>
<ZIP>OX9 7LG</ZIP>
<REGION>Oxfordshire</REGION>
<COUNTRY CODE="GB">UK</COUNTRY>
<PHONE_1>+380 6245 716300</PHONE_1>
<PHONE_2/>
<FAX_1>+380 6245 716311</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE/>
</PERSON>
<GROUP ID="????">
<GROUP_NAME>Bond team</GROUP_NAME>
<CONTACT_PERSON>Monica Bond</CONTACT_PERSON>
<EMAIL_1>mk#Hardware.dk</EMAIL_1>
<URL/>
<ADDRESS>
<DEPARTMENT>Nordic Hardware Centre</DEPARTMENT>
<ORGANISATION>Creyts, Dept 7512</ORGANISATION>
<ADDRESS_1>Blegdamsvej 219</ADDRESS_1>
<ADDRESS_2/>
<CITY>Copenhagen</CITY>
<ZIP>2900</ZIP>
<REGION/>
<COUNTRY CODE="DK">Denmark</COUNTRY>
<PHONE_1>+415 7667 7110</PHONE_1>
<PHONE_2>+415 1234 9429</PHONE_2>
<FAX_1>+415 7667 7007</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE>Group footnote</FOOTNOTE>
</GROUP>
<PERSON ID="3" ROLE="AUTHOR">
<PREFIX>Ms</PREFIX>
<FIRST_NAME>Monica</FIRST_NAME>
<MIDDLE_INITIALS/>
<LAST_NAME>Bond</LAST_NAME>
<SUFFIX/>
<POSITION>Director of the Hardware Information Management System</POSITION>
<EMAIL_1>mk#Hardware.dk</EMAIL_1>
<EMAIL_2/>
<URL>www.cc-ims.net</URL>
<MOBILE_PHONE/>
<ADDRESS>
<DEPARTMENT>Nordic Hardware Centre</DEPARTMENT>
<ORGANISATION>Creyts, Dept 7512</ORGANISATION>
<ADDRESS_1>Blegdamsvej 219</ADDRESS_1>
<ADDRESS_2/>
<CITY>Copenhagen</CITY>
<ZIP>2900</ZIP>
<REGION/>
<COUNTRY CODE="DK">Denmark</COUNTRY>
<PHONE_1>+415 7667 7110</PHONE_1>
<PHONE_2>+415 1234 9429</PHONE_2>
<FAX_1>+415 7667 7007</FAX_1>
<FAX_2/>
</ADDRESS>
<FOOTNOTE/>
</PERSON>
</CREATORS>
Destination:
<?xml version="1.0" encoding="utf-8"?>
<creatorGroup>
<creator affil="CD004002-aff-0001" creatorRole="author">
<forenames>Mike</forenames>
<surnamePrefix>J</surnamePrefix>
<surname>Petrov</surname>
<note id="CD004002-note-0001">
<p>Author footnote</p>
</note>
</creator>
<creator affil="CD004002-aff-0001" creatorRole="author">
<forenames>Anne</forenames>
<surname>Spencer</surname>
</creator>
<creator affil="CD004002-aff-0002" creatorRole="author">
<forenames>Ed</forenames>
<surname>Gantos</surname>
</creator>
<creator affil="CD004002-aff-0001" creatorRole="author">
<forenames>Sherry</forenames>
<surname>Wilson</surname>
</creator>
<creator affil="CD004002-aff-0003" type="collaboration" creatorRole="author">
<collab>Bond team</collab>
</creator>
<creator affil="CD004002-aff-0003" creatorRole="author">
<forenames>Monica</forenames>
<surname>Bond</surname>
</creator>
<creator affil="CD004002-aff-0001" creatorRole="contact">
<honorifics>Prof</honorifics>
<forenames>Mike</forenames>
<surnamePrefix>J</surnamePrefix>
<surname>Petrov</surname>
<jobTitle>Director</jobTitle>
<email>pontorez#pontorez.ru</email>
<email>webmaster#pontorez.ru</email>
</creator>
<affiliation id="CD004002-aff-0001" countryCode="GB">
<orgName>Example</orgName>
<address>
<street>Finch Pavilion</street>
<street>Middle Way</street>
<city>Oxford</city>
<country>UK</country>
<postCode>OX9 7LG</postCode>
</address>
</affiliation>
<affiliation id="CD004002-aff-0002" countryCode="GB">
<orgName>Centre for Statistics in Software</orgName>
<orgDiv>Head of SEO Support</orgDiv>
<address>
<street>Pearson College</street>
<street>Linton Road</street>
<city>Oxford</city>
<country>UK</country>
<postCode>OX9 6UD</postCode>
</address>
</affiliation>
<affiliation id="CD004002-aff-0003" countryCode="DK">
<collabContact>Monica Bond</collabContact>
<orgName>Creyts, Dept 7512</orgName>
<orgDiv>Nordic Hardware Centre</orgDiv>
<address>
<street>Blegdamsvej 219</street>
<city>Copenhagen</city>
<country>Denmark</country>
<postCode>2900</postCode>
</address>
</affiliation>
</creatorGroup>
XSL stylesheet that I created:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="affiliations" match="ORGANISATION" use="."/>
<xsl:template name="kreator">
<xsl:variable name="affid">
<!-- The numbering problem is in the below line: -->
<xsl:number format="0001" value="position()"/>
</xsl:variable>
<xsl:variable name="role">
<xsl:choose>
<xsl:when test="#ROLE='AUTHOR' or name()='GROUP'">author</xsl:when>
<xsl:otherwise>contact</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<creator affil="CD004002-aff-{$affid}">
<xsl:if test="GROUP_NAME!=''">
<xsl:attribute name="type">collaboration</xsl:attribute>
</xsl:if>
<xsl:attribute name="creatorRole">
<xsl:value-of select="$role"/>
</xsl:attribute>
<xsl:if test="$role='contact'">
<honorifics>
<xsl:value-of select="PREFIX"/>
</honorifics>
</xsl:if>
<xsl:if test="GROUP_NAME!=''">
<collab>
<xsl:value-of select="GROUP_NAME"/>
</collab>
</xsl:if>
<xsl:if test="FIRST_NAME">
<forenames>
<xsl:value-of select="FIRST_NAME"/>
</forenames>
</xsl:if>
<xsl:if test="MIDDLE_INITIALS!=''">
<surnamePrefix>
<xsl:value-of select="MIDDLE_INITIALS"/>
</surnamePrefix>
</xsl:if>
<xsl:if test="LAST_NAME">
<surname>
<xsl:value-of select="LAST_NAME"/>
</surname>
</xsl:if>
<xsl:if test="FOOTNOTE!='' and name()='PERSON'">
<note id="CD004002-note-{$affid}">
<p>
<xsl:value-of select="FOOTNOTE"/>
</p>
</note>
</xsl:if>
<xsl:if test="$role='contact'">
<jobTitle><xsl:value-of select="POSITION"/></jobTitle>
<email><xsl:value-of select="EMAIL_1"/></email>
<email><xsl:value-of select="EMAIL_2"/></email>
</xsl:if>
</creator>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="/CREATORS">
<xmp>
<creatorGroup>
<!-- list of creators: -->
<xsl:for-each select="PERSON[#ROLE='AUTHOR'] | GROUP">
<xsl:call-template name="kreator"/>
</xsl:for-each>
<xsl:for-each select="PERSON[#ROLE='CONTACT']">
<xsl:call-template name="kreator"/>
</xsl:for-each>
<!-- list of affiliations: -->
<xsl:for-each select="//ORGANISATION[generate-id(.)=generate-id(key('affiliations', .))]">
<affiliation>
<xsl:attribute name="id">
CD004002-aff-000<xsl:value-of select="position()"/>
</xsl:attribute>
<xsl:attribute name="countryCode">
<xsl:value-of select="../COUNTRY/#CODE"/>
</xsl:attribute>
<xsl:if test="../../CONTACT_PERSON">
<collabContact>
<xsl:value-of select="../../CONTACT_PERSON"/>
</collabContact>
</xsl:if>
<orgName>
<xsl:value-of select="."/>
</orgName>
<xsl:if test="../DEPARTMENT!=''">
<orgDiv>
<xsl:value-of select="../DEPARTMENT"/>
</orgDiv>
</xsl:if>
<address>
<street>
<xsl:value-of select="../ADDRESS_1"/>
</street>
<xsl:if test="../ADDRESS_2!=''">
<street>
<xsl:value-of select="../ADDRESS_2"/>
</street>
</xsl:if>
<city>
<xsl:value-of select="../CITY"/>
</city>
<country>
<xsl:value-of select="../COUNTRY"/>
</country>
<postCode>
<xsl:value-of select="../ZIP"/>
</postCode>
</address>
</affiliation>
<xsl:text>
</xsl:text>
</xsl:for-each>
</creatorGroup>
</xmp>
</xsl:template>
</xsl:stylesheet>
I.e. I want to get <creator> numbering like the below:
<creator affil="CD004002-aff-0001" creatorRole="author"/>
<creator affil="CD004002-aff-0001" creatorRole="author"/>
<creator affil="CD004002-aff-0002" creatorRole="author"/>
<creator affil="CD004002-aff-0001" creatorRole="author"/>
<creator affil="CD004002-aff-0003" type="collaboration" creatorRole="author"/>
<creator affil="CD004002-aff-0003" creatorRole="author"/>
<creator affil="CD004002-aff-0001" creatorRole="contact"/>
While my current XSL stylesheet produces wrong #affil's:
<creator affil="CD004002-aff-0004" creatorRole="author"/>
<creator affil="CD004002-aff-0004" creatorRole="author"/>
<creator affil="CD004002-aff-0001" creatorRole="author"/>
<creator affil="CD004002-aff-0004" creatorRole="author"/>
<creator affil="CD004002-aff-0002" type="collaboration" creatorRole="author"/>
<creator affil="CD004002-aff-0002" creatorRole="author"/>
<creator affil="CD004002-aff-0004" creatorRole="contact"/>
Can you help please? Thanks.
Your problem is that when you do:
<xsl:call-template name="kreator" />
then you do it in the context of the <PERSON> or <GROUP> you are currently handling.
This means that position() will tell you the position of those elements. It cannot magically know that you are interested in the position of the first <ORGANISATION> that matches the current one.
And this means you must iterate all <ORGANISATION>s much the same way you do it to calculate the <affiliation> attribute #id:
<xsl:variable name="affid">
<xsl:variable name="org" select="ADDRESS/ORGANISATION" />
<xsl:for-each select="//ORGANISATION[
generate-id()
=
generate-id(key('affiliations', .)[1])
]">
<xsl:if test=". = $org">
<xsl:number format="0001" value="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Note the use of <xsl:if> to make sure that the variable eventually contains only one value, even though you are looking through all of them.
For larger documents, you could introduce another key:
<xsl:key name="organisations" match="ORGANISATION" use="'all'" />
and use that as a drop-in replacement for all the comparatively inefficient "//ORGANISATION" expressions, e.g. instead of:
<xsl:for-each select="//ORGANISATION">
<!-- ... -->
</xsl:for-each>
use:
<xsl:for-each select="key('organisations', 'all')">
<!-- ... -->
</xsl:for-each>