Inserting a line break in a PDF generated from XSL FO using <xsl:value-of>
Asked Answered
T

11

34

I am using XSL FO to generate a PDF file containing a table with information. One of these columns is a "Description" column. An example of a string that I am populating one of these Description fields with is as follows:

This is an example Description.<br/>List item 1<br/>List item 2<br/>List item 3<br/>List item 4

Inside the table cell that corresponds to this Description, I would like the output to display as such:

This is an example Description.
List item 1
List item 2
List item 3
List item 4

I've learned from searching elsewhere that you can make line breaks in XSL FO using an <fo:block></fo:block> within another <fo:block> element. Therefore, even before I parse the XML with my XSL stylesheet, I replace all occurrences of <br/> with <fo:block/>, so that the literal value of the string now looks like:

This is an example Description.<fo:block/>List item 1<fo:block/>List item 2<fo:block/>List item 3<fo:block/>List item 4

The problem arises when the Description string I am using is obtained using <xsl:value-of>, example as follows:

<fo:block>
    <xsl:value-of select="descriptionStr"/>
</fo:block>

In which case, the value that gets output to my PDF document is the literal value, so it looks exactly like the previous example with all the <fo:block/> literals. I've tried manually hard-coding the <fo:block/> in the middle of another string, and it displays correctly. E.g. if I write inside my stylesheet:

<fo:block>Te<fo:block/>st</fo:block>

It will display correctly as:

Te
st

But this does not seem to happen when the <fo:block/> is inside the value of an <xsl:value-of select=""/> statement. I've tried searching for this on SO as well as Google, etc. to no avail. Any advice or help will be greatly appreciated. Thank you!

Twinkle answered 7/9, 2010 at 18:24 Comment(0)
C
6

You shouldn't use xsl:value-of instruction but xsl:apply-templates instead: for built-in rule for text node will just output their string value, and for empty br element you could declare a rule matching descriptionStr/br or descriptionStr//br (depending your input) in order to transform to empty fo:block.

Calm answered 7/9, 2010 at 19:22 Comment(3)
Hi Alejandro, I think I understand what you mean... but in my case, I apologize for not making this clear in my initial question, the string I am actually processing is not from an XML file per se. It's from a node set that I build using the EXSLT str:split function (exslt.org/str/functions/split/str.split.html). So, each child in the node set is enclosed in a '<token>' element tag, therefore I am unable to match a template with the name of the element that contains my string. Do you think I need to change my implementation so I can use the apply-templates call? Thank you so much.Twinkle
@user311811: if you have <token>This is an example Description.<br/>List item 1</token> you could match token/br. But from your question, this xsl:value-of select="descriptionStr" means string value of descriptionStr child element.Calm
@user311811: You should be able to use exslt:node-set() to treat the result from str:split() as a node set. If you're able to use XSLT 2.0 this would be so much easier though.Spenserian
V
51

You could also replace <br/> with &#xA; and add a linefeed-treatment="preserve" attribute to your <fo:block>.

Something like:

<fo:block linefeed-treatment="preserve">This is an example Description.&#xA;List item 1&#xA;List item 2&#xA;List item 3&#xA;List item 4</fo:block>

Edit

Some users may need to use \n instead of &#xA; depending on how they are creating the XML. See Retain the &#xA; during xml marshalling for more details.

V2 answered 8/9, 2010 at 3:32 Comment(3)
I am trying the same thing and couldn't get it working. When I generate the xml through xsd my output is <description>REPAIR CAB DOOR&amp;#xA;REPAIR &amp;#xA;</description> thus preventing from adding the new line character in my pdf document. Do you have any ideasOverbite
@Overbite - I'm not sure what you mean when you say you generate the xml through xsd (xsd is a schema). Can you create a new question with an example and more details on how you're running your XSL-FO? If you put the link to the question in a comment here, I'll try to take a look for you.V2
Here you go #13732152Overbite
P
13

This helped me and should be simplest solution (working with Apache FOP 1.1):

Why not replace your <br/> with Unicode character called line separator.

   <xsl:template match="br">
      <xsl:value-of select="'&#x2028;'"/>
   </xsl:template>

See https://en.wikipedia.org/wiki/Newline#Unicode

Pricillaprick answered 15/12, 2015 at 12:14 Comment(1)
Just using &#x2028; works for me, it is by far the simplest approach and does not interfere with linebreaks that are inserted for readability.Myriam
P
8

The following code worked:

<fo:block white-space-collapse="false" 
    white-space-treatment="preserve" 
    font-size="0pt" line-height="15px">.</fo:block>

It makes the xsl processor thinks this block contains a line of text, which actually has a 0pt font size. You can customize line height by providing your own value.

Pipsissewa answered 30/8, 2013 at 4:12 Comment(0)
C
6

You shouldn't use xsl:value-of instruction but xsl:apply-templates instead: for built-in rule for text node will just output their string value, and for empty br element you could declare a rule matching descriptionStr/br or descriptionStr//br (depending your input) in order to transform to empty fo:block.

Calm answered 7/9, 2010 at 19:22 Comment(3)
Hi Alejandro, I think I understand what you mean... but in my case, I apologize for not making this clear in my initial question, the string I am actually processing is not from an XML file per se. It's from a node set that I build using the EXSLT str:split function (exslt.org/str/functions/split/str.split.html). So, each child in the node set is enclosed in a '<token>' element tag, therefore I am unable to match a template with the name of the element that contains my string. Do you think I need to change my implementation so I can use the apply-templates call? Thank you so much.Twinkle
@user311811: if you have <token>This is an example Description.<br/>List item 1</token> you could match token/br. But from your question, this xsl:value-of select="descriptionStr" means string value of descriptionStr child element.Calm
@user311811: You should be able to use exslt:node-set() to treat the result from str:split() as a node set. If you're able to use XSLT 2.0 this would be so much easier though.Spenserian
B
3

Generating strings containing escaped XML markup is seldom the right answer, but if that's what you have to work with, then for input like this:

<Description><![CDATA[This is an example Description.<br/>List item 1<br/>List item 2<br/>List item 3<br/>List item 4]]></Description>

if you're using XSLT 2.0, you can use xsl:analyze-string to get the empty fo:block that you originally wanted:

<xsl:template match="Description">
  <fo:block>
    <xsl:analyze-string select="." regex="&lt;br/>">
      <xsl:matching-substring>
        <fo:block />
      </xsl:matching-substring>
      <xsl:non-matching-substring>
        <xsl:value-of select="." />
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </fo:block>
</xsl:template>

but if you are using XSLT 2.0, you can more concisely use linefeed-treatment="preserve" as per @Daniel Haley and use replace() to insert the linefeeds:

<xsl:template match="Description">
  <fo:block linefeed-treatment="preserve">
    <xsl:value-of select="replace(., '&lt;br/>', '&#xA;')" />
  </fo:block>
</xsl:template>

If you are using XSLT 1.0, you can recurse your way through the string:

<xsl:template match="Description">
  <fo:block linefeed-treatment="preserve">
    <xsl:call-template name="replace-br" />
  </fo:block>
</xsl:template>

<xsl:template name="replace-br">
  <xsl:param name="text" select="." />

  <xsl:choose>
    <xsl:when test="not(contains($text, '&lt;br/>'))">
      <xsl:value-of select="$text" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="substring-before($text, '&lt;br/>')"/>
      <xsl:text>&#xA;</xsl:text> <!-- or <fo:block /> -->
      <xsl:call-template name="replace-br">
        <xsl:with-param name="text" select="substring-after($text, '&lt;br/>')"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
Brimful answered 13/8, 2016 at 14:54 Comment(0)
E
2

Try using linefeed-treatment="preserve" and \n instead of <br> for a new line.

<fo:block linefeed-treatment="preserve" >
 <xsl:value-of select="Description" />
</fo:block>
Ergonomics answered 19/7, 2016 at 3:43 Comment(0)
A
1

Try this:

<fo:block><fo:inline color="transparent">x</fo:inline></fo:block>

This code adds a block which contains transparent text, making it look like a new line.

Actuality answered 29/8, 2013 at 9:23 Comment(2)
I wouldn't suggest a hack, but rather an answer. This is clearly a hack. See @Daniel Haley for a proper answerBrewington
I didn't realize there was a rule here against hacks.Poliard
L
0

For XSLT 1.0 I'm using my XSLT Line-Break Template on GitHub.

For XSL-FO it supports

  • Line breaks
  • Line delimiters (vs Line breaks)
  • Series of pointers in a row
  • Ignore Pointer Repetitions (disable the Series of pointers in a row)
  • Any string as a pointer to insert a break or a delimiter ("\n" is default)
  • Line delimiters' height
  • Default Line delimiter height from a current font size.
  • Auto ignoring of the "\r" char when searching a break place.
  • Added support for XSLT 2.0 for a seamless migration.
  • something else...

For XSLT 2.0 and later consider to use approaches like

  • XSLT 2.0 xsl:analyze-string (RegEx)
  • XPath 2.0 tokenize + XSLT (RegEx)
  • passing sequences as a template parameter (XSLT 2.0)
  • and so on
Lichee answered 20/1, 2016 at 13:41 Comment(0)
M
0

I usually use an empty block with a height that can be changed if I need more or less space:

<fo:block padding-top="5mm" />

I know this isn't the best looking solution but it's funtional.

Mireillemireles answered 27/12, 2018 at 9:3 Comment(0)
I
0

I had a text block that looks like this

<fo:table-cell display-align="right">
<fo:block font-size="40pt" text-align="right">
    <xsl:text> Text 1 </xsl:text>
    <fo:block> </fo:block>
    <xsl:text> Text2 </xsl:text>
    <fo:block> </fo:block>
    <xsl:text> Text 3</xsl:text>
</fo:block>

NB: note the empty

Increase answered 20/2, 2019 at 10:57 Comment(0)
S
-2

</fo:block> on it's own is not a direct substitute for <br/> <br/> is an html unpaired abberation that has no direct equivalent in xsl:fo

</fo:block> just means end of block. If you scatter them through your text you wont have valid xml, and your xsl processor will sick up errors.

For the line break formatting you want, each block will occur on a new line. You need a <fo:block> start block and </fo:block> end block pair for each line.

Shenitashenk answered 3/4, 2012 at 21:52 Comment(1)
Where is it mentioned in the OP or in any answer that just </fo:block> is being used or should be used?V2

© 2022 - 2024 — McMap. All rights reserved.