Why do I end up with 8 extra spaces when applying the transform from within MatLab rather than letting Firefox handle it? - xslt

I have the following structure:
<suite>
<faults>
<fault componentName="comp1">
<introduceWhen>Time1</introduceWhen>
<signals>
<signal name="sig11" value="1"/>
<signal name="sig22" value="1"/>
</signals>
</fault>
<fault componentName="comp2">
<introduceWhen>Time2</introduceWhen>
<signals>
<signal name="sig44" value="0"/>
</signals>
</fault>
</faults>
</suite>
And using the follwing template I extract some data and put it in a table cell that has white-space: pre set, in order to allow me to put each fault on a new line.
<?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:output doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
<xsl:output doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<xsl:template match="/suite">
<html>
<head>
<title>Space Demo</title>
<style>
td.faults {text-align: left; white-space: pre;}
</style>
</head>
<body>
<table>
<tr>
<td class="faults">
<xsl:apply-templates select="faults"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="faults">
<xsl:for-each select="fault">
<xsl:value-of select="concat(#componentName, ' ', introduceWhen, ' ')"/>
<xsl:for-each select="signals/signal">
<xsl:value-of select="concat(#value, ' ')"/>
</xsl:for-each>
<xsl:if test="position()!=last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It works exactly as I want it to if I only display the XML in Firefox, with the XSLT as a stylesheet. But if I apply the transform in MatLab, the resulting HTML gets generated with an extra 8 spaces after the $1
. MatLab uses Saxon 6.5.5 for XSLT processing.
I tried using the following template to trim the string, but it made no difference:
<xsl:template name="trim">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="string-length($str) > 0 and substring($str, 1, 1) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 2)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($str) > 0 and substring($str, string-length($str)) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 1, string-length($str)-1)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
What can I do to get rid of the extra 8 spaces? There is a working example of this behavior here: http://xsltransform.net/jz1PuP4

The indent is apparently an attempt of the Saxon processor to make the HTML code more pretty. Neither Xalan nor libxslt produce a similar indent.
Since HTML browsers ignore superfluous white space characters, the addition is harmless and I see no reason why you should be concerned.
Changing the output method to xml will remove the indent, because unlike with HTML, white space can be critical.

Related

Getting internal error while evaluating template - xslt

I have an issue while converting xml into csv. I'm getting a strange issue and need help.
This is my XML and XSLT, I'm getting the below error while transforming
"java.lang.RuntimeException: Internal error evaluating template at line 47 in module " that's at the line with content " xsl:template match="UsrOrder"" - bolded text
Tried but unable to figure out the issue.
XML
Below is the XML input I'm giving to XSLT and this has to be converted to CSV
<?xml version="1.0" encoding="UTF-8"?>
<document>
<businessobjects>
<UsrOrder>
<PlanOn>228.01</PlanOn>
<PROG/>
<FUND/>
<ORGN/>
<ACCT/>
<Buyer/>
<Delivery_Notes/>
<Supplier/>
<Line>1</Line>
<Item_Name>NewAir AC-12000CF Airco filter</Item_Name>
<Unit_of_Measure>STK</Unit_of_Measure>
<Order_Date/>
<Retrofit/>
<Description/>
<Product_Code/>
<Related_Invoice_Number/>
<Order_Attachments/>
<Quantity>1.0000</Quantity>
<Unit_Price>9.98</Unit_Price>
</UsrOrder>
</businessobjects>
</document>
```
XSLT
====
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv">
<xsl:output method="text" encoding="utf-8" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="delimiter" select="','" />
<csv:columns>
<column>Supplier</column>
<column>FUND</column>
<column>ORGN</column>
<column>ACCT</column>
<column>PROG</column>
<column>PlanOn</column>
<column>Delivery_Notes</column>
<column>Buyer</column>
<column>Line</column>
<column>ItemName</column>
<column>Quantity</column>
<column>Unit_Price</column>
<column>Unit_of_Measure</column>
<column>Description</column>
<column>Product_Code</column>
<column>Category</column>
<column>Retrofit</column>
<column>Related_Invoice_Number</column>
<column>OrderDate</column>
<column>OrderAttachments</column>
</csv:columns>
<xsl:template match="/document/businessobjects">
<!-- Output the CSV header -->
<xsl:for-each select="document('')/*/csv:columns/*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<!-- Output rows for each matched property -->
<xsl:apply-templates select="UsrOrder" />
</xsl:template>
**<xsl:template match="UsrOrder">**
<xsl:variable name="OrderOrderLines" select="." />
<!-- Loop through the columns in order -->
<xsl:for-each select="document('')/*/csv:columns/*">
<!-- Extract the column name and value -->
<xsl:variable name="column" select="." />
<xsl:variable name="value" select="$OrderOrderLines/*[name() = $column]" />
<xsl:value-of select="$value"/>
<!-- Quote the value if required -->
<!-- <xsl:choose>
<xsl:when test="contains($value, '"')">
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:when test="contains($value, $delimiter)">
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>-->
<!-- Add the delimiter unless we are the last expression -->
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<!-- Add a newline at the end of the record -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
"Internal error" means it's a problem in the XSLT processor, not in your code. You can consider things like checking known bugs and upgrading to the latest maintenance release; when you've done that, report it to the vendor.
Another technique is to try a different XSLT processor and see if it comes up with anything. When I run this on the current Saxon version (9.9.1.5), I get:
Static error at xsl:template on line 31 column 53 of test.xsl:
XTSE0120: No character data is allowed between top-level elements
Errors were reported during stylesheet compilation
So it could be that if you fix the problem in your stylesheet, the failure will go away. (The characters in question are not actually on line 31, they are the asterisks on line 46.)

Compare xml node values in xsl and highlight differences

I have an xml. I am transforming it using xsl stylesheet and showing in html page using java. My requirement is , I need to compare two node values and if there is a difference, I need to highlight the changed character value. How can this be done ?
XML :
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Message>
<DiffDetailMessage>
<TestCaseID>000001</TestCaseID>
<res1>VI7002 1D</res1>
<res2>VI7002 DD </res2>
</DiffDetailMessage>
<DiffDetailMessage>
<TestCaseID>000002</TestCaseID>
<res1>BS7002 1D</res1>
<res2>BS7002 SS </res2>
</DiffDetailMessage>
</Message>
XSL :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Report</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="Message/DiffDetailMessage">
<table border="2">
<xsl:apply-templates select="TestCaseID"/>
<xsl:apply-templates select="res1"/>
<xsl:apply-templates select="res2"/>
</table>
</xsl:template>
<xsl:template match="TestCaseID">
<tr><td><b>Test CaseID </b></td>
<td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template match="res1">
<tr><td><b>Res1</b></td>
<td><xsl:value-of select="."/> </td> </tr>
</xsl:template>
<xsl:template match="res2">
<tr><td><b>Res2</b></td>
<td><xsl:value-of select="."/></td> </tr>
</xsl:template>
</xsl:stylesheet>
How can I compare node values res1 with res2 ? In my case, value of "VI70002 ID" should be compared against "VI7002 DD" and since there is a change, I should highlight "D" character in html page using xsl. Can someone help on this regard.
If you are using only XSLT-1.0, you can use a recursive named template to iterate over the string char-by-char: the following template takes two strings as input and puts a bold emphasis on each different char. If the second string is longer than the first one, these trailing chars are highlighted, too.
<xsl:template name="cmp">
<xsl:param name="str1" />
<xsl:param name="str2" />
<xsl:choose>
<xsl:when test="substring($str1,1,1) = substring($str2,1,1)">
<xsl:value-of select="substring($str2,1,1)"/>
</xsl:when>
<xsl:when test="substring($str1,1,1) != substring($str2,1,1)">
<b><xsl:value-of select="substring($str2,1,1)"/></b>
</xsl:when>
<xsl:when test="$str1 = '' and substring($str2,1,1)">
<b><xsl:value-of select="substring($str2,1,1)"/></b>
</xsl:when>
</xsl:choose>
<xsl:if test="string-length($str1) > 0 or string-length($str2) > 0">
<xsl:call-template name="cmp">
<xsl:with-param name="str1" select="substring($str1,2)" />
<xsl:with-param name="str2" select="substring($str2,2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Call this template from one of the other templates to get a (partially) highlighted "string", e.g.
<xsl:template match="res2">
<tr><td><b>Res2</b></td>
<td>
<xsl:call-template name="cmp">
<xsl:with-param name="str1" select="../res1" />
<xsl:with-param name="str2" select="." />
</xsl:call-template>
</td> </tr>
</xsl:template>

Generic XSLT to do XML to CSV - almost there, but stuck

I have gathered bits and pieces of this XSLT from these forums. I'm trying to put them altogether to create a single, generic XSLT that can be used to convert XML to CSV by specifying the path to the nodes that should be included in the CSV file.
I have three things that I still can't figure out after about 10 hours of messing with it.
I want to iterate over each column named in csv:columns. During each iteration, I need to extract and store the text() of the column. I think this is the way to iterate, but want to make sure:
<xsl:for-each select="document('')/*/csv:columns/*">
Once I have the text() from the column, I need to put that into the columnname variable in such a way that it works when it is used with getNodeValue.
I was unable to set columnname using variable. If I didn't hard-code the value (surrounded by apostrophes), I could not get it to work. This is why I have the following line in the code:
<xsl:variable name="columnname" select="'location/city'" />
I want to pass the result of getNodeValue into quotevalue so that the result is properly quoted.
The XSLT:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://nowhere/" >
<xsl:output method="text" encoding="utf-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="delimiter" select="','" />
<csv:columns>
<column>title</column>
<column>location/city</column>
</csv:columns>
<xsl:template match="job">
<xsl:value-of select="concat(#id, ',')"/>
<!-- #1 I WANT TO LOOP THROUGH ALL OF THE CSV COLUMNS HERE -->
<!-- #2 How do I put the text into the variable 'columnname' variable so that it works with getNodeValue? -->
<xsl:variable name="columnname" select="'location/city'" />
<xsl:variable name="vXpathExpression" select="$columnname"/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
<!-- #3 After getNodeValue gets the value, I want to send that value into 'quotevalue' -->
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="getNodeValue">
<xsl:param name="pExpression"/>
<xsl:param name="pCurrentNode" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression"
select="substring-after($pExpression, '/')"/>
<xsl:with-param name="pCurrentNode" select=
"$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="quotevalue">
<xsl:param name="value"/>
<xsl:choose>
<!-- Quote the value if required -->
<xsl:when test="contains($value, '"')">
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:when test="contains($value, $delimiter)">
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Sample XML
<?xml version="1.0" encoding="utf-8"?>
<positionfeed
xmlns="http://nowhere/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2006-04">
<job id="2830302">
<employer>Acme</employer>
<title>Manager</title>
<description>Full time</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<location>
<city>Los Angeles</city>
<state>California</state>
</location>
</job>
<job id="2830303">
<employer>Acme</employer>
<title>Clerk, evenings</title>
<description>Part time</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<location>
<city>Albany</city>
<state>New York</state>
</location>
</job>
</positionfeed>
The current output using the XSLT I provided
2830302,Los Angeles
2830303,Albany
The output if the XSLT works as desired
2830302,Manager,Los Angeles
2830303,"Clerk, evenings",Albany
Solution (many thanks to Tim's help below)
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/" >
<xsl:output method="text" encoding="utf-8" />
<xsl:strip-space elements="*" />
<!-- Set the value of the delimiter character -->
<xsl:variable name="delimiter" select="','" />
<!-- The name of the node that contains the column values -->
<xsl:param name="containerNodeName" select="'job'"/>
<!-- All nodes that should be ignored during processing -->
<xsl:template match="source|feeddate"/>
<!-- The names of the nodes to be included in the CSV file -->
<xsl:variable name="columns" as="element()*">
<column header="Title">title</column>
<column header="Category">category</column>
<column header="Description">description</column>
<column header="PostingDate">postingdate</column>
<column header="URL">joburl</column>
<column header="City">location/city</column>
<column header="State">location/state</column>
</xsl:variable>
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- Warn about unmatched nodes -->
<xsl:template match="*">
<xsl:message terminate="no">
<xsl:text>WARNING: Unmatched element: </xsl:text>
<xsl:value-of select="name()"/>
</xsl:message>
<xsl:apply-templates/>
</xsl:template>
<!-- Generate the column headers -->
<xsl:template match="//*[*[local-name()=$containerNodeName]]">
<xsl:value-of select="'Id'"/>
<xsl:value-of select="$delimiter"/>
<xsl:for-each select="$columns/#header">
<xsl:variable name="colname" select="." />
<xsl:value-of select="$colname"/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
<!-- Generate the rows of column data -->
<xsl:template match="//*[local-name()=$containerNodeName]">
<!-- TODO: Handle attributes generically -->
<xsl:value-of select="#id"/>
<xsl:variable name="container" select="." />
<xsl:for-each select="$columns">
<xsl:value-of select="$delimiter"/>
<xsl:variable name="vXpathExpression" select="."/>
<xsl:call-template name="getQuotedNodeValue">
<xsl:with-param name="pCurrentNode" select="$container"/>
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="getQuotedNodeValue">
<xsl:param name="pExpression"/>
<xsl:param name="pCurrentNode" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:variable name="result" select="$pCurrentNode/*[name()=$pExpression]"/>
<xsl:call-template name="quotevalue">
<xsl:with-param name="value" select="$result"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getQuotedNodeValue">
<xsl:with-param name="pExpression" select="substring-after($pExpression, '/')"/>
<xsl:with-param name="pCurrentNode" select= "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="quotevalue">
<xsl:param name="value"/>
<xsl:choose>
<xsl:when test="contains($value, '"')">
<!-- Quote the value and escape the double-quotes -->
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:otherwise>
<!-- Quote the value -->
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Sample data to demonstrate solution
<?xml version="1.0" encoding="utf-8"?>
<positionfeed
xmlns="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/ http://www.job-search-engine.com/add-jobs/positionfeed.xsd"
version="2006-04">
<source>Casting360</source>
<feeddate>2016-11-11T21:48:34Z</feeddate><job id="1363612">
<employer>Casting360</employer>
<title>The Robert Irvine Show Is Seeking Guests</title>
<category>Reality TV</category>
<description>TV personality ROBERT IRVINE (Restaurant Impossible) is seeking guests looking for solutions to their unique problems to share their stories on his show!
Our next show is Thursday, September 22nd in LA. If you're not in LA we will provide your airfare, hotel, car service, and per diem.
Please note: WE ARE NOT LOOKING FOR RESUMES; THIS IS NOT AN ACTING GIG. We are looking for real people to share their stories!
*appearance fee (TBD)
If you or someone you know has a conflict that they need help resolving, WE WANT TO HEAR FROM YOU.
Please email tvgal.ri#gmail.com the following information:
Name
Phone number
Your story in 2-3 paragraphs
1-3 photos of yourself.</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1363612&city=Los+Angeles&state=CA</joburl>
<location>
<nation>USA</nation>
<city>Los Angeles</city>
<state>California</state>
</location>
<jobsource>Casting360</jobsource>
</job><job id="1370302">
<employer>Casting360</employer>
<title>Photoshoot for Publication</title>
<category>Modeling</category>
<description>6 FEMALE Models are wanted for publication photoshoot.
If you're not in the NYC Vicinity (NY, Pa, Ct,) DO NOT REPLY because your response will be summarily ignored.
Chosen models will be given a 5 look photo shoot. The shoot will occur on location (outdoors) in highly public locations chosen both for it's convenience and scenery.
The 5 looks (outfits) will be pre-determined by our staff of items most outfits within a model's wardrobe.
THIS IS A TF (UNPAID) SHOOT. After the release of the magazine, the photos agreed upon from the shoot shall be given to the model (in digital format) for her to build her portfolio.
Chosen models will receive a 5 outfit photo shoot at no cost to them by a NY Fashion Photographer.As a result, chosen models not only receive a free photo shoot, but also become PUBLISHED MODELS featured in a magazine.
The model (Janeykay) centered in the photo attached (Please look at the attached photo) is a Casting360 member who not only received her photo shoot, not only is being featured in a magazine, but also made the cover becoming a Cover Model from her shoot with us.</description>
<postingdate>2016-10-03T00:34:43Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1370302&city=New+York&state=NY</joburl>
<location>
<nation>USA</nation>
<city>New York</city>
<state>New York</state>
</location>
<jobsource>Casting360</jobsource>
</job><job id="1370962">
<employer>Casting360</employer>
<title>Actresses Needed for "Red Shore", Action Film</title>
<category>Acting</category>
<description>CASTING (non-union)
We are a New Independent company looking to shoot our first feature. We are currently looking to fill two Major roles.
Female/African American, Hispanic, Asian, Pacific Islander/ 5'5-5'10/ Age Late 30's-Early 40's.
Project description: A long standing feud between two best friends turned enemies escalates over a valuable Diamond on display in a New York City Museum. With the stakes high they each seek the help of both friends and strangers to settle their feud once and for all.
Please note this is a non-paid project.
Fight training will be provided for free.
Please email including age and height in your e-mail.
Those selected will be invited to our audition.</description>
<postingdate>2016-10-03T14:18:20Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1370962&city=New+York&state=NY</joburl>
<location>
<nation>USA</nation>
<city>New York</city>
<state>New York</state>
</location>
<jobsource>Casting360</jobsource>
</job>
</positionfeed>
As you are using XSLT 2.0, you could define your columns in a variable like so:
<xsl:variable name="columns" as="element()*">
<column>title</column>
<column>location/city</column>
</xsl:variable>
Then you can just iterate over them with a simple statement
<xsl:for-each select="$columns">
But the problem you may be having is that within this xsl:for-each you have changed context. You are no longer positioned on a job element, but the column element, and you don't want your expression to be relative to that. You really need to swap back to being on the job element, which you can do simply by setting a variable reference to the job element before the xsl:for-each and then using that as a parameter to the named template:
<xsl:template match="job">
<xsl:value-of select="#id"/>
<xsl:variable name="job" select="." />
<xsl:for-each select="$columns">
<xsl:value-of select="$delimiter"/>
<xsl:variable name="vXpathExpression" select="."/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pCurrentNode" select="$job"/>
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
As for quoting the result; instead of doing just xsl:value-of simply call the quote template with the value as a parameter
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:call-template name="quotevalue">
<xsl:with-param name="value" select="$pCurrentNode/*[name()=$pExpression]" />
</xsl:call-template>
</xsl:when>
EDIT: If you want a header row of column names, you would have to match the parent of the job node, and then just output the values of the $column variable
<xsl:template match="*[job]">
<xsl:value-of select="$columns" separator="," />
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
Or maybe this if you didn't want the full path
<xsl:value-of select="$columns/(tokenize(., '/')[last()])" separator="," />
Or you could extend your columns variable to have the header text
<xsl:variable name="columns" as="element()*">
<column header="Title">title</column>
<column header="City">location/city</column>
</xsl:variable>
Then you would do this...
<xsl:value-of select="$columns/#header" separator="," />

Displaying xml nodes dynamically and applying style to specific nodes using recursion

Below is my xml file.
<xml>
<top>
<main>
<firstname>John</firstname>
<lastname>John</lastname>
<table></table>
<chapter>
<firstname>Alex</firstname>
<lastname>Robert</lastname>
<p>Sample text chap</p>
<figure name="f1.svg"></figure>
<chapter>
<firstname>Rebec</firstname>
<lastname></lastname>
<p>Sample text</p>
<figure name="f2.svg"></figure>
</chapter>
</chapter>
</main>
</top>
</xml>
Desired output:
<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg
Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime.
I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations.
This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown.
And how do I match only 'lastname' and make it bold?
I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes.
Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.
XSLT:
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>
<xsl:template name="FetchNodes">
<xsl:param name="endIndex" />
<xsl:param name="startIndex" />
<xsl:param name="context" />
<xsl:if test="$startIndex <= $endIndex">
<xsl:if test="$context[$startIndex][name() = 'table']"">
<xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
</xsl:if>
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$endIndex" />
<xsl:with-param name="startIndex" select="$startIndex + 1"/>
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="table">
<xsl:value-of select="node()" />
</xsl:template>
With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.
Can anybody suggest something?
The problem it displaying "f1.svg" instead of "f2.svg" is because of this line
<xsl:variable name="ImageName">
<xsl:value-of select="$root/*/chapter/figure/#name" />
</xsl:variable>
You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first #name attribute regardless of your context. It should look this this
<xsl:variable name="ImageName">
<xsl:value-of select="#name" />
</xsl:variable>
Or better still, like this
<xsl:variable name="ImageName" select="#name" />
Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example
<xsl:template match="figure" mode="figure">
As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:
<xsl:choose>
<xsl:when test="self::lastname">
<fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()" />
</xsl:otherwise>
</xsl:choose>
EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.
For example, to turn a chapter into an fo:block do this
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
To output the firstname in bold, do this
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
Try this XSLT as a starting point, and build on it
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="main">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
<xsl:template match="lastname"/>
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>

Split attribute characters and output them using XSLT 1.0

I am in need to transform the below coding using XSLT 1.0 based on the separators attributes given. The text should be separated based on the separators given:
Input:
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<math display="inline"><mfenced separators=", : . ;"><mn>1</mn><mtext>b</mtext><mo>%</mo><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", : . ; ; : . ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
</chapter>
output required:
1,b:%.d;e
a,b;c;d;e
a,b:c.d;e
Also please note that if there are too many separator characters, the extra ones are ignored. If separator characters are given, but there are too few, the last one is repeated as necessary
I could not able to get the output only if the separator characters are lesser than the child elements.
XSLT 1.0 tried:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:template match="m:mfenced">
<xsl:variable name="text" select="#separators"/>
<xsl:for-each select="child::*">
<xsl:apply-templates/>
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:variable name="attr" select="string-length(translate($text, ' ', ''))"/>
<xsl:variable name="ch" select="count(parent::*/child::*)-1"/>
<xsl:if test="$ch=$attr"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="$ch gt $attr">
<xsl:if test="not(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+1,1)"/></xsl:if>
</xsl:if>
<xsl:if test="$ch lt $attr and count(following-sibling::*)>0"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
</xsl:when>
<xsl:otherwise><xsl:if test="count(following-sibling::*)>0"><xsl:value-of select="$text"/></xsl:if></xsl:otherwise></xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The following solution is based on obtaining the position of each <m:mi> within the <m:fenced> elements to obtain the next operator to be outputted.
Note. I am assuming that the string length used to represent each operator is 1.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:output method="text" />
<!-- Ignore all text nodes (just for demo) -->
<xsl:template match="text()" />
<xsl:template match="m:mfenced">
<!-- Print children values and operators -->
<xsl:apply-templates select="*" mode="list-op">
<xsl:with-param name="separator-str" select="#separators" />
<xsl:with-param name="separator-len" select="string-length(#separators)" />
</xsl:apply-templates>
<!-- Print new line -->
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Last m:mi elements for each m:mfenced are just printed -->
<xsl:template match="*[last()]" mode="list-op">
<xsl:value-of select="."/>
</xsl:template>
<!-- In this template we use the position() function to calculate the next
operator that is going to be outputted -->
<xsl:template match="*" mode="list-op">
<xsl:param name="separator-str" />
<!-- This parameter is not required, but allows us to cache
the length of the separators string instead of calculating it
for each m:mi element -->
<xsl:param name="separator-len" />
<!-- Print current value -->
<xsl:value-of select="." />
<!-- Calculate the separator position within the string -->
<xsl:variable name="string-position" select="2*position() - 1" />
<!-- Check if the position oveflows the position in the array, and
if it does, print the last separator in the string. -->
<xsl:choose>
<xsl:when test="$separator-len >= $string-position">
<xsl:value-of select="substring($separator-str, $string-position, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($separator-str, $separator-len)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>