css_parser 1.7.1 → 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 +4 -4
- data/lib/css_parser.rb +23 -33
- data/lib/css_parser/parser.rb +94 -98
- data/lib/css_parser/regexps.rb +36 -33
- data/lib/css_parser/rule_set.rb +343 -230
- data/lib/css_parser/version.rb +3 -1
- metadata +116 -4
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
|
-
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC= /
|
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
|
77
|
+
/ix.freeze
|
75
78
|
ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC = /
|
76
|
-
(?:(?:^|[\s
|
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,92 +1,255 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
2
5
|
module CssParser
|
3
6
|
class RuleSet
|
4
7
|
# Patterns for specificity calculations
|
5
|
-
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s
|
6
|
-
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
|
7
10
|
|
8
|
-
BACKGROUND_PROPERTIES = ['background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-attachment']
|
9
|
-
LIST_STYLE_PROPERTIES = ['list-style-type', 'list-style-position', 'list-style-image']
|
10
|
-
FONT_STYLE_PROPERTIES = ['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family']
|
11
|
-
BORDER_STYLE_PROPERTIES = ['border-width', 'border-style', 'border-color']
|
12
|
-
BORDER_PROPERTIES = ['border', 'border-left', 'border-right', 'border-top', 'border-bottom']
|
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
|
13
16
|
|
14
17
|
NUMBER_OF_DIMENSIONS = 4
|
15
18
|
|
16
19
|
DIMENSIONS = [
|
17
|
-
['margin', %w
|
18
|
-
['padding', %w
|
19
|
-
['border-color', %w
|
20
|
-
['border-style', %w
|
21
|
-
['border-width', %w
|
22
|
-
]
|
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
|
60
|
+
|
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
|
23
224
|
|
24
225
|
# Array of selector strings.
|
25
|
-
attr_reader
|
226
|
+
attr_reader :selectors
|
26
227
|
|
27
228
|
# Integer with the specificity to use for this RuleSet.
|
28
|
-
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
|
29
238
|
|
30
239
|
def initialize(selectors, block, specificity = nil)
|
31
240
|
@selectors = []
|
32
241
|
@specificity = specificity
|
33
|
-
@declarations = {}
|
34
|
-
@order = 0
|
35
242
|
parse_selectors!(selectors) if selectors
|
36
243
|
parse_declarations!(block)
|
37
244
|
end
|
38
245
|
|
39
246
|
# Get the value of a property
|
40
247
|
def get_value(property)
|
41
|
-
return '' unless
|
42
|
-
|
43
|
-
property = property.downcase.strip
|
44
|
-
properties = @declarations.inject(String.new) do |val, (key, data)|
|
45
|
-
#puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
|
46
|
-
importance = data[:is_important] ? ' !important' : ''
|
47
|
-
val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
|
48
|
-
val
|
49
|
-
end
|
50
|
-
return properties ? properties.strip : ''
|
51
|
-
end
|
52
|
-
alias_method :[], :get_value
|
53
|
-
|
54
|
-
# Add a CSS declaration to the current RuleSet.
|
55
|
-
#
|
56
|
-
# rule_set.add_declaration!('color', 'blue')
|
57
|
-
#
|
58
|
-
# puts rule_set['color']
|
59
|
-
# => 'blue;'
|
60
|
-
#
|
61
|
-
# rule_set.add_declaration!('margin', '0px auto !important')
|
62
|
-
#
|
63
|
-
# puts rule_set['margin']
|
64
|
-
# => '0px auto !important;'
|
65
|
-
#
|
66
|
-
# If the property already exists its value will be over-written.
|
67
|
-
def add_declaration!(property, value)
|
68
|
-
if value.nil? or value.empty?
|
69
|
-
@declarations.delete(property)
|
70
|
-
return
|
71
|
-
end
|
248
|
+
return '' unless declarations.key?(property)
|
72
249
|
|
73
|
-
|
74
|
-
is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
|
75
|
-
property = property.downcase
|
76
|
-
property.strip!
|
77
|
-
#puts "SAVING #{property} #{value} #{is_important.inspect}"
|
78
|
-
@declarations[property] = {
|
79
|
-
:value => value, :is_important => is_important, :order => @order += 1
|
80
|
-
}
|
81
|
-
end
|
82
|
-
alias_method :[]=, :add_declaration!
|
83
|
-
|
84
|
-
# Remove CSS declaration from the current RuleSet.
|
85
|
-
#
|
86
|
-
# rule_set.remove_declaration!('color')
|
87
|
-
def remove_declaration!(property)
|
88
|
-
@declarations.delete(property)
|
250
|
+
"#{declarations[property]};"
|
89
251
|
end
|
252
|
+
alias [] get_value
|
90
253
|
|
91
254
|
# Iterate through selectors.
|
92
255
|
#
|
@@ -98,42 +261,29 @@ module CssParser
|
|
98
261
|
# ...
|
99
262
|
# end
|
100
263
|
def each_selector(options = {}) # :yields: selector, declarations, specificity
|
101
|
-
|
264
|
+
decs = declarations.to_s(options)
|
102
265
|
if @specificity
|
103
|
-
@selectors.each { |sel| yield sel.strip,
|
266
|
+
@selectors.each { |sel| yield sel.strip, decs, @specificity }
|
104
267
|
else
|
105
|
-
@selectors.each { |sel| yield sel.strip,
|
268
|
+
@selectors.each { |sel| yield sel.strip, decs, CssParser.calculate_specificity(sel) }
|
106
269
|
end
|
107
270
|
end
|
108
271
|
|
109
272
|
# Iterate through declarations.
|
110
273
|
def each_declaration # :yields: property, value, is_important
|
111
|
-
|
112
|
-
|
113
|
-
value = data[:value]
|
114
|
-
yield property.downcase.strip, value.strip, data[:is_important]
|
274
|
+
declarations.each do |property_name, value|
|
275
|
+
yield property_name, value.value, value.important
|
115
276
|
end
|
116
277
|
end
|
117
278
|
|
118
279
|
# Return all declarations as a string.
|
119
|
-
#--
|
120
|
-
# TODO: Clean-up regexp doesn't seem to work
|
121
|
-
#++
|
122
280
|
def declarations_to_s(options = {})
|
123
|
-
|
124
|
-
each_declaration do |prop, val, is_important|
|
125
|
-
importance = (options[:force_important] || is_important) ? ' !important' : ''
|
126
|
-
str << "#{prop}: #{val}#{importance}; "
|
127
|
-
end
|
128
|
-
str.gsub!(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '')
|
129
|
-
str.strip!
|
130
|
-
str
|
281
|
+
declarations.to_s(options)
|
131
282
|
end
|
132
283
|
|
133
284
|
# Return the CSS rule set as a string.
|
134
285
|
def to_s
|
135
|
-
|
136
|
-
"#{@selectors.join(',')} { #{decs} }"
|
286
|
+
"#{@selectors.join(',')} { #{declarations} }"
|
137
287
|
end
|
138
288
|
|
139
289
|
# Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
|
@@ -151,45 +301,44 @@ module CssParser
|
|
151
301
|
#
|
152
302
|
# See http://www.w3.org/TR/CSS21/colors.html#propdef-background
|
153
303
|
def expand_background_shorthand! # :nodoc:
|
154
|
-
return unless
|
155
|
-
|
156
|
-
value =
|
157
|
-
|
158
|
-
if value
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
split_declaration('background', 'background-color', value.slice!(CssParser::RE_COLOUR))
|
168
|
-
split_declaration('background', 'background-size', extract_background_size_from(value))
|
169
|
-
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
|
+
}
|
170
317
|
|
171
|
-
|
318
|
+
declarations.replace_declaration!('background', replacement, preserve_importance: true)
|
172
319
|
end
|
173
320
|
|
174
321
|
def extract_background_size_from(value)
|
175
322
|
size = value.slice!(CssParser::RE_BACKGROUND_SIZE)
|
176
323
|
|
177
|
-
size.sub(
|
324
|
+
size.sub(%r{^\s*/\s*}, '') if size
|
178
325
|
end
|
179
326
|
|
180
327
|
# Split shorthand border declarations (e.g. <tt>border: 1px red;</tt>)
|
181
328
|
# Additional splitting happens in expand_dimensions_shorthand!
|
182
329
|
def expand_border_shorthand! # :nodoc:
|
183
330
|
BORDER_PROPERTIES.each do |k|
|
184
|
-
next unless
|
331
|
+
next unless declarations.key?(k)
|
185
332
|
|
186
|
-
value =
|
333
|
+
value = declarations[k].value.dup
|
187
334
|
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
+
}
|
191
340
|
|
192
|
-
|
341
|
+
declarations.replace_declaration!(k, replacement, preserve_importance: true)
|
193
342
|
end
|
194
343
|
end
|
195
344
|
|
@@ -197,15 +346,16 @@ module CssParser
|
|
197
346
|
# into their constituent parts. Handles margin, padding, border-color, border-style and border-width.
|
198
347
|
def expand_dimensions_shorthand! # :nodoc:
|
199
348
|
DIMENSIONS.each do |property, (top, right, bottom, left)|
|
200
|
-
next unless
|
201
|
-
|
349
|
+
next unless declarations.key?(property)
|
350
|
+
|
351
|
+
value = declarations[property].value.dup
|
202
352
|
|
203
353
|
# RGB and HSL values in borders are the only units that can have spaces (within params).
|
204
354
|
# We cheat a bit here by stripping spaces after commas in RGB and HSL values so that we
|
205
355
|
# can split easily on spaces.
|
206
356
|
#
|
207
357
|
# TODO: rgba, hsl, hsla
|
208
|
-
value.gsub!(RE_COLOUR) { |c| c.gsub(/(\s
|
358
|
+
value.gsub!(RE_COLOUR) { |c| c.gsub(/(\s*,\s*)/, ',') }
|
209
359
|
|
210
360
|
matches = value.strip.split(/\s+/)
|
211
361
|
|
@@ -223,58 +373,54 @@ module CssParser
|
|
223
373
|
|
224
374
|
t, r, b, l = values
|
225
375
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
@declarations.delete(property)
|
376
|
+
declarations.replace_declaration!(
|
377
|
+
property,
|
378
|
+
{top => t, right => r, bottom => b, left => l},
|
379
|
+
preserve_importance: true
|
380
|
+
)
|
232
381
|
end
|
233
382
|
end
|
234
383
|
|
235
384
|
# Convert shorthand font declarations (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
|
236
385
|
# into their constituent parts.
|
237
386
|
def expand_font_shorthand! # :nodoc:
|
238
|
-
return unless
|
387
|
+
return unless declarations.key?('font')
|
239
388
|
|
240
389
|
font_props = {}
|
241
390
|
|
242
391
|
# reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
|
243
|
-
['font-style', 'font-variant', 'font-weight', 'font-size',
|
244
|
-
'line-height'].each do |prop|
|
392
|
+
['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height'].each do |prop|
|
245
393
|
font_props[prop] = 'normal'
|
246
|
-
|
394
|
+
end
|
247
395
|
|
248
|
-
value =
|
249
|
-
value.gsub!(
|
250
|
-
is_important = @declarations['font'][:is_important]
|
251
|
-
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)
|
252
398
|
|
253
399
|
in_fonts = false
|
254
400
|
|
255
401
|
matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
|
256
402
|
matches.each do |match|
|
257
403
|
m = match[0].to_s.strip
|
258
|
-
m.gsub!(
|
404
|
+
m.gsub!(/;$/, '')
|
259
405
|
|
260
406
|
if in_fonts
|
261
|
-
if font_props.
|
262
|
-
font_props['font-family'] +=
|
407
|
+
if font_props.key?('font-family')
|
408
|
+
font_props['font-family'] += ", #{m}"
|
263
409
|
else
|
264
410
|
font_props['font-family'] = m
|
265
411
|
end
|
266
412
|
elsif m =~ /normal|inherit/i
|
267
413
|
['font-style', 'font-weight', 'font-variant'].each do |font_prop|
|
268
|
-
font_props[font_prop] = m unless font_props.
|
414
|
+
font_props[font_prop] = m unless font_props.key?(font_prop)
|
269
415
|
end
|
270
416
|
elsif m =~ /italic|oblique/i
|
271
417
|
font_props['font-style'] = m
|
272
|
-
elsif m =~ /small
|
418
|
+
elsif m =~ /small-caps/i
|
273
419
|
font_props['font-variant'] = m
|
274
420
|
elsif m =~ /[1-9]00$|bold|bolder|lighter/i
|
275
421
|
font_props['font-weight'] = m
|
276
422
|
elsif m =~ CssParser::FONT_UNITS_RX
|
277
|
-
if m =~
|
423
|
+
if m =~ %r{/}
|
278
424
|
font_props['font-size'], font_props['line-height'] = m.split('/')
|
279
425
|
else
|
280
426
|
font_props['font-size'] = m
|
@@ -283,9 +429,7 @@ module CssParser
|
|
283
429
|
end
|
284
430
|
end
|
285
431
|
|
286
|
-
|
287
|
-
|
288
|
-
@declarations.delete('font')
|
432
|
+
declarations.replace_declaration!('font', font_props, preserve_importance: true)
|
289
433
|
end
|
290
434
|
|
291
435
|
# Convert shorthand list-style declarations (e.g. <tt>list-style: lower-alpha outside;</tt>)
|
@@ -293,21 +437,18 @@ module CssParser
|
|
293
437
|
#
|
294
438
|
# See http://www.w3.org/TR/CSS21/generate.html#lists
|
295
439
|
def expand_list_style_shorthand! # :nodoc:
|
296
|
-
return unless
|
440
|
+
return unless declarations.key?('list-style')
|
297
441
|
|
298
|
-
value =
|
299
|
-
|
300
|
-
if value =~ CssParser::RE_INHERIT
|
301
|
-
LIST_STYLE_PROPERTIES.each do |prop|
|
302
|
-
split_declaration('list-style', prop, 'inherit')
|
303
|
-
end
|
304
|
-
end
|
442
|
+
value = declarations['list-style'].value.dup
|
305
443
|
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
+
}
|
309
450
|
|
310
|
-
|
451
|
+
declarations.replace_declaration!('list-style', replacement, preserve_importance: true)
|
311
452
|
end
|
312
453
|
|
313
454
|
# Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
|
@@ -321,23 +462,23 @@ module CssParser
|
|
321
462
|
end
|
322
463
|
|
323
464
|
# Combine several properties into a shorthand one
|
324
|
-
def create_shorthand_properties!
|
465
|
+
def create_shorthand_properties!(properties, shorthand_property) # :nodoc:
|
325
466
|
values = []
|
326
467
|
properties_to_delete = []
|
327
468
|
properties.each do |property|
|
328
|
-
if
|
329
|
-
values <<
|
469
|
+
if declarations.key?(property) and not declarations[property].important
|
470
|
+
values << declarations[property].value
|
330
471
|
properties_to_delete << property
|
331
472
|
end
|
332
473
|
end
|
333
474
|
|
334
|
-
if values.length
|
335
|
-
properties_to_delete.each do |property|
|
336
|
-
@declarations.delete(property)
|
337
|
-
end
|
475
|
+
return if values.length <= 1
|
338
476
|
|
339
|
-
|
477
|
+
properties_to_delete.each do |property|
|
478
|
+
declarations.delete(property)
|
340
479
|
end
|
480
|
+
|
481
|
+
declarations[shorthand_property] = values.join(' ')
|
341
482
|
end
|
342
483
|
|
343
484
|
# Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
|
@@ -349,12 +490,12 @@ module CssParser
|
|
349
490
|
# background-position by preceding it with a backslash. In this case we also need to
|
350
491
|
# have a background-position property, so we set it if it's missing.
|
351
492
|
# http://www.w3schools.com/cssref/css3_pr_background.asp
|
352
|
-
if
|
353
|
-
unless
|
354
|
-
|
493
|
+
if declarations.key?('background-size') and not declarations['background-size'].important
|
494
|
+
unless declarations.key?('background-position')
|
495
|
+
declarations['background-position'] = '0% 0%'
|
355
496
|
end
|
356
497
|
|
357
|
-
|
498
|
+
declarations['background-size'].value = "/ #{declarations['background-size'].value}"
|
358
499
|
end
|
359
500
|
|
360
501
|
create_shorthand_properties! BACKGROUND_PROPERTIES, 'background'
|
@@ -368,89 +509,67 @@ module CssParser
|
|
368
509
|
values = []
|
369
510
|
|
370
511
|
BORDER_STYLE_PROPERTIES.each do |property|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
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)
|
377
519
|
end
|
378
520
|
|
379
|
-
|
521
|
+
return if values.empty?
|
380
522
|
|
381
|
-
|
382
|
-
@declarations['border'] = {:value => values.join(' ')}
|
383
|
-
end
|
523
|
+
declarations['border'] = values.join(' ')
|
384
524
|
end
|
385
525
|
|
386
526
|
# Looks for long format CSS dimensional properties (margin, padding, border-color, border-style and border-width)
|
387
527
|
# and converts them into shorthand CSS properties.
|
388
528
|
def create_dimensions_shorthand! # :nodoc:
|
389
|
-
return if
|
529
|
+
return if declarations.size < NUMBER_OF_DIMENSIONS
|
390
530
|
|
391
531
|
DIMENSIONS.each do |property, dimensions|
|
392
|
-
|
532
|
+
values = %i[top right bottom left].each_with_index.with_object({}) do |(side, index), result|
|
533
|
+
next unless declarations.key?(dimensions[index])
|
534
|
+
|
535
|
+
result[side] = declarations[dimensions[index]].value
|
536
|
+
end
|
537
|
+
|
393
538
|
# All four dimensions must be present
|
394
|
-
if
|
395
|
-
values = {}
|
396
|
-
|
397
|
-
[
|
398
|
-
[:top, top],
|
399
|
-
[:right, right],
|
400
|
-
[:bottom, bottom],
|
401
|
-
[:left, left],
|
402
|
-
].each { |d, key| values[d] = @declarations[key][:value].downcase.strip }
|
403
|
-
|
404
|
-
if values[:left] == values[:right]
|
405
|
-
if values[:top] == values[:bottom]
|
406
|
-
if values[:top] == values[:left] # All four sides are equal
|
407
|
-
new_value = values[:top]
|
408
|
-
else # Top and bottom are equal, left and right are equal
|
409
|
-
new_value = values[:top] + ' ' + values[:left]
|
410
|
-
end
|
411
|
-
else # Only left and right are equal
|
412
|
-
new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
|
413
|
-
end
|
414
|
-
else # No sides are equal
|
415
|
-
new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
|
416
|
-
end
|
539
|
+
next if values.size != dimensions.size
|
417
540
|
|
418
|
-
|
419
|
-
|
541
|
+
new_value = values.values_at(*compute_dimensions_shorthand(values)).join(' ').strip
|
542
|
+
declarations[property] = new_value unless new_value.empty?
|
420
543
|
|
421
|
-
|
422
|
-
|
423
|
-
end
|
544
|
+
# Delete the longhand values
|
545
|
+
dimensions.each { |d| declarations.delete(d) }
|
424
546
|
end
|
425
547
|
end
|
426
548
|
|
427
|
-
|
428
549
|
# Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
|
429
550
|
# tries to convert them into a shorthand CSS <tt>font</tt> property. All
|
430
551
|
# font properties must be present in order to create a shorthand declaration.
|
431
552
|
def create_font_shorthand! # :nodoc:
|
432
|
-
FONT_STYLE_PROPERTIES.
|
433
|
-
return unless @declarations.has_key?(prop)
|
434
|
-
end
|
553
|
+
return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
|
435
554
|
|
436
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 << ' ' <<
|
568
|
+
new_value << ' ' << declarations['font-family'].value
|
450
569
|
|
451
|
-
|
570
|
+
declarations['font'] = new_value.gsub(/\s+/, ' ')
|
452
571
|
|
453
|
-
FONT_STYLE_PROPERTIES.each { |prop|
|
572
|
+
FONT_STYLE_PROPERTIES.each { |prop| declarations.delete(prop) }
|
454
573
|
end
|
455
574
|
|
456
575
|
# Looks for long format CSS list-style properties (e.g. <tt>list-style-type</tt>) and
|
@@ -463,37 +582,32 @@ module CssParser
|
|
463
582
|
|
464
583
|
private
|
465
584
|
|
466
|
-
|
467
|
-
def split_declaration(src, dest, v) # :nodoc:
|
468
|
-
return unless v and not v.empty?
|
585
|
+
attr_accessor :declarations
|
469
586
|
|
470
|
-
|
471
|
-
|
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
|
472
590
|
|
473
|
-
|
474
|
-
|
475
|
-
return
|
476
|
-
else
|
477
|
-
@declarations[dest] = {}
|
478
|
-
end
|
479
|
-
end
|
591
|
+
# `/* top | right | bottom | left */`
|
592
|
+
return %i[top right bottom left] if values[:left] != values[:right]
|
480
593
|
|
481
|
-
|
482
|
-
|
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]
|
483
598
|
end
|
484
599
|
|
485
600
|
def parse_declarations!(block) # :nodoc:
|
486
|
-
|
601
|
+
self.declarations = Declarations.new
|
487
602
|
|
488
603
|
return unless block
|
489
604
|
|
490
605
|
continuation = nil
|
491
|
-
block.split(/[
|
606
|
+
block.split(/[;$]+/m).each do |decs|
|
492
607
|
decs = continuation ? continuation + decs : decs
|
493
608
|
if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
|
494
|
-
continuation = decs
|
495
|
-
|
496
|
-
elsif matches = decs.match(/\s*(.[^:]*)\s*:\s*(.+?)(;?\s*\Z)/i)
|
609
|
+
continuation = "#{decs};"
|
610
|
+
elsif (matches = decs.match(/\s*(.[^:]*)\s*:\s*(.+?)(;?\s*\Z)/i))
|
497
611
|
# skip end_of_declaration
|
498
612
|
property = matches[1]
|
499
613
|
value = matches[2]
|
@@ -516,7 +630,6 @@ module CssParser
|
|
516
630
|
end
|
517
631
|
|
518
632
|
class OffsetAwareRuleSet < RuleSet
|
519
|
-
|
520
633
|
# File offset range
|
521
634
|
attr_reader :offset
|
522
635
|
|