Skip to content

Commit e319458

Browse files
committed
Refactor Declarations#replace_declaration
1 parent d6c7421 commit e319458

File tree

2 files changed

+65
-43
lines changed

2 files changed

+65
-43
lines changed

lib/css_parser/rule_set/declarations.rb

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ def delete(property)
103103
# @param [#to_s] property property name to be replaces
104104
# @param [Hash<String => [String, Value]>] replacements hash with properties to replace with
105105
#
106+
# This function is used to expand and collapse css properties that has
107+
# short and syntax like `border: 1px solid` is short for `border-width:
108+
# 1p; border-type: solid;` or `border-width: 1p; border-top-style:
109+
# solid;border-right-style: solid;border-bottom-style:
110+
# solid;border-left-style: solid;`
111+
#
112+
# This function also respects the order the order the rules was written in. If we had the declaration like
113+
# border-top-style:solid;
114+
# border-right-style: solid;
115+
# border-bottom-style: solid;
116+
# border-left-style: solid;
117+
# and want to replace "border-bottom-style" with "border-left-style: dashed;" border-left-style will still be "solid" because the rule that is last take presidency. If you replace "border-bottom-style" with "border-right-style: dashed;", "border-right-style" with be dashed
118+
#
119+
#
106120
# @example
107121
# declarations = Declarations.new('line-height' => '0.25px', 'font' => 'small-caps', 'font-size' => '12em')
108122
# declarations.replace_declaration!('font', {'line-height' => '1px', 'font-variant' => 'small-caps', 'font-size' => '24px'})
@@ -112,59 +126,55 @@ def delete(property)
112126
# {"line-height"=>#<CssParser::RuleSet::Declarations::Value:0x00000000038ac458 @important=false, @value="1px">,
113127
# "font-variant"=>#<CssParser::RuleSet::Declarations::Value:0x00000000039b3ec8 @important=false, @value="small-caps">,
114128
# "font-size"=>#<CssParser::RuleSet::Declarations::Value:0x00000000029c2c80 @important=false, @value="12em">}>
115-
def replace_declaration!(property, replacements, preserve_importance: false)
116-
property = normalize_property(property)
117-
raise ArgumentError, "property #{property} does not exist" unless key?(property)
129+
def replace_declaration!(replacing_property, replacements, preserve_importance: false)
130+
replacing_property = normalize_property(replacing_property)
131+
raise ArgumentError, "property #{replacing_property} does not exist" unless key?(replacing_property)
118132

119133
replacement_declarations = self.class.new(replacements)
120134

121135
if preserve_importance
122-
importance = get_value(property).important
136+
importance = get_value(replacing_property).important
123137
replacement_declarations.each_value { |value| value.important = importance }
124138
end
125139

126-
replacement_keys = declarations.keys
127-
replacement_values = declarations.values
128-
property_index = replacement_keys.index(property)
129-
130-
# We should preserve subsequent declarations of the same properties
131-
# and prior important ones if replacement one is not important
132-
replacements = replacement_declarations.each.with_object({}) do |(key, replacement), result|
133-
existing = declarations[key]
134-
135-
# No existing -> set
136-
unless existing
137-
result[key] = replacement
138-
next
140+
# remove declarations where replacement is important but not current
141+
each do |property, value|
142+
if replacement_declarations[property]&.important && !value.important
143+
delete(property)
139144
end
145+
end
140146

141-
# Replacement more important than existing -> replace
142-
if replacement.important && !existing.important
143-
result[key] = replacement
144-
replaced_index = replacement_keys.index(key)
145-
replacement_keys.delete_at(replaced_index)
146-
replacement_values.delete_at(replaced_index)
147-
property_index -= 1 if replaced_index < property_index
148-
next
147+
# remove replacement declarations where current is important but not replacement
148+
replacement_declarations.each do |property, value|
149+
if self[property]&.important && !value.important
150+
replacement_declarations.delete(property)
149151
end
150-
151-
# Existing is more important than replacement -> keep
152-
next if !replacement.important && existing.important
153-
154-
# Existing and replacement importance are the same,
155-
# value which is declared later wins
156-
result[key] = replacement if property_index > replacement_keys.index(key)
157152
end
158153

159-
return if replacements.empty?
160-
161-
replacement_keys.delete_at(property_index)
162-
replacement_keys.insert(property_index, *replacements.keys)
154+
propperties = declarations.keys
155+
property_index = propperties.index(replacing_property)
156+
property_with_higher_precidence =
157+
propperties[(property_index + 1)..].to_set
158+
replacement_declarations.each do |property, _value|
159+
if property_with_higher_precidence.member?(property)
160+
replacement_declarations.delete(property)
161+
else
162+
delete(property)
163+
end
164+
end
163165

164-
replacement_values.delete_at(property_index)
165-
replacement_values.insert(property_index, *replacements.values)
166+
new_declaration = []
167+
declarations.each do |property, value|
168+
if property == replacing_property
169+
replacement_declarations.each do |property, value|
170+
new_declaration << [property, value]
171+
end
172+
else
173+
new_declaration << [property, value]
174+
end
175+
end
166176

167-
self.declarations = replacement_keys.zip(replacement_values).to_h
177+
self.declarations = new_declaration.to_h
168178
end
169179

170180
def to_s(options = {})

test/rule_set/test_declarations.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,25 +311,37 @@ class RuleSetDeclarationsTest < Minitest::Test
311311
describe 'when prior declarations for the replacement declarations exist' do
312312
it 'replaces declarations when both are not important' do
313313
declarations = CssParser::RuleSet::Declarations.new(
314-
'bar1' => 'old_bar1_value', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
314+
'bar1' => 'old_bar1_value',
315+
'foo' => 'foo_value',
316+
'bar' => 'bar_value',
317+
'baz' => 'baz_value'
315318
)
316319

317320
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
318321
expected = CssParser::RuleSet::Declarations.new(
319-
'bar1' => 'bar1_value', 'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
322+
'foo' => 'foo_value',
323+
'bar1' => 'bar1_value',
324+
'bar2' => 'bar2_value',
325+
'baz' => 'baz_value'
320326
)
321327

322328
assert_equal expected, declarations
323329
end
324330

325331
it 'replaces declarations when both are important' do
326332
declarations = CssParser::RuleSet::Declarations.new(
327-
'bar1' => 'old_bar1_value !important', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
333+
'bar1' => 'old_bar1_value !important',
334+
'foo' => 'foo_value',
335+
'bar' => 'bar_value',
336+
'baz' => 'baz_value'
328337
)
329338

330339
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value'})
331340
expected = CssParser::RuleSet::Declarations.new(
332-
'bar1' => 'bar1_value !important', 'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
341+
'foo' => 'foo_value',
342+
'bar1' => 'bar1_value !important',
343+
'bar2' => 'bar2_value',
344+
'baz' => 'baz_value'
333345
)
334346

335347
assert_equal expected, declarations

0 commit comments

Comments
 (0)