css_parser 1.17.1 → 2.2.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/parser.rb +90 -47
- data/lib/css_parser/regexps.rb +5 -3
- data/lib/css_parser/rule_set.rb +90 -46
- data/lib/css_parser/version.rb +1 -1
- data/lib/css_parser.rb +4 -7
- metadata +4 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed16a56359267ef6ea44fe0e45a76611e1f8365f65e9437c4c556fd4dfaa983f
|
|
4
|
+
data.tar.gz: 51136b897f4f511bd1d285138e366e8d66e090d7a8b5811afd34178e804bfd35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51fd7d3070a36f6a8d048f4eefcc6e5e3a85bef1f14dd14d751242bb35de0b2efb1a6278641980183e2a27e5c33ad84b9b39ed04e768f7439a76a015572f393c
|
|
7
|
+
data.tar.gz: f3116a5e15595a2c869507d43eb8c744eb19d26c850991d9eeeb4ff190956191c2136c5add0408402177fa865cf18903fc917dd43c6932ce32ed5cb8843aa992
|
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,7 +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)"
|
|
21
|
+
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)".freeze
|
|
22
|
+
RULESET_TOKENIZER_RX = /\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/.freeze
|
|
20
23
|
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
|
|
21
24
|
STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
|
|
22
25
|
|
|
@@ -28,12 +31,6 @@ module CssParser
|
|
|
28
31
|
# Array of CSS files that have been loaded.
|
|
29
32
|
attr_reader :loaded_uris
|
|
30
33
|
|
|
31
|
-
#--
|
|
32
|
-
# Class variable? see http://www.oreillynet.com/ruby/blog/2007/01/nubygems_dont_use_class_variab_1.html
|
|
33
|
-
#++
|
|
34
|
-
@folded_declaration_cache = {}
|
|
35
|
-
class << self; attr_reader :folded_declaration_cache; end
|
|
36
|
-
|
|
37
34
|
def initialize(options = {})
|
|
38
35
|
@options = {
|
|
39
36
|
absolute_paths: false,
|
|
@@ -133,14 +130,14 @@ module CssParser
|
|
|
133
130
|
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
|
134
131
|
media_types = []
|
|
135
132
|
if (media_string = import_rule[-1])
|
|
136
|
-
media_string.split(
|
|
133
|
+
media_string.split(',').each do |t|
|
|
137
134
|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
|
|
138
135
|
end
|
|
139
136
|
else
|
|
140
137
|
media_types = [:all]
|
|
141
138
|
end
|
|
142
139
|
|
|
143
|
-
next unless options[:only_media_types].include?(:all) or media_types.empty? or
|
|
140
|
+
next unless options[:only_media_types].include?(:all) or media_types.empty? or media_types.intersect?(options[:only_media_types])
|
|
144
141
|
|
|
145
142
|
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
|
146
143
|
|
|
@@ -164,14 +161,44 @@ module CssParser
|
|
|
164
161
|
parse_block_into_rule_sets!(block, options)
|
|
165
162
|
end
|
|
166
163
|
|
|
167
|
-
# Add a CSS rule by setting the +selectors+, +declarations+
|
|
164
|
+
# Add a CSS rule by setting the +selectors+, +declarations+
|
|
165
|
+
# and +media_types+. Optional pass +filename+ , +offset+ for source
|
|
166
|
+
# reference too.
|
|
168
167
|
#
|
|
169
|
-
# +media_types+ can be a symbol or an array of symbols.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
# +media_types+ can be a symbol or an array of symbols. default to :all
|
|
169
|
+
# optional fields for source location for source location
|
|
170
|
+
# +filename+ can be a string or uri pointing to the file or url location.
|
|
171
|
+
# +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
|
|
172
|
+
def add_rule!(*args, selectors: nil, block: nil, filename: nil, offset: nil, media_types: :all) # rubocop:disable Metrics/ParameterLists
|
|
173
|
+
if args.any?
|
|
174
|
+
media_types = nil
|
|
175
|
+
if selectors || block || filename || offset || media_types
|
|
176
|
+
raise ArgumentError, "don't mix positional and keyword arguments arguments"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
warn '[DEPRECATION] `add_rule!` with positional arguments is deprecated. ' \
|
|
180
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
181
|
+
|
|
182
|
+
case args.length
|
|
183
|
+
when 2
|
|
184
|
+
selectors, block = args
|
|
185
|
+
when 3
|
|
186
|
+
selectors, block, media_types = args
|
|
187
|
+
else
|
|
188
|
+
raise ArgumentError
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
begin
|
|
193
|
+
rule_set = RuleSet.new(
|
|
194
|
+
selectors: selectors, block: block,
|
|
195
|
+
offset: offset, filename: filename
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
add_rule_set!(rule_set, media_types)
|
|
199
|
+
rescue ArgumentError => e
|
|
200
|
+
raise e if @options[:rule_set_exceptions]
|
|
201
|
+
end
|
|
175
202
|
end
|
|
176
203
|
|
|
177
204
|
# Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
|
|
@@ -180,8 +207,11 @@ module CssParser
|
|
|
180
207
|
# +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
|
|
181
208
|
# +media_types+ can be a symbol or an array of symbols.
|
|
182
209
|
def add_rule_with_offsets!(selectors, declarations, filename, offset, media_types = :all)
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
warn '[DEPRECATION] `add_rule_with_offsets!` is deprecated. Please use `add_rule!` instead.', uplevel: 1
|
|
211
|
+
add_rule!(
|
|
212
|
+
selectors: selectors, block: declarations, media_types: media_types,
|
|
213
|
+
filename: filename, offset: offset
|
|
214
|
+
)
|
|
185
215
|
end
|
|
186
216
|
|
|
187
217
|
# Add a CssParser RuleSet object.
|
|
@@ -323,17 +353,21 @@ module CssParser
|
|
|
323
353
|
in_at_media_rule = false
|
|
324
354
|
in_media_block = false
|
|
325
355
|
|
|
326
|
-
current_selectors =
|
|
327
|
-
current_media_query =
|
|
328
|
-
current_declarations =
|
|
356
|
+
current_selectors = +''
|
|
357
|
+
current_media_query = +''
|
|
358
|
+
current_declarations = +''
|
|
329
359
|
|
|
330
360
|
# once we are in a rule, we will use this to store where we started if we are capturing offsets
|
|
331
361
|
rule_start = nil
|
|
332
|
-
|
|
362
|
+
start_offset = nil
|
|
363
|
+
end_offset = nil
|
|
333
364
|
|
|
334
|
-
|
|
365
|
+
scanner = StringScanner.new(block)
|
|
366
|
+
until scanner.eos?
|
|
335
367
|
# save the regex offset so that we know where in the file we are
|
|
336
|
-
|
|
368
|
+
start_offset = scanner.pos
|
|
369
|
+
token = scanner.scan(RULESET_TOKENIZER_RX)
|
|
370
|
+
end_offset = scanner.pos
|
|
337
371
|
|
|
338
372
|
if token.start_with?('"') # found un-escaped double quote
|
|
339
373
|
in_string = !in_string
|
|
@@ -360,20 +394,24 @@ module CssParser
|
|
|
360
394
|
current_declarations.strip!
|
|
361
395
|
|
|
362
396
|
unless current_declarations.empty?
|
|
397
|
+
add_rule_options = {
|
|
398
|
+
selectors: current_selectors, block: current_declarations,
|
|
399
|
+
media_types: current_media_queries
|
|
400
|
+
}
|
|
363
401
|
if options[:capture_offsets]
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
add_rule!(current_selectors, current_declarations, current_media_queries)
|
|
402
|
+
add_rule_options[:filename] = options[:filename]
|
|
403
|
+
add_rule_options[:offset] = rule_start..end_offset
|
|
367
404
|
end
|
|
405
|
+
add_rule!(**add_rule_options)
|
|
368
406
|
end
|
|
369
407
|
|
|
370
|
-
current_selectors =
|
|
371
|
-
current_declarations =
|
|
408
|
+
current_selectors = +''
|
|
409
|
+
current_declarations = +''
|
|
372
410
|
|
|
373
411
|
# restart our search for selectors and declarations
|
|
374
412
|
rule_start = nil if options[:capture_offsets]
|
|
375
413
|
end
|
|
376
|
-
elsif
|
|
414
|
+
elsif /@media/i.match?(token)
|
|
377
415
|
# found '@media', reset current media_types
|
|
378
416
|
in_at_media_rule = true
|
|
379
417
|
current_media_queries = []
|
|
@@ -383,14 +421,14 @@ module CssParser
|
|
|
383
421
|
in_at_media_rule = false
|
|
384
422
|
in_media_block = true
|
|
385
423
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
|
386
|
-
current_media_query =
|
|
424
|
+
current_media_query = +''
|
|
387
425
|
elsif token.include?(',')
|
|
388
426
|
# new media query begins
|
|
389
427
|
token.tr!(',', ' ')
|
|
390
428
|
token.strip!
|
|
391
429
|
current_media_query << token << ' '
|
|
392
430
|
current_media_queries << CssParser.sanitize_media_query(current_media_query)
|
|
393
|
-
current_media_query =
|
|
431
|
+
current_media_query = +''
|
|
394
432
|
else
|
|
395
433
|
token.strip!
|
|
396
434
|
# special-case the ( and ) tokens to remove inner-whitespace
|
|
@@ -404,7 +442,7 @@ module CssParser
|
|
|
404
442
|
current_media_query << token << ' '
|
|
405
443
|
end
|
|
406
444
|
end
|
|
407
|
-
elsif in_charset or
|
|
445
|
+
elsif in_charset or /@charset/i.match?(token)
|
|
408
446
|
# iterate until we are out of the charset declaration
|
|
409
447
|
in_charset = !token.include?(';')
|
|
410
448
|
elsif !in_string && token.include?('}')
|
|
@@ -423,18 +461,22 @@ module CssParser
|
|
|
423
461
|
current_selectors << token
|
|
424
462
|
|
|
425
463
|
# mark this as the beginning of the selector unless we have already marked it
|
|
426
|
-
rule_start =
|
|
464
|
+
rule_start = start_offset if options[:capture_offsets] && rule_start.nil? && /^[^\s]+$/.match?(token)
|
|
427
465
|
end
|
|
428
466
|
end
|
|
429
467
|
|
|
430
468
|
# check for unclosed braces
|
|
431
469
|
return unless in_declarations > 0
|
|
432
470
|
|
|
433
|
-
|
|
434
|
-
|
|
471
|
+
add_rule_options = {
|
|
472
|
+
selectors: current_selectors, block: current_declarations,
|
|
473
|
+
media_types: current_media_queries
|
|
474
|
+
}
|
|
475
|
+
if options[:capture_offsets]
|
|
476
|
+
add_rule_options[:filename] = options[:filename]
|
|
477
|
+
add_rule_options[:offset] = rule_start..end_offset
|
|
435
478
|
end
|
|
436
|
-
|
|
437
|
-
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
|
|
479
|
+
add_rule!(**add_rule_options)
|
|
438
480
|
end
|
|
439
481
|
|
|
440
482
|
# Load a remote CSS file.
|
|
@@ -452,6 +494,8 @@ module CssParser
|
|
|
452
494
|
if options.is_a? Hash
|
|
453
495
|
opts.merge!(options)
|
|
454
496
|
else
|
|
497
|
+
warn '[DEPRECATION] `load_uri!` with positional arguments is deprecated. ' \
|
|
498
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
455
499
|
opts[:base_uri] = options if options.is_a? String
|
|
456
500
|
opts[:media_types] = deprecated if deprecated
|
|
457
501
|
end
|
|
@@ -478,6 +522,8 @@ module CssParser
|
|
|
478
522
|
if options.is_a? Hash
|
|
479
523
|
opts.merge!(options)
|
|
480
524
|
else
|
|
525
|
+
warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
|
|
526
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
481
527
|
opts[:base_dir] = options if options.is_a? String
|
|
482
528
|
opts[:media_types] = deprecated if deprecated
|
|
483
529
|
end
|
|
@@ -501,6 +547,8 @@ module CssParser
|
|
|
501
547
|
if options.is_a? Hash
|
|
502
548
|
opts.merge!(options)
|
|
503
549
|
else
|
|
550
|
+
warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
|
|
551
|
+
'Please use keyword arguments instead.', uplevel: 1
|
|
504
552
|
opts[:base_dir] = options if options.is_a? String
|
|
505
553
|
opts[:media_types] = deprecated if deprecated
|
|
506
554
|
end
|
|
@@ -514,7 +562,8 @@ module CssParser
|
|
|
514
562
|
#
|
|
515
563
|
# Raises a CircularReferenceError exception if io_exceptions are on,
|
|
516
564
|
# otherwise returns true/false.
|
|
517
|
-
|
|
565
|
+
# TODO: fix rubocop
|
|
566
|
+
def circular_reference_check(path) # rubocop:disable Naming/PredicateMethod
|
|
518
567
|
path = path.to_s
|
|
519
568
|
if @loaded_uris.include?(path)
|
|
520
569
|
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
|
|
@@ -594,7 +643,6 @@ module CssParser
|
|
|
594
643
|
uri.port = 443 unless uri.port
|
|
595
644
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
596
645
|
http.use_ssl = true
|
|
597
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
598
646
|
else
|
|
599
647
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
600
648
|
end
|
|
@@ -625,12 +673,7 @@ module CssParser
|
|
|
625
673
|
end
|
|
626
674
|
|
|
627
675
|
if charset
|
|
628
|
-
|
|
629
|
-
src.encode!('UTF-8', charset)
|
|
630
|
-
else
|
|
631
|
-
ic = Iconv.new('UTF-8//IGNORE', charset)
|
|
632
|
-
src = ic.iconv(src)
|
|
633
|
-
end
|
|
676
|
+
src.encode!('UTF-8', charset)
|
|
634
677
|
end
|
|
635
678
|
rescue
|
|
636
679
|
@redirect_count = nil
|
|
@@ -671,7 +714,7 @@ module CssParser
|
|
|
671
714
|
nodes = {}
|
|
672
715
|
lines.each do |line|
|
|
673
716
|
parts = line.split(':', 2)
|
|
674
|
-
if parts[1]
|
|
717
|
+
if parts[1].include?(':')
|
|
675
718
|
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
|
|
676
719
|
else
|
|
677
720
|
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
|
data/lib/css_parser/regexps.rb
CHANGED
|
@@ -11,7 +11,7 @@ module CssParser
|
|
|
11
11
|
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE | Regexp::NOENCODING) # [^\0-\177]
|
|
12
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
|
|
@@ -259,8 +259,10 @@ module CssParser
|
|
|
259
259
|
inherit
|
|
260
260
|
currentColor
|
|
261
261
|
].freeze
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
# CSS <number> allows the integer part to be omitted (e.g. `.1`), per CSS Values & Units.
|
|
263
|
+
# `(?:\d*\.)?\d+` accepts `1`, `1.5`, and `.5` while still rejecting bare `1.`.
|
|
264
|
+
RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?(?:\d*\.)?\d+%?\s*%?,-?\s*-?(?:\d*\.)?\d+%?\s*%?,-?\s*-?(?:\d*\.)?\d+%?\s*%?\)/i.freeze
|
|
265
|
+
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
|
|
264
266
|
RE_COLOUR_HEX = /\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/.freeze
|
|
265
267
|
RE_COLOUR_NAMED = /\s*\b(#{NAMED_COLOURS.join('|')})\b/i.freeze
|
|
266
268
|
RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
|
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,17 +89,20 @@ 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
|
|
96
107
|
rescue ArgumentError => e
|
|
97
108
|
raise e.exception, "#{property} #{e.message}"
|
|
@@ -142,7 +153,7 @@ module CssParser
|
|
|
142
153
|
|
|
143
154
|
if preserve_importance
|
|
144
155
|
importance = get_value(property).important
|
|
145
|
-
replacement_declarations.
|
|
156
|
+
replacement_declarations.each_value { |value| value.important = importance }
|
|
146
157
|
end
|
|
147
158
|
|
|
148
159
|
replacement_keys = declarations.keys
|
|
@@ -190,7 +201,7 @@ module CssParser
|
|
|
190
201
|
end
|
|
191
202
|
|
|
192
203
|
def to_s(options = {})
|
|
193
|
-
str = declarations.reduce(
|
|
204
|
+
str = declarations.reduce(+'') do |memo, (prop, value)|
|
|
194
205
|
importance = options[:force_important] || value.important ? ' !important' : ''
|
|
195
206
|
memo << "#{prop}: #{value.value}#{importance}; "
|
|
196
207
|
end
|
|
@@ -223,6 +234,12 @@ module CssParser
|
|
|
223
234
|
|
|
224
235
|
extend Forwardable
|
|
225
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
|
+
|
|
226
243
|
# Array of selector strings.
|
|
227
244
|
attr_reader :selectors
|
|
228
245
|
|
|
@@ -237,9 +254,38 @@ module CssParser
|
|
|
237
254
|
alias []= add_declaration!
|
|
238
255
|
alias remove_declaration! delete
|
|
239
256
|
|
|
240
|
-
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
|
+
|
|
241
279
|
@selectors = []
|
|
242
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
|
+
|
|
243
289
|
parse_selectors!(selectors) if selectors
|
|
244
290
|
parse_declarations!(block)
|
|
245
291
|
end
|
|
@@ -308,7 +354,7 @@ module CssParser
|
|
|
308
354
|
|
|
309
355
|
replacement =
|
|
310
356
|
if value.match(CssParser::RE_INHERIT)
|
|
311
|
-
BACKGROUND_PROPERTIES.
|
|
357
|
+
BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
|
|
312
358
|
else
|
|
313
359
|
{
|
|
314
360
|
'background-image' => value.slice!(CssParser::RE_IMAGE),
|
|
@@ -414,17 +460,17 @@ module CssParser
|
|
|
414
460
|
else
|
|
415
461
|
font_props['font-family'] = m
|
|
416
462
|
end
|
|
417
|
-
elsif
|
|
418
|
-
|
|
463
|
+
elsif /normal|inherit/i.match?(m)
|
|
464
|
+
FONT_WEIGHT_PROPERTIES.each do |font_prop|
|
|
419
465
|
font_props[font_prop] ||= m
|
|
420
466
|
end
|
|
421
|
-
elsif
|
|
467
|
+
elsif /italic|oblique/i.match?(m)
|
|
422
468
|
font_props['font-style'] = m
|
|
423
|
-
elsif
|
|
469
|
+
elsif /small-caps/i.match?(m)
|
|
424
470
|
font_props['font-variant'] = m
|
|
425
|
-
elsif
|
|
471
|
+
elsif /[1-9]00$|bold|bolder|lighter/i.match?(m)
|
|
426
472
|
font_props['font-weight'] = m
|
|
427
|
-
elsif
|
|
473
|
+
elsif CssParser::FONT_UNITS_RX.match?(m)
|
|
428
474
|
if m.include?('/')
|
|
429
475
|
font_props['font-size'], font_props['line-height'] = m.split('/', 2)
|
|
430
476
|
else
|
|
@@ -447,8 +493,8 @@ module CssParser
|
|
|
447
493
|
value = declaration.value.dup
|
|
448
494
|
|
|
449
495
|
replacement =
|
|
450
|
-
if
|
|
451
|
-
LIST_STYLE_PROPERTIES.
|
|
496
|
+
if CssParser::RE_INHERIT.match?(value)
|
|
497
|
+
LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
|
|
452
498
|
else
|
|
453
499
|
{
|
|
454
500
|
'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
|
|
@@ -513,15 +559,15 @@ module CssParser
|
|
|
513
559
|
#
|
|
514
560
|
# TODO: this is extremely similar to create_background_shorthand! and should be combined
|
|
515
561
|
def create_border_shorthand! # :nodoc:
|
|
516
|
-
values = BORDER_STYLE_PROPERTIES.
|
|
562
|
+
values = BORDER_STYLE_PROPERTIES.filter_map do |property|
|
|
517
563
|
next unless (declaration = declarations[property])
|
|
518
564
|
next if declaration.important
|
|
519
565
|
# can't merge if any value contains a space (i.e. has multiple values)
|
|
520
566
|
# we temporarily remove any spaces after commas for the check (inside rgba, etc...)
|
|
521
|
-
next if declaration.value.gsub(/,\s/, ',').strip
|
|
567
|
+
next if /\s/.match?(declaration.value.gsub(/,\s/, ',').strip)
|
|
522
568
|
|
|
523
569
|
declaration.value
|
|
524
|
-
end
|
|
570
|
+
end
|
|
525
571
|
|
|
526
572
|
return if values.size != BORDER_STYLE_PROPERTIES.size
|
|
527
573
|
|
|
@@ -538,7 +584,7 @@ module CssParser
|
|
|
538
584
|
return if declarations.size < NUMBER_OF_DIMENSIONS
|
|
539
585
|
|
|
540
586
|
DIMENSIONS.each do |property, dimensions|
|
|
541
|
-
values =
|
|
587
|
+
values = DIMENSION_DIRECTIONS.each_with_index.with_object({}) do |(side, index), result|
|
|
542
588
|
next unless (declaration = declarations[dimensions[index]])
|
|
543
589
|
|
|
544
590
|
result[side] = declaration.value
|
|
@@ -561,7 +607,7 @@ module CssParser
|
|
|
561
607
|
def create_font_shorthand! # :nodoc:
|
|
562
608
|
return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
|
|
563
609
|
|
|
564
|
-
new_value =
|
|
610
|
+
new_value = +''
|
|
565
611
|
['font-style', 'font-variant', 'font-weight'].each do |property|
|
|
566
612
|
unless declarations[property].value == 'normal'
|
|
567
613
|
new_value << declarations[property].value << ' '
|
|
@@ -598,7 +644,7 @@ module CssParser
|
|
|
598
644
|
return [:top] if values.values.uniq.count == 1
|
|
599
645
|
|
|
600
646
|
# `/* top | right | bottom | left */`
|
|
601
|
-
return
|
|
647
|
+
return DIMENSION_DIRECTIONS if values[:left] != values[:right]
|
|
602
648
|
|
|
603
649
|
# Vertical are the same & horizontal are the same, `/* vertical | horizontal */`
|
|
604
650
|
return [:top, :left] if values[:top] == values[:bottom]
|
|
@@ -612,20 +658,32 @@ module CssParser
|
|
|
612
658
|
return unless block
|
|
613
659
|
|
|
614
660
|
continuation = nil
|
|
615
|
-
block.split(
|
|
616
|
-
decs = (continuation ? continuation
|
|
617
|
-
if decs
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
value = matches[2]
|
|
623
|
-
add_declaration!(property, value)
|
|
624
|
-
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
|
|
625
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
|
|
626
680
|
end
|
|
627
681
|
end
|
|
628
682
|
|
|
683
|
+
def unmatched_open_parenthesis?(declarations)
|
|
684
|
+
(lparen_index = declarations.index(LPAREN)) && !declarations.index(RPAREN, lparen_index)
|
|
685
|
+
end
|
|
686
|
+
|
|
629
687
|
#--
|
|
630
688
|
# TODO: way too simplistic
|
|
631
689
|
#++
|
|
@@ -650,18 +708,4 @@ module CssParser
|
|
|
650
708
|
end
|
|
651
709
|
end
|
|
652
710
|
end
|
|
653
|
-
|
|
654
|
-
class OffsetAwareRuleSet < RuleSet
|
|
655
|
-
# File offset range
|
|
656
|
-
attr_reader :offset
|
|
657
|
-
|
|
658
|
-
# the local or remote location
|
|
659
|
-
attr_accessor :filename
|
|
660
|
-
|
|
661
|
-
def initialize(filename, offset, selectors, block, specificity = nil)
|
|
662
|
-
super(selectors, block, specificity)
|
|
663
|
-
@offset = offset
|
|
664
|
-
@filename = filename
|
|
665
|
-
end
|
|
666
|
-
end
|
|
667
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'
|
|
@@ -53,12 +52,10 @@ module CssParser
|
|
|
53
52
|
# TODO: declaration_hashes should be able to contain a RuleSet
|
|
54
53
|
# this should be a Class method
|
|
55
54
|
def self.merge(*rule_sets)
|
|
56
|
-
@folded_declaration_cache = {}
|
|
57
|
-
|
|
58
55
|
# in case called like CssParser.merge([rule_set, rule_set])
|
|
59
56
|
rule_sets.flatten! if rule_sets[0].is_a?(Array)
|
|
60
57
|
|
|
61
|
-
unless rule_sets.all?
|
|
58
|
+
unless rule_sets.all?(CssParser::RuleSet)
|
|
62
59
|
raise ArgumentError, 'all parameters must be CssParser::RuleSets.'
|
|
63
60
|
end
|
|
64
61
|
|
|
@@ -71,7 +68,7 @@ module CssParser
|
|
|
71
68
|
rule_set.expand_shorthand!
|
|
72
69
|
|
|
73
70
|
specificity = rule_set.specificity
|
|
74
|
-
specificity ||= rule_set.selectors.
|
|
71
|
+
specificity ||= rule_set.selectors.filter_map { |s| calculate_specificity(s) }.max || 0
|
|
75
72
|
|
|
76
73
|
rule_set.each_declaration do |property, value, is_important|
|
|
77
74
|
# Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order
|
|
@@ -111,7 +108,7 @@ module CssParser
|
|
|
111
108
|
#++
|
|
112
109
|
def self.calculate_specificity(selector)
|
|
113
110
|
a = 0
|
|
114
|
-
b = selector.scan(
|
|
111
|
+
b = selector.scan('#').length
|
|
115
112
|
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
|
|
116
113
|
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
|
|
117
114
|
|
|
@@ -139,7 +136,7 @@ module CssParser
|
|
|
139
136
|
css.gsub(URI_RX) do
|
|
140
137
|
uri = Regexp.last_match(1).to_s.gsub(/["']+/, '')
|
|
141
138
|
# Don't process URLs that are already absolute
|
|
142
|
-
unless uri.match(%r{^[a-z]+://}i)
|
|
139
|
+
unless uri.match?(%r{^[a-z]+://}i)
|
|
143
140
|
begin
|
|
144
141
|
uri = base_uri.join(uri)
|
|
145
142
|
rescue
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: css_parser
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Dunae
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: addressable
|
|
@@ -24,118 +23,6 @@ dependencies:
|
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
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
26
|
description: A set of classes for parsing CSS in Ruby.
|
|
140
27
|
email: code@dunae.ca
|
|
141
28
|
executables: []
|
|
@@ -156,7 +43,6 @@ metadata:
|
|
|
156
43
|
source_code_uri: https://github.com/premailer/css_parser
|
|
157
44
|
bug_tracker_uri: https://github.com/premailer/css_parser/issues
|
|
158
45
|
rubygems_mfa_required: 'true'
|
|
159
|
-
post_install_message:
|
|
160
46
|
rdoc_options: []
|
|
161
47
|
require_paths:
|
|
162
48
|
- lib
|
|
@@ -164,15 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
164
50
|
requirements:
|
|
165
51
|
- - ">="
|
|
166
52
|
- !ruby/object:Gem::Version
|
|
167
|
-
version: '
|
|
53
|
+
version: '3.3'
|
|
168
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
55
|
requirements:
|
|
170
56
|
- - ">="
|
|
171
57
|
- !ruby/object:Gem::Version
|
|
172
58
|
version: '0'
|
|
173
59
|
requirements: []
|
|
174
|
-
rubygems_version:
|
|
175
|
-
signing_key:
|
|
60
|
+
rubygems_version: 4.0.3
|
|
176
61
|
specification_version: 4
|
|
177
62
|
summary: Ruby CSS parser.
|
|
178
63
|
test_files: []
|