XSLT - Remove specific value from attribute but keep other values - regex

I have following XML input:
<table>
<tbody>
<tr>
<td style="width: 10px; margin-left: 10px;">td text</td>
<td style="color: red; width: 25px; text-align: center; margin-left: 10px;">
<span>span text</span>
</td>
</tr>
</tbody>
</table>
Please note that I have other nodes in the same document that should not be touched.
I want to remove certain attribute values from an element (in this case from td).
Let's say I want to remove the width value within a style attribute.
I don't know where in the style-attribute the width-value is set, it could be anywhere.
The span in the td doesn't really matter (this and some other elements are there in the input).
I expect the output to be like this:
<table>
<tbody>
<tr>
<td style="margin-left: 10px;">td text</td>
<td style="color: red; text-align: center; margin-left: 10px;">
<span>span text</span>
</td>
</tr>
</tbody>
</table>
I prefer using XSLT1, I did not bring the replace() function to work yet (but maybe I am doing something wrong).
I tried using this XSLT:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td/#style">
<xsl:attribute name="style">
<xsl:value-of select="replace(., 'width:.[[:digit:]]+px;', '')" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:template>
I am still a beginner in XSLT and this above doesn't work and I did not find a solution here.
Also, I don't know the width-value so I would need to replace the value with a regex (I used "width:.[[:digit:]]+px;") or something.
Is there maybe a easier method that can replace every specific value? So I could remove text-align aswell without having to think of a new regex?
I really hope that you can help me with this (surely easy) problem.
Thank you in advance!

Let's say I want to remove the width value within a style attribute. I
don't know where in the style-attribute the width-value is set, it
could be anywhere.
Try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td/#style[contains(., 'width:')]">
<xsl:attribute name="style">
<xsl:value-of select="substring-before(., 'width:')" />
<xsl:value-of select="substring-after(substring-after(., 'width:'), ';')" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Note:
I want to remove certain attribute values from an element (in this
case from td).
Actually, what you want is to remove certain properties from the style attribute. The above will work for removing a single property; if you want to remove more than one, you'll have to use a recursive template to do it.
Added:
Will there be an issue if the style contains border-width:1px as
this become border-?
Yes, this could be a problem. A possible solution would be:
<xsl:template match="td/#style">
<xsl:variable name="style" select="concat(' ', .)" />
<xsl:choose>
<xsl:when test="contains($style, ' width:')">
<xsl:attribute name="style">
<xsl:value-of select="substring-before($style, ' width:')" />
<xsl:value-of select="substring-after(substring-after($style, ' width:'), ';')" />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
However, this assumes that the ; separator in the source document is always followed by a space (as it is in the given example). Otherwise it gets more complicated.

Assuming you are using XSLT 2.0 (as replace is not supported in 1.0) you can use \d to match a digit in regular expressions, so you can write your pattern like so:
<xsl:value-of select="replace(., '( | $)width:\s*\d*px;?', '')" />
Note the \s* is used to match zero or more characters of whitespace, so allow for width:10px or width: 10px. Also not ( | $) is used to ensure a space before width (or if it is at the start), so that properties like border-width are not matched.
If you wanted to handle units other than px you could do this...
<xsl:value-of select="replace(., '( | $)width:[^;]+;?', '')" />
Read up on regular expressions at http://www.xml.com/pub/a/2003/06/04/tr.html.

Related

XPath: Cant get the right value out of a selected Tag

this is my first question here on stackoverflow, so please dont kill me.
But lets get right to the problem. I have a given xhtml and need to get specific information out of it.
Here is my xsl file so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:output method="xml" indent="yes" encoding="UTF-8"
omit-xml-declaration="no" />
<xsl:template match="html">
<xsl:element name="Speisekarte">
<xsl:attribute name="xsi:noNamespaceSchemaLocation"
namespace="http://www.w3.org/2001/XMLSchema-instance">Speiseplan_Mensa_Schema.xsd</xsl:attribute>
<xsl:for-each select=".//table[#class='table module-food-table']">
<xsl:element name="Speisen">
<xsl:element name="namen">
<xsl:value-of select="./thead/tr/th[1]" />
</xsl:element>
<xsl:for-each select="./tbody/tr">
<xsl:element name="zutaten">
<xsl:value-of select="./td" />
</xsl:element>
<xsl:element name="preis">
<xsl:element name="Studenten">
<xsl:value-of select="./td[2]" />
</xsl:element>
<xsl:element name="Mitarbeiter">
<xsl:value-of select="./td[3]" />
</xsl:element>
<xsl:element name="Gäste">
<xsl:value-of select="./td[4]" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
My problem is by the element "zutaten". I need to get "Hackbällchen in Rahmsauce" out of this xhtml file:
<tbody>
<tr>
<td style="width:70%">
Hackbällchen in <sup>(R)</sup> Rahmsauce <sup>(9,G,A,I)</sup>
<div class="price hidden-sm hidden-md hidden-lg">
<span>1,50 €</span><span>2,15 €</span><span>2,80 €</span>
</div>
</td>
<td class="hidden-xs" style="text-align:center">
1,50 €
</td>
<td class="hidden-xs" style="text-align:center">
2,15 €
</td>
<td class="hidden-xs" style="text-align:center">
2,80 €
</td>
</tr>
</tbody>
I tried:
<xsl:value-of select="./td/text()" />
which give me this: Hackbällchen in Rahmsauce 1,50 2,15 € 2,80 €
or i tried:
<xsl:value-of select="./td/text()[not(self::div)]" />
which give me an error.
And i tried many different things. Can u guys help me with this?
Hope my english was good enough to understand my problem.
Thanks in advance.
The expression you want is this...
<xsl:value-of select="td[1]/text()" />
By doing td/text() (the ./ prefix is unnecessary here), you are getting all td nodes, and all the text underneath all these nodes.
You are missing the predicate on the first column. The XPath ./td will select ALL of the td elements in that row. Using xsl:value-of will produce the computed text value of whatever you have selected, which happens to be all of the td elements (to include the text() node descendants). You can verify this by changing xsl:value-of to xsl:copy-of.
If you want just the text() from the first td column, use td[1]/text().

XSL, SUM & Multiply with Condition

Im doing an assignment for University (so im new to XSL coding) in making a quasi ecommerce site, and will provide as much detail as i can so it makes sense.
Sample XML Data:
<Items>
<Item>
<ItemID>50001</ItemID>
<ItemName>Samsung Galaxy S4</ItemName>
<ItemPrice>629</ItemPrice>
<ItemQty>14</ItemQty>
<ItemDesc>4G Mobile</ItemDesc>
<QtyHold>0</QtyHold>
<QtySold>1</QtySold>
</Item>
<Item>
<ItemID>50002</ItemID>
<ItemName>Samsung Galaxy S5</ItemName>
<ItemPrice>779</ItemPrice>
<ItemQty>21</ItemQty>
<ItemDesc>4G Mobile</ItemDesc>
<QtyHold>0</QtyHold>
<QtySold>1</QtySold>
</Item>
</Items>
Website
So the process is, when a person clicks 'Add to Cart' in the top Table, the ItemQty is decreased by 1 on the ItemQty in the XML, while it increases by 1 in the QtyHold in the XML. (QtyHold represents what has been added to the shopping Cart. Thus if QtyHold is >0 then its been added to the Cart)
My problem refers to the 2nd Table (code below), where the Total figure works - only if dealing with 1 Item. Thus, if Item Number '50001' is added a 2nd time, the Total wont change.
<xsl:template match="/">
<fieldset>
<legend>Shopping Cart</legend>
<BR />
<table border="1" id="CartTable" align="center">
<tr><th>Item Number</th>
<th>Price</th>
<th>Quantity</th>
<th>Remove</th></tr>
<xsl:for-each select="/Items/Item[QtyHold > 0]">
<tr><td><xsl:value-of select="ItemID"/></td>
<td>$<xsl:value-of select="ItemPrice"/></td>
<td><xsl:value-of select="QtyHold"/></td>
<td><button onclick="addtoCart({ItemID}, 'Remove')">Remove from Cart</button></td> </tr>
</xsl:for-each>
<tr><td ALIGN="center" COLSPAN="3">Total:</td><td>$<xsl:value-of select="sum(//Item[QtyHold >0]/ItemPrice)"/></td></tr>
</table>
<BR />
<button onclick="Purchase()" class="submit_btn float_l">Confirm Purchase</button>
<button onclick="CancelOrder()" class="submit_btn float_r">Cancel Order</button>
</fieldset>
</xsl:template>
</xsl:stylesheet>
So what needs to happen is within the following code, while it checks if the QtyHold is greater than 0 (which would mean its in the shopping Cart) & to sum these values, it also needs to multiply QtyHold & ItemPrice.
<xsl:value-of select="sum(//Item[QtyHold >0]/ItemPrice)"/>
I tried many variations of Code like this below... but can't seem to make anything work.
select="sum(//Item[QtyHold >0]/ItemPrice)/(QtyHold*ItemPrice"/>
If you are using XSLT 2.0, the expression you could use would be this:
<xsl:value-of select="sum(//Item[QtyHold >0]/(ItemPrice * QtyHold))"/>
However, in XSLT 1.0 that is not allowed. Instead, you could achieve the result you need with an extension function. In particular the "node-set" function. First you would create a variable like this, in which you construct new nodes holding each item total
<xsl:variable name="itemTotals">
<xsl:for-each select="//Item[QtyHold >0]">
<total>
<xsl:value-of select="ItemPrice * QtyHold" />
</total>
</xsl:for-each>
</xsl:variable>
Ideally, you would like to do sum($itemTotals/total), but this won't work, because itemTotals is a "Result Tree Fragment" and the sum function only accepts a node-set. So you use the node-set extension function to convert it. First declare this namespace in your XSLT...
xmlns:exsl="http://exslt.org/common"
Then, your sum function would look like this:
<xsl:value-of select="sum(exsl:node-set($itemTotals)/total)"/>
Alternatively, if you couldn't even use an extension function, you could use the "following-sibling" approach, to select each Item at a time, and keep a running total. So, you would have a template like this:
<xsl:template match="Item" mode="sum">
<xsl:param name="runningTotal" select="0" />
<xsl:variable name="newTotal" select="$runningTotal + ItemPrice * QtyHold" />
<xsl:variable name="nextItem" select="following-sibling::Item[1]" />
<xsl:choose>
<xsl:when test="$nextItem">
<xsl:apply-templates select="$nextItem" mode="sum">
<xsl:with-param name="runningTotal" select="$newTotal" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTotal" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And to call it, to get the sum, you just start off by selecting the first node
<xsl:apply-templates select="(//Item)[1]" mode="sum" />
Try this XSLT which demonstrates the various approaches
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<table border="1" id="CartTable" align="center">
<tr><th>Item Number</th>
<th>Price</th>
<th>Quantity</th>
</tr>
<xsl:for-each select="/Items/Item[QtyHold > 0]">
<tr>
<td><xsl:value-of select="ItemID"/></td>
<td>$<xsl:value-of select="ItemPrice"/></td>
<td><xsl:value-of select="QtyHold"/></td>
</tr>
</xsl:for-each>
<tr>
<td ALIGN="center" COLSPAN="2">Total:</td>
<xsl:variable name="itemTotals">
<xsl:for-each select="//Item[QtyHold >0]">
<total>
<xsl:value-of select="ItemPrice * QtyHold" />
</total>
</xsl:for-each>
</xsl:variable>
<td>
<!-- XSLT 2.0 only: $<xsl:value-of select="sum(//Item[QtyHold >0]/(ItemPrice * QtyHold))"/>-->
$<xsl:value-of select="sum(exsl:node-set($itemTotals)/total)"/>
$<xsl:apply-templates select="(//Item)[1]" mode="sum" />
</td>
</tr>
</table>
</xsl:template>
<xsl:template match="Item" mode="sum">
<xsl:param name="runningTotal" select="0" />
<xsl:variable name="newTotal" select="$runningTotal + ItemPrice * QtyHold" />
<xsl:variable name="nextItem" select="following-sibling::Item[1]" />
<xsl:choose>
<xsl:when test="$nextItem">
<xsl:apply-templates select="$nextItem" mode="sum">
<xsl:with-param name="runningTotal" select="$newTotal" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTotal" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As a final thought, why don't you just a new Total element to each Item element in your XML. Initially, it would be set to 0, like QtyHold. Then, when you increment QtyHold by 1, by what ever process you do, you can also increment Total by the amount held in ItemPrice. That way, you can just sum this Total node to get the overall total, without the need for extension functions or recursive templates.

How to add preceding-sibling's attributes, where attributes are having alphanumeric values

Suggest for how to add the preceding-sibling's attributes (alphanumeric text). In input xml, if attributes like "namest" having number alone, then adding the attribute is working fine. If attributes are having alphanumeric data then XSLT getting error at 'xsl:attribute name="cellNum"'. Please suggest. (XSLT 2).
Input XML:
<article>
<floats>
<table>
<tbody>
<tr><entry>1</entry><entry namest="col2" nameend="col5">2-5</entry><entry namest="col6" nameend="col9">6-9</entry><entry>10</entry><entry namest="col11" nameend="col13">11-13</entry><entry>14</entry></tr>
<tr><entry>2</entry><entry namest="col2" nameend="col5">2-5</entry><entry namest="col6" nameend="col9">6-9</entry><entry>11</entry></tr>
</tbody>
</table>
</floats>
</article>
XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="tr">
<tr>
<xsl:for-each select="entry">
<xsl:variable name="varNameST" select="sum(number(substring-after(preceding-sibling::entry/#namest, 'col')))"/>
<xsl:variable name="varNameEND" select="sum(number(substring-after(preceding-sibling::entry/#nameend, 'col')))"/>
<xsl:variable name="varCellcount"><xsl:number count="entry" format="1" level="single"/></xsl:variable>
<xsl:variable name="varColspan"><xsl:value-of select="sum($varNameEND)-sum($varNameST)+sum($varCellcount)"/></xsl:variable>
<entry>
<xsl:attribute name="cellNum"><xsl:value-of select="$varColspan"/></xsl:attribute>
<xsl:apply-templates/>
</entry>
</xsl:for-each>
</tr>
</xsl:template>
</xsl:stylesheet>
Required Output:
<article>
<floats>
<table>
<tbody>
<tr><entry cellNum="1">1</entry><entry cellNum="2">2-5</entry><entry cellNum="6">6-9</entry><entry cellNum="10">10</entry><entry cellNum="11">11-13</entry><entry cellNum="14">14</entry></tr>
<tr><entry cellNum="1">1</entry><entry cellNum="2">2-5</entry><entry cellNum="6">6-9</entry><entry cellNum="10">10</entry><entry cellNum="11">11-13</entry><entry cellNum="14">14</entry></tr>
</tbody>
</table>
</floats>
I think you want to use
<xsl:variable name="varNameST" select="sum(preceding-sibling::entry/#namest/number(substring-after(., 'col')))"/>
It is only possible with XSLT 2.0 however.

XSLT: How to get the attribute value of Table colspec to the individual Table cells?

Please suggest to get the attribute value of colspec to the 'td' of same position with respect to 'colspec'.
For Example third 'td' should get the attribute of third 'colspec'.
XML:
<article>
<table-wrap>
<table>
<colspec align="center"/>
<colspec align="left"/>
<colspec align="right"/>
<colspec align="center"/>
<colspec align="left"/>
<tbody>
<tr>
<td>01</td>
<td>02</td>
<td>03</td>
<td>04</td>
<td>05</td>
</tr>
</tbody>
</table>
</table-wrap>
XSLT:
<xsl:template match="td">
<xsl:variable name="varIndex">
<xsl:value-of select="count(preceding-sibling::td)+1"/>
</xsl:variable>
<xsl:variable name="varColspecAlign">
<xsl:value-of select="ancestor::table/colspec[$varIndex]/#align"/>
</xsl:variable>
<td>
<xsl:attribute name="align"><xsl:value-of select="$varColspecAlign"/></xsl:attribute>
<xsl:apply-templates/>
</td>
</xsl:template>
Required Out Put is:
<colspec align="center"/>
<colspec align="left"/>
<colspec align="right"/>
<colspec align="center"/>
<colspec align="left"/>
<tbody>
<tr>
<td align="center">01</td>
<td align="left">02</td>
<td align="right">03</td>
<td align="center">04</td>
<td align="left">05</td>
</tr>
</tbody>
I am not really sure why your template doesn't work as expected . (I am now - see the edit below). However, I do know how to fix it. Change this:
<xsl:variable name="varIndex">
<xsl:value-of select="count(preceding-sibling::td)+1"/>
</xsl:variable>
to:
<xsl:variable name="varIndex" select="count(preceding-sibling::td)+1"/>
---
EDIT
The reason why this makes a difference is this: When you define a variable the way you did, the data type of the variable is "result tree fragment". The other way declares a variable of type "number".
This has a direct consequence when using the variable as a predicate: if the variable is a RTF, the expression [$variable] will return a boolean value of either true() or false(), depending on the variable being empty or not. More precisely, a RTF cannot be empty (it contains at least one node, otherwise it would not be a RTF) - so the result is always true().
This means that your expression:
<xsl:value-of select="ancestor::table/colspec[$varIndex]/#align"/>
is actually evaluated as:
<xsl:value-of select="ancestor::table/colspec[true()]/#align"/>
In other words, the predicate does nothing and the expression is equivalent to:
<xsl:value-of select="ancestor::table/colspec/#align"/>
which (in XSLT 1.0) will select the #align value of the first colspec element in document order.
Converting the RTF to a number, either explicitly:
<xsl:value-of select="ancestor::table/colspec[number($varIndex)]/#align"/>
or implicitly:
<xsl:value-of select="ancestor::table/colspec[position()=$varIndex]/#align"/>
would also result in getting the result that you expect.
---
While you're at it, change the other variable to this format too, because it's more efficient this way (I've been told).
Speaking of efficiency, the best way to get "related" data is through a key. Try:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='xml' version='1.0' encoding='utf-8' indent='yes'/>
<xsl:key name="align" match="colspec" use="count(preceding-sibling::colspec)" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td">
<xsl:variable name="varIndex" select="count(preceding-sibling::td)"/>
<td>
<xsl:attribute name="align"><xsl:value-of select="key('align', $varIndex)/#align"/></xsl:attribute>
<xsl:apply-templates/>
</td>
</xsl:template>
</xsl:stylesheet>
try the position function, and for that you should use the foreach function as well.
the reason is that the template isn't changing it's context and therefor the position will not increase...
here a code for example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//table">
<xsl:for-each select="//td">
<xsl:variable name="varIndex" select="position()"/>
<xsl:variable name="varColspecAlign">
<xsl:value-of select="//colspec[$varIndex]/#align"/>
</xsl:variable>
<td>
<xsl:attribute name="align"><xsl:value-of select="$varColspecAlign"/></xsl:attribute>
</td>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 and string counting

So I'm trying to solve a problem in xslt which I would normally know how to do in an imperative language. I'm adding cells to a table from a list of xml elements, standard stuff. So:
<some-elements>
<element>"the"</element>
<element>"minds"</element>
<element>"of"</element>
<element>"Douglas"</element>
<element>"Hofstadter"</element>
<element>"and"</element>
<element>"Luciano"</element>
<element>"Berio"</element>
</some-elements>
However, I want to cut off one row and start a new one after a certain character maximum has been reached. So say I allow at the most, 20 characters per row. I'd end up with this:
<table>
<tr>
<td>"the"</td>
<td>"minds"</td>
<td>"of"</td>
<td>"Douglas"</td>
</tr>
<tr>
<td>"Hofstadter"</td>
<td>"and"</td>
<td>"Luciano"</td>
</tr>
<tr>
<td>"Berio"</td>
</tr>
</table>
In an imperative language, I'd append the elements to a row while adding each elements string-count to some mutable variable. When that variable exceeded 20, I'd stop, build a new row, and rerun the whole process (starting at the stopped element) on that row after returning the string-count to zero. However, I can't change variable values in XSLT. This whole stateless, function evaluation thing is throwing me for a loop.
Coming to this forum from xsl-list is like going back 10 years, why does everyone use xslt 1:-)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="some-elements">
<table>
<xsl:apply-templates select="element[1]"/>
</table>
</xsl:template>
<xsl:template match="element">
<xsl:param name="row"/>
<xsl:choose>
<xsl:when test="(string-length($row)+string-length(.))>20
or
not(following-sibling::element[1])">
<tr>
<xsl:copy-of select="$row"/>
<xsl:copy-of select="."/>
</tr>
<xsl:apply-templates select="following-sibling::element[1]"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="following-sibling::element[1]">
<xsl:with-param name="row">
<xsl:copy-of select="$row"/>
<xsl:copy-of select="."/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>