I am using XSLT to convert an XML document to an HTML document. I am using a technical writing editor that has a WYSIWYG editor but I can access the XML it is ultimately based on.
I have recently added variables to assist with subsequent translations of interface text. Here is an example of how they appear within ordinary text:
<text styleclass="Body" translate="true">Under </text><var styleclass="Interface b"><%BACKGROUND%></var><text styleclass="Body" translate="true">, click on one of the predesigned backgrounds</text>
I have already added new declarations to my XSLT document dealing with the styleclass attributes:
<xsl:template match="var">
<xsl:choose>
<xsl:when test="#style='font-weight:bold;'">
<strong>
<xsl:apply-templates/>
</strong>
</xsl:when>
<xsl:when test="#styleclass='Interface'">
<span style="color:white; background-color: rgb(98, 98, 98);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Interface b'">
<span style="color: rgb(98, 98, 98); background-color: rgb(233, 180, 0);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Interface c'">
<span style="color: black; background-color: rgb(234, 235, 237);">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:when test="#styleclass='Warning a'">
<span style="color:#e74c3c">
<xsl:apply-templates/>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
But I also want to change the terminal-node text <%BACKGROUND%> to simply Background. (I have a spreadsheet with the variable names and user-facing interface text, so this question does not involve individually replacing upper case characters with lower case characters.)
I think there are two approaches:
Simultaneously transforming the attribute and content of a node. My attempts here have failed.
Selecting terminal-node text, but I think that the text tag may pose a problem here.
Thanks for any suggestions.
Related
Need to inject title in span element from csv file matching id attribute of span element with IH_No in csv file.
Once HTML span element id attribute matches with IH_No in csv file then corresponding IH_Title value in csv should be injected in span element
Need your help.
Note: This is just chunk of the original csv file moreover the numbers will be 100K approx, So need a solution without having to use multiple conditions for IH_NO in csv.
I tried the below XSL but somehow I could not get the logic right. I tried lot of different things moreover I could not understand how to fetch values from csv. This xsl may not be completely relevant.
<?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:xhtml="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xs" version="2.0">
<xsl:variable name="inject" select="tokenize(unparsed-text('test.csv'), '\n,>')"/>
<xsl:variable name="value" select="document($inject)/xsl:template/#match"/>
<xsl:variable name="valuet" select="($inject)/title"/>
<xsl:template match="xhtml:div/xhtml:h1[#class = 'title']/xhtml:span/#id">
<xsl:text>Test1</xsl:text>
<xsl:value-of select="$valuet"/>
<xsl:if test="xhtml:h1[#class = 'title' and fn:substring(span/#id, 4) = tokenize($value, ',')[1]]">
<xsl:value-of select="$valuet"/>
</xsl:if>
</xsl:template>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Current HTML structure
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003034"></span>
</h1>
</div>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003052"></span>
</h1>
</div>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003058"></span>
</h1>
</div>
</body>
</html>
Expected HTML structure
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003034">replace Hydraulic pump, replace</span>
</h1>
</div>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003052">replace;Hydraulic pump, replace</span>
</h1>
</div>
<div class="chapter">
<h1 class="title">
<span class="ih" id="ih-8000003058">replace;Hydraulic pump, replace</span>
</h1>
</div>
</body>
</html>
Filename for csv is test.csv, Need to import this csv file in xsl and match the values with span element and fetch the IH_Title values in span element.
"IH_NO";"IH_TITLE";
"8000003034";replace;Coolant pump, replace;
"8000003052";replace;Fuel pump, replace;
"8000003058";replace;Hydraulic pump, replace;
Your question is difficult to understand.
Assuming that the HTML document is the input to the XSL transformation, you can get a result very close to the one you show by using:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:key name="title" match="IH_TITLE" use="#IH_NO" />
<xsl:variable name="titles">
<xsl:for-each select="tokenize(unparsed-text('test.csv'), '\n')">
<IH_TITLE IH_NO="{translate(substring-before(., ';'), '"', '')}">
<xsl:value-of select="substring-after(., ';')" />
</IH_TITLE>
</xsl:for-each>
</xsl:variable>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#class='ih']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="key('title', substring-after(#id, 'ih-'), $titles)" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I did not bother removing the trailing semicolon but it should be simple enough.
Added explanation:
Inside the $titles variable, the .csv file is tokenized to individual lines and a IH_TITLE element is created for each line. The first part of the line is used to populate the IH_NO attribute (after stripping the quotes), and the rest becomes the content of the element. In the given example, the contents of the variable are:
<IH_TITLE IH_NO="IH_NO">"IH_TITLE";</IH_TITLE>
<IH_TITLE IH_NO="8000003034">replace;Coolant pump, replace;</IH_TITLE>
<IH_TITLE IH_NO="8000003052">replace;Fuel pump, replace;</IH_TITLE>
<IH_TITLE IH_NO="8000003058">replace;Hydraulic pump, replace;</IH_TITLE>
This is a well-formed XML fragment that can be interrogated using the key() function to return the value of the supplied key.
I have the following XML (slightly simplified):
<?xml version="1.0" encoding="UTF-8"?>
<Message>
<DataItem>
<Name>Name1</Name>
<Use>a</Use>
<Use>b</Use>
<Use>c</Use>
<Use>d</Use>
<Use>e</Use>
<DataItem>
<Name>Item2</Name>
<Use>a</Use>
<Use>c</Use>
<Use>d</Use>
</DataItem>
<DataItem>
<Name>Item3</Name>
<Use>a</Use>
<Use>b</Use>
<Use>e</Use>
<DataItem>
<Name>Whatever</Name>
<Use>a</Use>
<Use>b</Use>
<Use>d</Use>
</DataItem>
</DataItem>
</DataItem>
</Message>
As you see, the structure is recursive. Now what I need to accomplish is create a table like so:
Item
a
b
c
d
e
Name1
x
x
x
x
x
Item2
x
x
x
Item3
x
x
x
Whatever
x
x
x
Now setting up the header is easy, I can just loop through all the values of the first data item (which always contains all the options). I can also populate the first column with the names. But what I cannot wrap my head around is how to fill the rest of the table. Any help is much appreciated!
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/Message">
<xsl:variable name="col" select="DataItem[1]/Use" />
<html>
<body>
<table border="1">
<!-- header -->
<tr>
<th>Item</th>
<xsl:for-each select="$col">
<th>
<xsl:value-of select="."/>
</th>
</xsl:for-each>
</tr>
<!-- data -->
<xsl:for-each select="//DataItem">
<tr>
<td>
<xsl:value-of select="Name"/>
</td>
<xsl:variable name="row" select="Use" />
<xsl:for-each select="$col">
<td>
<xsl:if test=". = $row">x</xsl:if>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I have to add two variables with scientific number values in XSLT.
I am getting NAN, when I used something like this xsl:with-param name="inputVal" select="($price1+$price2)". where Price1 = 1.0E7 and Price2 = 1.0E8. I have issue with Sum() as well.
Here is an example of what I am looking for
My XML :
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>CD1</title>
<price>1.0E7</price>
</cd>
<cd>
<title>CD2</title>
<price>1.1E7</price>
</cd>
<cd>
<title>CD3</title>
<price>1.2E7</price>
</cd>
</catalog>
My XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Price</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td>
<xsl:value-of select="title"/></td>
<td><xsl:call-template name="convertSciToNumString">
<xsl:with-param name="inputVal" select="price"/>
</xsl:call-template></td>
</tr>
</xsl:for-each>
<tr>
<th>Total</th>
<th><xsl:value-of select="sum(catalog/cd/price)"/></th>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template name="convertSciToNumString" >
<xsl:param name="inputVal" select="0"/>
<xsl:variable name="vMantissa" select="substring-before($inputVal, 'E')"/>
<xsl:variable name="vExponent" select="substring-after($inputVal, 'E')"/>
<xsl:variable name="vExponentAbs" select="translate($vExponent, '-', '')"/>
<xsl:variable name="vFactor" select="substring('100000000000000000000000000000000000000000000', 1, substring($vExponentAbs, 1) + 1)"/>
<xsl:choose>
<xsl:when test="$inputVal = ''">
</xsl:when>
<xsl:when test="number($inputVal)=$inputVal">
<xsl:value-of disable-output-escaping="no" select="format-number($inputVal, '##,###,###,###,###,###,##0.00')"/>
</xsl:when>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of disable-output-escaping="no" select="format-number($vMantissa div $vFactor, '##,###,###,###,###,###,##0.00')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of disable-output-escaping="no" select="format-number($vMantissa * $vFactor, '##,###,###,###,###,###,##0.00')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I am using version 1.0 and by following the suggestion to create a template to convert data from scientific format to number format, I am able to individually, but as I have to use sum() or variable1 + variable2 in some cases, I am not sure how to handle this.
The main problem with your attempt is that your template returns a formatted string, not a number.
The other thing is that you need to do this in two passes: first, convert the scientific-notation values to numbers; then sum the resulting numbers and - if necessary - format them for output.
If you are using the Apache Xalan processor, you can make this much easier by utilizing the EXSLT math:power() extension function:
XSLT 1.0 (+ EXSLT)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:math="http://exslt.org/math"
extension-element-prefixes="exsl math">
<xsl:template match="/catalog">
<!-- FIRST PASS -->
<xsl:variable name="cds-rtf">
<xsl:for-each select="cd">
<xsl:copy>
<xsl:copy-of select="title"/>
<price>
<xsl:variable name="significand" select="substring-before(price,'E')"/>
<xsl:variable name="magnitude" select="substring-after(price,'E')"/>
<xsl:value-of select="$significand * math:power(10, $magnitude)"/>
</price>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cds" select="exsl:node-set($cds-rtf)/cd"/>
<!-- OUTPUT -->
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr>
<th>Title</th>
<th>Price</th>
</tr>
<xsl:for-each select="$cds">
<tr>
<td>
<xsl:value-of select="title"/>
</td>
<td>
<xsl:value-of select="format-number(price, '#,###')"/>
</td>
</tr>
</xsl:for-each>
<tr>
<th>Total</th>
<th>
<xsl:value-of select="format-number(sum($cds/price), '#,###')"/>
</th>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result (rendered)
This might be a novice question but, then again, I'm a novice :-)
OLD SITUATION:
I have XML files in this format:
<chapter>
<title>chapter title</title>
<para>p1</para>
<para>p2</para>
<para>p3</para>
<!-- ....and so on for a lot of para elements -->
</chapter>
...which are handled by an XSL template like this...
<xsl:template match="para">
<xsl:if test="count(following-sibling::para) = 1 and count(preceding-sibling::para) > 13">
<!-- Insert Some Stuff -->
</xsl:if>
</xsl:template>
The logic here is: "Insert Some Stuff" before the second-to-last element if there are enough preceding <para> siblings.
This code gets the job done and works fine.
NEW SITUATION
Now, for reasons beyond my control, I need to adapt that template to handle the following kind of files as well...
<chapter>
<title>chapter title</title>
<para>p1</para>
<para>p2</para>
<para>p3</para>
<section>
<para>p4</para>
<para>p5</para>
</section>
<para>p6</para>
<section>
<title>section title</title>
</section>
<para>p7</para>
<section>
<para>p8</para>
</section>
<!-- ....and so on for a lot of para elements -->
</chapter>
The difference with these files is that the <section> element can appear randomly, sometimes containing <para> elements and sometimes not. There is no way of predicting when and where <section> elements will appear.
I need the original XSL template to work with this format in the same way despite the <section> elements being there. So that means that the <para> elements still need to be counted in the same way even if they are sometimes children and sometimes siblings.
In summary, I need the logic of the old code to work as before, as though it is completely ignoring the presence of the <section> elements.
What should the original template <xsl:if test="..."> be rewritten as to make this work?
Performance is not an issue - these are manually-run ad-hoc transformations.
XSLT 1.0 only, please
Thanks.
It is difficult to answer your question without seeing a complete stylesheet and the expected output/s.
Would not:
<xsl:template match="para">
<xsl:if test="count(following::para) = 1 and count(preceding::para) > 13">
<!-- Insert Some Stuff -->
</xsl:if>
</xsl:template>
work for you?
Added:
If you're sure that what you've suggested should work for the example
given in the question
Well, the example given in the question does not have the required minimum of para nodes - but if we reduce the threshold:
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="para">
<xsl:if test="count(following::para) = 1 and count(preceding::para) > 5">
<INSERTED_NODE/>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Test Input A
<chapter>
<title>Section Title 1</title>
<para>Para 1</para>
<para>Para 2</para>
<para>Para 3</para>
<section>
<title>Section Title 2</title>
<para>Para 4</para>
<para>Para 5</para>
</section>
<para>Para 6</para>
<section>
<title>Section Title 3</title>
<para>Para 7</para>
<para>Para 8</para>
</section>
<para>Para 9</para>
<para>Para 10</para>
</chapter>
Result A
<?xml version="1.0" encoding="UTF-8"?>
<chapter>
<title>Section Title 1</title>
<para>Para 1</para>
<para>Para 2</para>
<para>Para 3</para>
<section>
<title>Section Title 2</title>
<para>Para 4</para>
<para>Para 5</para>
</section>
<para>Para 6</para>
<section>
<title>Section Title 3</title>
<para>Para 7</para>
<para>Para 8</para>
</section>
<INSERTED_NODE/>
<para>Para 9</para>
<para>Para 10</para>
</chapter>
Test Input B
<chapter>
<title>Section Title 1</title>
<para>Para 1</para>
<para>Para 2</para>
<para>Para 3</para>
<section>
<title>Section Title 2</title>
<para>Para 4</para>
<para>Para 5</para>
</section>
<para>Para 6</para>
<section>
<title>Section Title 3</title>
<para>Para 7</para>
<para>Para 8</para>
</section>
<para>Para 9</para>
</chapter>
Result B
<?xml version="1.0" encoding="UTF-8"?>
<chapter>
<title>Section Title 1</title>
<para>Para 1</para>
<para>Para 2</para>
<para>Para 3</para>
<section>
<title>Section Title 2</title>
<para>Para 4</para>
<para>Para 5</para>
</section>
<para>Para 6</para>
<section>
<title>Section Title 3</title>
<para>Para 7</para>
<INSERTED_NODE/>
<para>Para 8</para>
</section>
<para>Para 9</para>
</chapter>
Edit
Is there a way to force it to be inserted once per chapter element?
Yes, you could do it this way:
<xsl:template match="para">
<xsl:variable name="i">
<xsl:number count="para" from="chapter" level="any"/>
</xsl:variable>
<xsl:variable name="n" select="count(ancestor::chapter//para)" />
<xsl:if test="$i + 1 = $n and $n > 5">
<INSERTED_NODE/>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
A more efficient solution would do the counting once at the chapter level, and pass it down as a parameter to the paras.
I have an xml data file that will contain a large number of repeating fields, each which are associated to about 10 unique facility names, like this:
<Dailyreport>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>North</facility>
<ispass>0</ispass>
</msg>
<msg>
<msgdate>05/27/2015</msgdata>
<facility>South</facility>
<ispass>1</ispass>
</msg>
</Dailyreport>
I have an XSL stylesheet version 1.0 that is working where I can obtain the count of occurrences by facility, which looks like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<table style="margin-left:auto;margin-right:auto" rules="all" border="1">
<xsl:if test="Dailyreport//msg[facility='North']">
<tr><td>North Building:</td></tr>
<tr><td>Total:<xsl:value-of select="count(Dailyreport/msg[facility='North'])"/></td></tr>
<tr><td>Pass:<xsl:value-of select="count(Dailyreport/msg[facility='North' and ispass='1'])"/></td></tr>
<tr><td>Fail:<xsl:value-of select="count(DailyELRreport/msg[facility='North' and ispass='0'])"/></td></tr>
<tr><td>-------------------</td></tr>
<tr><td>
</td></tr>
</xsl:if>
</table>
</html>
</xsl:template>
</xsl:stylesheet>
However, in order to get counts for all of the possible facilities, I have to repeat the (xsl:if test) section, with each of the facility names.
I would like to see if I can locate the facility names in a second xml data file and use the document() function to iterate through them by possibly loading them into a global parameter, and then using a call-template function to repeatedly call one single (xsl:if test) section. And have it use the parameter value instead of a fixed facility name....something like this:
<xsl:if test="Dailyreport//msg[facility=$sender]">
Everything I've tried has failed.
Wondering if anyone can help!
Thanks!
Given these two XML documents:
facilities.xml
<facilities>
<facility code="North">North Building</facility>
<facility code="South">South Building</facility>
</facilities>
XML (this is the document processed by XSLT)
<Dailyreport>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>North</facility>
<ispass>1</ispass>
</msg>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>North</facility>
<ispass>1</ispass>
</msg>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>North</facility>
<ispass>0</ispass>
</msg>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>South</facility>
<ispass>0</ispass>
</msg>
<msg>
<msgdate>05/27/2015</msgdate>
<facility>South</facility>
<ispass>0</ispass>
</msg>
</Dailyreport>
the folowing stylesheet:
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="*"/>
<xsl:key name="msg" match="msg" use="facility" />
<xsl:template match="/">
<xsl:variable name="report" select="." />
<table border="1">
<xsl:for-each select="document('facilities.xml')/facilities/facility">
<tr>
<th><xsl:value-of select="."/></th>
</tr>
<xsl:variable name="code" select="#code" />
<!-- switch context back to XML document -->
<xsl:for-each select="$report">
<xsl:variable name="messages" select="key('msg', $code)" />
<tr>
<td>Total:<xsl:value-of select="count($messages)"/></td>
</tr>
<tr>
<td>Pass:<xsl:value-of select="count($messages[ispass='1'])"/></td>
</tr>
<tr>
<td>Fail:<xsl:value-of select="count($messages[ispass='0'])"/></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<th>North Building</th>
</tr>
<tr>
<td>Total:3</td>
</tr>
<tr>
<td>Pass:2</td>
</tr>
<tr>
<td>Fail:1</td>
</tr>
<tr>
<th>South Building</th>
</tr>
<tr>
<td>Total:2</td>
</tr>
<tr>
<td>Pass:0</td>
</tr>
<tr>
<td>Fail:2</td>
</tr>
</table>
rendered as: