Reputation: 2165
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
<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:
<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>
Basically, I just want to
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
Last, replace that element with the corresponding global element. Bonus points if the attributes on the element being replaced are also copied.
<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.
Upvotes: 1
Views: 808
Reputation: 49804
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.
Upvotes: 4