premailer 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/premailer/adapter/nokogiri.rb +116 -57
- data/lib/premailer/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d0463a94da370db30e1e80fa7851c90f9b24743518137a2d04b2c21ed7246cd
|
|
4
|
+
data.tar.gz: 73bf8f6e3f7cade9389ff65a6fef58112e8cf2a756f0271adc8b3278bd998a5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d98924c3ff4088566f7952d8b56c2ae8747fc99e6ba3f286dcce5e9dd788b3b5c80d1faf60c1a8ff3bb77dc4be136477b1f862cd00b4f1f55b100c9d6ae1289
|
|
7
|
+
data.tar.gz: aff5c151b261c9f35eb58a9a87407cb5c6ee67804ae2ec9585ad4b59043eb6132e443223ad650565dfda7a9fdfe8c528315b5946c5b2d4ea1281ecb8e65bb373
|
|
@@ -15,10 +15,21 @@ class Premailer
|
|
|
15
15
|
doc = @processed_doc
|
|
16
16
|
@unmergable_rules = CssParser::Parser.new
|
|
17
17
|
|
|
18
|
+
# Accumulate matched rule sets per node in memory instead of
|
|
19
|
+
# round-tripping them through style attributes as [SPEC=n[...]]
|
|
20
|
+
# string markers (which costs two libxml2 attribute round-trips per
|
|
21
|
+
# element plus a regex re-scan, and dominates allocation churn).
|
|
22
|
+
rules_by_node = {}
|
|
23
|
+
|
|
18
24
|
# Give all styles already in style attributes a specificity of 1000
|
|
19
25
|
# per http://www.w3.org/TR/CSS21/cascade.html#specificity
|
|
26
|
+
# Identical style strings (repeated components) share one rule set so
|
|
27
|
+
# the fold cache below can hit on them.
|
|
28
|
+
rule_set_cache = {}
|
|
20
29
|
doc.search("*[@style]").each do |el|
|
|
21
|
-
|
|
30
|
+
style = el.attributes['style'].to_s
|
|
31
|
+
rs = rule_set_cache.fetch(style) { rule_set_cache[style] = build_rule_set(style, 1000) }
|
|
32
|
+
rules_by_node[el] = rs ? [rs] : []
|
|
22
33
|
end
|
|
23
34
|
# Iterate through the rules and merge them into the HTML
|
|
24
35
|
@css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
|
|
@@ -42,11 +53,25 @@ class Premailer
|
|
|
42
53
|
# than one element. Added to work around dodgy generated code.
|
|
43
54
|
selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
|
|
44
55
|
|
|
56
|
+
rule_set = nil
|
|
57
|
+
rule_set_missing = false
|
|
45
58
|
doc.search(selector).each do |el|
|
|
46
59
|
if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
# Enroll the element even when the rule below fails to build:
|
|
61
|
+
# the marker implementation wrote the style attribute before
|
|
62
|
+
# parsing, so matched elements end up with style="" when
|
|
63
|
+
# their only rule is invalid and exceptions are disabled.
|
|
64
|
+
rules = (rules_by_node[el] ||= [])
|
|
65
|
+
next if rule_set_missing
|
|
66
|
+
|
|
67
|
+
# One shared rule set per CSS rule; merge never mutates inputs
|
|
68
|
+
# (CachedRuleSet#expand_shorthand! is idempotent by design).
|
|
69
|
+
rule_set ||= build_rule_set(declaration, specificity)
|
|
70
|
+
if rule_set.nil?
|
|
71
|
+
rule_set_missing = true
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
rules << rule_set
|
|
50
75
|
end
|
|
51
76
|
end
|
|
52
77
|
rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
|
|
@@ -59,60 +84,28 @@ class Premailer
|
|
|
59
84
|
# Remove script tags
|
|
60
85
|
doc.search("script").remove if @options[:remove_scripts]
|
|
61
86
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
# Perform style folding. Elements sharing the same matched rule sets
|
|
88
|
+
# (ubiquitous in table-based email markup) fold to the same result, so
|
|
89
|
+
# the merge/expand/collapse/serialize pipeline runs once per unique
|
|
90
|
+
# (element name, rule list) pair instead of once per element.
|
|
91
|
+
fold_cache = {}
|
|
92
|
+
rules_by_node.each do |el, declarations|
|
|
93
|
+
related = Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
94
|
+
# Rule sets are interned above, so default identity hashing makes
|
|
95
|
+
# them usable directly as cache key elements.
|
|
96
|
+
cache_key = [related ? el.name : nil, *declarations]
|
|
97
|
+
|
|
98
|
+
folded = fold_cache[cache_key] ||= fold_declarations(declarations, related ? el.name : nil)
|
|
99
|
+
style, attributes = folded
|
|
100
|
+
|
|
101
|
+
# Write the inline STYLE attribute first so the attribute order for
|
|
102
|
+
# elements that had no style attribute matches the marker-based
|
|
103
|
+
# implementation (style attr was created during selector matching).
|
|
104
|
+
el['style'] = style
|
|
105
|
+
|
|
106
|
+
attributes&.each do |html_attr, value|
|
|
107
|
+
el[html_attr] = value if el[html_attr].nil?
|
|
72
108
|
end
|
|
73
|
-
|
|
74
|
-
# Perform style folding
|
|
75
|
-
merged = CssParser.merge(declarations)
|
|
76
|
-
begin
|
|
77
|
-
merged.expand_shorthand!
|
|
78
|
-
rescue ArgumentError => e
|
|
79
|
-
raise e if @options[:rule_set_exceptions]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Duplicate CSS attributes as HTML attributes
|
|
83
|
-
if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
|
|
84
|
-
Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
|
|
85
|
-
if el[html_attr].nil? && !merged[css_attr].empty?
|
|
86
|
-
new_val = merged[css_attr].dup
|
|
87
|
-
|
|
88
|
-
# Remove url() function wrapper
|
|
89
|
-
new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')
|
|
90
|
-
|
|
91
|
-
# Remove !important, trailing semi-colon, and leading/trailing whitespace
|
|
92
|
-
new_val.gsub!(/;$|\s*!important/, '').strip!
|
|
93
|
-
|
|
94
|
-
# For width and height tags, remove px units
|
|
95
|
-
new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
|
|
96
|
-
|
|
97
|
-
# For color-related tags, convert RGB to hex if specified by options
|
|
98
|
-
new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
|
|
99
|
-
|
|
100
|
-
el[html_attr] = new_val
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
unless @options[:preserve_style_attribute]
|
|
104
|
-
merged.instance_variable_get(:@declarations).tap do |declarations|
|
|
105
|
-
declarations.delete(css_attr)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Collapse multiple rules into one as much as possible.
|
|
112
|
-
merged.create_shorthand! if @options[:create_shorthands]
|
|
113
|
-
|
|
114
|
-
# write the inline STYLE attribute
|
|
115
|
-
el['style'] = merged.declarations_to_s
|
|
116
109
|
end
|
|
117
110
|
|
|
118
111
|
doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
|
|
@@ -161,6 +154,72 @@ class Premailer
|
|
|
161
154
|
end
|
|
162
155
|
end
|
|
163
156
|
|
|
157
|
+
# Merge a list of rule sets into a final style string plus the HTML
|
|
158
|
+
# attribute duplications (bgcolor/align/...) for RELATED_ATTRIBUTES
|
|
159
|
+
# elements. Element-independent: the per-element "attribute already
|
|
160
|
+
# set" guard stays with the caller.
|
|
161
|
+
def fold_declarations(declarations, related_el_name) # :nodoc:
|
|
162
|
+
merged = CssParser.merge(declarations)
|
|
163
|
+
if merged.equal?(declarations[0])
|
|
164
|
+
# CssParser.merge returns its input untouched when given a single
|
|
165
|
+
# rule set; copy it before the destructive fold pipeline below so
|
|
166
|
+
# rule sets shared across elements are not corrupted.
|
|
167
|
+
merged = CssParser::RuleSet.new(block: merged.declarations_to_s, specificity: merged.specificity)
|
|
168
|
+
end
|
|
169
|
+
begin
|
|
170
|
+
merged.expand_shorthand!
|
|
171
|
+
rescue ArgumentError => e
|
|
172
|
+
raise e if @options[:rule_set_exceptions]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
attributes = nil
|
|
176
|
+
# Duplicate CSS attributes as HTML attributes
|
|
177
|
+
if related_el_name
|
|
178
|
+
Premailer::RELATED_ATTRIBUTES[related_el_name].each do |css_attr, html_attr|
|
|
179
|
+
unless merged[css_attr].empty?
|
|
180
|
+
new_val = merged[css_attr].dup
|
|
181
|
+
|
|
182
|
+
# Remove url() function wrapper
|
|
183
|
+
new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')
|
|
184
|
+
|
|
185
|
+
# Remove !important, trailing semi-colon, and leading/trailing whitespace
|
|
186
|
+
new_val.gsub!(/;$|\s*!important/, '').strip!
|
|
187
|
+
|
|
188
|
+
# For width and height tags, remove px units
|
|
189
|
+
new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
|
|
190
|
+
|
|
191
|
+
# For color-related tags, convert RGB to hex if specified by options
|
|
192
|
+
new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
|
|
193
|
+
|
|
194
|
+
(attributes ||= []) << [html_attr, new_val]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
unless @options[:preserve_style_attribute]
|
|
198
|
+
merged.instance_variable_get(:@declarations).tap do |declarations|
|
|
199
|
+
declarations.delete(css_attr)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Collapse multiple rules into one as much as possible.
|
|
206
|
+
merged.create_shorthand! if @options[:create_shorthands]
|
|
207
|
+
|
|
208
|
+
# write the inline STYLE attribute
|
|
209
|
+
[merged.declarations_to_s, attributes]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Build an expanded rule set for folding. Returns nil (and optionally
|
|
213
|
+
# swallows the error, mirroring the old fold-time rescue) on bad CSS.
|
|
214
|
+
def build_rule_set(block, specificity) # :nodoc:
|
|
215
|
+
rs = Premailer::CachedRuleSet.new(block: block, specificity: specificity)
|
|
216
|
+
rs.expand_shorthand!
|
|
217
|
+
rs
|
|
218
|
+
rescue ArgumentError => e
|
|
219
|
+
raise e if @options[:rule_set_exceptions]
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
|
|
164
223
|
# Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
|
|
165
224
|
# and write it into the <tt>head</tt>.
|
|
166
225
|
#
|
data/lib/premailer/version.rb
CHANGED