Re: [xsl] String replacement in included content

Subject: Re: [xsl] String replacement in included content
From: Rush Manbert <rush@xxxxxxxxxxx>
Date: Wed, 12 Oct 2005 17:11:49 -0700
David,

Your suggestion of doing this transformation in 2 passes was brilliant. I have the whole thing working now, and it didn't require too much code or change to the existing stylesheet.

A couple of global variables:
<!-- At the outermost level, we want an empty replace string and a proper search string -->
<xsl:variable name="globalFind">
<str>%CT%</str>
</xsl:variable>
<xsl:variable name="globalReplace">
<rep></rep>
</xsl:variable>


My component match template (I haven't edited it, these things are really called <imlcomponent>:
<xsl:template priority="1.0" match="//imlcomponent">
<xsl:variable name="componentFilename">
<xsl:value-of select="@href" />
</xsl:variable>
<xsl:variable name="repl">
<xsl:choose>
<xsl:when test="@tag">
<rep><xsl:value-of select="@tag" /></rep>
</xsl:when>
<xsl:otherwise>
<!-- No tag is equivalent to removing all occurrences of %CT% -->
<rep></rep>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>


<xsl:choose>
<!-- The cases where we never expand the imlcomponent -->
<xsl:when test="ancestor::imltemplatehead or ancestor::imlheadcontentinsert">
<xsl:if test="$v='y'"><xsl:comment>
imlcomponent is NEVER expanded inside imltemplatehead. Just copy element.</xsl:comment></xsl:if>
<xsl:copy-of select="." />
</xsl:when>
<!-- In all other cases we will bring in the right content from the component file -->
<xsl:otherwise>
<xsl:choose>
<!-- Decide what mode we need to use for the template processing -->
<xsl:when test="ancestor::form or $templateBodyContentIsInsideForm != ''">
<!-- The form//include case -->
<xsl:if test="$v='y'"><xsl:comment>
imlcomponent inside a form: Replace with all body/form content of: <xsl:value-of select="$componentFilename" /></xsl:comment></xsl:if>
<xsl:variable name="textReplacedContents">
<xsl:apply-templates select="document(string($componentFilename))//body/form/*" mode="r">
<xsl:with-param name="replace" select="exslt:node-set($repl)" />
</xsl:apply-templates>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($textReplacedContents)" />
</xsl:when>
<xsl:otherwise>
<!-- The include case outside of a form -->
<xsl:if test="$v='y'"><xsl:comment>
imlcomponent outside a form: Replace with body element content of:
<xsl:value-of select="$componentFilename" />, excluding all imltemplate elements.</xsl:comment></xsl:if>
<xsl:variable name="textReplacedContents">
<xsl:apply-templates
select="document(string($componentFilename))//body/*[not(local-name()='imltemplate')]"


mode="r">
<xsl:with-param name="replace" select="exslt:node-set($repl)" />
</xsl:apply-templates>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($textReplacedContents)" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>


The key here was to apply the text replacement templates first to the node set, then to apply-templates to it.

The replacement templates:
<!--=======================================================================================
Template for processing a non-text node. Replaces all occurrences of the search string
terms in the element's attribute values with their corresponding replacement text, then
recursively processes all children for the same text replacement.
========================================================================================-->
<xsl:template match="node()" mode="r">
<xsl:comment>Template match for node() mode="r"</xsl:comment>
<xsl:param name="search" select="exslt:node-set($globalFind)" />
<xsl:param name="replace" select="exslt:node-set($globalReplace)" />
<xsl:copy>
<!-- String replacement for each attribute value -->
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:call-template name="str:replace">
<xsl:with-param name="string" select="string(.)" />
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="replace" select="$replace" />
</xsl:call-template>
</xsl:attribute>
</xsl:for-each>
<!-- Recurse over children -->
<xsl:apply-templates select="node()" mode="r">
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="replace" select="$replace" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>


<!--=======================================================================================
Template for processing a text node. Replaces all occurrences of the search string
terms in the text string with their corresponding replacement text.
========================================================================================-->
<xsl:template match="text()" mode="r">
<xsl:comment>Template match for text() mode="r"</xsl:comment>
<xsl:param name="search" select="exslt:node-set($globalFind)" />
<xsl:param name="replace" select="exslt:node-set($globalReplace)" />
<xsl:variable name="x">
<xsl:value-of select="." />
</xsl:variable>
<xsl:call-template name="str:replace">
<xsl:with-param name="string" select="$x" />
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="replace" select="$replace" />
</xsl:call-template>
</xsl:template>


This took care of the included components. To handle the case where a document can be used as either a component or a top level doc, I needed to find a way to do the equivalent transformation on the top level doc. I already had a template that matched a naked <HTML> element (which all of my documents contain), so I added the processing there:
<xsl:template match="html[not(@xmlns)]">
<!-- Do string replacement on all children -->
<xsl:variable name="temp">
<xsl:apply-templates select="@*|node()" mode="r">
</xsl:apply-templates>
</xsl:variable>
<!-- transform the html element as required -->
<xsl:element name="{name()}">
<xsl:attribute name="xmlns">
<xsl:text>http://www.w3.org/1999/xhtml</xsl:text>
</xsl:attribute> <xsl:copy-of select="@*" />
<!-- Process the text replaced nodeset -->
<xsl:apply-templates select="exslt:node-set($temp)" />
</xsl:element>
</xsl:template>


There's probably a better way to handle this, but the things I tried didn't work.

I think that's everything that is relevant.

Now I can handle:
<imlcomponent href="file" />
<imlcomponent href="file" tag="" />
<imlcomponent href="file" tag="FixedTag">
<imlcomponent href="file" tag="%CT%_AddedTag" />
where the last version allows me to "chain" the tags through a series of nested includes. Very cool.


Thank you again for your help. I would have flopped around for a long time without it.

Best regards,
Rush

Current Thread