31

I'm using Thymeleaf template engine with spring and I'd like to display text stored throught a multiline textarea.

In my database multiline string are store with "\n" like this : "Test1\nTest2\n...."

With th:text i've got : "Test1 Test2" with no line break.

How I can display line break using Thymeleaf and avoid manually "\n" replacing with < br/> and then avoid using th:utext (this open form to xss injection) ?

Thanks !

Daividh
  • 395
  • 1
  • 4
  • 11
  • See also: https://stackoverflow.com/questions/62822117/displaying-pretty-printed-json-from-variable-with-java-spring-boot-thymeleaf/62823125#62823125 – RobertG Apr 22 '22 at 17:22

8 Answers8

32

Two of your options:

  1. Use th:utext - easy setup option, but harder to read and remember
  2. Create a custom processor and dialect - more involved setup, but easier, more readable future use.

Option 1:

You can use th:utext if you escape the text using the expression utility method #strings.escapeXml( text ) to prevent XSS injection and unwanted formatting - http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#strings

To make this platform independent, you can use T(java.lang.System).getProperty('line.separator') to grab the line separator.

Using the existing Thymeleaf expression utilities, this works:

<p th:utext="${#strings.replace( #strings.escapeXml( text ),T(java.lang.System).getProperty('line.separator'),'&lt;br /&gt;')}" ></p>

Option 2:

The API for this is now different in 3 (I wrote this tutorial for 2.1) Hopefully you can combine the below logic with their official tutorial. One day maybe I'll have a minute to update this completely. But for now: Here's the official Thymeleaf tutorial for creating your own dialect.

Once setup is complete, all you will need to do to accomplish escaped textline output with preserved line breaks:

<p fd:lstext="${ text }"></p>

The main piece doing the work is the processor. The following code will do the trick:

package com.foo.bar.thymeleaf.processors 

import java.util.Collections;
import java.util.List;

import org.thymeleaf.Arguments;
import org.thymeleaf.Configuration;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.Node;
import org.thymeleaf.dom.Text;
import org.thymeleaf.processor.attr.AbstractChildrenModifierAttrProcessor;
import org.thymeleaf.standard.expression.IStandardExpression;
import org.thymeleaf.standard.expression.IStandardExpressionParser;
import org.thymeleaf.standard.expression.StandardExpressions;
import org.unbescape.html.HtmlEscape;

public class HtmlEscapedWithLineSeparatorsProcessor extends
        AbstractChildrenModifierAttrProcessor{

    public HtmlEscapedWithLineSeparatorsProcessor(){
        //only executes this processor for the attribute 'lstext'
        super("lstext");
    }

    protected String getText( final Arguments arguments, final Element element,
            final String attributeName) {

        final Configuration configuration = arguments.getConfiguration();

        final IStandardExpressionParser parser =
            StandardExpressions.getExpressionParser(configuration);

        final String attributeValue = element.getAttributeValue(attributeName);

        final IStandardExpression expression =
            parser.parseExpression(configuration, arguments, attributeValue);

        final String value = (String) expression.execute(configuration, arguments);

        //return the escaped text with the line separator replaced with <br />
        return HtmlEscape.escapeHtml4Xml( value ).replace( System.getProperty("line.separator"), "<br />" );


    }



    @Override
    protected final List<Node> getModifiedChildren(
            final Arguments arguments, final Element element, final String attributeName) {

        final String text = getText(arguments, element, attributeName);
        //Create new text node signifying that content is already escaped.
        final Text newNode = new Text(text == null? "" : text, null, null, true);
        // Setting this allows avoiding text inliners processing already generated text,
        // which in turn avoids code injection.
        newNode.setProcessable( false );

        return Collections.singletonList((Node)newNode);


    }

    @Override
    public int getPrecedence() {
        // A value of 10000 is higher than any attribute in the SpringStandard dialect. So this attribute will execute after all other attributes from that dialect, if in the same tag.
        return 11400;
    }


}

Now that you have the processor, you need a custom dialect to add the processor to.

package com.foo.bar.thymeleaf.dialects;

import java.util.HashSet;
import java.util.Set;

import org.thymeleaf.dialect.AbstractDialect;
import org.thymeleaf.processor.IProcessor;

import com.foo.bar.thymeleaf.processors.HtmlEscapedWithLineSeparatorsProcessor;

public class FooDialect extends AbstractDialect{

    public FooDialect(){
        super();
    }

    //This is what all the dialect's attributes/tags will start with. So like.. fd:lstext="Hi David!<br />This is so much easier..."
    public String getPrefix(){
        return "fd";
    }

    //The processors.
    @Override
    public Set<IProcessor> getProcessors(){
        final Set<IProcessor> processors = new HashSet<IProcessor>();
        processors.add( new HtmlEscapedWithLineSeparatorsProcessor() );
        return processors;
    }

}

Now you need to add it to your xml or java configuration:

If you are writing a Spring MVC application, you just have to set it at the additionalDialects property of the Template Engine bean, so that it is added to the default SpringStandard dialect:

    <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
  <property name="templateResolver" ref="templateResolver" />
  <property name="additionalDialects">
    <set>
      <bean class="com.foo.bar.thymeleaf.dialects.FooDialect"/>
    </set>
  </property>
    </bean>

Or if you are using Spring and would rather use JavaConfig you can create a class annotated with @Configuration in your base package that contains the dialect as a managed bean:

package com.foo.bar;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.foo.bar.thymeleaf.dialects.FooDialect;

@Configuration
public class TemplatingConfig {

    @Bean
    public FooDialect fooDialect(){
        return new FooDialect();
    }
}

Here are some further references on creating custom processors and dialects: http://www.thymeleaf.org/doc/articles/sayhelloextendingthymeleaf5minutes.html , http://www.thymeleaf.org/doc/articles/sayhelloagainextendingthymeleafevenmore5minutes.html and http://www.thymeleaf.org/doc/tutorials/2.1/extendingthymeleaf.html

David Roberts
  • 734
  • 7
  • 13
  • Perfect solution for me ! Thanks for your help – Daividh Jun 03 '15 at 13:59
  • 1
    @Chettor I'm not sure if Stackoverflow updates you when the answer is modified or not, but I included an example implementation for the custom processor and dialect approach. Hope it helps! – David Roberts Jun 11 '15 at 18:38
  • It will help me , Thanks ! – Parth Solanki May 13 '16 at 07:17
  • 2
    The thymeleaf API has changed since. Option 2 can be easily adapted to the 3.0 API following the official tutorial: https://www.thymeleaf.org/doc/tutorials/3.0/extendingthymeleaf.html – xtian Jul 01 '18 at 09:44
  • @xtian Thanks for letting me know this has changed! I added that link to the answer. I don't have time to update the full thing at the moment, but hopefully soon! – David Roberts Aug 03 '18 at 19:49
16

Try putting style="white-space: pre-line" on the element.

For example:

<span style="white-space: pre-line" th:text="${text}"></span>

You might also be interested in white-space: pre-wrap which also maintains consecutive spaces in addition to line breaks.

Avoid using th:utext if possible as it has serious security implications. If care is not taken, th:utext can create the possibility of XSS attacks.

shawnz
  • 331
  • 2
  • 5
9

Maybe not what the OP had in mind, but this works and prevents code injection:

<p data-th-utext="${#strings.replace(#strings.escapeXml(text),'&#10;','&lt;br&gt;')}"></p>

(Using HTML5-style Thymeleaf.)

holmis83
  • 15,922
  • 5
  • 82
  • 83
7

In my case escapeJava() returns unicode values for cyrillic symbols, so I wrap all in unescapeJava() method help to solve my problem.

<div class="text" th:utext="${#strings.unescapeJava(#strings.replace(#strings.escapeJava(comment.text),'\n','&lt;br /&gt;'))}"></div>
npace
  • 4,218
  • 1
  • 25
  • 35
  • This allows the possibility of XSS attacks, you should instead use escapeXml, not escapeJava (which will also fix the issue with Cyrillic symbols). If you don't care about XSS attacks, then it would be better to just remove both the escapeJava and unescapeJava calls. – shawnz Nov 17 '21 at 18:41
1

If you are using jQuery in thymleaf, you can format your code using this:

$('#idyourdiv').val().replace(/\n\r?/g, '<br />')

Hope that answer can help you

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
0

Try this

<p th:utext="${#strings.replace(#strings.escapeJava(description),'\n','&lt;br /&gt;')}" ></p>
K. Siva Prasad Reddy
  • 11,786
  • 12
  • 68
  • 95
  • It's near perfect ! Thanks Linebreak are correctly displayed BUT when user type escaped character like " or ' it show \" or \' (with backslash). Is there a way to prevent it ? – Daividh May 22 '15 at 15:35
0

You need to use th:utext and append break line to string. My code is:

StringBuilder message = new StringBuilder();
        message.append("some text");
        message.append("<br>");
        message.append("some text");

<span th:utext="${message}"></span>
0

Inspired by @DavidRoberts answer, I made a Thymeleaf dialect that makes it easy to keep the line breaks, if the css white-space property isn't an option. It also bring support for BBCode if you want it. You can either import it as a dependency (it's very light, and very easy to set up thanks to the starter project) or just use it as inspiration to make your own. Check it out here : https://github.com/oxayotl/meikik-project