Jinja render text in HTML preserving line breaks
All whitespace, including newlines, is turned into a single space in HTML.
Your options, from best to worst:
- Put
white-space: pre-wrap;
on the containing element. This tells HTML to show all whitespace exactly as it appears in the source, including newlines. (You could also use a<pre>
tag, but that will also disable word-wrapping, which you probably don't want.) - Treat the plain text as Markdown and throw a Markdown processor at it—one of the things Markdown does is wrap paragraphs in
<p>
. - In Python-land, do
.replace('\n', '<br>')
. But this leaves you vulnerable to XSS because there might be other HTML-like junk in the string, and fixing that is a bit of a pain.
As suggested by Martijn Pieters (by linking Flask snippet 28), there is also the possibility to add a custom filter for that. The link is outdated, because Flask Snippets are no longer provided.
So I will provide the snippet from web archive here:
nl2br filter
Posted by Dan Jacob on 2010-06-17 @ 05:03 and filed in Template Tricks
This is a nl2br (newline to <BR>) filter, adapted from the Jinja2 example here:
http://jinja.pocoo.org/2/documentation/api#custom-filters
import re
from jinja2 import evalcontextfilter, Markup, escape
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
app = Flask(__name__)
@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n') \
for p in _paragraph_re.split(escape(value)))
if eval_ctx.autoescape:
result = Markup(result)
return result
The link above about custom-filters seems to be outdated, too. Here is a similar link from the current stable version 1.1: https://flask.palletsprojects.com/en/1.1.x/templating/#registering-filters
I'm not really sure why he uses such a complicated result computation. For me the following code worked and it's much simpler. Maybe, the variant above is better if you don't use autoescape
(which I do not want to disable)?! Anyway, now both variants are available:
# custom_template_filters.py
from flask import Blueprint
from jinja2 import evalcontextfilter, Markup, escape
blueprint = Blueprint('custom_template_filters', __name__)
@evalcontextfilter
@blueprint.app_template_filter()
def newline_to_br(context, value: str) -> str:
result = "<br />".join(re.split(r'(?:\r\n|\r|\n){2,}', escape(value)))
if context.autoescape:
result = Markup(result)
return result
It is worth mentioning that the code snippet from Dan Jacob uses Python2, and I get the template filters running via Blueprint. For the sake of completeness I also provide the app.py using a factory method:
# app.py
from flask import Flask
def create_app() -> Flask:
app = Flask(__name__)
...
register_template_filters(flask_app=app)
return app
def register_template_filters(flask_app: Flask) -> None:
from . import custom_template_filters
flask_app.register_blueprint(custom_template_filters.blueprint)
return None
It is more or less an implementation detail how you will get the context filter working. The main idea is inside the function nlbr
or newline_to_br
itself. If you get the custom filter working, it is available in all your Jinja templates and you can use it like this:
{{ anystring | newline_to_br }}