XSLT XML transformation using Oracle 11g - xslt

I am shredding XML stored in CLOB column of a table using Oracle 11g SQL by passing ML through a XSLT using oracle function xmltransform.
After transformaing I get shredded data in column format
wherever parent nodes are repeating, child elements are not be repeated within a parent node
SQL query for transforming
---------------------------
with xml_xsl as (select '<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<ROWSET>
<xsl:apply-templates select="*">
<xsl:with-param name="lvl" select="0"/>
</xsl:apply-templates>
</ROWSET>
</xsl:template>
<xsl:template match="*">
<xsl:param name="id"/>
<xsl:param name="pid"/>
<xsl:param name="pxp"/>
<xsl:param name="lvl"/>
<xsl:param name="position"/>
<xsl:variable name="id" select="substring(generate-id(.),2)"/>
<xsl:variable name="name" select="local-name(.)"/>
<xsl:variable name="xp" select="concat($pxp,''/'',$name)"/>
<xsl:variable name="pos" select="count(preceding::*[name(.)=$name])"/>
<ROW id="{$id}" pid="{$pid}" name="{$name}" xpath="{$xp}" level="{$lvl+1}" position="{$pos}" kind="element">
<xsl:value-of select="text()"/>
</ROW>
<xsl:apply-templates select="*">
<xsl:with-param name="id" select="$id"/>
<xsl:with-param name="pid" select="$id"/>
<xsl:with-param name="pxp" select="$xp"/>
<xsl:with-param name="lvl" select="$lvl+1"/>
<xsl:with-param name="position" select="$pos"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>' as xsl,
'<x1:A xmlns:x1="http://www.w3.org/transform">
<x2:B xmlns:x2="http://www.w3.org/transform">
<C>99999</C>
<C>2222</C>
</x2:B>
<E>8888</E>
<D>
<G1>Deep</G1>
</D>
<D>
<G>Deep3</G>
</D>
</x1:A>' as xml from dual)
select x.*
from xml_xsl a
, xmltable('/ROWSET/ROW'
passing xmltransform(xmlparse(document a.xml), xmlparse(document a.xsl wellformed))
columns id number path '#id'
, p_id number path '#pid'
, node_name varchar2(30) path '#name'
, node_value varchar2(4000) path '.'
, xpath varchar2(4000) path '#xpath'
, pos number path '#position'
) x;
Input XML:
----------
<A>
<B>
<C>99999</C>
</B>
<E>8888</E>
<D>
<G1>Deep</G1>
</D>
<D>
<G>XYZ</G>
</D>
</A>
Output
-------
id Parentid node depth position value xpath
-- -------- ----- ----- -------- ----- ------
1 NULL A 0 0 NULL /A
2 1 B 1 0 NULL /A/B
3 2 C 2 0 99999 /A/B/C
4 1 E 1 0 8888 /A/E
5 1 D 1 0 NULL /A/D
6 5 G1 2 0 Deep /A/D/G1
7 1 D 1 1 NULL /A/D
**8 7 G 2 0 XYZ /A/D/G** Postion for node G showing 0 which should req. 1
As node G is not present in parent D previously but present in 2nd parent D the position for that should be 1 rather than 0.
I tried every thing but not getting solution.

Related

How to get maximum or minimum from multiple sets of calculated values with xpath

I need the maximum of three three kinds of values.
I've got a structure similar to this.
note:the first two answers are based on a previous example xml (in set 2 of night the max-above-average was 8). This was confusing, so I changed it to 7.
<data>
<record>
<max>60</max>
</record>
<day>
<set>
<average>49</average>
<max-above-average>3</max-above-average>
</set>
<set>
<average>45</average>
<max-above-average>9</max-above-average>
</set>
</day>
<night>
<set>
<average>50</average>
<max-above-average>5</max-above-average>
</set>
<set>
<average>52</average>
<max-above-average>7</max-above-average>
</set>
</night>
</data>
Now I need the maximum of the record, day and night. This would be maximum: 60, the value of the record in this example: 60 = 60, > 49 + 3, 45 +9, 50 + 5, 52+7. Day and night maximums need to be calculated. Because of this
max(//record/max | //day/set/(average + max-above-average)) | //night/set/(average +max-above-average))
does not work. The |-sign only works for nodes.
It gives following error:
Required item type of second operand of '|' is node(); supplied value has item type xs:double
I'm using xpath 2.0 and xslt 2.0.
Here are the wanted two XPath 2.0 expressions (for producing the "max" and the "min" value, respectively):
max(/*/(day|night)/*/(average+max-above-average))
and
min(/*/(day|night)/*/(average -min-above-average))
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
max: <xsl:text/>
<xsl:sequence select=
"max(/*/(day|night)/*/(average+max-above-average))"/>
min: <xsl:text/>
<xsl:sequence select=
"min(/*/(day|night)/*/(average -min-above-average))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<data>
<record>
<min>40</min>
<max>60</max>
</record>
<day>
<set>
<average>49</average>
<max-above-average>3</max-above-average>
<min-above-average>15</min-above-average>
</set>
<set>
<average>45</average>
<max-above-average>9</max-above-average>
<min-above-average>2</min-above-average>
</set>
</day>
<night>
<set>
<average>50</average>
<max-above-average>5</max-above-average>
<min-above-average>6</min-above-average>
</set>
<set>
<average>52</average>
<max-above-average>8</max-above-average>
<min-above-average>11</min-above-average>
</set>
</night>
</data>
the two XPath expressions are evaluated and the results of these evaluations are copied to the output:
max: 60
min: 34
Update:
The OP says in a comment that he wants "maximum of day and night ànd record" -- I really don't understand what he means by that.
Here is my attempt at guessing:
max(
(/*/record/max,
/*/(day|night)/*/(average+max-above-average, average+min-above-average)
)
)
When implanted in the XSLT transformation (above), this produces:
max: 64
An XSLT 1.0 approach:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/*">
<xsl:variable name="recordMax" select="record/max" />
<xsl:variable name="dayMax">
<xsl:apply-templates select="day" mode="max" />
</xsl:variable>
<xsl:variable name="nightMax">
<xsl:apply-templates select="night" mode="max" />
</xsl:variable>
<xsl:call-template name="Max">
<xsl:with-param name="v1" select="$recordMax" />
<xsl:with-param name="v2">
<xsl:call-template name="Max">
<xsl:with-param name="v1" select="$dayMax" />
<xsl:with-param name="v2" select="$nightMax" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="*[set]" mode="max">
<xsl:apply-templates select="set" mode="max">
<xsl:sort select="average + max-above-average"
data-type="number"
order="descending" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="set" mode="max">
<xsl:if test="position() = 1">
<xsl:value-of select="average + max-above-average" />
</xsl:if>
</xsl:template>
<xsl:template name="Max">
<xsl:param name="v1" />
<xsl:param name="v2" />
<xsl:choose>
<xsl:when test="not($v2 > $v1)">
<xsl:value-of select="$v1" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$v2" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
60

XSLT for-each with complex condition

I am transforming following XML to generate HTML.
XML
<clause code="section6">
<variable col="1" name="R1C1" row="1">Water</variable>
<variable col="2" name="R1C2" row="1">true</variable>
<variable col="1" name="R2C1" row="2">Gas</variable>
<variable col="2" name="R2C2" row="2"></variable>
<variable col="1" name="R3C1" row="3">Petrol</variable>
<variable col="2" name="R3C2" row="3">true</variable>
<clause>
XSLT
1: <xsl:for-each select="$clause/variable[#col='1']">
2: <xsl:sort select="#row" data-type="number"/>
3: <xsl:variable name="row-id" select="#row"/>
4: <xsl:variable name="row" select="$clause/variable[#row=$row-id]"/>
5: <xsl:if test="$clause/variable[#col='2' and #row=$row-id]='true'">
6: <xsl:value-of name="row-no" select="concat(position(), ') ')"/>
7: <xsl:value-of select="$clause/variable[#col='1' and #row=$row-id]"/>
8: </xsl:if>
9: </xsl:for-each>
The transformation works fine and shows result 1) Water 3) Petrol
The issue is sequence number. You can see condition on Line 5 filters rows that only have 'true' value in col 2 and position() used for displaying sequence number. I cannot have running counter in XLST.
I was wondering if I can add condition of Line 5 with for-each at Line 1. The result with above example should be 1) Water 2) Patrol any advice?
Does this do what you want?
I drive the for-each selection on col 2 being true. That way position, which equals where we are in the selected set of nodes will equal 2 not 3
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="clause/variable[#col='2' and text()='true']">
<xsl:sort select="#row" data-type="number"/>
<xsl:variable name="row-id" select="#row"/>
<xsl:value-of select="concat(position(), ') ')"/>
<xsl:value-of select="/clause/variable[#col='1' and #row=$row-id]"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Though I'd probably use templates in preference to the for-each and use current() so that we don't need the row-id variable:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="clause/variable[#col='2' and text()='true']">
<xsl:sort select="#row" data-type="number"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="clause/variable[#col='2' and text()='true']">
<xsl:value-of select="concat(position(), ') ')"/>
<xsl:value-of select="/clause/variable[#col='1' and #row=current()/#row]"/>
</xsl:template>
</xsl:stylesheet>
I don't think you can express this with a single XPath 1.0 expression.
In XSLT 1.0 I will use keys and the solution becomes short, elegant and efficient:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kVarCol2" match="variable[#col=2]" use="#row"/>
<xsl:template match="/*">
<xsl:for-each select="variable[#col='1'][key('kVarCol2', #row)='true']">
<xsl:sort select="#row" data-type="number"/>
<xsl:value-of select="concat('
', position(), ') ', .)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be made well-formed):
<clause code="section6">
<variable col="1" name="R1C1" row="1">Water</variable>
<variable col="2" name="R1C2" row="1">true</variable>
<variable col="1" name="R2C1" row="2">Gas</variable>
<variable col="2" name="R2C2" row="2"></variable>
<variable col="1" name="R3C1" row="3">Petrol</variable>
<variable col="2" name="R3C2" row="3">true</variable>
</clause>
the wanted, correct result is produced:
1) Water
2) Petrol
II. XPath 2.0 (single expression) solution:
for $i in 1 to max(/*/*/#row/xs:integer(.))
return
/*/variable[#row eq string($i)]
[#col eq '1'
and
../variable
[#row eq string($i)
and
#col eq '2'
and
. eq 'true'
]
]
/concat('
', position(), ') ', .)
When this XPath 2.0 expression is evaluated on the same XML document (above), the result is the same wanted string:
1) Water
2) Petrol

Checking for values in XSLT and condition

Rephrasing my question properly:
I have XSLT where I need to check for node values for specific data.
E.g. (pseudocode):
If only H occurs then set target element value as 'H'
Else if
Only B or N then set target element value as 'BN'
If Source XML:
<n1>N</n1>
<n1>B</n1>
<n1>N</n1>
Target node:
BN
If Source XML:
<n1>H</n1>
<n1>H</n1>
<n1>H</n1>
Target node:
H
There is no attribute. I have to only set text on meeting the condition in the target node.
Can I use something like:
<xsl:variable name="elements">
<xsl:for-each select="/test/elem">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
</xsl:if>
</xsl:for-each>
</xsl:variable>
Would the above give me concat of all element values and then I can do a check to see the if that contains x then I can do a set text?
When this stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="/root[not(n1 != 'H')]">
<xsl:value-of select="'H'"/>
</xsl:when>
<xsl:when test="/root[not(n1[. != 'B' and . != 'N'])]">
<xsl:value-of select="'BN'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'Something else'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:text>$value = </xsl:text>
<xsl:value-of select="$value"/>
</xsl:template>
</xsl:stylesheet>
is given this input
<root>
<n1>H</n1>
<n1>H</n1>
<n1>H</n1>
</root>
it produces the desired output
$value = H
Similarly with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>N</n1>
</root>
or with
<root>
<n1>N</n1>
<n1>N</n1>
<n1>N</n1>
</root>
we get
$value = BN
And with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>x</n1>
</root>
or with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>H</n1>
</root>
we get
$value = Something else
The string H, BN or Something else is the value of a variable. Instead of just outputting the value of that variable you can of course set it as the contents of a new element you create.

Accessing variables in recursive functions

I am creating a recursive XSLT function that run over all the Xml file child ..
<xsl:call-template name="testing">
<xsl:with-param name="root" select="ItemSet"></xsl:with-param>
</xsl:call-template>
While running the XSLT, I need to get the variable root node value, where I am
because when calling the root variable, I get the node with all the children node, but all I need is the single value of the node, and as it is recursive i can't act on the child node tags because it always changes. So how can I get the variable single specific value in anytime?
Neither $root nor " . " works.
XSL code:
<xsl:variable name="trial" select="count(./*)"></xsl:variable>
<xsl:choose>
<xsl:when test="count(./*) = 0">
:<xsl:value-of select="$root" /> <br/>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="./*">
<xsl:call-template name="testing">
<xsl:with-param name="root" select=".">
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
XML code :
<ItemSet>
<Item>
1
<iteml1>
1.1
</iteml1>
<iteml1>
1.2
</iteml1>
</Item>
<Item>
2
<iteml1>
2.1
<iteml2>
2.1.1
</iteml2>
</iteml1>
</Item>
</ItemSet>
what should if put as a code line in place of * so the solution would show:
1
1: 1.1 :
1: 1.2
2
2: 2.1
2: 2.1: 2.1.2
The wanted processing can be implemented efficiently and without explicit recursion:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="*[parent::*]">
<xsl:param name="pPath"/>
<xsl:value-of select="$pPath"/>
<xsl:variable name="vValue" select=
"normalize-space(text()[1])"/>
<xsl:value-of select="$vValue"/> <br/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select=
"concat($pPath, $vValue, ': ')"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<ItemSet>
<Item>
1
<iteml1>
1.1
</iteml1>
<iteml1>
1.2
</iteml1>
</Item>
<Item>
2
<iteml1>
2.1
<iteml2>
2.1.1
</iteml2>
</iteml1>
</Item>
</ItemSet>
the wanted result is produced:
1<br/>1: 1.1<br/>1: 1.2<br/>2<br/>2: 2.1<br/>2: 2.1: 2.1.1<br/>
and it is displayed in the browser as:
11: 1.11: 1.222: 2.12: 2.1: 2.1.1
You have two choices: If this is only going to be used in one place, you could create a variable in the stylesheet root and use that globally.
Otherwise, what you'll need to do is have two parameters, one of which is passed exactly as is on each call, so as well as calling <xsl:with-param name="root" select="." />, you also add <xsl:with-param name="baseroot" select="$baseroot" /> and define <xsl:param name="baseroot" select="$root" /> immediately below your <xsl:param name="root" />. Then you can use $baseroot in place of $root anywhere that you need the original value that was passed in at the top of the stack.

how to apply space between data in xslt

xml
<block4>
<tag>
<name>50K</name>
<value>
0501/045788775099
Praveen // name will come
MENENDEZ Y PELAYOA CORUNA SPA // address will come
</value>
</tag>
</block4>
i have written a xslt for this above tag but i have facing a problem with replacing remaining length with space
the above value you can see in middle line praveen is there let us assume for this xml message praveen we recieved for another message we cam may be recieve Tom but maximum length is 35 so we need to caluclate the string name value remaining length we should replace with SPACE so i dunno how replace a space over there ...
xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select ="block4/tag[name = '50K']">
<xsl:value-of select="concat(substring(value, 1, 5), ',',substring(substring- before(value,'
'),6), ',',substring-after(value,'
'))" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EXpected OUPUT lIKE:
0501/,045788775099,praveen............................MENENDEZ Y PELAYOA CORUNA SPA
where dots represents space dont assume dots
i need space over there assume think praveen is 7 char and remaining 28 char should make space wantedly in xslt
Try using
<xsl:text> </xsl:text>
The space is between those tags.
For more info: XSLT Controlling Whitespace
let us assume for this xml message
praveen we recieved for another
message we cam may be recieve Tom but
maximum length is 35 so we need to
caluclate the string name value
remaining length we should replace
with SPACE so i dunno how replace a
space over there ...
Use:
substring(concat($vstr, $vBlanks35), 1, 35)
This evaluates to the result of concatenating $vstr ('Praveen') with $vBlanks35 (35 blanks) and then taking the starting 35 characters.
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vstr" select="'Praveen'"/>
<xsl:variable name="vBlanks35" select=
"' '"/>
<xsl:template match="/">
"<xsl:value-of select=
"substring(concat($vstr, $vBlanks35), 1, 35)"/>"
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
"Praveen "
One (universal) way to add space in xml is to use the special xml attribute that preserves spaces:
<value xml:space="preserve">
your
values
here ...
</value>
Another method is to use XSL's preserve/strip space ...
You should use a XSLT version of SQL function RPAD:
<xsl:template name="rpad">
<xsl:param name="text" />
<xsl:param name="length" />
<xsl:param name="char" select="' '" />
<xsl:if test="$length > 0 and string-length($text) > 0">
<xsl:value-of select="$text" />
<xsl:call-template name="rpad">
<xsl:with-param name="text" select="$char" />
<xsl:with-param name="char" select="$char" />
<xsl:with-param name="length" select="$length - string-length($text)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Usage:
<xsl:call-template name="rpad">
<xsl:with-param name="text" select="'your string here'" />
<xsl:with-param name="length" select="35" />
</xsl:call-template>
Optionnally you may specify a char parameter for padding your string with a character other than space.