This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: Overlapping structures


Hi Stuart,

> In my XSL I want to test, from any node <z.start/> if there is the
> additional empty element <x/> before the next <z.end/> (i.e. if the
> imaginary "z" element "contains" x). I have not found any way I can
> achieve this -- any pointers please?

>From the z.start element, you can get hold of the next z.end element
by searching all following nodes and picking on the first z.end
element:

  following::z.end[1]

For efficiency, you only need to look at the first x element that
follows the z.start element:

  following::x[1]

and then check whether its immediately following z.end element is the
same as the z.end element that immediately follows the z.start
element. You could do this by comparing their IDs:

  generate-id(following::x[1]/following::z.end[1]) =
  generate-id(following::z.end[1])

or using set logic:

  count(following::x[1]/following::z.end[1] | following::z.end[1]) = 1

> Secondly, if I want to invert the structures, so that the "a" tags
> become the imaginary empty tags and the z tags "real" elements, I am
> currently cheating to overcome the well-formed constraint as follows
> (ignoring the x element above):

OK, this is pretty tricky in XSLT. I agree with Mike that a SAX filter
would be the best 'pure' method and agree with Jörg that disabling
output escaping would be an effective hack (though one that brings
with it the danger of generating non-well-formed XML).

If you want to use XSLT, probably the easiest thing to do is to split
it into two processes: first turn all the current 'real' elements into
empty placeholder elements, so that you have a completely flat
structure to work with; and second use a grouping method to create
'real' elements for the elements you're now interested in. I think you
can probably work out how to do both of those, but give a shout if you
can't.

For a one-step pure XSLT solution, you need to work through the node
tree step-by-step rather than just applying templates to everything.
The following template moves to the 'next' node in the tree and
applies templates to it in 'invert' mode. If there isn't a 'next' node
in the tree, then it climbs up the tree emitting empty .end markers by
applying templates in endTag mode.

<xsl:template match="node()" mode="moveToNext">
  <xsl:choose>
    <xsl:when test="node()">
      <xsl:apply-templates select="node()[1]" mode="invert" />
    </xsl:when>
    <xsl:when test="following-sibling::node()">
      <xsl:apply-templates select="following-sibling::node()[1]"
                           mode="invert" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select=".." mode="endTag" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The endTag mode template is:

<xsl:template match="*" mode="endTag">
  <xsl:element name="{name()}.end" />
  <xsl:choose>
    <xsl:when test="following-sibling::node()">
      <xsl:apply-templates select="following-sibling::node()[1]"
                           mode="invert" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select=".." mode="endTag" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The 'invert' mode template is the following - it just applies
templates to this node first in normal mode (to get e.g. the .start
marker) and then in moveToNext mode to move on to the next relevant
node:

<xsl:template match="node()" mode="invert">
  <xsl:apply-templates select="." />
  <xsl:apply-templates select="." mode="moveToNext" />
</xsl:template>

The template for elements in default mode gives the .start marker
element, and also gives a .end one if the element's empty:

<xsl:template match="*">
  <xsl:element name="{name()}.start" />
  <xsl:if test="not(node())">
    <xsl:element name="{name()}.end" />
  </xsl:if>
</xsl:template>

You then need to override the 'invert' template for the elements that
are currently placeholders. This generates the new element, applies
templates to the next node in the tree to get the content, and
applies templates to the node following the matching .end marker to
get the stuff after the end of the new element.

<xsl:template match="z.start" mode="invert">
  <z>
    <xsl:apply-templates select="." mode="moveToNext" />
  </z>
  <xsl:apply-templates select="following::z.end[1]" mode="moveToNext" />
</xsl:template>

The template for the .end marker in 'invert' mode does nothing.

<xsl:template match="z.end" mode="invert" />

And you also need a 'do nothing' template in 'endTag' mode for
whatever element you have that holds the elements you're inverting - I
used 'doc', so I have:

<xsl:template match="doc" mode="endTag" />

Finally, the thing that kicks it off is applying templates to the
first (meaningful) element in the document in 'invert' mode, which I
did from within the template for my 'doc' element:

<xsl:template match="doc">
  <doc>
    <xsl:apply-templates select="node()[1]" mode="invert" />
  </doc>
</xsl:template>

I'm afraid it's not very pretty, but I think it works (it does on a
small number of tests that I've done, using Saxon and MSXML, though
not, for some reason I haven't yet identified, with the version of
Xalan that I have here).

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]