[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message] Re: generate-id for identical elements
I used Jeni's functions for all these months, but then I found the time to try my hand at making my own solution again. This problem was the only XSL (or coding in any language) problem I had not solved with my own code, and that bugged me. So, just to close the loop (and because someone might need an XSL 1 solution someday), here's my solution to my own problem (which I bet many others have run into): <xsl:template name="getRef"> <xsl:param name="target"/> <xsl:param name="soFar"/> <!-- soFar has to be seeded with the document's root element --> <xsl:choose> <xsl:when test="contains($target, '/')"> <xsl:variable name="partBefore" select="substring-before($target, '/')"/> <xsl:variable name="elementName" select="substring-before($partBefore, '|')"/> <xsl:variable name="title" select="substring-after($partBefore, '|')"/> <xsl:call-template name="getRefID"> <xsl:with-param name="target" select="substring-after($target, '/')"/> <xsl:with-param name="soFar" select="$soFar/*[name()=$elementName][@title=$title]"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:variable name="elementName" select="substring-before($target, '|')"/> <xsl:variable name="title" select="substring-after($target, '|')"/> <xsl:for-each select="$soFar/*[name()=$elementName][@title=$title]"> <xsl:value-of select="generate-id(./@title)"/> </xsl:for-each> </xsl:otherwise> </xsl:choose> </xsl:template> Also, replacing <xsl:value-of select="generate-id(./@title)"/> with <xsl:number level="multiple" count="chapter|topic|heading|subheading|detail" from="book"/> produces the right number if you are using xsl:number to make IDs (as suggested by someone). BIG thanks to Jeni for the solution that I used all these months and for the education that I got from studying it. Jay Bryant Bryant Communication Services (on contract at Synergistic Solution Technologies) Jeni Tennison <jeni@xxxxxxxxxxxxxxxx> 09/23/2004 02:19 PM Please respond to xsl-list@xxxxxxxxxxxxxxxxxxxxxx To xsl-list@xxxxxxxxxxxxxxxxxxxxxx cc JBryant@xxxxxxxxx Subject Re: generate-id for identical elements > 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
|
PURCHASE STYLUS STUDIO ONLINE TODAY!Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced! Download The World's Best XML IDE!Accelerate XML development with our award-winning XML IDE - Download a free trial today! Subscribe in XML format
|