I'm trying to remove text nodes without success from a XML document, this is the XSLT I'm using:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*">
<xsl:element name="x">
<xsl:attribute name="attr">
<xsl:value-of select="name()"/>
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:element>
</xsl:template>
<xsl:template match="/*/*/a">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*/a/*">
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="/*/*/*">
<xsl:copy-of select="node()"/>
</xsl:template>
<xsl:template match="/*/*/*[not(self::a)]" />
<xsl:template match="text()" />
</xsl:stylesheet>
The line <xsl:template match="text()"> probably it's not working because other lines are more specific (I think), how can I do to remove ALL text nodes?
Your template to suppress text nodes is suppressing all text nodes for which matching templates are sought. But it's not suppressing all text nodes, because not all text nodes are processed using apply-templates. When you encounter some nodes (those that match the match-patterns /*/*/a/* and /*/*/*, you are copying all of their child nodes without applying templates to them; if some of those children are text nodes, or others of those children have text-node descendants, those text nodes are escaping your scythe. Get rid of the copy-of calls, then, and stick to copy with apply-templates.
Related
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>
With XSLT 2.0, I am trying to create a list of relations between all children of given elements, in a document such as:
<doc>
<part1>
<name>John</name>
<name>Paul</name>
<name>George</name>
<name>Ringo</name>
<place>Liverpool</place>
</part1>
<part2>
<name>Romeo</name>
<name>Romeo</name>
<name>Juliet</name>
<fam>Montague</fam>
<fam>Capulet</fam>
</part2>
</doc>
The result I would like to obtain, ideally by conflating and weighing the identical relations, would be (in whatever order) something like:
<doc>
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
<part2>
<rel weight="2"><name>Romeo</name><name>Juliet</name></rel>
<rel weight="2"><name>Romeo</name><fam>Montague</fam></rel>
<rel weight="2"><name>Romeo</name><fam>Capulet</fam></rel>
<rel><name>Juliet</name><fam>Montague</fam></rel>
<rel><name>Juliet</name><fam>Capulet</fam></rel>
<rel><fam>Montague</fam><fam>Capulet</fam></rel>
</part2>
</doc>
—but I'm not sure how to proceed. Many thanks in advance for your help.
You still haven't explained the logic that needs to be applied here, so this is based largely on a guess:
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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<!-- first pass-->
<xsl:variable name="unique-items">
<xsl:for-each-group select="*" group-by="concat(name(), '|', .)">
<item name="{name()}" count="{count(current-group())}" value="{.}"/>
</xsl:for-each-group>
</xsl:variable>
<!-- output -->
<xsl:copy>
<xsl:for-each select="$unique-items/item">
<xsl:variable name="left" select="."/>
<xsl:for-each select="following-sibling::item">
<xsl:variable name="weight" select="$left/#count * #count" />
<rel>
<xsl:if test="$weight gt 1">
<xsl:attribute name="weight" select="$weight"/>
</xsl:if>
<xsl:apply-templates select="$left | ." />
</rel>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:element name="{#name}">
<xsl:value-of select="#value"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The idea here is to remove duplicates in the first pass, then enumerate all combinations in the second (final) pass. The weight is computed by multiplying the number of occurrences of each member of a combination pair and shown only when it exceeds 1.
At least the combinatoric part of your problem could be solved with the following XSLT script. It does not solve the elimination of duplicates, but that could possibly be done in a second transformation.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- standard copy template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<xsl:copy>
<xsl:variable name="l" select="./*"/>
<xsl:for-each select="$l">
<xsl:variable name="a" select="."/>
<xsl:variable name="posa" select="position()"/>
<xsl:variable name="namea" select="name()"/>
<xsl:for-each select="$l">
<xsl:if test="position() > $posa and (. != $a or name() != $namea)">
<rel>
<xsl:copy-of select="$a"/>
<xsl:copy-of select="."/>
</rel>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to the first part of your example, this produces:
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
Which seems about correct. If have no idea if the duplicate elimination (or weighting, as you call it) could be done in the same transformation.
I'm trying to match a series of xml tags that are comma separated, and to then apply an xslt transformation on the whole group of nodes plus text. For example, given the following partial XML:
<p>Some text here
<xref id="1">1</xref>,
<xref id="2">2</xref>,
<xref id="3">3</xref>.
</p>
I would like to end up with:
<p>Some text here <sup>1,2,3</sup>.</p>
A much messier alternate would also be acceptable at this point:
<p>Some text here <sup>1</sup><sup>,</sup><sup>2</sup><sup>,</sup><sup>3</sup>.</p>
I have the transformation to go from a single xref to a sup:
<xsl:template match="xref"">
<sup><xsl:apply-templates/></sup>
</xsl:template>
But I'm at a loss as to how to match a group of nodes separated by commas.
Thanks.
Update: Thanks to #Flynn1179 who alerted me that the solution wasn't producing exactly the wanted output, I have slightly modified it. Now the wanted "good" format is produced.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match=
"xref[not(preceding-sibling::node()[1]
[self::text() and starts-with(.,',')]
)
]">
<xsl:variable name="vBreakText" select=
"following-sibling::text()[not(starts-with(.,','))][1]"/>
<xsl:variable name="vPrecedingTheBreak" select=
"$vBreakText/preceding-sibling::node()"/>
<xsl:variable name="vFollowing" select=
".|following-sibling::node()"/>
<xsl:variable name="vGroup" select=
"$vFollowing[count(.|$vPrecedingTheBreak)
=
count($vPrecedingTheBreak)
]
"/>
<sup>
<xsl:apply-templates select="$vGroup" mode="group"/>
</sup>
<xsl:apply-templates select="$vBreakText"/>
</xsl:template>
<xsl:template match="text()" mode="group">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (based on the provided one, but made more complex and interesting):
<p>Some text here
<xref id="1">1</xref>,
<xref id="2">2</xref>,
<xref id="3">3</xref>.
<ttt/>
<xref id="4">4</xref>,
<xref id="5">5</xref>,
<xref id="6">6</xref>.
<zzz/>
</p>
produces exactly the wanted, correct result:
<p>Some text here
<sup>1,2,3</sup>.
<ttt/>
<sup>4,5,6</sup>.
<zzz/>
</p>
Explanation:
We use a "fined-grained" identity rule, which processes the document node-by node in document order and copies the matched node "as-is"
We override the identity rule with a template that matches any xref element that is the first in a group of xref elements, each of which (but the last one) is followed by an immediate text-node-sibling that starts with the ',' character. Here we find the first text-node-sibling that breaks the rule (its starting character isn't ','.
Then we find all the nodes in the group, using the Kayessian (after #Michael Kay) formula for the intersection of two nodesets. This formula is: $ns1[count(.|$ns2) = count($ns2)]
Then we process all nodes in the group in a mode named "group".
Finally, we apply templates (in anonymous mode) to the breaking text node (that is the first node following the group), so that the chain of processing continues.
Interesting question. +1.
Here's an XSLT 2.0 solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="comma-regex">^\s*,\s*$</xsl:variable>
<!-- Identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Don't directly process xrefs that are second or later in a comma-separated series.
Note that this template has a higher default priority than the following one,
because of the predicate. -->
<xsl:template match="xref[preceding-sibling::node()[1]/
self::text()[matches(., $comma-regex)]/
preceding-sibling::*[1]/self::xref]" />
<!-- Don't directly process comma text nodes that are in the middle of a series. -->
<xsl:template match="text()[matches(., $comma-regex) and
preceding-sibling::*[1]/self::xref and following-sibling::*[1]/self::xref]" />
<!-- for xrefs that first (or solitary) in a comma-separated series: -->
<xsl:template match="xref">
<sup>
<xsl:call-template name="process-xref-series">
<xsl:with-param name="next" select="." />
</xsl:call-template>
</sup>
</xsl:template>
<xsl:template name="process-xref-series">
<xsl:param name="next"/>
<xsl:if test="$next">
<xsl:value-of select="$next"/>
<xsl:variable name="followingXref"
select="$next/following-sibling::node()[1]/
self::text()[matches(., $comma-regex)]/
following-sibling::*[1]/self::xref"/>
<xsl:if test="$followingXref">
<xsl:text>,</xsl:text>
<xsl:call-template name="process-xref-series">
<xsl:with-param name="next" select="$followingXref"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
(This could be simplified if we could make some assumptions about the input.)
Run against the sample input you gave, the result is:
<p>Some text here
<sup>1,2,3</sup>.
</p>
The second alternative can be achieved with
<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="p/text()[normalize-space() = ',' and preceding-sibling::node()[1][self::xref]]">
<sup>,</sup>
</xsl:template>
<xsl:template match="xref">
<sup>
<xsl:apply-templates/>
</sup>
</xsl:template>
</xsl:stylesheet>
There's an almost trivial solution to your 'messy alternative':
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="xref">
<sup>
<xsl:apply-templates />
</sup>
</xsl:template>
<xsl:template match="text()[normalize-space(.)=',']">
<sup>,</sup>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*| node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
EDIT: I just noticed it's almost a clone of Martin's solution, except without the additional check of a preceding xref element on the commas. His is probably safer :)
And a slightly less trivial solution to your preferred result, although this only works if you only have one collection of xref tags in any p tag. You didn't mention the possibility of more than one collection, and even if there are, I would have thought it unlikely they'd be within the same containing p tag. If that can happen though, it's possible to extend it further to allow for that, although it will get a lot more complicated.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="xref[not(preceding-sibling::text()[normalize-space(.)=','])]">
<sup>
<xsl:value-of select="." />
<xsl:for-each select="following-sibling::text() | following-sibling::xref">
<xsl:if test="following-sibling::text()[substring(.,1,1)='.']">
<xsl:value-of select="normalize-space(.)" />
</xsl:if>
</xsl:for-each>
</sup>
</xsl:template>
<xsl:template match="xref | text()[normalize-space(.)=',']" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*| node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In case you can use XSLT 2.0 (e.g. with Saxon 9 or AltovaXML or XQSharp) then here is an XSLT 2.0 solution that should produce the first output you asked for:
<xsl:stylesheet version="2.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="p">
<xsl:for-each-group select="node()" group-adjacent="self::xref or self::text()[normalize-space() = ',']">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<sup>
<xsl:value-of select="current-group()/normalize-space()" separator=""/>
</sup>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
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>
I worked out an XSL template that rewrites all hyperlinks on an HTML page, containing a certain substring in the href attribute. It looks like this:
<xsl:template match="A[contains(#href, 'asp')]">
<a>
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, #href, 'intranet')" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</a>
</xsl:template>
I'm not liking the fact that I must recreate the A element from scratch. I know you can do something like this:
<xsl:template match="A/#href">
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, ., 'intranet')" />
</xsl:attribute>
</xsl:template>
But how should I merge these two together? I tried f.e. this and it doesn't work (the element does not get selected):
<xsl:template match="A[contains(#href, 'asp')]/#href">
<xsl:attribute name="href">
<xsl:value-of select="bridge:linkFrom($bridge, $base, ., 'intranet')" />
</xsl:attribute>
</xsl:template>
Any help is much appreciated!
First: If you declare a rule for matching an attribute, then you must take care of apply templates to those attributes, because there is no built-in rule doing that and apply templates without select is the same as apply-templates select="node()".
So, this 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="a/#href[.='#']">
<xsl:attribute name="href">http://example.org</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
link
</root>
Output:
<root>
link
</root>
But, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href[.='#']">
<xsl:attribute name="href">http://example.org</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<a>link</a>
</root>
I would also expect it to work. I have no way of testing this now but did you try any other way of writing it?
For example:
<xsl:template match="A/#href[contains(. , 'asp')]">