css_parser 1.17.1 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f2b7e6abc1f1bea049098d58fd072a7a17985203d25eb87fe9a62e3d3329bc3
4
- data.tar.gz: ebb09d74054507e148544ad150cd8e3470a0601b736f9708bae567d26c08794c
3
+ metadata.gz: '09aa105491fc4a0047380d522d09033787cabe34436e08bdb4ac7002ee2fbbde'
4
+ data.tar.gz: 17a8b0825e77ed66bdceb75fcda6b8d93574d8e5b3c1d4fc5586e34d87013d88
5
5
  SHA512:
6
- metadata.gz: 4ac7f707b7394d57bef2dea84887b07239cc446b15e5c4d49f3097b311950a95c7816e2b3acc81842570a827f3eb959d8d0081f974b92d9b97f4772cb9c312bd
7
- data.tar.gz: a52877e078998a7d4b47938db8f41d67c2fc2a293a609e78efc54d9fbf403bae3cff8bb5145765b518d19d1a86177ae09eb44b6d8bdbafc07bcadd51672a94d1
6
+ metadata.gz: 34a7782bab86eb88756ac8b90e8873dd8330f6895d852a151c8ad51ed1de93d3fcf1cf5c3a3764e5f9811a0fac995d740057a50856762b5d5aa738bee036fc63
7
+ data.tar.gz: 4cf1c27c7f13463ba549e66028622861cd00c9bc9434a40c39f2d1c44c16b37e739747dee0d058d56718360d6902d7fd464ff4da69fe455ef58c35d338c2fa1f
@@ -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,12 @@ 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
34
+ IMPORTANT = '!important'.freeze
29
35
  class Declarations
30
36
  class Value
31
37
  attr_reader :value
@@ -58,7 +64,7 @@ module CssParser
58
64
 
59
65
  extend Forwardable
60
66
 
61
- def_delegators :declarations, :each
67
+ def_delegators :declarations, :each, :each_value
62
68
 
63
69
  def initialize(declarations = {})
64
70
  self.declarations = {}
@@ -142,7 +148,7 @@ module CssParser
142
148
 
143
149
  if preserve_importance
144
150
  importance = get_value(property).important
145
- replacement_declarations.each { |_key, value| value.important = importance }
151
+ replacement_declarations.each_value { |value| value.important = importance }
146
152
  end
147
153
 
148
154
  replacement_keys = declarations.keys
@@ -223,6 +229,12 @@ module CssParser
223
229
 
224
230
  extend Forwardable
225
231
 
232
+ # optional field for storing source reference
233
+ # File offset range
234
+ attr_reader :offset
235
+ # the local or remote location
236
+ attr_accessor :filename
237
+
226
238
  # Array of selector strings.
227
239
  attr_reader :selectors
228
240
 
@@ -237,9 +249,38 @@ module CssParser
237
249
  alias []= add_declaration!
238
250
  alias remove_declaration! delete
239
251
 
240
- def initialize(selectors, block, specificity = nil)
252
+ def initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) # rubocop:disable Metrics/ParameterLists
253
+ if args.any?
254
+ if selectors || block || offset || filename || specificity
255
+ raise ArgumentError, "don't mix positional and keyword arguments"
256
+ end
257
+
258
+ warn '[DEPRECATION] positional arguments are deprecated use keyword instead.', uplevel: 1
259
+
260
+ case args.length
261
+ when 2
262
+ selectors, block = args
263
+ when 3
264
+ selectors, block, specificity = args
265
+ when 4
266
+ filename, offset, selectors, block = args
267
+ when 5
268
+ filename, offset, selectors, block, specificity = args
269
+ else
270
+ raise ArgumentError
271
+ end
272
+ end
273
+
241
274
  @selectors = []
242
275
  @specificity = specificity
276
+
277
+ unless offset.nil? == filename.nil?
278
+ raise ArgumentError, 'require both offset and filename or no offset and no filename'
279
+ end
280
+
281
+ @offset = offset
282
+ @filename = filename
283
+
243
284
  parse_selectors!(selectors) if selectors
244
285
  parse_declarations!(block)
245
286
  end
@@ -308,7 +349,7 @@ module CssParser
308
349
 
309
350
  replacement =
310
351
  if value.match(CssParser::RE_INHERIT)
311
- BACKGROUND_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
352
+ BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
312
353
  else
313
354
  {
314
355
  'background-image' => value.slice!(CssParser::RE_IMAGE),
@@ -448,7 +489,7 @@ module CssParser
448
489
 
449
490
  replacement =
450
491
  if value =~ CssParser::RE_INHERIT
451
- LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
492
+ LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
452
493
  else
453
494
  {
454
495
  'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
@@ -612,20 +653,32 @@ module CssParser
612
653
  return unless block
613
654
 
614
655
  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
656
+ block.split(SEMICOLON) do |decs|
657
+ decs = (continuation ? "#{continuation};#{decs}" : decs)
658
+ if unmatched_open_parenthesis?(decs)
659
+ # Semicolon happened within parenthesis, so it is a part of the value
660
+ # the rest of the value is in the next segment
661
+ continuation = decs
662
+ next
625
663
  end
664
+
665
+ next unless (colon = decs.index(COLON))
666
+
667
+ property = decs[0, colon]
668
+ value = decs[(colon + 1)..]
669
+ property.strip!
670
+ value.strip!
671
+ next if property.empty? || value.empty? || value.casecmp?(IMPORTANT)
672
+
673
+ add_declaration!(property, value)
674
+ continuation = nil
626
675
  end
627
676
  end
628
677
 
678
+ def unmatched_open_parenthesis?(declarations)
679
+ (lparen_index = declarations.index(LPAREN)) && !declarations.index(RPAREN, lparen_index)
680
+ end
681
+
629
682
  #--
630
683
  # TODO: way too simplistic
631
684
  #++
@@ -650,18 +703,4 @@ module CssParser
650
703
  end
651
704
  end
652
705
  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
706
  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.1'.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.1
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-10-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.