demerge the values between merged cells - xslt

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).

Related

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 Count() between two values

I am trying to get a count between range of 100 to 199 (both are inclusive) for an attribute, but I am unable to join two results. Please help me out
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title code="120">Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title code="200">Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title code="100">Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>19.90</price>
<year>1982</year>
</cd>
</catalog>
The XSLT for above XML is
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:value-of select="count(//cd/title[#code >= 100 and 199 <= #code])"/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Please help me out to resolve this problem
Just use > for both tests:
count(//cd/title[#code >= 100 and 199 >= #code])
> shouldn't have to be escaped as >. It's fine either way.
Working example: http://xsltransform.net/ej9EGcn

Demerge the merged cell in 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.

Retrieve value of 5th to 10th titles in a collection of 15-16 titles using XSLT

Below is my XML file and I would like to retrieve titles 3 to 4 of the XML file using some form of count function with XSLT. Please help... thanks for your help
<?xml version="1.0">
<catalog>
<cd>
<title>Empire Burlesque</title>
</cd>
<cd>
<title>Hide your heart</title>
</cd>
<cd>
<title>Greatest Hits</title>
</cd>
<cd>
<title>Still got the blues</title>
</cd>
</catalog>
Try this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<result>
<cd><xsl:value-of select="catalog/cd[3]/title"/></cd>
<cd><xsl:value-of select="catalog/cd[4]/title"/></cd>
</result>
</xsl:template>
</xsl:stylesheet>
You are looking for the position() XPath function.
For example:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<result>
<xsl:copy-of
select="catalog/cd[position() >= 3 and position() <= 4]/title"/>
</result>
</xsl:template>
</xsl:stylesheet>
This short and completely "push style" transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="cd/node()"/>
<xsl:template match="cd[position() >= 3 and 4 >= position()]/title">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<catalog>
<cd>
<title>Empire Burlesque</title>
</cd>
<cd>
<title>Hide your heart</title>
</cd>
<cd>
<title>Greatest Hits</title>
</cd>
<cd>
<title>Still got the blues</title>
</cd>
</catalog>
produces the wanted, correct result:
<title>Greatest Hits</title>
<title>Still got the blues</title>
Explanation:
The empty-bodied template <xsl:template match="cd/node()"/> prevents the processing ("deletes") of any child of a cd .
The second template overrides the first only for a title child of a cd whose position() is not less than 3 and not greater than 4. It effectively copies the matched title element.
The <xsl:strip-space elements="*"/> instruction makes all this possible by deleting from the XML document all white-space-only text nodes. In this way the positions of cd elements in the node-list formed by the <xsl:apply-templates> instruction (in the built-in XSLT template for elements) would be 1, 2, 3, 4 and not 2, 4, 6, 8.