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"/>
Related
I'm trying to access a variable from within a for-each. I've done a lot of reading today to try to figure this out, but nothing quite fits my scenario. Eventually, I will have multiple series like seen below and I will use the variables that I'm pulling out and make different condition. The for-each that I have below is bringing back data from 400 records. I'm sorry, I cannot provide an XML. I'm not able to expose GUIDs and such.
UPDATE
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output media-type="xml" indent="yes"/>
<xsl:template match="/">
<Records>
<xsl:for-each select="Records/Record/Record[#levelGuid = 'level1']">
<xsl:variable name="rocName1" select="Field[#guid = '123']"/>
<xsl:variable name ="rocName2" select="substring-before($rocName1, ' - ')"/>
</xsl:for-each>
<xsl:for-each select="Records/Record/Record[#levelGuid = 'levelA']">
<xsl:variable name ="findingName" select="Field[#guid = '123']"/>
<xsl:variable name="findingName1" select="substring-after($findingName, ': ')"/>
<xsl:variable name="findingName2" select="substring-after($findingName1, 'PCIDSSv3.1:')"/>
</xsl:for-each>
<xsl:if test="$findingName1 = $rocName1">
<Records>
<findingID>
<xsl:for-each select="Records/Record/Record[#levelGuid = '123']">
<xsl:value-of select ="Field[#guid = '123']"/>
</xsl:for-each>
</findingID>
</Records>
</xsl:if>
</Records>
</xsl:template>
</xsl:stylesheet>
The desired output is any findingID that has a $findingName1 that equals $rocName1. The GUIDS only appear once, but each level has hundreds of records.
I'm trying to access a variable from within a for-each.
The variable $rocRecord is in scope inside the for-each. You can simply reference it and use it.
But I think you are trying to do something else. I.e., defining a variable inside for-each and wanting to use it outside it.
Variables are scoped within their focus-setting containing block. So the short answer is: you cannot do it. The long answer however...
Use templates. The only reason to do what you seem to want to be doing is to need to access the data elsewhere:
<xsl:template match="/">
<!-- in fact, you don't need for-each at all, but I leave it in for clarity -->
<xsl:for-each select="records/record">
<!--
apply-templates means: call the declared xsl:template that
matches this node, it is somewhat similar to a function call in other
languages, except that it works the other way around, the processor will
magically find the "function" (i.e., template) for you
-->
<xsl:apply-templates select="Field[#guid='123']" />
</xsl:for-each>
</xsl:template>
<xsl:template match="Field">
<!-- the focus here is what is the contents of your variable $rocName1 -->
<rocName>
<xsl:value-of select="substring=-before(., ' - ')" />
</rocName>
</xsl:template>
XSLT is a declarative, template-oriented, functional language with concepts that are quite unique compared to most other languages. It can take a few hours to get used to it.
You said you did a lot of reading, but perhaps it is time to check a little XSLT course? There are a few online, search for "XSLT fundamentals course". It will save you hours / days of frustration.
This is a good, short read to catch up on variables in XSLT.
Update
On second read, I think it looks like you are troubled by the fact that the loop goes on for 400 items and that you only want to output the value of $rocName1. The example I showed above, does exactly that, because apply-templates does nothing if the selection is empty, which is what happens if the guid is not found.
If the guid appears once, the code above will output it once. If it appears multiple times and you only want the first, append [1] to the select statement.
Update #2 (after your update with an example)
You have two loops:
<xsl:for-each select="Records/Record/Record[#levelGuid = 'level1']">
and
<xsl:for-each select="Records/Record/Record[#levelGuid = 'levelA']">
You then want to do something (created a findingId) when a record in the first loop matches a record in the second loop.
While you can solve this using (nested) loops, it is not necessary to do so, in fact, it is discouraged as it will make your code hard to read. As I explained in my original answer, apply-templates is usually the easier way to do get this to work.
Since the Record elements are siblings of one another, I would tackle this as follows:
<xsl:template match="/">
<Records>
<xsl:apply-templates select="Records/Records/Record[#levelGuid = 'level1']" />
</Records>
</xsl:template>
<xsl:template match="Record">
<xsl:variable name="rocName1" select="Field[#guid = '123']"/>
<xsl:variable name ="rocName2" select="substring-before($rocName1, ' - ')"/>
<xsl:variable name="findingNameBase" select="../Record[#levelGuid = 'levelA']" />
<xsl:variable name ="findingName" select="$findingNameBase/Field[#guid = '123']"/>
<xsl:variable name="findingName1" select="substring-after($findingName, ': ')"/>
<xsl:variable name="findingName2" select="substring-after($findingName1, 'PCIDSSv3.1:')"/>
<findingId rocName="{$rocName1}">
<xsl:value-of select="$findingName" />
</findingId>
</xsl:template>
While this can be simplified further, it is a good start to learn about applying templates, which is at the core of anything you do with XSLT. Learn about applying templates, because without it, XSLT will be very hard to understand.
What I want to do is given an element as context, I want to determine if it has a child with a given name and determine if that child has a node with a given name so I can do operations with it. It is important that I do this in XPath 1.0 syntax.
The code that I've gotten so far is this.
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'description')">
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'text')">
<xsl:value-of select="node()"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
It works, but it's big and ugly and I know that there's a way to condense it. The for-eachs there are unnecessary, since I'm only expecting one child node to be named description, and for it to only have one text node.
I feel like this solution should work
<xsl:for-each select="./description/text">
..
</xsl:for-each>
But it isn't, and I'm not really good enough with XPath Syntax to know why.
The reason I'm asking is because though I've found answers that detect whether a child node has a name, and I've found answers that can get to that child node's context, I haven't found an answer that combines the two, though maybe I just haven't been searching hard enough, in which case I apologize.
Edit: Woops, sorry yeah I forgot to mention that the contains() part of the code was also just a hack because I wasn't sure how to compare their values with equality.
Also as long as the answer is there, <xsl:for-each select="description/text"> does not work either.
A sample of the XML in question is this
<leaf>
<description>
<text> Various Words
</text>
</description>
</leaf>
where the context is the leaf and I am trying to get to the text node.
Edit: The Second Coming:
The problem for me was that my XSLT file was using a default namespace (in my case named a). If I had added that then Borodin's answer would have been correct.
To be specific, this is the code which ended up working for me in the end, in case anyone wants to know.
<xsl:for-each select="a:description/a:text>
<xsl:value-of select="node()"/>
</xsl:for-each>
Thanks Guys ^-^
Do you really want to check whether the element names contain those strings? Or, as your narrative says, do you want elements with that exact name?
To do something like what you have already written, use
<xsl:for-each select="*[contains(name(), 'description')]/*[contains(name(), 'text')]">
<xsl:value-of select="node()"/>
</xsl:for-each>
But if you know the complete names it is a lot neater:
<xsl:for-each select="description/text">
<xsl:value-of select="node()"/>
</xsl:for-each>
If that doesn't work then we need to see more of your source XML and your transform.
Update
If I use this XML
<leaf>
<description>
<text>Various Words</text>
</description>
<description>
<text>More Words</text>
</description>
<description>
<text>Other Words</text>
</description>
</leaf>
and apply this stylesheet
<?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:template match="/leaf">
<xsl:for-each select="description/text">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the output is the expected Various WordsMore WordsOther Words. I don't know how to help you unless you describe your situation better, except to say that transforms should be written with another template rather than for-each wherever possible. Like this variation which produces the same output as above.
<?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:template match="/leaf">
<xsl:apply-templates select="description/text"/>
</xsl:template>
<xsl:template match="text">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
hi all i have written a logic based on a requirement concact more than two data at a time in my xslt code but i m not reaching my expected output can any one give some suggestions
here is my xml
<Swift>
<block4>
<tag>
<name>50K</name>
<value>
0101/0457887750
SAMAROCA
MENENDEZ Y PELAYO
</value>
</tag>
</block4>
</Swift>
i have written an xslt here :
<xsl:template match="swift/message/block4/tag [name='50K']">
<xsl:variable name ="del50k" select ="(translate(substring-after(value,'
'),'
','~'))"/>
<xsl:value-of select="concat(substring(value, 1, 5), ',',substring(substring-before(value,'
'),6), ',',$del50k)" />
</xsl:template>
is that way doing is correct or not ? can any one help
EXPECTED OUTPUT:
0101/,0457887750,SAMAROCA~MENENDEZ Y PELAYO
I'm giving you a full working example based on your input. A few notes:
Use normalize-space() and split the string by space.
Just play with substring-before and substring-after.
make sure to use xsl:strip-space.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="space" select="' '"/>
<xsl:template match="block4/tag[name='50K']">
<xsl:variable name="value" select="normalize-space(value)"/>
<xsl:variable name="code" select="substring-before($value,$space)"/>
<xsl:variable name="string1" select="concat(
substring-before($code,'/'),
'/,',
substring-after($code,'/'))"/>
<xsl:variable name="string2" select="substring-before(
substring-after($value,$space),
$space)"/>
<xsl:variable name="string3" select="substring-after(
substring-after($value,$space),
$space)"/>
<xsl:value-of select="concat($string1,',',$string2,'~',$string3)"/>
</xsl:template>
<xsl:template match="name|value"/>
</xsl:stylesheet>
Your biggest problem is that value is you context node (defined in your template's match attribute), but you're referring to value in your XPath. This will look for a value node within the value node, which is obviously wrong.
In your <xsl:variable> and <xsl:value-of> statements, change refences to value to ., to refer to the current node instead.
I think that's probably not the only issue, but given that your template isn't going to match anything in that document anyway, it's difficult to derive where else it could be going wrong. One possible additional problem is that your substring-before(value,'
') predicate within your <xsl:value-of> isn't going to return anything with the formatting given, as there's a newline before the 0101/etc... Now I think about it, that's also going to be issue in the substring-after in the previous line. That's very dependent on how it's actually formatted though, but from what you've given here, it is a problem.
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 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