0

xslt 1.0 I am afraid. I have access to exslt. I am otherwise looking to try to avoid using extension functions.

I need to find the most recently CLOSED xslt element of a particular type, rather than the most recently opened.

EDIT for clarity: ie if i moved a cursor backwards from current() through the text representation of the document until I hit </x> then I wish to find the x that that is the closing tag of.

an illustrative example:

<e3>
  <e2>
    <x id="1">
      <y/><!--if run with this 'y' as current() then it should pick up nothing-->
      ...
      <x id="2">
        <y/><!--if run with this 'y' as current() then it should pick up nothing-->
        ...
        <x id="3"/>
        <y/><!--if run with this 'y' as current() then it should pick up id="3"-->
      </x>
      ...
      <y/><!--if run with this 'y' as current() then it should pick up id="2"-->
    </x>
  </e2>
  <e1>
    ...
    <y/><!--if run with this 'y' as current() then it should pick up id="1"-->
  </e1>
</e3>

[please note this is illustrative, the x's and y's can be arbitrarily nested withing containing elements]

All of my attempts to do this are nasty horror shows. The 'best' of them is simply to preprocess and put a self closing marker after each relevant close, and then use preceding::marker[1]/preceding-sibling:x[1] but I hate doing a full copy to achieve this.

Otherwise my best bet is

<xsl:variable name="currentId" select="generate-id(.)"/>
preceding:x[1]/ancestor-or-self:x[following::node()[generate-id(.) = $currentId]][last()]

(ie find the most recently opened one, check its ancestors to see if the current node is after it)

The issue is efficiency, this potentially runs generate-id(.) though the whole document many, many times.

I guess I am asking if I am missing something 'obvious'.


TRYING AGAIN: Suppose I had the following xslt:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:variable name="intermediate">
      <xsl:apply-template select="." mode="intermediate" />
    </xsl:variable>
    <xsl:copy>
      <xsl:apply-templates mode="final" select="exslt:node-set($intermediate)"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template mode="intermediate" match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" mode="intermediate" />
    </xsl:copy>
  </xsl:template>

  <xsl:template mode="intermediate" match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" mode="intermediate" />
    </xsl:copy>
  </xsl:template>
  <xsl:template mode="intermediate" match="x">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" mode="intermediate" />
    </xsl:copy>
    <heyAnXJustEndedHere/><!-- heyAnXJustEndedHere is guaranteed to not be an element name in the input-->
  </xsl:template>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates mode="final" select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="y">
    <xsl:copy>
      <xsl:attribute name="lastClosingXhadID"><xsl:value-of select="preceeding::heyAnXJustEndedHere[1]/preceding-sibling::x[1]/@id"/></xsl:attribute>
      <xsl:apply-templates mode="final" select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

I am looking for a way to achieve the same output, for all input documents, without using node-set / 2 passes.

12
  • I don't understand what you mean by "if run here". Any instruction in XSLT is run from some context in the XML tree. In your example, you have two "if run" comments in the same context of x[@id='3'] yet you expect different results. Commented Jun 26 at 10:58
  • @michael.hor257k I am suggesting there are actual nodes in the position of those comments that would be the current() node of the template, i have changed the example somewhat, clearer?
    – tolanj
    Commented Jun 26 at 10:59
  • So maybe I am missing something, but if these comments were actual nodes, then the expression preceding-sibling::x[1]/@id when evaluated from their respective contexts would return the values you expect. Commented Jun 26 at 11:15
  • P.S. Your edited example is not well-formed XML (no single root element) and cannot be processed by XSLT at all. Commented Jun 26 at 11:22
  • @michael.hor257k its an example fragment intended to illustrate the question not a document, The sibling behavior is due to the simplification so I will change the example for that
    – tolanj
    Commented Jun 26 at 11:30

1 Answer 1

0

I have gone for this in the end, which is not too bad:

<!-- If something preceeds me it closed before me, its ancestors either preceed me OR contain me
 the last x to close before me MUST be either the last to open before me, or an ancestor of it. 
 They closed before me iff they are not my ancestors, with the lowest depth that does so being the last closing-->

<xsl:variable name="lastClosingX" select="preceding::x[1]/ancestor-or-self::x[not(exslt:has-same-node(., current()/ancestor::x))][last()]"/>

Not the answer you're looking for? Browse other questions tagged or ask your own question.