tags:

views:

200

answers:

3

Related to the question here.

I have XML input of the form:

<book>
    <chapter>
        ...
    </chapter>
</book>

I am currently outputting something like:

<div class="book" id="book-3">
    <div class="chapter>
        ...
    </div>
</div>

Using the rule <div class="book" id="book-{position()}"> to create the a book id. So that the value of the id increments on each <book> node.

I would like output of the form:

<div class="book" id="book-3">
    <div class="chapter" id="book-3-chapter-7">
        ...
    </div>
</div>

I have been trying to use a rule which retrieved the id attribute from the parent and output it in the child. It was only as I was asking this question that I realised of course there would never be a match for "../@id" because the attribute doesn't exist in the source XML, only the output.

Attempting to get the parent attribute to use as part of the id is probably not "the XSLT way". So even if there is a way to access the output, it's probably the wrong way to do this.

Once the chapter tags are created with their id, there is children of the chapter elements I would like to follow the same convention (so they would have <div id="book-2-chapter12-verse212">). The id will be used by a separate Java application to link in to the document, so it's important that it follows a convention (i.e. generated ids won't work).

What would be the best way to create this nested incremented counter using XSLT?

P.S. The title of the question is probably miles off what I really want to ask, but I don't know XSLT enough to ask the right question. Please feel free to edit, or suggest a better title.


EDIT: Although there have been good answers for the original question, as I said, I wasn't really sure how to ask the question properly. Having been given the answer for the question I should have asked, I have modified the question.

This means there are answers which address my (badly-put) original question and an answer which understands the intent I found difficult to express. I have accepted the latter.

I hope the others who answered find this acceptable, and if there are any suggestions for how to handle asking the wrong question in the first place, I'd be glad to hear them.

+2  A: 

I would probably add a parameter to the template, and use with-param in the apply-templates to pass it down. Avoids all the ambiguity.

<xsl:template match="..."> <!-- the child match -->
    <xsl:parameter name="parentId"/>
    <child id="{$parentId}-@id">
      <!-- code involving $parentId -->
    </child>
</xsl:template>

and something like:

<xsl:apply-templates select="child">
    <xsl:with-param name="parentId" select="@id"/>
</xsl:apply-templates>

Another option is to use .. etc frmo the child - perhaps using a template-match on the @id (from both places) to avoid duplication.

Marc Gravell
This certainly seems sensible! Would I then take the line `<div class="book" id="book-{position()}">` and extract the `{position()} into an `<xsl:variable>` to be used as the parameter?
Grundlefleck
@Grundlefleck: You would simply skip the variable part and declare `<xsl:with-param name="parentId" select="position()" />` (though I would probably name id `$bookNum` instead).
Tomalak
@Tomalak: thanks, and nice to see you again :-p
Grundlefleck
+3  A: 

You should take a look at the xsl:number element. It has a bunch of formatting and grouping constructs to accomplish the sort of counts and labels you are trying to identify.

This XML.com article explains how to use some of the attributes to produce multi-level numbers(counts) for book, chapter, section, etc.

Something like:

<xsl:number format="1. " level="multiple" count="book|chapter|verse"/>

applied in the template for the verse would produce: 2.12.212

Mads Hansen
A: 

I would use a generic function for this purpose:

<xsl:template match="*">
  <xsl:copy>
    <xsl:attribute name="id">
      <xsl:call-template name="compute-id"/>
    </xsl:attribute>
    <xsl:apply-templates />
  </xsl:copy>
</xsl:template>

<xsl:template name="compute-id">
  <xsl:param name="curr-elem" select="."/>
  <xsl:param name="id" select="''"/>
  <xsl:choose>
    <xsl:when test="$curr-elem/..">
    <xsl:call-template name="compute-id">
      <xsl:with-param name="curr-elem" select="$curr-elem/.."/>
      <xsl:with-param name="id">
        <xsl:value-of select="concat(name($curr-elem), count($curr-elem/preceding-sibling::*[name()=name($curr-elem)])+1)"/>
        <xsl:if test="$id != ''">
          <xsl:value-of select="concat('-', $id)"/>
        </xsl:if>
      </xsl:with-param>
    </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$id"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
Erlock