[xsl] String replacement in included content

Subject: [xsl] String replacement in included content
From: Rush Manbert <rush@xxxxxxxxxxx>
Date: Tue, 11 Oct 2005 11:57:52 -0700
Hi,

My apologies for the long post.

I have written a DTD and a XSL stylesheet that extend XHTML. I parse
the XHTML documents and apply the XSL transformations using
libxml/libxslt, so I'm limited to using XSLT 1.0 and a few EXSLT
extras.

One of the elements I have defined is <component tag="something" href="URI" />
where URI is expected to be a file reference to a valid XHTML document (as
extended by my DTD). The included document is opened, and the body
content is inserted in the including document, replacing the <component>
element. My original version did not include the tag attribute, and I have
a template that works correctly. The design constraints are as follow:
* A component document must be a valid (extended) XHTML document
* A component document may contain a <form> element within the scope
of the <body> element. In that case, if the <component> element in
the including document is within the scope of a <form> element, then
only the content of the component document that is within the first <form>
element scope is included in the including document. (i.e. we don't nest
the forms.) If the including document <component> element is NOT
within the scope of a <form>, then the entire body content of the
component document is included.
* A component document may be included into an including document more
than once.
* A component document may contain a <component> element


As I said, all of this works at present. What I need to add now is handling
of the tag attribute. The idea here is that when we include a component
doc, we specify a text tag value that can be used within the included
content. My first cut at this is to define a replacement text, %CT% (CT
stands for Component Tag and the surrounding % symbols are for parsing.)
that can appear anywhere in the component document that "free form"
text is allowed. In general, this means attribute contents. My application
uses the contents of form action attributes and input name attributes to
do client side scripting (Not Javascript embedded in the XHTML documents),
so I need to be able to make them unique in order to lookup and call the
right script methods.

A single level example:
The including doc (simplified)
------------------------------
<head>  </head>
<body>
   <component tag="A" href="Component1.xhtm" />
   <component tag="B" href="Component1.xhtm" />
</body>

The file Component1.xhtm:
------------------------
<head> </head>
<body>
   <form action="%CT%_Component1Action" method="get">
       <input type="submit" name="%CT%_Component1Submit" />
   </form>
</body>


The desired output: ------------------ <head> </head> <body> <form action="A_Component1Action" method="get"> <input type="submit" name="A_Component1Submit" /> </form> <form action="B_Component1Action" method="get"> <input type="submit" name="B_Component1Submit" /> </form> </body>


A multilevel example: The including doc (simplified) ------------------------------ <head> </head> <body> <form action="MyFormAction" method-"get"> <component tag="A" href="Component2.xhtm" /> </form> </body>

The file Component2.xhtm:
------------------------
<head> </head>
<body>
   <input type="submit" name="Component2Submit_%CT%" />
   <component tag="B_%CT%" href="Component3.xhtm" />
   <component tag="C_%CT%" href="Component3.xhtm" />
</body>

The file Component3.xhtm:
------------------------
<head> </head>
<body>
   <input type="submit" name="Component3Submit_%CT%" />
</body>

The desired output:
------------------
<head> </head>
<body>
   <form action="MyFormAction" method-"get">
       <input type="submit" name="Component2Submit_A" />
       <input type="submit" name="Component3Submit_B_A" />
       <input type="submit" name="Component3Submit_C_A" />
   </form>
</body>

NOTE that %CT% could appear in the middle of the strings as well, i.e.
<input type="submit" name="Component1%CT%Submit" />


Since ant XHTML document may be used as a component, I must also allow for the case where we just parse and display a document that contains the special %CT% sequences, but there is no Component Tag defined. In that case the best outcome would be if I could just remove the instances of %CT%. So if my application were to just parse and transform Component1.xhtm as the top level document, the desired output would be: <head> </head> <body> <form action="_Component1Action" method="get"> <input type="submit" name="_Component1Submit" /> </form> </body>

Here is my original template that works but ignores the tag attribute. It has some
extra complications dealing with templates (another XHTML extension), but the
important part is where the $contents variable is being defined and used:
<xsl:template priority="1.0" match="//component">
<xsl:variable name="componentFilename">
<xsl:value-of select="@href" />
</xsl:variable>
<xsl:choose>
<!-- The cases where we never expand the component -->
<xsl:when test="ancestor::imltemplatehead or ancestor::imlheadcontentinsert">
<xsl:if test="$v='y'"><xsl:comment>component 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>component inside a form: Replace with all body/form content of: <xsl:value-of select="$componentFilename" /></xsl:comment></xsl:if>
<xsl:variable name="contents" select="document(string($componentFilename))//body/form/*" />
<xsl:apply-templates select="exslt:node-set($contents)" />
</xsl:when>
<xsl:otherwise>
<!-- The include case outside of a form -->
<xsl:if test="$v='y'"><xsl:comment>component 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="contents" select="document(string($componentFilename))//body/*[not(local-name()='imltemplate')]" />
<xsl:apply-templates select="exslt:node-set($contents)" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>


        </xsl:choose>
   </xsl:template>

As a first attempt, ignoring the stuff about replacing %CT% when there is
no tag defined, I tried modifying this template to use the str:replace
template available at www.exslt.org, but that has not worked out so well.
If I could just treat the contents of the component file as a string for
text replacement purposes, then turn it into a node set for use with
xsl:apply-templates it seems like I would be partway there.

If anyone has some insight to offer, I would be very grateful. I should also add
that if this is really hard, I can modify my application to do the tag replacements,
because I have a custom rendering step that happens after the XSLT transformations
have been applied. I would rather do this with XSLT because I can then display
a document with a browser and have it be as close as possible in appearance to
what it would be if displayed by my application.


Thanks,
Rush

Current Thread