Adding an attribute via xslt - xslt

I am working on an issue where I need to add an attribute to an element under certain conditions. Here is the XML that I have. When an AdditionalItem element has a non-empty Value element, I need to add an attribute called action as such:
<AdditionalItems>
**<AdditionalItem>**
<Keys>
<Key>Intake Source</Key>
</Keys>
<IdentifierDisplay>Intake Source</IdentifierDisplay>
<DataType>
<type>Enumeration</type>
<enumeration>
<String>311</String>
<String>NIS Inspector</String>
<String>Other CCD Agency</String>
</enumeration>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<Enumerations>
<Enumeration>
<Keys>
<Key>311</Key>
</Keys>
<IdentifierDisplay>311</IdentifierDisplay>
</Enumeration>
<Enumeration>
<Keys>
<Key>NIS Inspector</Key>
</Keys>
<IdentifierDisplay>NIS Inspector</IdentifierDisplay>
</Enumeration>
<Enumeration>
<Keys>
<Key>Other CCD Agency</Key>
</Keys>
<IdentifierDisplay>Other CCD Agency</IdentifierDisplay>
</Enumeration>
</Enumerations>
<inputRequired>false</inputRequired>
<fieldType>Enumeration</fieldType>
</DataType>
<Name>Intake Source</Name>
**<Value>311</Value>**
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Other CCD Agency</Key>
</Keys>
<IdentifierDisplay>Other CCD Agency</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Other CCD Agency</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>311 Agent</Key>
</Keys>
<IdentifierDisplay>311 Agent</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>311 Agent</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Case Number</Key>
</Keys>
<IdentifierDisplay>Case Number</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Case Number</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Case Created Date</Key>
</Keys>
<IdentifierDisplay>Case Created Date</IdentifierDisplay>
<DataType>
<type>Date</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Date</fieldType>
</DataType>
<Name>Case Created Date</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Complaintant Name:</Key>
</Keys>
<IdentifierDisplay>Complaintant Name:</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Complaintant Name:</Name>
<Value>Fred Fredderson</Value>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Phone Number:</Key>
</Keys>
<IdentifierDisplay>Phone Number:</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Phone Number:</Name>
<Value>3033333333</Value>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Email</Key>
</Keys>
<IdentifierDisplay>Email</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Email</Name>
<Value>1#2.com</Value>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Council District:</Key>
</Keys>
<IdentifierDisplay>Council District:</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Council District:</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Inspector Distict:</Key>
</Keys>
<IdentifierDisplay>Inspector Distict:</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Inspector Distict:</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
<AdditionalItem>
<Keys>
<Key>Permit Number</Key>
</Keys>
<IdentifierDisplay>Permit Number</IdentifierDisplay>
<DataType>
<type>String</type>
<inputRange>
<maxValue>0.0</maxValue>
</inputRange>
<inputRequired>false</inputRequired>
<fieldType>Text</fieldType>
</DataType>
<Name>Permit Number</Name>
<Value/>
<security>F</security>
<drillDown>false</drillDown>
</AdditionalItem>
</AdditionalItems>
My first thought was to do a for-each on //AdditionalItem, then check to see if the length of the Value element was > 0. If so, add the action attribute. Does that seem like a reasonable approach? Something similar to this:
<xsl:for-each select="/ns2:UpdateCAP/ns2:AdditionalInformation//AdditionalItem">
<xsl:if test="string-length(Value) > 0">
<!-- somehow add the attribute -->
</xsl:if>
</xsl:for-each>

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AdditionalItem[Value[text()]]">
<xsl:copy>
<xsl:attribute name="action">Add</xsl:attribute>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
We can not change an existing document xml using xslt. We need to copy all the elements, adding an attribute to the desired location.
So, we copy all nodes and attributes using the first template and copy AdditionalItem node that contains a non-empty inner Value node, adding an attribute using second template.
AdditionalItem matches node with name AdditionalItem.
AdditionalItem[Value] matches node with name AdditionalItem and inner node with name Value that has any content (may be empty).
AdditionalItem[Value[text()]] matches node with name AdditionalItem and inner node with name Value that has some content (non-empty).

Related

XSLT3 Streaming for appending integer position of node

I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current code
``
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)

XSLT - Recursive grouping / count based on attribute values

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.

Genric XSLT style sheet to transform xml

I am looking for some help building an XSLT stylesheet. I have provided the input XML. I need it transformed to the output XML shown.
I am thinking ..
start from the root node
traverse the tree to check if attribute or element.
If element , then string concat and format in
<businessElements>
<key>requestId</key>
<type>String</type>
<value>TV00001001</value>
</businessElements>
Input XML
<entity1>
<requestID>TV00001001</requestID> //nested entities
<entity2>
<effectiveDt>2001-12-31T12:00:00</effectiveDt> // attribute with value
<companyCd>companyCd</companyCd>
< entity3>
<vo1>
<att1>true</ att1>
< att2>vehicleId</att2>
<att3>true</att3>
</vo1>
</ entity3>
</ entity2>
</ entity1>
Output XML
< entity1>
<businessElements>
<key>requestId</key>
<type>String</type>
<value>TV00001001</value>
</businessElements>
< entity2>
<businessElements>
<key>effectiveDt</key>
<type>Date</type>
<value>12/11/2016</value>
</businessElements>
<businessElements>
<key>companyCd</key>
<type>String</type>
<value>0001</value>
</businessElements>
< entity3>
< vo1>
<businessElements>
<key>vehicleId</key>
<type>String</type>
<value>5</value>
</businessElements>
</ vo1>
</ entity3>
</ entity2>
</ entity1>
I see no discernible logic in your output - especially as it contains values that are not in the input.
Try this as your starting point:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(*)]">
<businessElements>
<key>
<xsl:value-of select="name()" />
</key>
<type>???</type>
<value>
<xsl:value-of select="." />
</value>
</businessElements>
</xsl:template>
</xsl:stylesheet>
This takes all the leaf nodes (elements that do not have other elements as their children) and transform them to a businessElements node, where key is the name of the original element and value is its string-value.
I am not sure where the type value should come from.
Applied to the following well-formed (!) input example:
XML
<entity1>
<requestID>TV00001001</requestID>
<entity2>
<effectiveDt>2001-12-31T12:00:00</effectiveDt>
<companyCd>companyCd</companyCd>
<entity3>
<vo1>
<att1>true</att1>
<att2>vehicleId</att2>
<att3>true</att3>
</vo1>
</entity3>
</entity2>
</entity1>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<entity1>
<businessElements>
<key>requestID</key>
<type>???</type>
<value>TV00001001</value>
</businessElements>
<entity2>
<businessElements>
<key>effectiveDt</key>
<type>???</type>
<value>2001-12-31T12:00:00</value>
</businessElements>
<businessElements>
<key>companyCd</key>
<type>???</type>
<value>companyCd</value>
</businessElements>
<entity3>
<vo1>
<businessElements>
<key>att1</key>
<type>???</type>
<value>true</value>
</businessElements>
<businessElements>
<key>att2</key>
<type>???</type>
<value>vehicleId</value>
</businessElements>
<businessElements>
<key>att3</key>
<type>???</type>
<value>true</value>
</businessElements>
</vo1>
</entity3>
</entity2>
</entity1>

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>