css_parser 1.17.1 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f2b7e6abc1f1bea049098d58fd072a7a17985203d25eb87fe9a62e3d3329bc3
4
- data.tar.gz: ebb09d74054507e148544ad150cd8e3470a0601b736f9708bae567d26c08794c
3
+ metadata.gz: 5597d8abc3bd6dac3e4f4c7bb241cc4e1110ee0bdae226870ec35ab7ba85ee4f
4
+ data.tar.gz: 7191e6f6768060de7ddedc0420b0650046ca329bfbf5ef42444b342970eab752
5
5
  SHA512:
6
- metadata.gz: 4ac7f707b7394d57bef2dea84887b07239cc446b15e5c4d49f3097b311950a95c7816e2b3acc81842570a827f3eb959d8d0081f974b92d9b97f4772cb9c312bd
7
- data.tar.gz: a52877e078998a7d4b47938db8f41d67c2fc2a293a609e78efc54d9fbf403bae3cff8bb5145765b518d19d1a86177ae09eb44b6d8bdbafc07bcadd51672a94d1
6
+ metadata.gz: d8a71809154ea8535a22709a8877a6d8c7a93ada53a42e1a497ef8893292a261580fc1f2aa65c723d217498124278e2c226dafc69f2549cdbff0b6b4a7eb8895
7
+ data.tar.gz: 8c644bed6fe56143c4ccc5fe02a29e98de9cc728efdc7881f1816a10e562faf0a4e67210ff329068d4b5a2bb377239cfcd25b5594527b893522fbd6ec7cfa994
@@ -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.
@@ -329,11 +365,15 @@ module CssParser
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,11 +400,14 @@ 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.merge!(filename: options[:filename], offset: rule_start..end_offset)
367
409
  end
410
+ add_rule!(**add_rule_options)
368
411
  end
369
412
 
370
413
  current_selectors = String.new
@@ -423,18 +466,21 @@ module CssParser
423
466
  current_selectors << token
424
467
 
425
468
  # 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]+$/
469
+ rule_start = start_offset if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
427
470
  end
428
471
  end
429
472
 
430
473
  # check for unclosed braces
431
474
  return unless in_declarations > 0
432
475
 
433
- unless options[:capture_offsets]
434
- return add_rule!(current_selectors, current_declarations, current_media_queries)
476
+ add_rule_options = {
477
+ selectors: current_selectors, block: current_declarations,
478
+ media_types: current_media_queries
479
+ }
480
+ if options[:capture_offsets]
481
+ add_rule_options.merge!(filename: options[:filename], offset: rule_start..end_offset)
435
482
  end
436
-
437
- add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
483
+ add_rule!(**add_rule_options)
438
484
  end
439
485
 
440
486
  # Load a remote CSS file.
@@ -452,6 +498,8 @@ module CssParser
452
498
  if options.is_a? Hash
453
499
  opts.merge!(options)
454
500
  else
501
+ warn '[DEPRECATION] `load_uri!` with positional arguments is deprecated. ' \
502
+ 'Please use keyword arguments instead.', uplevel: 1
455
503
  opts[:base_uri] = options if options.is_a? String
456
504
  opts[:media_types] = deprecated if deprecated
457
505
  end
@@ -478,6 +526,8 @@ module CssParser
478
526
  if options.is_a? Hash
479
527
  opts.merge!(options)
480
528
  else
529
+ warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
530
+ 'Please use keyword arguments instead.', uplevel: 1
481
531
  opts[:base_dir] = options if options.is_a? String
482
532
  opts[:media_types] = deprecated if deprecated
483
533
  end
@@ -501,6 +551,8 @@ module CssParser
501
551
  if options.is_a? Hash
502
552
  opts.merge!(options)
503
553
  else
554
+ warn '[DEPRECATION] `load_file!` with positional arguments is deprecated. ' \
555
+ 'Please use keyword arguments instead.', uplevel: 1
504
556
  opts[:base_dir] = options if options.is_a? String
505
557
  opts[:media_types] = deprecated if deprecated
506
558
  end
@@ -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
@@ -26,6 +26,11 @@ module CssParser
26
26
 
27
27
  WHITESPACE_REPLACEMENT = '___SPACE___'
28
28
 
29
+ # Tokens for parse_declarations!
30
+ COLON = ':'.freeze
31
+ SEMICOLON = ';'.freeze
32
+ LPAREN = '('.freeze
33
+ RPAREN = ')'.freeze
29
34
  class Declarations
30
35
  class Value
31
36
  attr_reader :value
@@ -58,7 +63,7 @@ module CssParser
58
63
 
59
64
  extend Forwardable
60
65
 
61
- def_delegators :declarations, :each
66
+ def_delegators :declarations, :each, :each_value
62
67
 
63
68
  def initialize(declarations = {})
64
69
  self.declarations = {}
@@ -142,7 +147,7 @@ module CssParser
142
147
 
143
148
  if preserve_importance
144
149
  importance = get_value(property).important
145
- replacement_declarations.each { |_key, value| value.important = importance }
150
+ replacement_declarations.each_value { |value| value.important = importance }
146
151
  end
147
152
 
148
153
  replacement_keys = declarations.keys
@@ -223,6 +228,12 @@ module CssParser
223
228
 
224
229
  extend Forwardable
225
230
 
231
+ # optional field for storing source reference
232
+ # File offset range
233
+ attr_reader :offset
234
+ # the local or remote location
235
+ attr_accessor :filename
236
+
226
237
  # Array of selector strings.
227
238
  attr_reader :selectors
228
239
 
@@ -237,9 +248,38 @@ module CssParser
237
248
  alias []= add_declaration!
238
249
  alias remove_declaration! delete
239
250
 
240
- def initialize(selectors, block, specificity = nil)
251
+ def initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) # rubocop:disable Metrics/ParameterLists
252
+ if args.any?
253
+ if selectors || block || offset || filename || specificity
254
+ raise ArgumentError, "don't mix positional and keyword arguments"
255
+ end
256
+
257
+ warn '[DEPRECATION] positional arguments are deprecated use keyword instead.', uplevel: 1
258
+
259
+ case args.length
260
+ when 2
261
+ selectors, block = args
262
+ when 3
263
+ selectors, block, specificity = args
264
+ when 4
265
+ filename, offset, selectors, block = args
266
+ when 5
267
+ filename, offset, selectors, block, specificity = args
268
+ else
269
+ raise ArgumentError
270
+ end
271
+ end
272
+
241
273
  @selectors = []
242
274
  @specificity = specificity
275
+
276
+ unless offset.nil? == filename.nil?
277
+ raise ArgumentError, 'require both offset and filename or no offset and no filename'
278
+ end
279
+
280
+ @offset = offset
281
+ @filename = filename
282
+
243
283
  parse_selectors!(selectors) if selectors
244
284
  parse_declarations!(block)
245
285
  end
@@ -308,7 +348,7 @@ module CssParser
308
348
 
309
349
  replacement =
310
350
  if value.match(CssParser::RE_INHERIT)
311
- BACKGROUND_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
351
+ BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
312
352
  else
313
353
  {
314
354
  'background-image' => value.slice!(CssParser::RE_IMAGE),
@@ -448,7 +488,7 @@ module CssParser
448
488
 
449
489
  replacement =
450
490
  if value =~ CssParser::RE_INHERIT
451
- LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
491
+ LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
452
492
  else
453
493
  {
454
494
  'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
@@ -612,20 +652,32 @@ module CssParser
612
652
  return unless block
613
653
 
614
654
  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
655
+ block.split(SEMICOLON) do |decs|
656
+ decs = (continuation ? "#{continuation};#{decs}" : decs)
657
+ if unmatched_open_parenthesis?(decs)
658
+ # Semicolon happened within parenthesis, so it is a part of the value
659
+ # the rest of the value is in the next segment
660
+ continuation = decs
661
+ next
625
662
  end
663
+
664
+ next unless (colon = decs.index(COLON))
665
+
666
+ property = decs[0, colon]
667
+ value = decs[(colon + 1)..]
668
+ property.strip!
669
+ value.strip!
670
+ next if property.empty? || value.empty?
671
+
672
+ add_declaration!(property, value)
673
+ continuation = nil
626
674
  end
627
675
  end
628
676
 
677
+ def unmatched_open_parenthesis?(declarations)
678
+ (lparen_index = declarations.index(LPAREN)) && !declarations.index(RPAREN, lparen_index)
679
+ end
680
+
629
681
  #--
630
682
  # TODO: way too simplistic
631
683
  #++
@@ -650,18 +702,4 @@ module CssParser
650
702
  end
651
703
  end
652
704
  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
705
  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.19.0'.freeze
5
5
  end
data/lib/css_parser.rb CHANGED
@@ -111,7 +111,7 @@ module CssParser
111
111
  #++
112
112
  def self.calculate_specificity(selector)
113
113
  a = 0
114
- b = selector.scan(/\#/).length
114
+ b = selector.scan('#').length
115
115
  c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
116
116
  d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
117
117
 
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.19.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-08-23 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.