defmodule Phoenix.HTML.Format do
@moduledoc """
Helpers related to formatting text.
"""
@doc ~S"""
Returns text transformed into HTML using simple formatting rules.
Two or more consecutive newlines `\n\n` or `\r\n\r\n` are considered as a
paragraph and text between them is wrapped in `<p>` tags.
One newline `\n` or `\r\n` is considered as a linebreak and a `<br>` tag is inserted.
## Examples
iex> text_to_html("Hello\n\nWorld") |> safe_to_string
"<p>Hello</p>\n<p>World</p>\n"
iex> text_to_html("Hello\nWorld") |> safe_to_string
"<p>Hello<br>\nWorld</p>\n"
iex> opts = [wrapper_tag: :div, attributes: [class: "p"]]
...> text_to_html("Hello\n\nWorld", opts) |> safe_to_string
"<div class=\"p\">Hello</div>\n<div class=\"p\">World</div>\n"
## Options
* `:escape` - if `false` does not html escape input (default: `true`)
* `:wrapper_tag` - tag to wrap each paragraph (default: `:p`)
* `:attributes` - html attributes of the wrapper tag (default: `[]`)
* `:insert_brs` - if `true` insert `<br>` for single line breaks (default: `true`)
"""
@spec text_to_html(Phoenix.HTML.unsafe(), Keyword.t()) :: Phoenix.HTML.safe()
def text_to_html(string, opts \\ []) do
escape? = Keyword.get(opts, :escape, true)
wrapper_tag = Keyword.get(opts, :wrapper_tag, :p)
attributes = Keyword.get(opts, :attributes, [])
insert_brs? = Keyword.get(opts, :insert_brs, true)
string
|> maybe_html_escape(escape?)
|> String.split(["\n\n", "\r\n\r\n"], trim: true)
|> Enum.filter(¬_blank?/1)
|> Enum.map(&wrap_paragraph(&1, wrapper_tag, attributes, insert_brs?))
|> Phoenix.HTML.html_escape()
end
defp maybe_html_escape(string, true),
do: string |> Phoenix.HTML.Engine.html_escape() |> IO.iodata_to_binary()
defp maybe_html_escape(string, false),
do: string
defp not_blank?("\r\n" <> rest), do: not_blank?(rest)
defp not_blank?("\n" <> rest), do: not_blank?(rest)
defp not_blank?(" " <> rest), do: not_blank?(rest)
defp not_blank?(""), do: false
defp not_blank?(_), do: true
defp wrap_paragraph(text, tag, attributes, insert_brs?) do
[Phoenix.HTML.Tag.content_tag(tag, insert_brs(text, insert_brs?), attributes), ?\n]
end
defp insert_brs(text, false) do
text
|> split_lines()
|> Enum.intersperse(?\s)
|> Phoenix.HTML.raw()
end
defp insert_brs(text, true) do
text
|> split_lines()
|> Enum.map(&Phoenix.HTML.raw/1)
|> Enum.intersperse([Phoenix.HTML.Tag.tag(:br), ?\n])
end
defp split_lines(text) do
String.split(text, ["\n", "\r\n"], trim: true)
end
end