tags:

views:

202

answers:

4

I am using XSLT to convert a very large XML document into (X)HTML. For some of the tags I am converting them to a <div>. I would like to be able to create a unique id for these tags, using an incremented integer to form part of the unique id.

An example of the rule I am using is:

<xsl:template match="bookcoll/book">
    <div class="book">
        <xsl:apply-templates/>
    </div>
</xsl:template>

This XSLT template is working nicely. What I would now like to have is the tag:

<div class="book">;

becoming:

<div class="book" id="book-[COUNTER-VALUE]">

Ideally the counter would start from 1, not 0.

I don't know if it makes much difference, I am using the Java packages javax.xml.parsers and javax.xml.transform to perform the actual transformation. I am a bit of an XSLT noob, so if there's any pertinent information I've missed please let me know.

How could this be achieved in XSLT?

+6  A: 

The natural/idiomatic/failsafe solution would be:

<div class="book" id="book-{generate-id()}">

It's not incrementing, but it's guaranteed to be unique. And it's going to produce HTML-valid ID strings (name tokens).

EDIT: If it must be incrementing, do something like the following:

<!-- in the calling template… -->
<xsl:apply-templates select="bookcoll/book[xpath to filter them if necessary]" />

<!-- …later -->
<xsl:template match="bookcoll/book">
  <div class="book" id="book-{position()}">
    <xsl:apply-templates/>
  </div>
</xsl:template>

You can use format-number() to adapt the output of position() to your needs.

position() will return the node position relative to the "batch" that is currently being processed. With an explicit call to <xsl:apply-templates> you make sure that they are numbered the way you want.

Tomalak
I had read of this way to do it, but the incrementing is really what I'm after :-(
Grundlefleck
The ${COUNTER} was my way of showing it was a variable (as in the bash way of doing it). Edited the question to remove the ambiguity.
Grundlefleck
The `{}` already do variable interpolation in XSLT, the `$` merely denotes a variable.
Tomalak
This may be going out of the scope of the question. position() is incrementing by 2, i.e. it goes book-2, book-4... I assume my template is matching more nodes than I'm expecting with match="bookcoll/book". How do you suggest I figure out the nodes being found by that match?
Grundlefleck
For example, you could be processing the empty text nodes in-between the `<book>` nodes. That's why I said "explicit call to `<xsl:apply-templates>`". A naked `<xsl:apply-templates />` processes any child node it can find, text nodes included.
Tomalak
So - how does `<xsl:template match="bookcoll/book">` get invoked in your code?
Tomalak
The stylesheet: http://pastebin.com/m205a679a, the calling (Java) code: http://pastebin.com/m3493e28cI'm knew to using XSLT, so if it is awful, non-idiomatic, please let me know. Thanks for your help Tomalak :)
Grundlefleck
Change all those `<xsl:apply-templates />` that are intended to process child elements (as opposed to text nodes) to `<xsl:apply-templates select="*" />`, and you should be fine. The star selects element nodes only, so that the text nodes are not processed and cannot spoil your count. Oh, and if you post your XML to pastebin, I can see what I can do about the "idiomatic" part. ;-)
Tomalak
+1 for generate-id()
Thorbjørn Ravn Andersen
The actual XML is pretty massive, ~3MB, over 48k lines, so I'll spare you that! Your help is much appreciated, Tomalak!
Grundlefleck
A stripped-down version of the XML would be enough, I just need to see structure and sample values.
Tomalak
Okay, thanks. The document here: http://pastebin.com/m5e2efa20 is a stripped-down version of one of four texts which use the following DTD: http://pastebin.com/m1fe90b9f Thanks again for all your help!
Grundlefleck
A: 

You can add callouts to static methods on Java classes to your transforms... it works but there are some downsides like 1) Now your transform is tied to some Java code and is harder to test/debug in external tools like Oxygen (although there are some ways to mitigate this) 2) You have to maintain state as statics or perhaps thread locals, which can introduce all kinds of problems (synchronization issues, questions about resetting if you are doing this multiple times, Etc)

Ryan
The transformations are not going to be a regular ocurrence. They're already kicked off with Java code, and they don't need to be performant. Do you have a source or a link to a tutorial for this technique?
Grundlefleck
A: 

Look into the position() function.

Voytek Jarnot
+2  A: 

As suggested several times before, you need position(), but you have to iterate over the items using xsl:for-each:

<xsl:template match="bookcoll">
    <xsl:for-each select="book">
        <div class="book" id="book-{position()}">
            <xsl:apply-templates/>
        </div>
    </xsl:for-each>
</xsl:template>

This will produce something like:

<div class="book" id="book-1">book1</div>
<div class="book" id="book-2">book2</div>
<div class="book" id="book-3">book3</div>

for

<bookcoll>
    <book>book1</book>
    <book>book2</book>
    <book>book3</book>
</bookcoll>
BalusC
I am already processing `<xsl:template match="bookcoll">` to convert hose to divs. I can't appear to get the existing bit to play nice with the for-each loop. Can that be done?
Grundlefleck
Do your XML basically match the XML example I posted? If so, the posted XSL example should do.
BalusC
It does, but I also wanted to transform the `<bookcoll>` to a `<div>`, and was having a bit of trouble doing both. Got it working though, thanks. +1 btw
Grundlefleck