I’m new in the xslt topic and have a problem that can't solve on my own.
Here e excample of my xml file:
<node>
<failure><![CDATA[
some useless information.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
CRS urn:ogc:def:crs:EPSG::25833 not possible.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
some useless information.]]>
</failure>
</node>
The main problem is that the information stand in a CDATA block and many different informations are mixed up. I have found a way to get them out, but only as a string value not able to differentiate between the sort.
I need a way to extract elements that fit the pattern: "CRS [-unknown-] [id] not [result]"
What i want is something like this:
<failure>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25833 </id>
<result> not posible </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
</failure>
Can somebody help me or made experience with simular problems?
XSLT 2.0 has xsl:analyze-string which is designed for precisely this task, so if at all possible I suggest you upgrade to a 2.0 processor such as Saxon:
<xsl:template match="node">
<failure>
<xsl:analyze-string select="failure"
regex="^\s*CRS\s*(\S+)\s*(not\s*.*)$" flags="m">
<xsl:matching-substring>
<CRS>
<id><xsl:value-of select="regex-group(1)" /></id>
<result><xsl:value-of select="normalize-space(regex-group(2))" /></result>
</CRS>
</xsl:matching-substring>
</xsl:analyze-string>
</failure>
</xsl:template>
String manipulation facilities in XSLT 1.0 are extremely limited in comparison, and since XSLT is a functional language without updateable variables you'd have to write some sort of hideously complex set of recursive call-template logic to split up the text into separate lines and then extract the relevant bits out of each line in turn using substring-before and substring-after.
<xsl:template name="each-line">
<xsl:param name="val" />
<!-- pull out everything before the first newline and normalize (trim leading
and trailing whitespace and squash internal whitespace to a single space
character -->
<xsl:variable name="firstLine"
select="normalize-space(substring-before($val, '
'))" />
<!-- pull out everything after the first newline -->
<xsl:variable name="rest" select="substring-after($val, '
')" />
<xsl:if test="$firstLine">
<xsl:call-template name="process-line">
<xsl:with-param name="line" select="$firstLine" />
</xsl:call-template>
</xsl:if>
<!-- if there are still some non-empty lines left then process them recursively -->
<xsl:if test="normalize-space($rest)">
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="$rest" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="process-line">
<xsl:param name="line" />
<xsl:if test="starts-with($line, 'CRS ') and contains($line, ' not ')">
<!-- here $line will be something like
"CRS urn:ogc:def:crs:EPSG::25830 not defined." -->
<CRS>
<!-- ID is everything between the first and second spaces -->
<id><xsl:value-of select="substring-before(substring-after($line, ' '), ' ')" /></id>
<!-- result is everything after the second space -->
<result><xsl:value-of select="substring-after(substring-after($line, ' '), ' ')" /></result>
</CRS>
</xsl:if>
</xsl:template>
You would call this logic using a construct like
<xsl:template match="node">
<failure>
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="failure" />
</xsl:call-template>
</failure>
</xsl:template>
Related
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="," />
I have the following structure where I want to fish out the most relevant data and glue it together to make a headline.
<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>
What I would like it to be is comp1 1 1 comp2 0, but so far, the best I've managed is comp1 1 1comp2 0, using this:
<xsl:template match="faults">
<xsl:for-each select="fault">
<xsl:variable name="compName" select="#componentName"/>
<xsl:variable name="sigVals" select="string-join(signals/signal/#value, ' ')"/>
<xsl:value-of select="concat(#compName, ' ', $sigVals)"/>
</xsl:for-each>
</xsl:template>
The solution I have doesn't feel very elegant, so if you have alternate approaches, I would very much like to see them. I also have full control over the XML, so if there are any good solutions that changes the structure of the XML, that too would be of interest.
As you seem to be using XSLT 2.0, you could make it simply:
<xsl:template match="/faults">
<xsl:for-each select="fault">
<xsl:value-of select="#componentName, signals/signal/#value"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
Or, if you want to be more meticulous:
<xsl:template match="/faults">
<xsl:for-each select="fault">
<xsl:value-of select="#componentName, signals/signal/#value"/>
<xsl:if test="position()!=last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
Or (taking a hint from Michael Kay's answer) even simpler:
<xsl:template match="/faults">
<xsl:value-of select="fault/#componentName | fault/signals/signal/#value"/>
</xsl:template>
The xsl:value-of instruction already inserts a separator between individual values; there is no need to use string-join().
Isn't it just
string-join((//#componentName | //#value), ' ')
?
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>
I have the following xml file. I need to fetch all unique "owner" values from this and perform some operations.
<issues>
<issue>
<owner>12345</owner>
</issue>
<issue>
<owner>87654</owner>
</issue>
<issue>
<owner>12345</owner>
</issue>
</issues>
<tests>
<test>
<owner>34598</owner>
</test>
<test>
<owner>12345</owner>
</test>
<test>
<owner>34598</owner>
</test>
<test>
<owner>11111</owner>
</test>
</tests>
I tried using following xslt script.
<xsl:for-each select="issues/issue[not(child::owner=preceding- sibling::issue/owner)]/owner">
<!--some code-->
</xsl:for-each>
<xsl:for-each select="tests/test[not(child::owner=preceding- sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner">
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<!--some code-->
</xsl:if>
</xsl:for-each>
But am getting duplicate values. Could anyone please help me with this?
In XSLT 2.0 you can just use for-each-group:
<xsl:for-each-group select="issues/issue | tests/test" group-by="owner">
<!-- in here, . is the first issue/test with a given owner and current-group()
is the sequence of all issue/test elements that share the same owner -->
</xsl:for-each-group>
If you are stuck on 1.0 then you need to use a technique called "Muenchian grouping" - define a key that groups elements with the same owner, then process just the first item in each group using a generate-id trick
<xsl:key name="ownerKey" match="issue | test" use="owner" />
<xsl:for-each select="(issues/issue | tests/test)[generate-id()
= generate-id(key('ownerKey', owner)[1])]">
<!-- one iteration per unique owner, with . being the parent element of the
first occurrence -->
</xsl:for-each>
But am getting duplicate values.
It's not quite clear where you are getting the duplicate values - since your posted code does not output anything. If you had tested something like:
...
<xsl:for-each select="issues/issue[not(child::owner=preceding-sibling::issue/owner)]/owner">
<out>
<xsl:value-of select="." />
</out>
</xsl:for-each>
....
you would have seen that it does work (albeit inefficiently) and returns:
...
<out>12345</out>
<out>87654</out>
...
Similarly, testing the following snippet:
...
<xsl:for-each select="tests/test[not(child::owner=preceding-sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner"/>
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<out>
<xsl:value-of select="." />
</out>
</xsl:if>
</xsl:for-each>
...
produces:
...
<out>34598</out>
<out>11111</out>
...
So the problem must be in the part of the code that you haven't posted. Note also that the code you did post has several syntax errors, e.g. :
<xsl:value-of select="//issues/issue/owner">
needs to be:
<xsl:value-of select="//issues/issue/owner"/>
I am getting an input structure as something like this
<ParameterSet>2|InterfaceMethod|EQ|I|GenericQuery|NIL</ParameterSet>
<ParameterSet>1|TargetFilename|EQ|I|VendorMaster|NIL</ParameterSet>
the output should look something like below
<Parameter>
<Expression>2</Expression>
<Parametername>InterfaceMethod</Parametername>
<Parameter_Opt>EQ</Parameter_Opt>
<Parameter_Sign>I</Parameter_Sign>
<Range_Low_Value>GenericQuery</Range_Low_Value>
<Range_High_Value>NIL</Range_High_Value>
</Parameter>
<Parameter>
<Expression>1</Expression>
<Parametername>TargetFilename</Parametername>
<Parameter_Opt>EQ</Parameter_Opt>
<Parameter_Sign>I</Parameter_Sign>
<Range_Low_Value>VendorMaster</Range_Low_Value>
<Range_High_Value>NIL</Range_High_Value>
</Parameter>
My Problem is the tag gets converted to <ParameterSet> and am not able to use foreach when I write into another repetitive structure.
Can anyone provide some sample code.
The operation you are looking for - turning lexical XML into a tree of nodes - is called parsing. Some XSLT processors have an extension function, e.g. saxon:parse(), that does this (in XSLT 3.0 it's available out-of-the-box as fn:parse-xml()). With other processors, you may be able to write your own extension function by calling out to Java or Javascript.
Your input data isn't XML, possibly it's encoded (escaped) XML. So, you need to turn it into well-formed XML, in other words perform pre-processing. Then apply XSL transform.
xslt is good at rearranging xml into different xml, not at changing non-xml into xml. While you could do this with xslt using lots of nested substring-before or the like, it will be much much easier if you can run it through sed or something first to create xml out of the |-delimited string.
Thanks everyone for your inputs, I wanted this solution in XSLT as that is only processor I had and also it is running on version 1.0. Got it working with the code below
<xsl:variable name="TempArea">
<ParameterSet>2|InterfaceMethod|EQ|I|GenericQuery|NIL</ParameterSet>
<ParameterSet>1|TargetFilename|EQ|I|VendorMaster|NIL</ParameterSet>
<ParameterSet>1|CompanyCode|EQ|I|4900|NIL</ParameterSet></xsl:when>
</xsl:variable>
<xsl:call-template name="for.loop.Parameters">
<xsl:with-param name="sourceNodes" select="substring-after($TempArea,'<ParameterSet>')"/>
</xsl:call-template>
<xsl:template name="for.loop.Parameters">
<xsl:param name="sourceNodes"/>
<xsl:variable name="temp">
<xsl:choose>
<xsl:when test="string-length($sourceNodes) > '0'">
<xsl:value-of select="substring-before($sourceNodes,'</ParameterSet>')"/>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="Expression" select="substring-before($temp, '|')"/>
<xsl:variable name="remaining" select="substring-after($temp, '|')"/>
<xsl:variable name="Name" select="substring-before($remaining, '|')"/>
<xsl:variable name="remainingNext" select="substring-after($remaining, '|')"/>
<xsl:variable name="Option" select="substring-before($remainingNext, '|')"/>
<xsl:variable name="remainingNext1" select="substring-after($remainingNext, '|')"/>
<xsl:variable name="Sign" select="substring-before($remainingNext1, '|')"/>
<xsl:variable name="remainingNext2" select="substring-after($remainingNext1, '|')"/>
<xsl:variable name="LowValue" select="substring-before($remainingNext2, '|')"/>
<xsl:variable name="HighValue" select="substring-after($remainingNext2, '|')"/>
<Parameter>
<Expression>
<xsl:value-of select="$Expression"/>
</Expression>
<Parametername>
<xsl:value-of select="$Name"/>
</Parametername>
<Parameter_Opt>
<xsl:value-of select="$Option"/>
</Parameter_Opt>
<Parameter_Sign>
<xsl:value-of select="$Sign"/>
</Parameter_Sign>
<Range_Low_Value>
<xsl:value-of select="$LowValue"/>
</Range_Low_Value>
<Range_High_Value>
<xsl:value-of select="$HighValue"/>
</Range_High_Value>
</Parameter>
<xsl:variable name="test">
<xsl:value-of select="substring-after($sourceNodes,'</ParameterSet>')"/>
</xsl:variable>
<xsl:if test="string-length($test)> 1 ">
<xsl:call-template name="for.loop.Parameters">
<xsl:with-param name="sourceNodes">
<xsl:value-of select="substring-after($test,'<ParameterSet>')"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>