I am creating a recursive XSLT function that run over all the Xml file child ..
<xsl:call-template name="testing">
<xsl:with-param name="root" select="ItemSet"></xsl:with-param>
</xsl:call-template>
While running the XSLT, I need to get the variable root node value, where I am
because when calling the root variable, I get the node with all the children node, but all I need is the single value of the node, and as it is recursive i can't act on the child node tags because it always changes. So how can I get the variable single specific value in anytime?
Neither $root nor " . " works.
XSL code:
<xsl:variable name="trial" select="count(./*)"></xsl:variable>
<xsl:choose>
<xsl:when test="count(./*) = 0">
:<xsl:value-of select="$root" /> <br/>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="./*">
<xsl:call-template name="testing">
<xsl:with-param name="root" select=".">
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
XML code :
<ItemSet>
<Item>
1
<iteml1>
1.1
</iteml1>
<iteml1>
1.2
</iteml1>
</Item>
<Item>
2
<iteml1>
2.1
<iteml2>
2.1.1
</iteml2>
</iteml1>
</Item>
</ItemSet>
what should if put as a code line in place of * so the solution would show:
1
1: 1.1 :
1: 1.2
2
2: 2.1
2: 2.1: 2.1.2
The wanted processing can be implemented efficiently and without explicit recursion:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="*[parent::*]">
<xsl:param name="pPath"/>
<xsl:value-of select="$pPath"/>
<xsl:variable name="vValue" select=
"normalize-space(text()[1])"/>
<xsl:value-of select="$vValue"/> <br/>
<xsl:apply-templates select="*">
<xsl:with-param name="pPath" select=
"concat($pPath, $vValue, ': ')"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<ItemSet>
<Item>
1
<iteml1>
1.1
</iteml1>
<iteml1>
1.2
</iteml1>
</Item>
<Item>
2
<iteml1>
2.1
<iteml2>
2.1.1
</iteml2>
</iteml1>
</Item>
</ItemSet>
the wanted result is produced:
1<br/>1: 1.1<br/>1: 1.2<br/>2<br/>2: 2.1<br/>2: 2.1: 2.1.1<br/>
and it is displayed in the browser as:
11: 1.11: 1.222: 2.12: 2.1: 2.1.1
You have two choices: If this is only going to be used in one place, you could create a variable in the stylesheet root and use that globally.
Otherwise, what you'll need to do is have two parameters, one of which is passed exactly as is on each call, so as well as calling <xsl:with-param name="root" select="." />, you also add <xsl:with-param name="baseroot" select="$baseroot" /> and define <xsl:param name="baseroot" select="$root" /> immediately below your <xsl:param name="root" />. Then you can use $baseroot in place of $root anywhere that you need the original value that was passed in at the top of the stack.
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'm generating a CSV file from an XML using XSL. The XML contains Main elements with child elements Tags, which in turn contain varying amounts of child elements Tag. A part of the XML looks for example like this:
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
</Tags>
</Main>
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
<Tag>tag4</Tag>
<Tag>tag5</Tag>
<Tag>tag6</Tag>
</Tags>
</Main>
In the XSL I have a for each loop that goes through all my Main elements of my XML file. I want to print the values for all the Tag elements. I do this in another for-each loop which is inside the major loop. However, I always want to iterate 10 times, regardless of the amount of Tag elements. I want to print some text in each of the remaining iterations when I have exceeded the amount of printable Tag.
This is the output I'm after:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
After the Tag for each loop, I'm calling a template, providing a variable with the amount of Tag in Tags. I then want the template to call itself recursively until it has done the varying amount of remaining iterations for the Tag elements of the current Main element. The amount of Tag elements changes with each Main iteration, which I suspect is a problem in my current solution (which causes my transformation software, Notepad++ with XML Tools, to crash):
<xsl:template match="/">
<xsl:for-each select="Main">
<xsl:for-each select="Tags/Tag">
<xsl:value-of select="Tag"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="count(Tags/*)"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="repeatable">
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Does anyone have any idea if it's possible to do this type of varying iteration, or am I out of luck?
Edit:
I managed to solve it. The problem was I had forgotten to pass on the variable tagamount with each recursive call. See my solution further below.
I couldn't wrap my head around your code. How about something simpler?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="sep" select="','"/>
<xsl:variable name="LF" select="'
'"/>
<xsl:variable name="filler" select="'1,2,3,4,5,6,7,8,9,10'"/>
<xsl:template match="/">
<xsl:for-each select="rt/Main/Tags">
<xsl:for-each select="Tag">
<xsl:value-of select="concat(., $sep)"/>
</xsl:for-each>
<xsl:value-of select="substring($filler, 2*count(Tag)+1)"/>
<xsl:value-of select="$LF"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note:
1. Your XML is missing a root element: I am using "rt" as a placeholder.
2. For testing purposes, I have changed "1,1,1,..." into "1,2.3...".
Here is one way to do it.
This XSLT stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Sets the number of iterations per Tags element. -->
<xsl:variable name="maximum" select="10"/>
<!-- Matches all the Tags elements and calls a recursive template, intializing the count to 1. -->
<xsl:template match="//Tags">
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="1"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- A recursive template that will repeat itself until its count reaches the maximum value.
If the count is equal to or less then the number of Tag elements inside the current Tags
element, then find the Tag element in the count position and print its value. Otherwise,
print 1. -->
<xsl:template name="output-tags">
<xsl:param name="count"/>
<xsl:if test="$count <= $maximum">
<xsl:choose>
<xsl:when test="$count <= count(Tag)">
<xsl:value-of select="Tag[count(preceding-sibling::Tag) = $count - 1]"/>
<xsl:text>,</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>1,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="$count + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
produces the following output when applied to your example input XML:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
Thanks for the answers!
I managed to solve it right after I posted. The problem was I had forgotten to send the tagamount variable with the recursive call. After adding it, it works. The repeatable template then looks like this:
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="$tagamount"/> <-----------
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
I read lot of articles but did not find a conclusive help to my problem.
I have an XML document to which I apply an xslt to get a csv file as output.
I send a parameter to my xsl transformation to filter the target nodes to apply the templates.
The xml document looks like that (I removed some unuseful nodes for comprehension):
<GetMOTransactionsResponse xmlns="http://www.exane.com/pott" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exane.com/pott PoTTMOTransaction.xsd">
<MOTransaction>
<Transaction VersionNumber="2" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164597</TransactionId>
<InternalTransaction Type="Switch">
<BookCounterparty>
<Id Type="Risque">94</Id>
</BookCounterparty>
</InternalTransaction>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO1E" />
<Entity>0021</Entity>
</MOTransaction>
<MOTransaction>
<Transaction VersionNumber="1" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164598</TransactionId>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO3E" />
<Entity>0021</Entity>
</MOTransaction>
</GetMOTransactionsResponse>
My xslt is below (sorry it's quite long, and I write it more simple than it really is):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pott="http://www.exane.com/pott">
<xsl:output method="text" omit-xml-declaration="no" indent="no" />
<xsl:param name="instrumentalSystem"></xsl:param>
<xsl:template name="abs">
<xsl:param name="n" />
<xsl:choose>
<xsl:when test="$n = 0">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="$n > 0">
<xsl:value-of select="format-number($n, '#')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number(0 - $n, '#')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="outputFormat">
<!--Declaration of variables-->
<xsl:variable name="GrossPrice" select="pott:GrossPrice" />
<xsl:variable name="TransactionId" select="pott:Transaction/pott:TransactionId[#Type='Risque']" />
<xsl:variable name="VersionNumber" select="pott:Transaction/#VersionNumber" />
<!--Set tags values-->
<xsl:value-of select="$Entity" />
<xsl:text>;</xsl:text>
<xsl:value-of select="concat('0000000', pott:MOAccount/#Account) "/>
<xsl:text>;</xsl:text>
<xsl:text>;</xsl:text>
<xsl:value-of select="$TransactionId" />
<xsl:text>;</xsl:text>
<xsl:value-of select="$VersionNumber" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="/">
<xsl:choose>
<!-- BB -->
<xsl:when test="$instrumentalSystem = 'BB'">
<!--xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]"-->
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<!-- CP -->
<xsl:when test="$instrumentalSystem = 'CP'">
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[not(pott:InternalTransaction)]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
If parameter = BB, I want to select MOTransaction nodes that have a child Transaction that contains a InternalTransaction node.
If parameter = CP, I want to select MOTransaction nodes that don't have a child Transaction that contains a InternalTransaction node
When I write
pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction], I get the Transaction nodes and not the MOTransaction nodes
I think I am not very far from the expected result, but despite all my attempts, I fail.
If anyone can help me.
I hope being clear, otherwise I can give more information.
Looking at one of xsl:for-each statements, you are doing this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
You say you want to select MOTransaction elements, but it is actually selecting the child Transaction elements. To match the logic you require, it should be this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[pott:InternalTransaction]]">
In fact, this should also work
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]">
Similarly, for the second statement (in the case of the parameter being "CP"), it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[not(pott:InternalTransaction)]]">
Alternatively, it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[not(pott:Transaction/pott:InternalTransaction)]">
They are not quite the same though, as the first will only include MOTransaction elements that have Transaction child elements, whereas the second will include MOTransaction that don't have any Transaction childs at all.
As a slight aside, you don't really need to use an xsl:for-each and xsl:call-template here. It might be better to use template matching.
Firstly, try changing the named template <xsl:template name="outputFormat"> to this
<xsl:template match="pott:MOTransaction">
Then, you can re-write you merge the xsl:for-each and xsl:call-template into a single xsl:apply-templates call.
<xsl:apply-template select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]" />
I have difficulties to write a template that generates a breadcrumb trial out of a node structure. It is not working correctly up to now, there is some flaw in my thinking how it should walk the item path.
Consider the following page structure:
<!-- ===== SITE PAGE STRUCTURE ===================================== -->
<index>
<item section="home" id="index"></item>
<item section="service" id="index">
<item id="content-management-systems">
<item id="p1-1"/>
<item id="p1-2"/>
<item id="p1-3"/>
</item>
<item id="online-stores"></item>
<item id="search-engines-and-ir"></item>
<item id="web-applications"></item>
</item>
<item section="solutions" id="index">
<item id="document-clustering"></item>
</item>
<item section="company" id="index">
<item section="company" id="about"></item>
<item section="company" id="philosophy" ></item>
...
</item>
...
</item>
This site index represents a site-structure of xml content pages in its hierarchy (consider it to be a menu). It contains of sections, that represent the site sections just as home, company, service, solutions, etc. These sections can contain sub-sections with pages, or just regular content pages. A content page (its xml contents such as title, text content, etc) is identified by the #id attribute in the item tree. The #id attribute mainly is used to fetch the content of the entire page that will be rendered to html.
The breadcrumb template uses the item node #id attribute to get the title of the page (which will be shown in the breadcrumb trail).
I try to implement the following template that walks the tree by checking the target section attribute #section and the target page attribute #id in the tree. I expect it to walk the axis down until the target item_target is found by comparing the ancestors #section attribute and the #id with $item_target of each node in that axis.
For example: Attribute *$item_section=service* and the page id *target item_target=p1-1* should now recursively "walk" to the section branch "service" (depth 1), check if the target page #id is found on this level. In this case it is not found, so it makes the next recurive call (via apply-templates) to the next item node level (in this case it would be content-management-systems, there the target item page p1-1 is found, so the trail process is finished:
The result should like this:
home >> service >> content management systems >> p1-1
But unfortunately it is not working correct, at least not in every case. Also maybe it can be solved more easily. I try to implement it as an recursive template that walks from the top (level 0) to the target page (item node) as a leaf.
<!-- walk item path to generate a breadcrumb trail -->
<xsl:template name="breadcrumb">
<a>
<xsl:attribute name="href">
<xsl:text>/</xsl:text>
<xsl:value-of select="$req-lg"/>
<xsl:text>/home/index</xsl:text>
</xsl:attribute>
<xsl:value-of select="'Home'"/>
</a>
<xsl:apply-templates select="$content/site/index" mode="Item-Path">
<xsl:with-param name="item_section" select="'service'"/>
<xsl:with-param name="item_target" select="'search-engines-and-ir'"/>
<xsl:with-param name="depth" select="0"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item" mode="Item-Path">
<xsl:param name="item_section" />
<xsl:param name="item_target" />
<xsl:param name="depth" />
<!--
depth=<xsl:value-of select="$depth"/>
count=<xsl:value-of select="count(./node())"/><br/>
-->
<xsl:variable name="cur-id" select="#id"/>
<xsl:variable name="cur-section" select="#section"/>
<xsl:choose>
<xsl:when test="#id=$item_target">
>>
<a>
<xsl:attribute name="href">
<xsl:text>/</xsl:text>
<!-- req-lg: global langauge variable -->
<xsl:value-of select="$req-lg"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="$item_section"/>
<xsl:text>/</xsl:text>
<xsl:if test="$depth = 2">
<xsl:value-of select="../#id"/>
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="#id"/>
</xsl:attribute>
<xsl:value-of
select="$content/page[#id=$cur-id]/title"/>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:if test="ancestor-or-self::item/#section = $item_section and count(./node()) > 0">
>>:
<a>
<xsl:attribute name="href">
<xsl:text>/</xsl:text>
<!-- req-lg: global langauge variable -->
<xsl:value-of select="$req-lg"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="$item_section"/>
<xsl:text>/</xsl:text>
<xsl:if test="$depth = 2">
<xsl:value-of select="../#id"/>
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:value-of select="#id"/>
</xsl:attribute>
<xsl:value-of
select="$content/page[#id=$cur-id and #section=$item_section]/title"/>
</a>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates select="item" mode="Item-Path">
<xsl:with-param name="item_section" select="$item_section"/>
<xsl:with-param name="item_target" select="$item_target"/>
<xsl:with-param name="depth" select="$depth + 1"/>
</xsl:apply-templates>
</xsl:template>
So as the hardcoded parameters in the template breadcrumb, target section = 'service' and target page = 'search-engines-and-ir', I expect an output like
home >> service >> search-engines-and-ir
But the output is
home >> service >> content-management-systems >> search-engines-and-ir
which is obviously not correct.
Can anybody give me a hint how to correct this issue? It would be even more elegant to avoid that depth checking, but up to now I cannot think of a other way, I am sure there is a more elegant solution.
I work with XSLT 1.0 (libxml via PHP5).
Hope my question is clear enough, if not, please ask :-) Thanks for the help in advance!
As simple as this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kNodeById" match="item" use="#id"/>
<xsl:template match="/">
<xsl:text>home</xsl:text>
<xsl:call-template name="findPath">
<xsl:with-param name="pStart" select="'service'"/>
<xsl:with-param name="pEnd" select="'search-engines-and-ir'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="findPath">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:for-each select=
"key('kNodeById', $pEnd)
[ancestor::item[#section=$pStart]]
[1]
/ancestor-or-self::item
[not(descendant::item[#section=$pStart])]
">
<xsl:value-of select=
"concat('>>', #id[not(../#section)], #section)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the wanted, correct result is produced:
home>>service>>search-engines-and-ir
Do Note:
This solution prints the breadcrumb from any node -- anywhere in the hierarchy to any of its descendent nodes -- anywhere in the hierarchy. More precisely, for the first item (in document order) with id attribute equal to $pEnd, the breadcrumb is generated from its inner-most ancestor whose section attribute is equal to $pStart -- to that item element.
This solution should be much more efficient than any solution using //, because we are using a key to locate efficiently the "end" item element.
II. XSLT 2.0 solution:
Much shorter and easier -- an XPathe 2.0 single expression:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kNodeById" match="item" use="#id"/>
<xsl:template match="/">
<xsl:value-of select=
"string-join(
(
'home',
key('kNodeById', $pEnd)
[ancestor::item[#section=$pStart]]
[1]
/ancestor-or-self::item
[not(descendant::item[#section=$pStart])]
/(#id[not(../#section)], #section)[1]
),
'>>'
)
"/>
</xsl:template>
</xsl:stylesheet>
You can just iterate over the ancestor-or-self:: axis . This is easy to do without recursion. For example...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html><body>
<xsl:call-template name="bread-crumbs">
<xsl:with-param name="items" select="*/item" />
<xsl:with-param name="section" select="'service'" />
<xsl:with-param name="leaf" select="'p1-2'" />
</xsl:call-template>
</body></html>
</xsl:template>
<xsl:template name="bread-crumbs">
<xsl:param name="items" />
<xsl:param name="section" />
<xsl:param name="leaf" />
<xsl:value-of select="concat($section,'>>')" />
<xsl:for-each select="$items/self::item[#section=$section]//item[#id=$leaf]/
ancestor-or-self::item[not(#section)]">
<xsl:value-of select="#id" />
<xsl:if test="position() != last()">
<xsl:value-of select="'>>'" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...on your sample input yields...
<html>
<body>service>>content-management-systems>>p1-2</body>
</html>
...which renders as...
service>>content-management-systems>>p1-2
I have a constant and a variable that I wann mouch together to select a specific node, this is what I want to do:
<xsl:attribute name="value">
<xsl:value-of>
<xsl:attribute name="select">
<xsl:text>/root/meta/url_params/
<xsl:value-of select="$inputid" />
</xsl:attribute>
</xsl:value-of>
</xsl:attribute>
How come it doesn't work, and what could I do instad?
While #Alejandro is right that in the general case dynamic evaluation will be needed (and this may be provided in XSLT 2.1+), there are manageable simpler cases.
For example, if $inputid contains just a name, you probably want this:
<xsl:value-of select="/root/meta/url_params/*[name()=$inputid]"/>
We can implement a rather general dynamic XPath evaluator if we only restrict each location path to be an element name:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="inputId" select="'param/yyy/value'"/>
<xsl:variable name="vXpathExpression"
select="concat('root/meta/url_params/', $inputId)"/>
<xsl:template match="/">
<xsl:value-of select="$vXpathExpression"/>: <xsl:text/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression"
select="$vXpathExpression"/>
</xsl:call-template>
</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:stylesheet>
when this transformation is applied on this XML document:
<root>
<meta>
<url_params>
<param>
<xxx>
<value>5</value>
</xxx>
</param>
<param>
<yyy>
<value>8</value>
</yyy>
</param>
</url_params>
</meta>
</root>
the wanted, correct result is produced:
root/meta/url_params/param/yyy/value: 8
There is no runtime evaluation for XPath expression in standar XSLT 1.0
So, depending what is $inputid, you could have different solutions.
But this /root/meta/url_params/$inputid is wrong because right hand of / must be a relative path in XPath 1.0 (in XPath 2.0 can be a function call, also).
For this particulary case you can use:
/root/meta/url_params/*[name()=$inputid]
or
/root/meta/url_params/*[#id=$inputid]
For a general case, I will go with walker pattern like Dimitre's answer.