XSLT: Copy All, but 'cut-and-paste' some c

2019-08-12 17:39发布

问题:

Background

A more general version of my problem was recently posted but due to the quality of the post, it received no responses. Here's my attempt!

I have the following XML structure that I want to transform

Pre-processed XML

<schema>

    <!-- element to be imbedded -->

    <element name="tryCatch.catch">
        <complexType>
            <complexContent>
                <extension base="escalateContainer">
                    <attribute name="var" type="_var" />
                </extension>
            </complexContent>
        </complexType>
    </element>

    <complexType name="tryCatch">
        <complexContent>
            <extension base="scriptElement">
                <sequence>
                    <element name="try" type="escalateContainer" />
                    <element name="catch" type="tryCatch.catch" minOccurs="0" />
                    <element name="finally" type="escalateContainer" minOccurs="0" />
                </sequence>
            </extension>
        </complexContent>
    </complexType>

</schema>

...into the following structure:

Post XSLT-Structure

<schema>

    <complexType name="tryCatch">
        <complexContent>
            <extension base="scriptElement">
                <sequence>
                    <element name="try" type="escalateContainer" />

                    <!-- Change here! -->

                    <element name="tryCatch.catch" minOccurs="0">
                        <complexType>
                            <complexContent>
                                <extension base="escalateContainer">
                                    <attribute name="var" type="_var" />
                                </extension>
                            </complexContent>
                        </complexType>

                    </element>
                    <element name="finally" type="escalateContainer" minOccurs="0" />
                </sequence>
            </extension>
        </complexContent>
    </complexType>

</schema>

Overview of the Process

Basically, I just want to

  1. Copy everything
  2. Then find all element children of a complexType that have a . in their type (by construction - I have ensured this pattern works interally in my XML). A simplified version of my match is:

    complexType.element.type = element.name

  3. Last, replace that element with the corresponding global element. Bonus points if the attributes on the element being replaced are also copied.

What I have So Far

<xsl:template match="@* | node()">

        <xsl:copy>

            <xsl:apply-templates select="@* | node()" />

        </xsl:copy>

</xsl:template>

<xsl:template match="schema">

    <xsl:for-each select="element">

        <xsl:variable name="self" select="current()" />

        <xsl:variable name="name" select="@name" />

        <xsl:for-each select="/schema/complexType//element[@type=$name]">   

            <xsl:copy-of select="$self" />

        </xsl:for-each>         

    </xsl:for-each>

</xsl:template>

I think I'm off to the right start - copy everything with the identity template, then process what I want to change. The problem is that my second template only returns the copied element! The match appears to work great, I just lose all the other content I'm trying to preserve.

回答1:

Generally the usage of for-each isn't the best idea. It makes the templates inflexible and hard to read. (Of course sometimes you can't avoid it but in this case it is possible.) You should leave iteration to the XSLT engine by using apply-templates. I'd do something like this:

<xsl:template match="@* | node()" priority="-1"> <!-- copy everything unless another template with higher priority says otherwise -->
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
</xsl:template>

<xsl:template match="schema"> 
   <xsl:copy> <!-- copy the <schema> element -->
    <xsl:apply-templates select="complexType"/> <!-- copy all complexType elements within schema -->
   </xsl:copy>
</xsl:template>

<xsl:template match="element[contains(@type, '.')]"> <!-- if we encounter an <element> whose name contains the '.' character -->
        <xsl:variable name="type" select="@type" />
    <xsl:apply-templates select="//element[@name=$type]"/> <!-- copy the matching element instead -->
</xsl:template>

Update: I've just realised I got @type and @name mixed up, fixed that.



标签: xml xslt