css_parser 3.3.0 → 3.4.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/css_parser/regexps.rb +3 -0
- data/lib/css_parser/rule_set.rb +51 -40
- data/lib/css_parser/version.rb +1 -1
- data/lib/css_parser.rb +7 -4
- 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: 3ff7ead55436d394e644feb148e017f631ea4ede0783746107a72cc790a70ee5
|
|
4
|
+
data.tar.gz: 8bdd909c65c7f7fe4139d164898e6cd7906b1b649b6a68429dee620cc5798cd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ecf5d9f9ef55e3f69269c511b51f5363bdc6e317fa1446ae04be7850256c30039dd9719c74705df29dbb9011847d3cbe3602bbd11f8b273cd7b6c92b9e2268cb
|
|
7
|
+
data.tar.gz: 7ddbd0f9bab2c8327f081a48cce1e7fb20cc133eb5af8530eb1dcc6ef1acc2836f9a2274e75e74dec6b9866461754ceecc759efbbd7aec7dbe6b06814bd2cbc9
|
data/lib/css_parser/regexps.rb
CHANGED
|
@@ -35,6 +35,9 @@ module CssParser
|
|
|
35
35
|
#++
|
|
36
36
|
IMPORTANT_IN_PROPERTY_RX = /\s*!important\b\s*/i.freeze
|
|
37
37
|
|
|
38
|
+
# What sanitize_media_query would change: blank, padded, or not single-space separated
|
|
39
|
+
UNSANITIZED_MEDIA_QUERY_RX = /\A\z|\A[\s\0]|[\s\0]\z|\s\s|[^\S ]/.freeze
|
|
40
|
+
|
|
38
41
|
RE_INSIDE_OUTSIDE = regex_possible_values 'inside', 'outside'
|
|
39
42
|
RE_SCROLL_FIXED = regex_possible_values 'scroll', 'fixed'
|
|
40
43
|
RE_REPEAT = regex_possible_values 'repeat(\-x|\-y)*|no\-repeat'
|
data/lib/css_parser/rule_set.rb
CHANGED
|
@@ -18,6 +18,11 @@ module CssParser
|
|
|
18
18
|
|
|
19
19
|
NUMBER_OF_DIMENSIONS = 4
|
|
20
20
|
|
|
21
|
+
NORMALIZED_PROPERTY_RX = /\A[-a-z0-9_]+\z/.freeze
|
|
22
|
+
BLANK_VALUE_RX = /\A[\s\0]*\z/.freeze
|
|
23
|
+
SURROUNDING_WHITESPACE_RX = /\A[\s\0]|[\s\0]\z/.freeze
|
|
24
|
+
TRAILING_SEMICOLON_RX = /\s*;\s*\Z/.freeze
|
|
25
|
+
|
|
21
26
|
DIMENSIONS = [
|
|
22
27
|
['margin', %w[margin-top margin-right margin-bottom margin-left]],
|
|
23
28
|
['padding', %w[padding-top padding-right padding-bottom padding-left]],
|
|
@@ -35,6 +40,8 @@ module CssParser
|
|
|
35
40
|
RPAREN = ')'.freeze
|
|
36
41
|
IMPORTANT = '!important'.freeze
|
|
37
42
|
class Declarations
|
|
43
|
+
EMPTY_DECLARATIONS = {}.freeze
|
|
44
|
+
|
|
38
45
|
class Value
|
|
39
46
|
attr_reader :value
|
|
40
47
|
attr_accessor :important
|
|
@@ -45,12 +52,17 @@ module CssParser
|
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def value=(value)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
value.
|
|
55
|
+
# sub and strip always return a copy, and the caller's string must not be mutated:
|
|
56
|
+
# only copy when there is something to remove, so a clean value allocates nothing
|
|
57
|
+
value = value.to_s
|
|
58
|
+
value = value.sub(TRAILING_SEMICOLON_RX, '') if value.match?(TRAILING_SEMICOLON_RX)
|
|
59
|
+
self.important = value.match?(CssParser::IMPORTANT_IN_PROPERTY_RX)
|
|
60
|
+
value = value.sub(CssParser::IMPORTANT_IN_PROPERTY_RX, '') if important
|
|
61
|
+
value = value.strip if value.match?(SURROUNDING_WHITESPACE_RX)
|
|
51
62
|
raise ArgumentError, 'value is empty' if value.empty?
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
# value may still be the caller's string: -value freezes a copy instead of it
|
|
65
|
+
@value = -value
|
|
54
66
|
end
|
|
55
67
|
|
|
56
68
|
def to_s
|
|
@@ -68,7 +80,7 @@ module CssParser
|
|
|
68
80
|
|
|
69
81
|
def_delegators :declarations, :each, :each_value
|
|
70
82
|
|
|
71
|
-
def initialize(declarations =
|
|
83
|
+
def initialize(declarations = EMPTY_DECLARATIONS)
|
|
72
84
|
self.declarations = {}
|
|
73
85
|
declarations.each { |property, value| add_declaration!(property, value) }
|
|
74
86
|
end
|
|
@@ -98,7 +110,7 @@ module CssParser
|
|
|
98
110
|
|
|
99
111
|
if value.is_a?(Value) && (!currently_important || value.important)
|
|
100
112
|
declarations[property] = value
|
|
101
|
-
elsif value.to_s.
|
|
113
|
+
elsif value.to_s.match?(BLANK_VALUE_RX)
|
|
102
114
|
delete property
|
|
103
115
|
else
|
|
104
116
|
value = Value.new(value)
|
|
@@ -162,18 +174,19 @@ module CssParser
|
|
|
162
174
|
|
|
163
175
|
# We should preserve subsequent declarations of the same properties
|
|
164
176
|
# and prior important ones if replacement one is not important
|
|
165
|
-
replacements =
|
|
177
|
+
replacements = {}
|
|
178
|
+
replacement_declarations.declarations.each do |key, replacement|
|
|
166
179
|
existing = declarations[key]
|
|
167
180
|
|
|
168
181
|
# No existing -> set
|
|
169
182
|
unless existing
|
|
170
|
-
|
|
183
|
+
replacements[key] = replacement
|
|
171
184
|
next
|
|
172
185
|
end
|
|
173
186
|
|
|
174
187
|
# Replacement more important than existing -> replace
|
|
175
188
|
if replacement.important && !existing.important
|
|
176
|
-
|
|
189
|
+
replacements[key] = replacement
|
|
177
190
|
replaced_index = replacement_keys.index(key)
|
|
178
191
|
replacement_keys.delete_at(replaced_index)
|
|
179
192
|
replacement_values.delete_at(replaced_index)
|
|
@@ -186,7 +199,7 @@ module CssParser
|
|
|
186
199
|
|
|
187
200
|
# Existing and replacement importance are the same,
|
|
188
201
|
# value which is declared later wins
|
|
189
|
-
|
|
202
|
+
replacements[key] = replacement if property_index > replacement_keys.index(key)
|
|
190
203
|
end
|
|
191
204
|
|
|
192
205
|
return if replacements.empty?
|
|
@@ -197,16 +210,17 @@ module CssParser
|
|
|
197
210
|
replacement_values.delete_at(property_index)
|
|
198
211
|
replacement_values.insert(property_index, *replacements.values)
|
|
199
212
|
|
|
200
|
-
self.declarations =
|
|
213
|
+
self.declarations = {}
|
|
214
|
+
replacement_keys.each_with_index { |key, index| declarations[key] = replacement_values[index] }
|
|
201
215
|
end
|
|
202
216
|
|
|
203
217
|
def to_s(options = {})
|
|
204
|
-
str =
|
|
218
|
+
str = +''
|
|
219
|
+
declarations.each do |prop, value|
|
|
205
220
|
importance = options[:force_important] || value.important ? ' !important' : ''
|
|
206
|
-
|
|
221
|
+
str << prop << ': ' << value.value << importance << '; '
|
|
207
222
|
end
|
|
208
|
-
|
|
209
|
-
str.gsub!(/^[\s^({)]+|[\n\r\f\t]*|\s+$/mx, '')
|
|
223
|
+
str.gsub!(/^[\s^({)]+|[\n\r\f\t]+/, '')
|
|
210
224
|
str.strip!
|
|
211
225
|
str
|
|
212
226
|
end
|
|
@@ -226,7 +240,10 @@ module CssParser
|
|
|
226
240
|
attr_writer :declarations
|
|
227
241
|
|
|
228
242
|
def normalize_property(property)
|
|
229
|
-
property = property.to_s
|
|
243
|
+
property = property.to_s
|
|
244
|
+
return property if property.match?(NORMALIZED_PROPERTY_RX)
|
|
245
|
+
|
|
246
|
+
property = property.downcase
|
|
230
247
|
property.strip!
|
|
231
248
|
property
|
|
232
249
|
end
|
|
@@ -399,14 +416,14 @@ module CssParser
|
|
|
399
416
|
DIMENSIONS.each do |property, (top, right, bottom, left)|
|
|
400
417
|
next unless (declaration = declarations[property])
|
|
401
418
|
|
|
402
|
-
value = declaration.value
|
|
419
|
+
value = declaration.value
|
|
403
420
|
|
|
404
421
|
# RGB and HSL values in borders are the only units that can have spaces (within params).
|
|
405
422
|
# We cheat a bit here by stripping spaces after commas in RGB and HSL values so that we
|
|
406
423
|
# can split easily on spaces.
|
|
407
424
|
#
|
|
408
425
|
# TODO: rgba, hsl, hsla
|
|
409
|
-
value.gsub
|
|
426
|
+
value = value.gsub(RE_COLOUR) { |c| c.gsub(/(\s*,\s*)/, ',') } if value.include?(',')
|
|
410
427
|
|
|
411
428
|
matches = split_value_preserving_function_whitespace(value)
|
|
412
429
|
|
|
@@ -424,7 +441,7 @@ module CssParser
|
|
|
424
441
|
raise ArgumentError, "Cannot parse #{value}"
|
|
425
442
|
end
|
|
426
443
|
|
|
427
|
-
replacement = [
|
|
444
|
+
replacement = {top => values[0], right => values[1], bottom => values[2], left => values[3]}
|
|
428
445
|
|
|
429
446
|
declarations.replace_declaration!(property, replacement, preserve_importance: true)
|
|
430
447
|
end
|
|
@@ -518,17 +535,13 @@ module CssParser
|
|
|
518
535
|
|
|
519
536
|
# Combine several properties into a shorthand one
|
|
520
537
|
def create_shorthand_properties!(properties, shorthand_property) # :nodoc:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
properties.each do |property|
|
|
524
|
-
next unless (declaration = declarations[property])
|
|
525
|
-
next if declaration.important
|
|
526
|
-
|
|
527
|
-
values << declaration.value
|
|
528
|
-
properties_to_delete << property
|
|
538
|
+
properties_to_delete = properties.select do |property|
|
|
539
|
+
(declaration = declarations[property]) && !declaration.important
|
|
529
540
|
end
|
|
530
541
|
|
|
531
|
-
return if
|
|
542
|
+
return if properties_to_delete.length <= 1
|
|
543
|
+
|
|
544
|
+
values = properties_to_delete.map { |property| declarations[property].value }
|
|
532
545
|
|
|
533
546
|
properties_to_delete.each do |property|
|
|
534
547
|
declarations.delete(property)
|
|
@@ -584,14 +597,11 @@ module CssParser
|
|
|
584
597
|
return if declarations.size < NUMBER_OF_DIMENSIONS
|
|
585
598
|
|
|
586
599
|
DIMENSIONS.each do |property, dimensions|
|
|
587
|
-
values = DIMENSION_DIRECTIONS.each_with_index.with_object({}) do |(side, index), result|
|
|
588
|
-
next unless (declaration = declarations[dimensions[index]])
|
|
589
|
-
|
|
590
|
-
result[side] = declaration.value
|
|
591
|
-
end
|
|
592
|
-
|
|
593
600
|
# All four dimensions must be present
|
|
594
|
-
next
|
|
601
|
+
next unless dimensions.all? { |dimension| declarations.key?(dimension) }
|
|
602
|
+
|
|
603
|
+
top, right, bottom, left = dimensions.map { |dimension| declarations[dimension].value }
|
|
604
|
+
values = {top: top, right: right, bottom: bottom, left: left}
|
|
595
605
|
|
|
596
606
|
new_value = values.values_at(*compute_dimensions_shorthand(values)).join(' ').strip
|
|
597
607
|
declarations[property] = new_value unless new_value.empty?
|
|
@@ -641,7 +651,7 @@ module CssParser
|
|
|
641
651
|
|
|
642
652
|
def compute_dimensions_shorthand(values)
|
|
643
653
|
# All four sides are equal, returning single value
|
|
644
|
-
return [:top] if values
|
|
654
|
+
return [:top] if values[:top] == values[:right] && values[:right] == values[:bottom] && values[:bottom] == values[:left]
|
|
645
655
|
|
|
646
656
|
# `/* top | right | bottom | left */`
|
|
647
657
|
return DIMENSION_DIRECTIONS if values[:left] != values[:right]
|
|
@@ -670,12 +680,13 @@ module CssParser
|
|
|
670
680
|
next unless (colon = decs.index(COLON))
|
|
671
681
|
|
|
672
682
|
property = decs[0, colon]
|
|
673
|
-
value = decs[
|
|
683
|
+
value = decs[colon + 1, decs.length]
|
|
674
684
|
property.strip!
|
|
675
685
|
value.strip!
|
|
676
|
-
|
|
686
|
+
# casecmp? case-folds both strings, allocating two of them per declaration
|
|
687
|
+
next if property.empty? || value.empty? || value.casecmp(IMPORTANT).zero?
|
|
677
688
|
|
|
678
|
-
add_declaration!(property, value)
|
|
689
|
+
add_declaration!(property, value.freeze)
|
|
679
690
|
continuation = nil
|
|
680
691
|
end
|
|
681
692
|
end
|
|
@@ -688,7 +699,7 @@ module CssParser
|
|
|
688
699
|
# TODO: way too simplistic
|
|
689
700
|
#++
|
|
690
701
|
def parse_selectors!(selectors) # :nodoc:
|
|
691
|
-
@selectors = selectors.split(',').map do |s|
|
|
702
|
+
@selectors = selectors.split(',').map! do |s|
|
|
692
703
|
s.gsub!(/\s+/, ' ')
|
|
693
704
|
s.strip!
|
|
694
705
|
s
|
data/lib/css_parser/version.rb
CHANGED
data/lib/css_parser.rb
CHANGED
|
@@ -85,9 +85,10 @@ module CssParser
|
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
merged =
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
merged = RuleSet.new(nil, nil)
|
|
89
|
+
properties.each do |property, details|
|
|
90
|
+
value = details[:value]
|
|
91
|
+
merged[property] = details[:is_important] ? "#{value.gsub(/;\Z/, '')}!important" : value
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
merged.create_shorthand!
|
|
@@ -107,7 +108,7 @@ module CssParser
|
|
|
107
108
|
#++
|
|
108
109
|
def self.calculate_specificity(selector)
|
|
109
110
|
a = 0
|
|
110
|
-
b = selector.
|
|
111
|
+
b = selector.count('#')
|
|
111
112
|
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
|
|
112
113
|
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
|
|
113
114
|
|
|
@@ -147,6 +148,8 @@ module CssParser
|
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
def self.sanitize_media_query(raw)
|
|
151
|
+
return raw if raw.is_a?(Symbol) && !raw.match?(UNSANITIZED_MEDIA_QUERY_RX)
|
|
152
|
+
|
|
150
153
|
mq = raw.to_s.gsub(/\s+/, ' ')
|
|
151
154
|
mq.strip!
|
|
152
155
|
mq = 'all' if mq.empty?
|