0
votes

Is it possible to have xsl:apply-templates be limited to match the node only, without it trying to match nested elements?

I understand I could use call-template. Here are details on why I am trying to do this -

I have a document composed of Section and Field elements.

Section can contain nested Section elements or Field elements. Field elements do not have nesting.

As an example, for Field, a snippet of the template code is:

<xsl:template match="Field" mode="Field">

    <xsl:apply-templates select="." mode="FieldHeight" />

    .....

</xsl:template>

<xsl:template match="Field[LayoutTitlePosition = 'top']" mode="FieldHeight">
    <xsl:attribute name="h" select="$glTopFieldHeight" />
</xsl:template>

<xsl:template match="Field[LayoutTitlePosition = ('left', 'right', 'none')]" mode="FieldHeight">
    <xsl:attribute name="h" select="$glHorzFieldHeight" />
</xsl:template>

The same could be accomplished with xsl:choose / xsl:when statements, but I find the matching search parameters is nicer syntax and easier to maintain. I'm able to maintain a common Field template and override specific parts based on the input. Since Fields are not nested I don't need to be concerned about 'subFields' being matched.

I want to do the same thing for Section headings, but the problem is if I apply a template to a Section, it will also potentially match nested sections. This happens when I apply a template to a Section without a title that contains a subsection with a title - the subsection is getting matched twice.

EDIT Example of issue.

I've attempted to create a minimal example to reproduce the issue. The input document:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <Section>
        <!-- no title -->
        <Section>
            <Title>Sub section title</Title>
            <Field>
                <Type>Text</Type>
            </Field>
        </Section>
    </Section>
</Root>

The XSL transform document:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" />

<xsl:template match="*" >
    <subform layout="tb">
        <xsl:apply-templates select=".[name() = 'Section']" mode="Heading" />
        <xsl:apply-templates select=".[name() = 'Field']" mode="Field" />
        <xsl:apply-templates select="*[name() = ('Section', 'Field')]" />
    </subform>
</xsl:template>

<xsl:template match="Section[Title]" mode="Heading">
    <subform layout="tb">
        <xsl:call-template name="drawHeading" />
    </subform>
</xsl:template>

<xsl:template name="drawHeading">
    <Heading><xsl:value-of select="Title" /></Heading>
</xsl:template>

<xsl:template match="Field" mode="Field">
    <field name="{@Name}">
    </field>
</xsl:template>

Output, from transforming the input document:

<?xml version="1.0" encoding="UTF-8"?>
<subform layout="tb">
    <subform layout="tb">
        <subform layout="tb">
            <Heading>Sub section title</Heading>
        </subform>
        <subform layout="tb">
            <subform layout="tb">
                <Heading>Sub section title</Heading>
            </subform>
            <subform layout="tb">
                <field name=""/>
            </subform>
        </subform>
    </subform>
</subform>

It seemed the sub-section title is getting matched when the parent section without a title did not match.

The result I wanted to achieve is:

<?xml version="1.0" encoding="UTF-8"?>
<subform layout="tb">
    <subform layout="tb">

        <subform layout="tb">
            <subform layout="tb">
                <Heading>Sub section title</Heading>
            </subform>
            <subform layout="tb">
                <field name=""/>
            </subform>
        </subform>
    </subform>
</subform>

If the Title is present in the source document, example:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <Section>
        <Title>Section title</Title>
        <Section>
            <Title>Sub section title</Title>
            <Field>
                <Type>Text</Type>
            </Field>
        </Section>
    </Section>
</Root>

Then it should produce:

<?xml version="1.0" encoding="UTF-8"?>
<subform layout="tb">
    <subform layout="tb">
        <subform layout="tb">
            <Heading>Section title</Heading>
        </subform>
        <subform layout="tb">
            <subform layout="tb">
                <Heading>Sub section title</Heading>
            </subform>
            <subform layout="tb">
                <field name=""/>
            </subform>
       </subform>
   </subform>
</subform>
1
Consider to show us a sample of the input, the result you want, the code you have and the result you get. I afraid I don't understand why doing e.g. <xsl:template match="Section" mode="foo"><xsl:apply-templates select="." mode="bar"/></xsl:template> would process any nested section, unless the xsl:template match="Section" mode="bar" explicitly processes child nodes with apply-templates. - Martin Honnen
Thank you. I have added an example. - dave
Please also post the result you want to achieve. - Martin Honnen
I have added a result example, thank you. - dave

1 Answers

1
votes

It seems that simply using two specialized templates for Field and Title should do:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" />

<xsl:template match="*" >
    <subform layout="tb">
        <xsl:apply-templates/>
    </subform>
</xsl:template>


<xsl:template match="Title">
    <subform layout="tb">
        <Heading><xsl:value-of select="." /></Heading>
    </subform>
</xsl:template>

<xsl:template match="Field">
    <subform layout="tb">
       <field name="{@Name}"></field> 
    </subform>
</xsl:template>

</xsl:stylesheet>

Online at http://xsltransform.net/pPJ8LUX.