Optimize XSLT code by eliminating repeating code - xslt

This XSLT transformation works, but I am repeating the same code multiple times, which makes it very redundant!
How can I optimize this?
<xsl:for-each select="RVWT">
<xsl:variable name="rvwt" select="tokenize(., '\|')"/>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">
<xsl:value-of select="$rvwt[1]"/>
</Text>
<TextSourceTitle>
<xsl:value-of select="normalize-space(substring($rvwt[2], 3))"/>
</TextSourceTitle>
</xsl:for-each>
<xsl:if test="not(RVWT)">
<xsl:for-each select="RVW">
<xsl:variable name="rvwt" select="tokenize(., '\|')"/>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">
<xsl:value-of select="$rvwt[1]"/>
</Text>
<TextSourceTitle>
<xsl:value-of select="normalize-space(substring($rvwt[2], 3))"/>
</TextSourceTitle>
</xsl:for-each>
</xsl:if>
Thanks!

I. I would use the best that XSLT 2.0 can offer: creating a function:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:sequence select=
"my:Extract(RVW[not(../RVWT)] | RVWT)"/>
</xsl:template>
<xsl:function name="my:Extract">
<xsl:param name="pItems" as="item()+"/>
<xsl:for-each select="$pItems">
<xsl:variable name="vItemTokens" select="tokenize(., '\|')"/>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">
<xsl:value-of select="$vItemTokens[1]"/>
</Text>
<TextSourceTitle>
<xsl:value-of select="normalize-space(substring($vItemTokens[2], 3))"/>
</TextSourceTitle>
</xsl:for-each>
</xsl:function>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<t>
<RVWT>a|bbbTail|c</RVWT>
<RVWT>d|eeeTail|f</RVWT>
<RVWT>g|hhhTail|i</RVWT>
<RVW>p|qqqTail|r</RVW>
</t>
the wanted, correct result is produced:
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">a</Text>
<TextSourceTitle>bTail</TextSourceTitle>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">d</Text>
<TextSourceTitle>eTail</TextSourceTitle>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">g</Text>
<TextSourceTitle>hTail</TextSourceTitle>
when applied on this document:
<t>
<RVWTX>a|bbbTail|c</RVWTX>
<RVWTX>d|eeeTail|f</RVWTX>
<RVWTX>g|hhhTail|i</RVWTX>
<RVW>p|qqqTail|r</RVW>
</t>
again the wanted, correct result is produced:
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">p</Text>
<TextSourceTitle>qTail</TextSourceTitle>
II. Do note:
With this approach we have the added benefit that the function accepts any sequence of items, not only elements.
For example, one could call the function like this:
my:Extract(('a|bbbTail|c', 'd|eeeTail|f'))
and still get the wanted result:
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">a</Text>
<TextSourceTitle>bTail</TextSourceTitle>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">d</Text>
<TextSourceTitle>eTail</TextSourceTitle>

You can write a template
<xsl:template match="RVWT | RVW">
<xsl:variable name="rvwt" select="tokenize(., '\|')"/>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">
<xsl:value-of select="$rvwt[1]"/>
</Text>
<TextSourceTitle>
<xsl:value-of select="normalize-space(substring($rvwt[2], 3))"/>
</TextSourceTitle>
</xsl:template>
and then in the parent you process <xsl:apply-templates select="if (RVWT) then RVWT else RVW"/>.

I am guessing you could do:
<xsl:for-each select="RVWT | RVW[not(../RVWT)]">
<xsl:variable name="rvwt" select="tokenize(., '\|')"/>
<TextTypeCode>08</TextTypeCode>
<Text textformat="05">
<xsl:value-of select="$rvwt[1]"/>
</Text>
<TextSourceTitle>
<xsl:value-of select="normalize-space(substring($rvwt[2], 3))"/>
</TextSourceTitle>
</xsl:for-each>

Related

Some kind of bubble sort in XSLT

I want to sort all the <text> elements by the value of the attribute top.
However an element should only be sorted if its previous sibling has a value of top that exceeds its own by 2 or more units.
For example, the following elements
<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>
<text top="35">text 5</text>
<text top="40">text 6</text>
should be transformed to:
<text top="35">text 5</text>
<text top="40">text 6</text>
<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>
So that the group:
<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>
remains as is after sorting.
I only use XSLT from time to time and only know the usual sorting approach:
<xsl:for-each select="text">
<xsl:sort select="#top" />
<xsl:copy>
<xsl:copy-of select="./node()|./#*" />
</xsl:copy>
</xsl:for-each>
But the result I want to achieve would require some kind of bubble sort.
Not sure whether it's doable with pure XSLT.
I have an XSLT 2.0 processor.
I wonder whether in XSLT 2/3 it can just be done with an adequate group-ending-with pattern:
<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:param name="limit" as="xs:integer" select="1"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="root">
<xsl:for-each-group select="text" group-ending-with="text[abs(xs:decimal(following-sibling::text[1]/#top) - xs:decimal(#top)) > $limit]">
<xsl:sort select="min(current-group()/#top/xs:decimal(.))"/>
<xsl:sequence select="current-group()"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Based on the much simplified XQuery code
for tumbling window $group in root/text
start when true()
end $e next $ne when abs(xs:decimal($ne/#top) - xs:decimal($e/#top)) > 1
order by min($group/#top/xs:decimal(.))
return
$group
As I undertand the requeriments are grouping and then sorting. Do note that it is assumed that groups which their elements have less than 2 units of increment are sorted among the others groups taking only the minimum into account (meaning that groups don't overlap).
This stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="*[text]">
<xsl:for-each-group
select="text"
group-adjacent="boolean(
(preceding-sibling::text[1]
|following-sibling::text[1])
[abs(#top - current()/#top) < 2])">
<xsl:sort select="min(#top)"/>
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:copy-of select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:perform-sort select="current-group()">
<xsl:sort select="#top" data-type="number"/>
</xsl:perform-sort>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Output:
<text top="35">text 5</text>
<text top="40">text 6</text>
<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>
Test it in here
EDIT: not assuming only increasing sequence with abs() function.

Creating a JSON from xml with date and time logic templates

I'm trying to create a JSON from a XML which has messages and each message has its date/time.
Below is the XML
<message>
<messageText heading="Temporary Maintenance Message 1">test message1</messageText>
<displayScheduleContainer>
<startDate>22/05/2019</startDate>
<startTimeHrs>12</startTimeHrs>
<startTimeMins>45</startTimeMins>
<noEndDate>true</noEndDate>
</displayScheduleContainer>
</message>
<message>
<messageText heading="Temporary Maintenance Message 1">test message2</messageText>
<displayScheduleContainer>
<startDate>22/06/2019</startDate>
<startTimeHrs>12</startTimeHrs>
<startTimeMins>45</startTimeMins>
<noEndDate>true</noEndDate>
</displayScheduleContainer>
</message>
The logic inside XSLT reads the date and time to activate the message
<xsl:for-each select="xalan:nodeset($messageData)/activeMessage/message">
<xsl:variable name="variableN">
<xsl:call-template name="jsonMsg" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$variableN = 'true'">
<xsl:copy-of select="messageText/text()" />
<xsl:if test="position() < last()">,</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:template name="jsonMsg">
<xsl:choose>
<xsl:when test="displayScheduleContainer/noEndDate = 'true'">
<xsl:variable name="messageInDateTime">
<xsl:call-template name="noEndDateTemplate">
<xsl:with-param name="startDateTime"
select="concat(displayScheduleContainer/startDate, ' ', displayScheduleContainer/startTimeHrs, ':', displayScheduleContainer/startTimeMins)" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$messageInDateTime" />
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="noEndDateTemplate">
<xsl:param name="startDateTime" />
<xsl:variable name="sdf"
select="java:text.SimpleDateFormat.new('dd/MM/yyyy hh:mm')" />
<xsl:variable name="currentDateTime" select="java:util.Date.new()" />
<xsl:choose>
<xsl:when
test="java:compareTo(java:parse($sdf, $startDateTime), $currentDateTime) < 0">
<xsl:text>true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>false</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The problem i'm facing here is if the last value is false, I end up getting the comma at the end. As i'm checking for the last position and adding the comma. Due to this the whole JSON is broken. In this case it adds the comma because i'm displaying the text only if it is true.
"message": ["test message1", ]
I'm using XSLT 1.0
Instead of xsl:choose, append a predicate to your select expression. Here's a simplified example:
XML
<messages>
<message>
<messageText>test message1</messageText>
<displayScheduleContainer>
<noEndDate>true</noEndDate>
</displayScheduleContainer>
</message>
<message>
<messageText>test message2</messageText>
<displayScheduleContainer>
<noEndDate>true</noEndDate>
</displayScheduleContainer>
</message>
<message>
<messageText>test message3</messageText>
<displayScheduleContainer>
<noEndDate>false</noEndDate>
</displayScheduleContainer>
</message>
</messages>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/messages">
<xsl:for-each select="message[displayScheduleContainer/noEndDate = 'true']">
<xsl:value-of select="messageText" />
<xsl:if test="position() < last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result
"test message1,test message2"
Added:
If the test is too complex to fit in a predicate, do the transformation in two passes. Here, again, a simplified example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/messages">
<!-- first pass -->
<xsl:variable name="eligible-messages">
<xsl:for-each select="message">
<xsl:if test="displayScheduleContainer/noEndDate = 'true'">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<xsl:for-each select="exsl:node-set($eligible-messages)/message">
<xsl:value-of select="messageText" />
<xsl:if test="position() < last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Replace the test in:
<xsl:if test="displayScheduleContainer/noEndDate = 'true'">
with the test/s you want to perform.

Looping Element to Single Element in XSLT

I need to convert the following content
<QTLS_ITEM>
<ID>123</ID>
<ID1>1345</ID1>
<SERAIL_NUMBER>1026977­04257</SERAIL_NUMBER>
<PROD_NAME>upgrade</PROD_NAME>
</QTLS_ITEM>
<QTLS_ITEM>
<ID>123</ID>
<ID1>1345</ID1>
<SERAIL_NUMBER>1026977­04257</SERAIL_NUMBER>
<PROD_NAME>Plug­in</PROD_NAME>
</QTLS_ITEM>
<QTLS_ITEM>
<ID>123</ID>
<ID1>1345</ID1>
<SERAIL_NUMBER>1026977­04257</SERAIL_NUMBER>
<PROD_NAME>License</PROD_NAME>
</QTLS_ITEM>
This is a looping element type.<QTLS_ITEM> is a repeating element. For each element I have to concatenate those two fields and get the value as below.
I want to transform it into a single Element like
<Item_description>
1026977­04257 upgrade
1026977­04257 Plug­in
1026977­04257 License
<Item_Description>
Which means I need to concatenate both the SERAIL_NUMBER and PROD_NAME.
Can anyone help on this?
<xsl:template name="string-join">
<xsl:param name="nodes"/>
<xsl:param name="delimiter"/>
<xsl:for-each select="$nodes">
<xsl:value-of select="."/>
<xsl:if test="$nodes[position()!=last()-1]">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
I am using this to get the Serial numbers separated by comma. But I want to concatenate and get the result in the above format.
The answer for my case is
<xsl:template name="join">
<xsl:param name="list"/>
<xsl:param name="separator"/>
<xsl:for-each select="db:SERAIL_NUMBER | db:PROD_NAME">($list value)
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$separator"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
The input that you show us is not well-formed XML, because it does not have a single root element. Given a well-formed XML input such as:
XML
<root>
<QTLS_ITEM>
<SERAIL_NUMBER>102697704257</SERAIL_NUMBER>
<PROD_NAME>upgrade</PROD_NAME>
</QTLS_ITEM>
<QTLS_ITEM>
<SERAIL_NUMBER>102697704257</SERAIL_NUMBER>
<PROD_NAME>Plugin</PROD_NAME>
</QTLS_ITEM>
<QTLS_ITEM>
<SERAIL_NUMBER>102697704257</SERAIL_NUMBER>
<PROD_NAME>License</PROD_NAME>
</QTLS_ITEM>
</root>
you can do simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<Item_description>
<xsl:for-each select="QTLS_ITEM">
<xsl:value-of select="SERAIL_NUMBER, PROD_NAME" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</Item_description>
</xsl:template>
</xsl:stylesheet>
to get:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Item_description>102697704257 upgrade
102697704257 Plugin
102697704257 License
</Item_description>
This has worked out in my case
<xsl:template name="join">
<xsl:param name="list"/>
<xsl:param name="separator"/>
<xsl:for-each select="db:SERAIL_NUMBER|db:PROD_NAME">($list value)
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$separator"/>
</xsl:if>
</xsl:for-each>
</xsl:template>

XSLT -One output in SSIS

I have a XML file when I use SSIS XML as Source it is generating multiple outputs. I am using XML task to design XSLT for getting single output.
Following is the XML. I need only single output in SSIS. How to write XSLT for the same?
<?xml version="1.0" encoding="utf-8"?>
<Test>
<iden type="shiftid">100</iden >
<iden type="trailid">25</iden >
<TestHeader>
<SequenceNum>111</SequenceNum>
<TestType>CONTAINER</TestType>
<hub role="origin">
<name />
<hubId>030</hubId>
<hubType>New</hubType>
</hub>
<hub role="Dest">
<name />
<hubId>0757</hubId>
<hubType>hublet</hubType>
</hub>
<TestItem>
<lineNumber>1</lineNumber>
<container>1005</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>TRANS-SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
<TestItem>
<lineNumber>2</lineNumber>
<container>1005</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
<TestItem>
<lineNumber>1</lineNumber>
<container>1006</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
</TestHeader>
<TestContainer type="RAPP" id="1005">
<uom hb="m3" type="board">10.0729</uom>
</TestContainer>
<TestContainer type="RAMP" id="1006">
<uom hb="m3" type="board">10.0729</uom>
</TestContainer>
</Test>
I have struggled and comeup with XSLT as follows
<?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:template name="Iden" match="Test">
<xsl:for-each select="../iden">
<xsl:choose>
<xsl:when test="#type='shiftid'">
<ShipmentID>
<xsl:value-of select="."/>
</ShipmentID>
</xsl:when>
<xsl:when test="#type='trailid'">
<TrailerID>
<xsl:value-of select="."/>
</TrailerID>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template name="Item" match="Test">
<xsl:param name="MatchContainer"/>
<xsl:for-each select="//TestItem">
<xsl:if test="$MatchContainer=container">
<Testitem>
<lineNumber>
<xsl:value-of select="lineNumber"/>
</lineNumber>
<containerTestitemID>
<xsl:value-of select="container"/>
</containerTestitemID>
<ConditionCode>
<xsl:value-of select="conditionCode"/>
</ConditionCode>
<Originator>
<xsl:value-of select="hub[#role='originator']/hubId"/>
</Originator>
<OriginatorType>
<xsl:value-of select="hub[#role='originator']/hubType"/>
</OriginatorType>
</Testitem>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="container" match="Test">
<xsl:for-each select="../TestContainer">
<Testcontainerlist>
<ContainerType>
<xsl:value-of select="#type"/>
</ContainerType>
<ContainerID>
<xsl:value-of select="#id"/>
</ContainerID>
<Volume>
<xsl:value-of select="uom"/>
</Volume>
<VolumeUOM>
<xsl:value-of select="volume/#hb"/>
</VolumeUOM>
<VolumeType>
<xsl:value-of select="volume/#type"/>
</VolumeType>
<xsl:call-template name="Item">
<xsl:with-param name="MatchContainer" select="#id"/>
</xsl:call-template>
</Testcontainerlist>
</xsl:for-each>
</xsl:template>
<xsl:template name="Header" match="Test">
<xsl:for-each select="TestHeader">
<Test>
<xsl:call-template name="Iden">
</xsl:call-template>
<dropSequenceNumber>
<xsl:value-of select="SequenceNum"/>
</dropSequenceNumber>
<TestType>
<xsl:value-of select="TestType"/>
</TestType>
<OriginID>
<xsl:value-of select="hub[#role='origin']/hubId"/>
</OriginID>
<DestinationID>
<xsl:value-of select="hub[#role='Dest']/hubId"/>
</DestinationID>
<OriginType>
<xsl:value-of select="hub[#role='origin']/hubType"/>
</OriginType>
<DestinationType>
<xsl:value-of select="hub[#role='Dest']/hubType"/>
</DestinationType>
<xsl:call-template name="container">
</xsl:call-template>
</Test>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
My desired output should be as follows
<?xml version="1.0" encoding="utf-8"?>
<Test>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAPP</ContainerType>
<ContainerID>1005</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>1</lineNumber>
<containerTestitemID>1005</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>TRANS-SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAPP</ContainerType>
<ContainerID>1005</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>2</lineNumber>
<containerTestitemID>1005</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAMP</ContainerType>
<ContainerID>1006</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>1</lineNumber>
<containerTestitemID>1006</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
</Test>

XSLT 1.0 Split comma seperated string into named nodes

A customer supplied XML contains the delivery address as a comma separated string, I would like to split this string into named nodes using XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
Company, Streetaddress 20, 1234 AA, City
</text>
</root>
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress 20</ADDRESS>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</text>
</root>
I tried several recursive templates for XSLT 1.0 which do a fine job splitting but the resulting nodes are identically named.
If possible, how can this be achieved using XSLT 1.0?
Does it have to be a recursive template? How about a straight-forward chain of substring-before and substring-after like this:
<xsl:template match="text">
<xsl:copy>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($s1, ','))"/>
</ADDRESS>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</xsl:copy>
</xsl:template>
For the fun of it, here is a generic version using a recursive template.
<xsl:template match="text">
<xsl:copy>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="'COMPANY,ADDRESS,ZIPCODE,CITY'"/>
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="parse-comma-separated">
<xsl:param name="elements"/>
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($elements, ',')">
<xsl:element name="{normalize-space(substring-before($elements, ','))}">
<xsl:value-of select="normalize-space(substring-before($text, ','))"/>
</xsl:element>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="substring-after($elements, ',')"/>
<xsl:with-param name="text" select="substring-after($text, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{normalize-space($elements)}">
<xsl:value-of select="normalize-space($text)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
As the supplied XML only had the address as single node (Streettaddress 20) I added a second variable to split the address string into streetaddress and housenumber.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="text">
<root>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<xsl:variable name="address_temp" select="normalize-space(substring-before($s1, ','))"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($address_temp, ' '))"/>
</ADDRESS>
<HOUSENUMBER>
<xsl:value-of select="normalize-space(substring-after($address_temp, ' '))"/>
</HOUSENUMBER>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</root>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress</ADDRESS>
<HOUSENUMBER>20</HOUSENUMBER>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</root>