Re: [xsl] How to use generate-id() inside an xsl:function without a node available?

Subject: Re: [xsl] How to use generate-id() inside an xsl:function without a node available?
From: Abel Braaksma <abel.online@xxxxxxxxx>
Date: Wed, 28 Feb 2007 00:00:19 +0100
Andrew Welch wrote:
Out of interest, how can you call a function multiple times and not be
able to create an ID in the code in the code that makes the call?

For example, in the above code snippet you have (1 to 20) to use.  If
you were iterating over nodes you could use them.  In a recursive
named template you could use the value you're recursing with etc...

My point is - in what situation would you need to create a node to
call generate-id() on?

Well, you asked for it ;)


The story started like this: transform some tabulated data (inside XML) into some nicely formatted PDF table using XSL-FO. Simple enough. But there came the requirement: please make it look "professional" and add skewed and bottom-up texts as the headers. I'm not sure of your understanding of XSL-FO, but I quickly found out that bottom-up texts, skewing (and rotating), along with skewed lines were not amongst the default possibilities of XSL-FO.

In came: SVG. Never done a thing in SVG, but after a couple of hours I found a nice-enough solution using <svg:defs>, <svg:filters>, <svg:use> and many others. SVG is hard without the use of ID and IDREF values (and XLink for that matter). The headers I was talking of earlier on are repeated on several occasions in the same XSL-FO file, and the IDs of all these elements must be unique.

The number of captions was variable, so I chose a simple approach (I though) for creating them (Michael, I added the type definition this time!):

<xsl:function name="my:caption" as="element()">
  <xsl:param name="caption-list" as="xs:string+" />
  <xsl:param name="style-list" as="xs:string+" />
  .... create SVG and FO parts
</xsl:function>

Now, this design has some flaws, but (oh irony) because I didn't want to create intermediate nodes, I chose sequences of strings. The reason being: quick implementation and easy match to my current input, and no extra XML structure to remember.

Well, by now you may already understand where the problem came in: yes, indeed, while inside the above function, it is not possible to use, in any way, generate-id, without first generating a node. If I new this beforehand, I might have chosen a different approach. But still, using nodes, it would not be enough, because these nodes are referenced several times and generate-id() would then create the same ID each time and I don't want that (SVG won't even work then).

To make the long story short: yes, there are workarounds, and no, I cannot use nodes alone (I'd have to add some way of numbering, which is, I may add, a very cruel way of making something unique).

You may want to ask why I need to reference the same node several times. Well, the answer would be: svg:use to create some filter effects, some additional paths and some lines, all for one caption.

Last but not least: in general, when you have a function that returns a node with an ID value (according to xml:id must be unique in the document), and you want this function to be "safe" (i.e.: always return a unique id), you cannot rely on a node that is passed in: it is next-to-impossible to test for the uniqueness of the node itself, and when the function would be called twice with the same node, you want it to return two nodes with different ID values (consider <a name="id" /> or the SVG situation above).

I know, this seems to be strongly against the "no-side-effects" rule and against the principle that when a function is called twice with the same arguments it should return the same results. Well, this is the exception to the rule (as Michael explained).

Cheers,
-- Abel Braaksma

Current Thread