XSLT - How to split up a parent's children - xslt

My xml structure contains a program as the parent of both certificates and courses. I want to split the structure up to create an independent listing of certificates and courses without the common program parent. The original structure is:
<root>
<program>
<program-name>Math</program-name>
<certificate>...</certificate> <!-- There can 0 or more of these -->
<course>...</course> <!-- There can 0 or more of these -->
</program>
<program>
...
</program>
</root>
The output should look like so:
<root>
<program-courses>
<program>
<program-name>Math</program-name>
<course/> <!-- There can 0 or more of these -->
</program>
...
</program-courses>
<program-certificates>
<program>
<program-name>Math</program-name>
<certificate/> <!-- There can 0 or more of these -->
</program>
...
</program-certificates>
</root>
Update: Answered using Paul's suggestion for using a mode this is what the relevant portion of the xslt became:
<xsl:template match="root">
<xsl:element name="root">
<xsl:element name="program-courses">
<xsl:apply-templates select="root/program-area" mode="course"/>
</xsl:element>
<xsl:element name="program-certificates">
<xsl:apply-templates select="root/program-area" mode="certificate"/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="program-area" mode="course">
<xsl:element name="program-area">
<!-- Get the name here -->
<xsl:element name="course">
<xsl:apply-templates select="course"/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="program-area" mode="certificate">
<xsl:element name="program-area">
<!-- Get the name here -->
<xsl:element name="course">
<xsl:apply-templates select="certificate"/>
</xsl:element>
</xsl:element>
</xsl:template>
Note that this solution is pared down from the actual one so it may not work as is against the original input.

Selecting program elements, You could use #mode (on apply-templates and a corresponding template) to differentiate between whether you are operating within the output of program-courses or program-certificates
From root, you could select program/course or program/certificate to generate the output program.
From root, you could use for-each select="program" and for the part that is intended to output program-courses only extract the program-name and course element, and perform the corresponding extraction in the part that outputs program-certificates.

This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="program">
<xsl:apply-templates select="course[1]|certificate[1]"/>
</xsl:template>
<xsl:template match="course[1]">
<program-courses>
<program>
<xsl:apply-templates select="../*[not(self::certificate)]"
mode="copy"/>
</program>
</program-courses>
</xsl:template>
<xsl:template match="certificate[1]">
<program-certificates>
<program>
<xsl:apply-templates select="../*[not(self::course)]"
mode="copy"/>
</program>
</program-certificates>
</xsl:template>
<xsl:template match="node()" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<program-certificates>
<program>
<program-name>Math</program-name>
<certificate>...</certificate>
</program>
</program-certificates>
<program-courses>
<program>
<program-name>Math</program-name>
<course>...</course>
</program>
</program-courses>
</root>
EDIT: If you want something more "push style" like your posted solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" mode="certificate">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="node()" mode="course">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="program">
<program-courses>
<program>
<xsl:apply-templates mode="course"/>
</program>
</program-courses>
<program-certificates>
<program>
<xsl:apply-templates mode="certificate"/>
</program>
</program-certificates>
</xsl:template>
<xsl:template match="course" mode="certificate"/>
<xsl:template match="certificate" mode="course"/>
</xsl:stylesheet>

Related

Add element after document has been produced

After having created an XHTML document using XSLT, I need to add an element (link:schemaRef).
The reason is that I am merging 2 XHTML document and it is only the merged document that should have the element I need to add. I reduced the length of the link just to fit the example better.
I cannot see that the result file has the added link.
Something obviously wrong in my code?
My code base:
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Find and add element in document -->
<xsl:template match="/xhtml:html/xhtml:body/xhtml:div[1]/ix:header/ix:hidden/ix:references">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:element name="link:schemaRef">
<xsl:attribute name="xlink:type">simple</xsl:attribute>
<xsl:attribute name="xlink:href">http://example.org</xsl:attribute>
</xsl:element>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
You still haven't explained how your input looks but if you want to perform two transformations within one stylesheet then you can use modes to separate them plus store the first transformation step's result in a variable you push to the second mode:
<xsl:mode name="m1" on-no-match="shallow-copy"/>
<xsl:variable name="intermediary-result">
<xsl:apply-templates mode="m1"/>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="$intermediary-result" mode="m2"/>
</xsl:template>
<xsl:mode name="m2" on-no-match="shallow-copy"/>
<xsl:template mode="m2" match="/xhtml:html/xhtml:body/xhtml:div[1]/ix:header/ix:hidden/ix:references">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:element name="link:schemaRef">
<xsl:attribute name="xlink:type">simple</xsl:attribute>
<xsl:attribute name="xlink:href">http://example.org</xsl:attribute>
</xsl:element>
<xsl:apply-templates mode="#current"/>
</xsl:copy>
</xsl:template>

One to One XSLT map with namespace manipulations

I have the following xml:
<ns0:Root xmlns:ns0="http://root" xmlns:nm="http://notroot">
<nm:MSH>
<content>bla</content>
</nm:MSH>
<ns0:Second>
<ns0:item>aaa</ns0:item>
</ns0:Second>
<ns0:Third>
<ns0:itemb>vv</ns0:itemb>
</ns0:Third>
</ns0:Root>
That is my expected result:
<Root xmlns="http://root" xmlns:nm="http://notroot">
<nm:MSH>
<content>bla</content>
</nm:MSH>
<Second>
<item>aaa</item>
</Second>
<Third>
<itemb>vv</itemb>
</Third>
</Root>
I need to write an xslt 1.0 that perform that map.
I really doesn't have a clue how to do it, thus it seems pretty simple.
Can anyone please help ?
To move the elements from xmlns:ns0="http://root" namespace to default namespace.
Use:
<xsl:template match="ns0:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
To make http://root the default namespace add xmlns="http://root"to the stylesheet declaration.
Therefore you may try something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns0="http://root"
xmlns="http://root"
exclude-result-prefixes="ns0" >
<!-- Identity transform (e.g. http://en.wikipedia.org/wiki/Identity_transform#Using_XSLT -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- match root element and fore notroot namespace to this -->
<xsl:template match="/*">
<Root xmlns:nm="http://notroot">
<xsl:apply-templates select="#*|node()" />
</Root>
</xsl:template>
<xsl:template match="content">
<content>
<xsl:apply-templates select="#*|node()" />
</content>
</xsl:template>
<!-- move attributes with prefix ns0 to default namespace -->
<xsl:template match="#ns0:*">
<xsl:attribute name="newns:{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- move elements with prefix ns0 to default namespace -->
<xsl:template match="ns0:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The only difference between your input and output is that the content element inside nm:MSH has moved from being in no namespace to being in the http://root namespace. This can be handled by an identity transformation with tweaks:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns0="http://root">
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="content">
<ns0:content><xsl:apply-templates select="#*|node()" /></ns0:content>
</xsl:template>
</xsl:stylesheet>

Rename a batch of values with xslt

We have a program that uses xml to save configurations of our program. Someone decided to rename a couple of values in our database and these renames should now also be backwards compatible in the configurations of our customers.
An example of a configuration
<configuration>
<fruitToEat>yellow_curved_thing</fruitToEat> <!-- should now become banana -->
</configuration>
A simple match would be (not tested, just an example):
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match"/configuration/fruitToEat/text()">
<xsl:text>banana</xsl:text>
</xsl:template>
</xsl:template>
But this is just one example and I want to do this 150 times.
Is it possible to make an xsl that reads a simple text file or ini file that tells me how the 150 matches should look alike?
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- recreate this template 150 times from an ini file or something -->
<xsl:template match"/configuration/fruitToEat/text()[.='yellow_curved_thing']">
<xsl:text>banana</xsl:text>
</xsl:template>
</xsl:template>
An example of my mapping file could be simply:
yellow_curved_thing = banana
round_thing = tomato
round_dotted = strawberry
And I would simply want a small xslt that tells me:
<xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- recreate this template 150 times from an ini file or something -->
<xsl:template match"/configuration/fruitToEat/text()[.=$fileRow0]">
<xsl:text>$fileRow1</xsl:text>
</xsl:template>
</xsl:template>
So even if I think there is more complexity behind the curtain, may be this will help a little bit.
There are some possibilities to do this with xlst. Which would be best in long term depends on complexity in real life and how often you need to do this with different "mapping" information.
For your easy example you can put the "mapping" information into a xml file. This could be done by some script form ini file.
<mappings>
<mapping name="fruitToEat" >
<map from="yellow_curved_thing" to="banana" />
<map from="round_thing" to="tomato" />
<map from="round_dotted" to="strawberry" />
</mapping>
</mappings>
Than you can have a template which make use of this mapping information:
<xsl:variable name="fruitMapping"
select="document('fruitmapping.xml')//mapping[#name='fruitToEat']" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/configuration/fruitToEat/text()" >
<!-- find the entry in "ini file" -->
<xsl:variable name ="map" select="$fruitMapping/map[#from = current()]" />
<xsl:choose>
<xsl:when test="$map" >
<xsl:value-of select="$map/#to"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
But if this is a onetime job I would implement this "mapping" direct a template. Like this:
<xsl:template match="/configuration/fruitToEat/text()" >
<xsl:choose>
<xsl:when test=".='yellow_curved_thing'" >banana</xsl:when>
<xsl:when test=".='round_thing'" >tomato</xsl:when>
<xsl:when test=".='round_dotted'" >strawberry</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It seems you wanted to create your XSLT dynamic XSLT on the basis of your configuration file which is also an XML document. Have a look this exmple considering this:
configuration.xml
<p>
<configuration>
<fruitToEat>yellow_curved_thing</fruitToEat>
<mapped>banana</mapped>
</configuration>
<configuration>
<fruitToEat>round_thing</fruitToEat>
<mapped>tomato</mapped>
</configuration>
</p>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:element name="xsl:stylesheet">
<xsl:attribute name="version">
<xsl:text>1.0</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:template">
<xsl:attribute name="match">
<xsl:text>node()|#*</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:copy">
<xsl:element name="xsl:apply-templates">
<xsl:attribute name="select">
<xsl:text>node()|#*</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
<xsl:for-each select="//configuration">
<xsl:element name="xsl:template">
<xsl:attribute name="match">
<xsl:text>configuration/fruitToEat/text()[.=</xsl:text>
<xsl:text>'</xsl:text>
<xsl:value-of select="fruitToEat"/>
<xsl:text>']</xsl:text>
</xsl:attribute>
<xsl:element name="xsl:text">
<xsl:value-of select="mapped"/>
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="configuration/fruitToEat/text()[.='yellow_curved_thing']">
<xsl:text>banana</xsl:text>
</xsl:template>
<xsl:template match="configuration/fruitToEat/text()[.='round_thing']">
<xsl:text>tomato</xsl:text>
</xsl:template>
</xsl:stylesheet>

XSL repeat the parent node for each child node

For each child node, I want to duplicate my parent node so that the resulting xml, contains only one child for the parent node with the other nodes being the same.
Here is a sample input
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
What I want to do is - repeat a3 for as many times as the node a311 comes. Rest all nodes are retained
Output
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a3>
<a31>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
More semantic with "tunnel param" pattern, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()" name="identity">
<xsl:param name="pCurrent"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="pCurrent" select="$pCurrent"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="a3">
<xsl:variable name="vCurrent" select="."/>
<xsl:variable name="vDescendants" select=".//a311"/>
<xsl:for-each select="$vDescendants|$vCurrent[not($vDescendants)]">
<xsl:variable name="vDescendant" select="."/>
<xsl:for-each select="$vCurrent">
<xsl:call-template name="identity">
<xsl:with-param name="pCurrent" select="$vDescendant"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="a311">
<xsl:param name="pCurrent"/>
<xsl:if test="generate-id()=generate-id($pCurrent)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a3>
<a31>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
EDIT: Handling no descendants case.
The following (XSLT 1.0) stylesheet produces the desired result:
<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="a3">
<xsl:apply-templates select="a31/a311" />
</xsl:template>
<xsl:template match="a311">
<a3>
<a31>
<a311>
<xsl:value-of select="." />
</a311>
</a31>
<xsl:apply-templates select="../../*[not(self::a31)]" />
</a3>
</xsl:template>
</xsl:stylesheet>
You didn't specify the XSLT version. Here's an XSLT2 stylesheet that will accomplish what you want:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a3" mode="#default">
<xsl:apply-templates select="a31/a311"/>
</xsl:template>
<xsl:template match="a311">
<xsl:apply-templates select="../.." mode="x">
<xsl:with-param name="label" select="text()"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="a3" mode="x">
<xsl:param name="label"/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<a31>
<a311><xsl:value-of select="$label"/></a311>
</a31>
<xsl:apply-templates select="* except a31"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It uses an identity transform to pass through the "uninteresting" elements while trapping the <a3> node and applying special handling. If you need an XSLT1 solution, merely
Replace select="* except a31" with select="*[name() != 'a31']"
Remove the mode="#default" in the first "a3" template

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>