@@ -36,7 +36,8 @@ class << self; attr_reader :folded_declaration_cache; end
36
36
def initialize ( options = { } )
37
37
@options = { :absolute_paths => false ,
38
38
:import => true ,
39
- :io_exceptions => true } . merge ( options )
39
+ :io_exceptions => true ,
40
+ :capture_offsets => false } . merge ( options )
40
41
41
42
# array of RuleSets
42
43
@rules = [ ]
@@ -117,7 +118,7 @@ def add_block!(block, options = {})
117
118
options [ :media_types ] = [ options [ :media_types ] ] . flatten . collect { |mt | CssParser . sanitize_media_query ( mt ) }
118
119
options [ :only_media_types ] = [ options [ :only_media_types ] ] . flatten . collect { |mt | CssParser . sanitize_media_query ( mt ) }
119
120
120
- block = cleanup_block ( block )
121
+ block = cleanup_block ( block , options )
121
122
122
123
if options [ :base_uri ] and @options [ :absolute_paths ]
123
124
block = CssParser . convert_uris ( block , options [ :base_uri ] )
@@ -139,26 +140,41 @@ def add_block!(block, options = {})
139
140
140
141
import_path = import_rule [ 0 ] . to_s . gsub ( /['"]*/ , '' ) . strip
141
142
143
+ import_options = { :media_types => media_types }
144
+ import_options [ :capture_offsets ] = true if options [ :capture_offsets ]
145
+
142
146
if options [ :base_uri ]
143
147
import_uri = Addressable ::URI . parse ( options [ :base_uri ] . to_s ) + Addressable ::URI . parse ( import_path )
144
- load_uri! ( import_uri , options [ :base_uri ] , media_types )
148
+ import_options [ :base_uri ] = options [ :base_uri ]
149
+ load_uri! ( import_uri , import_options )
145
150
elsif options [ :base_dir ]
146
- load_file! ( import_path , options [ :base_dir ] , media_types )
151
+ import_options [ :base_dir ] = options [ :base_dir ]
152
+ load_file! ( import_path , import_options )
147
153
end
148
154
end
149
155
end
150
156
151
157
# Remove @import declarations
152
- block . gsub! ( RE_AT_IMPORT_RULE ) { | m | ' ' * m . length }
158
+ block = remove_all ( block , RE_AT_IMPORT_RULE , options )
153
159
154
160
parse_block_into_rule_sets! ( block , options )
155
161
end
156
162
157
163
# Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
158
164
#
159
165
# +media_types+ can be a symbol or an array of symbols.
160
- def add_rule! ( selectors , declarations , media_types = :all , offset = nil )
161
- rule_set = RuleSet . new ( selectors , declarations , nil , offset )
166
+ def add_rule! ( selectors , declarations , media_types = :all )
167
+ rule_set = RuleSet . new ( selectors , declarations )
168
+ add_rule_set! ( rule_set , media_types )
169
+ end
170
+
171
+ # Add a CSS rule by setting the +selectors+, +declarations+, +uri+, +offset+ and +media_types+.
172
+ #
173
+ # +uri+ can be a string or uri pointing to the file or url location.
174
+ # +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
175
+ # +media_types+ can be a symbol or an array of symbols.
176
+ def add_file_rule! ( selectors , declarations , uri , offset , media_types = :all )
177
+ rule_set = FileRuleSet . new ( uri , offset , selectors , declarations )
162
178
add_rule_set! ( rule_set , media_types )
163
179
end
164
180
@@ -289,16 +305,15 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
289
305
current_media_query = ''
290
306
current_declarations = ''
291
307
292
- # once we are in a rule, we will use this to store where we started
308
+ # once we are in a rule, we will use this to store where we started if we are capturing offsets
293
309
rule_start = nil
294
310
offset = nil
295
311
296
312
block . scan ( /(([\\ ]{2,})|([\\ ]?[{}\s "])|(.[^\s "{}\\ ]*))/ ) do |matches |
297
- # encode here because it can affect the length of the string
298
- token = matches [ 0 ] . encode ( 'UTF-8' , 'binary' , invalid : :replace , undef : :replace , replace : '' )
313
+ token = matches [ 0 ]
299
314
300
- # save the regex offset so tat we know where in the file we are
301
- offset = Regexp . last_match . offset ( 0 )
315
+ # save the regex offset so that we know where in the file we are
316
+ offset = Regexp . last_match . offset ( 0 ) if options [ :capture_offsets ]
302
317
303
318
if token =~ /\A "/ # found un-escaped double quote
304
319
in_string = !in_string
@@ -324,13 +339,18 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
324
339
in_declarations -= 1
325
340
326
341
unless current_declarations . strip . empty?
327
- add_rule! ( current_selectors , current_declarations , current_media_queries , ( rule_start ..offset . last ) )
342
+ if options [ :capture_offsets ]
343
+ add_file_rule! ( current_selectors , current_declarations , options [ :filename ] , ( rule_start ..offset . last ) , current_media_queries )
344
+ else
345
+ add_rule! ( current_selectors , current_declarations , current_media_queries )
346
+ end
328
347
end
329
348
330
- # restart our search for selectors and declarations
331
- rule_start = nil
332
349
current_selectors = ''
333
350
current_declarations = ''
351
+
352
+ # restart our search for selectors and declarations
353
+ rule_start = nil if options [ :capture_offsets ]
334
354
end
335
355
elsif token =~ /@media/i
336
356
# found '@media', reset current media_types
@@ -366,23 +386,26 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
366
386
end
367
387
else
368
388
if token =~ /\{ / and not in_string
369
- current_selectors . gsub! ( /^[\s ]*/ , '' )
370
- current_selectors . gsub! ( /[\s ]*$/ , '' )
389
+ current_selectors . strip!
371
390
in_declarations += 1
372
391
else
373
- # if we are in a selector, add the token to te current selectors
392
+ # if we are in a selector, add the token to the current selectors
374
393
current_selectors += token
375
394
376
395
# mark this as the beginning of the selector unless we have already marked it
377
- rule_start = offset . first if rule_start . nil? && token =~ /^[^\s ]+$/
396
+ rule_start = offset . first if options [ :capture_offsets ] && rule_start . nil? && token =~ /^[^\s ]+$/
378
397
end
379
398
end
380
399
end
381
400
end
382
401
383
402
# check for unclosed braces
384
403
if in_declarations > 0
385
- add_rule! ( current_selectors , current_declarations , current_media_queries , ( rule_start ..offset . last ) )
404
+ if options [ :capture_offsets ]
405
+ add_file_rule! ( current_selectors , current_declarations , options [ :filename ] , ( rule_start ..offset . last ) , current_media_queries )
406
+ else
407
+ add_rule! ( current_selectors , current_declarations , current_media_queries )
408
+ end
386
409
end
387
410
end
388
411
@@ -395,7 +418,6 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
395
418
# Deprecated: originally accepted three params: `uri`, `base_uri` and `media_types`
396
419
def load_uri! ( uri , options = { } , deprecated = nil )
397
420
uri = Addressable ::URI . parse ( uri ) unless uri . respond_to? :scheme
398
- #base_uri = nil, media_types = :all, options = {}
399
421
400
422
opts = { :base_uri => nil , :media_types => :all }
401
423
@@ -413,22 +435,46 @@ def load_uri!(uri, options = {}, deprecated = nil)
413
435
414
436
opts [ :base_uri ] = uri if opts [ :base_uri ] . nil?
415
437
438
+ # pass on the uri if we are capturing file offsets
439
+ opts [ :filename ] = uri . to_s if opts [ :capture_offsets ]
440
+
416
441
src , = read_remote_file ( uri ) # skip charset
417
442
if src
418
443
add_block! ( src , opts )
419
444
end
420
445
end
421
446
422
447
# Load a local CSS file.
423
- def load_file! ( file_name , base_dir = nil , media_types = :all )
424
- file_name = File . expand_path ( file_name , base_dir )
448
+ def load_file! ( file_name , options = { } , deprecated = nil )
449
+ opts = { :base_dir => nil , :media_types => :all }
450
+
451
+ if options . is_a? Hash
452
+ opts . merge! ( options )
453
+ else
454
+ opts [ :base_dir ] = options if options . is_a? String
455
+ opts [ :media_types ] = deprecated if deprecated
456
+ end
457
+
458
+ file_name = File . expand_path ( file_name , opts [ :base_dir ] )
425
459
return unless File . readable? ( file_name )
426
460
return unless circular_reference_check ( file_name )
427
461
428
- src = IO . read ( file_name )
429
- base_dir = File . dirname ( file_name )
462
+ # using open takes a little longer than IO.read but retains line-breaks consistently
463
+ # across platforms which is important when capturing offsets
464
+ if opts [ :capture_offsets ]
465
+ fh = open ( file_name , 'rb' )
466
+ src = fh . read
467
+ fh . close
430
468
431
- add_block! ( src , { :media_types => media_types , :base_dir => base_dir } )
469
+ # pass on the file name if we are capturing file offsets
470
+ opts [ :filename ] = file_name
471
+ else
472
+ src = IO . read ( file_name )
473
+ end
474
+
475
+ opts [ :base_dir ] = File . dirname ( file_name )
476
+
477
+ add_block! ( src , opts )
432
478
end
433
479
434
480
# Load a local CSS string.
@@ -454,16 +500,33 @@ def circular_reference_check(path)
454
500
end
455
501
end
456
502
503
+ # Remove a pattern from a given string
504
+ #
505
+ # Returns a string.
506
+ def remove_all ( css , regex , options )
507
+ # if we are capturing file offsets, replace the characters with spaces to retail the original positions
508
+ return css . gsub ( regex ) { |m | ' ' * m . length } if options [ :capture_offsets ]
509
+
510
+ # otherwise just strip it out
511
+ css . gsub ( regex , '' )
512
+ end
513
+
457
514
# Strip comments and clean up blank lines from a block of CSS.
458
515
#
459
516
# Returns a string.
460
- def cleanup_block ( block ) # :nodoc:
461
- # Strip CSS comments but make sure the string stays the same length so that we can retain byte offsets
462
- block . gsub! ( STRIP_CSS_COMMENTS_RX ) { |m | ' ' * m . length }
517
+ def cleanup_block ( block , options = { } ) # :nodoc:
518
+ # Strip CSS comments
519
+ utf8_block = block . encode ( 'UTF-8' , 'binary' , invalid : :replace , undef : :replace , replace : ' ' ) )
520
+ utf8_block = remove_all ( utf8_block , STRIP_CSS_COMMENTS_RX , options )
463
521
464
522
# Strip HTML comments - they shouldn't really be in here but
465
523
# some people are just crazy...
466
- block . gsub ( STRIP_HTML_COMMENTS_RX ) { |m | ' ' * m . length }
524
+ utf8_block = remove_all ( utf8_block , STRIP_HTML_COMMENTS_RX , options )
525
+
526
+ # Strip lines containing just whitespace
527
+ utf8_block . gsub! ( /^\s +$/ , "" ) unless options [ :capture_offsets ]
528
+
529
+ utf8_block
467
530
end
468
531
469
532
# Download a file into a string.
@@ -491,8 +554,9 @@ def read_remote_file(uri) # :nodoc:
491
554
492
555
src = '' , charset = nil
493
556
494
- uri = Addressable ::URI . parse ( uri . to_s )
495
557
begin
558
+ uri = Addressable ::URI . parse ( uri . to_s )
559
+
496
560
if uri . scheme == 'file'
497
561
# local file
498
562
path = uri . path
0 commit comments