Skip to content

Commit

Permalink
Merge pull request #57 from danschultzer/support-css-input
Browse files Browse the repository at this point in the history
Support css input for inline style parsing
  • Loading branch information
danschultzer authored Oct 18, 2020
2 parents 2b2b5a8 + 108f540 commit db99ffa
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v0.3.12 (TBA)

* `Premailex.HTMLInlineStyles.process/3` now supports passing in CSS as an argument

## v0.3.11 (2020-10-08)

* Fixed bug where the inline styles where applied to more than the first match causing in some cases styles to be missing for subsequent parent elements
Expand Down
59 changes: 44 additions & 15 deletions lib/premailex/html_inline_styles.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,40 @@ defmodule Premailex.HTMLInlineStyles do
Processes an HTML string adding inline styles.
Options:
* `css_selector` - the style tags to be processed for inline styling, defaults to `style,link[rel="stylesheet"][href]`
* `optimize` - list or atom option for optimizing the output. The following values can be used:
* `:css_selector` - the style tags to be processed for inline styling, defaults to `style,link[rel="stylesheet"][href]`
* `:optimize` - list or atom option for optimizing the output. The following values can be used:
* `:none` - no optimization (default)
* `:all` - apply all optimization steps
* `:remove_style_tags` - Remove style tags (can be combined in a list)
"""
@spec process(String.t(), Keyword.t()) :: String.t()
def process(html, options \\ []) do
css_selector = Keyword.get(options, :css_selector, "style,link[rel=\"stylesheet\"][href]")
@spec process(String.t() | HTMLParser.html_tree(), [CSSParser.rule_set()], Keyword.t()) :: String.t()
def process(html_or_html_tree, css_rule_sets_or_options \\ nil, options \\ nil)
def process(html, css_rule_sets_or_options, options) when is_binary(html) do
html
|> HTMLParser.parse()
|> process(css_rule_sets_or_options, options)
end
def process(html_tree, css_rule_sets_or_options, nil) do
case Keyword.keyword?(css_rule_sets_or_options) do
true -> process(html_tree, nil, css_rule_sets_or_options)
false -> process(html_tree, css_rule_sets_or_options, [])
end
end
def process(html_tree, nil, options) do
css_selector = Keyword.get(options, :css_selector, "style,link[rel=\"stylesheet\"][href]")
css_rule_sets = load_styles(html_tree, css_selector)
options = Keyword.put_new(options, :css_selector, css_selector)

process(html_tree, css_rule_sets, options)
end
def process(html_tree, css_rules_sets, options) do
optimize_steps = Keyword.get(options, :optimize, :none)
tree = HTMLParser.parse(html)
optimize_options = Keyword.take(options, [:css_selector])

tree
|> load_styles(css_selector)
|> apply_styles(tree)
css_rules_sets
|> apply_styles(html_tree)
|> normalize_styles()
|> optimize(optimize_steps, css_selector: css_selector)
|> optimize(optimize_steps, optimize_options)
|> HTMLParser.to_string()
end

Expand All @@ -37,8 +54,19 @@ defmodule Premailex.HTMLInlineStyles do
|> Enum.reduce([], &Enum.concat(&1, &2))
end

defp apply_styles(styles, tree) do
Enum.reduce(styles, tree, &add_rule_set_to_html(&1, &2))
defp apply_styles(styles, html_tree) do
html_tree
|> HTMLParser.all("body")
|> case do
[] -> html_tree
body -> body
end
|> List.wrap()
|> Enum.reduce(html_tree, fn body_or_html_tree, html_tree ->
Util.traverse_until_first(html_tree, body_or_html_tree, fn tree ->
Enum.reduce(styles, tree, &add_rule_set_to_html(&1, &2))
end)
end)
end

defp load_css({"style", _, content}) do
Expand Down Expand Up @@ -138,12 +166,13 @@ defmodule Premailex.HTMLInlineStyles do
defp optimize(tree, [:all], options), do: optimize(tree, [:remove_style_tags], options)

defp optimize(tree, steps, options) do
maybe_remove_style_tags(tree, steps, options)
maybe_remove_style_tags(tree, steps, Keyword.get(options, :css_selector))
end

defp maybe_remove_style_tags(tree, steps, options) do
defp maybe_remove_style_tags(tree, _steps, nil), do: tree
defp maybe_remove_style_tags(tree, steps, css_selector) do
case Enum.member?(steps, :remove_style_tags) do
true -> HTMLParser.filter(tree, Keyword.get(options, :css_selector))
true -> HTMLParser.filter(tree, css_selector)
false -> tree
end
end
Expand Down
47 changes: 39 additions & 8 deletions test/premailex/html_inline_styles_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ defmodule Premailex.HTMLInlineStylesTest do
{:ok, input: @input}
end

test "process/1", %{input: input} do
test "process/3", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input)

assert parsed =~
Expand Down Expand Up @@ -148,46 +148,77 @@ defmodule Premailex.HTMLInlineStylesTest do
assert parsed =~ "<div class=\"match-order-test-4 same-match\" style=\"color:yellow;\">"
end

test "process/1 with css_selector", %{input: input} do
test "process/3 with css_selector", %{input: input} do
parsed =
Premailex.HTMLInlineStyles.process(input, css_selector: "link[rel=\"stylesheet\"][href]")

assert parsed =~
"<p style=\"color:#333333;font-family:Arial, sans-serif;font-size:16px;font-weight:bold;line-height:22px;margin:0;padding:0;\">First paragraph"
end

test "process/1 with optimize: :all", %{input: input} do
test "process/3 with optimize: :all", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input, optimize: :all)
refute parsed =~ "<style>"
refute parsed =~ "<link href"
end

test "process/1 with optimize: :remove_style_tags", %{input: input} do
test "process/3 with optimize: :remove_style_tags", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input, optimize: :remove_style_tags)
refute parsed =~ "<style>"
refute parsed =~ "<link href"
end

test "process/1 with optimize: [:remove_style_tags]", %{input: input} do
test "process/3 with optimize: [:remove_style_tags]", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input, optimize: [:remove_style_tags])
refute parsed =~ "<style>"
refute parsed =~ "<link href"
end

test "process/1 with optimize: [:unknown]", %{input: input} do
test "process/3 with optimize: [:unknown]", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input, optimize: [:unknown])
assert parsed =~ "<style>"
assert parsed =~ "<link href"
end

test "process/1 with optimize: [:none]", %{input: input} do
test "process/3 with optimize: [:none]", %{input: input} do
parsed = Premailex.HTMLInlineStyles.process(input, optimize: :none)
assert parsed =~ "<style>"
assert parsed =~ "<link href"
end

test "process/1 with no loaded styles" do
test "process/3 with no loaded styles" do
parsed = Premailex.HTMLInlineStyles.process("<span style=\"width: 100%;\">Hello</span>")
assert parsed =~ "<span style=\"width: 100%;\">Hello</span>"
end

test "process/3 accepts html tree as first argument" do
html_tree = Premailex.HTMLParser.parse(@input)
parsed = Premailex.HTMLInlineStyles.process(html_tree)

assert parsed =~ "<style>"
assert parsed =~ "<link href"
assert parsed =~ "<body style=\"color:#333333;font-family:Arial, sans-serif;font-size:14px;line-height:22px;\">"

parsed = Premailex.HTMLInlineStyles.process(html_tree, [optimize: :all])

refute parsed =~ "<style>"
refute parsed =~ "<link href"
assert parsed =~ "<body style=\"color:#333333;font-family:Arial, sans-serif;font-size:14px;line-height:22px;\">"
end

test "process/3 accepts css rule set as second argument" do
css_rule_set = Premailex.CSSParser.parse("*{color:red;}")
parsed = Premailex.HTMLInlineStyles.process(@input, css_rule_set)

assert parsed =~ "<style>"
assert parsed =~ "<link href"
assert parsed =~ "<body style=\"color:red;\">"

css_rule_set = Premailex.CSSParser.parse("*{color:red;}")
parsed = Premailex.HTMLInlineStyles.process(@input, css_rule_set, [optimize: :all])

assert parsed =~ "<style>"
assert parsed =~ "<link href"
assert parsed =~ "<body style=\"color:red;\">"
end
end

0 comments on commit db99ffa

Please sign in to comment.