Luke
Luke

Reputation: 23

XSL Preserve Space in XML CDATA

I've tried a variety of solutions but can't seem to get this working.

I am doing an XSL transformation of XML to XML (FOP) for PDF creation. The source XML has <code> elements whose contents start with a CDATA declaration. The transformation removes newlines.

Example input XML:

<?xml version="1.0" encoding="iso-8859-1"?>
<myxml>
    <code><![CDATA[import java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null && key.length() > 0) {
                System.out.println(key);
            }
        }
    ]]></code>
</myxml>

Example XSL:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.w3.org/TR/xhtml1/strict" 
    xmlns:fo="http://www.w3.org/1999/XSL/Format" 
    exclude-result-prefixes="fo" 
    >
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" cdata-section-elements="code"/>
<xsl:preserve-space elements="code" />

<fo:block font-family="Courier New" font-size="12pt" color="black" 
    space-after="12pt" space-before="12pt" space-before.precedence="4">

      <fo:block>
          <xsl:text>Copy</xsl:text>
          <xsl:copy>
            <xsl:value-of select="code"/>
          </xsl:copy>
      </fo:block>

      <fo:block>
          <xsl:text>Copy Text</xsl:text>
          <xsl:copy>
            <xsl:value-of select="code/text()"/>
          </xsl:copy>
      </fo:block>

      <fo:block>
          <xsl:text>Original</xsl:text>
          <xsl:value-of select="code"/>
      </fo:block>

      <fo:block>
          <xsl:text>Normalise space</xsl:text>
          <value-of select="normalize-space(code)" disable-output-escaping="yes"/>
      </fo:block>

      <fo:block>          
          <xsl:text>Copy with extra CDATA wrapper?</xsl:text>
          <xsl:copy>
            <xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
            <xsl:value-of select="code"/>
            <xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
          </xsl:copy>         
      </fo:block>

      <fo:block>
        <xsl:text>Again, an attempt to wrap</xsl:text>
        <xsl:text disable-output-escaping="yes">
          &lt;![CDATA[
        </xsl:text>
        <xsl:value-of select="code" />
        <xsl:text disable-output-escaping="yes">
          ]]&gt;
        </xsl:text>
      </fo:block>
</fo:block>

Permutations... I have tried all permutations of the following:

In total, 6 permutations.

These settings seem to make little difference for me. The following is output for each test block:

Upvotes: 2

Views: 3031

Answers (3)

halfer
halfer

Reputation: 20469

(Posted on behalf of the OP. This solution was an edit to the question, so I've rolled that edit back and posted this here.)

Input XML:

<?xml version="1.0" encoding="iso-8859-1"?>
<solution>
   <solution_name>A Set of Functions</solution_name>
   <description>Just for the sake of example</description>
   <functions>
      <function>
        <code><![CDATA[function String getStringSafely(String textToCheck, String defaultValue) {
                if (textToCheck == null || textToCheck.length() <= 0) {
                    return defaultValue;
                }
                return textToCheck;
            }
        ]]></code>
      </function>
      <function><code><![CDATA[import java.nio.charset.Charset;
import com.example.library.Something;

function String doSomething(String key) {
        if (key == null || key.length() <= 0) {
            return "";
        }
        System.out.println(String.format("Yep, got a value[%1$s]", key));
    }
]]></code>
      </function>
    </functions>
</solution>

Example XSL:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.w3.org/TR/xhtml1/strict" 
    xmlns:fo="http://www.w3.org/1999/XSL/Format" 
    exclude-result-prefixes="fo" 
    >
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" cdata-section-elements="code"/>
<xsl:preserve-space elements="code" />
<xsl:template match="solution">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">

  <fo:layout-master-set>
    <fo:simple-page-master master-name="solution-page">
      <fo:region-body margin="1in"/>
    </fo:simple-page-master>
  </fo:layout-master-set>

  <fo:page-sequence master-reference="solution-page">
    <fo:flow flow-name="xsl-region-body" id="solution">
        <fo:block text-align="center" 
        space-after="40pt" space-before="100pt" space-after.precedence="3" 
        font-family="Helvetica" font-weight="bold" font-size="14pt" color="#0050B2" >
                <xsl:value-of select="solution_name"/>
        </fo:block>
        <fo:block font-family="Helvetica" font-size="12pt" color="black" 
    space-after="12pt" space-before="12pt" space-before.precedence="4">
            <xsl:value-of select="description"/>
        </fo:block>
        <xsl:for-each select="functions">
            <xsl:variable name="this" select="." />
            <xsl:call-template name="solution_functions">
                <xsl:with-param name="data" select="$this"/>
            </xsl:call-template>
        </xsl:for-each>
    </fo:flow>
  </fo:page-sequence>
</fo:root>
</xsl:template>

<xsl:template name="solution_functions">
    <xsl:param name="data"/>
  <fo:block id="functions" break-before="page" 
            font-family="Helvetica"
            font-weight="bold"
            font-size="14pt"
            color="#0050B2" space-after="12pt"
            space-before="16pt" space-after.precedence="3">
        Functions
  </fo:block>
    <xsl:for-each select="$data/function">
        <xsl:variable name="this" select="." />
        <xsl:call-template name="present_function">
            <xsl:with-param name="data" select="$this"/>
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template name="present_function">
    <xsl:param name="data"/>
    <fo:block 
                font-family="Helvetica"
                font-weight="bold"
                font-size="12pt" 
                space-after="12pt"
                space-before="16pt" space-after.precedence="3">
            Code
    </fo:block>
    <fo:block font-family="Helvetica" font-size="12pt" color="black" 
        space-after="12pt" space-before="12pt" space-before.precedence="4">
        <fo:block>
            <xsl:text>This works! Thanks ...</xsl:text>
            <fo:block linefeed-treatment="preserve" 
                      white-space-collapse="false" 
                      white-space-treatment="preserve">
                <xsl:value-of select="$data/code"/>
            </fo:block>
        </fo:block>
    <fo:block>
       [ All following examples were part of the original post. The above is the one which works. The below, kept for future reference to original question. ]
    </fo:block>
    <fo:block>
        <xsl:text>Original</xsl:text>
        <fo:block linefeed-treatment="preserve" 
                  white-space-collapse="false" 
                  white-space-treatment="preserve">
            <xsl:value-of select="$data/code"/>
        </fo:block>
    </fo:block>

      <fo:block>
          <xsl:text>Copy</xsl:text>
          <xsl:copy>
            <xsl:value-of select="$data/code"/>
          </xsl:copy>
      </fo:block>

      <fo:block>
          <xsl:text>Copy Text</xsl:text>
          <xsl:copy>
            <xsl:value-of select="$data/code/text()"/>
          </xsl:copy>
      </fo:block>

      <fo:block>
          <xsl:text>Original</xsl:text>
          <xsl:value-of select="$data/code"/>
      </fo:block>

      <fo:block>
          <xsl:text>Normalise space</xsl:text>
          <value-of select="normalize-space($data/code)" disable-output-escaping="yes"/>
      </fo:block>

      <fo:block>          
          <xsl:text>Copy with extra CDATA wrapper?</xsl:text>
          <xsl:copy>
            <xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
            <xsl:value-of select="$data/code"/>
            <xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
          </xsl:copy>         
      </fo:block>

      <fo:block>
        <xsl:text>Again, an attempt to wrap</xsl:text>
        <xsl:text disable-output-escaping="yes">
          &lt;![CDATA[
        </xsl:text>
        <xsl:value-of select="$data/code" />
        <xsl:text disable-output-escaping="yes">
          ]]&gt;
        </xsl:text>
      </fo:block>

    </fo:block>
</xsl:template>
</xsl:stylesheet>

Upvotes: 2

kjhughes
kjhughes

Reputation: 111686

Let's clear up a few fundamental misunderstandings reflected in your code.

  1. As @michael.hor257k mentioned, you have no xsl:template constructs. This is actually ok for simplified stylesheets that you're attempting, but such form has no xsl:stylesheet root element; it simply begins with the XML root element desired in your output XML. It then pulls from the input XML as you're doing with xsl:value-of/@select constructs. So, major fix #1 is to add <xsl:template match="/">... or remove <xsl:stylesheet>. We'll go with the second option (simplified stylesheets) since it seems to be what you've favoring.
  2. Next, realize that your XPaths are failing to select anything, so all of your test cases aren't proving anything. Major fix #2: Rather than selecting "code/', select "/myxml/code" in all of your test cases.

Here are your test cases with the above changes (and a few other minor fixes) made:

<?xml version="1.0" encoding="iso-8859-1"?>
<fo:block font-family="Courier New" font-size="12pt" color="black" 
          space-after="12pt" space-before="12pt" space-before.precedence="4"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:fo="http://www.w3.org/1999/XSL/Format"
          xsl:version="1.0">
  <fo:block>
    <xsl:text>Copy</xsl:text>
    <xsl:copy>
      <xsl:value-of select="/myxml/code"/>
    </xsl:copy>
  </fo:block>
  <fo:block>
    <xsl:text>Copy Text</xsl:text>
    <xsl:copy>
      <xsl:value-of select="/myxml/code/text()"/>
    </xsl:copy>
  </fo:block>
  <fo:block>
    <xsl:text>Original</xsl:text>
    <xsl:value-of select="/myxml/code"/>
  </fo:block>
  <fo:block>
    <xsl:text>Normalise space</xsl:text>
    <xsl:value-of select="normalize-space(code)" disable-output-escaping="yes"/>
  </fo:block>
  <fo:block>          
    <xsl:text>Copy with extra CDATA wrapper?</xsl:text>
    <xsl:copy>
      <xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
      <xsl:value-of select="/myxml/code"/>
      <xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
    </xsl:copy>         
  </fo:block>
  <fo:block>
    <xsl:text>Again, an attempt to wrap</xsl:text>
    <xsl:text disable-output-escaping="yes">
      &lt;![CDATA[
    </xsl:text>
    <xsl:value-of select="/myxml/code" />
    <xsl:text disable-output-escaping="yes">
      ]]&gt;
    </xsl:text>
  </fo:block>
</fo:block>

Running the above XSLT against your input XML yields more useful test results:

<?xml version="1.0" encoding="UTF-8"?><fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" font-family="Courier New" font-size="12pt" color="black" space-after="12pt" space-before="12pt" space-before.precedence="4"><fo:block>Copyimport java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null &amp;&amp; key.length() &gt; 0) {
                System.out.println(key);
            }
        }
    </fo:block><fo:block>Copy Textimport java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null &amp;&amp; key.length() &gt; 0) {
                System.out.println(key);
            }
        }
    </fo:block><fo:block>Originalimport java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null &amp;&amp; key.length() &gt; 0) {
                System.out.println(key);
            }
        }
    </fo:block><fo:block>Normalise space</fo:block><fo:block>Copy with extra CDATA wrapper?<![CDATA[import java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null &amp;&amp; key.length() &gt; 0) {
                System.out.println(key);
            }
        }
    ]]></fo:block><fo:block>Again, an attempt to wrap
      <![CDATA[
    import java.nio.charset.Charset;
    import com.my.library.AClass;
    import com.my.library.AnotherClass;

    public String getStringValue(String key) {
            // Just some ramblings

            // Dummy code...
            if (key != null &amp;&amp; key.length() &gt; 0) {
                System.out.println(key);
            }
        }

      ]]>
    </fo:block></fo:block>

I believe from there you'll be able to identify the test case results that produce the results you seek.

Upvotes: 1

Mads Hansen
Mads Hansen

Reputation: 66783

Put your code inside of an fo:block and set the following attributes to control the treatment of whitespace and carriage returns:

Like this:

<fo:block>
    <xsl:text>Original</xsl:text>
    <fo:block linefeed-treatment="preserve" 
              white-space-collapse="false" 
              white-space-treatment="preserve">
        <xsl:value-of select="code"/>
    </fo:block>
</fo:block>

Upvotes: 1

Related Questions