Map empty to a node with xslt - 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" />

Related

Read xml file to capture the values from <value> tags

We have a requirement to read the xml file and capture the EmployeeName and EmailId values from tags to create the output as xml file.
The first tag always represents EmployeeName and 5th tag always represents EmailId.
Need to capture the values present in the row/value....
The input xml file as follows:
<?xml version="1.0" encoding="utf-8"?>
<dataset xmlns="http://developer.net.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
<!--
<dataset
xmlns="http://developer.net.com/schemas/xmldata/1/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://developer.net.com/schemas/xmldata/1/ xmldata.xsd"
>
-->
<metadata>
<item length="20" type="xs:string" name="EmployeeName"/>
<item length="4" type="xs:string" name="Full/Part Time Code"/>
<item type="xs:dateTime" name="Hire Date"/>
<item type="xs:dateTime" name="Termination Date"/>
<item length="30" type="xs:string" name="EmailID"/>
<item length="30" type="xs:string" name="State"/>
</metadata>
<data>
<row>
<value>JOSEPH</value>
<value>F</value>
<value>1979-04-19T00:00:00</value>
<value>2007-08-27T00:00:00</value>
<value>joseph.Tim#gmail.com</value>
<value>TX</value>
</row>
<row>
<value>NANDY</value>
<value>F</value>
<value>1979-04-19T00:00:00</value>
<value>2007-08-27T00:00:00</value>
<value>Nandy123#gmailcom</value>
<value>PA</value>
</row>
</data>
</dataset>
The Expected Ouput as below:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:EMPLOYEEDETAILS xmlns:ns0="http://net.com/EmployeeDetails">
<Records>
<EmployeeName>JOSEPH</EmployeeName>
<EmailId>joseph.Tim#gmail.com</EmailId>
</Records>
<Records>
<EmployeeName>NANDY</EmployeeName>
<EmailId>Nandy123#gmailcom</EmailId>
</Records>
</ns0:EMPLOYEEDETAILS>
Thanks,
Ravi
Please try the XSLT below. You need to make additional changes for matching namespaces according to the input XML and adding the root node <EMPLOYEEDETAILS> in the output.
EDIT: XSLT solution updated to handle the namespace issue. Root node <ns0:EMPLOYEEDETAILS> included in the solution.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://developer.net.com/schemas/xmldata/1/">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:param name="param-name" select="1" />
<xsl:param name="param-email" select="5" />
<xsl:template match="/">
<ns0:EMPLOYEEDETAILS>
<xsl:for-each select="//ns0:data/ns0:row">
<Records>
<xsl:for-each select="ns0:value">
<xsl:choose>
<xsl:when test="position() = $param-name">
<EmployeeName>
<xsl:value-of select="." />
</EmployeeName>
</xsl:when>
<xsl:when test="position() = $param-email">
<EmailId>
<xsl:value-of select="." />
</EmailId>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</Records>
</xsl:for-each>
</ns0:EMPLOYEEDETAILS>
</xsl:template>
</xsl:stylesheet>
Output
<ns0:EMPLOYEEDETAILS xmlns:ns0="http://developer.net.com/schemas/xmldata/1/">
<Records>
<EmployeeName>JOSEPH</EmployeeName>
<EmailId>joseph.Tim#gmail.com</EmailId>
</Records>
<Records>
<EmployeeName>NANDY</EmployeeName>
<EmailId>Nandy123#gmailcom</EmailId>
</Records>
</ns0:EMPLOYEEDETAILS>

Transformation of text file into an xml using datapower

I want to transfer a non-xml text file delimited by '|' characters into an xml using Datapower.
Following is file (sample1)
10|20003|24/23/25|23890
Now i have to break this into the following XML
<ResponseType>
<ResCode>10</ResCode>
<Id>20003</Id>
<SoftCode>24/23/25</SoftCode>
<StatusCode>23890</StatusCode>
</ResponseType>
What I did was following--
1>Create a Transform action in the service that will be receiving non-XML requests.
2>Select "Use XSLT specified in this action on a non-XML message" to specify that this is a Binary Transform.
3>Upload the following stylesheet as the Processing Control File.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
version="1.0">
<dp:input-mapping href="sample1.ffd" type="ffd"/>
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:copy-of select="ResponseType"/>
<xsl:call-template name="str:tokenize">
<xsl:with-param name="string" select="string" />
</xsl:call-template>
</xsl:template>
<xsl:template name="str:tokenize">
<xsl:with-param name="string" select="">
str:tokenize('string', '|')
</xsl:with param>
</xsl:template>
</xsl:stylesheet>
and here is my sample1.ffd(which I have uploaded in my local:// directory in Datapower
<File name="ResponseType">
<!-- capture all data into this tag -->
<Field name="ResCode/Id/SoftCode/StatusCode" />
</File>
But I am not getting desired output , I think my xslt is quite wrong
What can I do do to get desired output?
In DataPower using FFD the following should work:
1) Add the FFD file (below one of my old education samples):
<File name="CSVFILE">
<Group name="CSVLine" minOccurs="0" maxOccurs="unbounded" delim="\n">
<Field name="id"/>
<Field name="fname" delim=","/>
<Field name="lname" delim=","/>
<Field name="title" delim=","/>
<Field name="dept" delim=","/>
<Field name="org"/>
</Group>
</File>
2) Add the XSLT (this one simply copies the FFD transformed XML to output):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
version="1.0">
<dp:input-mapping href="CSVFILE.FFD" type="ffd"/>
<!-- This stylesheet copies the input to the output -->
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
3) Send a message:
1,Anders,Wasen,B2B Architect,DataPower Dev,Enfo Zystems
2,Jean-Luc,Piccard,Captain,USS Enterprise,Star Fleet
4) This will result in the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<CSVFILE>
<CSVLine>
<id>1</id>
<fname>Anders</fname>
<lname>Wasen</lname>
<title>B2B Architect</title>
<dept>DataPower Dev,Enfo Zystems</dept>
<org/>
</CSVLine>
<CSVLine>
<id>2</id>
<fname>Jean-Luc</fname>
<lname>Piccard</lname>
<title>Captain</title>
<dept>USS Enterprise,Star Fleet</dept>
<org/>
</CSVLine>
</CSVFILE>
Make sure that you change the XSLT Transform action into "Transform Binary" and set Request Type to "non-xml", else it will not work!
Hope this will help you! :)
I'm not sure how IBM Datapower might solve this problem, but for XSLT, you would at least wrap your input in a XML element:
<Whatever>
10|20003|24/23/25|23890
</Whatever>
And then you could go on with a transformation like follows. The hard part is splitting your text input. In XSLT 1.0, there is no function available for that, so you need a recursive template.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxml="urn:schemas-microsoft-com:xslt" version="1.0" exclude-result-prefixes="msxml">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<xsl:variable name="tokenized">
<items>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="//text()" />
</xsl:call-template>
</items>
</xsl:variable>
<ResponseType>
<ResCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[1]/text()" />
</ResCode>
<Id>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[2]/text()" />
</Id>
<SoftCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[3]/text()" />
</SoftCode>
<StatusCode>
<xsl:copy-of select="msxml:node-set($tokenized)/items/item[4]/text()" />
</StatusCode>
</ResponseType>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="string" />
<xsl:variable name="item" select="normalize-space( substring-before( concat( $string, '|'), '|'))" />
<xsl:if test="$item">
<item>
<xsl:value-of select="$item" />
</item>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="substring-after($string,'|')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Select value of element which is referenced by other

I have (this is an example) the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<list>
<toot id="1">
<value>A</value>
</toot>
<toot id="2">
<value>B</value>
</toot>
<toot id="3">
<value>C</value>
</toot>
<toot id="4">
<value>D</value>
</toot>
</list>
<otherlist>
<foo>
<value ref="2" />
</foo>
<foo>
<value ref="3" />
</foo>
</otherlist>
</body>
And the following XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/body">
<xsl:apply-templates select="otherlist"/>
</xsl:template>
<xsl:template match="otherlist">
<xsl:for-each select="foo">
<result>
<value><xsl:value-of select="/body/list/toot[#id=value/#ref]/value" /></value><!-- This is the important -->
<ref><xsl:value-of select="value/#ref" /></ref>
</result>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And this is the result when it make/transform the xml:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value/>
<ref>2</ref>
</result>
<result>
<value/>
<ref>3</ref>
</result>
And the problem is that is empty. What I wanna get is:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value>B</value>
<ref>2</ref>
</result>
<result>
<value>C</value>
<ref>3</ref>
</result>
I think the problem is the XPath /body/list/toot[#id=value/#ref]/value specifically the condition [#id=value/#ref]. Is not correct it? How to use a value of other element witch is reference by the current one?
Yes the problem is in XPath where the context is changing so you are actually looking for toot element which has #id attribute and value child with #ref attribute (which is actually child of foo) and these two are equal.
You can employ current() function to make it working
<xsl:value-of select="/body/list/toot[#id=current()/value/#ref]/value"/>
Or you can store value of #ref into a variable and use this variable in predicate
<xsl:variable name="tmpRef" select="value/#ref" />
<xsl:value-of select="/body/list/toot[#id=$tmpRef]/value"/>

Get distinct values from xml

My sample xml looks below: I need to get the distinct states from xml. I am using xslt 1.0 in vs 2010 editor.
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
My xslt looks like below:
<?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"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="disst">
<xsl:for-each select="//states/node[contains(value,1)]/state[generate-id()=generate-id(key('st',.)[1])]" >
<xsl:choose>
<xsl:when test="(position() != 1)">
<xsl:value-of select="concat(', ',.)" disable-output-escaping="yes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Output: DE,NJ,NY
My above xml looks good for the above test xml.
If I change the xml as below:
<?xml version="1.0" encoding="utf-8" ?>
<states>
<node>
<value>2</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>DE</state>
</node>
<node>
<value>1</value>
<state>NJ</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
<node>
<value>1</value>
<state>NY</state>
</node>
</states>
It in not picking the state DE. Can any one suggest the suitable solution.Thanks in advance.
I need to find out the distinct states from the xml.
The problem here is your use of a predicate in your Muenchian grouping XPath:
[contains(value,1)]
This will often make Muenchian grouping fail to find all of the available distinct values. Instead, you should add the predicate to the key:
<xsl:key name="st" match="//states/node[contains(value, 1)]/state" use="." />
Alternatively, you can apply the predicate inside the grouping statement:
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, 1)][1])]" />
Full XSLT (with some improvements):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:user="urn:my-scripts">
<xsl:output method="text" indent="yes"/>
<xsl:key name="st" match="//states/node/state" use="." />
<xsl:variable name="a" select="1" />
<xsl:variable name="disst">
<xsl:apply-templates
select="//states/node
/state[generate-id() =
generate-id(key('st',.)[contains(../value, $a)][1])]" />
</xsl:variable>
<xsl:template match="state">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select ="." disable-output-escaping="yes" />
</xsl:template>
<xsl:template match="/" >
<xsl:value-of disable-output-escaping="yes" select="$disst"/>
</xsl:template>
</xsl:stylesheet>
Result when run on your sample XML:
DE,NJ,NY

xml to xml using xslt : Recursive matching & creating hierarchy like tree

I am completely new to xslt. Please help me to write style sheet.I have input xml like this
Input XML:
<elements>
<e1>
<pid>1</pid>
<cid>2</cid>
</e1>
<e1>
<pid>1</pid>
<cid>3</cid>
</e1>
<e1>
<pid>2</pid>
<cid>4</cid>
</e1>
</elements>
Desired XML:
<tree>
<unit id="1">
<unit id="2">
<unit id="4">
<data></data>
</unit>
<data></data>
</unit>
<unit id="3">
<data></data>
</unit>
<data></data>
</unit>
</tree>
I feel this should be really easy but I'm struggling to find information about how to do this. My XSLT knowledge isn't great.
I'm not 100% sure how you want the XSLT to determine from that input that the top id is 1 (is it because it's the only pid value with no corresponding cid values, or is it always 1?). Nonetheless, this should do the job:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kItemsByC" match="e1" use="cid" />
<xsl:key name="kItemsByP" match="e1" use="pid" />
<xsl:template match="/">
<tree>
<xsl:call-template name="Unit">
<!-- This will be the value of the <pid> that has no <cid> references to
it (assuming there is only one top-level <pid>) -->
<xsl:with-param name="id"
select="string(/elements/e1/pid[not(key('kItemsByC', .))])" />
</xsl:call-template>
</tree>
</xsl:template>
<xsl:template match="e1" name="Unit">
<xsl:param name="id" select="cid" />
<unit id="{$id}">
<xsl:apply-templates select="key('kItemsByP', $id)" />
<data />
</unit>
</xsl:template>
</xsl:stylesheet>
When this is run on your sample input, this produces:
<tree>
<unit id="1">
<unit id="2">
<unit id="4">
<data />
</unit>
<data />
</unit>
<unit id="3">
<data />
</unit>
<data />
</unit>
</tree>
Note: The above XSLT has logic to attempt to dynamically locate the top-level ID. If it can be assumed that the top-level unit will always have ID 1, then one key and the above XSLT's (somewhat) complicated formula can be eliminated:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kItemsByP" match="e1" use="pid" />
<xsl:template match="/">
<tree>
<xsl:call-template name="Unit">
<xsl:with-param name="id" select="1" />
</xsl:call-template>
</tree>
</xsl:template>
<xsl:template match="e1" name="Unit">
<xsl:param name="id" select="cid" />
<unit id="{$id}">
<xsl:apply-templates select="key('kItemsByP', $id)" />
<data />
</unit>
</xsl:template>
</xsl:stylesheet>
This also produces the requested output when run on your sample input.
Ah, after reading JLRishe I think I get it: "pid" means "parent ID", "cid" means "child ID", and e1 represents a parent-child relationship. Brilliant detective work, I would never have worked that out for myself.
The basic model is that when you are positioned on a parent element you do apply-templates to its children. This applies just as well if the parent/child relationships are represented by primary/foreign keys as when they are represented using the XML hierarchy. So the essence is:
<xsl:template match="e1">
<unit id="{pid}">
<xsl:apply-templates select="//e1[pid=current()/cid]"/>
<data/>
</unit>
</xsl:template>
which is essentially JLRishe's solution except he has added an optimization using keys.