Question

I want to include HTML within an element's attribute (for the data-clearing attribute used here: http://foundation.zurb.com/docs/components/clearing.html). My <caption> data is available two ways:

<caption mode="formatted">
    <p>A fairly long looking <a href="http://www.stackoverflow.com">caption with a link</a> that goes to an external site.</p>
</caption>
<caption mode="unformatted">
    <![CDATA[A fairly long looking <a href="http://www.stackoverflow.com">caption with a link</a> that goes to an external site.]]>
</caption>

Here is my template:

<xsl:template match="images/entry">
    <!-- process contents of caption node-->
    <xsl:variable name="cap">
        <xsl:text disable-output-escaping="yes">&lt;![CDATA[</xsl:text>
        <xsl:apply-templates select="caption/*" mode="html" />
        <xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
    </xsl:variable>

    <li>
        <xsl:copy-of select="$cap"/> //outside of attribute it works
        <img>
            <xsl:attribute name="src">/image/2/150/112/5<xsl:value-of select="@path"/>/<xsl:value-of select="filename"/></xsl:attribute>
            <xsl:attribute name="data-caption">
                <xsl:copy-of select="$cap"/> //inside of attribute it removes the <a> tag
            </xsl:attribute>
        </img>
    </li>
</xsl:template>

The mode=html matches the <a> tag in the <caption> node with this template:

<!-- mark external links -->
<xsl:template match="a" mode="html">
<a href="{@href}" title="{@title}">
    <xsl:if test="number(substring(@href,1,4)='http')">
        <xsl:attribute name="class">external</xsl:attribute>
        <xsl:attribute name="target">_blank</xsl:attribute>
    </xsl:if>
    <xsl:apply-templates select="* | @* | text()" mode="html"/>
</a>
</xsl:template>

If I use the "unformatted" caption, it keeps the <a> tag (desired behavior). However, when I use that caption, I cannot use the 'mark external links' template to fix the <a> tag. Using the "formatted" caption, I can process the <a> tag like I want to, but it gets lost when I use xsl:copy-of inside of the <img> <xsl:attribute>. It will display fine outside of the attribute, like this:

<![CDATA[<p>A fairly long looking <a href="http://www.stackoverflow.com" title="" class="external" target="_blank">caption with a link</a> that goes to an external site.</p>]]>

Is there any way to get my final result to look like:

<img src="/image/2/150/112/5/images/my-image.jpg" 
    data-caption="<![CDATA[A fairly long looking <a class="external" target="_blank" href="http://www.stackoverflow.com">caption with a link</a> that goes to an external site.]]>;" />

Thanks for reading.

Was it helpful?

Solution

Firstly, let's be clear that you are not including "nodes" in your attributes; what you want is to serialize an attribute that contains XML markup. We're talking at the lexical level, not the tree level, and nodes exist only at the tree level.

To generate this output, there are two challenges. Firstly, you need to construct a string containing the lexical XML, and then pass this string as the value of the attribute. Secondly, you need to prevent special characters in this string being escaped.

For the first problem, there are two approaches: you can invoke an external serialize() function that converts a tree to lexical XML as a string, such as saxon:serialize() if you are using Saxon, or you can write your own (which isn't difficult for simple cases, and it has already been done - David Carlisle has written a complete XML serializer in XSLT).

The second problem is tricky. The XSLT serialization spec (all versions) is adamant that the HTML serialization method should not escape "<" appearing in attribute values, but it has little or nothing to say about ">". Saxon escapes ">" as "&gt;", ostensibly because this is required by older browsers (probably very old by now!), but I don't think this is required by the spec, and other processors may differ. Disable-output-escaping does not work for attribute values, so you may have to resort to constructing the entire element serialization by hand using disable-output-escaping. Alternatively, with XSLT 2.0 you can use character maps to force output of a ">" in an attribute value.

In your example code, you are using disable-output-escaping while writing the value of a variable. There's a yoyo history for this in the spec. An erratum to XSLT 1.0 (the so-called "sticky doe" erratum) said it was allowed, but this was reversed in XSLT 2.0, because it was incompatible with allowing full navigational access to result-tree-fragments held in variables. So the bottom line is that it may or may not work depending on the processor you use - but of course that's true of disable-output-escaping generally.

A completely different solution to this requirement might be to output something different - for example using chevrons in place of angle brackets - and then filter the serialized output through a textual filter that substitutes the relevant characters to the ones you actually want.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top