简体   繁体   中英

XSL document() function not working when using a variable value in a predicate

I have 2 xml documents:

  1. A technical article containing small inline img's
  2. A file containing, among other things, img's that are full-size versions of the small images in the article. I'll call this file a "side file".

My goal is to use xsl to update the xml in the article so that, instead of having one img for each figure, I have 2 img's ... the small one originally coded in the article xml, and the corresponding larger one. Both of these img's will be children of a new element, image-set.

So here's the 'Before' and 'After' situation:

Before:

<figure>
   <heading alttoc="" refname="fig1" type="figure">Figure 1. netserver on SUT in out
      of the box configuration</heading>
   <img alt="netserver on SUT in out-of-the-box configuration" 
        height="288" src="figure1.jpg" width="572"/>
</figure>

After:

<figure>
   <heading alttoc="" refname="fig1" type="figure">Figure 1. netserver on SUT in out
      of the box configuration</heading>
         <image-set>
                <img alt="netserver on SUT in out-of-the-box configuration" 
                     height="288" src="figure1.jpg" width="572"/>
                <img alt="netserver on SUT in out-of-the-box configuration" 
                     height="456" src="figure1_ian.jpg" width="905"/>
         <!--The figureNumber is: 1-->
         </image-set>
</figure>

Another XSL will transform the updated article XML file into HTML. The smaller img will be displayed inline as before, but the larger img will be available in an overlay when the user clicks a "See full-size version" link.

Problem description: Each article can contain many images. Each side file can contain many images. I have to match up the right image in the side file with the image in the article file. I'm creating a variable using xsl:number to store, for each img, a number that corresponds to the order in which each image appears in the article file, and I'm trying to reference that variable in the document() function as a predicate to get the right img in the side file. It's not working:

Here's the variable for storing the order:

<xsl:variable name="figureNumber">
        <xsl:number />
</xsl:variable>

Here's the code with the document() function that's not working with the variable:

<!-- Output the larger version of the same img that sits in the sidefile.
The "/" as the second argument causes processor to look for the  sidefile 
in same folder as the article xml file. -->
<xsl:copy-of select="document('sidefile.xml',/)//figure[$figureNumber]/img" />
<xsl:comment>The figureNumber is: <xsl:value-of select="$figureNumber"/></xsl:comment>

When I run this, instead of getting just the img I want from the side file (in the example above, I should be getting just the first image, or img[1]), I get all the img's in the side file:

<figure>
  <heading alttoc="" refname="fig1" type="figure">Figure 1. netserver on SUT in out
    of the box configuration</heading>
  <image-set>
    <img alt="netserver on SUT in out-of-the-box configuration" 
         height="288" src="figure1.jpg" width="572"/>
    <img alt="netserver on SUT in out-of-the-box configuration" 
         height="456" src="figure1_ian.jpg" width="905"/>
    <img alt="netperf on SUT in out-of-the-box configuration" 
         height="456" src="figure2_ian.jpg" width="905"/>
    <img alt="netperf and netserver (bidirectional) on SUT out of the box" 
         height="456" src="figure3_ian.jpg" width="905"/>
    <img alt="netserver, out of the box with numactl" 
         height="456" src="figure4_ian.jpg" width="905"/>
    <img alt="netperf, out of the box with numactl" 
         height="456" src="figure5_ian.jpg" width="905"/>
    <img alt="netperf and netserver (bidirectional), out of the box with numactl" 
         height="456" src="figure6_ian.jpg" width="905"/>
    <img alt="netserver, Ethernet SMP IRQ affinity, no irqbalance" 
         height="456" src="figure7_ian.jpg" width="905"/>
    <img alt="netperf, Ethernet SMP IRQ affinity, no irqbalance" 
         height="456" src="figure8_ian.jpg" width="905"/>
    <img alt="netperf and netserver (bidirectional), Ethernet SMP IRQ affinity, no irqbalance" 
         height="456" src="figure9_ian.jpg" width="905"/>
    <img alt="netserver, Ethernet SMP IRQ affinity and numactl, no irqbalance" 
         height="456" src="figure10_ian.jpg" width="905"/>
    <img alt="netperf, Ethernet SMP IRQ affinity and numactl, no irqbalance" 
         height="456" src="figure11_ian.jpg" width="905"/>
    <img alt="Bidirectional,  Ethernet SMP IRQ affinity and numactl, no irqbalance" 
         height="456" src="figure12_ian.jpg" width="905"/>
    <img alt="netserver, Ethernet SMP IRQ affinity, no irqbalance, bonded interfaces" 
         height="456" src="figure13_ian.jpg" width="905"/>
    <img alt="netserver, Ethernet SMP IRQ affinity, no irqbalance, with and without bonding" 
         height="456" src="figure14_ian.jpg" width="905"/>
    <!--The figureNumber is: 1-->
  </image-set>
</figure>

However, when I hard-code a predicate in the document() function, I get only the correct img (as in my "After" example above):

<xsl:copy-of select="document('sidefile.xml',/)//figure[1]/img" />

I'm using oXygen 14.2 and have tried this transform with both XALAN and SAXON with the same results.

What am I doing wrong?

19 Aug 2013 update:

I have since tried an alternate method of getting to the correct in the sidefile, but again no luck in getting the document() function to work with a variable inside it. As in my previous method (using offsets), when I replace the variable in the document() function with a literal, it works.

This works:

<xsl:copy-of select="document('sidefile.xml',/)/dw-document/dw-sidefile/docbody/figure/img[preceding::heading[1]/@refname = 'fig1']" />

This doesn't:

<xsl:copy-of select="document('sidefile.xml',/)/dw-document/dw-sidefile/docbody/figure/img[preceding::heading[1]/@refname = $figureRef]" />

The $figureRef variable is defined as follows: With the pointer at a figure element in the article file, grab the string after the '#' in the @href value of the first anchor following the figure; then, surround it with single quotes:

<xsl:variable name="figureRef" select="concat($singleQuote,substring-after(following::a[1]/@href,'#'),$singleQuote)"/>

Here's an xml snippet from the article file showing these elements:

<figure>
    <heading alttoc="" refname="fig1" type="figure">Figure 1. netserver on SUT in out of the box configuration</heading>
<img alt="netserver on SUT in out-of-the-box configuration" height="288" src="figure1.jpg" width="572"/>
</figure>
<p><b><a href="http://www.ibm.com/developerworks/library/l-scalability/sidefile.html#fig1">Enlarge Figure 1.</a></b></p>

...and I know the figureRef variable is resolving to the correct value for each figure in the article because I'm adding xsl:comment's right before the document function to verify that its value is what it should be:

<xsl:when test="not(image-set)">
            <xsl:element name="figure">
                <xsl:apply-templates select="heading" />
                <xsl:element name="image-set">
                    <!-- Output the img element that was inside the original figure element -->
                    <xsl:apply-templates select="img" />
                    <!-- Output the larger version of the same img that sits in the
                        sidefile.  The "/" as the second argument causes processor to look for the
                        sidefile in same folder as the article xml file. -->
                    <xsl:comment>The figureRef is: <xsl:value-of select="$figureRef"/></xsl:comment>
                    <xsl:copy-of
                        select="document('sidefile.xml',/)/dw-document/dw-sidefile/docbody/figure/img[preceding::heading[1]/@refname = $figureRef]" />
                    <xsl:comment>The figureNumber is: <xsl:value-of select="$figureNumber"/></xsl:comment>                        
                </xsl:element>
            </xsl:element>
        </xsl:when>

...and you can see that it's correct from the result document snippet below (which should have another img element after the first):

<image-set>
    <img alt="netserver on SUT in out-of-the-box configuration" height="288" src="figure1.jpg" width="572"/>
    <!--The figureRef is: 'fig1'-->
    <!--The figureNumber is: 1-->
</image-set>

Sheesh. Here is an xml snippet showing the structure of the side file:

<figure>
    <heading alttoc="" refname="fig1" type="figure">Figure 1. netserver on SUT in out
      of the box configuration</heading>
    <img alt="netserver on SUT in out-of-the-box configuration" height="456" src="figure1_ian.jpg" width="905"/>
  </figure>

  <!-- Spacer  -->
  <br/>
  <br/>
  <!-- Return link -->
  <p>
    <a href="index.html#fig1">Return to article</a>
  </p>
  <!-- Spacer -->
  <br/>

  <figure>
    <heading alttoc="" refname="fig2" type="figure">Figure 2. netperf on SUT in out of
      the box configuration</heading>
    <img alt="netperf on SUT in out-of-the-box configuration" height="456" src="figure2_ian.jpg" width="905"/>
  </figure>

The first thing you're doing wrong is trying to link things by offsets -- well known for fifty years or so as the simplest form of linking to implement and the most fragile and error prone. It breaks any time anything changes, and when it breaks, it breaks silently , which makes it really dangerous. It's just asking for trouble; don't do it. (See this scar here? and that one there? I got those trying to make offset-based linking work reliably. Learn from my sorrow and tears!)

If (as in your example), the small and large image have the same alt attribute and the image file names are systematically related, you would get better results with something like

<xsl:variable name="alt" select="@alt"/>
<xsl:copy-of select="document('side-file.xml',/)
                     //img[@alt = $alt]"/>

Optionally, do a sanity check on the file names:

<xsl:variable name="fn-small" select="@src"/>
<xsl:variable name="fn-big" 
              select="document('side-file.xml',/)
                     //img[@alt = $alt]/
                     @src"/>
<xsl:if test="substring-before($fn-small,'.jpg')
             != substring-before($fn-big,'_ian.jpg')">
  <xsl:message>Problems with image <xsl:value-of 
    select="concat($fn-small, ' / ', $fn-big)"/>.</xsl:message>
</xsl:if>

However, if your main file and your side file really do reliably have the small and large images in the same sequence, the approach you are attempting can be made to work. (At least, up until the first time anything in either document changes the order or number of figures.) Since you show very little of your main file and nothing of your side file, it's hard to know exactly what is going wrong with your code, but there are some obvious things to check:

  • <xsl:number/> defaults to <xsl:number level="single" .../> -- so it should work fine if all the figures in your main document are siblings. (That would mean you have a technical article with fourteen figures and no sections. Please tell me that's not so.) If you want a running sequence number for figures, you want something like <xsl:number level="any"/> -- I think some XSLT coders would write <xsl:variable name="figureNumber" select="1 + count(preceeding::figure)"/> instead.
  • The fact that you write document('sidefile.xml',/)//figure ... suggests that the figure elements in the side document may be at any depth, and not necessarily all siblings; if that's so, selecting them by [$figureNumer] is never going to work. Recall that // expands to /descendant-or-self::*/, so your expression expands to

     document('sidefile.xml',/) /descendant-or-self::* /child::figure[position() = $figureNumber] /img 

which means that the implicit position() in the predicate will get the figure's position in the sequence of its parent's figure children, not its position among the figure descendants of the document node.

Problem identified and fixed, thanks to a colleague's very simple suggestion.

The $figureRef variable is already a string value, so no need to surround it with single quotes. Adding the quotes ensured I'd never get the result set I needed. So....

Before:

<xsl:variable name="figureRef" select="concat($singleQuote,substring-after(following::a[1]/@href,'#'),$singleQuote)"/>

After:

<xsl:variable name="figureRef"><xsl:value-of select="substring-after(following::a[1]/@href,'#')"/></xsl:variable>

Using it in the document() function:

<xsl:copy-of select="document('sidefile.xml',/)/dw-document/dw-sidefile/docbody/figure/img[preceding::heading[1]/@refname = $figureRef]" />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM