Re: [xsl] keeping text together in PDF

Subject: Re: [xsl] keeping text together in PDF
From: "Matthew L. Avizinis" <mla@xxxxxxxxx>
Date: Mon, 23 Jan 2006 09:37:21 -0500
If this gets posted twice, I'm sorry, but at least on my end it looks like my original reply was filtered. So just for kicks here it is again in hopes it has some use to someone.

Hi all,
Also just for kicks for anyone that might be interested, I had a similar issue and similar template awhile ago where we had to replace named character entities with their Unicode code number equivalent and also preserve the & before the last processing step. However, the list of replacement chars frequently changed because of requests from our content department necessitating IT involvement (namely me) to change the template. What I did was to create a special javascript function to process text, a template that calls that function with a couple parameters, and an xml file which contains a list of the special characters and their replacements. This allows our content people to edit the list themselves without IT involvement in the process. It is also somewhat easier, imho, to maintain. Perhaps the method will be useful to someone else. code follows.
[NOTE: Our method applies to Xalan-J but certainly can be modified for other processors.]


here is the stylesheet which contains the replace template:
============BEGIN STYLESHEET=================
<xsl:stylesheet version="1.0"
               xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
               xmlns:replace="http://www.replacementchars.com/replace";
               extension-element-prefixes="replace">

 <xsl:include href="replaceFun.xslt"/>
 <xsl:variable name="replaceSet" select="document('replacechars.xml')"/>

<xsl:template name="replace">
<xsl:param name="sText"/>
<xsl:param name="fText"/>
<xsl:param name="rText"/>
<xsl:value-of select="replace:replaceChars(string($sText), string($fText), string($rText))"/>
</xsl:template>


<xsl:template name="text-out">
<xsl:param name="title"/>
<xsl:variable name="begin-text">
<xsl:choose> <xsl:when test=" not($title) "><xsl:value-of select="."/></xsl:when>
<xsl:otherwise><xsl:value-of select="$title"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>


<xsl:variable name="processed-text" select="replace:replaceSpecialChars($replaceSet, string($begin-text))"/>

   <xsl:value-of select="$processed-text"/>
 </xsl:template>
</xsl:stylesheet>
==================END STYLESHEET==============================

here is the stylesheet which has the text processing character replacement functions:
============BEGIN replaceFUN STYLESHEET=========================
<?xml version="1.0"?>
<!--
This file define two javascript functions used for replacing special characters, which are defined
in ./replacechars.xml. Function replaceSpeicalChars() processes the NodeList passed in from the XSLT file
that called this function. The process STRICTLY follows the structure of replace:char element
in the file ./replacechars.xml. If the structure changes, function replaceSpecialChar() needs to be
modified.


Fucntion replaceSpecialChars() generally does two things:
- stores values of all replace:find and replace:with elements into arrays
- go through the inputStr, find the if there is a match as replaceFind, and
if so, replace it with replaceWith
Finally, the function retunrs the processed string back to the XSLT file


 Function replaceChars():
 - replace all substring "replacefind" with the string "replacewith" in the
   string "inputStr"
-->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
               xmlns:xalan="http://xml.apache.org/xalan";
               xmlns:replace="http://www.gleim.com/replace";
               extension-element-prefixes="replace"
               version="1.0">

<xalan:component prefix="replace"
functions="replaceChars replaceSpecialChars">
<xalan:script lang="javascript">
var replaceFind = new Array(); //stores values of every replace:find element
var replaceWith = new Array(); //stores values of every replace:with element
//Elements in the above two array should be unique. Two arrays should have the same size.
var counter = 0; //size of the above two arrays
var initialized = false;//records if two arrays initialized


function replaceChars (inputStr, replaceFind, replaceWith) {
var inputStr = new java.lang.String(inputStr);
var replaceFind = new java.lang.String(replaceFind);
var replaceWith = new java.lang.String(replaceWith);
replaceFind = replaceFind.replaceAll("\\.", "\\\\.");
//passed in replaceFind is not regular expression; we need to escape special chars in
//replaceFind
inputStr = inputStr.replaceAll(replaceFind, replaceWith);
return inputStr;
}


     function replaceSpecialChars (tree, inputStr) {
   if (tree.getLength() &lt; 1) {
     java.lang.System.out.println("Error! :empty nodeSet passed in.");
     return;
   }

       if (!initialized) { //initialize two arrays
         var replace_chars_node;
         var roots = tree.item(0).getChildNodes();

         for (i=0; i &lt; roots.getLength(); i++) {
           if(roots.item(i).getNodeName() != "replace:chars") {
             continue;
           } else {
             replace_chars_node = roots.item(i);
             break;
           }
         }

var replace_text_list = replace_chars_node.getChildNodes();//list of replace:text elements
var replace_text_node;


for (i=0; i &lt; replace_text_list.getLength(); i++) {
//get replace:text node one by one
replace_text_node = replace_text_list.item(i);
if(replace_text_node.getNodeType() == replace_text_node.TEXT_NODE) {
//skip all the newline, whitespace, etc
continue;
}
//every replace:text node contains one replace:find and one replace:with element
var replace_find_node = replace_text_node.getFirstChild();
var replace_with_node = replace_text_node.getLastChild();
replaceFind[counter] = replace_find_node.getFirstChild().getNodeValue();
replaceWith[counter] = replace_with_node.getFirstChild().getNodeValue();
counter++;
}
initialized = true;
}


       //do the replacing for all symbols defined in replacechars.xml
       var string = new java.lang.String(inputStr);
   for (i=0; i &lt; replaceFind.length; i++) {
     string = string.replaceAll(replaceFind[i], replaceWith[i]);
   }

   return string;
     }
   </xalan:script>
 </xalan:component>

</xsl:stylesheet>
==============END STYLESHEET=====================

and finally a short example of the xml file with the replace characters:
=============BEGIN XML FILE=====================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stylesheet [
<!ENTITY % XHTML-lat1 PUBLIC "-//W3C//ENTITIES Latin 1//EN//XML" "xhtml-lat1.ent">
%XHTML-lat1;
<!ENTITY % XHTML-special PUBLIC "-//W3C//ENTITIES Special//EN//XML" "xhtml-special.ent">
%XHTML-special;
<!ENTITY % XHTML-symbol PUBLIC "-//W3C//ENTITIES Symbols//EN//XML" "xhtml-symbol.ent">
%XHTML-symbol;
]>
<replace:chars xmlns:replace="http://www.gleim.com/replace";>
<replace:text>
<replace:find>&amp;</replace:find>
<replace:with>&amp;#38;</replace:with>
</replace:text>
<replace:text>
<replace:find>&lt;</replace:find>
<replace:with>&amp;#60;</replace:with>
</replace:text>
<replace:text>
<replace:find>&minus;</replace:find>
<replace:with>&amp;#8722;</replace:with>
</replace:text>
<replace:text>
<replace:find>&ndash;</replace:find>
<replace:with>&amp;#8211;</replace:with>
</replace:text>
<replace:text>
<replace:find>&le;</replace:find>
<replace:with>&amp;#8804;</replace:with>
</replace:text>
</replace:chars>




Nadia.Swaby@xxxxxx wrote:
Hi Ken,

As far as I can tell, there is no "separation" being produced.  The FO
document does not have any whitespace between the period and the 125".  I
am starting to think it is a bug with AntennaHouse, as this doesn't happen
with any of the other measurements in the document.  The difference between
this one and the others is that the period falls at then end of a line, and
I think AntennaHouse maybe interpreting this as the end of a sentence.
Instead of keeping the period with the number 1, the rest of the text falls
onto the next line.

I there a way to fix this via XSL, or should I just send a bug report to
AntennaHouse?

Just for kicks, here is the template processes text, but I am sure it is
not the problem:

      <xsl:template name="specchar">
            <xsl:param name="text" select="."/>
            <xsl:choose>
                  <xsl:when test="contains($text, 'b ')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b ')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x2260;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b ')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'CC<C-')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'CC<C-')"/>
                        </xsl:call-template>
                        <xsl:value-of select="substring-before($text,
'CC<C-')"/>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'CC<C-')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text,'Cb,b0')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'Cb,b0')"/>
                        </xsl:call-template>
                        <xsl:value-of select="substring-before($text,
'Cb,b0')"/>
                        <fo:inline font-style="normal" font-weight="normal
"> </fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'Cb,b0')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'b	$')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b	$')"/>
                        </xsl:call-template>
                        <xsl:value-of select="substring-before($text, 'b	$')
"/>
                        <fo:inline font-style="normal" font-weight="normal
">&#x2264;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b	$')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'b	%')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b	%')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x2265;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b	%')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'b+')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b+')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x222B;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b+')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'b')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x221A;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N1')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N1')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B1;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N1')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N2')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N2')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B2;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N2')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N3')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N3')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B3;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N3')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N4')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N4')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B4;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N4')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N5')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N5')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B5;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N5')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x0398;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N8')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N8')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03B8;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N8')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N;')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N;')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03BB;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N;')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03C0;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03C1;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03C3;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03C4;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N&')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N&')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03A6;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N&')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03C6;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N(')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'N(')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03A8;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N(')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'b&')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'b&')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#x2126;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'b&')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'O	')">
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-before($text,'O	')"/>
                        </xsl:call-template>
                        <fo:inline font-style="normal" font-weight="normal
">&#969;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'O	')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N')">
                        <xsl:value-of select="substring-before($text, 'N')
"/>
                        <fo:inline font-style="normal" font-weight="normal
">&#x0393;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N')">
                        <xsl:value-of select="substring-before($text, 'N')
"/>
                        <fo:inline font-style="normal" font-weight="normal
">&#x2206;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:when test="contains($text, 'N#')">
                        <xsl:value-of select="substring-before($text, 'N#')
"/>
                        <fo:inline font-style="normal" font-weight="normal
">&#x03A3;</fo:inline>
                        <xsl:call-template name="specchar">
                              <xsl:with-param name="text" select="
substring-after($text,'N#')"/>
                        </xsl:call-template>
                  </xsl:when>
                  <xsl:otherwise>
                        <xsl:value-of select="$text"/>
                  </xsl:otherwise>
            </xsl:choose>
      </xsl:template>

And here is what appears in the FO document (note: I have changed/removed
some text, as this is proprietary information):

<fo:block widows="2" orphans="2" space-before="10pt">The nominal machine
stock allowance for all machined surfaces specified in the CPC Norms for
Sand Castings is .125" based on a cast profile tolerance of  B1 .070".  See
Norms. The stock allowance for flanges where metal is fed, at the bottom of
the mold (Drag) should be .510''. This provides sufficient thickness for
removing sand or other defects without weld repair. </fo:block>

Nadia Swaby


"G. Ken Holman" <gkholman@CraneSoftw To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx rights.com> cc: Subject: Re: [xsl] keeping text together in PDF 2006-01-16 10:32 Please respond to xsl-list





At 2006-01-16 10:11 -0500, Nadia.Swaby@xxxxxx wrote:
I have a problem with an XML Document that is being rendered to PDF, using
AntennaHouse.  The XML contains the following text:
for Sand Castings is .125"

When the document is rendered to PDF, the . (period) gets separated from
the 125". I discovered that a way around this is to create the fo
document
as follows:
<fo:block>
....for Sand Castings is <fo:inline
keep-together.within-line="always
">.125"</fo:inline>
</fo:block>

Can you show your original stylesheet that produced the separation? There is nothing in XSL-FO 1.0 to imply there is separation between the decimal point and the number.

What I've seen from some students is they inadvertently include
significant white-space in their XSLT and misinterpret the results as
issues of XSL-FO.

This is what I need to know: Is there any way (using XSL 1.0) to surround
a string that contains '.n(1-5)" ' with an fo:inline element?

No, not easily. You cannot match on a portion of text, so you would end up with convoluted recursive calls.

Or will I
have to add and element to the XML so that the user can specify which text
need to be kept on the same line?

Hopefully neither and we'll find out the problem in your original stylesheet.

I hope this helps.

.. . . . . . . . Ken


-- Upcoming XSLT/XSL-FO hands-on courses: Denver,CO March 13-17,2006 World-wide on-site corporate, govt. & user group XML/XSL training. G. Ken Holman mailto:gkholman@xxxxxxxxxxxxxxxxxxxx Crane Softwrights Ltd. http://www.CraneSoftwrights.com/s/ Box 266, Kars, Ontario CANADA K0A-2E0 +1(613)489-0999 (F:-0995) Male Cancer Awareness Aug'05 http://www.CraneSoftwrights.com/s/bc Legal business disclaimers: http://www.CraneSoftwrights.com/legal

Current Thread