tags:

views:

113

answers:

2

I am attempting to apply an XSLT transformation to a batch of XML documents. The main point of the transform is to re-order several elements. I wish to preserve any comments that directly precede an element:

<!-- not this comment -->
<element />

<!-- this comment -->
<!-- and this one -->
<element />

The closest I've come to a solution is to use the expression:

<xsl:template match="element">
    <xsl:copy-of select="preceding-sibling::comment()"/>
</xsl:template>

which grabs too many comments:

<!-- not this comment -->
<!-- this comment -->
<!-- and this one -->

I understand why the aforementioned XPath does not work correctly, but I don't have any good ideas on how to proceed. What I'm looking for is something to select all preceding comments whose following-sibling is either another comment or the current element being processed:

preceding-sibling::comment()[following-sibling::reference_to_current_element() or following-sibling::comment()]
+1  A: 
<xsl:template match="element">
  <xsl:copy-of select="preceding-sibling::comment()[
    generate-id(following-sibling::*[1]) = generate-id(current())
  "/>
</xsl:template>

More efficient:

<xsl:key 
  name  = "kPrecedingComment" 
  match = "comment()" 
  use   = "generate-id(following-sibling::*[1])" 
/>

<!-- ... -->

<xsl:template match="element">
  <xsl:copy-of select="key('kPrecedingComment', generate-id())" />
</xsl:template>
Tomalak
Thanks, your solution works perfectly! I'll have to do some reading on generate-id() and key().
Joshua Johnson
@Joshua Johnson: Regarding keys: I've written up something that explains how they work on the example of JavaScript, take a look if you want: http://stackoverflow.com/questions/948218/xslt-3-level-grouping-on-attributes/955527#955527 (just read the lower part of the answer).
Tomalak
+1  A: 

I think the best way of putting it would be as follows:

I want the preciding comments but only those with the current node as the first element to follow them.

Then, in xpath1.0/xslt1.0:

<xsl:template match="element">
<xsl:copy-of select="preceding-sibling::comment()[count(following-sibling::*[1]|current()) = 1]"/>
</xsl:template>
Alejandro
+1 - But this lacks a short explanation *why* it works. ;-)
Tomalak
@Tomalak: You're right. Sorry for that! Every node in a node-set is disctint (there is not duplicate "node reference" in a node-set). So, to check that two nodes are in fact the same node, you don't need the generate-id() function (wich garantee a unique id string for a node into the same transformation process). You only need to check that the union produce a node-set with one node. It's a reduce for de identity node-set expression: "count($a | $b) = count($b) and count($b) = count($a)". Sorry for translation, my lenguage is spanish.
Alejandro