XSLT - A generic rule in apply template to include multiple nodes - xslt

I am working over sorting the Grand Parent based on a field in Father. The source file looks like
<Root>
<AllData>
<Data_not_to_be_sorted>
<Additional_data1>
<Some_test_data1/>
<Some_test_data2/>
</Additional_data1>
</Data_not_to_be_sorted>
<RealData>
<Some_data1/>
<Some_data2/>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>4</Value>
<Name>name in 4</Name>
</Father>
</GrandFather>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>3</Value>
<Name>name in 3</Name>
</Father>
</GrandFather>
</RealData>
<RealData>
<Some_data1/>
<Some_data2/>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>2</Value>
<Name>name in 2</Name>
</Father>
</GrandFather>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>1</Value>
<Name>name in 1</Name>
</Father>
</GrandFather>
</RealData>
</AllData>
</Root>
The XSLT code, I am using is as below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RealData">
<xsl:copy>
<xsl:apply-templates select="Data_required_as_it_is"></xsl:apply-templates>
<xsl:apply-templates select="GrandFather">
<xsl:sort select="Father/Value" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The code is working well...My query is - Can I write a generic line for
Data_required_as_it_is. Imagine a situation where I have 20 different xml tags for "Data_required_as_it_is" so either I need write all of them manually or just write them in a generic way...
The output of the code is as below:
<Root>
<AllData>
<Data_not_to_be_sorted>
<Additional_data1>
<Some_test_data1/>
<Some_test_data2/>
</Additional_data1>
</Data_not_to_be_sorted>
<RealData>
<Some_data1/>
<Some_data2/>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>3</Value>
<Name>name in 3</Name>
</Father>
</GrandFather>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>4</Value>
<Name>name in 4</Name>
</Father>
</GrandFather>
</RealData>
<RealData>
<Some_data1/>
<Some_data2/>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>1</Value>
<Name>name in 1</Name>
</Father>
</GrandFather>
<GrandFather>
<Data_required_as_it_is></Data_required_as_it_is>
<Father>
<Value>2</Value>
<Name>name in 2</Name>
</Father>
</GrandFather>
</RealData>
</AllData>
</Root>

<xsl:template match="RealData">
<xsl:copy>
<xsl:apply-templates select="Data_required_as_it_is"></xsl:apply-templates>
<xsl:apply-templates select="GrandFather">
<xsl:sort select="Father/Value" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
One could use a more generic code like this:
<xsl:template match="RealData">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="self::GrandFather/Father/Value" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
Note: This will sort any any non-GrandFather node before any GrandFather node and will do the specified sorting of GrandFather elements by their Father/Value elements.
In case it is desired that the non-GrandFather elements remain interspersed within the GrandFather elements, this can also be accomplished -- ask another question if interested.

If you want to match anything that isn't a GrandFather element at this point, you should be able to do something like this:
<xsl:apply-templates select="*[not(self::GrandFather)]"></xsl:apply-templates>
Note that if you had elements after any existing GrandFather elements, they would be moved to before the sorted GrandFather elements in the output in this case though.

Related

XSLT 1.0: How to combine and sum the fields of children in records based on having the same id field?

I have the following:
<ns0:tXML>
<Message>
<Report>
<Page>
<PageID>01</PageID>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>10</Num>
</Totals>
</PageDetail>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>5</Num>
</Totals>
</PageDetail>
</Page>
<Page>
<PageID>02</PageID>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>10</Num>
</Totals>
</PageDetail>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>3</Num>
</Totals>
</PageDetail>
</Page>
</Report>
</Message>
</ns0:tXML>
I want to make the output so that PageDetails are combined for each Page as long as their PageName and PageID are the same, including summing the values of the combined.
Output Wanted:
<ns0:tXML>
<Message>
<Report>
<Page>
<PageID>01</PageID>
<PageDetail>
<PageName>11</PageName>
<Totals>
<Num>15</Num>
</Totals>
</PageDetail>
</Page>
<Page>
<PageID>02</PageID>
<PageDetail>
<PageName>12</PageName>
<Totals>
<Num>13</Num>
</Totals>
</PageDetail>
</Page>
</Report>
</Message>
</ns0:tXML>
How would I go about it? All efforts with using keys and playing with templates has led to cases where only one of the Pages got created, or it combined all the Pages no matter where they were on the xml, showing that I was likely trying to do an all apply to it rather than sticking to the current context.
Let's start from a little correction to your source. It should include
the namespace specification:
<ns0:tXML xmlns:ns0="urn.dummy.com">
otherwise there is reported the following error:
The prefix "ns0" for element "ns0:tXML" is not bound.
One of possible solutions is to use the following script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="urn.dummy.com">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="Pd" match="PageDetail" use="concat(../PageID, '|', PageName)"/>
<xsl:template match="Page">
<xsl:copy>
<xsl:copy-of select="PageID"/>
<xsl:for-each select="PageDetail[generate-id()=generate-id(key('Pd',
concat(../PageID,'|', PageName))[1])]">
<xsl:variable name="kk" select="concat(../PageID,'|', PageName)"/>
<xsl:copy>
<xsl:copy-of select="PageName"/>
<xsl:element name="Totals">
<xsl:element name="Num">
<xsl:value-of select="sum(key('Pd', $kk)/Totals/Num)"/>
</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For a working example, generating just your expected result,
see: http://xsltransform.net/93YRmgt

xsl not transforming to desired output

I am trying to replicate the example located here Click Here
However, I am unable to achieve the desired results.
The output is not at all getting generated as per desired.
The input I have used :
<?xml version="1.0" encoding="UTF-8" ?>
<Order xmlns="http://www.book.org">
<Bundle>
<authors>
<author>
<authorId>100</authorId>
<authorName>Kathisiera</authorName>
</author>
<author>
<authorId>200</authorId>
<authorName>Bates</authorName>
</author>
<author>
<authorId>300</authorId>
<authorName>Gavin King</authorName>
</author>
</authors>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
**The Schema I have used in for my transformation**
<!-- begin snippet: js hide: false console: true babel: false -->
The XSLT I am using for my transformation :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:socket="http://www.oracle.com/XSL/Transform/java/oracle.tip.adapter.socket.ProtocolTranslator"
xmlns:oracle-xsl-mapper="http://www.oracle.com/xsl/mapper/schemas"
xmlns:dvm="http://www.oracle.com/XSL/Transform/java/oracle.tip.dvm.LookupValue"
xmlns:mhdr="http://www.oracle.com/XSL/Transform/java/oracle.tip.mediator.service.common.functions.MediatorExtnFunction"
xmlns:oraxsl="http://www.oracle.com/XSL/Transform/java"
xmlns:oraext="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.ExtFunc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xref="http://www.oracle.com/XSL/Transform/java/oracle.tip.xref.xpath.XRefXPathFunctions"
xmlns:ns0="http://www.book.org"
exclude-result-prefixes="oracle-xsl-mapper xsi xsd xsl ns0 socket dvm mhdr oraxsl oraext xp20 xref">
<oracle-xsl-mapper:schema>
<!--SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY.-->
<oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:source type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:source>
</oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:mapTargets>
<oracle-xsl-mapper:target type="XSD">
<oracle-xsl-mapper:schema location="../Schemas/BooksOrder.xsd"/>
<oracle-xsl-mapper:rootElement name="Order" namespace="http://www.book.org"/>
</oracle-xsl-mapper:target>
</oracle-xsl-mapper:mapTargets>
<!--GENERATED BY ORACLE XSL MAPPER 12.2.1.2.0(XSLT Build 161003.0739.0018) AT [THU JAN 23 13:13:32 IST 2020].-->
</oracle-xsl-mapper:schema>
<!--User Editing allowed BELOW this line - DO NOT DELETE THIS LINE-->
<xsl:key name="k" match="ns0:Order/ns0:Bundle/ns0:books/ns0:book" use="ns0:orderId"/>
<xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:books/ns0:author" use="ns0:authorId"/>
<xsl:template match="/ns0:Order">
<xsl:copy>
<xsl:apply-templates select="//ns0:books"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//ns0:books">
<xsl:apply-templates select="ns0:book[generate-id(.) = generate-id(key('k', ns0:orderId))]"/>
</xsl:template>
<xsl:template match="ns0:book">
<Bundle>
<authors>
<xsl:apply-templates select="key('a', string(key('k', ns0:orderId)/ns0:bookAuthorId ))" />
</authors>
<books>
<xsl:copy-of select="key('k', ns0:orderId)"/>
</books>
</Bundle>
</xsl:template>
<xsl:template match="ns0:author">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However the output I am getting is :
<?xml version = '1.0' encoding = 'UTF-8'?>
<Order xmlns="http://www.book.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.book.org file:/C:/JDeveloper/mywork/SOADevelopment/XsltLearner/SOA/Schemas/BooksOrder.xsd">
<Bundle>
<authors/>
<books>
<book>
<orderId>1111</orderId>
<bookName>Head First Java</bookName>
<bookAuthorId>100</bookAuthorId>
</book>
<book>
<orderId>1111</orderId>
<bookName>Hibernate In Action</bookName>
<bookAuthorId>300</bookAuthorId>
</book>
</books>
</Bundle>
<Bundle>
<authors/>
<books>
<book>
<orderId>5555</orderId>
<bookName>Head First Servlets</bookName>
<bookAuthorId>200</bookAuthorId>
</book>
</books>
</Bundle>
</Order>
The node is not getting populated.
I have tried using copy-of to see the output for the second key but it prints nothing.
Could you please suggest me where I am going wrong.
Any Idea what I am doing incorrect?
Please help
Thanks!
The pattern for the key would <xsl:key name="a" match="ns0:Order/ns0:Bundle/ns0:authors/ns0:author" use="ns0:authorId"/>, as far as I understand your posted document sample. I would also remove the string call in any use of the key function e.g. use <xsl:apply-templates select="key('a', key('k', ns0:orderId)/ns0:bookAuthorId )"/>.

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)

Sorting on attribute collected from reference list

I am trying to sort a list of categorized xml elements, using XSLT 2.0. Each element has a unique ID and the categorization is defined in another list containing these and more elements. Here's an example of a starting XML document. The section that I want sorted is /Atlas/VisitedCities. It should be sorted according to area of the world and date of the visit:
<?xml version="1.0" encoding="UTF-8"?>
<Atlas>
<Cities>
<City id="1" worldPart="Africa">
<Name>Luxor</Name>
<Founded>-3200</Founded>
<Location>Egypt</Location>
</City>
<City id="2" worldPart="Africa">
<Name>Tripoli</Name>
<Founded>-700</Founded>
<Location>Libya</Location>
</City>
<City id="3" worldPart="Americas">
<Name>Cholula</Name>
<Founded>-200</Founded>
<Location>Mexico</Location>
</City>
<City id="4" worldPart="Americas">
<Name>Flores</Name>
<Founded>-1000</Founded>
<Location>Guatemala</Location>
</City>
<City id="5" worldPart="Europe">
<Name>Argos</Name>
<Founded>-5000</Founded>
<Location>Greece</Location>
</City>
<City id="6" worldPart="Europe">
<Name>Athens</Name>
<Founded>-4000</Founded>
<Location>Greece</Location>
</City>
</Cities>
<VisitedCities lastUpdate="2018-09-10">
<VisitedCity cityID="6">
<Date>1883-08-26</Date>
<Visitor>Dora</Visitor>
</VisitedCity>
<VisitedCity cityID="3">
<Date>1907-01-02</Date>
<Visitor>Nemo</Visitor>
</VisitedCity>
<VisitedCity cityID="4">
<Date>1940-02-08</Date>
<Visitor>Jimenez</Visitor>
</VisitedCity>
<VisitedCity cityID="2">
<Date>1886-06-10</Date>
<Visitor>James T. Kirk</Visitor>
</VisitedCity>
</VisitedCities>
</Atlas>
The wanted output is this:
<?xml version="1.0" encoding="UTF-8"?>
<Atlas>
<Cities>
<City id="1" worldPart="Africa">
<Name>Luxor</Name>
<Founded>-3200</Founded>
<Location>Egypt</Location>
</City>
<City id="2" worldPart="Africa">
<Name>Tripoli</Name>
<Founded>-700</Founded>
<Location>Libya</Location>
</City>
<City id="3" worldPart="Americas">
<Name>Cholula</Name>
<Founded>-200</Founded>
<Location>Mexico</Location>
</City>
<City id="4" worldPart="Americas">
<Name>Flores</Name>
<Founded>-1000</Founded>
<Location>Guatemala</Location>
</City>
<City id="5" worldPart="Europe">
<Name>Argos</Name>
<Founded>-5000</Founded>
<Location>Greece</Location>
</City>
<City id="6" worldPart="Europe">
<Name>Athens</Name>
<Founded>-4000</Founded>
<Location>Greece</Location>
</City>
</Cities>
<VisitedCities lastUpdate="2018-09-10">
<VisitedCity cityID="2">
<Date>1886-06-10</Date>
<Visitor>James T. Kirk</Visitor>
</VisitedCity>
<VisitedCity cityID="6">
<Date>1883-08-26</Date>
<Visitor>Dora</Visitor>
</VisitedCity>
<VisitedCity cityID="3">
<Date>1907-01-02</Date>
<Visitor>Nemo</Visitor>
</VisitedCity>
<VisitedCity cityID="4">
<Date>1940-02-08</Date>
<Visitor>Jimenez</Visitor>
</VisitedCity>
</VisitedCities>
</Atlas>
The stylesheet (XSLT 2.0) that I am struggling with looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<!-- Format output -->
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<!-- Copy everything that does not match later templates. -->
<xsl:template match="node()|#*" priority="-1">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="city.list" select="/Atlas/Cities"/>
<xsl:variable name="sort-order" as="element()*">
<wPart>Africa</wPart>
<wPart>Europe</wPart>
<wPart>Americas</wPart>
</xsl:variable>
<xsl:template match="/Atlas/VisitedCities">
<xsl:variable name="city-list" select="."/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each select="$sort-order">
<xsl:variable name="this-wpart" select="./text()"/>
<!-- How to select VisitedCity based on info in other list??? -->
<xsl:apply-templates select="$city-list/VisitedCity[$city.list/City[#cityID=$city-list/VisitedCity/#cityID]/#worldPart=$this-wpart]">
<xsl:sort select="./Date"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I think I understand why this stylesheet will not work (it does not sort at all), as I don't know how to make the selection in the (last) apply-templates. I don't see how to refer to the outermost elements from the inner parts of this expression.
It might suffice to set up a key to reference the City elements by the id attribute to then, in the xsl:sort select expression reference the worldPart attribute. Additionally you could replace the for-each on your continent order with an index-of() call with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="city-by-id" match="Cities/City" use="#id"/>
<xsl:variable name="sort-order" as="element()*">
<wPart>Africa</wPart>
<wPart>Europe</wPart>
<wPart>Americas</wPart>
</xsl:variable>
<xsl:template match="VisitedCities">
<xsl:copy>
<xsl:apply-templates select="VisitedCity">
<xsl:sort select="index-of($sort-order, key('city-by-id', #cityID)/#worldPart)"/>
<xsl:sort select="Date"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/eiZQaFJ
That complete example is XSLT 3 but to use it with XSLT 2 you would just replace the xsl:mode declaration in there with your template you have prefixed with the comment <!-- Copy everything that does not match later templates. -->, that is, with the identity transformation template.

Flat file data mapping to sibling nodes

I have a flat file coming in where each record is a customer and it has a shipto and billto address in that record. The output schema has a customer record with an address child node. I don't know how to map the 2 addresses from the incoming record to sibiling child nodes of the customer record.
I have an input file that is defined like:
<customer>
<customernum/>
<shipaddrcity/>
<shipaddrstate/>
<shipaddrzip/>
<billaddrcity/>
<billaddrstate/>
<billaddrzip/>
</customer>
The output needs to end up looking like:
<customer>
<customernum/>
<addr>
<type/>
<city/>
<state/>
<zip/>
</addr>
<addr>
<type/>
<city/>
<state/>
<zip/>
</addr>
</customer>
I'm quite new to biztalk and have been unable to find any decent examples of how to complete this using the Biztalk Mapper. I'm also willing to listen to how to do this with xslt.
You probably want something specific to BizTalk, and I don't know anything about BizTalk, but this might be helpful to you any way.
Given an input document of ...
<customers>
<customer>
<customernum>1</customernum>
<shipaddrcity>Cairns</shipaddrcity>
<shipaddrstate>QLD</shipaddrstate>
<shipaddrzip>b</shipaddrzip>
<billaddrcity>Sydney</billaddrcity>
<billaddrstate>NSW</billaddrstate>
<billaddrzip>c</billaddrzip>
</customer>
<customer>
<customernum>2</customernum>
<shipaddrcity>d</shipaddrcity>
<shipaddrstate>WA</shipaddrstate>
<shipaddrzip>e</shipaddrzip>
<billaddrcity>Melbourne</billaddrcity>
<billaddrstate>Vic</billaddrstate>
<billaddrzip>f</billaddrzip>
</customer>
</customers>
... this XSLT 1.0 stylesheet ...
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="customer">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(
self::shipaddrstate|
self::shipaddrzip |
self::billaddrstate|
self::billaddrzip )]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="shipaddrcity">
<addr>
<type>ship</type>
<city><xsl:value-of select="." /></city>
<xsl:apply-templates select="../(shipaddrstate|shipaddrzip)" />
</addr>
</xsl:template>
<xsl:template match="billaddrcity">
<addr>
<type>bill</type>
<city><xsl:value-of select="." /></city>
<xsl:apply-templates select="../(billaddrstate|billaddrzip)" />
</addr>
</xsl:template>
<xsl:template match="shipaddrstate|billaddrstate">
<state><xsl:value-of select="." /></state>
</xsl:template>
<xsl:template match="shipaddrzip|billaddrzip">
<zip><xsl:value-of select="." /></zip>
</xsl:template>
</xsl:transform>
... when applied to the input document, will yield ...
<customers>
<customer>
<customernum>1</customernum>
<addr>
<type>ship</type>
<city>Cairns</city>
<state>QLD</state>
<zip>b</zip>
</addr>
<addr>
<type>bill</type>
<city>Sydney</city>
<state>NSW</state>
<zip>c</zip>
</addr>
</customer>
<customer>
<customernum>2</customernum>
<addr>
<type>ship</type>
<city>d</city>
<state>WA</state>
<zip>e</zip>
</addr>
<addr>
<type>bill</type>
<city>Melbourne</city>
<state>Vic</state>
<zip>f</zip>
</addr>
</customer>
</customers>