3

Background

Maintain readable XSL source code while generating HTML without excessive breaks that introduce spaces between sentences and their terminating punctuation. From Rethinking XSLT:

White space in XSLT stylesheets is especially problematic because it serves two purposes: (1) for formatting the XSLT stylesheet itself; and (2) for specifying where whitespace should go in the output of XSLT-processed XML data.

Problem

An XSL template contains the following code:

  <xsl:if test="@min-time &lt; @max-time">
    for
    <xsl:value-of select="@min-time" />
    to
    <xsl:value-of select="@max-time" />
    minutes
  </xsl:if>

  <xsl:if test="@setting">
    on <xsl:value-of select="@setting" /> heat
  </xsl:if>
  .

This, for example, generates the following output (with whitespace exactly as shown):

    for
    2
    to
    3
    minutes
  .

All major browsers produce:

for 2 to 3 minutes .

Nearly flawless, except for the space between the word minutes and the punctuation. The desired output is:

for 2 to 3 minutes.

It might be possible to eliminate the space by removing the indentation and newlines within the XSL template, but that means having ugly XSL source code.

Workaround

Initially the desired output was wrapped in a variable and then written out as follows:

<xsl:value-of select="normalize-space($step)" />.

This worked until I tried to wrap <span> elements into the variable. The <span> elements never appeared within the generated HTML code. Nor is the following code correct:

<xsl:copy-of select="normalize-space($step)" />.

Technical Details

The stylesheet already uses:

<xsl:strip-space elements="*" />
<xsl:output indent="no" ... />

Related

Question

How do you tell the XSLT processor to eliminate that space?

Thank you!

4

2 回答 2

3

Instead of using copy-of you can apply the identity template with an additional template that trims the spaces from the text nodes. You only create one variable like in your first workaround.

You call:

<li><xsl:apply-templates select="$step" mode="nospace" />.</li>

The templates:

<xsl:template match="text()" mode="nospace" priority="1" >
    <xsl:value-of select="normalize-space(.)" />
</xsl:template>

<xsl:template match="node() | @*" mode="nospace">
    <xsl:copy>
        <xsl:apply-templates select="node() | @*" mode="nospace" />
    </xsl:copy>
</xsl:template>
于 2012-05-14T02:58:28.283 回答
2

I. This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="t[@max-time > @min-time]">
  <span>
    <xsl:value-of select=
      "concat('for ', @min-time, ' to ', @max-time, ' minutes')"/>
  </span>
  <xsl:apply-templates select="@setting"/>
  <xsl:text>.</xsl:text>
 </xsl:template>

 <xsl:template match="@setting">
  <span>
    <xsl:value-of select="concat(' on ', ., ' heat')"/>
  </span>
 </xsl:template>
</xsl:stylesheet>

when applied on the following XML document (none has been presented!):

<t min-time="2" max-time="3" setting="moderate"/>

produces the wanted, correct result:

<span>for 2 to 3 minutes</span>
<span> on moderate heat</span>.

and it is displayed by the browser as:

for 2 to 3 minutes on moderate heat.

When the same transformation is applied on this XML document:

<t min-time="2" max-time="3"/>

again the correct, wanted result is produced:

<span>for 2 to 3 minutes</span>.

and it is displayed by the browser as:

for 2 to 3 minutes.


II. Layout (visual) solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" xmlns:gen="gen:gen" xmlns:gen-attr="gen:gen-attr"
 exclude-result-prefixes="my gen">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <my:layout>
  <span>for <gen-attr:min-time/> to <gen-attr:max-time/> minutes</span>
  <gen-attr:setting><span> on <gen:current/> heat</span></gen-attr:setting>
  <gen:literal>.</gen:literal>
 </my:layout>

 <xsl:variable name="vLayout" select="document('')/*/my:layout/*"/>
 <xsl:variable name="vDoc" select="/"/>

 <xsl:template match="node()|@*">
   <xsl:param name="pCurrent"/>
     <xsl:copy>
       <xsl:apply-templates select="node()|@*">
       <xsl:with-param name="pCurrent" select="$pCurrent"/>
       </xsl:apply-templates>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:apply-templates select="$vLayout">
   <xsl:with-param name="pCurrent" select="$vDoc/*"/>
  </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="gen-attr:*">
  <xsl:param name="pCurrent"/>
  <xsl:value-of select="$pCurrent/@*[name() = local-name(current())]"/>
 </xsl:template>

 <xsl:template match="gen-attr:setting">
  <xsl:param name="pCurrent"/>
  <xsl:variable name="vnextCurrent" select=
   "$pCurrent/@*[name() = local-name(current())]"/>
  <xsl:apply-templates select="node()[$vnextCurrent]">
    <xsl:with-param name="pCurrent" select="$vnextCurrent"/>
  </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="gen:current">
  <xsl:param name="pCurrent"/>
   <xsl:value-of select="$pCurrent"/>
 </xsl:template>

 <xsl:template match="gen:literal">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

This transformation gives us an idea how to make a visual (skeletal) representation of the wanted output and use it to "populate" it with the wanted data from the source XML document.

The result is identical with that of the first solution. If this transformation is run "as-is" it will produce a lot of namespaces -- they are harmless and will not be produced if the layout is in a separate XML file.

于 2012-05-14T03:46:54.780 回答