css_parser 1.17.1 → 1.21.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: c8880ebd6978e8a8a3108ea93b4d567252aee152b28f237dab31809f1488f42e
4
+ data.tar.gz: 4e8379acf246b7fe2573a2ada4fc49c01e9c3b2de43fe80d07f42390f8f16450
5
5
  SHA512:
6
- metadata.gz: 4ac7f707b7394d57bef2dea84887b07239cc446b15e5c4d49f3097b311950a95c7816e2b3acc81842570a827f3eb959d8d0081f974b92d9b97f4772cb9c312bd
7
- data.tar.gz: a52877e078998a7d4b47938db8f41d67c2fc2a293a609e78efc54d9fbf403bae3cff8bb5145765b518d19d1a86177ae09eb44b6d8bdbafc07bcadd51672a94d1
6
+ metadata.gz: 480947bb06a6c40ab955f76ff9eb7ba9ac5caf160d7fe343d16901e9f37210c9ccab22a07a4d3165ec26bec4cd28547f530718978d2d969ed398c8195351a0c0
7
+ data.tar.gz: e7a757c9dc096e71d5f617343f0900bacddea42bef4b26ca68f2830728a480ace908807dc4db82b6b0b77a11c1b2c9295ad814bb2aaa80fd069c959cd58afe65
@@ -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
 
@@ -133,7 +136,7 @@ module CssParser
133
136
  block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
134
137
  media_types = []
135
138
  if (media_string = import_rule[-1])
136
- media_string.split(/,/).each do |t|
139
+ media_string.split(',').each do |t|
137
140
  media_types << CssParser.sanitize_media_query(t) unless t.empty?
138
141
  end
139
142
  else
@@ -164,14 +167,44 @@ module CssParser
164
167
  parse_block_into_rule_sets!(block, options)
165
168
  end
166
169
 
167
- # Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
170
+ # Add a CSS rule by setting the +selectors+, +declarations+
171
+ # and +media_types+. Optional pass +filename+ , +offset+ for source
172
+ # reference too.
168
173
  #
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]
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
175
208
  end
176
209
 
177
210
  # Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
@@ -180,8 +213,11 @@ module CssParser
180
213
  # +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
181
214
  # +media_types+ can be a symbol or an array of symbols.
182
215
  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)
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
+ )
185
221
  end
186
222
 
187
223
  # Add a CssParser RuleSet object.
@@ -323,17 +359,21 @@ module CssParser
323
359
  in_at_media_rule = false
324
360
  in_media_block = false
325
361
 
326
- current_selectors = String.new
327
- current_media_query = String.new
328
- current_declarations = String.new
362
+ current_selectors = +''
363
+ current_media_query = +''
364
+ current_declarations = +''
329
365
 
330
366
  # once we are in a rule, we will use this to store where we started if we are capturing offsets
331
367
  rule_start = nil
332
- offset = nil
368
+ start_offset = nil
369
+ end_offset = nil
333
370
 
334
- block.scan(/\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/) do |token|
371
+ scanner = StringScanner.new(block)
372
+ until scanner.eos?
335
373
  # 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]
374
+ start_offset = scanner.pos
375
+ token = scanner.scan(RULESET_TOKENIZER_RX)
376
+ end_offset = scanner.pos
337
377
 
338
378
  if token.start_with?('"') # found un-escaped double quote
339
379
  in_string = !in_string
@@ -360,20 +400,24 @@ module CssParser
360
400
  current_declarations.strip!
361
401
 
362
402
  unless current_declarations.empty?
403
+ add_rule_options = {
404
+ selectors: current_selectors, block: current_declarations,
405
+ media_types: current_media_queries
406
+ }
363
407
  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)
408
+ add_rule_options[:filename] = options[:filename]
409
+ add_rule_options[:offset] = rule_start..end_offset
367
410
  end
411
+ add_rule!(**add_rule_options)
368
412
  end
369
413
 
370
- current_selectors = String.new
371
- current_declarations = String.new
414
+ current_selectors = +''
415
+ current_declarations = +''
372
416
 
373
417
  # restart our search for selectors and declarations
374
418
  rule_start = nil if options[:capture_offsets]
375
419
  end
376
- elsif token =~ /@media/i
420
+ elsif /@media/i.match?(token)
377
421
  # found '@media', reset current media_types
378
422
  in_at_media_rule = true
379
423
  current_media_queries = []
@@ -383,14 +427,14 @@ module CssParser
383
427
  in_at_media_rule = false
384
428
  in_media_block = true
385
429
  current_media_queries << CssParser.sanitize_media_query(current_media_query)
386
- current_media_query = String.new
430
+ current_media_query = +''
387
431
  elsif token.include?(',')
388
432
  # new media query begins
389
433
  token.tr!(',', ' ')
390
434
  token.strip!
391
435
  current_media_query << token << ' '
392
436
  current_media_queries << CssParser.sanitize_media_query(current_media_query)
393
- current_media_query = String.new
437
+ current_media_query = +''
394
438
  else
395
439
  token.strip!
396
440
  # special-case the ( and ) tokens to remove inner-whitespace
@@ -423,18 +467,22 @@ module CssParser
423
467
  current_selectors << token
424
468
 
425
469
  # 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]+$/
470
+ rule_start = start_offset if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
427
471
  end
428
472
  end
429
473
 
430
474
  # check for unclosed braces
431
475
  return unless in_declarations > 0
432
476
 
433
- unless options[:capture_offsets]
434
- return add_rule!(current_selectors, current_declarations, current_media_queries)
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
435
484
  end
436
-
437
- add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
485
+ add_rule!(**add_rule_options)
438
486
  end
439
487
 
440
488
  # Load a remote CSS file.
@@ -452,6 +500,8 @@ module CssParser
452
500
  if options.is_a? Hash
453
501
  opts.merge!(options)
454
502
  else
503
+ warn '[DEPRECATION] `load_uri!` with positional arguments is deprecated. ' \
504
+ 'Please use keyword arguments instead.', uplevel: 1
455
505
  opts[:base_uri] = options if options.is_a? String
456
506
  opts[:media_types] = deprecated if deprecated
457
507
  end
@@ -478,6 +528,8 @@ module CssParser
478
528
  if options.is_a? Hash
479
529
  opts.merge!(options)
480
530
  else
531
+ warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
532
+ 'Please use keyword arguments instead.', uplevel: 1
481
533
  opts[:base_dir] = options if options.is_a? String
482
534
  opts[:media_types] = deprecated if deprecated
483
535
  end
@@ -501,6 +553,8 @@ module CssParser
501
553
  if options.is_a? Hash
502
554
  opts.merge!(options)
503
555
  else
556
+ warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
557
+ 'Please use keyword arguments instead.', uplevel: 1
504
558
  opts[:base_dir] = options if options.is_a? String
505
559
  opts[:media_types] = deprecated if deprecated
506
560
  end
@@ -625,12 +679,7 @@ module CssParser
625
679
  end
626
680
 
627
681
  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
682
+ src.encode!('UTF-8', charset)
634
683
  end
635
684
  rescue
636
685
  @redirect_count = nil
@@ -671,7 +720,7 @@ module CssParser
671
720
  nodes = {}
672
721
  lines.each do |line|
673
722
  parts = line.split(':', 2)
674
- if parts[1] =~ /:/
723
+ if parts[1].include?(':')
675
724
  nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
676
725
  else
677
726
  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
@@ -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 = {}
@@ -142,7 +150,7 @@ module CssParser
142
150
 
143
151
  if preserve_importance
144
152
  importance = get_value(property).important
145
- replacement_declarations.each { |_key, value| value.important = importance }
153
+ replacement_declarations.each_value { |value| value.important = importance }
146
154
  end
147
155
 
148
156
  replacement_keys = declarations.keys
@@ -190,7 +198,7 @@ module CssParser
190
198
  end
191
199
 
192
200
  def to_s(options = {})
193
- str = declarations.reduce(String.new) do |memo, (prop, value)|
201
+ str = declarations.reduce(+'') do |memo, (prop, value)|
194
202
  importance = options[:force_important] || value.important ? ' !important' : ''
195
203
  memo << "#{prop}: #{value.value}#{importance}; "
196
204
  end
@@ -223,6 +231,12 @@ module CssParser
223
231
 
224
232
  extend Forwardable
225
233
 
234
+ # optional field for storing source reference
235
+ # File offset range
236
+ attr_reader :offset
237
+ # the local or remote location
238
+ attr_accessor :filename
239
+
226
240
  # Array of selector strings.
227
241
  attr_reader :selectors
228
242
 
@@ -237,9 +251,38 @@ module CssParser
237
251
  alias []= add_declaration!
238
252
  alias remove_declaration! delete
239
253
 
240
- def initialize(selectors, block, specificity = nil)
254
+ def initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) # rubocop:disable Metrics/ParameterLists
255
+ if args.any?
256
+ if selectors || block || offset || filename || specificity
257
+ raise ArgumentError, "don't mix positional and keyword arguments"
258
+ end
259
+
260
+ warn '[DEPRECATION] positional arguments are deprecated use keyword instead.', uplevel: 1
261
+
262
+ case args.length
263
+ when 2
264
+ selectors, block = args
265
+ when 3
266
+ selectors, block, specificity = args
267
+ when 4
268
+ filename, offset, selectors, block = args
269
+ when 5
270
+ filename, offset, selectors, block, specificity = args
271
+ else
272
+ raise ArgumentError
273
+ end
274
+ end
275
+
241
276
  @selectors = []
242
277
  @specificity = specificity
278
+
279
+ unless offset.nil? == filename.nil?
280
+ raise ArgumentError, 'require both offset and filename or no offset and no filename'
281
+ end
282
+
283
+ @offset = offset
284
+ @filename = filename
285
+
243
286
  parse_selectors!(selectors) if selectors
244
287
  parse_declarations!(block)
245
288
  end
@@ -308,7 +351,7 @@ module CssParser
308
351
 
309
352
  replacement =
310
353
  if value.match(CssParser::RE_INHERIT)
311
- BACKGROUND_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
354
+ BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
312
355
  else
313
356
  {
314
357
  'background-image' => value.slice!(CssParser::RE_IMAGE),
@@ -414,17 +457,17 @@ module CssParser
414
457
  else
415
458
  font_props['font-family'] = m
416
459
  end
417
- elsif m =~ /normal|inherit/i
418
- ['font-style', 'font-weight', 'font-variant'].each do |font_prop|
460
+ elsif /normal|inherit/i.match?(m)
461
+ FONT_WEIGHT_PROPERTIES.each do |font_prop|
419
462
  font_props[font_prop] ||= m
420
463
  end
421
- elsif m =~ /italic|oblique/i
464
+ elsif /italic|oblique/i.match?(m)
422
465
  font_props['font-style'] = m
423
- elsif m =~ /small-caps/i
466
+ elsif /small-caps/i.match?(m)
424
467
  font_props['font-variant'] = m
425
- elsif m =~ /[1-9]00$|bold|bolder|lighter/i
468
+ elsif /[1-9]00$|bold|bolder|lighter/i.match?(m)
426
469
  font_props['font-weight'] = m
427
- elsif m =~ CssParser::FONT_UNITS_RX
470
+ elsif CssParser::FONT_UNITS_RX.match?(m)
428
471
  if m.include?('/')
429
472
  font_props['font-size'], font_props['line-height'] = m.split('/', 2)
430
473
  else
@@ -447,8 +490,8 @@ module CssParser
447
490
  value = declaration.value.dup
448
491
 
449
492
  replacement =
450
- if value =~ CssParser::RE_INHERIT
451
- LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
493
+ if CssParser::RE_INHERIT.match?(value)
494
+ LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
452
495
  else
453
496
  {
454
497
  'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
@@ -513,15 +556,15 @@ module CssParser
513
556
  #
514
557
  # TODO: this is extremely similar to create_background_shorthand! and should be combined
515
558
  def create_border_shorthand! # :nodoc:
516
- values = BORDER_STYLE_PROPERTIES.map do |property|
559
+ values = BORDER_STYLE_PROPERTIES.filter_map do |property|
517
560
  next unless (declaration = declarations[property])
518
561
  next if declaration.important
519
562
  # can't merge if any value contains a space (i.e. has multiple values)
520
563
  # we temporarily remove any spaces after commas for the check (inside rgba, etc...)
521
- next if declaration.value.gsub(/,\s/, ',').strip =~ /\s/
564
+ next if /\s/.match?(declaration.value.gsub(/,\s/, ',').strip)
522
565
 
523
566
  declaration.value
524
- end.compact
567
+ end
525
568
 
526
569
  return if values.size != BORDER_STYLE_PROPERTIES.size
527
570
 
@@ -538,7 +581,7 @@ module CssParser
538
581
  return if declarations.size < NUMBER_OF_DIMENSIONS
539
582
 
540
583
  DIMENSIONS.each do |property, dimensions|
541
- values = [:top, :right, :bottom, :left].each_with_index.with_object({}) do |(side, index), result|
584
+ values = DIMENSION_DIRECTIONS.each_with_index.with_object({}) do |(side, index), result|
542
585
  next unless (declaration = declarations[dimensions[index]])
543
586
 
544
587
  result[side] = declaration.value
@@ -561,7 +604,7 @@ module CssParser
561
604
  def create_font_shorthand! # :nodoc:
562
605
  return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
563
606
 
564
- new_value = String.new
607
+ new_value = +''
565
608
  ['font-style', 'font-variant', 'font-weight'].each do |property|
566
609
  unless declarations[property].value == 'normal'
567
610
  new_value << declarations[property].value << ' '
@@ -598,7 +641,7 @@ module CssParser
598
641
  return [:top] if values.values.uniq.count == 1
599
642
 
600
643
  # `/* top | right | bottom | left */`
601
- return [:top, :right, :bottom, :left] if values[:left] != values[:right]
644
+ return DIMENSION_DIRECTIONS if values[:left] != values[:right]
602
645
 
603
646
  # Vertical are the same & horizontal are the same, `/* vertical | horizontal */`
604
647
  return [:top, :left] if values[:top] == values[:bottom]
@@ -612,20 +655,32 @@ module CssParser
612
655
  return unless block
613
656
 
614
657
  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
658
+ block.split(SEMICOLON) do |decs|
659
+ decs = (continuation ? "#{continuation};#{decs}" : decs)
660
+ if unmatched_open_parenthesis?(decs)
661
+ # Semicolon happened within parenthesis, so it is a part of the value
662
+ # the rest of the value is in the next segment
663
+ continuation = decs
664
+ next
625
665
  end
666
+
667
+ next unless (colon = decs.index(COLON))
668
+
669
+ property = decs[0, colon]
670
+ value = decs[(colon + 1)..]
671
+ property.strip!
672
+ value.strip!
673
+ next if property.empty? || value.empty? || value.casecmp?(IMPORTANT)
674
+
675
+ add_declaration!(property, value)
676
+ continuation = nil
626
677
  end
627
678
  end
628
679
 
680
+ def unmatched_open_parenthesis?(declarations)
681
+ (lparen_index = declarations.index(LPAREN)) && !declarations.index(RPAREN, lparen_index)
682
+ end
683
+
629
684
  #--
630
685
  # TODO: way too simplistic
631
686
  #++
@@ -650,18 +705,4 @@ module CssParser
650
705
  end
651
706
  end
652
707
  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
708
  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 = '1.21.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'
@@ -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? { |rs| rs.is_a?(CssParser::RuleSet) }
60
+ unless rule_sets.all?(CssParser::RuleSet)
62
61
  raise ArgumentError, 'all parameters must be CssParser::RuleSets.'
63
62
  end
64
63
 
@@ -71,7 +70,7 @@ module CssParser
71
70
  rule_set.expand_shorthand!
72
71
 
73
72
  specificity = rule_set.specificity
74
- specificity ||= rule_set.selectors.map { |s| calculate_specificity(s) }.compact.max || 0
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
@@ -111,7 +110,7 @@ module CssParser
111
110
  #++
112
111
  def self.calculate_specificity(selector)
113
112
  a = 0
114
- b = selector.scan(/\#/).length
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.17.1
4
+ version: 1.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-07 00:00:00.000000000 Z
11
+ date: 2024-12-13 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: []
@@ -164,14 +52,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
52
  requirements:
165
53
  - - ">="
166
54
  - !ruby/object:Gem::Version
167
- version: '2.7'
55
+ version: '3.0'
168
56
  required_rubygems_version: !ruby/object:Gem::Requirement
169
57
  requirements:
170
58
  - - ">="
171
59
  - !ruby/object:Gem::Version
172
60
  version: '0'
173
61
  requirements: []
174
- rubygems_version: 3.1.6
62
+ rubygems_version: 3.4.19
175
63
  signing_key:
176
64
  specification_version: 4
177
65
  summary: Ruby CSS parser.