Multi-document xslt, value-of with addition misbehaving - xslt

I am trying to write something for another individual and im stuck on the final part of the stylesheet.
We have two XML Documents:
TestXML.xml:
<rootNode>
<header>
<title agg="sum">1</title>
<records agg="sum">10</records>
<number agg="min">5</number>
</header>
</rootNode>
and TestXMLTwo.xml:
<rootNode>
<header>
<title agg="sum">2</title>
<records agg="sum">20</records>
<number agg="min">15</number>
</header>
</rootNode>
Where if the node has its agg attribute equal to 'sum' we combine the values of the two documents nodes. I am doing this using:
<xsl:param name="InputFileOne">[EditedOut]\TestXML.xml</xsl:param>
<xsl:param name="InputFileTwo">[EditedOut]\TestXMLTwo.xml</xsl:param>
<xsl:template match="node()|#*">
<xsl:call-template name="ConcatFiles"/>
</xsl:template>
<xsl:template name="ConcatFiles">
<xsl:variable name="tempStoreDocOne" select ="document($InputFileOne)/rootNode/header" />
<xsl:variable name="tempStoreDocTwo" select ="document($InputFileTwo)/rootNode/header" />
<xsl:element name="rootNode">
<xsl:element name="header">
<xsl:for-each select="$tempStoreDocOne/node()">
<xsl:choose>
<xsl:when test="./#agg = 'sum'">
<xsl:variable name="tempElementDocTwo" select ="$tempStoreDocTwo/."/>
<xsl:element name="{name(.)}">
<xsl:value-of select=". + $tempElementDocTwo"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
However on the line <xsl:value-of select=". + $tempElementDocTwo"/> I just get a value of '22016' for the <title> and 22025 for the <records>. Can someone enlighten me as to where I'm going wrong?

Change <xsl:for-each select="$tempStoreDocOne/node()"> to <xsl:for-each select="$tempStoreDocOne/*">, then add a variable storing the position i.e.
<xsl:variable name="pos" select="position()"/>
inside of the for-each, then change
<xsl:variable name="tempElementDocTwo" select ="$tempStoreDocTwo/."/>
to
<xsl:variable name="tempElementDocTwo" select ="$tempStoreDocTwo/*[$pos]"/>
Currently you are accessing the string value of the complete header element in the second document which is the concatenation of its descendant nodes while you want to access the child element with the same position as the one in the first document.

Here is a much simpler and shorter solution (no explicit conditionals):
<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:param name="pDoc2">
<rootNode>
<header>
<title agg="sum">2</title>
<records agg="sum">20</records>
<number agg="min">15</number>
</header>
</rootNode>
</xsl:param>
<xsl:variable name="vDoc2" select="document('')/*/xsl:param"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="header/*[#agg='sum']">
<title>
<xsl:value-of select=
". + $vDoc2/*/header/*[name()=name(current()) and #agg='sum']"/>
</title>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the first XML document (the second is inlined in the transformation just for convenience):
<rootNode>
<header>
<title agg="sum">1</title>
<records agg="sum">10</records>
<number agg="min">5</number>
</header>
</rootNode>
the wanted, correct result is produced:
<rootNode>
<header>
<title>3</title>
<title>30</title>
<number agg="min">5</number>
</header>
</rootNode>

Related

Add child elements in particular order

I am working on an xml to xml transform via XSLT. I Have the following:
stylesheet.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.org"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"> <!-- xs namespace allows typed functions and parameters -->
<xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*" />
<xsl:param name="other-id" select="Request/Order/OtherId" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" copy-namespaces="no" />
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*|text()|comment()|processing-instruction()">
<xsl:copy />
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="Details">
<xsl:element name="{local-name()}">
<xsl:element name="Signon>
<xsl:element name="SignonDt>2017-01-01</xsl:element>
<xsl:element name="MessageQuantity">3</xsl:element>
<xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="NotificationRq">
<xsl:element name="{local-name()}">
<xsl:element name="RqUID">Test</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Policy/SDDCd" />
<xsl:template match="Policy">
<xsl:element name="{local-name()}">
<xsl:element name="RFDCd">
<xsl:call-template name="getRFDCd" />
</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template name="getRFDCd">
<xsl:choose>
<xsl:when test="contains($other-id, 'RFD 2')">
<xsl:text>AUB</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>CL</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Contact/Addr" >
<xsl:element name="{local-name()}">
<xsl:element name="AddrTypeCd">StreetAddress</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<!-- other templates structured similar to these -->
input.xml
<Request>
<Details>
<NotificationRq>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
</NotificationRq>
<Policy>
<PolNumber>1234567890</PolNumber>
<SDDCd>T35</SDDCd>
</Policy>
<Contact>
<Addr>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
</Request>
output.xml
<Details xmlns="http://test.org">
<Signon>
<SignonDt>2017-01-01</Signon>
<MessageQuantity>3</MessageQuantity>
</Signon>
<NotificationRq>
<RqUId>Test</RqUID>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
</NotificationRq>
<Policy>
<RFDCd>CL</RFDCd>
<PolNumber>1234567890</PolNumber>
</Policy>
<Contact>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
Note: I am using <xsl:element name="{local-name()}"> due to an earlier issue; i am copying source xml which has no namespace to new output which does, and this solution, along with the extra template alongside the identity transform fixed the issue of a namespace attribute being added to all elements
The problem is, this xml is sent downstream where schema validation occurs, and because of this, position of newly created child elements matter. As shown in the snippets above, new child elements are added as the first child of the parent. For some cases this is fine and this is where they should be, but for around half of the created elements, they must appear last(with the odd one or two requiring insertion at a specified position). Still using input.xml as an example, here is how the desired output should look:
desired-output.xml
<Details xmlns="http://test.org">
<NotificationRq>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
<RqUId>Test</RqUID>
</NotificationRq>
<Signon>
<SignonDt>2017-01-01</Signon>
<MessageQuantity>3</MessageQuantity>
</Signon>
<Policy>
<PolNumber>1234567890</PolNumber>
<RFDCd>CL</RFDCd>
</Policy>
<Contact>
<Addr>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
Is there a way to specify where in the existing child order of an element the new child should appear? Is one of my templates causing the insertion order to always be in the first position?
Additional Info: I have seen a few questions on specific order insertion, but they usually seem to be for a sequence of elements, or a series of repeating elements, e.g. how do I insert another author element in a series of author elements, and the solutions tend to make use of a position function to determine if one has looped to the right index, and then insert. The xml I am working with is made up of unique elements which may hold a value or may contain several children(with some of those containing children etc). There are no repeating elements in the xml, so I don't think I can make use of a solution as described above(unless someone knows how to do such a thing for non repeating children of an element). Also, I am using Saxon HE version 9.7.0-8
Why can't you do simply:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.org">
<xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="NotificationRq">
<NotificationRq>
<xsl:apply-templates/>
<RqUId>Test</RqUId>
</NotificationRq>
</xsl:template>
</xsl:stylesheet>

Add mandatory nodes with XSLT

I am facing an xslt/xpath problem and hope someone could help, in a few words here is what I try to achieve.
I have to transform an XML document where some nodes may be missing, these missing nodes are mandatory in the final result. I have the set of mandatory node names available in an xsl:param.
The base document is:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TRANSFORM.xslt"?>
<BEGIN>
<CLIENT>
<NUMBER>0021732561</NUMBER>
<NAME1>John</NAME1>
<NAME2>Connor</NAME2>
</CLIENT>
<PRODUCTS>
<PRODUCT_ID>12</PRODUCT_ID>
<DESCRIPTION>blah blah</DESCRIPTION>
</PRODUCTS>
<PRODUCTS>
<PRODUCT_ID>13</PRODUCT_ID>
<DESCRIPTION>description ...</DESCRIPTION>
</PRODUCTS>
<OPTIONS>
<OPTION_ID>1</OPTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</OPTIONS>
<PROMOTIONS>
<PROMOTION_ID>1</PROMOTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</PROMOTIONS>
</BEGIN>
Here is the stylesheet so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:param name="mandatoryNodes" as="xs:string*" select=" 'PRODUCTS', 'OPTIONS', 'PROMOTIONS' "/>
<xsl:template match="/">
<xsl:apply-templates select="child::node()"/>
</xsl:template>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="BEGIN">
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no node with this name -->
<xsl:if test="count(*[name() = 'current()']) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I tried the transformation in XML Spy, the xsl:iftest failed saying that 'current item is PRODUCTS of type xs:string.
I've tried the same xsl:if outside of a for-each and it seems to work ... what am I missing ?
Inside of <xsl:for-each select="$mandatoryNodes"> the context item is a string but you want to access the primary input document and its nodes so you need to store that document or the template's context node in a variable and use that e.g.
<xsl:template match="BEGIN">
<xsl:variable name="this" select="."/>
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no child node of `BEGIN` with this name -->
<xsl:if test="count($this/*[name() = current()]) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>

search keyword and replace the text in xml file based on external xml file

I have a xml file main.xml with following markup and data.
main.xml
<xml>
<content>
<para>
This is a para.
</para>
<sub para>
This is para.
</sub para>
</content>
</xml>
I have another xml file keyword.xml with list of keywords that we need to find any where in above xml and replace the keyword value.
keyword.xml
<xml>
<keywordList>
<keyword>
<value>para</value>
<replace> paragraph </replace>
</keyword>
<keyword>
<value>is</value>
<replace>IS</replace>
</keyword>
</xml>
Can we do it in xslt so that the output should be
output
<xml>
<content>
<para>
This IS a paragraph.
</para>
<sub para>
This IS paragraph.
</sub para>
</content>
</xml>
Try the following
<?xml version="1.0" encoding="UTF-8"?>
<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:variable name="keywords" select="document('keyword.xml')"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:analyze-string select="." regex="[A-Za-z]+">
<xsl:matching-substring>
<xsl:variable name="repl" select="$keywords//keyword[value = current()]"/>
<xsl:choose>
<xsl:when test="$repl">
<xsl:value-of select="$repl/replace"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="current()"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
Note that the replace value for para includes spaces around the new word, hence the additional spaces:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<content>
<para>
This IS a paragraph .
</para>
<subpara>
This IS paragraph .
</subpara>
</content>
</xml>
This is an XSLT 1.0 solution (of course, can be used with XSLT 2.0, too):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>para</old>
<new> paragraph </new>
</pattern>
<pattern>
<old> is </old>
<new> IS </new>
</pattern>
</my:params>
<xsl:variable name="vrtfPats">
<xsl:for-each select="document('')/*/my:params/*">
<xsl:sort select="string-length(old)"
data-type="number" order="descending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vPats" select=
"ext:node-set($vrtfPats)/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="multiReplace" priority="2">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test= "string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText,
1 + not($vPat) + string-length($vPat/old/node())
)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be well-formed):
<xml>
<content>
<para>
This is a para.
</para>
<sub_para>
This is para.
</sub_para>
</content>
</xml>
the wanted, correct result is produced:
<xml>
<content>
<para>
This IS a paragraph .
</para>
<sub_para>
This IS paragraph .
</sub_para>
</content>
</xml>
Explanation: The text is scanned character by character and the longest possible target string starting at that position in the text is replaced with its specified replacement.

how to re-number filtered elements in result set

XML:
<?xml version="1.0" encoding="utf-8"?>
<book>
<chapter>
<section name="a">...</section>
<section name="b">...</section>
<section name="c">...</section>
<section name="d">...</section>
<section name="e">...</section>
...
</chapter>
<appendix>
<section name="reference">...</section>
</appendix>
</book>
Hi, I want to output all the sections under chapter and appendix nodes. The sections under appendix will be printed out definitely. But not all sections under chapter are allowed to print out, they depend on some external condition (if the section name is in allowed list which passed in from java application).
Also sections under chapter should be output with correct sequence number before section name which looks like the following:
Desired result
b ...
d ...
(which means section a, c, e are filtered out)
My question is how I can produce the above desired output for //chapter/section? Any hints or help is highly appreciated
My XSL:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ex="http://example.com/namespace" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="validChapters" />
<xsl:function name="ex:isValidChapter" as="xs:boolean">
<xsl:param name="str-in" as="xs:string"/>
<xsl:choose>
<xsl:when test="contains($validChapters, $str-in)">
<xsl:sequence select="true()" />
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="false()" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<xsl:apply-templates select="chapter" />
<xsl:apply-templates select="appendix" />
</xsl:template>
<xsl:template match="chapter">
...
<xsl:apply-templates select="section" />
</xsl:template>
<xsl:template match="appendix">
...
<xsl:apply-templates select="section" />
</xsl:template>
<xsl:template match="section">
...
<xsl:apply-templates />
</xsl:template>
<xsl:template match="//chapter/section">
<xsl:if test="ex:isValidChapter(#name)">
<fo:block>
<xsl:number format="1."/>
<xsl:value-of select="#name" />
</fo:block>
<xsl:apply-templates />
</xsl:if>
</xsl:template>
...
</xsl:stylesheet>
The answer is simply to provide a count= attribute to your <xsl:number> instruction:
<xsl:number format="1." count="section[ex:isValidChapter(#name)]"/>
Thus in numbering the sections, only sections that are "valid" will be counted, so the numbering will be correct.
Note also that you need to modify your template that says:
<xsl:template match="/">
<xsl:apply-templates select="chapter" />
since <chapter> is not a child of the root node. (It is a child of the document element, <book>.) E.g.
<xsl:template match="/book">
<xsl:apply-templates select="chapter" />
Also, your function doesn't need a choose/when/otherwise to turn a boolean result into a boolean. It can be shortened to:
<xsl:function name="ex:isValidChapter" as="xs:boolean">
<xsl:param name="str-in" as="xs:string"/>
<xsl:sequence select="contains($validChapters, $str-in)"/>
</xsl:function>

xslt generate children based on split and parent node name

is it possible to do the following in xsl. I'm tring to split the contents of an element and create sub-elements based on the split. To make things trickier there are the occasional exception (ie node-4 doesn't get split). I'm wondering if there is a way i can do this without explicit splits hardcoded for each element. Again, not sure if this is possible. thanks for the help!
original XML:
<document>
<node>
<node-1>hello world1</node-1>
<node-2>hello^world2</node-2>
<node-3>hello^world3</node-3>
<node-4>hello^world4</node-4>
</node>
</document>
transformed XML
<document>
<node>
<node-1>hello world1</node-1>
<node-2>
<node2-1>hello</node2-1>
<node2-2>world2</node2-2>
</node-2>
<node-3>
<node3-1>hello</node3-1>
<node3-2>world3</node3-2>
</node-3>
<node-4>hello^world4</node-4>
</node>
</document>
To make things trickier there are the
occasional exception (ie node-4
doesn't get split). I'm wondering if
there is a way i can do this without
explicit splits hardcoded for each
element.
Pattern matching text nodes to tokenize, this more semantic stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[contains(.,'^')]" name="tokenize">
<xsl:param name="pString" select="concat(.,'^')"/>
<xsl:param name="pCount" select="1"/>
<xsl:if test="$pString">
<xsl:element name="{translate(name(..),'-','')}-{$pCount}">
<xsl:value-of select="substring-before($pString,'^')"/>
</xsl:element>
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring-after($pString,'^')"/>
<xsl:with-param name="pCount" select="$pCount + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node-4/text()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<document>
<node>
<node-1>hello world1</node-1>
<node-2>
<node2-1>hello</node2-1>
<node2-2>world2</node2-2>
</node-2>
<node-3>
<node3-1>hello</node3-1>
<node3-2>world3</node3-2>
</node-3>
<node-4>hello^world4</node-4>
</node>
</document>
Note: A classic tokenizer (In fact, this use a normalized string allowing empty items in sequence). Pattern matching and overwriting rules (preserving node-4 text node).
Here's an XSL 1.0 solution. I presume that the inconsistency in node-4 in your sample output was just a typo. Otherwise you'll have to define why node3 was split and node4 wasn't.
<?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" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<document>
<node>
<xsl:apply-templates select="document/node/*"/>
</node>
</document>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="tag" select="name()"/>
<xsl:choose>
<xsl:when test="contains(text(),'^')">
<xsl:element name="{$tag}">
<xsl:element name="{concat($tag,'-1')}">
<xsl:value-of select="substring-before(text(),'^')"/>
</xsl:element>
<xsl:element name="{concat($tag,'-2')}">
<xsl:value-of select="substring-after(text(),'^')"/>
</xsl:element>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This works as long as all the nodes you want split are at the same level, under /document/node. If the real document structure is different you will have to tweak the solution to match.
Can you use XSLT 2.0? If so, it sounds like <xsl:analyze-string> is right up your alley. You can split based on a regexp.
If you need further details, ask...
solution i used:
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes"/>
<xsl:preserve-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="node()" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="node-2 | node-3" name="subFieldCarrotSplitter">
<xsl:variable name="tag" select="name()"/>
<xsl:element name="{$tag}">
<xsl:for-each select="str:split(text(),'^')">
<xsl:element name="{concat($tag,'-',position())}">
<xsl:value-of select="text()"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>