xml transform using XSLT, order by alphabet in inner node - xslt

I have next WSDL file
<definitions targetNamespace="http://soft.com/" name="LoggingWebService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws.config.softid.softcomputer.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema>
<xsd:import namespace="http://soft.com/" schemaLocation="my.xsd"/>
</xsd:schema>
</types>
<message name="log">
<part name="parameters" element="tns:log"/>
</message>
<message name="getLogs">
<part name="parameters" element="tns:getLogs"/>
</message>
<portType name="LoggingWebService">
<operation name="log">
<input message="tns:log"/>
</operation>
<operation name="getLogs">
<input message="tns:getLogs"/>
<output message="tns:getLogsResponse"/>
</operation>
</portType>
</definitions>
I want transform this file using javax.transformation to another file, where messages will be ordered by alphabet (using string in 'name').
<definitions targetNamespace="http://soft.com/" name="LoggingWebService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws.config.softid.softcomputer.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema>
<xsd:import namespace="http://soft.com/" schemaLocation="my.xsd"/>
</xsd:schema>
</types>
<message name="getLogs">
<part name="parameters" element="tns:getLogs"/>
</message>
<message name="log">
<part name="parameters" element="tns:log"/>
</message>
<portType name="LoggingWebService">
<operation name="getLogs">
<input message="tns:getLogs"/>
<output message="tns:getLogsResponse"/>
</operation>
<operation name="log">
<input message="tns:log"/>
</operation>
</portType>
</definitions>
What XSLT file I need for this? Help me plz

This stylesheet will also work on the files in your previous question.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
version="1.0" >
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="/wsdl:definitions" >
<xsl:copy>
<xsl:copy-of select="wsdl:types" />
<xsl:apply-templates select="wsdl:message" >
<xsl:sort select="#name" />
</xsl:apply-templates>
<xsl:apply-templates select="wsdl:portType" />
</xsl:copy>
</xsl:template>
<xsl:template match="wsdl:message">
<xsl:copy-of select="current()"/>
</xsl:template>
<xsl:template match="wsdl:portType">
<xsl:copy>
<xsl:apply-templates select="wsdl:operation">
<xsl:sort select="#name"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="wsdl:operation">
<xsl:copy-of select="current()"/>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:import namespace="http://soft.com/" schemaLocation="my.xsd"/>
</xsd:schema>
</types>
<message name="getLogs">
<part element="tns:getLogs" name="parameters"/>
</message>
<message name="log">
<part element="tns:log" name="parameters"/>
</message>
<portType>
<operation name="getLogs">
<input message="tns:getLogs"/>
<output message="tns:getLogsResponse"/>
</operation>
<operation name="log">
<input message="tns:log"/>
</operation>
</portType>
</definitions>

Related

XSLT 3.0 extract transformed nodes as multiple documents

I have this XML structure
<doc>
<Bundle>
<entry>
<Observation>
<id value="o1-3" />
<subject>
<reference value="Subject/1" />
</subject>
<valueQuantity>
<value value="400" />
<unit value="U" />
</valueQuantity>
<referenceRange>
<low>
<value value="0" />
<unit value="U" />
</low>
<high>
<value value="45" />
<unit value="U" />
</high>
</referenceRange>
</Observation>
</entry>
<entry>
<Observation>
<id value="o8-3" />
<subject>
<reference value="Subject/1" />
</subject>
<valueQuantity>
<value value="0.39" />
<unit value="L" />
</valueQuantity>
<referenceRange>
<low>
<value value="0.14" />
<unit value="L" />
</low>
<high>
<value value="0.35" />
<unit value="L" />
</high>
</referenceRange>
</Observation>
</entry>
</Bundle>
<Bundle>
<entry>
<Observation>
<id value="o3-4" />
<subject>
<reference value="Subject/2" />
</subject>
<valueQuantity>
<value value="10" />
<unit value="U" />
</valueQuantity>
<referenceRange>
<low>
<value value="3" />
<unit value="U" />
</low>
<high>
<value value="30" />
<unit value="U" />
</high>
</referenceRange>
</Observation>
</entry>
<entry>
<Observation>
<id value="o15-4" />
<subject>
<reference value="Subject/2" />
</subject>
<valueQuantity>
<value value="7.1" />
<unit value="m" />
</valueQuantity>
<referenceRange>
<low>
<value value="3.5" />
<unit value="m" />
</low>
<high>
<value value="5.0" />
<unit value="m" />
</high>
</referenceRange>
</Observation>
</entry>
</Bundle>
</doc>
I am developing below mechanism:
Interpret if the valueQuantity is deviated from the referenceRange, if yes, transform the entry
Extract the Observation node grouped by Observation/subject as separate document.
A correctly interpreted Observation and extracted document is below:
<?xml version="1.0" encoding="UTF-8"?>
<Interpretation xmlns="http://intelli.org/interpretation">
<Subject>Subject/1</Subject>
<Observations>
<id value="o1-3"/>
<subject>
<reference value="Subject/1"/>
</subject>
<valueQuantity>
<value value="400"/>
<unit value="U"/>
</valueQuantity>
<referenceRange>
<low>
<value value="0"/>
<unit value="U"/>
</low>
<high>
<value value="45"/>
<unit value="U"/>
</high>
</referenceRange></Observations></Interpretation>
My XSLT:
<!-- Interpretation Starts -->
<xsl:template match="valueQuantity">
<xsl:param name="value" as="xs:double*" select="value/#value" />
<xsl:param name="low" as="xs:double*" select="following::referenceRange[1]/low/value/#value" />
<xsl:param name="high" as="xs:double*" select="following::referenceRange[1]/high/value/#value" />
<xsl:if test="$value lt $low or $value gt $high">
<xsl:element name="Interpretation">
</xsl:element>
</xsl:if>
<!-- Interpretation Ends -->
<!-- Identity Transform -->
<xsl:copy-of select="." />
<!-- Extraction Starts: Locality? -->
<xsl:for-each select="parent::Observation">
<xsl:result-document include-content-type="no" href="/interpret&extract/deviation/{concat('interpretation/', id/#value, '.xml')}">
<xsl:copy-of select="." />
</xsl:result-document>
</xsl:for-each>
</xsl:template>
I guess (because you haven't explained it clearly) that you're trying to write all the entry/valueQuantity elements that have the same value for entry/subject/reference to the same output file. The spec doesn't allow that (for a number of reasons: the results would depend on order of execution, parallel execution would become very difficult, and the resulting XML document would have no outer wrapper element).
Instead, do a separate pass over the input to generate this output file, using something like
<xsl:for-each-group select="entry" group-by="subject/reference/#value">
<xsl:result-document href="{...}">
<wrapper>
<xsl:copy-of select="current-group()"/>
</wrapper>
</xsl:result-document>
</xsl:for-each-group>

Map empty to a node with xslt

I am using "Altova MapForce" mapping tool and I'm mapping node to node. The resulted xml (Using the altova xslt processor) is without nodes that are not empty. The resulted xml (Using the biztalk xslt processor, with the generated xsl from the Altova Mapforce) is with nodes that are not empty. That nodes are empty on the source.
My Goal is to connect the source node (Using Altova MapForce mapping tool) to a custom written XSLT, and then to connect it to the target.
Here is my code: in.xml - Instance input for executing the map (Please notice the empty id tag: )
<ns0:Root xmlns:ns0="http://BizTalk_Server_Project1.Schema1">
<id root="root_0" extension="extension_1" />
</ns0:Root>
Schema1.xsd - source and target schema
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://BizTalk_Server_Project1.Schema1" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" targetNamespace="http://BizTalk_Server_Project1.Schema1" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Root">
<xs:complexType>
<xs:sequence>
<xs:element name="id">
<xs:complexType>
<xs:attribute name="root" type="xs:string" />
<xs:attribute name="extension" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
New.mfd - Altova mapping file
<?xml version="1.0" encoding="UTF-8"?>
<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="19">
<component name="defaultmap1" blackbox="0" uid="1" editable="1">
<properties SelectedLanguage="xslt"/>
<structure>
<children>
<component name="document" library="xml" uid="4" kind="14">
<properties/>
<view rbx="150" rby="200"/>
<data>
<root>
<header>
<namespaces>
<namespace/>
<namespace uid="http://BizTalk_Server_Project1.Schema1"/>
<namespace uid="http://www.altova.com/mapforce"/>
</namespaces>
</header>
<entry name="FileInstance" ns="2" expanded="1">
<entry name="document" ns="2" expanded="1" casttotargettypemode="cast-in-subtree">
<entry name="Root" ns="1" expanded="1">
<entry name="id" expanded="1">
<entry name="extension" type="attribute" outkey="4"/>
</entry>
</entry>
</entry>
</entry>
</root>
<document schema="Schema1.xsd" outputinstance="Schema1.xml" instanceroot="{http://BizTalk_Server_Project1.Schema1}Root"/>
<wsdl/>
</data>
</component>
<component name="document" library="xml" uid="5" kind="14">
<properties XSLTDefaultOutput="1"/>
<view ltx="593" rbx="743" rby="200"/>
<data>
<root>
<header>
<namespaces>
<namespace/>
<namespace uid="http://BizTalk_Server_Project1.Schema1"/>
<namespace uid="http://www.altova.com/mapforce"/>
</namespaces>
</header>
<entry name="FileInstance" ns="2" expanded="1">
<entry name="document" ns="2" expanded="1" casttotargettypemode="cast-in-subtree">
<entry name="Root" ns="1" expanded="1">
<entry name="id" expanded="1">
<entry name="extension" type="attribute" inpkey="5"/>
</entry>
</entry>
</entry>
</entry>
</root>
<document schema="Schema1.xsd" outputinstance="Schema1.xml" instanceroot="{http://BizTalk_Server_Project1.Schema1}Root"/>
<wsdl/>
</data>
</component>
</children>
<graph directed="1">
<edges/>
<vertices>
<vertex vertexkey="4">
<edges>
<edge vertexkey="5" edgekey="6"/>
</edges>
</vertex>
</vertices>
</graph>
</structure>
</component>
</mapping>
Xsl.xsl - Xsl generated by the Altova Mapper
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="http://BizTalk_Server_Project1.Schema1" xmlns:agt="http://www.altova.com/Mapforce/agt" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 agt xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template name="agt:var2_MapToSchema1_function">
<xsl:param name="par0"/>
<xsl:attribute name="extension">
<xsl:value-of select="string($par0/#extension)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://BizTalk_Server_Project1.Schema1 C:/Users/OhadAv/Desktop/ForAltova/Schema1.xsd</xsl:attribute>
<id xmlns="">
<xsl:for-each select="ns0:Root/id">
<xsl:variable name="var1_extension">
<xsl:if test="#extension">
<xsl:value-of select="'1'"/>
</xsl:if>
</xsl:variable>
<xsl:if test="string(boolean(string($var1_extension))) != 'false'">
<xsl:call-template name="agt:var2_MapToSchema1_function">
<xsl:with-param name="par0" select="."/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</id>
</Root>
</xsl:template>
</xsl:stylesheet>
GeneratedByBizTalkMapperAfterXSLTmap.xml - Output instance of the BizTalk mapper after using the "Xsl.xsl" for the map (Please notice the non-empty id tag that has been generated when editing the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Root xsi:schemaLocation="http://BizTalk_Server_Project1.Schema1 C:/Users/OhadAv/Desktop/ForAltova/Schema1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://BizTalk_Server_Project1.Schema1">
<id extension="extension_1" xmlns="">
</id>
</Root>
The xslt generated by the Altova mapper looks pretty messy.
e.g. use of a one time only variable, a call template just used once, and repeated conversion like string(boolean(string(
I'm not 100% clear on what mapping you need to do in your map.
If you want to remove the root attribute entirely, and provide an id element even if it is missing, then the below xslt will work:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://BizTalk_Server_Project1.Schema1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="ns0 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns0:Root">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:element name="id">
<xsl:for-each select="ns0:Root/id">
<xsl:if test="#extension">
<xsl:attribute name="extension">
<xsl:value-of select="string(#extension)"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
</xsl:element>
</Root>
</xsl:template>
</xsl:stylesheet>
This produces XML without the xmlns, extraneous schema locations, etc like so:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<id extension="extension_1" />
</Root>
If however you mean that you want to omit the id element entirely if it isn't present on the input node, then change the template as follows.
<xsl:template match="/ns0:Root">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:if test="id">
<xsl:element name="id">
<xsl:attribute name="extension">
<xsl:value-of select="string(id/#extension)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</Root>
</xsl:template>
Which Maps
<ns0:Root xmlns:ns0="http://BizTalk_Server_Project1.Schema1">
</ns0:Root>
To:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://BizTalk_Server_Project1.Schema1" />

XSLT - Generate easier xml structure to avoid grouping

How can I convert this document:
<Root>
<!-- yes, I know I don't need a 'Root' element! Legacy code... -->
<Plans>
<Plan AreaID="1" UnitID="83">
<Part ID="9122" Name="foo" />
<Part ID="9126" Name="bar" />
</Plan>
<Plan AreaID="1" UnitID="86">
<Part ID="8650" Name="baz" />
</Plan>
<Plan AreaID="2" UnitID="26">
<Part ID="215" Name="quux" />
</Plan>
<Plan AreaID="1" UnitID="95">
<Part ID="7350" Name="meh" />
</Plan>
</Plans>
</Root>
to:
<areas>
<area id="1">
<unit id="83">
<part id="9122">foo</part>
<part id="9126">bar</part>
</unit>
<unit id="86">
<part id="8650">baz</part>
</unit>
<unit id="95">
<part id="7350">meh</part>
</unit>
</area>
<area id="2">
<unit id="26">
<part id="215">quux</part>
</unit>
</area>
</areas>
Do I need to group area elements?
Here is a sample stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="Plan" use="#AreaID"/>
<xsl:template match="Plans">
<areas>
<xsl:apply-templates select="Plan[generate-id() = generate-id(key('k1', #AreaID)[1])]" mode="group"/>
</areas>
</xsl:template>
<xsl:template match="Plan" mode="group">
<area id="{#AreaID}">
<xsl:apply-templates select="key('k1', #AreaID)"/>
</area>
</xsl:template>
<xsl:template match="Plan">
<unit id="{#UnitID}">
<xsl:apply-templates/>
</unit>
</xsl:template>
<xsl:template match="Part">
<part id="{#ID}">
<xsl:value-of select="#Name"/>
</part>
</xsl:template>
</xsl:stylesheet>

XML to XML - Create Unique IDs and reference them in the same document

I have a source xml that contains the addresses in spot and need to transform into an xml that holds all addresses into a single element and references each one.
I am using Saxon 9.1 processor and stylesheet version 1.0.
Thank you for helping.
Source Code:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Target Code:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="Prev_1" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="Curr_2" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="Mail_3" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="Prev_1" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID UniqueID="Curr_2" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID UniqueID="Mail_3" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Have played with key and generate-id as I was trying to Generate the ID's first and copy them in the address. Here is my last trial of the xslt (best result I got was to have the UniqueID's empty so I have no idea how far off this solution is :) )
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="ID_key" match="*[#ReferedID]" use="#ReferedID"/>
<xsl:template match="/">
<Application>
<ContactDetails>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<AddressDetails>
<xsl:attribute name="StartDate">
<xsl:value-of select="#StartDate"/>
</xsl:attribute>
<xsl:attribute name="Type">
<xsl:value-of select="#Type" />
</xsl:attribute>
<AddressRef>
<xsl:attribute name="ReferedID">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
</AddressRef>
</AddressDetails>
</xsl:for-each>
</ContactDetails>
<AddressSegment>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<Address>
<ID>
<xsl:attribute name="UniqueID">
<xsl:value-of select="Address/ID[generate-id()=generate-id(key('ID_key',#UniqueID))]" />
</xsl:attribute>
</ID>
<City>
<xsl:attribute name="City">
<xsl:value-of select="Address/City/#City"/>
</xsl:attribute>
</City>
<Postcode>
<sl:attribute name="Postcode">
<xsl:value-of select="Address/Postcode/#Postcode"/>
</xsl:attribute>
</Postcode>
</Address>
</xsl:for-each>
</AddressSegment>
</Application>
</xsl:template>
</xsl:stylesheet>
To give you an example of how you could use generate-id and modes, the sample stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ContactDetails>
<xsl:apply-templates select="AddressDetails/Address" mode="det"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"/>
</AddressSegment>
</xsl:copy>
</xsl:template>
<xsl:template match="Address" mode="det">
<AddressDetails StartDate="{../#StartDate}" Type="{../#Type}">
<AddressRef ReferedID="{generate-id()}"/>
</AddressDetails>
</xsl:template>
<xsl:template match="Address">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ID ID="{generate-id()}"/>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
transforms the input
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
with Saxon 6.5.5 into the output
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d0e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d0e7"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d0e11"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d0e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d0e7"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d0e11"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</ContactDetails>
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" />
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"
mode="ref" />
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails/Address">
<AddressRef ReferedID="{generate-id()}" />
</xsl:template>
<xsl:template match="AddressDetails/Address" mode="ref">
<xsl:copy>
<xsl:apply-templates select="#*" />
<ID ID="{generate-id(../*)}" />
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
On this input:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Produces the desired result:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e7" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e11" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID ID="d1e7" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID ID="d1e11" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Note the use of the Identity Transform, the most fundamental transformation.
Create Unique IDs and reference them in the same document
If you need to create unique ID for an element, use thegenerate-id() function. Do note that this function generates a unique identifier for a given element in the input document. Therefore if you call the function on the same element, you'll always obtain the same ID. This is really what you want.
For simplicity, in the following example transform, I've applied the templates to AddressDetails two times, each time with a different mode.
Do note the correct generation and reference of the id in the output by applying the generate-id() function on the AddressDetails node.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" mode="contact"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails" mode="segment"/>
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails" mode="contact">
<xsl:copy>
<xsl:copy-of select="#*"/>
<AddressRef ReferedID="{generate-id(.)}"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AddressDetails" mode="segment">
<Address>
<ID ID="{generate-id(.)}"/>
<xsl:copy-of select="Address/*"/>
</Address>
</xsl:template>
</xsl:stylesheet>
When applied to the input provided in the question, returns:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e13"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e23"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d1e13"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d1e23"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</Application>

Identifying unique subtrees in XSL

I have some XML that looks like this:
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
I have some XSL that I'm using to generate Java code from this XML. Previously I've been making a key, then generating a Java class for each group.
<xsl:key name="groupsByName" match="//group" use="#name"/>
....
<xsl:for-each select="//group[generate-id(.) = generate-id(key('groupsByName',#name)[1])]">
<xsl:call-template name="class-for-group"/>
</xsl:for-each>
All was well. Now, I've discovered that some messages have groups using the same name as groups present elsewhere, but missing one of the fields. To continue the example XML from above:
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
A group named "foo" is present, but it's missing the field with name "action".
What I'd like to do is to generate a Java class for each unique subtree. Is this possible? I can't work out what the xsl:key for that would look like. The closest idea I've had is
<xsl:key name="groupsKey" match="//group" use="concat(#name,count(*))"/>
which works for the case in the example above, but is hardly elegant. If there were instead two groups named "foo" with the same number (but different types) of fields, it would fail, so it's not actually a solution.
To be clear, the ideal key (or whatever alternative) would end up calling the template only once for the "peter" and "wendy" cases above, once for the "nana" case and again once for this case:
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
...because the fields within the group are different to those in the other cases. My key above doesn't cover this case. Is there a way to do so?
This transformation fulfills the requirements:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kGroupByType" match="group"
use="#type"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:call-template name="makeType"/>
</xsl:copy>
</xsl:template>
<xsl:template mode="pass2"
match="group[generate-id()
=
generate-id(key('kGroupByType',#type)[1])
]
">
class <xsl:value-of select="concat(#name, '|', #type)"/>
</xsl:template>
<xsl:template name="makeType">
<xsl:attribute name="type">
<xsl:text>(</xsl:text>
<xsl:for-each select="*">
<xsl:value-of select="#type"/>
<xsl:if test="not(position()=last())">+</xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document (with all additions):
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
the wanted result is produced:
class foo|(integer+integer+integer)
class foo|(integer+integer)
class foo|(string+integer+integer)
It is left as an exercise to the reader to further adjust this to produce valid names in one's PL, and also to make this work with structures of unlimited nestedness (which I may do in another answer -- however, we need a more precise definition for this more general provlem).