dennis bloodnok
dennis bloodnok

Reputation: 23

Double slash in XSLT

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

Answers (2)

Felan
Felan

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

Mathias M&#252;ller
Mathias M&#252;ller

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

Related Questions