[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message]

Re: generate-id for identical elements

Subject: Re: generate-id for identical elements
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Thu, 23 Sep 2004 20:19:49 +0100
xsl generate id
To summarize the problem, I have identical elements (but they do have different parent elements), and I am trying to get cross-references to these identical elements with generate-id().

To get the same generated ID, you need to make sure that you call generate-id() with the same argument. You generate the anchor from the <chapter> element, in:


  <xsl:template match="chapter">
    <h1><a name="{generate-id()}"/><xsl:value-of select="@title"/></h1>
    <xsl:apply-templates/>
  </xsl:template>

You generate the link to that chapter with the code:


  <xsl:template match="ref">
    <xsl:variable name="reftext">
      <xsl:call-template name="makeRef">
        <xsl:with-param name="inString" select="@target"/>
      </xsl:call-template>
    </xsl:variable>
    <a href="#{generate-id(//$reftext)}"><xsl:value-of select="."/></a>
  </xsl:template>

Here, the argument to the generate-id() function is the new document node that's generated when you use the content of <xsl:variable> to set the variable.


Using XSLT 2.0 (I assume that you're happy to do so, since you're using Saxon 8), what you need to do is write a function that takes a path like those used in your references and returns the element that the path references. You can then use the result of calling the function with that path as the argument to the generate-id() function, and get the same ID as the one that you've used in the anchor.

A function to do this might look like:

<xsl:variable name="book" select="/book" />

<xsl:function name="my:parsePath" as="element()">
  <xsl:param name="path" as="xs:string" />
  <xsl:sequence select="my:parsePath($path, $book)" />
</xsl:function>

<xsl:function name="my:parsePath" as="element()">
  <xsl:param name="path" as="xs:string" />
  <xsl:param name="element" as="element()" />
  <xsl:variable name="step" as="xs:string"
    select="if (contains($path, '/')) then substring-before($path, '/')
                                      else $path" />

  <xsl:variable name="elementName" as="xs:string"
    select="substring-before($step, '|')" />
  <xsl:variable name="title" as="xs:string"
    select="substring-after($step, '|')" />

  <xsl:variable name="newElement" as="element()"
    select="$element/*[name() = $elementName][@title = $title]" />

  <xsl:sequence select="if (contains($path, '/'))
                        then my:parsePath(substring-after($path, '/'),
                                          $newElement)
                        else $newElement" />
</xsl:function>

Note that the two function definitions both have the same name, but have different arguments; essentially, this means you have a function whose second argument is optional and defaults to the <book> document element. Also note that the <book> document element has to be stored in a (global) variable to be used in the function, since function bodies are evaluated without the context item being set, and therefore the processor doesn't know what "/" means (which document it refers to) when it sees it at the beginning of a path.

The code breaks down the path step by step, and locates, from the element passed as the second argument, the child element whose name is the same as the substring before the | within the step, and whose title is the same as the substring after the | within the step. If there are more steps, the function calls itself recursively from the element that it's just identified.

You can use the function as in:

<xsl:template match="ref">
  <xsl:variable name="reftext" select="my:parsePath(@target)" />
  <a href="#{generate-id($reftext)}"><xsl:value-of select="."/></a>
</xsl:template>

If you're don't want to use XSLT 2.0, then you need to use a recursive *template* instead, and since templates can't return existing nodes in XSLT 1.0, it will need to do the generate-id() thing itself. Something like:

<xsl:template name="my:parsePath">
  <xsl:param name="path" />
  <xsl:param name="element" select="/book" />
  <xsl:variable name="step">
    <xsl:choose>
      <xsl:when test="contains($path, '/')">
        <xsl:value-of select="substring-before($path, '/')" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$path" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>

  <xsl:variable name="elementName"
    select="substring-before($step, '|')" />
  <xsl:variable name="title"
    select="substring-after($step, '|')" />

  <xsl:variable name="newElement"
    select="$element/*[name() = $elementName][@title = $title]" />

  <xsl:choose>
    <xsl:when test="contains($path, '/')">
      <xsl:call-template name="my:parsePath">
        <xsl:with-param name="path"
          select="substring-after($path, '/')" />
        <xsl:with-param name="element" select="$newElement" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="generate-id($newElement)" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

and call it like:

<xsl:template match="ref">
  <xsl:variable name="reftext">
    <xsl:call-template name="my:parsePath">
      <xsl:with-param name="path" select="@target" />
    </xsl:call-template>
  </xsl:variable>
  <a href="#{$reftext}"><xsl:value-of select="."/></a>
</xsl:template>

Cheers,

Jeni
--
Jeni Tennison
http://www.jenitennison.com

Current Thread

PURCHASE STYLUS STUDIO ONLINE TODAY!

Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!

Buy Stylus Studio Now

Download The World's Best XML IDE!

Accelerate XML development with our award-winning XML IDE - Download a free trial today!

Don't miss another message! Subscribe to this list today.
Email
First Name
Last Name
Company
Subscribe in XML format
RSS 2.0
Atom 0.3
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member
Stylus Studio® and DataDirect XQuery ™are products from DataDirect Technologies, is a registered trademark of Progress Software Corporation, in the U.S. and other countries. © 2004-2013 All Rights Reserved.