Breaking up Paragraphs (BR)

1. Breaking up paragraphs
2. Newline to BR element.

1.

Breaking up paragraphs

Jeni Tennison

> I wish to break the text into paragraphs only when it reaches a 
> <BR TYPE="END">.
	

Basically, this is a grouping problem. You want to group all the nodes within the TEXT element according to which BR[@TYPE = 'END'] they come after and place each group within a P element.

If you apply templates just to the BR[@TYPE = 'END'] elements, then you can gather the relevant content for the paragraph at that point: you know that there need to be as many paragraphs as BR[@TYPE = 'END'] elements (of course as long as they're preceded by some content).

So a template that takes a BR[@TYPE = 'END'] element and outputs a paragraph of the content preceding it would look something like:

<xsl:template match="BR[@TYPE = 'END']">
   <!-- locate the preceding BR -->
   <xsl:variable name="br-before"
                 select="preceding-sibling::BR[@TYPE = 'END'][1]" />
   <!-- the content are those nodes that come before this one and
        after the preceding BR -->
   <xsl:variable name="content"
                 select="preceding-sibling::node()
                          [not($br-before) or
                           generate-id(preceding-sibling::BR
                                        [@TYPE = 'END'][1]) =
                           generate-id($br-before)]" />
   <!-- if there are any nodes in that list -->
   <xsl:if test="$content">
      <!-- output a paragraph -->
      <P>
         <!-- with a copy of those nodes as the content -->
         <xsl:apply-templates select="$content" />
      </P>
   </xsl:if>
</xsl:template>

To make this work properly, you need to only apply templates to those BR elements:

   <xsl:apply-templates select="BR[@TYPE = 'END']" />

You also need to create a paragraph for any stray text at the end of the TEXT element that *doesn't* have a BR[@TYPE = 'END'] following it:

   <xsl:variable name="end-content"
                 select="node()[not(self::BR[@TYPE = 'END']) and
                                not(following-sibling::BR
                                      [@TYPE = 'END'])]" />
   <xsl:if test="$end-content">
      <P>
         <xsl:apply-templates select="$end-content" />
      </P>
   </xsl:if>

These both need to go within a template that matches the TEXT element:

<xsl:template match="TEXT">
   <xsl:apply-templates select="BR[@TYPE = 'END']" />
   <xsl:variable name="end-content"
                 select="node()[not(self::BR[@TYPE = 'END']) and
                                not(following-sibling::BR
                                      [@TYPE = 'END'])]" />
   <xsl:if test="$end-content">
      <P>
         <xsl:copy-of select="$end-content" />
      </P>
   </xsl:if>
</xsl:template>

Finally, to ignore all that irrelevant whitespace, you need to strip spaces within the TEXT element:

<xsl:strip-space elements="TEXT" />

This does the grouping that you're after. There are other ways of doing it - in particular you could use Muenchian grouping with keys in order to achieve the same effect. You can also write a recursive to template to do collect the relevant nodes for the paragraph content.

2.

Newline to BR element.

Jeni Tennison

> The problem is that when this gets converted to HTML all of the line
> breaks in the CDATA section are lost, and it becomes one long line.
> Is there a way to format what's inside the CDATA tag to have <br>
> after each line?
	

Sure. The following template uses recursion to go through a string and substitute all the line breaks (&#xA;) with 'br' elements. (These are the default substitutions - you can substitute other things by passing different values for the $from and $to parameters.)

<xsl:template name="substitute">
   <xsl:param name="string" />
   <xsl:param name="from" select="'&#xA;'" />
   <xsl:param name="to">
      <br />
   </xsl:param>
   <xsl:choose>
      <xsl:when test="contains($string, $from)">
         <xsl:value-of select="substring-before($string, $from)" />
         <xsl:copy-of select="$to" />
         <xsl:call-template name="substitute">
            <xsl:with-param name="string"
                            select="substring-after($string, $from)" />
            <xsl:with-param name="from" select="$from" />
            <xsl:with-param name="to" select="$to" />
         </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
         <xsl:value-of select="$string" />
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>                

So, for your example you can call the above template on the failure information using:

<xsl:template match="failure">
      <p>FAILURE INFO:</p>
      <xsl:call-template name="substitute">
         <xsl:with-param name="string" select="." />
      </xsl:call-template>
</xsl:template>

The other option, of course, is to use the HTML 'pre' element or to use the CSS white-space property set to 'pre' to tell the browser to take notice of the whitespace in the HTML that you're producing. These might be easier to handle than recursing through the string.