Demerge the merged cell in XSLT - xslt

Can someone help me with the below xslt question.
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="T">1</price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="F">Title 2</title>
<artist rowmerge="F">Sample 2</artist>
<price rowmerge="T"></price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="F">Title 3</title>
<artist rowmerge="F">Sample 3</artist>
<price rowmerge="F">3</price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="T">Title 4</title>
<artist rowmerge="F">sample 4</artist>
<price rowmerge="T">4</price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="T"></title>
<artist rowmerge="F">Sample 5</artist>
<price rowmerge="T"></price>
<year>1988</year>
</cd>
</catalog>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="f">1</price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="F">Title 2</title>
<artist rowmerge="F">Sample 2</artist>
<price rowmerge="f">1</price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="F">Title 3</title>
<artist rowmerge="F">Sample 3</artist>
<price rowmerge="F">3</price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="F">Title 4</title>
<artist rowmerge="F">sample 4</artist>
<price rowmerge="F">4</price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="F">Title 4</title>
<artist rowmerge="F">Sample 5</artist>
<price rowmerge="F">4</price>
<year>1988</year>
</cd>
</catalog>
If rowmerge attribute is 'T' in the first cd for any tag (title/artist/price)then I need to copy the price value from first cd to next cd. I am new to xslt.

You first should read up on the XSLT Identity Template, which on its own will copy nodes from the source document to the output.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
What this means is that you only need to write templates for the nodes you wish to transform. Considering just the price elements for now, you are trying to amend price elements which have a rowmerge set to "T" and which are empty. In this case, you want to copy the price from the first most preceding 'cd'. This is achieved like so:
<xsl:template match="price[#rowmerge='T'][not(normalize-space())]">
<price>
<xsl:apply-templates select="#*|node()"/>
<xsl:value-of select="../preceding-sibling::*[1]/price" />
</price>
</xsl:template>
So, it is very similar to the identity template, but it has the extra xsl:value-of statement to copy the value from the preceding node. Or rather the price value from the preceding node of the parent cd element.
Of course, you could repeat this template for each of the possible child elements of cd, but this would be a lot of repetitive coding. Better would be to have a more generic template to cover all cases:
<xsl:template match="*[#rowmerge='T'][not(normalize-space())]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:value-of select="../preceding-sibling::*[1]/*[name() = name(current())]" />
</xsl:copy>
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*[#rowmerge='T'][not(normalize-space())]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:value-of select="../preceding-sibling::*[1]/*[name() = name(current())]" />
</xsl:copy>
</xsl:template>
<xsl:template match="#rowmerge[. = 'T']">
<xsl:attribute name="rowmerge">F</xsl:attribute>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Do note there is also a template to convert the rowmerge attributes from having a value of T to F.
EDIT: In answer to your comment, if you have to look more than one sibling back (that is to say, you have two consecutive elements that are empty), then try one of these two expressions
<xsl:value-of select="../preceding-sibling::*[*[name() = name(current())][normalize-space()]][1]/*[name() = name(current())]" />
<xsl:value-of select="(../preceding-sibling::*/*[name() = name(current())][normalize-space()])[last()]" />
EDIT 2: If the nodes contain more than just text, then to copy all the child elements, you use xsl:copy-of instead of xsl:value-of. For example...
<xsl:copy-of select="../preceding-sibling::*[1]/*[name() = name(current())]/node()" />
Note the use of node() on the end, to ensure only the child nodes are copied, not the price (for example) element itself.

Related

How to select the preceding-sibling inside a for each loop?

My XML
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>
<name>Empire Burlesque1 </name>
</title>
</cd>
<cd>
<title>
<name>Empire Burlesque 2</name>
</title>
</cd>
<cd>
<title>
<name>Empire Burlesque 2</name>
</title>
</cd>
</catalog>
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="catalog/cd">
<xsl:call-template name="currentValue" />
<xsl:call-template name="prevValue" />
</xsl:for-each>
</xsl:template>
<xsl:template name="currentValue">
<xsl:value-of select="title/name" />
</xsl:template>
<xsl:template name="prevValue">
<xsl:value-of select="preceding-sibling::title[name][1]" />
</xsl:template>
</xsl:stylesheet>
The preceding sibling does not print anything. I want to store both in different variables and compare them. Can you help me pointing what is wrong here?
Currently, when you are checking for the preceding element you are positioned on cd element, and that only has other cd elements as siblings. Therefore, the expression you want is this:
<xsl:value-of select="preceding-sibling::cd[1]/title/name" />

XSLT sum concatenated instead of summed

I have the following input
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<carac NAME="aaa" NOT="10"/>
<value VAL="1"/>
</cd>
<cd>
<carac NAME="aaa" NOT="10"/>
<value VAL="2"/>
</cd>
<cd>
<carac NAME="aaa" NOT="20"/>
<value VAL="3"/>
</cd>
<cd>
<carac NAME="aaa" NOT="10"/>
<value VAL="4"/>
</cd>
<cd>
<carac NAME="bbb" NOT="30"/>
<value VAL="5"/>
</cd>
<cd>
<carac NAME="bbb" NOT="30"/>
<value VAL="6"/>
</cd>
<cd>
<carac NAME="ccc" NOT="40"/>
<value VAL="7"/>
</cd>
<cd>
<carac NAME="ccc" NOT="50"/>
<value VAL="8"/>
</cd>
</catalog>
and I want to get for every different NAME the sum of all the different NOT, so if for the same NAME the NOT is repeated, it has to be summed only once.
The output for this example has to be: aaa30 bbb30 ccc90
My XSL looks like this, but instead of giving the result I want is showing aaa1020 bbb30 ccc4050
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="nameList" match="catalog/cd/carac" use="#NAME"/>
<xsl:key name="notList" match="catalog/cd/carac" use="concat(#NAME,'_',#NOT)"/>
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="//carac[generate-id()=generate-id(key('nameList', #NAME)[1])]">
<xsl:variable name="name" select="./#NAME"/>
<xsl:variable name="lines">
<xsl:for-each select="//carac[generate-id()=generate-id(key('notList',concat($name,'_',#NOT))[1])]">
<noti>
<xsl:value-of select="#NOT"/>
</noti>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$name"/>
<xsl:value-of select="sum($lines)"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Try it this way?
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="nameList" match="catalog/cd/carac" use="#NAME"/>
<xsl:key name="notList" match="catalog/cd/carac" use="concat(#NAME, '_', #NOT)"/>
<xsl:template match="/catalog">
<html>
<body>
<xsl:for-each select="cd/carac[generate-id()=generate-id(key('nameList', #NAME)[1])]">
<xsl:value-of select="#NAME"/>
<xsl:variable name="current-group" select="key('nameList', #NAME)" />
<xsl:value-of select="sum($current-group[generate-id()=generate-id(key('notList', concat(#NAME, '_', #NOT))[1])]/#NOT)"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

How to wrap input xsml in c data using xslt

input to the xslt will be like below:
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
Output of the xsl should be like below:
<Output>
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
]]>
</Output>
I have written below code but < and > are not replacing with < > in the output.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="output">
<output>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:text disable-output-escaping="yes"> <?xml version="1.0" encoding="UTF-8"?> </xsl:text>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</output>
</xsl:variable>
<xsl:copy-of select="$output"/>
</xsl:template>
</xsl:stylesheet>
Need to create CDATA out of variable like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="output">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
</xsl:variable>
<output>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:text disable-output-escaping="yes">
</xsl:text>
<xsl:text disable-output-escaping="yes"><?xml version="1.0" encoding="UTF-8"?></xsl:text>
<xsl:copy-of select="$output"/>
<xsl:text disable-output-escaping="yes">
]]></xsl:text>
</output>
</xsl:template>
</xsl:stylesheet>
CDATA sections are really just an alternate escaping mechanism: at an XML level your desired result is the <Output/> element with text content that happens to be the serialized output. DataPower includes a <dp:serialize/> extension that can do this.
I think a working stylesheet should look something like
<?xml version="1.0"?>
<xsl:stylesheet
version="1.0"
extension-element-prefixes="dp"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions">
<xsl:output method="xml" cdata-section-elements="Output"/>
<xsl:template match="/">
<Output>
<dp:serialize select="."/>
</Output>
</xsl:template>
</xsl:stylesheet>
(This has always been a little bit odd construction, because you can embed XML in XML directly; if you control the application and schema you might consider trying to change it so you don't need to XML parse the text content of an XML element.)

xslt copy-of to different namespace in the resulting document

In my xslt code, I can use copy-of to copy the element and its descendant nodes, but the catch is that the resulting schema though has the same structure has a different namespace. Is there any way I can still use copy-of and accomplish this? I am using XSLT 2.0
Following is the example of Source and Target XMLs, the cd elements can be copied using copy-of in XSL, but they have different namespaces.
Source XML
<catalog xmlns="namespace1">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
Target XML
<books xmlns="namespace2">
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</books>
Used Martin Honnen's idea and created this
<xsl:template name="changeNamespace">
<xsl:param name="nodes"/>
<xsl:for-each select="$nodes">
<xsl:choose>
<xsl:when test="count(child::*)>0">
<xsl:element name="newnamespace:{local-name()}">
<xsl:call-template name="changeNamespace">
<xsl:with-param name="nodes" select="./child::*"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="newnamespace:{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
You can't use xsl:copy or xsl:copy-of to change the namespace of a node, you need to transform the nodes with e.g.
<xsl:template match="ns1:*">
<xsl:element name="ns2:{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
which then assumes you have e.g. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="source-namespace" xmlns:ns2="target-namespace" version="1.0"> in scope or you can of course also put the target namespace into the xsl:element:
<xsl:template match="ns1:*">
<xsl:element name="ns2:{local-name()}" namespace="target-namespace">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
If you have attributes where you need to change the namespace you need to set up a similar template for attribute nodes.
With XSLT 2.0 you can simplify the stylesheet structure with e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="namespace1" xmlns="namespace2" version="2.0">
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>

demerge the values between merged cells

Can someone help me with the below xslt issue.
I have table with 5 rows.
First 3 cells(1,2,3) are merged and first cell has value.
Next 2 cells(4,5) are merged and fourth cell has value.
In this case my xslt is working fine for the sample input.ie) if the cells are merged and its first cell has value then the xslt is working.
But incase if fourth cell dont have any value then xslt is not working fine.
how to find how many cells are merged together?
If rowspan =3 then three cells are merged. Also if rowspan=0 then this cell is merged with previous row.
If rowapsn=1 then cell is not merged.
Also if rowmerged='T' then the cell is merged and rowmerged='F' means cell is not merged
Sample input:
<catalog>
<cd>
<title rowmerge="F">Title 1</title>
<artist rowmerge="F">sample 1</artist>
<price rowmerge="T" rowspan="3" >1</price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="F">Title 1</title>
<artist rowmerge="F">sample 1</artist>
<price rowmerge="T" rowspan="0"></price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="F">Title 3</title>
<artist rowmerge="F">Sample 3</artist>
<price rowmerge="T" rowspan="0"></price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="T">Title 4</title>
<artist rowmerge="F">sample 4</artist>
<price rowmerge="T" rowspan="2"></price>
<year >1985</year>
</cd>
<cd>
<title rowmerge="T"></title>
<artist rowmerge="F">Sample 5</artist>
<price rowmerge="T" rowspan="0"></price>
<year>1988</year>
</cd>
</catalog>
Expected output:
<?xml version="1.0"?>
<catalog>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="F" rowspan="3">1</price>
<year>1985</year>
</cd>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="F" rowspan="0">1</price>
<year>1985</year>
</cd>
<cd>
<title rowmerge="F">Title 3</title>
<artist rowmerge="F">Sample 3</artist>
<price rowmerge="F" rowspan="0">1</price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="F">Title 4</title>
<artist rowmerge="F">sample 4</artist>
<price rowmerge="F" rowspan="2"/>
<year>1985</year>
</cd>
<cd>
<title rowmerge="F"/>
<artist rowmerge="F">Sample 5</artist>
<price rowmerge="F" rowspan="0"/>
<year>1988</year>
</cd>
</catalog>
tried with below xslt:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*[#rowmerge='T'][not(normalize-space())] [#rowspan= 0] ">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:value-of select="(../preceding-sibling::*/*[name() = name(current())][normalize-space()])[last()]/node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="#rowmerge[. = 'T']">
<xsl:attribute name="rowmerge">F</xsl:attribute>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
What i am getting:
<?xml version="1.0"?>
<catalog>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="F" rowspan="3">1</price>
<year>1985</year>
</cd>
<cd>
<title rowmerge="f">Title 1</title>
<artist rowmerge="f">sample 1</artist>
<price rowmerge="F" rowspan="0">1</price>
<year>1985</year>
</cd>
<cd>
<title rowmerge="F">Title 3</title>
<artist rowmerge="F">Sample 3</artist>
<price rowmerge="F" rowspan="0">1</price>
<year>1988</year>
</cd>
<cd>
<title rowmerge="F">Title 4</title>
<artist rowmerge="F">sample 4</artist>
<price rowmerge="F" rowspan="2"/>
<year>1985</year>
</cd>
<cd>
<title rowmerge="F"/>
<artist rowmerge="F">Sample 5</artist>
<price rowmerge="F" rowspan="0">1</price>
<year>1988</year>
</cd>
</catalog>
You can achieve this with a small change to one of your predicates:
<xsl:template match="*[#rowmerge='T'][not(normalize-space())] [#rowspan= 0] ">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:value-of select="(../preceding-sibling::*/*[name() = name(current())]
[#rowspan != 0])[last()]" />
</xsl:copy>
</xsl:template>
I've changed the [normalize-space()] to [#rowspan != 0] - the "head" cell for this span is not necessarily the nearest preceding one with a non-empty value, but it will always be the nearest preceding one with a non-zero rowspan.
(I've also removed the redundant /node() from the end of the value-of).