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.
- 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
|