I want to select the last node matching a particular pattern anywhere in the document.
I was trying something like
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="//node_name[last()]"/>
</xsl:template>
</xsl:stylesheet>
However, when running this with Saxon 9.4 on the following document :-
<a>
<node_name attr="1"/>
<b>
<c>
</c>
<node_name attr="2"/>
</b>
</a>
I get this output where the copy statement lies :-
<node_name attr="1"/><node_name attr="2"/>
Whereas i actually want the output :-
<node_name attr="2"/>
What am I missing out on here?
Also, the nature of my document is such that I do not know in advance what the exact path to this node will be (since it's composed of a bunch of recursive elements).
You are not looking for a node_name that is last, you are looking for the last of all the node_names. Therefore, the following XPath expression should work:
(//node_name)[last()]
Related
how can I get a "locale" attribute from urlset tag inside in template, tried many variants but nothing works from it...
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="https://kazik.reved-rfs.ru/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" locale="hu-hu">
<xsl:template match="sitemap:urlset">
...
<td>
<xsl:value-of select="urlset/#locale"/>
</td>
Inside the template for sitemap:urlset if you use an XPath like urlset/#locale it will be looking for a urlset element that is a child of the sitemap:urlset element, and then looking for the #locale on that element.
In order to address the #locale element for the matched sitemap:urlset element, use:
<xsl:value-of select="#locale"/>
You could also use an explicit XPath that "jumps up" to the top of the tree and selects from that element (but since you are already "standing on" that matched element, I'd use the relative XPath above):
<xsl:value-of select="/sitemap:urlset/#locale"/>
I have the following xml-node :
<name>P & P</name>
And following XSL
<a href="example.htm" >
<xsl:attribute name="title">
<xsl:value-of select="name" disable-output-escaping="yes"></xsl:value-of>
</xsl:attribute>
<xsl:value-of select="name" disable-output-escaping="yes"></xsl:value-of>
</a>
That compiles to this HTML
<a href="example.com" title="P & P">
P & P
</a>
So the non-escaping worked for the value (the text between <A></A>) but not for the attribute.
Am I missing something here?
Thanks!
From an OP's comment:
I need this xml (P & P) in the title attribute of an HTML tag. A
better solution is most welcome!
What you need to generate can be done perfectly without D-O-E.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<a href="example.htm" title="{.}">
<xsl:value-of select="."/>
</a>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<t>P & P</t>
the wanted, correct result is produced:
P & P
I've been looking around and I guess this is why : (if I understand correctly) ?
Out of the specs : (http://www.w3.org/TR/xslt)
It is an error for output escaping to be disabled for a text node that
is used for something other than a text node in the result tree. Thus,
it is an error to disable output escaping for an xsl:value-of or
xsl:text element that is used to generate the string-value of a
comment, processing instruction or attribute node; it is also an error
to convert a result tree fragment to a number or a string if the
result tree fragment contains a text node for which escaping was
disabled. In both cases, an XSLT processor may signal the error; if it
does not signal the error, it must recover by ignoring the
disable-output-escaping attribute.
So disabling output for escaping an attribute is just not possible apparantly. The workaround that I see is to build a string 'by hand' as XSL - How to disable output escaping for an attribute?
Still hard to believe that I'm not missing sth. trivial here.
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
I have a document, something like this:
<root>
<A node="1"/>
<B node="2"/>
<A node="3"/>
<A node="4"/>
<B node="5"/>
<B node="6"/>
<A node="7"/>
<A node="8"/>
<B node="9"/>
</root>
Using xpath, How can I select all B elements that consecutively follow a given A element?
It's something like following-silbing::B, except I want them to be only the immediately following elements.
If I am on A (node==1), then I want to select node 2.
If I am on A (node==3), then I want to select nothing.
If I am on A (node==4), then I want to select 5 and 6.
Can I do this in xpath? EDIT: It is within an XSL stylesheet select statement.
EDIT2: I don't want to use the node attribute on the various elements as a unique identifier. I included the node attribute only for purposes of illustrating my point. In the actual XML doc, I don't have an attribute that I use as a unique identifer. The xpath "following-sibling::UL[preceding-sibling::LI[1]/#node = current()/#node]"
keys on the node attribute, and that's not what I want.
Short answer (assuming current() is ok, since this is tagged xslt):
following-sibling::B[preceding-sibling::A[1]/#node = current()/#node]
Example stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:apply-templates select="/root/A"/>
</xsl:template>
<xsl:template match="A">
<div>A: <xsl:value-of select="#node"/></div>
<xsl:apply-templates select="following-sibling::B[preceding-sibling::A[1]/#node = current()/#node]"/>
</xsl:template>
<xsl:template match="B">
<div>B: <xsl:value-of select="#node"/></div>
</xsl:template>
</xsl:stylesheet>
Good luck!
While #Chris Nielsen's answer is the right approach, it leaves an uncertainty in cases where the compared attribute is not unique. The more correct way of solving this is:
following-sibling::B[
generate-id(preceding-sibling::A[1]) = generate-id(current())
]
This makes sure that the preceding-sibling::A is identical to the current A, instead of just comparing some attribute values. Unless you have attributes that are guaranteed to be unique, this is the only safe way.
A solution might be to first gather up all the following nodes using following-sibling::*, grab the first of these and require it to be a 'B' node.
following-sibling::*[position()=1][name()='B']
How can I, with XSLT, select nodes based on a substring of the nodes' element name?
For example, consider the XML:
<foo_bar>Keep this.
<foo_who>Keep this, too.
<fu_bar>Don't want this.</fu_bar>
</foo_who>
</foo_bar>
From which I want to output:
<foo_bar>Keep this.
<foo_who>Keep this, too.
</foo_who>
</foo_bar>
Here I want to select for processing those nodes whose names match a regex like "foo.*".
I think I need an XSLT template match attribute expression, or an apply-templates select attribute expression, that applies the regex to the element's name. But maybe this can't be done without some construct like an statement?
Any help would be appreciated.
Here is some XSL that finds elements that start with "foo" to get you started. I don't think regex functionality was added until XSLT 2.0 based on Regular Expression Matching in XSLT 2.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:variable name="name" select="local-name()"/>
<xsl:if test="starts-with($name, 'foo')">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It gives this output, which seems to have an extra newline.
<foo_bar>Keep this.
<foo_who>Keep this, too.
</foo_who>
</foo_bar>