css_parser 1.0.1 → 1.1.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.
@@ -1,13 +1,12 @@
1
- $:.unshift File.dirname(__FILE__)
2
1
  require 'uri'
2
+ require 'open-uri'
3
3
  require 'digest/md5'
4
4
  require 'zlib'
5
5
  require 'iconv'
6
- require 'css_parser/rule_set'
7
- require 'css_parser/regexps'
8
- require 'css_parser/parser'
9
6
 
10
7
  module CssParser
8
+ VERSION = '1.1.1'
9
+
11
10
  # Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
12
11
  # (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
13
12
  #
@@ -146,4 +145,8 @@ module CssParser
146
145
  end
147
146
  out
148
147
  end
149
- end
148
+ end
149
+
150
+ require File.dirname(__FILE__) + '/css_parser/rule_set'
151
+ require File.dirname(__FILE__) + '/css_parser/regexps'
152
+ require File.dirname(__FILE__) + '/css_parser/parser'
@@ -15,13 +15,13 @@ module CssParser
15
15
  # [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>.
16
16
  # [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>.
17
17
  class Parser
18
- USER_AGENT = "Ruby CSS Parser/#{RUBY_VERSION} (http://code.dunae.ca/css_parser/)"
18
+ USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (http://github.com/alexdunae/css_parser)"
19
19
 
20
20
  STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
21
21
  STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
22
22
 
23
23
  # Initial parsing
24
- RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["']+(.[^'"]*)["']\)?([\w\s\,]*);?/i
24
+ RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\])]*)\)?[;\n]?/
25
25
 
26
26
  #--
27
27
  # RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
@@ -82,6 +82,9 @@ module CssParser
82
82
 
83
83
  # Add a raw block of CSS.
84
84
  #
85
+ # In order to follow +@import+ rules you must supply either a
86
+ # +:base_dir+ or +:base_uri+ option.
87
+ #
85
88
  # ==== Example
86
89
  # css = <<-EOT
87
90
  # body { font-size: 10pt }
@@ -92,21 +95,42 @@ module CssParser
92
95
  # EOT
93
96
  #
94
97
  # parser = CssParser::Parser.new
95
- # parser.load_css!(css)
98
+ # parser.add_block!(css)
96
99
  #--
97
100
  # TODO: add media_type
98
101
  #++
99
102
  def add_block!(block, options = {})
100
- options = {:base_uri => nil, :charset => nil, :media_types => :all}.merge(options)
103
+ options = {:base_uri => nil, :base_dir => nil, :charset => nil, :media_types => :all}.merge(options)
101
104
 
102
105
  block = cleanup_block(block)
103
106
 
104
107
  if options[:base_uri] and @options[:absolute_paths]
105
108
  block = CssParser.convert_uris(block, options[:base_uri])
106
109
  end
110
+
111
+ # Load @imported CSS
112
+ block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
113
+ media_types = []
114
+ if media_string = import_rule[-1]
115
+ media_string.split(/\s|\,/).each do |t|
116
+ media_types << t.to_sym unless t.empty?
117
+ end
118
+ end
119
+
120
+ import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
121
+
122
+ if options[:base_uri]
123
+ import_uri = URI.parse(options[:base_uri].to_s).merge(import_path)
124
+ load_uri!(import_uri, options[:base_uri], media_types)
125
+ elsif options[:base_dir]
126
+ load_file!(import_path, options[:base_dir], media_types)
127
+ end
128
+ end
129
+
130
+ # Remove @import declarations
131
+ block.gsub!(RE_AT_IMPORT_RULE, '')
107
132
 
108
133
  parse_block_into_rule_sets!(block, options)
109
-
110
134
  end
111
135
 
112
136
  # Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
@@ -245,33 +269,25 @@ module CssParser
245
269
  # Load a remote CSS file.
246
270
  def load_uri!(uri, base_uri = nil, media_types = :all)
247
271
  base_uri = uri if base_uri.nil?
248
- src, charset = read_remote_file(uri)
249
-
250
- # Load @imported CSS
251
- src.scan(RE_AT_IMPORT_RULE).each do |import_rule|
252
- import_path = import_rule[1].to_s.gsub(/['"]*/, '').strip
253
- import_uri = URI.parse(base_uri.to_s).merge(import_path)
254
- #puts import_uri.to_s
255
272
 
256
- media_types = []
257
- if media_string = import_rule[import_rule.length-1]
258
- media_string.split(/\s|\,/).each do |t|
259
- media_types << t.to_sym unless t.empty?
260
- end
261
- end
262
-
263
- # Recurse
264
- load_uri!(import_uri, nil, media_types)
273
+ src, charset = read_remote_file(uri)
274
+ unless src.empty?
275
+ add_block!(src, {:media_types => media_types, :base_uri => base_uri})
265
276
  end
277
+ end
278
+
279
+ # Load a local CSS file.
280
+ def load_file!(file_name, base_dir = nil, media_types = :all)
281
+ file_name = File.expand_path(file_name, base_dir)
282
+ return unless File.readable?(file_name)
266
283
 
267
- # Remove @import declarations
268
- src.gsub!(RE_AT_IMPORT_RULE, '')
269
-
270
- # Relative paths need to be converted here
271
- src = CssParser.convert_uris(src, base_uri) if base_uri and @options[:absolute_paths]
284
+ src = IO.read(file_name)
285
+ base_dir = File.dirname(file_name)
272
286
 
273
- add_block!(src, {:media_types => media_types})
287
+ add_block!(src, {:media_types => media_types, :base_dir => base_dir})
274
288
  end
289
+
290
+
275
291
 
276
292
  protected
277
293
  # Strip comments and clean up blank lines from a block of CSS.
@@ -298,7 +314,11 @@ module CssParser
298
314
  # TODO: add option to fail silently or throw and exception on a 404
299
315
  #++
300
316
  def read_remote_file(uri) # :nodoc:
301
- raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @loaded_uris.include?(uri.to_s)
317
+ if @loaded_uris.include?(uri.to_s)
318
+ raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @options[:io_exceptions]
319
+ return '', nil
320
+ end
321
+
302
322
  @loaded_uris << uri.to_s
303
323
 
304
324
  begin
@@ -318,7 +338,7 @@ module CssParser
318
338
 
319
339
  fh.close
320
340
  return src, fh.charset
321
- rescue
341
+ rescue Exception => e
322
342
  raise RemoteFileError if @options[:io_exceptions]
323
343
  return '', nil
324
344
  end
@@ -342,4 +362,4 @@ module CssParser
342
362
  @css_warnings = []
343
363
  end
344
364
  end
345
- end
365
+ end
@@ -16,7 +16,7 @@ module CssParser
16
16
  URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
17
17
 
18
18
  # Initial parsing
19
- RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["']+(.[^'"]*)["']\)?([\w\s\,]*);?/i
19
+ RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["''"]?(.[^'"\s"']*)["''"]?\)?([\w\s\,^\])]*)\)?;?/
20
20
 
21
21
  #--
22
22
  #RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
@@ -84,7 +84,7 @@ module CssParser
84
84
 
85
85
  # Iterate through declarations.
86
86
  def each_declaration # :yields: property, value, is_important
87
- decs = @declarations.sort { |a,b| a[1][:order].nil? || b[1][:order].nil? ? 1 : a[1][:order] <=> b[1][:order] }
87
+ decs = @declarations.sort { |a,b| a[1][:order].nil? || b[1][:order].nil? ? 0 : a[1][:order] <=> b[1][:order] }
88
88
  decs.each do |property, data|
89
89
  value = data[:value]
90
90
  yield property.downcase.strip, value.strip, data[:is_important]
@@ -98,8 +98,10 @@ module CssParser
98
98
  def declarations_to_s(options = {})
99
99
  options = {:force_important => false}.merge(options)
100
100
  str = ''
101
- importance = options[:force_important] ? ' !important' : ''
102
- each_declaration { |prop, val| str += "#{prop}: #{val}#{importance}; " }
101
+ each_declaration do |prop, val, is_important|
102
+ importance = (options[:force_important] || is_important) ? ' !important' : ''
103
+ str += "#{prop}: #{val}#{importance}; "
104
+ end
103
105
  str.gsub(/^[\s]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
104
106
  end
105
107
 
@@ -1,7 +1,7 @@
1
1
  require File.dirname(__FILE__) + '/test_helper'
2
2
 
3
- # Test cases for the CssParser's downloading functions.
4
- class CssParserDownloadingTests < Test::Unit::TestCase
3
+ # Test cases for the CssParser's loading functions.
4
+ class CssParserLoadingTests < Test::Unit::TestCase
5
5
  include CssParser
6
6
  include WEBrick
7
7
 
@@ -14,7 +14,7 @@ class CssParserDownloadingTests < Test::Unit::TestCase
14
14
  www_root = File.dirname(__FILE__) + '/fixtures/'
15
15
 
16
16
  @server_thread = Thread.new do
17
- s = WEBrick::HTTPServer.new(:Port => 12000, :DocumentRoot => www_root, :Logger => Log.new(nil, BasicLog::ERROR), :AccessLog => [])
17
+ s = WEBrick::HTTPServer.new(:Port => 12000, :DocumentRoot => www_root, :Logger => Log.new(nil, BasicLog::FATAL), :AccessLog => [])
18
18
  @port = s.config[:Port]
19
19
  begin
20
20
  s.start
@@ -32,12 +32,32 @@ class CssParserDownloadingTests < Test::Unit::TestCase
32
32
  @server_thread = nil
33
33
  end
34
34
 
35
+ def test_loading_a_local_file
36
+ file_name = File.dirname(__FILE__) + '/fixtures/simple.css'
37
+ @cp.load_file!(file_name)
38
+ assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
39
+ end
40
+
35
41
  def test_loading_a_remote_file
36
42
  @cp.load_uri!("#{@uri_base}/simple.css")
37
43
  assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
38
44
  end
39
45
 
40
- def test_following_at_import_rules
46
+ def test_following_at_import_rules_local
47
+ base_dir = File.dirname(__FILE__) + '/fixtures'
48
+ @cp.load_file!('import1.css', base_dir)
49
+
50
+ # from '/import1.css'
51
+ assert_equal 'color: lime;', @cp.find_by_selector('div').join(' ')
52
+
53
+ # from '/subdir/import2.css'
54
+ assert_equal 'text-decoration: none;', @cp.find_by_selector('a').join(' ')
55
+
56
+ # from '/subdir/../simple.css'
57
+ assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
58
+ end
59
+
60
+ def test_following_at_import_rules_remote
41
61
  @cp.load_uri!("#{@uri_base}/import1.css")
42
62
 
43
63
  # from '/import1.css'
@@ -50,6 +70,15 @@ class CssParserDownloadingTests < Test::Unit::TestCase
50
70
  assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
51
71
  end
52
72
 
73
+ def test_following_at_import_rules_from_add_block
74
+ css_block = '@import "../simple.css";'
75
+
76
+ @cp.add_block!(css_block, :base_uri => "#{@uri_base}/subdir/")
77
+
78
+ # from 'simple.css'
79
+ assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
80
+ end
81
+
53
82
  def test_importing_with_media_types
54
83
  @cp.load_uri!("#{@uri_base}/import-with-media-types.css")
55
84
 
@@ -62,6 +91,21 @@ class CssParserDownloadingTests < Test::Unit::TestCase
62
91
  assert_raise CircularReferenceError do
63
92
  @cp.load_uri!("#{@uri_base}/import-circular-reference.css")
64
93
  end
94
+
95
+ cp_without_exceptions = Parser.new(:io_exceptions => false)
96
+
97
+ assert_nothing_raised CircularReferenceError do
98
+ cp_without_exceptions.load_uri!("#{@uri_base}/import-circular-reference.css")
99
+ end
100
+
101
+ end
102
+
103
+ def test_toggling_circular_reference_exception
104
+ cp_without_exceptions = Parser.new(:io_exceptions => false)
105
+
106
+ assert_nothing_raised CircularReferenceError do
107
+ cp_without_exceptions.load_uri!("#{@uri_base}/import-circular-reference.css")
108
+ end
65
109
  end
66
110
 
67
111
  def test_toggling_not_found_exceptions
@@ -1,8 +1,6 @@
1
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__), '../'))
2
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__), '../lib/'))
3
1
  require 'rubygems'
4
2
  require 'test/unit'
5
- require 'css_parser'
6
3
  require 'net/http'
7
4
  require 'open-uri'
8
5
  require 'WEBrick'
6
+ require File.dirname(__FILE__) + '/../lib/css_parser'
@@ -75,6 +75,12 @@ class RuleSetTests < Test::Unit::TestCase
75
75
  assert_equal(declarations.split(' ').sort, rs.declarations_to_s.split(' ').sort)
76
76
  end
77
77
 
78
+ def test_important_declarations_to_s
79
+ declarations = 'color: #fff; font-weight: bold !important;'
80
+ rs = RuleSet.new('#content p, a', declarations)
81
+ assert_equal(declarations.split(' ').sort, rs.declarations_to_s.split(' ').sort)
82
+ end
83
+
78
84
  def test_overriding_specificity
79
85
  rs = RuleSet.new('#content p, a', 'color: white', 1000)
80
86
  rs.each_selector do |sel, decs, spec|
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: css_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ hash: 17
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 1
9
+ - 1
10
+ version: 1.1.1
5
11
  platform: ruby
6
12
  authors:
7
13
  - Alex Dunae
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-02-21 00:00:00 -08:00
18
+ date: 2010-10-26 00:00:00 -07:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -32,7 +38,7 @@ files:
32
38
  - test/fixtures/simple.css
33
39
  - test/fixtures/subdir/import2.css
34
40
  - test/test_css_parser_basic.rb
35
- - test/test_css_parser_downloading.rb
41
+ - test/test_css_parser_loading.rb
36
42
  - test/test_css_parser_media_types.rb
37
43
  - test/test_css_parser_misc.rb
38
44
  - test/test_css_parser_regexps.rb
@@ -55,27 +61,33 @@ rdoc_options:
55
61
  require_paths:
56
62
  - lib
57
63
  required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
58
65
  requirements:
59
66
  - - ">="
60
67
  - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
61
71
  version: "0"
62
- version:
63
72
  required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
64
74
  requirements:
65
75
  - - ">="
66
76
  - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
67
80
  version: "0"
68
- version:
69
81
  requirements: []
70
82
 
71
83
  rubyforge_project:
72
- rubygems_version: 1.3.5
84
+ rubygems_version: 1.3.7
73
85
  signing_key:
74
86
  specification_version: 3
75
87
  summary: Ruby CSS parser.
76
88
  test_files:
77
89
  - test/test_css_parser_basic.rb
78
- - test/test_css_parser_downloading.rb
90
+ - test/test_css_parser_loading.rb
79
91
  - test/test_css_parser_media_types.rb
80
92
  - test/test_css_parser_misc.rb
81
93
  - test/test_css_parser_regexps.rb