Data Bars through stylesheet transform? - xslt

i have some data in XML format. For example:
<?xml version="1.0"?>
<TableSizes>
<Table name="AuditLog" rows="13193925" reserved="4896864" data="2522592" indexSize="2373824" unused="448"/>
<Table name="Customers" rows="7021839" reserved="3243392" data="1480640" indexSize="1762640" unused="112"/>
</TableSizes>
i would like that data to be transformed and displayed by the client. So when i feed the User Agent the xml, i supply it a stylesheet:
<?xml version="1.0"?>
<?xml-stylesheet type='text/xsl' href='databaseSize.xslt' media='all'?>
<TableSizes>
<Table name="AuditLog" rows="13193925" reserved="4896864" data="2522592" indexSize="2373824" unused="448"/>
<Table name="Customers" rows="7021839" reserved="3243392" data="1480640" indexSize="1762640" unused="112"/>
</TableSizes>
This causes it to be transformed into some suitable HTML. And that works well enough.
But now i want to add Data Bars:
As it is now, i've been generating the HTML on the server (i.e. the server is deciding how to display content, rather than a stylesheet):
Which requires each table cell to have a custom background style applied, with the computed gradient stops:
<TR>
<TD>AuditLog
<TD style="background: linear-gradient(to right, #658FC6 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">1319,,3925
<TD style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">489,,6864
<TD>252,,2592
<TD>237,,3824
<TD>448
</TR>
<TR>
<TD>Customers
<TD style="background: linear-gradient(to right, #658FC6 0%,rgb(255,255,255) 51%,rgb(255,255,255) 100%)">702,,1839
<TD style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 62%,rgb(255,255,255) 100%)">324,,3392
<TD>148,,0640
<TD>176,,2640
<TD>112
</TR>
Obviously i'd rather have all of this performed in the client by a stylesheet, rather than doing it on the server.
Possible?
Note: If it's not: it's not a problem; just say so.
What XSLT do i have so far?
Essentially, none. The XST i have so far does nothing to add data bars:
<TD><xsl:value-of select="#name"/>
<TD><xsl:value-of select="#rows"/>
<TD><xsl:value-of select="#reserved"/>
<TD><xsl:value-of select="#data"/>
<TD><xsl:value-of select="#indexSize"/>
<TD><xsl:value-of select="#unused"/>
i have add a dummy amount of gradient drawing code:
<TD><xsl:value-of select="#name"/>
<TD style='background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 50%,rgb(255,255,255) 100%)'><xsl:value-of select="#rows"/>
<TD style='background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 50%,rgb(255,255,255) 100%)'><xsl:value-of select="#reserved"/>
<TD><xsl:value-of select="#data"/>
<TD><xsl:value-of select="#indexSize"/>
<TD><xsl:value-of select="#unused"/>
Strictly speaking, i don't even have that XSLT. i didn't want to write an entire XSLT when it cannot accomplish what i want. The XSLT i have above i created on-the-fly when someone suggested that the question isn't work answering until i at least pretend to have some XSLT first.

You can do the calculations pretty easily in XSLT and put that into a style attribute.
I created a template for each AuditLog and Customers. And then in the Customers template I just defined a variable for the previous values in AuditLog to use in the calculation. From there I used each to figure out the percent needed for the gradient(along with some quick 'rounding').
<xsl:value-of select="substring(((#rows div $PrevRow) * 100),1,2)"/>
So when you take your XML and transform it with this XSL
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="Table[#name='AuditLog']">
<tr>
<td>
<xsl:value-of select="#name"/>
</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">
<xsl:value-of select="#rows"/>
</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">
<xsl:value-of select="#reserved"/>
</td>
<td>
<xsl:value-of select="#data"/>
</td>
<td>
<xsl:value-of select="#indexSize"/>
</td>
<td>
<xsl:value-of select="#unused"/>
</td>
</tr>
</xsl:template>
<xsl:template match="Table[#name='Customers']">
<xsl:variable name="PrevRow" select="preceding-sibling::Table/#rows"/>
<xsl:variable name="PrevReserved" select="preceding-sibling::Table/#reserved"/>
<tr>
<td>
<xsl:value-of select="#name"/>
</td>
<td>
<xsl:attribute name="style">
<xsl:text>background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) </xsl:text>
<xsl:value-of select="substring(((#rows div $PrevRow) * 100),1,2)"/>
<xsl:text>%,rgb(255,255,255) 100%)</xsl:text>
</xsl:attribute>
<xsl:value-of select="#rows"/>
</td>
<td>
<xsl:attribute name="style">
<xsl:text>background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) </xsl:text>
<xsl:value-of select="substring(((#reserved div $PrevReserved) * 100),1,2)"/>
<xsl:text>%,rgb(255,255,255) 100%)</xsl:text>
</xsl:attribute>
<xsl:value-of select="#reserved"/>
<br/>
<xsl:value-of select="$PrevReserved"/>
</td>
<td>
<xsl:value-of select="#data"/>
</td>
<td>
<xsl:value-of select="#indexSize"/>
</td>
<td>
<xsl:value-of select="#unused"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
You get this output:
<tr>
<td>AuditLog</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">13193925</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 100%,rgb(255,255,255) 100%)">4896864</td>
<td>2522592</td>
<td>2373824</td>
<td>448</td>
</tr>
<tr>
<td>Customers</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 53%,rgb(255,255,255) 100%)">7021839</td>
<td style="background: linear-gradient(to right, #FF595E 0%,rgb(255,255,255) 66%,rgb(255,255,255) 100%)">3243392<br/>4896864</td>
<td>1480640</td>
<td>1762640</td>
<td>112</td>
</tr>

Related

Using multiple regex in XSLT : how to reduce processing time

I am quite new to programming and XSLT: I try to improve the way I ask questions and explain problems, but I still have a long way to go. Sorry if there is something unclear.
I need to detect various alphabets in my XML document, which looks like this, with a lot more different language options.
<text>
<p>Some text. dise´mbər Some text. Some text.</p> <!-- text in International Phonetic Alphabet + English -->
<p>Some text. dise´mbər Some text. Издательство Академии Наук СССР Some text.</p> <!-- text in International Phonetic Alphabet + English + Cyrillic alphabet -->
<p>Some text. Издательство Академии Наук СССР dise´mbər Some text. Some text.</p>
<p>Some text. Some text. Издательство Академии Наук СССР Some text.</p> <!-- text in English + Cyrillic alphabet -->
</text>
What I started to do in XSLT is this:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="no" encoding="UTF-8" omit-xml-declaration="no" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:for-each select="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="processing-instruction()">
<xsl:processing-instruction name="{local-name()}"><xsl:apply-templates></xsl:apply-templates></xsl:processing-instruction>
</xsl:template>
<xsl:template name="IPA">
<xsl:variable name="text" ><xsl:copy-of select="."/></xsl:variable>
<xsl:analyze-string select="$text" regex="((\p{{IsIPAExtensions}}|\p{{IsPhoneticExtensions}})+)" >
<xsl:matching-substring>
<IPA><xsl:value-of select="regex-group(1)"/></IPA>
</xsl:matching-substring>
<xsl:non-matching-substring><xsl:copy-of select="."></xsl:copy-of></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="Cyrillic">
<xsl:variable name="texte" ><xsl:call-template name="IPA"></xsl:call-template></xsl:variable>
<xsl:analyze-string select="$texte" regex="(\p{{IsCyrillic}}+)" >
<xsl:matching-substring>
<Cyrillic><xsl:apply-templates select="regex-group(1)"/></Cyrillic>
</xsl:matching-substring>
<xsl:non-matching-substring><xsl:call-template name="IPA"></xsl:call-template></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="Cyrillic"></xsl:call-template>
</xsl:template>
</xsl:stylesheet>
So that I could get an XML like this:
<?xml version="1.0" encoding="UTF-8"?><text>
<p>Some text. dise´mb<IPA>ə</IPA>r Some text. Some text.</p>
<p>Some text. dise´mb<IPA>ə</IPA>r Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic> <Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> Some text.</p>
<p>Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic>
<Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> dise´mb<IPA>ə</IPA>r Some text. Some text.</p>
<p>Some text. Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic>
<Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> Some text.</p>
</text>
This is what I needed, however, there is a ten or so regex blocks that I use and the processing time will be quite long if I use this method. What would you do instead? Do you think XSLT is appropriate for this?
Thank you !
Maria
(XSLT 2, Saxon-HE 9.8.0.8)
Edit: here's the profile:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Analysis of Stylesheet Execution Time</title>
</head>
<body>
<h1>Analysis of Stylesheet Execution Time</h1>
<p>Total time: 72128.065 milliseconds</p>
<h2>Time spent in each template, function or global variable:</h2>
<p>The table below is ordered by the total net time spent in the template, function
or global variable. Gross time means the time including called templates and functions
(recursive calls only count from the original entry); net time means time excluding
time spent in called templates and functions.
</p>
<table border="border" cellpadding="10">
<thead>
<tr>
<th>file</th>
<th>line</th>
<th>instruction</th>
<th>count</th>
<th>average time (gross/ms)</th>
<th>total time (gross/ms)</th>
<th>average time (net/ms)</th>
<th>total time (net/ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td> "*code/unicode.xsl" </td>
<td>21</td>
<td>template Greek</td>
<td align="right">2,755,968</td>
<td align="right">0.017</td>
<td align="right">46,854.785</td>
<td align="right">0.017</td>
<td align="right">46,854.785</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>32</td>
<td>template Hebrew</td>
<td align="right">1,329,696</td>
<td align="right">0.043</td>
<td align="right">57,529.163</td>
<td align="right">0.008</td>
<td align="right">10,674.378</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>54</td>
<td>template IPA</td>
<td align="right">333,984</td>
<td align="right">0.206</td>
<td align="right">68,964.076</td>
<td align="right">0.019</td>
<td align="right">6,381.186</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>43</td>
<td>template Cyrillic</td>
<td align="right">665,392</td>
<td align="right">0.094</td>
<td align="right">62,582.890</td>
<td align="right">0.008</td>
<td align="right">5,053.727</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>65</td>
<td>template Arabic</td>
<td align="right">167,068</td>
<td align="right">0.421</td>
<td align="right">70,284.800</td>
<td align="right">0.008</td>
<td align="right">1,320.724</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>76</td>
<td>template Arrows</td>
<td align="right">83,536</td>
<td align="right">0.849</td>
<td align="right">70,945.946</td>
<td align="right">0.008</td>
<td align="right">661.146</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>8</td>
<td>template *</td>
<td align="right">12,122</td>
<td align="right">5.959</td>
<td align="right">72,238.100</td>
<td align="right">0.034</td>
<td align="right">413.937</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>87</td>
<td>template Dingbats</td>
<td align="right">41,768</td>
<td align="right">1.708</td>
<td align="right">71,323.074</td>
<td align="right">0.009</td>
<td align="right">377.128</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>98</td>
<td>template Private</td>
<td align="right">20,884</td>
<td align="right">3.427</td>
<td align="right">71,576.916</td>
<td align="right">0.012</td>
<td align="right">253.842</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>18</td>
<td>template processing-instruction()</td>
<td align="right">6,907</td>
<td align="right">0.014</td>
<td align="right">98.490</td>
<td align="right">0.014</td>
<td align="right">98.490</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>121</td>
<td>template text()</td>
<td align="right">20,884</td>
<td align="right">3.429</td>
<td align="right">71,600.976</td>
<td align="right">0.001</td>
<td align="right">24.060</td>
</tr>
</tbody>
</table>
</body>
</html>
The profile of Martin Honnen's code:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Analysis of Stylesheet Execution Time</title>
</head>
<body>
<h1>Analysis of Stylesheet Execution Time</h1>
<p>Total time: 2900.594 milliseconds</p>
<h2>Time spent in each template, function or global variable:</h2>
<p>The table below is ordered by the total net time spent in the template, function
or global variable. Gross time means the time including called templates and functions
(recursive calls only count from the original entry); net time means time excluding
time spent in called templates and functions.
</p>
<table border="border" cellpadding="10">
<thead>
<tr>
<th>file</th>
<th>line</th>
<th>instruction</th>
<th>count</th>
<th>average time (gross/ms)</th>
<th>total time (gross/ms)</th>
<th>average time (net/ms)</th>
<th>total time (net/ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td> "*code/unicode.xsl" </td>
<td>44</td>
<td>template text()</td>
<td align="right">222,968</td>
<td align="right">0.009</td>
<td align="right">1,949.720</td>
<td align="right">0.009</td>
<td align="right">1,949.720</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>26</td>
<td>template text()</td>
<td align="right">20,884</td>
<td align="right">0.135</td>
<td align="right">2,823.597</td>
<td align="right">0.042</td>
<td align="right">873.877</td>
</tr>
</tbody>
</table>
</body>
</html>
Regular expressions such as \p{IsIPAExtensions} should be reasonably efficient: most of the blocks are a single consecutive range of codepoints and testing a character should simply check whether it is in that range. The cost, I suspect, arises not so much from the cost of checking one character against one Unicode block, but from the number of characters and the number of blocks.
It might be worth getting a profile at the Java level to see where it is spending its time. I can guess, but a profile would reveal if my guess is right.
The thing that can kill performance with regular expressions is backtracking, but I don't immediately see any risk of backtracking with this code.
The only other approach that comes to mind is to generate an enormous translate() call that classifies characters into groups (so all latin characters become "1", all Cyrillic characters become "2", etc) and then to process the result using `<xsl:for-each-group select="string-to-codepoints(.)" group-adjacent=".">. But there's no guarantee that would perform any better, and it's a lot of work to do the experiments to find out.
In XSLT 3, I would consider the following approach:
<?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"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="scripts"
as="map(xs:string, xs:string)*"
select="map { 'Cyrillic' : '\p{IsCyrillic}+'},
map { 'IPA' : '[\p{IsIPAExtensions}\p{IsPhoneticExtensions}]+' }"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="text()">
<xsl:iterate select="$scripts">
<xsl:param name="input" select="."/>
<xsl:on-completion>
<xsl:sequence select="$input"/>
</xsl:on-completion>
<xsl:next-iteration>
<xsl:with-param name="input">
<xsl:apply-templates select="$input" mode="wrap">
<xsl:with-param name="script-map" tunnel="yes" select="."/>
</xsl:apply-templates>
</xsl:with-param>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
<xsl:mode name="wrap" on-no-match="shallow-copy"/>
<xsl:template match="text()" mode="wrap">
<xsl:param name="script-map" tunnel="yes"/>
<xsl:analyze-string select="." regex="{$script-map?*}">
<xsl:matching-substring>
<xsl:element name="{map:keys($script-map)}">
<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>
I haven't measured whether it performs better but for the regular expressions I would [\p{IsIPAExtensions}\p{IsPhoneticExtensions}]+ consider to be easier than (\p{IsIPAExtensions}|\p{IsPhoneticExtensions})+.
The other improvements are to rely on the xsl:mode based identity transformation and xsl:iterate.

xsl counting and xpath syntax

I have a table of projects created in SharePoint 2010. I am trying to create reporting for management and need to get counts of various fields. I have used xsl to get the values of fields when viewing single items, so I am fairly familiar with the language. However, I cannot find a good explanation of the syntax for counting multiple items.
I have a table like this:
<table>
<tr>
<th class="ms-vb2">
Project Title
</th>
<th class="ms-vb2">
Project Leader
</th>
<th class="ms-vb2">
Project Status
</th>
</tr>
<tr>
<td class="ms-vb2">
Project Title 1
</td>
<td class="ms-vb2">
Project Leader 1
</td>
<td class="ms-vb2">
Completed
</td>
</tr>
<tr>
<td class="ms-vb2">
Project Title 2
</td>
<td class="ms-vb2">
Project Leader 1
</td>
<td class="ms-vb2">
Withdrawn
</td>
</tr>
<tr>
<td class="ms-vb2">
Project Title 3
</td>
<td class="ms-vb2">
Project Leader 2
</td>
<td class="ms-vb2">
Completed
</td>
</tr>
<!--About 100 more rows-->
</table>
There is a lot of nesting going on, so I am having difficulty targeting specific areas and I have very little control over the html due to this being generated by SharePoint.
Here is the reporting table I am trying to create using XSL:
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>
<xsl:value-of select="count(../td[#ms-vb2='Completed'])" /><!--Should be 2-->
</td>
<td>
<xsl:value-of select="count(../td[#ms-vb2='Withdrawn'])" /><!--Should be 1-->
</td>
<td>
<xsl:value-of select="count(../td[#ms-vb2='On Hold'])" /><!--Should be 0-->
</td>
</tr>
</table>
I know there is a problem with my XPATH syntax, but I cannot figure it out.
A complete XSLT would look 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 method="html" indent="yes" />
<xsl:template match="/table">
<html>
<body>
<table id="FourBlockerHead" class="ClearBlockFloat" border="1">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<xsl:for-each select="tr">
<tr>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='Completed'])" /><!--Should be 2-->
</td>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='Withdrawn'])" /><!--Should be 1-->
</td>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='On Hold'])" /><!--Should be 0-->
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
You can include this in your source XML file with
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="source.xslt"?>
in your XML file to get a well formatted HTML output.
The output would look like this:
Instead of:
<xsl:value-of select="count(../td[#ms-vb2='Completed')" />
try:
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Completed'])" />
and likewise for the other two.
Notes:
You did not provide the context, so I have changed the path to an absolute one, that counts all nodes in the entire document;
You don't have an attribute named ms-vb2;
You need to trim the whitespace in the data cell before comparing ot to a string with no whitespace.
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="table">
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Completed'])" />
</td>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Withdrawn'])" />
</td>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='On Hold'])" />
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
applied to your input example, will return:
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>0</td>
</tr>
</table>

Move a string to a header element from child only if the string exists in all the child elements using XSLT

My XML code & XSLT code
explanation :
I'm trying to move the string '2' from the elements with
(tr[#type='detail'] and td[#column='1'])
to the category header
(tr [#type='categoryhead' and level='2'])
Any help on this is greatly appreciated
Thanks a ton
<!--=============My XML=============-->
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1,2)</td>
<td colname="2">500</td>`enter code here`
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,2,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
XSLT where I'm trying to move the string '2' from the elements with (tr[#type='detail'] and td[#column='1'])to the category header (tr [#type='categoryhead' and level='2'])
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://mynamespace.com" version="2.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #* "/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td">
<xsl:for-each select="//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)]">
<xsl:variable name="IsFooted" select="contains(.,$arg1)"></xsl:variable>
<xsl:value-of select="count(//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)])"/>
<xsl:choose>
<xsl:when test="$IsFooted='true'">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Desired XML Output:
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages (2)</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1)</td>
<td colname="2">500</td>
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
The terminology is not quite clear here, as looking at the output sample, all that is happening is a "(2)" is being appended to a particular table cell, so it is not really moving anything.
Also, your question title of the question mentions about child elements, but looking at the XML structure, the "detail" rows are actually siblings of the "categoryhead" rows. This is probably where the problem lies; how do you correlate the "detail" rows with the associated "categoryheader". One way to do this could be using a key
<xsl:key name="row"
match="a:tr[#level != '1']"
use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
This gets the rows (other than at level 1) and groups them by the first preceding row with a lowel #level attribute.
Now, for your template match, because you are changing "td" elements, I would change the template to match such an element, rather than the "tr" element
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
You can then use the key to get the 'child' elements, but instead of thinking in terms of if all child elements contain a '2', reverse the logic and check whether any child eleent doesn't contain a '2'
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://composition.bowne.com/2010/v4" version="1.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<xsl:key name="row" match="a:tr[#level != '1']" use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
<xsl:variable name="IsMissing" select="key('row', generate-id(..))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As an aside, the namespace in your XSLT does not match the namespace in your XML. You would need these to match for this to work, but I assume this is a typo.
EDIT: To remove the '2' from the 'detail' rows, try adding the following template. The 'isMissing' variable is a bit messier, because it has to find the associated 'categoryhead' first. Also note it uses 'replace' which is only available in XSLT 2.0.
<xsl:template match="a:tbody/a:tr[#type='detail']/a:td[#colname='1']">
<xsl:variable name="parentLevel" select="../#level - 1" />
<xsl:variable name="IsMissing" select="key('row', generate-id(../preceding-sibling::a:tr[#level=$parentLevel][1]))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="replace(., concat(',', $arg1), '')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Extracting the attributes when the occurance of them is not particular

I am working on the extracting the values background color and width attributes from "td" of a table.
There are several ways of occurance of Back ground color and width. T
I have following set of valid elements.
1.<td style="BACKGROUND-COLOR: yellow; WIDTH: 52%"></td>
(combination of BACKGROUND-COLOR and Width in one order)
2.<td style="WIDTH: 52%;BACKGROUND-COLOR: green"></td>
(combination of BACKGROUND-COLOR and Width in another order)
3.<td style="WIDTH:52%;BACKGROUND-COLOR: green"></td>
(Spaces could vary from ":" and value)
4.<td style="BACKGROUND-COLOR: gray"></td>
(only BACKGROUND-COLOR in style)
5.<td style="BACKGROUND-COLOR: Gray"></td>
(Value of BACKGROUND-COLOR can be case sensitive)
6.<td style="BACKGROUND-COLOR: #ffff00"></td>
(value of BACKGROUND-COLOR can be hexadecimal also)
7.<td bgcolor="#008000" style="WIDTH: 54%">
(BACKGROUND-COLOR can occur as bgcolr also(hexadecimal code)
8.<td bgcolor="yellow">
(BACKGROUND-COLOR can occur as bgcolr also(string))
List of valid colors and their codes:(all the values are case sensitive)
Yellow:#ffff00
Gray:#808080
Green:#008000
Output:
1.<bgclr>Yellow</bgclr>
<colwidth>52</colwidth>
2.<bgclr>Green</bgclr>
<colwidth>52</colwidth>
3. <bgclr>Green</bgclr>
<colwidth>52</colwidth>
4.<bgclr>Gray</bgclr>
5.<bgclr>Gray</bgclr>
6.<bgclr>Yellow</bgclr>
7.<bgclr>Green</bgclr>
<colwidth>54</colwidth>
8.<bgclr>Yellow</bgclr>
I have tried my level best to solve this, and it looks complicated for me.
I am also providing the valid xml file.
<tr>
<td style="BACKGROUND-COLOR: yellow; WIDTH: 52%"></td>
<td style="WIDTH: 52%;BACKGROUND-COLOR: green"></td>
<td style="WIDTH:52%;BACKGROUND-COLOR: green"></td>
<td style="BACKGROUND-COLOR: gray"></td>
<td style="BACKGROUND-COLOR: Gray"></td>
<td style="BACKGROUND-COLOR: #ffff00"></td>
<td bgcolor="#008000" style="WIDTH: 54%"></td>
<td bgcolor="yellow"></td>
</tr>
Can any one help on this.
Thanks.
We can leverage the power of xsl:apply-templates and predicates to avoid a lot of painful xsl:choose statements. A template approach will also give us a more modular solution.
This XSLT 1.0 style-sheet ...
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:template match="/">
<tr>
<xsl:apply-templates select="*/td"/>
</tr>
</xsl:template>
<xsl:template match="td">
<td>
<xsl:apply-templates
select="self::*[contains(#style,'BACKGROUND-COLOR:') or #bgcolor]"
mode="bg-colour"/>
<xsl:apply-templates
select="self::*[contains(#style,'WIDTH:')]"
mode="width"/>
</td>
</xsl:template>
<xsl:template match="td[#bgcolor]" mode="bg-colour">
<xsl:call-template name="render-bg-colour">
<xsl:with-param name="raw-colour" select="#bgcolor" />
</xsl:call-template>
</xsl:template>
<xsl:template match="td" mode="bg-colour">
<xsl:call-template name="render-bg-colour">
<xsl:with-param name="raw-colour" select="
substring-before( substring-after(concat(#style,';'),'BACKGROUND-COLOR:'), ';')" />
</xsl:call-template>
</xsl:template>
<xsl:variable name="palette">
<colours>
<colour>
<code>#ffff00</code>
<name>Yellow</name>
</colour>
<colour>
<code>#808080</code>
<name>Gray</name>
</colour>
<colour>
<code>#008000</code>
<name>Green</name>
</colour>
</colours>
</xsl:variable>
<xsl:template name="render-bg-colour">
<xsl:param name="raw-colour" />
<xsl:variable name="trim-colour" select="normalize-space( $raw-colour)" />
<xsl:variable name="canon-colour" select="
document('')//xsl:variable[#name='palette']/colours/colour[
(translate($trim-colour, $uppercase, $lowercase) =
translate(name , $uppercase, $lowercase) ) or
($raw-colour = code)
]/name/text()"/>
<bgclr>
<xsl:value-of select="$canon-colour" />
</bgclr>
</xsl:template>
<xsl:template match="td" mode="width">
<colwidth>
<xsl:value-of select="
normalize-space( substring-before( substring-after(#style,'WIDTH:'), '%'))" />
</colwidth>
</xsl:template>
</xsl:stylesheet>
... when applied to this input document ...
<tr>
<td style="BACKGROUND-COLOR: yellow; WIDTH: 52%"></td>
<td style="WIDTH: 52%;BACKGROUND-COLOR: green"></td>
<td style="WIDTH:52%;BACKGROUND-COLOR: green"></td>
<td style="BACKGROUND-COLOR: gray"></td>
<td style="BACKGROUND-COLOR: Gray"></td>
<td style="BACKGROUND-COLOR: #ffff00"></td>
<td bgcolor="#008000" style="WIDTH: 54%"></td>
<td bgcolor="yellow"></td>
</tr>
... will yield this output document...
<tr>
<td>
<bgclr>Yellow</bgclr>
<colwidth>52</colwidth>
</td>
<td>
<bgclr>Green</bgclr>
<colwidth>52</colwidth>
</td>
<td>
<bgclr>Green</bgclr>
<colwidth>52</colwidth>
</td>
<td>
<bgclr>Gray</bgclr>
</td>
<td>
<bgclr>Gray</bgclr>
</td>
<td>
<bgclr>Yellow</bgclr>
</td>
<td>
<bgclr>Green</bgclr>
<colwidth>54</colwidth>
</td>
<td>
<bgclr>Yellow</bgclr>
</td>
</tr>

XML to table, how to get position() of context node to the specified ancestor(s)

I have such xml:
<A1>
<B1>
<C1>
<C2>
<C3>
</B1>
<B2>
<C4>
<C5>
<C6>
</B2>
</A1>
<A2>
<B3>
<C7>
<C8>
<C9>
</B3>
<B4>
<C10>
<C11>
<C12>
</B4>
</A2>
I need to transform it to table with nested rows:
<table border="yes">
<tr>
<td>A1</td>
<td>B1</td>
<td>C1</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C2</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td>B2</td>
<td>C3</td>
</tr>
<tr>
<td></td>
<td></td>
<td>C4</td>
</tr>
A and B appear only if they are new (not repeating in every row);
I'm trying to use position()
<xsl:template match="c">
<tr>
<td>
<xsl:if test="IT IS THE FIRST C IN A">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="position(1)">
<xsl:value-of select="parent"/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
It seems that we should emulate position() for ancestor.
Is there a general solution for any number of nested rows?
You perhaps need something like the following (if I understood your question correctly):
<xsl:template match="C">
<tr>
<td>
<xsl:if test="generate-id(ancestor::A/descendant::C[1]) = generate-id(.)">
<xsl:value-of select="ancestor::A"/>
</xsl:if>
</td>
<td>
<xsl:if test="not(previous-sibling::C)">
<xsl:value-of select=".."/>
</xsl:if>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="descendant::C"/>
</xsl:template>
Edit: you may also use
not(previous-sibling::C) and not(../previous-sibling::B)
as the first test (instead of using generate-id()).