css_parser_master 1.2.4

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