XSLT deepening content structure - xslt

given the following structure:
<TITEL1>...</TITEL1>
<p>..</p>
<TITEL2>...</TITEL2>
<TITEL3>...</TITEL3>
<TITEL3>...</TITEL3>
<P>...<P>
is there a way to get to this:
<TITEL1>
<TITEL>...</TITEL>
<p>...</p>
<TITEL2>
<TITEL>...</TITEL>
<TITEL3>
<TITEL>...</TITEL>
<P>...</P>
</TITEL3>
<TITEL3>
<TITEL>...</TITEL>
<P>...</P>
</TITEL3>
</TITEL2>
</TITEL1>
or in other words,is there a way to have higher level titels inclose lower level titels and all content that follows them, thus creating a nested structure. The content of each TITEL1,2 and 3 tag should go into a new <TITEL>-element

With XSLT 2.0 (as implemented by Saxon 9 or AltovaXML tools) you can use xsl:for-each-group group-starting-with and a recursive function:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/2010/mf"
exclude-result-prefixes="xsd mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="level" as="xsd:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('TITEL', $level))]">
<xsl:choose>
<xsl:when test="self::*[starts-with(local-name(), concat('TITEL', $level))]">
<xsl:element name="TITEL{$level}">
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:nest(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="ROOT">
<xsl:sequence select="mf:nest(*, 1)"/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]">
<TITEL>
<xsl:apply-templates select="#* | node()"/>
</TITEL>
</xsl:template>
</xsl:stylesheet>
With that stylesheet the input
<ROOT>
<TITEL1>Titel 1, 1</TITEL1>
<p>..</p>
<TITEL2>Titel 2, 1</TITEL2>
<TITEL3>Titel 3, 1</TITEL3>
<TITEL3>Titel 3, 2</TITEL3>
<P>...</P>
</ROOT>
is transformed to the output
<TITEL1>
<TITEL>Titel 1, 1</TITEL>
<p>..</p>
<TITEL2>
<TITEL>Titel 2, 1</TITEL>
<TITEL3>
<TITEL>Titel 3, 1</TITEL>
</TITEL3>
<TITEL3>
<TITEL>Titel 3, 2</TITEL>
<P>...</P>
</TITEL3>
</TITEL2>
</TITEL1>

There isn't a particularly eligant way of doing what you want. It's (probably) possible, but it would involve some pretty ugly (and slow) XPath queries using the following-sibling axis with filters on the preceding-sibling axis matching back to the current node.
If it's at all a possibility, I would recommend creating the hierarchy outside of XSLT (in C#, Java, etc)
If you choose to go down the scary path, you would be looking to do something like this (untested):
<xsl:template match="TITEL1">
<TITEL1>
<xsl:apply-templates
select="following-sibling::(p|TITEL2)[(preceding-sibling::TITEL1)[1]=.]" />
</TITEL1>
</xsl:template>
<xsl:template match="TITEL2">
<TITEL1>
<xsl:apply-templates
select="following-sibling::(p|TITEL3)[(preceding-sibling::TITEL2)[1]=.]" />
</TITEL1>
</xsl:template>
...
This is only an example, and I can already see problems with the match. Coming up with the final XPath query would be quite involved, if it's actually possible at all.

If you can't use XSLT 2.0, here is an XSLT 1.0 stylesheet that should produce the same result as the XSLT 2.0 stylesheet I posted earlier:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="ROOT">
<xsl:apply-templates select="*[1]" mode="nest">
<xsl:with-param name="level" select="1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]" mode="nest">
<xsl:param name="level"/>
<xsl:choose>
<xsl:when test="$level = substring-after(local-name(), 'TITEL')">
<xsl:element name="TITEL{$level}">
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[starts-with(local-name(), concat('TITEL', $level))][1]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="." mode="nest">
<xsl:with-param name="level" select="$level + 1"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*[not(starts-with(local-name(), 'TITEL'))]" mode="nest">
<xsl:param name="level"/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]">
<TITEL>
<xsl:apply-templates select="#* | node()"/>
</TITEL>
</xsl:template>
</xsl:stylesheet>

Related

Need to perform two transformation for the same content in xsl

I have two different transformation.After first transformation get over second need to be applied but not able to do.I have referred other stackoverflow post but its not working for me.
Sample Input:
<Para>1<AAA>2<BBB>3</BBB>4</AAA>5</Para>
Requirement is like BBB may available outside of AAA as well.We need to remove all AAA and put the value in comma separated format.
Expected output after First:
<Para>12<BBB>3</BBB>45</Para>
Expected output on the Second:
<Para>12,3,45</Para>
First:
Here i am just removing tag AAA from the content.And retriving its content and child.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match ="*/AAA">
<xsl:apply-templates/>
</xsl:template>
Second:
Here I am just appending comma between the node element by removing all the tags inside para and retrieving its content.
<xsl:param name="Para"></xsl:param>
<xsl:template match="Para">
<xsl:copy>
<xsl:variable name="BBB-element-exists">
<xsl:for-each select="node()[self::BBB ][normalize-space(.)!='']">
<xsl:if test="(self::Change and (child::BBB)) or (self::BBB)">
<xsl:value-of select="'BBBexists'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$BBB-element-exists = 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:if test="position() >1 and not(parent::Change) ">
<xsl:value-of select="','" />
</xsl:if>
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
<xsl:if test="$BBB-element-exists != 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will need to use at least one mode to separate processing of the second step from the first:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/AAA">
<xsl:apply-templates/>
</xsl:template>
<xsl:variable name="first-pass">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="$first-pass/node()" mode="step2"/>
</xsl:template>
<xsl:template match="Para" mode="step2">
<xsl:copy>
<xsl:variable name="BBB-element-exists">
<xsl:for-each select="node()[self::BBB ][normalize-space(.)!='']">
<xsl:if test="(self::Change and (child::BBB)) or (self::BBB)">
<xsl:value-of select="'BBBexists'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$BBB-element-exists = 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:if test="position() >1 and not(parent::Change) ">
<xsl:value-of select="','" />
</xsl:if>
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
<xsl:if test="$BBB-element-exists != 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:transform>

Wrap an ordered list of nodes

Hi I have an xml document which look like that:
<a> <!-- Several nodes "a" with the same structure for children -->
<b>12</b>
<c>12</c>
<d>12</d>
<e>12</e>
<f>12</f>
<g>12</g>
</a>
I'm trying to obtain the following document using xslt 2.0
<a>
<b>12</b>
<c>12</c>
<wrap>
<d>12</d>
<e>12</e>
<f>12</f>
<g>12</g>
</wrap>
</a>
I started my xsl file with
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
And changed it for several cases like replacing a string part, filter some nodes, etc.
But I'm stuck with "selecting four consecutive nodes", any clue on how to achieve the wrapping?
If all your a elements are genuinely exactly the same structure then the simplest would be a brute-force
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates select="b | c" />
<wrap>
<xsl:apply-templates select="d | e | f | g" />
</wrap>
</xsl:copy>
</xsl:template>
or if you want to be a bit cleverer
<wrap>
<xsl:apply-templates select="* except (b | c)" />
</wrap>
If you want to always "wrap" the last four child elements of a, then how about
<xsl:template match="a">
<xsl:variable name="lastFour" select="*[position() > (last() - 4)]" />
<xsl:copy>
<xsl:apply-templates select="* except $lastFour" />
<wrap>
<xsl:apply-templates select="$lastFour" />
</wrap>
</xsl:copy>
</xsl:template>
With XSLT 2.0 you can also make use of for-each-group group-adjacent:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(self::d | self::e | self::f | self::g)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<wrap>
<xsl:apply-templates select="current-group()"/>
</wrap>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Replacing the namespace in input xml

I have two types of input xml, one with namespace prefix and another one without prefix and i want to replace the namespaces.
Sample1
<v1:Library xmlns:v1="http://testlibrary" xmlns:v2="http://commonprice">
<v1:Books_details>
<v1:Name>test1</v1:Name>
<v1:title>test2</v1:title>
<v2:price xmlns="http://commonprice">12</v2:price>
</v1:Books_details>
</v1:Library>
Sample2
<Library xmlns="http://testlibrary">
<Books_details>
<Name>test1</Name>
<title>test2</title>
<price xmlns="http://commonprice">12</price>
</Books_details>
</Library>
I have written following XSLT to change the namespace from "http://testlibrary" to "http://newlibrary" and it works fine for sample1 but it doesn't work for the sample2. It gives wrong result. It also the change the namespace of the price element even though it doesn't have namespace to be replaced.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="old_namespace"/>
<xsl:param name="new_namespace"/>
<xsl:template match="/">
<xsl:apply-templates select="#* | node()"/>
</xsl:template>
<xsl:template match="text() | comment() | processing-instruction()">
<xsl:copy>
<xsl:apply-templates select="text() | comment() | processing-instruction()"/>
</xsl:copy>
</xsl:template>
<!-- Template used to copy elements -->
<xsl:template match="*">
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="contains(name(), ':')">
<xsl:value-of select="name()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="local-name()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$name}" namespace="{$new_namespace}">
<!-- Copy all namespace through except for namespace to be changed -->
<xsl:for-each select="namespace::*">
<xsl:if test="string(.)!=$old_namespace">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note: My first answer was wrong because in XSLT 1.0, you can't use variable references within a match pattern.
This style-sheet will take either Sample1 or Sample2 as input document and replace all occurrences of the old namespace, from the element names, with the new namespace. Note: you can change the xsl:variable for xsl:param.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="old_namespace" select="'http://testlibrary'" />
<xsl:variable name="new_namespace" select="'http://newlibrary'" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:choose>
<xsl:when test="namespace-uri()=$old_namespace">
<xsl:element name="{local-name()}" namespace="{$new_namespace}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Caveat
The above style-sheet will only change the namespace of the elements. It will not change the namespaces of attributes, nor will it remove extraneous namespaces nodes.
On a more specific solution
It is very unusual to require a general solution for an operation on a variable namespace. Namespaces, being what they are tend to be fixed and known. Consider carefully, if you really need a generalized solution. If you need a specific solution, meaning replacing occurrences of a specific namespace, then things get a lot easier, such as with this style-sheet...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:old="http://testlibrary"
xmlns:new="http://newlibrary">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="old:*">
<xsl:element name="{local-name()}" namespace="http://newlibrary">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
UPDATE
I just noticed Dimitre's solution to a very similar question here.

Use xslt replace function to replace a word with an element

I want to usw the XSLT replace function to replace words in a text with
<strong>word</strong>.
I wrote the following template:
<xsl:template name="make-bold">
<xsl:param name="text"/>
<xsl:param name="word"/>
<xsl:variable name="replacement">
<strong><xsl:value-of select="$word"/></strong>
</xsl:variable>
<xsl:value-of select="replace($text, $word, $replacement )" />
</xsl:template>
Unfortunately, and are not rendered, althoug the rest works.
Could anyone help me?
Best, Suidu
Well the replace function http://www.w3.org/TR/xpath-functions/#func-replace takes a string and returns a string. You seem to want to create an element node, not a simple string. In that case using analyze-string http://www.w3.org/TR/xslt20/#analyze-string instead of replace could help.
Here is a sample XSLT 2.0 stylesheet:
<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:output method="html" indent="no"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="text()" mode="wrap">
<xsl:with-param name="words" as="xs:string+" select="('foo', 'bar')"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" mode="wrap">
<xsl:param name="words" as="xs:string+"/>
<xsl:param name="wrapper-name" as="xs:string" select="'strong'"/>
<xsl:analyze-string select="." regex="{string-join($words, '|')}">
<xsl:matching-substring>
<xsl:element name="{$wrapper-name}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
When you run that with an XSLT 2.0 processor like Saxon 9 against the following input sample
<html>
<body>
<p>This is an example with foo and bar words.</p>
</body>
</html>
the output is as follows:
<html>
<body>
<p>This is an example with <strong>foo</strong> and <strong>bar</strong> words.</p>
</body>
</html>
hmm is is because here it its the string value that is replaced, you might try to use the node set?
i cannot test as i dont use xslt 2.0 but you might try a recursive template ie
<xsl:template match="yourtextelement">
<xsl:call-template name="MaketextStrong">
</xsl:template>
<xsl:template name="MaketextStrong">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text, 'texttomakestrong')">
<xsl:value-of select="substring-before($text, 'texttomakestrong')"/>
<strong>texttomakestrong</strong>
<xsl:call-template name="break">
<xsl:with-param name="text" select="substring-after($text,
'texttomakestrong')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

xml attribute replacement with xslt

How can i replace an attribute in xml using xsl transformation, depending on its value.
For example, if there is such xml
<Text Style='style1'>
...
</Text>
transform it to
<Text Font='Arial' Bold='true' Color='Red'>
...
</Text>
For Style='style2' set another attributes and values, for example Font='Sans' Italic='true'.
One posible way: ussing attribute sets. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:attribute-set name="style1">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="style2">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Italic">true</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="Text[#Style='style1']">
<xsl:copy use-attribute-sets="style1">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Text[#Style='style2']">
<xsl:copy use-attribute-sets="style2">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<Text Style='style1'></Text>
<Text Style='style2'></Text>
</root>
Output:
<Text Font="Arial" Bold="true" Color="Red"></Text>
<Text Font="Sans" Italic="true"></Text>
Other way: inline "attribute sets". This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my">
<xsl:output indent="yes"/>
<my:style1 Font="Arial" Bold="true" Color="Red"/>
<my:style2 Font="Sans" Italic="true"/>
<xsl:template match="Text[#Style]">
<xsl:copy>
<xsl:copy-of select="document('')/*/my:*
[local-name()=current()/#Style]/#*"/>
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will need to have some kind of rule that you use to convert the style from one to the other. Making the assumption you are inputting html you will need something like.
<xsl:template match="#* | node()">
<xsl:choose>
<xsl:when test="local-name() = 'Style'">
<xsl:apply-templates select="." mode="Style" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#Style" mode="Style">
<xsl:choose>
<xsl:when test="node() = 'style1'">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:when>
<xsl:when test="node() = 'style2'">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:template>