I just need to overwrite the variable in xsl
Example:
x=0
if x=0
then
x=3
I need to change the value of variable.
I am very new to xsl, please help me how to achieve this. This may be silly but I don't have any idea..
I just need to overwrite the variable in xsl
Example x=0 if x=0 then x=3
XSLT is a functional language and among other things this means that a variable, once defined cannot be changed.
Certainly, this fact doesn't mean that a given problem cannot be solved using XSLT -- only that the solution doesn't contain any modifications of variable values, once defined.
Tell us what is your specific problem, and many people will be able to provide an XSLT solution :)
As other comments have noted, variables in XSLT cannot be modified once they are set. The easiest way I've found to do this is to nest variables inside each other.
<xsl:variable name="initial_condition" select="VALUE"/>
Later
<xsl:variable name="modified_condition" select="$initial_condition + MODIFIER"/>
Some of our xsl has whole reams of nested calculations which really should be in the business logic which produces the source XML. Due to a period of time where there was no developer / time to add this business logic, it was added as part of the presentation layer.
It becomes extremely hard to maintain code like this, especially considering you've probably got control flow considerations to make. The variable names end up being very convoluted and readability drops through the floor. Code like this should be a last resort, it's not really what XSLT is designed for.
The <xsl:variable> in xslt is not actual a variable. Which means it can not be changed after you have defined it and you can use it like this:
lets say we have this xml with name test.xml:
<?xml version="1.0" encoding="UTF-8"?>
<client-list>
<client>
<name>person1</name>
</client>
<client>
<name>person2</name>
</client>
<client>
<name>person3</name>
</client>
</client-list>
and we want to transform it to csv-like (comma separated values) but replacing the person1 with a hidden person with name person4. Then lets say we have this xml with name test.xsl which will be used to transform the test.xml:
<?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"/>
<xsl:variable name="hiddenname">person4</xsl:variable>
<!-- this template is for the root tag client-list of the test.xml -->
<xsl:template match="/client-list">
<!-- for each tag with name client you find, ... -->
<xsl:for-each select="client">
<!-- if the tag with name -name- don't have the value person1 just place its data, ... -->
<xsl:if test="name != 'person1'">
<xsl:value-of select="name"/>
</xsl:if>
<!-- if have the value person1 place the data from the hiddenperson -->
<xsl:if test="name = 'person1'">
<xsl:value-of select="$hiddenname"/>
</xsl:if>
<!-- and place a comma -->
<xsl:text>,</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the results will be
person4,person2,person3,
I hope this will help you.
Related
I have the following XSLT template
<xsl:template match="/root">
<r>
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
</r>
</xsl:template>
Which I use to translate the following example XML
<root>
<transport>
<route>
<ship> <!-- this can be a car -->
<fuel>
<litres>
42
</litres>
</fuel>
</ship>
</route>
<route>
<enddate>2015-08-21</enddate>
<car>
<fuel>
<litres>
42
</litres>
</fuel>
</car>
</route>
</transport>
</root>
Notice that under route I can have a ship OR a car.
I can't find a way to reduce the xpath exression to only cover for the choice between ship and car
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
To make the above work I have to change it to this:
<xsl:value-of
select="transport/route[not(enddate)]/ship/fuel/litres |
transport/route[not(enddate)]/car/fuel/litres"/>
but I that feels as I'm copying to much of the expression and violates my DRY nature. I tried transport/route[not(enddate)][car|ship]/fuel/litres but without success.
What should the expression be if I want to get rid of the duplication of the xpath?
One way to write your expression is as follows:
<xsl:value-of select="transport/route[not(enddate)]/*[self::car or self::ship]/fuel/litres"/>
Alternatively, if a route element can contain only only child element (whether ship, car or something else, you could also write this:
<xsl:value-of select="transport/route[not(enddate)][ship or car]/*/fuel/litres"/>
Using an XSLT 2.0 or 3.0 processor you can use <xsl:value-of
select="transport/route[not(enddate)]/(ship | car)/fuel/litres"/> but be aware that value-of with a version="2.0" or version="3.0" stylesheet outputs a sequence of values of the selected sequence and not the first value in the selected sequence like version="1.0" does.
I am facing issue in writing xapth. Let me explain the problem.
I am writing xslt to transform some xml. The xslt also loads one xml file from disk into xslt variable.
PeopleXml.xml:
<TestXml>
<People>
<Person id="MSA1" name="Sachin">
<Profession>
<Role>Developer</Role>
</Profession>
</Person>
<Person id="ZAG4" name="Rahul">
<Profession>
<Role>Tester</Role>
</Profession>
</Person>
</People>
</TestXml>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://MyNamespace"
version="2.0">
<xsl:variable name="PeopleXml" select ="document('PeopleXml.xml')"/>
<xsl:variable name="peopleList" select="$PeopleXml/TestXml/People/Person"/>
<xsl:variable name="person1" select="MSA1"/>
<xsl:variable name="person" select="$peopleList/Person[#id=$person1]/#name"/>
<xsl:template match="/">
<xsl:value-of select="$person"/>
</xsl:template>
</xsl:stylesheet>
Issue: The xpath "$peopleList/Person[#id=$person1]/#name" is not returning anything. Infact, $peopleList/Person also does not work. However, I can see two person nodes in $peopleList variable when I debugged the code.
Could anyone help me, what I am doing wrong in xpath?
EDIT
Above xapth issue has been resolved after applying Daniel's solution. Now, only issue remained is with accessing child nodes of person based on some condition.
Following test does not work.
<xsl:variable name="roleDev" select="'Developer'"/>
<xsl:when test="$peopleList/Profession/Role=$roleDev">
<xsl:value-of select="We have atleast one Developer"/>
</xsl:when>
Your problem is here:
<xsl:variable name="person1" select="MSA1"/>
This results in having the $person1 variable empty.
Why?
Because the expression MSA1 is evaluated -- the current node doesn't have any children named "MSA1" and nothing is selected.
Solution:
Specify the wanted string as string literal:
<xsl:variable name="person1" select="'MSA1'"/>
Your Second Question:
Now, only issue remained is with accessing child nodes of person based
on some condition.
Use:
boolean($peopleList[Profession/Role = 'Developer'])
This produces true() exactly when there is a node in $peopleList such that it has at least one Profession/Role crand-child whose string value is the string "Developer"
Since the variable peopleList is already Person nodes, you should access them like this:
<xsl:variable name="person" select="$peopleList[#id=$person1]/#name"/>
I have a set of data called <testData> with many nodes inside.
How do I detect if the node exists or not?
I've tried
<xsl:if test="/testData">
and
<xsl:if test="../testData">
Neither one works. I'm sure this is possible but I'm not sure how. :P
For context the XML file is laid out like this
<overall>
<body/>
<state/>
<data/>(the one I want access to
</overall>
I'm currently in the <body> tag, though I'd like to access it globally. Shouldn't /overall/data work?
Edit 2:
Right now I have an index into data that I need to use at anytime when apply templates to the tags inside of body. How do I tell, while in body, that data exists? Sometimes it does, sometimes it doesn't. Can't really control that. :)
Try count(.//testdata) > 0.
However if your context node is textdata and you want to test whether it has somenode child or not i would write:
<xsl:if test="somenode">
...
</xsl:if>
But I think that's not what you really want. I think you should read on different techniques of writing XSLT stylesheets (push/pull processing, etc.). When applying these, then such expressions are not usually necessary and stylesheets become simplier.
This XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()"/> <!-- for clarity only -->
<xsl:template match="body">
<xsl:if test="following-sibling::data">
<xsl:text>Data occurs</xsl:text>
</xsl:if>
<xsl:if test="not(following-sibling::data)">
<xsl:text>No Data occurs</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to this sample:
<overall>
<body/>
<state/>
<data/>(the one I want access to
</overall>
Will produce this correct result:
Data occurs
When applied to this sample:
<overall>
<body/>
<state/>
</overall>
Result will be:
No Data occurs
This will work with XSL 1.0 if someone needs...
<xsl:choose>
<xsl:when test="/testdata">node exists</xsl:when>
<xsl:otherwise>node does not exists</xsl:otherwise>
</xsl:choose>
I'm writing XSL and I want to make comments throughout the code that will be stripped when it's processed, like PHP, however I'm not sure how.
I'm aware of the comment object, but it prints out an HTML comment when processed. :\
<xsl:comment>comment</xsl:comment>
You use standard XML comments:
<!-- Comment -->
These are not processed by the XSLT transformer.
Just make sure that you put your <!-- comments --> AFTER the opening XML declaration (if you use one, which you really don't need):
BREAKS:
<!-- a comment -->
<?xml version="1.0"?>
WORKS:
<?xml version="1.0"?>
<!-- a comment -->
I scratched my head on this same issue for a bit while debugging someone else's XSLT... seems obvious, but easily overlooked.
Note that white space on either side of the comments can end up in the output stream, depending on your XSLT processor and its settings for handling white-space. If this is an issue for your output, make sure the comment is bracketed by xslt tags.
EG
<xsl:for-each select="someTag">
<xsl:text>"</xsl:text>
<!-- output the id -->
<xsl:value-of select="#id"/>
<xsl:text>"</xsl:text>
</xsl:for-each>
Will output " someTagID" (the indent tab/spaces in front of the comment tag are output).
To remove, either unindent it flush with left margin, or bracket it like
<xsl:text>"</xsl:text><!-- output the id --><xsl:value-of select="#id"/>
This is the way to do it in order to create a comment node that won't be displayed in html
<xsl:comment>
<!-- Content:template -->
</xsl:comment>
Sure. Read http://www.w3.org/TR/xslt#built-in-rule and then it should be apparent why this simple stylesheet will (well, should) do what you want:
<?xml version="1.0"?>
<xsl:stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="comment()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()|#*"/>
</xsl:stylesheet>
Try :
<xsl:template match="/">
<xsl:for-each select="//comment()">
<SRC_COMMENT>
<xsl:value-of select="."/>
</SRC_COMMENT>
</xsl:for-each>
</xsl:template>
or use a <xsl:comment ...> instruction for a more literal duplication of the source document content in place of my <SRC_COMMENT> tag.
I'm adapting an XSLT from a third party which transforms an arbitrary number of XMLs into a single HTML document. It's a pretty complex script and it will be revised in the future, so I'm trying to do a minimal adaptation in order to get it to work for our needs.
The following is a stripped down version of the XSLT (containing the essentials):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:param name="files" select="document('files.xml')//File"/>
<xsl:param name="root" select="document($files)"/>
<xsl:template match="/">
<xsl:for-each select="$root/RootNode">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="RootNode">
<xsl:for-each select="//Node">
<xsl:text>Node: </xsl:text><xsl:value-of select="."/><xsl:text>, </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Now files.xml contains a list of all the URLs of the files to be included (in this case the local files file1.xml and file2.xml). Because we want to read XMLs from memory rather than from disk, and because the invocation of the XSLT only allows for a single XML source, I have combined the two files in a single XML document. The following is a combination of two files (there may be more in a real situation)
<?xml version="1.0" encoding="UTF-8"?>
<TempNode>
<RootNode>
<Node>1</Node>
<Node>2</Node>
</RootNode>
<RootNode>
<Node>3</Node>
<Node>4</Node>
</RootNode>
</TempNode>
where the first RootNode originally resided in file1.xml and the second in file2.xml.
Due to the complexity of the actual XSLT, I've figured that my best shot is to try to alter the $root-param. I've tried the following:
<xsl:param name="root" select="/TempNode"/>
The problem is this. In the case of <xsl:param name="root" select="document($files)"/>, the XPath expression "//Node" in <xsl:for-each select="//Node"> selects the Node's from file1.xml and file2.xml independently, i.e. producing the following (desired) list:
Node: 1, Node: 2, Node: 3, Node: 4,
However, when I combine the content of the two files into a single XML and parse this (and use the suggested $root-definition), the expression "//Node" will select all Node's that are children of the TempNode. (In other words, the desired list, as represented above, is produced twice due to the combination with the outer <xsl:for-each select="$root/RootNode"> loop.)
(A side note: as observed in comment a) in this page, document() apparently changes the root node, perhaps explaining this behavior.)
My question becomes:
How can I re-define $root, using the combined XML as source instead of a multi-source through document(), so that the list is only produced once, without touching the remainder of the XSLT? It's like if $root defined using the document()-function, there is no common root node in the param. Is it possible to define a param with two "separate" node trees?
Btw: I've tried defining a document like this
<xsl:param name="root">
<xsl:for-each select="/TempNode/*">
<xsl:document>
<xsl:copy-of select="."/>
</xsl:document>
</xsl:for-each>
</xsl:param>
thinking it might solve the problem, but the "//Node" expression still fetches all the Nodes. Is the context node in the <xsl:template match="RootNode">-template actually somewhere in the input document and not the param? (Honestly, I'm pretty confused when it comes to context nodes.)
Thanks in advance!
(Updated more)
OK, some of the problem is becoming clear. First, just to make sure I understand, you aren't actually passing parameters for $files and $root to the XSLT processor invocation, right? (They might as well be variables rather than params?)
Now to the main issues... In XPath, when you evaluate an expression that begins with "/" (including "//"), the context node is ignored [mostly]. Therefore, when you have
<xsl:template match="RootNode">
<xsl:for-each select="//Node">
the matched RootNode is ignored. Maybe you wanted
<xsl:template match="RootNode">
<xsl:for-each select=".//Node">
in which the for-each would select Node elements that are descendants of the matched RootNode? This would fix your problem of generating the desired node list twice.
I inserted [mostly] above because I recalled that an "absolute location path" starts from "the root node of the document containing the context node". So the context node does affect what document is used for "//Node". Maybe that's what you intended all along? I guess I was slow to catch on to that.
(A side note: as observed in comment
a) in this page, document() apparently
changes the root node, perhaps
explaining this behavior.)
Or more precisely,
An absolute location path ["/..."]
followed by a relative location
path... selects the set of nodes that
would be selected by the relative
location path relative to the root
node of the document containing the
context node.
document() doesn't actually change anything, in the sense of side effects; rather, it returns a set of nodes contained (usually) by different documents than the primary source document. XSLT instructions like xsl:apply-templates and xsl:for-each establish new values for the context node inside the scope of their template bodies. So if you use xsl:apply-templates and xsl:for-each with select="document(...)/...", the context node inside the scope of those instructions will belong to an external document, so any use of "/..." as an XPath will start from that external document.
Updated again
How can I re-define $root, using the
combined XML as source instead of a
multi-source through document(), so
that the list is only produced once,
without touching the remainder of the
XSLT?
As #Alej hinted, it's really not possible given the above constraint. If you're selecting "//Node" in each iteration of the loop over "$root/RootNode", then in order for each iteration not to select the same nodes as the other iterations, each value of "$root/RootNode" must be in a different document. Since you're using the combined XML source, instead of a multi-source, this is not possible.
But if you don't insist that your <xsl:for-each select="//..."> XPath expression cannot change, it becomes very easy. :-) Just put a "." before the "//".
It's like if $root defined using the document()-function, there is no common root node
in the param.
The value of the param is a node-set. All nodes in the set may be contained in the same document, or they may not, depending on whether the first argument to document() is a nodeset or just a single node.
Is it possible to define a param with two "separate" node trees?
I believe by "separate", you mean "belonging to different documents"? Yes it is, but I don't think you can do it in XSLT 1.0 unless you're selecting nodes that belong to different documents in the first place.
You mentioned trying
<xsl:param name="root">
<xsl:for-each select="/TempNode/*">
<xsl:document>
<xsl:copy-of select="."/>
</xsl:document>
</xsl:for-each>
</xsl:param>
but <xsl:document> is not defined in XSLT 1.0, and your stylesheet says version="1.0". Do you have XSLT 2.0 available? If so, let us know and we can pursue this option. To be honest, <xsl:document> is not familiar territory for me. But I'm happy to learn along with you.
You can apply only nodes you need:
Input:
<?xml version="1.0" encoding="UTF-8"?>
<TempNode>
<RootNode>
<Node>1</Node>
<Node>2</Node>
</RootNode>
<RootNode>
<Node>3</Node>
<Node>4</Node>
</RootNode>
</TempNode>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="TempNode/RootNode"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RootNode">
<xsl:value-of select="concat('RootNode-', generate-id(.), '
')"/>
<xsl:apply-templates select="Node"/>
</xsl:template>
<xsl:template match="Node">
<xsl:value-of select="concat('Node', ., '
')"/>
</xsl:template>
</xsl:stylesheet>
Output:
RootNode-N65540
Node1
Node2
RootNode-N65549
Node3
Node4