css_parser 1.5.0.pre2 → 1.8.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 +5 -5
- data/lib/css_parser.rb +28 -36
- data/lib/css_parser/parser.rb +119 -118
- data/lib/css_parser/regexps.rb +38 -35
- data/lib/css_parser/rule_set.rb +373 -259
- data/lib/css_parser/version.rb +3 -1
- metadata +122 -8
data/lib/css_parser/regexps.rb
CHANGED
@@ -1,65 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CssParser
|
2
|
-
def self.regex_possible_values
|
4
|
+
def self.regex_possible_values(*values)
|
3
5
|
Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i')
|
4
6
|
end
|
5
7
|
|
6
8
|
# :stopdoc:
|
7
9
|
# Base types
|
8
10
|
RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
|
9
|
-
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE, 'n')
|
11
|
+
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE, 'n') # [^\0-\177]
|
10
12
|
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
|
11
13
|
RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
|
12
14
|
RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE, 'n')
|
13
15
|
|
14
16
|
# General strings
|
15
|
-
RE_STRING1 =
|
16
|
-
RE_STRING2 =
|
17
|
+
RE_STRING1 = /("(.[^\n\r\f"]*|\\#{RE_NL}|#{RE_ESCAPE})*")/.freeze
|
18
|
+
RE_STRING2 = /('(.[^\n\r\f']*|\\#{RE_NL}|#{RE_ESCAPE})*')/.freeze
|
17
19
|
RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
|
18
20
|
|
19
21
|
RE_INHERIT = regex_possible_values 'inherit'
|
20
22
|
|
21
|
-
RE_URI =
|
22
|
-
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
|
23
|
-
RE_GRADIENT = /[-a-z]*gradient\([-a-z0-9 .,#%()]*\)/im
|
23
|
+
RE_URI = /(url\(\s*(\s*#{RE_STRING}\s*)\s*\))|(url\(\s*([!#$%&*\-~]|#{RE_NON_ASCII}|#{RE_ESCAPE})*\s*)\)/ixm.freeze
|
24
|
+
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im.freeze
|
25
|
+
RE_GRADIENT = /[-a-z]*gradient\([-a-z0-9 .,#%()]*\)/im.freeze
|
24
26
|
|
25
27
|
# Initial parsing
|
26
|
-
RE_AT_IMPORT_RULE =
|
28
|
+
RE_AT_IMPORT_RULE = /@import\s+(url\()?["']?(.[^'"\s]*)["']?\)?([\w\s,^\])]*)\)?;?/.freeze
|
27
29
|
|
28
30
|
#--
|
29
|
-
#RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
|
31
|
+
# RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
|
30
32
|
|
31
|
-
#RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
|
33
|
+
# RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
|
32
34
|
#++
|
33
|
-
IMPORTANT_IN_PROPERTY_RX =
|
35
|
+
IMPORTANT_IN_PROPERTY_RX = /\s*!important\b\s*/i.freeze
|
34
36
|
|
35
37
|
RE_INSIDE_OUTSIDE = regex_possible_values 'inside', 'outside'
|
36
38
|
RE_SCROLL_FIXED = regex_possible_values 'scroll', 'fixed'
|
37
39
|
RE_REPEAT = regex_possible_values 'repeat(\-x|\-y)*|no\-repeat'
|
38
|
-
RE_LIST_STYLE_TYPE = regex_possible_values
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
RE_LIST_STYLE_TYPE = regex_possible_values(
|
41
|
+
'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', 'lower-roman',
|
42
|
+
'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha',
|
43
|
+
'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana',
|
44
|
+
'hira-gana-iroha', 'katakana-iroha', 'katakana', 'none'
|
45
|
+
)
|
42
46
|
|
43
|
-
STRIP_CSS_COMMENTS_RX =
|
44
|
-
STRIP_HTML_COMMENTS_RX =
|
47
|
+
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
48
|
+
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
45
49
|
|
46
50
|
# Special units
|
47
|
-
BOX_MODEL_UNITS_RX = /(auto|inherit|0|(
|
51
|
+
BOX_MODEL_UNITS_RX = /(auto|inherit|0|(-*([0-9]+|[0-9]*\.[0-9]+)(rem|vw|vh|vm|vmin|vmax|e[mx]+|px|[cm]+m|p[tc+]|in|%)))([\s;]|\Z)/imx.freeze
|
48
52
|
RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
|
49
53
|
RE_BACKGROUND_POSITION = Regexp.new("((((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)[\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
|
50
54
|
RE_BACKGROUND_SIZE = Regexp.new("\\s*/\\s*((((#{RE_LENGTH_OR_PERCENTAGE})|auto|cover|contain|initial|inherit)[\\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
|
51
|
-
FONT_UNITS_RX = /((
|
52
|
-
RE_BORDER_STYLE = /(
|
55
|
+
FONT_UNITS_RX = /((x+-)*small|medium|larger*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|%)*)/i.freeze
|
56
|
+
RE_BORDER_STYLE = /(\s*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)(\s*$)?/imx.freeze
|
53
57
|
RE_BORDER_UNITS = Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
|
54
58
|
|
55
|
-
|
56
59
|
# Patterns for specificity calculations
|
57
|
-
|
58
|
-
(
|
60
|
+
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC = /
|
61
|
+
(?:\.\w+) # classes
|
59
62
|
|
|
60
|
-
\[(
|
63
|
+
\[(?:\w+) # attributes
|
61
64
|
|
|
62
|
-
(
|
65
|
+
(?::(?: # pseudo classes
|
63
66
|
link|visited|active
|
64
67
|
|hover|focus
|
65
68
|
|lang
|
@@ -71,16 +74,16 @@ module CssParser
|
|
71
74
|
|only-child|only-of-type
|
72
75
|
|empty|contains
|
73
76
|
))
|
74
|
-
/ix
|
75
|
-
|
76
|
-
((
|
77
|
+
/ix.freeze
|
78
|
+
ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC = /
|
79
|
+
(?:(?:^|[\s+>~]+)\w+ # elements
|
77
80
|
|
|
78
|
-
|
81
|
+
:{1,2}(?: # pseudo-elements
|
79
82
|
after|before
|
80
83
|
|first-letter|first-line
|
81
84
|
|selection
|
82
85
|
)
|
83
|
-
)/ix
|
86
|
+
)/ix.freeze
|
84
87
|
|
85
88
|
# Colours
|
86
89
|
NAMED_COLOURS = %w[
|
@@ -235,11 +238,11 @@ module CssParser
|
|
235
238
|
transparent
|
236
239
|
inherit
|
237
240
|
currentColor
|
238
|
-
]
|
239
|
-
RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i
|
240
|
-
RE_COLOUR_NUMERIC_ALPHA = /\b(hsla|rgba)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i
|
241
|
-
RE_COLOUR_HEX = /\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b
|
242
|
-
RE_COLOUR_NAMED = /\s*\b(#{NAMED_COLOURS.join('|')})\b/i
|
241
|
+
].freeze
|
242
|
+
RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
|
243
|
+
RE_COLOUR_NUMERIC_ALPHA = /\b(hsla|rgba)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
|
244
|
+
RE_COLOUR_HEX = /\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/.freeze
|
245
|
+
RE_COLOUR_NAMED = /\s*\b(#{NAMED_COLOURS.join('|')})\b/i.freeze
|
243
246
|
RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
|
244
247
|
# :startdoc:
|
245
248
|
end
|
data/lib/css_parser/rule_set.rb
CHANGED
@@ -1,77 +1,255 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
1
5
|
module CssParser
|
2
6
|
class RuleSet
|
3
7
|
# Patterns for specificity calculations
|
4
|
-
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s
|
5
|
-
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(
|
8
|
+
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s+>]+)\w+|:(first-line|first-letter|before|after))/i.freeze
|
9
|
+
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.\w+)|(\[\w+)|(:(link|first-child|lang))/i.freeze
|
10
|
+
|
11
|
+
BACKGROUND_PROPERTIES = ['background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-attachment'].freeze
|
12
|
+
LIST_STYLE_PROPERTIES = ['list-style-type', 'list-style-position', 'list-style-image'].freeze
|
13
|
+
FONT_STYLE_PROPERTIES = ['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family'].freeze
|
14
|
+
BORDER_STYLE_PROPERTIES = ['border-width', 'border-style', 'border-color'].freeze
|
15
|
+
BORDER_PROPERTIES = ['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].freeze
|
16
|
+
|
17
|
+
NUMBER_OF_DIMENSIONS = 4
|
18
|
+
|
19
|
+
DIMENSIONS = [
|
20
|
+
['margin', %w[margin-top margin-right margin-bottom margin-left]],
|
21
|
+
['padding', %w[padding-top padding-right padding-bottom padding-left]],
|
22
|
+
['border-color', %w[border-top-color border-right-color border-bottom-color border-left-color]],
|
23
|
+
['border-style', %w[border-top-style border-right-style border-bottom-style border-left-style]],
|
24
|
+
['border-width', %w[border-top-width border-right-width border-bottom-width border-left-width]]
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
class Declarations
|
28
|
+
class Value
|
29
|
+
attr_reader :value
|
30
|
+
attr_accessor :important
|
31
|
+
|
32
|
+
def initialize(value, important: nil)
|
33
|
+
self.value = value
|
34
|
+
@important = important unless important.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def value=(value)
|
38
|
+
value = value.to_s.sub(/\s*;\s*\Z/, '')
|
39
|
+
self.important = !value.slice!(CssParser::IMPORTANT_IN_PROPERTY_RX).nil?
|
40
|
+
value.strip!
|
41
|
+
raise ArgumentError, 'value is empty' if value.empty?
|
42
|
+
|
43
|
+
@value = value.freeze
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
return value unless important
|
48
|
+
|
49
|
+
"#{value} !important"
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
return false unless other.is_a?(self.class)
|
54
|
+
|
55
|
+
value == other.value && important == other.important
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
extend Forwardable
|
6
60
|
|
7
|
-
|
8
|
-
|
61
|
+
def_delegators :declarations, :each
|
62
|
+
|
63
|
+
def initialize(declarations = {})
|
64
|
+
self.declarations = {}
|
65
|
+
declarations.each { |property, value| add_declaration!(property, value) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Add a CSS declaration
|
69
|
+
# @param [#to_s] property that should be added
|
70
|
+
# @param [Value, #to_s] value of the property
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# declarations['color'] = 'blue'
|
74
|
+
#
|
75
|
+
# puts declarations['color']
|
76
|
+
# => #<CssParser::RuleSet::Declarations::Value:0x000000000305c730 @important=false, @order=1, @value="blue">
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# declarations['margin'] = '0px auto !important'
|
80
|
+
#
|
81
|
+
# puts declarations['margin']
|
82
|
+
# => #<CssParser::RuleSet::Declarations::Value:0x00000000030c1838 @important=true, @order=2, @value="0px auto">
|
83
|
+
#
|
84
|
+
# If the property already exists its value will be over-written.
|
85
|
+
# If the value is empty - property will be deleted
|
86
|
+
def []=(property, value)
|
87
|
+
property = normalize_property(property)
|
88
|
+
|
89
|
+
if value.is_a?(Value)
|
90
|
+
declarations[property] = value
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
if value.to_s.strip.empty?
|
95
|
+
delete(property)
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
declarations[property] = Value.new(value)
|
100
|
+
end
|
101
|
+
alias add_declaration! []=
|
102
|
+
|
103
|
+
def [](property)
|
104
|
+
declarations[normalize_property(property)]
|
105
|
+
end
|
106
|
+
alias get_value []
|
107
|
+
|
108
|
+
def key?(property)
|
109
|
+
declarations.key?(normalize_property(property))
|
110
|
+
end
|
111
|
+
|
112
|
+
def size
|
113
|
+
declarations.size
|
114
|
+
end
|
115
|
+
|
116
|
+
# Remove CSS declaration
|
117
|
+
# @param [#to_s] property property to be removed
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# declarations.delete('color')
|
121
|
+
def delete(property)
|
122
|
+
declarations.delete(normalize_property(property))
|
123
|
+
end
|
124
|
+
alias remove_declaration! delete
|
125
|
+
|
126
|
+
# Replace CSS property with multiple declarations
|
127
|
+
# @param [#to_s] property property name to be replaces
|
128
|
+
# @param [Hash<String => [String, Value]>] replacements hash with properties to replace with
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# declarations = Declarations.new('line-height' => '0.25px', 'font' => 'small-caps', 'font-size' => '12em')
|
132
|
+
# declarations.replace_declaration!('font', {'line-height' => '1px', 'font-variant' => 'small-caps', 'font-size' => '24px'})
|
133
|
+
# declarations
|
134
|
+
# => #<CssParser::RuleSet::Declarations:0x00000000029c3018
|
135
|
+
# @declarations=
|
136
|
+
# {"line-height"=>#<CssParser::RuleSet::Declarations::Value:0x00000000038ac458 @important=false, @value="1px">,
|
137
|
+
# "font-variant"=>#<CssParser::RuleSet::Declarations::Value:0x00000000039b3ec8 @important=false, @value="small-caps">,
|
138
|
+
# "font-size"=>#<CssParser::RuleSet::Declarations::Value:0x00000000029c2c80 @important=false, @value="12em">}>
|
139
|
+
def replace_declaration!(property, replacements, preserve_importance: false)
|
140
|
+
property = normalize_property(property)
|
141
|
+
raise ArgumentError, "property #{property} does not exist" unless key?(property)
|
142
|
+
|
143
|
+
replacement_declarations = self.class.new(replacements)
|
144
|
+
|
145
|
+
if preserve_importance
|
146
|
+
importance = get_value(property).important
|
147
|
+
replacement_declarations.each { |_key, value| value.important = importance }
|
148
|
+
end
|
149
|
+
|
150
|
+
replacement_keys = declarations.keys
|
151
|
+
replacement_values = declarations.values
|
152
|
+
property_index = replacement_keys.index(property)
|
153
|
+
|
154
|
+
# We should preserve subsequent declarations of the same properties
|
155
|
+
# and prior important ones if replacement one is not important
|
156
|
+
replacements = replacement_declarations.each.with_object({}) do |(key, value), result|
|
157
|
+
# Replacement property doesn't exist, adding
|
158
|
+
next result[key] = value unless declarations.key?(key)
|
159
|
+
|
160
|
+
# Replacement property is important while existing one is not,
|
161
|
+
# replacing unconditionally
|
162
|
+
if value.important && !declarations[key].important
|
163
|
+
result[key] = value
|
164
|
+
replaced_index = replacement_keys.index(key)
|
165
|
+
replacement_keys.delete_at(replaced_index)
|
166
|
+
replacement_values.delete_at(replaced_index)
|
167
|
+
property_index -= 1 if replaced_index < property_index
|
168
|
+
next
|
169
|
+
end
|
170
|
+
|
171
|
+
# Existing value is important while replacing is not, existing one
|
172
|
+
# takes precedence
|
173
|
+
next if !value.important && declarations[key].important
|
174
|
+
|
175
|
+
# Importance of existing and replacement values are the same,
|
176
|
+
# value which is declared later wins
|
177
|
+
result[key] = value if property_index > replacement_keys.index(key)
|
178
|
+
end
|
179
|
+
|
180
|
+
return if replacements.empty?
|
181
|
+
|
182
|
+
replacement_keys.delete_at(property_index)
|
183
|
+
replacement_keys.insert(property_index, *replacements.keys)
|
184
|
+
|
185
|
+
replacement_values.delete_at(property_index)
|
186
|
+
replacement_values.insert(property_index, *replacements.values)
|
187
|
+
|
188
|
+
self.declarations = replacement_keys.zip(replacement_values).to_h
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_s(options = {})
|
192
|
+
str = declarations.reduce(String.new) do |memo, (prop, value)|
|
193
|
+
importance = options[:force_important] || value.important ? ' !important' : ''
|
194
|
+
memo << "#{prop}: #{value.value}#{importance}; "
|
195
|
+
end
|
196
|
+
# TODO: Clean-up regexp doesn't seem to work
|
197
|
+
str.gsub!(/^[\s^({)]+|[\n\r\f\t]*|\s+$/mx, '')
|
198
|
+
str.strip!
|
199
|
+
str
|
200
|
+
end
|
201
|
+
|
202
|
+
def ==(other)
|
203
|
+
return false unless other.is_a?(self.class)
|
204
|
+
|
205
|
+
declarations == other.declarations && declarations.keys == other.declarations.keys
|
206
|
+
end
|
207
|
+
|
208
|
+
protected
|
209
|
+
|
210
|
+
attr_reader :declarations
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
attr_writer :declarations
|
215
|
+
|
216
|
+
def normalize_property(property)
|
217
|
+
property = property.to_s.downcase
|
218
|
+
property.strip!
|
219
|
+
property
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
extend Forwardable
|
9
224
|
|
10
225
|
# Array of selector strings.
|
11
|
-
attr_reader
|
226
|
+
attr_reader :selectors
|
12
227
|
|
13
228
|
# Integer with the specificity to use for this RuleSet.
|
14
|
-
attr_accessor
|
229
|
+
attr_accessor :specificity
|
230
|
+
|
231
|
+
# @!method add_declaration!
|
232
|
+
# @see CssParser::RuleSet::Declarations#add_declaration!
|
233
|
+
# @!method delete
|
234
|
+
# @see CssParser::RuleSet::Declarations#delete
|
235
|
+
def_delegators :declarations, :add_declaration!, :delete
|
236
|
+
alias []= add_declaration!
|
237
|
+
alias remove_declaration! delete
|
15
238
|
|
16
239
|
def initialize(selectors, block, specificity = nil)
|
17
240
|
@selectors = []
|
18
241
|
@specificity = specificity
|
19
|
-
@declarations = {}
|
20
|
-
@order = 0
|
21
242
|
parse_selectors!(selectors) if selectors
|
22
243
|
parse_declarations!(block)
|
23
244
|
end
|
24
245
|
|
25
246
|
# Get the value of a property
|
26
247
|
def get_value(property)
|
27
|
-
return '' unless
|
28
|
-
|
29
|
-
property = property.downcase.strip
|
30
|
-
properties = @declarations.inject('') do |val, (key, data)|
|
31
|
-
#puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
|
32
|
-
importance = data[:is_important] ? ' !important' : ''
|
33
|
-
val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
|
34
|
-
val
|
35
|
-
end
|
36
|
-
return properties ? properties.strip : ''
|
37
|
-
end
|
38
|
-
alias_method :[], :get_value
|
39
|
-
|
40
|
-
# Add a CSS declaration to the current RuleSet.
|
41
|
-
#
|
42
|
-
# rule_set.add_declaration!('color', 'blue')
|
43
|
-
#
|
44
|
-
# puts rule_set['color']
|
45
|
-
# => 'blue;'
|
46
|
-
#
|
47
|
-
# rule_set.add_declaration!('margin', '0px auto !important')
|
48
|
-
#
|
49
|
-
# puts rule_set['margin']
|
50
|
-
# => '0px auto !important;'
|
51
|
-
#
|
52
|
-
# If the property already exists its value will be over-written.
|
53
|
-
def add_declaration!(property, value)
|
54
|
-
if value.nil? or value.empty?
|
55
|
-
@declarations.delete(property)
|
56
|
-
return
|
57
|
-
end
|
248
|
+
return '' unless declarations.key?(property)
|
58
249
|
|
59
|
-
|
60
|
-
is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
|
61
|
-
property = property.downcase.strip
|
62
|
-
#puts "SAVING #{property} #{value} #{is_important.inspect}"
|
63
|
-
@declarations[property] = {
|
64
|
-
:value => value, :is_important => is_important, :order => @order += 1
|
65
|
-
}
|
66
|
-
end
|
67
|
-
alias_method :[]=, :add_declaration!
|
68
|
-
|
69
|
-
# Remove CSS declaration from the current RuleSet.
|
70
|
-
#
|
71
|
-
# rule_set.remove_declaration!('color')
|
72
|
-
def remove_declaration!(property)
|
73
|
-
@declarations.delete(property)
|
250
|
+
"#{declarations[property]};"
|
74
251
|
end
|
252
|
+
alias [] get_value
|
75
253
|
|
76
254
|
# Iterate through selectors.
|
77
255
|
#
|
@@ -83,41 +261,29 @@ module CssParser
|
|
83
261
|
# ...
|
84
262
|
# end
|
85
263
|
def each_selector(options = {}) # :yields: selector, declarations, specificity
|
86
|
-
|
264
|
+
decs = declarations.to_s(options)
|
87
265
|
if @specificity
|
88
|
-
@selectors.each { |sel| yield sel.strip,
|
266
|
+
@selectors.each { |sel| yield sel.strip, decs, @specificity }
|
89
267
|
else
|
90
|
-
@selectors.each { |sel| yield sel.strip,
|
268
|
+
@selectors.each { |sel| yield sel.strip, decs, CssParser.calculate_specificity(sel) }
|
91
269
|
end
|
92
270
|
end
|
93
271
|
|
94
272
|
# Iterate through declarations.
|
95
273
|
def each_declaration # :yields: property, value, is_important
|
96
|
-
|
97
|
-
|
98
|
-
value = data[:value]
|
99
|
-
yield property.downcase.strip, value.strip, data[:is_important]
|
274
|
+
declarations.each do |property_name, value|
|
275
|
+
yield property_name, value.value, value.important
|
100
276
|
end
|
101
277
|
end
|
102
278
|
|
103
279
|
# Return all declarations as a string.
|
104
|
-
#--
|
105
|
-
# TODO: Clean-up regexp doesn't seem to work
|
106
|
-
#++
|
107
280
|
def declarations_to_s(options = {})
|
108
|
-
|
109
|
-
str = ''
|
110
|
-
each_declaration do |prop, val, is_important|
|
111
|
-
importance = (options[:force_important] || is_important) ? ' !important' : ''
|
112
|
-
str += "#{prop}: #{val}#{importance}; "
|
113
|
-
end
|
114
|
-
str.gsub(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
|
281
|
+
declarations.to_s(options)
|
115
282
|
end
|
116
283
|
|
117
284
|
# Return the CSS rule set as a string.
|
118
285
|
def to_s
|
119
|
-
|
120
|
-
"#{@selectors.join(',')} { #{decs} }"
|
286
|
+
"#{@selectors.join(',')} { #{declarations} }"
|
121
287
|
end
|
122
288
|
|
123
289
|
# Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
|
@@ -135,140 +301,126 @@ module CssParser
|
|
135
301
|
#
|
136
302
|
# See http://www.w3.org/TR/CSS21/colors.html#propdef-background
|
137
303
|
def expand_background_shorthand! # :nodoc:
|
138
|
-
return unless
|
139
|
-
|
140
|
-
value =
|
141
|
-
|
142
|
-
if value
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
split_declaration('background', 'background-color', value.slice!(CssParser::RE_COLOUR))
|
152
|
-
split_declaration('background', 'background-size', extract_background_size_from(value))
|
153
|
-
split_declaration('background', 'background-position', value.slice(CssParser::RE_BACKGROUND_POSITION))
|
304
|
+
return unless declarations.key?('background')
|
305
|
+
|
306
|
+
value = declarations['background'].value.dup
|
307
|
+
|
308
|
+
replacement = BACKGROUND_PROPERTIES.map { |key| [key, 'inherit'] }.to_h if value.match(CssParser::RE_INHERIT)
|
309
|
+
replacement ||= {
|
310
|
+
'background-image' => value.slice!(Regexp.union(CssParser::URI_RX, CssParser::RE_GRADIENT, /none/i)),
|
311
|
+
'background-attachment' => value.slice!(CssParser::RE_SCROLL_FIXED),
|
312
|
+
'background-repeat' => value.slice!(CssParser::RE_REPEAT),
|
313
|
+
'background-color' => value.slice!(CssParser::RE_COLOUR),
|
314
|
+
'background-size' => extract_background_size_from(value),
|
315
|
+
'background-position' => value.slice!(CssParser::RE_BACKGROUND_POSITION)
|
316
|
+
}
|
154
317
|
|
155
|
-
|
318
|
+
declarations.replace_declaration!('background', replacement, preserve_importance: true)
|
156
319
|
end
|
157
320
|
|
158
321
|
def extract_background_size_from(value)
|
159
322
|
size = value.slice!(CssParser::RE_BACKGROUND_SIZE)
|
160
323
|
|
161
|
-
size.sub(
|
324
|
+
size.sub(%r{^\s*/\s*}, '') if size
|
162
325
|
end
|
163
326
|
|
164
327
|
# Split shorthand border declarations (e.g. <tt>border: 1px red;</tt>)
|
165
328
|
# Additional splitting happens in expand_dimensions_shorthand!
|
166
329
|
def expand_border_shorthand! # :nodoc:
|
167
|
-
|
168
|
-
next unless
|
330
|
+
BORDER_PROPERTIES.each do |k|
|
331
|
+
next unless declarations.key?(k)
|
169
332
|
|
170
|
-
value =
|
333
|
+
value = declarations[k].value.dup
|
171
334
|
|
172
|
-
|
173
|
-
|
174
|
-
|
335
|
+
replacement = {
|
336
|
+
"#{k}-width" => value.slice!(CssParser::RE_BORDER_UNITS),
|
337
|
+
"#{k}-color" => value.slice!(CssParser::RE_COLOUR),
|
338
|
+
"#{k}-style" => value.slice!(CssParser::RE_BORDER_STYLE)
|
339
|
+
}
|
175
340
|
|
176
|
-
|
341
|
+
declarations.replace_declaration!(k, replacement, preserve_importance: true)
|
177
342
|
end
|
178
343
|
end
|
179
344
|
|
180
345
|
# Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
|
181
346
|
# into their constituent parts. Handles margin, padding, border-color, border-style and border-width.
|
182
347
|
def expand_dimensions_shorthand! # :nodoc:
|
183
|
-
|
184
|
-
|
185
|
-
'border-color' => 'border-%s-color',
|
186
|
-
'border-style' => 'border-%s-style',
|
187
|
-
'border-width' => 'border-%s-width'}.each do |property, expanded|
|
348
|
+
DIMENSIONS.each do |property, (top, right, bottom, left)|
|
349
|
+
next unless declarations.key?(property)
|
188
350
|
|
189
|
-
|
190
|
-
|
191
|
-
value = @declarations[property][:value]
|
351
|
+
value = declarations[property].value.dup
|
192
352
|
|
193
353
|
# RGB and HSL values in borders are the only units that can have spaces (within params).
|
194
354
|
# We cheat a bit here by stripping spaces after commas in RGB and HSL values so that we
|
195
355
|
# can split easily on spaces.
|
196
356
|
#
|
197
357
|
# TODO: rgba, hsl, hsla
|
198
|
-
value.gsub!(RE_COLOUR) { |c| c.gsub(/(\s
|
358
|
+
value.gsub!(RE_COLOUR) { |c| c.gsub(/(\s*,\s*)/, ',') }
|
199
359
|
|
200
360
|
matches = value.strip.split(/\s+/)
|
201
361
|
|
202
|
-
t, r, b, l = nil
|
203
|
-
|
204
362
|
case matches.length
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
when 4
|
215
|
-
t = matches[0]
|
216
|
-
r = matches[1]
|
217
|
-
b = matches[2]
|
218
|
-
l = matches[3]
|
363
|
+
when 1
|
364
|
+
values = matches.to_a * 4
|
365
|
+
when 2
|
366
|
+
values = matches.to_a * 2
|
367
|
+
when 3
|
368
|
+
values = matches.to_a
|
369
|
+
values << matches[1] # left = right
|
370
|
+
when 4
|
371
|
+
values = matches.to_a
|
219
372
|
end
|
220
373
|
|
221
|
-
|
222
|
-
split_declaration(property, expanded % 'right', r)
|
223
|
-
split_declaration(property, expanded % 'bottom', b)
|
224
|
-
split_declaration(property, expanded % 'left', l)
|
374
|
+
t, r, b, l = values
|
225
375
|
|
226
|
-
|
376
|
+
declarations.replace_declaration!(
|
377
|
+
property,
|
378
|
+
{top => t, right => r, bottom => b, left => l},
|
379
|
+
preserve_importance: true
|
380
|
+
)
|
227
381
|
end
|
228
382
|
end
|
229
383
|
|
230
384
|
# Convert shorthand font declarations (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
|
231
385
|
# into their constituent parts.
|
232
386
|
def expand_font_shorthand! # :nodoc:
|
233
|
-
return unless
|
387
|
+
return unless declarations.key?('font')
|
234
388
|
|
235
389
|
font_props = {}
|
236
390
|
|
237
391
|
# reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
|
238
|
-
['font-style', 'font-variant', 'font-weight', 'font-size',
|
239
|
-
'line-height'].each do |prop|
|
392
|
+
['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height'].each do |prop|
|
240
393
|
font_props[prop] = 'normal'
|
241
|
-
|
394
|
+
end
|
242
395
|
|
243
|
-
value =
|
244
|
-
|
245
|
-
order = @declarations['font'][:order]
|
396
|
+
value = declarations['font'].value.dup
|
397
|
+
value.gsub!(%r{/\s+}, '/') # handle spaces between font size and height shorthand (e.g. 14px/ 16px)
|
246
398
|
|
247
399
|
in_fonts = false
|
248
400
|
|
249
401
|
matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
|
250
402
|
matches.each do |match|
|
251
403
|
m = match[0].to_s.strip
|
252
|
-
m.gsub!(
|
404
|
+
m.gsub!(/;$/, '')
|
253
405
|
|
254
406
|
if in_fonts
|
255
|
-
if font_props.
|
256
|
-
font_props['font-family'] +=
|
407
|
+
if font_props.key?('font-family')
|
408
|
+
font_props['font-family'] += ", #{m}"
|
257
409
|
else
|
258
410
|
font_props['font-family'] = m
|
259
411
|
end
|
260
412
|
elsif m =~ /normal|inherit/i
|
261
413
|
['font-style', 'font-weight', 'font-variant'].each do |font_prop|
|
262
|
-
font_props[font_prop] = m unless font_props.
|
414
|
+
font_props[font_prop] = m unless font_props.key?(font_prop)
|
263
415
|
end
|
264
416
|
elsif m =~ /italic|oblique/i
|
265
417
|
font_props['font-style'] = m
|
266
|
-
elsif m =~ /small
|
418
|
+
elsif m =~ /small-caps/i
|
267
419
|
font_props['font-variant'] = m
|
268
420
|
elsif m =~ /[1-9]00$|bold|bolder|lighter/i
|
269
421
|
font_props['font-weight'] = m
|
270
422
|
elsif m =~ CssParser::FONT_UNITS_RX
|
271
|
-
if m =~
|
423
|
+
if m =~ %r{/}
|
272
424
|
font_props['font-size'], font_props['line-height'] = m.split('/')
|
273
425
|
else
|
274
426
|
font_props['font-size'] = m
|
@@ -277,9 +429,7 @@ module CssParser
|
|
277
429
|
end
|
278
430
|
end
|
279
431
|
|
280
|
-
|
281
|
-
|
282
|
-
@declarations.delete('font')
|
432
|
+
declarations.replace_declaration!('font', font_props, preserve_importance: true)
|
283
433
|
end
|
284
434
|
|
285
435
|
# Convert shorthand list-style declarations (e.g. <tt>list-style: lower-alpha outside;</tt>)
|
@@ -287,21 +437,18 @@ module CssParser
|
|
287
437
|
#
|
288
438
|
# See http://www.w3.org/TR/CSS21/generate.html#lists
|
289
439
|
def expand_list_style_shorthand! # :nodoc:
|
290
|
-
return unless
|
291
|
-
|
292
|
-
value = @declarations['list-style'][:value]
|
440
|
+
return unless declarations.key?('list-style')
|
293
441
|
|
294
|
-
|
295
|
-
LIST_STYLE_PROPERTIES.each do |prop|
|
296
|
-
split_declaration('list-style', prop, 'inherit')
|
297
|
-
end
|
298
|
-
end
|
442
|
+
value = declarations['list-style'].value.dup
|
299
443
|
|
300
|
-
|
301
|
-
|
302
|
-
|
444
|
+
replacement = LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h if value =~ CssParser::RE_INHERIT
|
445
|
+
replacement ||= {
|
446
|
+
'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
|
447
|
+
'list-style-position' => value.slice!(CssParser::RE_INSIDE_OUTSIDE),
|
448
|
+
'list-style-image' => value.slice!(Regexp.union(CssParser::URI_RX, /none/i))
|
449
|
+
}
|
303
450
|
|
304
|
-
|
451
|
+
declarations.replace_declaration!('list-style', replacement, preserve_importance: true)
|
305
452
|
end
|
306
453
|
|
307
454
|
# Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
|
@@ -315,23 +462,23 @@ module CssParser
|
|
315
462
|
end
|
316
463
|
|
317
464
|
# Combine several properties into a shorthand one
|
318
|
-
def create_shorthand_properties!
|
465
|
+
def create_shorthand_properties!(properties, shorthand_property) # :nodoc:
|
319
466
|
values = []
|
320
467
|
properties_to_delete = []
|
321
468
|
properties.each do |property|
|
322
|
-
if
|
323
|
-
values <<
|
469
|
+
if declarations.key?(property) and not declarations[property].important
|
470
|
+
values << declarations[property].value
|
324
471
|
properties_to_delete << property
|
325
472
|
end
|
326
473
|
end
|
327
474
|
|
328
|
-
if values.length
|
329
|
-
properties_to_delete.each do |property|
|
330
|
-
@declarations.delete(property)
|
331
|
-
end
|
475
|
+
return if values.length <= 1
|
332
476
|
|
333
|
-
|
477
|
+
properties_to_delete.each do |property|
|
478
|
+
declarations.delete(property)
|
334
479
|
end
|
480
|
+
|
481
|
+
declarations[shorthand_property] = values.join(' ')
|
335
482
|
end
|
336
483
|
|
337
484
|
# Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
|
@@ -340,15 +487,15 @@ module CssParser
|
|
340
487
|
# Leaves properties declared !important alone.
|
341
488
|
def create_background_shorthand! # :nodoc:
|
342
489
|
# When we have a background-size property we must separate it and distinguish it from
|
343
|
-
# background-position by
|
490
|
+
# background-position by preceding it with a backslash. In this case we also need to
|
344
491
|
# have a background-position property, so we set it if it's missing.
|
345
492
|
# http://www.w3schools.com/cssref/css3_pr_background.asp
|
346
|
-
if
|
347
|
-
unless
|
348
|
-
|
493
|
+
if declarations.key?('background-size') and not declarations['background-size'].important
|
494
|
+
unless declarations.key?('background-position')
|
495
|
+
declarations['background-position'] = '0% 0%'
|
349
496
|
end
|
350
497
|
|
351
|
-
|
498
|
+
declarations['background-size'].value = "/ #{declarations['background-size'].value}"
|
352
499
|
end
|
353
500
|
|
354
501
|
create_shorthand_properties! BACKGROUND_PROPERTIES, 'background'
|
@@ -361,100 +508,68 @@ module CssParser
|
|
361
508
|
def create_border_shorthand! # :nodoc:
|
362
509
|
values = []
|
363
510
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
511
|
+
BORDER_STYLE_PROPERTIES.each do |property|
|
512
|
+
next unless declarations.key?(property) and not declarations[property].important
|
513
|
+
# can't merge if any value contains a space (i.e. has multiple values)
|
514
|
+
# we temporarily remove any spaces after commas for the check (inside rgba, etc...)
|
515
|
+
return nil if declarations[property].value.gsub(/,\s/, ',').strip =~ /\s/
|
516
|
+
|
517
|
+
values << declarations[property].value
|
518
|
+
declarations.delete(property)
|
371
519
|
end
|
372
520
|
|
373
|
-
|
374
|
-
@declarations.delete('border-style')
|
375
|
-
@declarations.delete('border-color')
|
521
|
+
return if values.empty?
|
376
522
|
|
377
|
-
|
378
|
-
@declarations['border'] = {:value => values.join(' ')}
|
379
|
-
end
|
523
|
+
declarations['border'] = values.join(' ')
|
380
524
|
end
|
381
525
|
|
382
526
|
# Looks for long format CSS dimensional properties (margin, padding, border-color, border-style and border-width)
|
383
527
|
# and converts them into shorthand CSS properties.
|
384
528
|
def create_dimensions_shorthand! # :nodoc:
|
385
|
-
|
529
|
+
return if declarations.size < NUMBER_OF_DIMENSIONS
|
386
530
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
'border-style' => 'border-%s-style',
|
391
|
-
'border-width' => 'border-%s-width'}.each do |property, expanded|
|
531
|
+
DIMENSIONS.each do |property, dimensions|
|
532
|
+
values = %i[top right bottom left].each_with_index.with_object({}) do |(side, index), result|
|
533
|
+
next unless declarations.key?(dimensions[index])
|
392
534
|
|
393
|
-
|
394
|
-
foldable = @declarations.select do |dim, val|
|
395
|
-
dim == top or dim == right or dim == bottom or dim == left
|
535
|
+
result[side] = declarations[dimensions[index]].value
|
396
536
|
end
|
537
|
+
|
397
538
|
# All four dimensions must be present
|
398
|
-
if
|
399
|
-
values = {}
|
400
|
-
|
401
|
-
directions.each { |d| values[d.to_sym] = @declarations[expanded % d][:value].downcase.strip }
|
402
|
-
|
403
|
-
if values[:left] == values[:right]
|
404
|
-
if values[:top] == values[:bottom]
|
405
|
-
if values[:top] == values[:left] # All four sides are equal
|
406
|
-
new_value = values[:top]
|
407
|
-
else # Top and bottom are equal, left and right are equal
|
408
|
-
new_value = values[:top] + ' ' + values[:left]
|
409
|
-
end
|
410
|
-
else # Only left and right are equal
|
411
|
-
new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
|
412
|
-
end
|
413
|
-
else # No sides are equal
|
414
|
-
new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
|
415
|
-
end
|
539
|
+
next if values.size != dimensions.size
|
416
540
|
|
417
|
-
|
418
|
-
|
541
|
+
new_value = values.values_at(*compute_dimensions_shorthand(values)).join(' ').strip
|
542
|
+
declarations[property] = new_value unless new_value.empty?
|
419
543
|
|
420
|
-
|
421
|
-
|
422
|
-
end
|
544
|
+
# Delete the longhand values
|
545
|
+
dimensions.each { |d| declarations.delete(d) }
|
423
546
|
end
|
424
547
|
end
|
425
548
|
|
426
|
-
|
427
549
|
# Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
|
428
550
|
# tries to convert them into a shorthand CSS <tt>font</tt> property. All
|
429
551
|
# font properties must be present in order to create a shorthand declaration.
|
430
552
|
def create_font_shorthand! # :nodoc:
|
431
|
-
|
432
|
-
'line-height', 'font-family'].each do |prop|
|
433
|
-
return unless @declarations.has_key?(prop)
|
434
|
-
end
|
553
|
+
return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
|
435
554
|
|
436
|
-
new_value =
|
555
|
+
new_value = String.new
|
437
556
|
['font-style', 'font-variant', 'font-weight'].each do |property|
|
438
|
-
unless
|
439
|
-
new_value
|
557
|
+
unless declarations[property].value == 'normal'
|
558
|
+
new_value << declarations[property].value << ' '
|
440
559
|
end
|
441
560
|
end
|
442
561
|
|
443
|
-
new_value
|
562
|
+
new_value << declarations['font-size'].value
|
444
563
|
|
445
|
-
unless
|
446
|
-
new_value
|
564
|
+
unless declarations['line-height'].value == 'normal'
|
565
|
+
new_value << '/' << declarations['line-height'].value
|
447
566
|
end
|
448
567
|
|
449
|
-
new_value
|
450
|
-
|
451
|
-
@declarations['font'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
|
568
|
+
new_value << ' ' << declarations['font-family'].value
|
452
569
|
|
453
|
-
['font
|
454
|
-
'line-height', 'font-family'].each do |prop|
|
455
|
-
@declarations.delete(prop)
|
456
|
-
end
|
570
|
+
declarations['font'] = new_value.gsub(/\s+/, ' ')
|
457
571
|
|
572
|
+
FONT_STYLE_PROPERTIES.each { |prop| declarations.delete(prop) }
|
458
573
|
end
|
459
574
|
|
460
575
|
# Looks for long format CSS list-style properties (e.g. <tt>list-style-type</tt>) and
|
@@ -467,41 +582,37 @@ module CssParser
|
|
467
582
|
|
468
583
|
private
|
469
584
|
|
470
|
-
|
471
|
-
def split_declaration(src, dest, v) # :nodoc:
|
472
|
-
return unless v and not v.empty?
|
585
|
+
attr_accessor :declarations
|
473
586
|
|
474
|
-
|
475
|
-
|
587
|
+
def compute_dimensions_shorthand(values)
|
588
|
+
# All four sides are equal, returning single value
|
589
|
+
return %i[top] if values.values.uniq.count == 1
|
476
590
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
@declarations[dest] = @declarations[src].merge({:value => v.to_s.strip})
|
591
|
+
# `/* top | right | bottom | left */`
|
592
|
+
return %i[top right bottom left] if values[:left] != values[:right]
|
593
|
+
|
594
|
+
# Vertical are the same & horizontal are the same, `/* vertical | horizontal */`
|
595
|
+
return %i[top left] if values[:top] == values[:bottom]
|
596
|
+
|
597
|
+
%i[top left bottom]
|
485
598
|
end
|
486
599
|
|
487
600
|
def parse_declarations!(block) # :nodoc:
|
488
|
-
|
601
|
+
self.declarations = Declarations.new
|
489
602
|
|
490
603
|
return unless block
|
491
604
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
block.split(/[\;$]+/m).each do |decs|
|
496
|
-
decs = continuation + decs
|
605
|
+
continuation = nil
|
606
|
+
block.split(/[;$]+/m).each do |decs|
|
607
|
+
decs = continuation ? continuation + decs : decs
|
497
608
|
if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
|
498
|
-
continuation = decs
|
499
|
-
|
500
|
-
|
501
|
-
property
|
502
|
-
|
609
|
+
continuation = "#{decs};"
|
610
|
+
elsif (matches = decs.match(/\s*(.[^:]*)\s*:\s*(.+?)(;?\s*\Z)/i))
|
611
|
+
# skip end_of_declaration
|
612
|
+
property = matches[1]
|
613
|
+
value = matches[2]
|
503
614
|
add_declaration!(property, value)
|
504
|
-
continuation =
|
615
|
+
continuation = nil
|
505
616
|
end
|
506
617
|
end
|
507
618
|
end
|
@@ -510,12 +621,15 @@ module CssParser
|
|
510
621
|
# TODO: way too simplistic
|
511
622
|
#++
|
512
623
|
def parse_selectors!(selectors) # :nodoc:
|
513
|
-
@selectors = selectors.split(',').map
|
624
|
+
@selectors = selectors.split(',').map do |s|
|
625
|
+
s.gsub!(/\s+/, ' ')
|
626
|
+
s.strip!
|
627
|
+
s
|
628
|
+
end
|
514
629
|
end
|
515
630
|
end
|
516
631
|
|
517
632
|
class OffsetAwareRuleSet < RuleSet
|
518
|
-
|
519
633
|
# File offset range
|
520
634
|
attr_reader :offset
|
521
635
|
|