bisgardo
bisgardo

Reputation: 4600

Rendering a multiline string in Thymeleaf

In a variant of this question, I want to render a multiline string into an HTML table using Thymeleaf.

That is, how do I convert a string like

Cronut fixie tousled migas.
Whatever neutra offal fanny pack, photo booth kitsch bespoke hammock swag.
Keffiyeh yuccie meditation mustache cornhole paleo.

into

<table class="table">
    <tr>
        <td>Cronut fixie tousled migas.</td>
    </tr>
    <tr>
        <td>Whatever neutra offal fanny pack, photo booth kitsch bespoke hammock swag.</td>
    </tr>
    <tr>
        <td>Keffiyeh yuccie meditation mustache cornhole paleo.</td>
    </tr>
</table>

Upvotes: 0

Views: 3653

Answers (2)

ashurexm
ashurexm

Reputation: 6335

I originally posted this answer on a previous SO question but this one also seems to pop up. I've modified it to output a table instead of just newlines.

It's possible to do this with a custom dialect and attribute processor to handle doing this without a lot of inline SpEl or hacks.

Creating a Custom Attribute Processor

public class NewlineAttrProcessor extends AbstractUnescapedTextChildModifierAttrProcessor
{
    public NewlineAttrProcessor()
    {
        super("nl2br");
    }

    @Override
    protected String getText(Arguments arguments, Element element, 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);
        final String[] lines = StringUtils.split(value, "\n");

        return "<table><td>" + StringUtils.join(lines, "</td><td>") + "</td></table>";
    }

    @Override
    public int getPrecedence()
    {
        return 10000;
    }
}

You have to extend the AbstractUnescapedTextChildModifierAttrProcessor processor or else you'll get the html entity tags for <table>...</table> and you won't actually get the HTML.

Creating a custom dialect

To implement a custom dialect you need a dialect class like so:

public class MyCustomDialect extends AbstractDialect
{
    @Override
    public String getPrefix()
    {
        return "cd";
    }

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

The getPrefix method return value is what you would use to call any custom processors that you make. For example, Thymeleaf uses th. The custom processor we implemented above this is looking for nl2br so to call it you would use the cd:nl2br attribute instead of th:text.

Registering your new dialect

In your main class, you just need to create a @Bean that will return a new instance of your dialect class.

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Bean
    public MyCustomDialect myCustomDialect()
    {
        return new MyCustomDialect();
    }
}

Using your custom processor

Finally, in your template file you would have an HTML tag like this:

<div cd:nl2br="${myObject.myField}">MY MULTILINE FIELD</div>

For a more thorough guide to implementing custom dialects I recommend the Thymeleaf docs:

Upvotes: 1

bisgardo
bisgardo

Reputation: 4600

With the string contained in the model variable model.text, a possible solution (when using the Spring dialect) is this:

<table>
    <tr th:each="line : ${#strings.arraySplit(model.text, T(org.apache.commons.lang3.StringUtils).LF)}">
        <td th:text="${line}"></td>
    </tr>
</table>

The reason for using T(org.apache.commons.lang3.StringUtils).LF instead of just \n is that (as also described in this answer) SpEL escapes the backslash, and then decides to split on 'n'-letters instead of newlines.

Of course, one should also consider the solution of just splitting the string into an array directly in the controller (i.e. using plain Java).

Upvotes: 1

Related Questions