Current section
Files
Jump to
Current section
Files
lib/premailex/html_inline_styles.ex
defmodule Premailex.HTMLInlineStyles do
@moduledoc """
Module that processes inline styling in HMTL.
"""
alias Premailex.{CSSParser, Util}
@doc false
def process(html) do
html
|> Floki.find("style,link[rel=\"stylesheet\"][href]")
|> Enum.map(&load_css(&1))
|> Enum.filter(&!is_nil(&1))
|> Enum.reduce([], &Enum.concat(&1, &2))
|> Enum.reduce(Floki.parse(html), &add_rule_set_to_html(&1, &2))
|> normalize_style()
|> Floki.raw_html
end
defp load_css({"style", _, content}) do
content
|> Enum.join("\n")
|> CSSParser.parse()
end
defp load_css({"link", attrs, _}),
do: load_css({"link", List.keyfind(attrs, "href", 0)})
defp load_css({"link", {"href", url}}) do
url
|> HTTPoison.get()
|> parse_url_response()
end
defp parse_url_response({:ok, %{body: resp}}), do: CSSParser.parse(resp)
defp parse_url_response(_), do: nil
defp add_rule_set_to_html(%{selector: selector, rules: rules, specificity: specificity}, html) do
html
|> Floki.find(selector)
|> Enum.reduce(html, &update_style_for_html(&2, &1, rules, specificity))
end
defp update_style_for_html(html, needle, rules, specificity) do
Util.traverse(html, needle, &update_style_for_element(&1, rules, specificity))
end
defp update_style_for_element({name, attrs, children}, rules, specificity) do
style = attrs
|> Enum.into(%{})
|> Map.get("style", nil)
|> set_inline_style_specificity()
|> add_styles_with_specificity(rules, specificity)
attrs = attrs
|> Enum.filter(fn {name, _} -> name != "style" end)
|> Enum.concat([{"style", style}])
{name, attrs, children}
end
defp set_inline_style_specificity(nil), do: ""
defp set_inline_style_specificity("[SPEC=" <> _rest = style), do: style
defp set_inline_style_specificity(style), do: "[SPEC=1000[#{style}]]"
defp add_styles_with_specificity(style, rules, specificity) do
"#{style}[SPEC=#{specificity}[#{CSSParser.to_string(rules)}]]"
end
defp normalize_style(html) do
html
|> Floki.find("[style]")
|> Enum.reduce(html, &merge_styles(&2, &1))
end
defp merge_styles(html, needle) do
Util.traverse_until_first(html, needle, &merge_style/1)
end
defp merge_style({name, attrs, children}) do
style = ~r/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/
|> Regex.scan(attrs |> Enum.into(%{}) |> Map.get("style"))
|> Enum.map(fn [_, specificity, rule] ->
%{specificity: specificity,
rules: CSSParser.parse_rules(rule)}
end)
|> CSSParser.merge()
|> CSSParser.to_string()
attrs = attrs
|> Enum.filter(fn {name, _} -> name != "style" end)
|> Enum.concat([{"style", style}])
{name, attrs, children}
end
end