Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xslt: How can I apply two templates to the same node during processing?

Tags:

xml

xslt-2.0

I have an XSL template that matches any element with a <var> child:

<xsl:template match="*[var]">
    <xsl:copy >
        <xsl:attribute name="editable">
            <xsl:for-each select="var[@attr]">
                <xsl:value-of
                              select="concat(@attr,
                                             substring(' ',
                                                       1 div (position()!=last())))"/>
            </xsl:for-each>
        </xsl:attribute>
        <xsl:attribute name="constraints">
            <xsl:for-each select="var[@ok]">
                <xsl:value-of
                              select="concat(@attr,':',@ok,
                                             substring(';',
                                                       1 div (position()!=last())))"/>
            </xsl:for-each>
        </xsl:attribute>
        <!-- if this is an <a> then we have to put the stuff inside it inside it -->
        <xsl:apply-templates select="@*" />
        <xsl:apply-templates />
    </xsl:copy>
</xsl:template>

It concatenates the attrs of the var elements into the editable attribute of the parent; and the oks into constraints.

Then I have a template that matches any <field> element:

<xsl:template match="field">
     <span>
        <xsl:attribute name="field">
            <xsl:choose>
                <xsl:when test="boolean(@name)">
                   <xsl:value-of select="./@name"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text>true</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
        <xsl:apply-templates />
    </span>
</xsl:template>

This simply converts it to <span field="name"> with a name the same of the field, if the field had one, or else 'true'.

Problem I'm having is, *[var] matches a field if the field has a <var> as a child. But what I want to happen is for the *[var] to match first, and then field to match as well, but afterwards.

Currently, with an input of

<field name="field1">
    <var attr="class" ok="small,smaller" />
    Text
</field>

I get

<field name="field1" editable="class" constraints="class:small,smaller">
    Text
</field>

But I want

<span field="field1" editable="class" constraints="class:small,smaller">
    Text
</span>

I found some answers on SO about doing two passes but I'm not sure whether it's the right answer, nor quite how to implement the answers I did find. How should I approach this, and if there's an easy answer, what is it? :)

TIA Altreus

like image 372
Altreus Avatar asked Jan 21 '23 16:01

Altreus


1 Answers

If you want to apply two templates to the same node, you can use xsl:next-match: from the highest-priority rule that matches a node, you can call xs:next-match to invoke the next-highest, and so on. You can adjust the priorities "by hand", of course, using the priority attribute on xsl:template.

There are several other possibilities here:

(a) it seems to me from a quick glance that the processing of the "var" element could be done in a template that matches the var element rather than *[var], in which case it can be invoked by xsl:apply-templates in the usual way

(b) the first template can invoke the second using apply-templates in a special mode

(c) the first template can construct a temporary element in a variable and then apply-templates to that.

like image 172
Michael Kay Avatar answered Apr 27 '23 17:04

Michael Kay