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