开发者

How do I Emit Escaped XML representation of a Node in my XSLT's HTML Output

I'm transforming XML to an HTML document. In this document I want to embed XML markup for a node that was just transformed (the HTML document is a technical spec).

For example, if my XML was this:

<transform-me>
    <node id="1">
        <stuff type="floatsam">
           <details>Various bits</details>
        </stuff>
    </node>
</transform-me>

I'd want my XSLT output to loo开发者_开发技巧k something like this:

<h1>Node 1</h1>
   <h2>Stuff (floatsam)</h2>
   Various bits
   <h2>The XML</h2>
   &lt;stuff type=&quot;floatsam&quot;&gt;
      &lt;details&gt;Various bits&lt;/details&gt;
   &lt;/stuff&gt;

I'm hoping there is an XSLT function that I can call in my <stuff> template to which I can pass the current node (.) and get back escaped XML markup for <stuff> and all its descendants. I have a feeling unparsed-text() might be the way to go but can't get it to work.


Very simple template

<xsl:template match="node()" mode="print">

        <xsl:choose>

            <!-- is it element? -->
            <xsl:when test="name()">

                <!-- start tag -->
                <xsl:text>&lt;</xsl:text>
                <xsl:value-of select="name()" />

                <!-- attributes -->
                <xsl:apply-templates select="@*" mode="print" />

                <xsl:choose>

                    <!-- has children -->
                    <xsl:when test="node()">
                        <!-- closing bracket -->
                        <xsl:text>&gt;</xsl:text>

                        <!-- children -->
                        <xsl:apply-templates mode="print" />

                        <!-- end tag -->
                        <xsl:text>&lt;/</xsl:text>
                        <xsl:value-of select="name()" />
                        <xsl:text>&gt;</xsl:text>
                    </xsl:when>

                    <!-- is empty -->
                    <xsl:otherwise>
                        <!-- closing bracket -->
                        <xsl:text>/&gt;</xsl:text>
                    </xsl:otherwise>

                </xsl:choose>

            </xsl:when>

            <!-- text -->
            <xsl:otherwise>
                <xsl:copy />
            </xsl:otherwise>

        </xsl:choose>

</xsl:template>

<xsl:template match="@*" mode="print">
    <xsl:text> </xsl:text>
    <xsl:value-of select="name()" />
    <xsl:text>=&quot;</xsl:text>
    <xsl:value-of select="." />
    <xsl:text>&quot;</xsl:text>
</xsl:template>

Used

<xsl:apply-templates mode="print" />

You can even add pretty printing if you want.


The answer to this is more complex that you would at first think. Proper "double-escaping" of attribute values and text nodes must occur.

This XSLT 1.0 template does a correct (though not complete) printing of an XML node, including (an attempt to do) proper pretty-printing with configurable indentation:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="html" encoding="utf-8" />

   <!-- defaults and configurable parameters -->
  <xsl:param name="NL"        select="'&#xA;'" /><!-- newline sequence -->
  <xsl:param name="INDENTSEQ" select="'&#x9;'" /><!-- indent sequence -->

  <xsl:variable name="LT" select="'&lt;'" />
  <xsl:variable name="GT" select="'&gt;'" />

  <xsl:template match="transform-me">
    <html>
      <body>
        <!-- this XML-escapes an entire sub-structure -->
        <pre><xsl:apply-templates select="*" mode="XmlEscape" /></pre>
      </body>
    </html>
  </xsl:template>

  <!-- element nodes will be handled here, incl. proper indenting -->
  <xsl:template match="*" mode="XmlEscape">
    <xsl:param name="indent" select="''" />

    <xsl:value-of select="concat($indent, $LT, name())" />
    <xsl:apply-templates select="@*" mode="XmlEscape" />

    <xsl:variable name="HasChildNode" select="node()[not(self::text())]" />
    <xsl:variable name="HasChildText" select="text()[normalize-space()]" />
    <xsl:choose>
      <xsl:when test="$HasChildNode or $HasChildText">
        <xsl:value-of select="$GT" />
        <xsl:if test="not($HasChildText)">
          <xsl:value-of select="$NL" />
        </xsl:if>
        <!-- render child nodes -->
        <xsl:apply-templates mode="XmlEscape" select="node()">
          <xsl:with-param name="indent" select="concat($INDENTSEQ, $indent)" />
        </xsl:apply-templates>
        <xsl:if test="not($HasChildText)">
          <xsl:value-of select="$indent" />
        </xsl:if>
        <xsl:value-of select="concat($LT, '/', name(), $GT, $NL)" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat(' /', $GT, $NL)" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- comments will be handled here -->
  <xsl:template match="comment()" mode="XmlEscape">
    <xsl:param name="indent" select="''" />
    <xsl:value-of select="concat($indent, $LT, '!--', ., '--', $GT, $NL)" />
  </xsl:template>

  <!-- text nodes will be printed XML-escaped -->
  <xsl:template match="text()" mode="XmlEscape">
    <xsl:if test="not(normalize-space() = '')">
      <xsl:call-template name="XmlEscapeString">
        <xsl:with-param name="s" select="." />
        <xsl:with-param name="IsAttribute" select="false()" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <!-- attributes become a string: '{name()}="{escaped-value()}"' -->
  <xsl:template match="@*" mode="XmlEscape">
    <xsl:value-of select="concat(' ', name(), '=&quot;')" />
    <xsl:call-template name="XmlEscapeString">
      <xsl:with-param name="s" select="." />
      <xsl:with-param name="IsAttribute" select="true()" />
    </xsl:call-template>
    <xsl:value-of select="'&quot;'" />
  </xsl:template>

  <!-- template to XML-escape a string -->
  <xsl:template name="XmlEscapeString">
    <xsl:param name="s" select="''" />
    <xsl:param name="IsAttribute" select="false()" />
    <!-- chars &, < and > are never allowed -->
    <xsl:variable name="step1">
      <xsl:call-template name="StringReplace">
        <xsl:with-param name="s"       select="$s" />
        <xsl:with-param name="search"  select="'&amp;'" />
        <xsl:with-param name="replace" select="'&amp;amp;'" />
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="step2">
      <xsl:call-template name="StringReplace">
        <xsl:with-param name="s"       select="$step1" />
        <xsl:with-param name="search"  select="'&lt;'" />
        <xsl:with-param name="replace" select="'&amp;lt;'" />
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="step3">
      <xsl:call-template name="StringReplace">
        <xsl:with-param name="s"       select="$step2" />
        <xsl:with-param name="search"  select="'&gt;'" />
        <xsl:with-param name="replace" select="'&amp;gt;'" />
      </xsl:call-template>
    </xsl:variable>
    <!-- chars ", TAB, CR and LF are never allowed in attributes -->
    <xsl:choose>
      <xsl:when test="$IsAttribute">
        <xsl:variable name="step4">
          <xsl:call-template name="StringReplace">
            <xsl:with-param name="s"       select="$step3" />
            <xsl:with-param name="search"  select="'&quot;'" />
            <xsl:with-param name="replace" select="'&amp;quot;'" />
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="step5">
          <xsl:call-template name="StringReplace">
            <xsl:with-param name="s"       select="$step4" />
            <xsl:with-param name="search"  select="'&#x9;'" />
            <xsl:with-param name="replace" select="'&amp;#x9;'" />
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="step6">
          <xsl:call-template name="StringReplace">
            <xsl:with-param name="s"       select="$step5" />
            <xsl:with-param name="search"  select="'&#xA;'" />
            <xsl:with-param name="replace" select="'&amp;#xD;'" />
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="step7">
          <xsl:call-template name="StringReplace">
            <xsl:with-param name="s"       select="$step6" />
            <xsl:with-param name="search"  select="'&#xD;'" />
            <xsl:with-param name="replace" select="'&amp;#xD;'" />
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="$step7" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$step3" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- generic string replace template -->
  <xsl:template name="StringReplace">
    <xsl:param name="s"       select="''" />
    <xsl:param name="search"  select="''" />
    <xsl:param name="replace" select="''" />

    <xsl:choose>
      <xsl:when test="contains($s, $search)">
        <xsl:value-of select="substring-before($s, $search)" />
        <xsl:value-of select="$replace" />
        <xsl:variable name="rest" select="substring-after($s, $search)" />
        <xsl:if test="$rest">
          <xsl:call-template name="StringReplace">
            <xsl:with-param name="s"       select="$rest" />
            <xsl:with-param name="search"  select="$search" />
            <xsl:with-param name="replace" select="$replace" />
          </xsl:call-template>
        </xsl:if>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$s" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

When applied to this test XML:

<transform-me>
  <node id="1">
    <!-- a comment -->
      <stuff type="fl&quot;&apos;&#10;&#9;oatsam">
        <details>Various bits &amp; pieces</details>
        <details>
        </details>
        <details attr="value">
          <childnode>text and &lt;escaped-text /&gt;</childnode>
        </details>
      </stuff>
  </node>
</transform-me>

The following output is produced (source code):

<html>
<body>
<pre>&lt;node id="1"&gt;
    &lt;!-- a comment --&gt;
    &lt;stuff type="fl&amp;quot;'&amp;#xD;&amp;#x9;oatsam"&gt;
        &lt;details&gt;Various bits &amp;amp; pieces&lt;/details&gt;
        &lt;details /&gt;
        &lt;details attr="value"&gt;
            &lt;childnode&gt;text and &amp;lt;escaped-text /&amp;lt;&lt;/childnode&gt;
        &lt;/details&gt;
    &lt;/stuff&gt;
&lt;/node&gt;
</pre>
</body>
</html>

and when viewed in the browser you see:

<node id="1">
    <!-- a comment -->
    <stuff type="fl&quot;'&#xD;&#x9;oatsam">
        <details>Various bits &amp; pieces</details>
        <details />
        <details attr="value">
            <childnode>text and &lt;escaped-text /&lt;</childnode>
        </details>
    </stuff>
</node>

Note that by "not complete" I mean that things like namespaces and processing instructions for example are currently unhandled. But its easy to make a template for them.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜