css_parser 1.0.1 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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