Reputation: 23
I have a stylesheet that takes two files, one from an engineering repo, the second from a documentation repo, and merges them to create some DITA files (which are further processed). Recently, I tried to split the contents of the documentation file into a generic file and a specific file. Thus my merge is now one engineering file with two documentation files.
The generic file is as it was:
<?xml version="1.0" encoding="UTF-8"?>
<messages xmlns:xs="http://www.w3.org/2001/XMLSchema">
<message id="IDENT_STRING">
....
</message>
</messages>
The specific file has an ENTITY tag pointing at the generic file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE messages [
<!ENTITY generic-file SYSTEM "generic-file.xml">
]>
<messages> &generic-file; <!-- specific-file -->
<message id="IDENT_STRING2">
....
</message>
</messages>
The select was written like this:
<xsl:copy-of select="$docid/message[@id=$id]/doc/explanation/text()"/>
This would only grab the content from the specific file. It wasn't until I changed the select to have two slashes that my stylesheet worked correctly. This is the correct version:
<xsl:copy-of select="$docid//message[@id=$id]/doc/explanation/text()"/>
My question(s) to the community is 1) why is this second syntax correct? and 2) how would I have found it a little more expeditiously?
Upvotes: 2
Views: 8061
Reputation: 1273
Here is a good source of information on xpath which what the /x/y//z is, http://www.w3.org/TR/xpath/#location-paths
From the abbreviated syntax section: // is short for /descendant-or-self::node()/. For example, //para is short for /descendant-or-self::node()/child::para and so will select any para element in the document (even a para element that is a document element will be selected by //para since the document element node is a child of the root node); div//para is short for div/descendant-or-self::node()/child::para and so will select all para descendants of div children.
Whether the // is at the start or the middle of the xpath it's meaning is same.
As far as learning this stuff, for me I had to construct small artificial xml or simplified shells of the xml I was trying to transform and then run the xslt. In Visual Studio there was a fairly convenient means to do so back in 2005 or so, I assume it's still there though I haven't messed with it in awhile. I found MSDN or w3.org to be good resources, though the language on w3 can be bit to digest at times.
Using this xml:
<?xml version="1.0" encoding="utf-8"?>
<root>
<a id="a1">
<d id="1" />
<d id="2" />
</a>
<a id="a2">
<d id="3" />
<d id="4" />
</a>
<b>
<c id="1" />
<c id="2" />
</b>
</root>
With this xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<test>
<xsl:apply-templates/>
</test>
</xsl:template>
<xsl:template match="b">
<z>
<select>//a</select>
<xsl:copy-of select="//a"/>
</z>
<z>
<select>.//a</select>
<xsl:copy-of select=".//a"/>
</z>
<z>
<select>.//c</select>
<xsl:copy-of select=".//c"/>
</z>
<z>
<select>/root/a/d</select>
<xsl:copy-of select="/root/a/d"/>
</z>
<z>
<select>/root/a</select>
<xsl:copy-of select="/root/a"/>
</z>
<z>
<question>so what is the node?</question>
<period>
<xsl:copy-of select="."/>
</period>
<slash>
<xsl:copy-of select="/"/>
</slash>
</z>
</xsl:template>
</xsl:stylesheet>
Get this result:
<?xml version="1.0" encoding="utf-8"?>
<test>
<z>
<select>//a</select>
<a id="a1">
<d id="1" />
<d id="2" />
</a>
<a id="a2">
<d id="3" />
<d id="4" />
</a>
</z>
<z>
<select>.//a</select>
</z>
<z>
<select>.//c</select>
<c id="1" />
<c id="2" />
</z>
<z>
<select>/root/a/d</select>
<d id="1" />
<d id="2" />
<d id="3" />
<d id="4" />
</z>
<z>
<select>/root/a</select>
<a id="a1">
<d id="1" />
<d id="2" />
</a>
<a id="a2">
<d id="3" />
<d id="4" />
</a>
</z>
<z>
<question>so what is the node?</question>
<period>
<b>
<c id="1" />
<c id="2" />
</b>
</period>
<slash>
<root>
<a id="a1">
<d id="1" />
<d id="2" />
</a>
<a id="a2">
<d id="3" />
<d id="4" />
</a>
<b>
<c id="1" />
<c id="2" />
</b>
</root>
</slash>
</z>
</test>
So when constructing an xpath, the // will get descendants of that node type, if you start off an xpath with // you are going to the document.
If you start off with . then you are starting your xpath with the current node (if you use xsl:apply-templates the current node is whatever it is after the match in the xsl:template, but if you use xsl:call-template the current node is the same as the current node where you made the xsl:call-template).
If you start off an xpath with a / then you are referencing the root of your document.
Presumably the $docId is a node set that refers to one document or the other, that sets the start point of your xpath, so $docId//message means get all message elements in the node set that is $docId.
And finally the reason you needed the // is that you didn't fully specify the path. In the example I provide I constructed a /root/a/d, which is the full path to the d elements and it pulled all 4 of them out. The $docId//message just allows you are looking for any elements 'message' that is below the root of node set of $docId. Sometimes you may not have a clean symmetry to fetch records: for example, if there you have /messages/critical/message and /messages/warning/message, it would be more convenient to use an xpath of /messages//message or perhaps //messages to get what you need.
I hope this helps.
Upvotes: 5
Reputation: 22647
Without seeing the content of the $docid
variable, your problem is difficult to diagnose. But I'll give it a try.
I assume that this variable contains something like:
<xsl:variable name="docid" select="document('generic-file.xml')"/>
Now, //element
retrieves elements (in this case those with the name "element") regardless of where they are in the document tree - and, more importantly, regardless of where you are in the tree. In other words, an expression that starts with //
does not depend on the context.
So, the following
<xsl:copy-of select="$docid//message[@id=$id]/doc/explanation/text()"/>
means: retrieve from an external document all message
elements regardless of where they are if their id
attribute corresponds to a certain variable $id
. Then, look for a child element doc
and its child element explanation
and copy the text nodes of the latter.
On the other hand, this
<xsl:copy-of select="$docid/message[@id=$id]/doc/explanation/text()"/>
means: retrieve from an external document an element that is the root element and that is called "message". But obviously, in your XML there is no root element message
. The root element there is messages
. So this might have worked:
<xsl:copy-of select="$docid/messages/message[@id=$id]/doc/explanation/text()"/>
An expression with /
depends on the context wherein it is executed.
Even more detail. The transformation below illustrates that although the template matches c
elements (so the context is c
), the a
element can be retrieved with //
.
Input
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a/>
<b>
<c/>
</b>
</root>
Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="c">
<xsl:copy-of select="//a"/>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<a/>
If the template is changed to the following:
<xsl:template match="c">
<xsl:copy-of select=".//a"/>
</xsl:template>
There is no output.
Upvotes: 1