xsl-list
[Top] [All Lists]

Re: String replacement in included content

2005-10-12 17:11:49
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


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--



<Prev in Thread] Current Thread [Next in Thread>