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.
- data/lib/css_parser.rb +8 -5
- data/lib/css_parser/parser.rb +50 -30
- data/lib/css_parser/regexps.rb +1 -1
- data/lib/css_parser/rule_set.rb +5 -3
- data/test/{test_css_parser_downloading.rb → test_css_parser_loading.rb} +48 -4
- data/test/test_helper.rb +1 -3
- data/test/test_rule_set.rb +6 -0
- metadata +19 -7
data/lib/css_parser.rb
CHANGED
@@ -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'
|
data/lib/css_parser/parser.rb
CHANGED
@@ -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/#{
|
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
|
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.
|
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
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
268
|
-
|
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
|
-
|
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
|
data/lib/css_parser/regexps.rb
CHANGED
@@ -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\()?["']
|
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 + ')*\")')
|
data/lib/css_parser/rule_set.rb
CHANGED
@@ -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? ?
|
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
|
-
|
102
|
-
|
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
|
4
|
-
class
|
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::
|
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
|
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
|
data/test/test_helper.rb
CHANGED
@@ -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'
|
data/test/test_rule_set.rb
CHANGED
@@ -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
|
-
|
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-
|
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/
|
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.
|
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/
|
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
|