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