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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f2b7e6abc1f1bea049098d58fd072a7a17985203d25eb87fe9a62e3d3329bc3
4
- data.tar.gz: ebb09d74054507e148544ad150cd8e3470a0601b736f9708bae567d26c08794c
3
+ metadata.gz: ed16a56359267ef6ea44fe0e45a76611e1f8365f65e9437c4c556fd4dfaa983f
4
+ data.tar.gz: 51136b897f4f511bd1d285138e366e8d66e090d7a8b5811afd34178e804bfd35
5
5
  SHA512:
6
- metadata.gz: 4ac7f707b7394d57bef2dea84887b07239cc446b15e5c4d49f3097b311950a95c7816e2b3acc81842570a827f3eb959d8d0081f974b92d9b97f4772cb9c312bd
7
- data.tar.gz: a52877e078998a7d4b47938db8f41d67c2fc2a293a609e78efc54d9fbf403bae3cff8bb5145765b518d19d1a86177ae09eb44b6d8bdbafc07bcadd51672a94d1
6
+ metadata.gz: 51fd7d3070a36f6a8d048f4eefcc6e5e3a85bef1f14dd14d751242bb35de0b2efb1a6278641980183e2a27e5c33ad84b9b39ed04e768f7439a76a015572f393c
7
+ data.tar.gz: f3116a5e15595a2c869507d43eb8c744eb19d26c850991d9eeeb4ff190956191c2136c5add0408402177fa865cf18903fc917dd43c6932ce32ed5cb8843aa992
@@ -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(/,/).each do |t|
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 !(media_types & options[:only_media_types]).empty?
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+ and +media_types+.
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
- def add_rule!(selectors, declarations, media_types = :all)
171
- rule_set = RuleSet.new(selectors, declarations)
172
- add_rule_set!(rule_set, media_types)
173
- rescue ArgumentError => e
174
- raise e if @options[:rule_set_exceptions]
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
- rule_set = OffsetAwareRuleSet.new(filename, offset, selectors, declarations)
184
- add_rule_set!(rule_set, media_types)
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 = String.new
327
- current_media_query = String.new
328
- current_declarations = String.new
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
- offset = nil
362
+ start_offset = nil
363
+ end_offset = nil
333
364
 
334
- block.scan(/\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/) do |token|
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
- offset = Regexp.last_match.offset(0) if options[:capture_offsets]
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
- add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
365
- else
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 = String.new
371
- current_declarations = String.new
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 token =~ /@media/i
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 = String.new
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 = String.new
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 token =~ /@charset/i
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 = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
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
- unless options[:capture_offsets]
434
- return add_rule!(current_selectors, current_declarations, current_media_queries)
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
- def circular_reference_check(path)
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
- if String.method_defined?(:encode)
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
@@ -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("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE | Regexp::NOENCODING)
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
- RE_COLOUR_NUMERIC = /\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
263
- 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
+ # 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)
@@ -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
- declarations[property] = Value.new(value)
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.each { |_key, value| value.important = importance }
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(String.new) do |memo, (prop, value)|
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, specificity = nil)
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.map { |key| [key, 'inherit'] }.to_h
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 m =~ /normal|inherit/i
418
- ['font-style', 'font-weight', 'font-variant'].each do |font_prop|
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 m =~ /italic|oblique/i
467
+ elsif /italic|oblique/i.match?(m)
422
468
  font_props['font-style'] = m
423
- elsif m =~ /small-caps/i
469
+ elsif /small-caps/i.match?(m)
424
470
  font_props['font-variant'] = m
425
- elsif m =~ /[1-9]00$|bold|bolder|lighter/i
471
+ elsif /[1-9]00$|bold|bolder|lighter/i.match?(m)
426
472
  font_props['font-weight'] = m
427
- elsif m =~ CssParser::FONT_UNITS_RX
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 value =~ CssParser::RE_INHERIT
451
- LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
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.map do |property|
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 =~ /\s/
567
+ next if /\s/.match?(declaration.value.gsub(/,\s/, ',').strip)
522
568
 
523
569
  declaration.value
524
- end.compact
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 = [:top, :right, :bottom, :left].each_with_index.with_object({}) do |(side, index), result|
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 = String.new
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 [:top, :right, :bottom, :left] if values[:left] != values[:right]
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(/[;$]+/m).each do |decs|
616
- decs = (continuation ? continuation + decs : decs)
617
- if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
618
- continuation = "#{decs};"
619
- elsif (matches = decs.match(/\s*(.[^:]*)\s*:\s*(?m:(.+))(?:;?\s*\Z)/i))
620
- # skip end_of_declaration
621
- property = matches[1]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CssParser
4
- VERSION = '1.17.1'.freeze
4
+ VERSION = '2.2.0'.freeze
5
5
  end
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? { |rs| rs.is_a?(CssParser::RuleSet) }
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.map { |s| calculate_specificity(s) }.compact.max || 0
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(/\#/).length
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: 1.17.1
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: 2024-04-07 00:00:00.000000000 Z
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: '2.7'
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: 3.1.6
175
- signing_key:
60
+ rubygems_version: 4.0.3
176
61
  specification_version: 4
177
62
  summary: Ruby CSS parser.
178
63
  test_files: []