Subject: Re: [xsl] Overlapping structures From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx> Date: Tue, 9 Oct 2001 16:24:39 +0100 |
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
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
RE: [xsl] Overlapping structures, Michael Kay | Thread | [xsl] Re: XSL-List Digest V3 #1158, Avani Goel |
[xsl] SVG text element and word bre, DPawson | Date | [xsl] xsl:import, variables and htm, Julio Kriger |
Month |