css_parser 1.11.0 → 1.21.1
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/parser.rb +107 -44
- data/lib/css_parser/regexps.rb +7 -5
- data/lib/css_parser/rule_set.rb +92 -46
- data/lib/css_parser/version.rb +1 -1
- data/lib/css_parser.rb +7 -8
- metadata +8 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebc43b9f09241b83ffe9a2878e0a2e5295c79f6d2e02bb20dcaf54d376f91e58
|
|
4
|
+
data.tar.gz: 5b29858923af894f1b84251c59de6a4638ae0929b63ac0b432a7607d9dd32111
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd7a00f2be5a2ddbf730eb115dbc0e15f9ac7a71ac92e1a056161a2b418e01d7abeb757c6f8d5fabafd9e4f7dba034b209ca571012ee44ee6b377b2e4bd8100f
|
|
7
|
+
data.tar.gz: ac8aeba706f1ae35126c6d5c4a150a52862af8654f1342e6c5b36ebce8c0b71c26e0d0931d09081b3b01a70685c3d2b173031a3e1ff32a8477799c3e39e013e4
|
data/lib/css_parser/parser.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
3
5
|
module CssParser
|
|
4
6
|
# Exception class used for any errors encountered while downloading remote files.
|
|
5
7
|
class RemoteFileError < IOError; end
|
|
@@ -16,8 +18,8 @@ module CssParser
|
|
|
16
18
|
# [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>.
|
|
17
19
|
# [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>.
|
|
18
20
|
class Parser
|
|
19
|
-
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
|
|
20
|
-
|
|
21
|
+
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)".freeze
|
|
22
|
+
RULESET_TOKENIZER_RX = /\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/.freeze
|
|
21
23
|
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
|
22
24
|
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
|
23
25
|
|
|
@@ -36,10 +38,14 @@ module CssParser
|
|
|
36
38
|
class << self; attr_reader :folded_declaration_cache; end
|
|
37
39
|
|
|
38
40
|
def initialize(options = {})
|
|
39
|
-
@options = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
@options = {
|
|
42
|
+
absolute_paths: false,
|
|
43
|
+
import: true,
|
|
44
|
+
io_exceptions: true,
|
|
45
|
+
rule_set_exceptions: true,
|
|
46
|
+
capture_offsets: false,
|
|
47
|
+
user_agent: USER_AGENT
|
|
48
|
+
}.merge(options)
|
|
43
49
|
|
|
44
50
|
# array of RuleSets
|
|
45
51
|
@rules = []
|
|
@@ -130,7 +136,7 @@ module CssParser
|
|
|
130
136
|
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
|
131
137
|
media_types = []
|
|
132
138
|
if (media_string = import_rule[-1])
|
|
133
|
-
media_string.split(
|
|
139
|
+
media_string.split(',').each do |t|
|
|
134
140
|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
|
|
135
141
|
end
|
|
136
142
|
else
|
|
@@ -161,12 +167,44 @@ module CssParser
|
|
|
161
167
|
parse_block_into_rule_sets!(block, options)
|
|
162
168
|
end
|
|
163
169
|
|
|
164
|
-
# Add a CSS rule by setting the +selectors+, +declarations+
|
|
170
|
+
# Add a CSS rule by setting the +selectors+, +declarations+
|
|
171
|
+
# and +media_types+. Optional pass +filename+ , +offset+ for source
|
|
172
|
+
# reference too.
|
|
165
173
|
#
|
|
166
|
-
# +media_types+ can be a symbol or an array of symbols.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
# +media_types+ can be a symbol or an array of symbols. default to :all
|
|
175
|
+
# optional fields for source location for source location
|
|
176
|
+
# +filename+ can be a string or uri pointing to the file or url location.
|
|
177
|
+
# +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
|
|
178
|
+
def add_rule!(*args, selectors: nil, block: nil, filename: nil, offset: nil, media_types: :all) # rubocop:disable Metrics/ParameterLists
|
|
179
|
+
if args.any?
|
|
180
|
+
media_types = nil
|
|
181
|
+
if selectors || block || filename || offset || media_types
|
|
182
|
+
raise ArgumentError, "don't mix positional and keyword arguments arguments"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
warn '[DEPRECATION] `add_rule!` with positional arguments is deprecated. ' \
|
|
186
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
187
|
+
|
|
188
|
+
case args.length
|
|
189
|
+
when 2
|
|
190
|
+
selectors, block = args
|
|
191
|
+
when 3
|
|
192
|
+
selectors, block, media_types = args
|
|
193
|
+
else
|
|
194
|
+
raise ArgumentError
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
begin
|
|
199
|
+
rule_set = RuleSet.new(
|
|
200
|
+
selectors: selectors, block: block,
|
|
201
|
+
offset: offset, filename: filename
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
add_rule_set!(rule_set, media_types)
|
|
205
|
+
rescue ArgumentError => e
|
|
206
|
+
raise e if @options[:rule_set_exceptions]
|
|
207
|
+
end
|
|
170
208
|
end
|
|
171
209
|
|
|
172
210
|
# Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
|
|
@@ -175,8 +213,11 @@ module CssParser
|
|
|
175
213
|
# +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
|
|
176
214
|
# +media_types+ can be a symbol or an array of symbols.
|
|
177
215
|
def add_rule_with_offsets!(selectors, declarations, filename, offset, media_types = :all)
|
|
178
|
-
|
|
179
|
-
|
|
216
|
+
warn '[DEPRECATION] `add_rule_with_offsets!` is deprecated. Please use `add_rule!` instead.', uplevel: 1
|
|
217
|
+
add_rule!(
|
|
218
|
+
selectors: selectors, block: declarations, media_types: media_types,
|
|
219
|
+
filename: filename, offset: offset
|
|
220
|
+
)
|
|
180
221
|
end
|
|
181
222
|
|
|
182
223
|
# Add a CssParser RuleSet object.
|
|
@@ -318,17 +359,21 @@ module CssParser
|
|
|
318
359
|
in_at_media_rule = false
|
|
319
360
|
in_media_block = false
|
|
320
361
|
|
|
321
|
-
current_selectors =
|
|
322
|
-
current_media_query =
|
|
323
|
-
current_declarations =
|
|
362
|
+
current_selectors = +''
|
|
363
|
+
current_media_query = +''
|
|
364
|
+
current_declarations = +''
|
|
324
365
|
|
|
325
366
|
# once we are in a rule, we will use this to store where we started if we are capturing offsets
|
|
326
367
|
rule_start = nil
|
|
327
|
-
|
|
368
|
+
start_offset = nil
|
|
369
|
+
end_offset = nil
|
|
328
370
|
|
|
329
|
-
|
|
371
|
+
scanner = StringScanner.new(block)
|
|
372
|
+
until scanner.eos?
|
|
330
373
|
# save the regex offset so that we know where in the file we are
|
|
331
|
-
|
|
374
|
+
start_offset = scanner.pos
|
|
375
|
+
token = scanner.scan(RULESET_TOKENIZER_RX)
|
|
376
|
+
end_offset = scanner.pos
|
|
332
377
|
|
|
333
378
|
if token.start_with?('"') # found un-escaped double quote
|
|
334
379
|
in_string = !in_string
|
|
@@ -355,20 +400,24 @@ module CssParser
|
|
|
355
400
|
current_declarations.strip!
|
|
356
401
|
|
|
357
402
|
unless current_declarations.empty?
|
|
403
|
+
add_rule_options = {
|
|
404
|
+
selectors: current_selectors, block: current_declarations,
|
|
405
|
+
media_types: current_media_queries
|
|
406
|
+
}
|
|
358
407
|
if options[:capture_offsets]
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
add_rule!(current_selectors, current_declarations, current_media_queries)
|
|
408
|
+
add_rule_options[:filename] = options[:filename]
|
|
409
|
+
add_rule_options[:offset] = rule_start..end_offset
|
|
362
410
|
end
|
|
411
|
+
add_rule!(**add_rule_options)
|
|
363
412
|
end
|
|
364
413
|
|
|
365
|
-
current_selectors =
|
|
366
|
-
current_declarations =
|
|
414
|
+
current_selectors = +''
|
|
415
|
+
current_declarations = +''
|
|
367
416
|
|
|
368
417
|
# restart our search for selectors and declarations
|
|
369
418
|
rule_start = nil if options[:capture_offsets]
|
|
370
419
|
end
|
|
371
|
-
elsif
|
|
420
|
+
elsif /@media/i.match?(token)
|
|
372
421
|
# found '@media', reset current media_types
|
|
373
422
|
in_at_media_rule = true
|
|
374
423
|
current_media_queries = []
|
|
@@ -378,19 +427,28 @@ module CssParser
|
|
|
378
427
|
in_at_media_rule = false
|
|
379
428
|
in_media_block = true
|
|
380
429
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
|
381
|
-
current_media_query =
|
|
430
|
+
current_media_query = +''
|
|
382
431
|
elsif token.include?(',')
|
|
383
432
|
# new media query begins
|
|
384
433
|
token.tr!(',', ' ')
|
|
385
434
|
token.strip!
|
|
386
435
|
current_media_query << token << ' '
|
|
387
436
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
|
388
|
-
current_media_query =
|
|
437
|
+
current_media_query = +''
|
|
389
438
|
else
|
|
390
439
|
token.strip!
|
|
391
|
-
|
|
440
|
+
# special-case the ( and ) tokens to remove inner-whitespace
|
|
441
|
+
# (eg we'd prefer '(width: 500px)' to '( width: 500px )' )
|
|
442
|
+
case token
|
|
443
|
+
when '('
|
|
444
|
+
current_media_query << token
|
|
445
|
+
when ')'
|
|
446
|
+
current_media_query.sub!(/ ?$/, token)
|
|
447
|
+
else
|
|
448
|
+
current_media_query << token << ' '
|
|
449
|
+
end
|
|
392
450
|
end
|
|
393
|
-
elsif in_charset or
|
|
451
|
+
elsif in_charset or /@charset/i.match?(token)
|
|
394
452
|
# iterate until we are out of the charset declaration
|
|
395
453
|
in_charset = !token.include?(';')
|
|
396
454
|
elsif !in_string && token.include?('}')
|
|
@@ -409,18 +467,22 @@ module CssParser
|
|
|
409
467
|
current_selectors << token
|
|
410
468
|
|
|
411
469
|
# mark this as the beginning of the selector unless we have already marked it
|
|
412
|
-
rule_start =
|
|
470
|
+
rule_start = start_offset if options[:capture_offsets] && rule_start.nil? && /^[^\s]+$/.match?(token)
|
|
413
471
|
end
|
|
414
472
|
end
|
|
415
473
|
|
|
416
474
|
# check for unclosed braces
|
|
417
475
|
return unless in_declarations > 0
|
|
418
476
|
|
|
419
|
-
|
|
420
|
-
|
|
477
|
+
add_rule_options = {
|
|
478
|
+
selectors: current_selectors, block: current_declarations,
|
|
479
|
+
media_types: current_media_queries
|
|
480
|
+
}
|
|
481
|
+
if options[:capture_offsets]
|
|
482
|
+
add_rule_options[:filename] = options[:filename]
|
|
483
|
+
add_rule_options[:offset] = rule_start..end_offset
|
|
421
484
|
end
|
|
422
|
-
|
|
423
|
-
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
|
485
|
+
add_rule!(**add_rule_options)
|
|
424
486
|
end
|
|
425
487
|
|
|
426
488
|
# Load a remote CSS file.
|
|
@@ -438,6 +500,8 @@ module CssParser
|
|
|
438
500
|
if options.is_a? Hash
|
|
439
501
|
opts.merge!(options)
|
|
440
502
|
else
|
|
503
|
+
warn '[DEPRECATION] `load_uri!` with positional arguments is deprecated. ' \
|
|
504
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
441
505
|
opts[:base_uri] = options if options.is_a? String
|
|
442
506
|
opts[:media_types] = deprecated if deprecated
|
|
443
507
|
end
|
|
@@ -464,6 +528,8 @@ module CssParser
|
|
|
464
528
|
if options.is_a? Hash
|
|
465
529
|
opts.merge!(options)
|
|
466
530
|
else
|
|
531
|
+
warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
|
|
532
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
467
533
|
opts[:base_dir] = options if options.is_a? String
|
|
468
534
|
opts[:media_types] = deprecated if deprecated
|
|
469
535
|
end
|
|
@@ -472,7 +538,7 @@ module CssParser
|
|
|
472
538
|
return unless File.readable?(file_name)
|
|
473
539
|
return unless circular_reference_check(file_name)
|
|
474
540
|
|
|
475
|
-
src =
|
|
541
|
+
src = File.read(file_name)
|
|
476
542
|
|
|
477
543
|
opts[:filename] = file_name if opts[:capture_offsets]
|
|
478
544
|
opts[:base_dir] = File.dirname(file_name)
|
|
@@ -487,6 +553,8 @@ module CssParser
|
|
|
487
553
|
if options.is_a? Hash
|
|
488
554
|
opts.merge!(options)
|
|
489
555
|
else
|
|
556
|
+
warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
|
|
557
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
490
558
|
opts[:base_dir] = options if options.is_a? String
|
|
491
559
|
opts[:media_types] = deprecated if deprecated
|
|
492
560
|
end
|
|
@@ -585,7 +653,7 @@ module CssParser
|
|
|
585
653
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
586
654
|
end
|
|
587
655
|
|
|
588
|
-
res = http.get(uri.request_uri, {'User-Agent' =>
|
|
656
|
+
res = http.get(uri.request_uri, {'User-Agent' => @options[:user_agent], 'Accept-Encoding' => 'gzip'})
|
|
589
657
|
src = res.body
|
|
590
658
|
charset = res.respond_to?(:charset) ? res.encoding : 'utf-8'
|
|
591
659
|
|
|
@@ -611,12 +679,7 @@ module CssParser
|
|
|
611
679
|
end
|
|
612
680
|
|
|
613
681
|
if charset
|
|
614
|
-
|
|
615
|
-
src.encode!('UTF-8', charset)
|
|
616
|
-
else
|
|
617
|
-
ic = Iconv.new('UTF-8//IGNORE', charset)
|
|
618
|
-
src = ic.iconv(src)
|
|
619
|
-
end
|
|
682
|
+
src.encode!('UTF-8', charset)
|
|
620
683
|
end
|
|
621
684
|
rescue
|
|
622
685
|
@redirect_count = nil
|
|
@@ -657,7 +720,7 @@ module CssParser
|
|
|
657
720
|
nodes = {}
|
|
658
721
|
lines.each do |line|
|
|
659
722
|
parts = line.split(':', 2)
|
|
660
|
-
if parts[1]
|
|
723
|
+
if parts[1].include?(':')
|
|
661
724
|
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
|
|
662
725
|
else
|
|
663
726
|
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
|
data/lib/css_parser/regexps.rb
CHANGED
|
@@ -8,10 +8,10 @@ module CssParser
|
|
|
8
8
|
# :stopdoc:
|
|
9
9
|
# Base types
|
|
10
10
|
RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
|
|
11
|
-
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE
|
|
12
|
-
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE
|
|
11
|
+
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE | Regexp::NOENCODING) # [^\0-\177]
|
|
12
|
+
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE | Regexp::NOENCODING)
|
|
13
13
|
RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
|
|
14
|
-
RE_IDENT = Regexp.new("[
|
|
14
|
+
RE_IDENT = Regexp.new("[-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE | Regexp::NOENCODING)
|
|
15
15
|
|
|
16
16
|
# General strings
|
|
17
17
|
RE_STRING1 = /("(.[^\n\r\f"]*|\\#{RE_NL}|#{RE_ESCAPE})*")/.freeze
|
|
@@ -52,8 +52,10 @@ module CssParser
|
|
|
52
52
|
# Special units
|
|
53
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
|
|
54
54
|
RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
RE_SINGLE_BACKGROUND_POSITION = /#{RE_LENGTH_OR_PERCENTAGE}|left|center|right|top|bottom/i.freeze
|
|
56
|
+
RE_SINGLE_BACKGROUND_SIZE = /#{RE_LENGTH_OR_PERCENTAGE}|auto|cover|contain|initial|inherit/i.freeze
|
|
57
|
+
RE_BACKGROUND_POSITION = /#{RE_SINGLE_BACKGROUND_POSITION}\s+#{RE_SINGLE_BACKGROUND_POSITION}|#{RE_SINGLE_BACKGROUND_POSITION}/.freeze
|
|
58
|
+
RE_BACKGROUND_SIZE = %r{\s*/\s*(#{RE_SINGLE_BACKGROUND_SIZE}\s+#{RE_SINGLE_BACKGROUND_SIZE}|#{RE_SINGLE_BACKGROUND_SIZE})}.freeze
|
|
57
59
|
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
60
|
RE_BORDER_STYLE = /(\s*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)(\s*$)?/imx.freeze
|
|
59
61
|
RE_BORDER_UNITS = Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
|
data/lib/css_parser/rule_set.rb
CHANGED
|
@@ -11,8 +11,10 @@ module CssParser
|
|
|
11
11
|
BACKGROUND_PROPERTIES = ['background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-attachment'].freeze
|
|
12
12
|
LIST_STYLE_PROPERTIES = ['list-style-type', 'list-style-position', 'list-style-image'].freeze
|
|
13
13
|
FONT_STYLE_PROPERTIES = ['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family'].freeze
|
|
14
|
+
FONT_WEIGHT_PROPERTIES = ['font-style', 'font-weight', 'font-variant'].freeze
|
|
14
15
|
BORDER_STYLE_PROPERTIES = ['border-width', 'border-style', 'border-color'].freeze
|
|
15
16
|
BORDER_PROPERTIES = ['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].freeze
|
|
17
|
+
DIMENSION_DIRECTIONS = [:top, :right, :bottom, :left].freeze
|
|
16
18
|
|
|
17
19
|
NUMBER_OF_DIMENSIONS = 4
|
|
18
20
|
|
|
@@ -26,6 +28,12 @@ module CssParser
|
|
|
26
28
|
|
|
27
29
|
WHITESPACE_REPLACEMENT = '___SPACE___'
|
|
28
30
|
|
|
31
|
+
# Tokens for parse_declarations!
|
|
32
|
+
COLON = ':'.freeze
|
|
33
|
+
SEMICOLON = ';'.freeze
|
|
34
|
+
LPAREN = '('.freeze
|
|
35
|
+
RPAREN = ')'.freeze
|
|
36
|
+
IMPORTANT = '!important'.freeze
|
|
29
37
|
class Declarations
|
|
30
38
|
class Value
|
|
31
39
|
attr_reader :value
|
|
@@ -58,7 +66,7 @@ module CssParser
|
|
|
58
66
|
|
|
59
67
|
extend Forwardable
|
|
60
68
|
|
|
61
|
-
def_delegators :declarations, :each
|
|
69
|
+
def_delegators :declarations, :each, :each_value
|
|
62
70
|
|
|
63
71
|
def initialize(declarations = {})
|
|
64
72
|
self.declarations = {}
|
|
@@ -81,18 +89,23 @@ module CssParser
|
|
|
81
89
|
# puts declarations['margin']
|
|
82
90
|
# => #<CssParser::RuleSet::Declarations::Value:0x00000000030c1838 @important=true, @order=2, @value="0px auto">
|
|
83
91
|
#
|
|
84
|
-
# If the property already exists its value will be over-written
|
|
92
|
+
# If the property already exists its value will be over-written unless it was !important and the new value
|
|
93
|
+
# is not !important.
|
|
85
94
|
# If the value is empty - property will be deleted
|
|
86
95
|
def []=(property, value)
|
|
87
96
|
property = normalize_property(property)
|
|
97
|
+
currently_important = declarations[property]&.important
|
|
88
98
|
|
|
89
|
-
if value.is_a?(Value)
|
|
99
|
+
if value.is_a?(Value) && (!currently_important || value.important)
|
|
90
100
|
declarations[property] = value
|
|
91
101
|
elsif value.to_s.strip.empty?
|
|
92
102
|
delete property
|
|
93
103
|
else
|
|
94
|
-
|
|
104
|
+
value = Value.new(value)
|
|
105
|
+
declarations[property] = value if !currently_important || value.important
|
|
95
106
|
end
|
|
107
|
+
rescue ArgumentError => e
|
|
108
|
+
raise e.exception, "#{property} #{e.message}"
|
|
96
109
|
end
|
|
97
110
|
alias add_declaration! []=
|
|
98
111
|
|
|
@@ -140,7 +153,7 @@ module CssParser
|
|
|
140
153
|
|
|
141
154
|
if preserve_importance
|
|
142
155
|
importance = get_value(property).important
|
|
143
|
-
replacement_declarations.
|
|
156
|
+
replacement_declarations.each_value { |value| value.important = importance }
|
|
144
157
|
end
|
|
145
158
|
|
|
146
159
|
replacement_keys = declarations.keys
|
|
@@ -188,7 +201,7 @@ module CssParser
|
|
|
188
201
|
end
|
|
189
202
|
|
|
190
203
|
def to_s(options = {})
|
|
191
|
-
str = declarations.reduce(
|
|
204
|
+
str = declarations.reduce(+'') do |memo, (prop, value)|
|
|
192
205
|
importance = options[:force_important] || value.important ? ' !important' : ''
|
|
193
206
|
memo << "#{prop}: #{value.value}#{importance}; "
|
|
194
207
|
end
|
|
@@ -221,6 +234,12 @@ module CssParser
|
|
|
221
234
|
|
|
222
235
|
extend Forwardable
|
|
223
236
|
|
|
237
|
+
# optional field for storing source reference
|
|
238
|
+
# File offset range
|
|
239
|
+
attr_reader :offset
|
|
240
|
+
# the local or remote location
|
|
241
|
+
attr_accessor :filename
|
|
242
|
+
|
|
224
243
|
# Array of selector strings.
|
|
225
244
|
attr_reader :selectors
|
|
226
245
|
|
|
@@ -235,9 +254,38 @@ module CssParser
|
|
|
235
254
|
alias []= add_declaration!
|
|
236
255
|
alias remove_declaration! delete
|
|
237
256
|
|
|
238
|
-
def initialize(selectors, block,
|
|
257
|
+
def initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) # rubocop:disable Metrics/ParameterLists
|
|
258
|
+
if args.any?
|
|
259
|
+
if selectors || block || offset || filename || specificity
|
|
260
|
+
raise ArgumentError, "don't mix positional and keyword arguments"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
warn '[DEPRECATION] positional arguments are deprecated use keyword instead.', uplevel: 1
|
|
264
|
+
|
|
265
|
+
case args.length
|
|
266
|
+
when 2
|
|
267
|
+
selectors, block = args
|
|
268
|
+
when 3
|
|
269
|
+
selectors, block, specificity = args
|
|
270
|
+
when 4
|
|
271
|
+
filename, offset, selectors, block = args
|
|
272
|
+
when 5
|
|
273
|
+
filename, offset, selectors, block, specificity = args
|
|
274
|
+
else
|
|
275
|
+
raise ArgumentError
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
239
279
|
@selectors = []
|
|
240
280
|
@specificity = specificity
|
|
281
|
+
|
|
282
|
+
unless offset.nil? == filename.nil?
|
|
283
|
+
raise ArgumentError, 'require both offset and filename or no offset and no filename'
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
@offset = offset
|
|
287
|
+
@filename = filename
|
|
288
|
+
|
|
241
289
|
parse_selectors!(selectors) if selectors
|
|
242
290
|
parse_declarations!(block)
|
|
243
291
|
end
|
|
@@ -306,7 +354,7 @@ module CssParser
|
|
|
306
354
|
|
|
307
355
|
replacement =
|
|
308
356
|
if value.match(CssParser::RE_INHERIT)
|
|
309
|
-
BACKGROUND_PROPERTIES.
|
|
357
|
+
BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
|
|
310
358
|
else
|
|
311
359
|
{
|
|
312
360
|
'background-image' => value.slice!(CssParser::RE_IMAGE),
|
|
@@ -412,17 +460,17 @@ module CssParser
|
|
|
412
460
|
else
|
|
413
461
|
font_props['font-family'] = m
|
|
414
462
|
end
|
|
415
|
-
elsif
|
|
416
|
-
|
|
463
|
+
elsif /normal|inherit/i.match?(m)
|
|
464
|
+
FONT_WEIGHT_PROPERTIES.each do |font_prop|
|
|
417
465
|
font_props[font_prop] ||= m
|
|
418
466
|
end
|
|
419
|
-
elsif
|
|
467
|
+
elsif /italic|oblique/i.match?(m)
|
|
420
468
|
font_props['font-style'] = m
|
|
421
|
-
elsif
|
|
469
|
+
elsif /small-caps/i.match?(m)
|
|
422
470
|
font_props['font-variant'] = m
|
|
423
|
-
elsif
|
|
471
|
+
elsif /[1-9]00$|bold|bolder|lighter/i.match?(m)
|
|
424
472
|
font_props['font-weight'] = m
|
|
425
|
-
elsif
|
|
473
|
+
elsif CssParser::FONT_UNITS_RX.match?(m)
|
|
426
474
|
if m.include?('/')
|
|
427
475
|
font_props['font-size'], font_props['line-height'] = m.split('/', 2)
|
|
428
476
|
else
|
|
@@ -445,8 +493,8 @@ module CssParser
|
|
|
445
493
|
value = declaration.value.dup
|
|
446
494
|
|
|
447
495
|
replacement =
|
|
448
|
-
if
|
|
449
|
-
LIST_STYLE_PROPERTIES.
|
|
496
|
+
if CssParser::RE_INHERIT.match?(value)
|
|
497
|
+
LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
|
|
450
498
|
else
|
|
451
499
|
{
|
|
452
500
|
'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
|
|
@@ -511,15 +559,15 @@ module CssParser
|
|
|
511
559
|
#
|
|
512
560
|
# TODO: this is extremely similar to create_background_shorthand! and should be combined
|
|
513
561
|
def create_border_shorthand! # :nodoc:
|
|
514
|
-
values = BORDER_STYLE_PROPERTIES.
|
|
562
|
+
values = BORDER_STYLE_PROPERTIES.filter_map do |property|
|
|
515
563
|
next unless (declaration = declarations[property])
|
|
516
564
|
next if declaration.important
|
|
517
565
|
# can't merge if any value contains a space (i.e. has multiple values)
|
|
518
566
|
# we temporarily remove any spaces after commas for the check (inside rgba, etc...)
|
|
519
|
-
next if declaration.value.gsub(/,\s/, ',').strip
|
|
567
|
+
next if /\s/.match?(declaration.value.gsub(/,\s/, ',').strip)
|
|
520
568
|
|
|
521
569
|
declaration.value
|
|
522
|
-
end
|
|
570
|
+
end
|
|
523
571
|
|
|
524
572
|
return if values.size != BORDER_STYLE_PROPERTIES.size
|
|
525
573
|
|
|
@@ -536,7 +584,7 @@ module CssParser
|
|
|
536
584
|
return if declarations.size < NUMBER_OF_DIMENSIONS
|
|
537
585
|
|
|
538
586
|
DIMENSIONS.each do |property, dimensions|
|
|
539
|
-
values =
|
|
587
|
+
values = DIMENSION_DIRECTIONS.each_with_index.with_object({}) do |(side, index), result|
|
|
540
588
|
next unless (declaration = declarations[dimensions[index]])
|
|
541
589
|
|
|
542
590
|
result[side] = declaration.value
|
|
@@ -559,7 +607,7 @@ module CssParser
|
|
|
559
607
|
def create_font_shorthand! # :nodoc:
|
|
560
608
|
return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
|
|
561
609
|
|
|
562
|
-
new_value =
|
|
610
|
+
new_value = +''
|
|
563
611
|
['font-style', 'font-variant', 'font-weight'].each do |property|
|
|
564
612
|
unless declarations[property].value == 'normal'
|
|
565
613
|
new_value << declarations[property].value << ' '
|
|
@@ -596,7 +644,7 @@ module CssParser
|
|
|
596
644
|
return [:top] if values.values.uniq.count == 1
|
|
597
645
|
|
|
598
646
|
# `/* top | right | bottom | left */`
|
|
599
|
-
return
|
|
647
|
+
return DIMENSION_DIRECTIONS if values[:left] != values[:right]
|
|
600
648
|
|
|
601
649
|
# Vertical are the same & horizontal are the same, `/* vertical | horizontal */`
|
|
602
650
|
return [:top, :left] if values[:top] == values[:bottom]
|
|
@@ -610,20 +658,32 @@ module CssParser
|
|
|
610
658
|
return unless block
|
|
611
659
|
|
|
612
660
|
continuation = nil
|
|
613
|
-
block.split(
|
|
614
|
-
decs = (continuation ? continuation
|
|
615
|
-
if decs
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
value = matches[2]
|
|
621
|
-
add_declaration!(property, value)
|
|
622
|
-
continuation = nil
|
|
661
|
+
block.split(SEMICOLON) do |decs|
|
|
662
|
+
decs = (continuation ? "#{continuation};#{decs}" : decs)
|
|
663
|
+
if unmatched_open_parenthesis?(decs)
|
|
664
|
+
# Semicolon happened within parenthesis, so it is a part of the value
|
|
665
|
+
# the rest of the value is in the next segment
|
|
666
|
+
continuation = decs
|
|
667
|
+
next
|
|
623
668
|
end
|
|
669
|
+
|
|
670
|
+
next unless (colon = decs.index(COLON))
|
|
671
|
+
|
|
672
|
+
property = decs[0, colon]
|
|
673
|
+
value = decs[(colon + 1)..]
|
|
674
|
+
property.strip!
|
|
675
|
+
value.strip!
|
|
676
|
+
next if property.empty? || value.empty? || value.casecmp?(IMPORTANT)
|
|
677
|
+
|
|
678
|
+
add_declaration!(property, value)
|
|
679
|
+
continuation = nil
|
|
624
680
|
end
|
|
625
681
|
end
|
|
626
682
|
|
|
683
|
+
def unmatched_open_parenthesis?(declarations)
|
|
684
|
+
(lparen_index = declarations.index(LPAREN)) && !declarations.index(RPAREN, lparen_index)
|
|
685
|
+
end
|
|
686
|
+
|
|
627
687
|
#--
|
|
628
688
|
# TODO: way too simplistic
|
|
629
689
|
#++
|
|
@@ -648,18 +708,4 @@ module CssParser
|
|
|
648
708
|
end
|
|
649
709
|
end
|
|
650
710
|
end
|
|
651
|
-
|
|
652
|
-
class OffsetAwareRuleSet < RuleSet
|
|
653
|
-
# File offset range
|
|
654
|
-
attr_reader :offset
|
|
655
|
-
|
|
656
|
-
# the local or remote location
|
|
657
|
-
attr_accessor :filename
|
|
658
|
-
|
|
659
|
-
def initialize(filename, offset, selectors, block, specificity = nil)
|
|
660
|
-
super(selectors, block, specificity)
|
|
661
|
-
@offset = offset
|
|
662
|
-
@filename = filename
|
|
663
|
-
end
|
|
664
|
-
end
|
|
665
711
|
end
|
data/lib/css_parser/version.rb
CHANGED
data/lib/css_parser.rb
CHANGED
|
@@ -6,7 +6,6 @@ require 'net/https'
|
|
|
6
6
|
require 'digest/md5'
|
|
7
7
|
require 'zlib'
|
|
8
8
|
require 'stringio'
|
|
9
|
-
require 'iconv' unless String.method_defined?(:encode)
|
|
10
9
|
|
|
11
10
|
require 'css_parser/version'
|
|
12
11
|
require 'css_parser/rule_set'
|
|
@@ -58,7 +57,7 @@ module CssParser
|
|
|
58
57
|
# in case called like CssParser.merge([rule_set, rule_set])
|
|
59
58
|
rule_sets.flatten! if rule_sets[0].is_a?(Array)
|
|
60
59
|
|
|
61
|
-
unless rule_sets.all?
|
|
60
|
+
unless rule_sets.all?(CssParser::RuleSet)
|
|
62
61
|
raise ArgumentError, 'all parameters must be CssParser::RuleSets.'
|
|
63
62
|
end
|
|
64
63
|
|
|
@@ -71,17 +70,17 @@ module CssParser
|
|
|
71
70
|
rule_set.expand_shorthand!
|
|
72
71
|
|
|
73
72
|
specificity = rule_set.specificity
|
|
74
|
-
specificity ||= rule_set.selectors.
|
|
73
|
+
specificity ||= rule_set.selectors.filter_map { |s| calculate_specificity(s) }.max || 0
|
|
75
74
|
|
|
76
75
|
rule_set.each_declaration do |property, value, is_important|
|
|
77
76
|
# Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order
|
|
78
|
-
if
|
|
77
|
+
if !properties.key?(property)
|
|
79
78
|
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
|
80
79
|
elsif is_important
|
|
81
|
-
if
|
|
80
|
+
if !properties[property][:is_important] || properties[property][:specificity] <= specificity
|
|
82
81
|
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
|
83
82
|
end
|
|
84
|
-
elsif properties[property][:specificity] < specificity
|
|
83
|
+
elsif properties[property][:specificity] < specificity || properties[property][:specificity] == specificity
|
|
85
84
|
unless properties[property][:is_important]
|
|
86
85
|
properties[property] = {value: value, specificity: specificity, is_important: is_important}
|
|
87
86
|
end
|
|
@@ -111,7 +110,7 @@ module CssParser
|
|
|
111
110
|
#++
|
|
112
111
|
def self.calculate_specificity(selector)
|
|
113
112
|
a = 0
|
|
114
|
-
b = selector.scan(
|
|
113
|
+
b = selector.scan('#').length
|
|
115
114
|
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
|
|
116
115
|
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
|
|
117
116
|
|
|
@@ -139,7 +138,7 @@ module CssParser
|
|
|
139
138
|
css.gsub(URI_RX) do
|
|
140
139
|
uri = Regexp.last_match(1).to_s.gsub(/["']+/, '')
|
|
141
140
|
# Don't process URLs that are already absolute
|
|
142
|
-
unless uri.match(%r{^[a-z]+://}i)
|
|
141
|
+
unless uri.match?(%r{^[a-z]+://}i)
|
|
143
142
|
begin
|
|
144
143
|
uri = base_uri.join(uri)
|
|
145
144
|
rescue
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: css_parser
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.21.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Dunae
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-03-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -24,118 +24,6 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: benchmark-ips
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: bump
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: maxitest
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: memory_profiler
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rake
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: rubocop
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - "~>"
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '1.8'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - "~>"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '1.8'
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: rubocop-rake
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0'
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">="
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0'
|
|
125
|
-
- !ruby/object:Gem::Dependency
|
|
126
|
-
name: webrick
|
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
|
128
|
-
requirements:
|
|
129
|
-
- - ">="
|
|
130
|
-
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
132
|
-
type: :development
|
|
133
|
-
prerelease: false
|
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
-
requirements:
|
|
136
|
-
- - ">="
|
|
137
|
-
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
139
27
|
description: A set of classes for parsing CSS in Ruby.
|
|
140
28
|
email: code@dunae.ca
|
|
141
29
|
executables: []
|
|
@@ -155,7 +43,8 @@ metadata:
|
|
|
155
43
|
changelog_uri: https://github.com/premailer/css_parser/blob/master/CHANGELOG.md
|
|
156
44
|
source_code_uri: https://github.com/premailer/css_parser
|
|
157
45
|
bug_tracker_uri: https://github.com/premailer/css_parser/issues
|
|
158
|
-
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
post_install_message:
|
|
159
48
|
rdoc_options: []
|
|
160
49
|
require_paths:
|
|
161
50
|
- lib
|
|
@@ -163,15 +52,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
163
52
|
requirements:
|
|
164
53
|
- - ">="
|
|
165
54
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '
|
|
55
|
+
version: '3.0'
|
|
167
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
57
|
requirements:
|
|
169
58
|
- - ">="
|
|
170
59
|
- !ruby/object:Gem::Version
|
|
171
60
|
version: '0'
|
|
172
61
|
requirements: []
|
|
173
|
-
rubygems_version: 3.
|
|
174
|
-
signing_key:
|
|
62
|
+
rubygems_version: 3.4.19
|
|
63
|
+
signing_key:
|
|
175
64
|
specification_version: 4
|
|
176
65
|
summary: Ruby CSS parser.
|
|
177
66
|
test_files: []
|