css_parser_master 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ doc/*
2
+ pkg/*
3
+ css_parser-*.gem
4
+ *.DS_Store
@@ -0,0 +1,34 @@
1
+ === Ruby CSS Parser CHANGELOG
2
+
3
+ ==== Version 1.1.2
4
+ * Extracted declarations API from RuleSet
5
+ * Added declarations API to both RuleSet and Selector classes
6
+ * Tests still work!
7
+
8
+ TODO
9
+ Use enumerables Declarations and Selectors instead of simple string arrays for much greater power and flexibility!
10
+
11
+ ==== Version 1.1.1
12
+ * Encapsulated selector into its own class
13
+ * Encapsulated declaration into its own class
14
+ * Gem now uses Jewler (new Rakefile.rb created)
15
+ * All tests work with new selector and declaration iteration strategies which now returns a Selector and Declaration instance
16
+
17
+ ==== Version 1.1.0
18
+ * Added support for local @import
19
+ * Better remote @import handling
20
+
21
+ ==== Version 1.0.1
22
+ * Fallback for declartions without sort order
23
+
24
+ ==== Version 1.0.0
25
+ * Various test fixes and udpate for Ruby 1.9 (thanks to Tyler Cunnion)
26
+ * Allow setting CSS declarations to nil
27
+
28
+ ==== Version 0.9
29
+ * Initial version forked from Premailer project
30
+
31
+ ==== TODO: Future
32
+ * border shorthand/folding support
33
+ * re-implement caching on CssParser.merge
34
+ * correctly parse http://www.webstandards.org/files/acid2/test.html
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ === Ruby CSS Parser Master License
2
+
3
+ Copyright (c) 2010-05 Kristian Mandrup
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
File without changes
@@ -0,0 +1,77 @@
1
+ = Ruby CSS Parser
2
+
3
+ Load, parse and cascade CSS rule sets in Ruby.
4
+
5
+ === Setup
6
+
7
+ Install the gem.
8
+
9
+ gem install css_parser
10
+
11
+ Done.
12
+
13
+ === An example
14
+ require 'css_parser_master'
15
+ include CssParserMaster
16
+
17
+ parser = CssParserMaster::Parser.new
18
+ parser.load_uri!('http://example.com/styles/style.css')
19
+
20
+ # load a remote file, setting the base_uri and media_types
21
+ parser.load_uri!('../style.css', 'http://example.com/styles/inc/', [:screen, :handheld])
22
+
23
+ # load a local file, setting the base_dir and media_types
24
+ parser.load_file!('print.css', '~/styles/', :print)
25
+
26
+ # lookup a rule by a selector
27
+ parser.find('#content')
28
+ #=> 'font-size: 13px; line-height: 1.2;'
29
+
30
+ # lookup a rule by a selector and media type
31
+ parser.find('#content', [:screen, :handheld])
32
+
33
+ # iterate through selectors by media type
34
+ parser.each_selector(:screen) do |sel|
35
+ puts sel
36
+ puts sel.selector
37
+ puts sel.declarations
38
+ puts sel.specificity
39
+ ...
40
+ end
41
+
42
+ # add a block of CSS
43
+ css = <<-EOT
44
+ body { margin: 0 1em; }
45
+ EOT
46
+
47
+ parser.add_block!(css)
48
+
49
+ # output all CSS rules in a single stylesheet
50
+ parser.to_s
51
+ => #content { font-size: 13px; line-height: 1.2; }
52
+ body { margin: 0 1em; }
53
+
54
+ === Testing
55
+
56
+ You can run the suite of unit tests using <tt>rake test</tt>.
57
+
58
+ The download/import tests use WEBrick. The tests set up
59
+ a temporary server on port 12000 and pull down files from the <tt>test/fixtures/</tt>
60
+ directory.
61
+
62
+ === Design notes
63
+
64
+ The extensions that come with CssParserMaster were created in order to generate a more 'powerful' parse model.
65
+ This model should provide more powerful and intuitive ways of iterating and operating on the model.
66
+ Mainly Selector and Declaration were added as classes instead of the old version where they were simply hashes and strings.
67
+
68
+ === Credits and code
69
+
70
+ Original CssParser by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-10.
71
+ CssParserMaster was created by Kristian Mandrup as an extension of CssParser in order to allow releases of the updated gem with some improvements.
72
+
73
+ Original project homepage: http://github.com/alexdunae/css_parser
74
+
75
+ Thanks to {Tyler Cunnion}[http://github.com/tylercunnion] for the updates leading to 1.0.0.
76
+
77
+ Made on Vancouver Island.
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "css_parser_master"
5
+ gem.summary = %Q{Parse CSS files, access and operate on a model of the CSS rules}
6
+ gem.description = %Q{Parse a CSS file and access/operate on rulesets, selectors, declarations etc. Includes specificity calculated according to W3C spec.}
7
+ gem.email = "kmandrup@gmail.com"
8
+ gem.homepage = "http://github.com/kristianmandrup/load-me"
9
+ gem.authors = ["Kristian Mandrup", "Alex Dunae"]
10
+ # gem.add_development_dependency "rspec", ">= 2.0.0"
11
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
12
+
13
+ # add more gem options here
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.4
@@ -0,0 +1,156 @@
1
+ require 'uri'
2
+ require 'digest/md5'
3
+ require 'zlib'
4
+ require 'iconv'
5
+
6
+ module CssParserMaster
7
+ VERSION = '1.2.5'
8
+
9
+ # Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
10
+ # (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
11
+ #
12
+ # Takes one or more RuleSet objects.
13
+ #
14
+ # Returns a RuleSet.
15
+ #
16
+ # ==== Cascading
17
+ # If a RuleSet object has its +specificity+ defined, that specificity is
18
+ # used in the cascade calculations.
19
+ #
20
+ # If no specificity is explicitly set and the RuleSet has *one* selector,
21
+ # the specificity is calculated using that selector.
22
+ #
23
+ # If no selectors or multiple selectors are present, the specificity is
24
+ # treated as 0.
25
+ #
26
+ # ==== Example #1
27
+ # rs1 = RuleSet.new(nil, 'color: black;')
28
+ # rs2 = RuleSet.new(nil, 'margin: 0px;')
29
+ #
30
+ # merged = CssParserMaster.merge(rs1, rs2)
31
+ #
32
+ # puts merged
33
+ # => "{ margin: 0px; color: black; }"
34
+ #
35
+ # ==== Example #2
36
+ # rs1 = RuleSet.new(nil, 'background-color: black;')
37
+ # rs2 = RuleSet.new(nil, 'background-image: none;')
38
+ #
39
+ # merged = CssParserMaster.merge(rs1, rs2)
40
+ #
41
+ # puts merged
42
+ # => "{ background: none black; }"
43
+ #--
44
+ # TODO: declaration_hashes should be able to contain a RuleSet
45
+ # this should be a Class method
46
+ def self.merge(*rule_sets)
47
+ @folded_declaration_cache = {}
48
+
49
+ # in case called like CssParser.merge([rule_set, rule_set])
50
+ rule_sets.flatten! if rule_sets[0].kind_of?(Array)
51
+
52
+ unless rule_sets.all? {|rs| rs.kind_of?(CssParser::RuleSet)}
53
+ raise ArgumentError, "all parameters must be CssParser::RuleSets."
54
+ end
55
+
56
+ return rule_sets[0] if rule_sets.length == 1
57
+
58
+ # Internal storage of CSS properties that we will keep
59
+ properties = {}
60
+
61
+ rule_sets.each do |rule_set|
62
+ rule_set.expand_shorthand!
63
+
64
+ specificity = rule_set.specificity
65
+ unless specificity
66
+ if rule_set.selectors.length == 1
67
+ specificity = calculate_specificity(rule_set.selectors[0])
68
+ else
69
+ specificity = 0
70
+ end
71
+ end
72
+
73
+ rule_set.each_declaration do |decl|
74
+
75
+ property = decl.property
76
+ value = decl.value
77
+ is_important = decl.important
78
+
79
+ # Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order
80
+ if not properties.has_key?(decl.property) or
81
+ is_important or # step 2
82
+ properties[property][:specificity] < specificity or # step 3
83
+ properties[property][:specificity] == specificity # step 4
84
+ properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
85
+ end
86
+ end
87
+ end
88
+
89
+ merged = RuleSet.new(nil, nil)
90
+
91
+ # TODO: what about important
92
+ properties.each do |property, details|
93
+ merged[property.strip] = details[:value].strip
94
+ end
95
+
96
+ merged.create_shorthand!
97
+ merged
98
+ end
99
+
100
+ # Calculates the specificity of a CSS selector
101
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
102
+ #
103
+ # Returns an integer.
104
+ #
105
+ # ==== Example
106
+ # CssParser.calculate_specificity('#content div p:first-line a:link')
107
+ # => 114
108
+ #--
109
+ # Thanks to Rafael Salazar and Nick Fitzsimons on the css-discuss list for their help.
110
+ #++
111
+ def self.calculate_specificity(selector)
112
+ a = 0
113
+ b = selector.scan(/\#/).length
114
+ c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX).length
115
+ d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX).length
116
+
117
+ (a.to_s + b.to_s + c.to_s + d.to_s).to_i
118
+ rescue
119
+ return 0
120
+ end
121
+
122
+ # Make <tt>url()</tt> links absolute.
123
+ #
124
+ # Takes a block of CSS and returns it with all relative URIs converted to absolute URIs.
125
+ #
126
+ # "For CSS style sheets, the base URI is that of the style sheet, not that of the source document."
127
+ # per http://www.w3.org/TR/CSS21/syndata.html#uri
128
+ #
129
+ # Returns a string.
130
+ #
131
+ # ==== Example
132
+ # CssParser.convert_uris("body { background: url('../style/yellow.png?abc=123') };",
133
+ # "http://example.org/style/basic.css").inspect
134
+ # => "body { background: url('http://example.org/style/yellow.png?abc=123') };"
135
+ def self.convert_uris(css, base_uri)
136
+ out = ''
137
+ base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)
138
+
139
+ out = css.gsub(URI_RX) do |s|
140
+ uri = $1.to_s
141
+ uri.gsub!(/["']+/, '')
142
+ # Don't process URLs that are already absolute
143
+ unless uri =~ /^[a-z]+\:\/\//i
144
+ begin
145
+ uri = base_uri.merge(uri)
146
+ rescue; end
147
+ end
148
+ "url('" + uri.to_s + "')"
149
+ end
150
+ out
151
+ end
152
+ end
153
+
154
+ require File.dirname(__FILE__) + '/css_parser_master/rule_set'
155
+ require File.dirname(__FILE__) + '/css_parser_master/regexps'
156
+ require File.dirname(__FILE__) + '/css_parser_master/parser'
@@ -0,0 +1,31 @@
1
+ module CssParserMaster
2
+ class Declaration
3
+ attr_accessor :property, :value, :important, :order
4
+
5
+ def initialize(property, value, important = false, order = 0)
6
+ # puts "init new declaration: #{property}"
7
+ @property = property
8
+ @value = value
9
+ @important = important
10
+ @order = order
11
+ end
12
+
13
+ def [] index
14
+ case index
15
+ when :value
16
+ value
17
+ when :order
18
+ order
19
+ when :is_important
20
+ important
21
+ when :property
22
+ property
23
+ end
24
+ end
25
+
26
+ def to_text(importance = nil)
27
+ "#{property}: #{value}#{ ' !important' if important || importance};"
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ module CssParserMaster
2
+ module DeclarationAPI
3
+
4
+ def ensure_valid_declarations!
5
+ @declarations.each do |d|
6
+ name = d[0]
7
+ prop = d[1]
8
+ if prop.kind_of? Hash
9
+ value = prop[:value]
10
+ important = prop[:is_important]
11
+ @declarations[d[0]] = Declaration.new(name, value, important, @order += 1)
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ def each_declaration # :yields: property, value, is_important
18
+ ensure_valid_declarations!
19
+ decs = @declarations.sort { |a,b| a[1].order <=> b[1].order }
20
+ # puts "decs: #{decs.inspect}"
21
+ decs.each do |decl|
22
+ yield decl[1]
23
+ end
24
+ end
25
+
26
+
27
+ # Return all declarations as a string.
28
+ #--
29
+ # TODO: Clean-up regexp doesn't seem to work
30
+ #++
31
+ def declarations_to_s(options = {})
32
+ options = {:force_important => false}.merge(options)
33
+ str = ''
34
+ importance = options[:force_important] # ? ' !important' : ''
35
+ self.each_declaration do |decl|
36
+ str += "#{decl.to_text(importance)}"
37
+ end
38
+ str.gsub(/^[\s]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
39
+ end
40
+
41
+
42
+ # Add a CSS declaration to the current RuleSet.
43
+ #
44
+ # rule_set.add_declaration!('color', 'blue')
45
+ #
46
+ # puts rule_set['color']
47
+ # => 'blue;'
48
+ #
49
+ # rule_set.add_declaration!('margin', '0px auto !important')
50
+ #
51
+ # puts rule_set['margin']
52
+ # => '0px auto !important;'
53
+ #
54
+ # If the property already exists its value will be over-written.
55
+ def add_declaration!(property, value)
56
+ if value.nil? or value.empty?
57
+ @declarations.delete(property)
58
+ return
59
+ end
60
+
61
+ value.gsub!(/;\Z/, '')
62
+ is_important = !value.gsub!(CssParserMaster::IMPORTANT_IN_PROPERTY_RX, '').nil?
63
+ property = property.downcase.strip
64
+
65
+ decl = CssParserMaster::Declaration.new property.downcase.strip, value.strip, is_important, @order += 1
66
+ # puts "new decl: #{decl.inspect}, #{decl.class}"
67
+ @declarations[property] = decl
68
+ end
69
+ alias_method :[]=, :add_declaration!
70
+
71
+ def parse_declarations!(block) # :nodoc:
72
+ @declarations ||= {}
73
+
74
+ return unless block
75
+
76
+ block.gsub!(/(^[\s]*)|([\s]*$)/, '')
77
+
78
+ decs = block.split(/[\;$]+/m)
79
+
80
+ decs.each do |decs|
81
+ if matches = decs.match(/(.[^:]*)\:(.[^;]*)(;|\Z)/i)
82
+ property, value, end_of_declaration = matches.captures
83
+
84
+ # puts "parse - property: #{property} , value: #{value}"
85
+ add_declaration!(property, value)
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ module CssParserMaster
2
+ class Declarations
3
+ include Enumerable
4
+
5
+ attr_reader :declarations
6
+
7
+ def << declaration
8
+ declarations << declaration
9
+ end
10
+
11
+ def each
12
+ @declarations.each { |dec| yield dec }
13
+ end
14
+
15
+ def empty?
16
+ declarations.empty?
17
+ end
18
+
19
+ def initialize(declarations)
20
+ @declarations = declarations
21
+ end
22
+ end
23
+ end