css_toolkit 1.3

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,195 @@
1
+ module CssTidy
2
+
3
+ # parser current context
4
+ NONE = 0
5
+ IN_SELECTOR = 1
6
+ IN_PROPERTY = 2
7
+ IN_VALUE = 3
8
+ IN_STRING = 4
9
+ IN_COMMENT = 5
10
+ IN_AT_BLOCK = 6
11
+
12
+ # All whitespace allowed in CSS
13
+ WHITESPACE = [' ',"\n","\t","\r","\x0B"]
14
+
15
+ # All CSS tokens used by tidy
16
+ TOKENS = %w[/ @ } { ; : = ' " ( , \\ ! $ % & ) * + . < > ? [ ] ^ ` | ~]
17
+ # All CSS units (CSS 3 units included)
18
+ units = ['in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz']
19
+
20
+ # Available at-rules
21
+ AT_RULES = {
22
+ 'page' => IN_SELECTOR,
23
+ 'font-face' => IN_SELECTOR,
24
+ 'charset' => IN_VALUE,
25
+ 'import' => IN_VALUE,
26
+ 'namespace' => IN_VALUE,
27
+ 'media' => IN_AT_BLOCK
28
+ }
29
+
30
+ # Properties that need a value with unit
31
+ unit_values = [
32
+ 'background', 'background-position',
33
+ 'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-width',
34
+ 'border-top-width', 'border-right-width', 'border-left-width', 'border-bottom-width',
35
+ 'bottom', 'border-spacing', 'font-size', 'height', 'left',
36
+ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
37
+ 'max-height', 'max-width', 'min-height', 'min-width', 'outline-width',
38
+ 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
39
+ 'position', 'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width'
40
+ ]
41
+
42
+ # Properties that allow <color> as value
43
+ color_values = [
44
+ 'background-color', 'border-color', 'border-top-color', 'border-right-color',
45
+ 'border-bottom-color', 'border-left-color', 'color', 'outline-color'
46
+ ]
47
+
48
+ # Default values for the background properties
49
+ background_prop_default = {
50
+ 'background-image' => 'none',
51
+ 'background-size' => 'auto',
52
+ 'background-repeat' => 'repeat',
53
+ 'background-position' => '0 0',
54
+ 'background-attachment' => 'scroll',
55
+ 'background-clip' => 'border',
56
+ 'background-origin' => 'padding',
57
+ 'background-color' => 'transparent',
58
+ }
59
+
60
+ # A list of all shorthand properties that are devided into four properties and/or have four subvalues
61
+ SHORTHANDS = {
62
+ 'border-color' => ['border-top-color','border-right-color','border-bottom-color','border-left-color'],
63
+ 'border-style' => ['border-top-style','border-right-style','border-bottom-style','border-left-style'],
64
+ 'border-width' => ['border-top-width','border-right-width','border-bottom-width','border-left-width'],
65
+ 'margin' => ['margin-top','margin-right','margin-bottom','margin-left'],
66
+ 'padding' => ['padding-top','padding-right','padding-bottom','padding-left'],
67
+ }
68
+
69
+ # All CSS Properties
70
+ PROPERTIES = {
71
+ 'background' => '1.0,2.0,2.1',
72
+ 'background-color' => '1.0,2.0,2.1',
73
+ 'background-image' => '1.0,2.0,2.1',
74
+ 'background-repeat' => '1.0,2.0,2.1',
75
+ 'background-attachment' => '1.0,2.0,2.1',
76
+ 'background-position' => '1.0,2.0,2.1',
77
+ 'border' => '1.0,2.0,2.1',
78
+ 'border-top' => '1.0,2.0,2.1',
79
+ 'border-right' => '1.0,2.0,2.1',
80
+ 'border-bottom' => '1.0,2.0,2.1',
81
+ 'border-left' => '1.0,2.0,2.1',
82
+ 'border-color' => '1.0,2.0,2.1',
83
+ 'border-top-color' => '2.0,2.1',
84
+ 'border-bottom-color' => '2.0,2.1',
85
+ 'border-left-color' => '2.0,2.1',
86
+ 'border-right-color' => '2.0,2.1',
87
+ 'border-style' => '1.0,2.0,2.1',
88
+ 'border-top-style' => '2.0,2.1',
89
+ 'border-right-style' => '2.0,2.1',
90
+ 'border-left-style' => '2.0,2.1',
91
+ 'border-bottom-style' => '2.0,2.1',
92
+ 'border-width' => '1.0,2.0,2.1',
93
+ 'border-top-width' => '1.0,2.0,2.1',
94
+ 'border-right-width' => '1.0,2.0,2.1',
95
+ 'border-left-width' => '1.0,2.0,2.1',
96
+ 'border-bottom-width' => '1.0,2.0,2.1',
97
+ 'border-collapse' => '2.0,2.1',
98
+ 'border-spacing' => '2.0,2.1',
99
+ 'bottom' => '2.0,2.1',
100
+ 'caption-side' => '2.0,2.1',
101
+ 'content' => '2.0,2.1',
102
+ 'clear' => '1.0,2.0,2.1',
103
+ 'clip' => '1.0,2.0,2.1',
104
+ 'color' => '1.0,2.0,2.1',
105
+ 'counter-reset' => '2.0,2.1',
106
+ 'counter-increment' => '2.0,2.1',
107
+ 'cursor' => '2.0,2.1',
108
+ 'empty-cells' => '2.0,2.1',
109
+ 'display' => '1.0,2.0,2.1',
110
+ 'direction' => '2.0,2.1',
111
+ 'float' => '1.0,2.0,2.1',
112
+ 'font' => '1.0,2.0,2.1',
113
+ 'font-family' => '1.0,2.0,2.1',
114
+ 'font-style' => '1.0,2.0,2.1',
115
+ 'font-variant' => '1.0,2.0,2.1',
116
+ 'font-weight' => '1.0,2.0,2.1',
117
+ 'font-stretch' => '2.0',
118
+ 'font-size-adjust' => '2.0',
119
+ 'font-size' => '1.0,2.0,2.1',
120
+ 'height' => '1.0,2.0,2.1',
121
+ 'left' => '1.0,2.0,2.1',
122
+ 'line-height' => '1.0,2.0,2.1',
123
+ 'list-style' => '1.0,2.0,2.1',
124
+ 'list-style-type' => '1.0,2.0,2.1',
125
+ 'list-style-image' => '1.0,2.0,2.1',
126
+ 'list-style-position' => '1.0,2.0,2.1',
127
+ 'margin' => '1.0,2.0,2.1',
128
+ 'margin-top' => '1.0,2.0,2.1',
129
+ 'margin-right' => '1.0,2.0,2.1',
130
+ 'margin-bottom' => '1.0,2.0,2.1',
131
+ 'margin-left' => '1.0,2.0,2.1',
132
+ 'marks' => '1.0,2.0',
133
+ 'marker-offset' => '2.0',
134
+ 'max-height' => '2.0,2.1',
135
+ 'max-width' => '2.0,2.1',
136
+ 'min-height' => '2.0,2.1',
137
+ 'min-width' => '2.0,2.1',
138
+ 'overflow' => '1.0,2.0,2.1',
139
+ 'orphans' => '2.0,2.1',
140
+ 'outline' => '2.0,2.1',
141
+ 'outline-width' => '2.0,2.1',
142
+ 'outline-style' => '2.0,2.1',
143
+ 'outline-color' => '2.0,2.1',
144
+ 'padding' => '1.0,2.0,2.1',
145
+ 'padding-top' => '1.0,2.0,2.1',
146
+ 'padding-right' => '1.0,2.0,2.1',
147
+ 'padding-bottom' => '1.0,2.0,2.1',
148
+ 'padding-left' => '1.0,2.0,2.1',
149
+ 'page-break-before' => '1.0,2.0,2.1',
150
+ 'page-break-after' => '1.0,2.0,2.1',
151
+ 'page-break-inside' => '2.0,2.1',
152
+ 'page' => '2.0',
153
+ 'position' => '1.0,2.0,2.1',
154
+ 'quotes' => '2.0,2.1',
155
+ 'right' => '2.0,2.1',
156
+ 'size' => '1.0,2.0',
157
+ 'speak-header' => '2.0,2.1',
158
+ 'table-layout' => '2.0,2.1',
159
+ 'top' => '1.0,2.0,2.1',
160
+ 'text-indent' => '1.0,2.0,2.1',
161
+ 'text-align' => '1.0,2.0,2.1',
162
+ 'text-decoration' => '1.0,2.0,2.1',
163
+ 'text-shadow' => '2.0',
164
+ 'letter-spacing' => '1.0,2.0,2.1',
165
+ 'word-spacing' => '1.0,2.0,2.1',
166
+ 'text-transform' => '1.0,2.0,2.1',
167
+ 'white-space' => '1.0,2.0,2.1',
168
+ 'unicode-bidi' => '2.0,2.1',
169
+ 'vertical-align' => '1.0,2.0,2.1',
170
+ 'visibility' => '1.0,2.0,2.1',
171
+ 'width' => '1.0,2.0,2.1',
172
+ 'widows' => '2.0,2.1',
173
+ 'z-index' => '1.0,2.0,2.1',
174
+ # Speech
175
+ 'volume' => '2.0,2.1',
176
+ 'speak' => '2.0,2.1',
177
+ 'pause' => '2.0,2.1',
178
+ 'pause-before' => '2.0,2.1',
179
+ 'pause-after' => '2.0,2.1',
180
+ 'cue' => '2.0,2.1',
181
+ 'cue-before' => '2.0,2.1',
182
+ 'cue-after' => '2.0,2.1',
183
+ 'play-during' => '2.0,2.1',
184
+ 'azimuth' => '2.0,2.1',
185
+ 'elevation' => '2.0,2.1',
186
+ 'speech-rate' => '2.0,2.1',
187
+ 'voice-family' => '2.0,2.1',
188
+ 'pitch' => '2.0,2.1',
189
+ 'pitch-range' => '2.0,2.1',
190
+ 'stress' => '2.0,2.1',
191
+ 'richness' => '2.0,2.1',
192
+ 'speak-punctuation' => '2.0,2.1',
193
+ 'speak-numeral' => '2.0,2.1',
194
+ }
195
+ end
@@ -0,0 +1,197 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'css_properties'
3
+
4
+ module CssTidy
5
+
6
+ # The RuleSet class takes ONE selector and a set of declarations
7
+ # Declarations are stored in order to allow for hacks such as the box model hacks
8
+ class RuleSet < CssBase
9
+ attr_accessor :declarations, :selectors
10
+
11
+ def initialize(opts={})
12
+ @selectors = []
13
+
14
+ # declarations are made up of: property S* ':' S* value;
15
+ @declarations = []
16
+ add_rule(opts)
17
+ end
18
+
19
+ def add_rule(opts={})
20
+ if opts[:selector] && opts[:declarations]
21
+ # we assume the declarations are valid, but top and tail whitespace
22
+ # and avoid duplicate selectors
23
+ @selectors << opts[:selector].strip unless @selectors.member?( opts[:selector])
24
+ opts[:declarations].strip.split(/[\;$]+/m).each do |declaration|
25
+ if matches = declaration.match(/(.[^:]*)\:(.[^;]*)(;|\Z)/i)
26
+ property, value, end_of_declaration = matches.captures
27
+ @declarations << CssTidy::Declaration.new(property.strip, value.strip)
28
+ end
29
+ end
30
+ opts[:declarations].strip.split(';').each do |declaration|
31
+ property, value = declaration.strip.split(/:/)
32
+ end
33
+ end
34
+ self
35
+ end
36
+
37
+ def << (declaration)
38
+ property, value = declaration.strip.split(':')
39
+ @declarations << CssTidy::Declaration.new(property.strip, value.strip)
40
+ end
41
+
42
+ def to_hash
43
+ declarations = []
44
+ @declarations.each do |declaration|
45
+ declarations << declaration.to_s
46
+ end
47
+ {@selectors, declarations}
48
+ end
49
+
50
+ def to_s(format=:one_line)
51
+ return '' if @selectors.empty? || @declarations.empty?
52
+
53
+ declarations = []
54
+ @declarations.each do |declaration|
55
+ declarations << declaration.to_s
56
+ end
57
+
58
+ case format
59
+ when :one_line
60
+ @selectors.join(',') + '{' + declarations.join(';') + '}'
61
+ when :multi_line
62
+ @selectors.join(',') + "{\n " + declarations.join(";\n ") + "\n}"
63
+ end
64
+ end
65
+
66
+ # Rule Sets know how to optimise themselves
67
+
68
+ def optimize(options)
69
+ merge_4_part_longhands
70
+ @declarations.each do |declaration|
71
+ declaration.optimize_colors if options[:optimize_colors]
72
+ declaration.fix_invalid_colors if options[:fix_invalid_colors]
73
+ declaration.downcase_property if options[:downcase_properties]
74
+ declaration.optimize_zeros if options[:optimize_zeros]
75
+ declaration.optimize_mp_shorthands if options[:optimize_margin_padding]
76
+ declaration.optimize_filters if options[:optimize_filters]
77
+ declaration.optimize_urls if options[:optimize_urls]
78
+ declaration.optimize_font_weight if options[:optimize_font_weight]
79
+ declaration.optimize_punctuation # no option
80
+ end
81
+ optimize_selectors(options)
82
+ end
83
+
84
+ def merge_4_part_longhands
85
+ SHORTHANDS.each do |shorthand, longhands|
86
+ values = []
87
+ important_count = 0
88
+ @declarations.each_with_index do |declaration, idx|
89
+ # if one is important, the new merged rule will be important.
90
+ # this will probably break CSS in some rare cases
91
+ important_count = declaration.important? ? important_count + 1 : important_count + 0
92
+ case declaration.property
93
+ when longhands[0]
94
+ values[0] = declaration.value
95
+ when longhands[1]
96
+ values[1] = declaration.value
97
+ when longhands[2]
98
+ values[2] = declaration.value
99
+ when longhands[3]
100
+ values[3] = declaration.value
101
+ end
102
+ end
103
+ # remove nil values
104
+ values.compact!
105
+ if values.size == 4
106
+ # remove the old ones
107
+ @declarations.delete_if do |decl|
108
+ longhands.include?(decl.property)
109
+ end
110
+ values.map!{|v| v.gsub(/!important/, '').strip}
111
+ important = important_count > 0 ? '!important' : ''
112
+ # add the new rule
113
+ @declarations << CssTidy::Declaration.new(shorthand, values.join(' ') + important)
114
+ end
115
+ end
116
+ end
117
+
118
+ def optimize_selectors(options)
119
+ @selectors.map do |selector|
120
+ # squish up IE comment in selector hack
121
+ selector.gsub!(/\s*>\s*\/\*\s*\*\/\s*/, '>/**/' )
122
+ # special case for IE
123
+ selector.gsub!(/:first-(line|letter)(,|\Z)/, ':first-\1 \2')
124
+ # remove any kind of comment string
125
+ if options[:keep_ie7_selector_comments]
126
+ selector.gsub!(/\/\*\*\//, '_IE7_') # protect from next options
127
+ end
128
+ if ! options[:keep_selector_comments]
129
+ selector.gsub!(/(\s+)?\/\*(.|[\r\n])*?\*\/(\s+)?/, '')
130
+ end
131
+ selector.gsub!(/_IE7_/, '/**/') # restore hack comments
132
+
133
+ end
134
+ end
135
+
136
+ # starting with colors
137
+ def optimize_colors
138
+ @declarations.each do |declaration|
139
+ declaration.optimize_colors
140
+ end
141
+ end
142
+
143
+ # starting with colors
144
+ def downcase_selectors
145
+ @selectors.map {|selector| selector.downcase }
146
+ end
147
+
148
+ def empty?
149
+ @declarations.empty?
150
+ end
151
+
152
+ def == (other_set)
153
+ # must at least have the same number of declarations and be same type of object
154
+ if other_set.respond_to?(:declaration_count) && declaration_count == other_set.declaration_count
155
+ number_of_identical_declations = 0
156
+ other_set.declarations.each do |other_dec|
157
+ @declarations.each do |this_dec|
158
+ if this_dec == other_dec
159
+ number_of_identical_declations += 1
160
+ end
161
+ end
162
+ end
163
+ true if declaration_count == number_of_identical_declations
164
+ end
165
+ end
166
+
167
+ def + (other_set)
168
+ @selectors += other_set.selectors
169
+ self
170
+ end
171
+
172
+ def declaration_count
173
+ @declarations.length
174
+ end
175
+
176
+ def inspect(indent='')
177
+ puts indent + " + " + @selectors.join(',')
178
+ @declarations.each do |decl|
179
+ puts indent * 2 + "| " + decl.to_s
180
+ end
181
+ end
182
+
183
+ def clear
184
+ @selectors = []
185
+ @declarations = []
186
+ end
187
+
188
+ private
189
+
190
+
191
+ def each_declaration
192
+ @values.each_index do |index|
193
+ yield @properties[index], @values[index]
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,119 @@
1
+ module CssTidy
2
+
3
+ class StyleSheet
4
+ attr_accessor :in_media
5
+ alias :end_at_block :in_media
6
+
7
+ def initialize
8
+ # nodes can contain any kind of object:
9
+ # * charset
10
+ # * media (which can contain media or rulesets)
11
+ # * rulesets
12
+ @nodes = []
13
+ @charset = ''
14
+ @in_media = false
15
+ end
16
+
17
+ def << (object)
18
+ # if we are in media block, then add object to the last node
19
+ if @in_media
20
+ if object.class == CssTidy::MediaSet
21
+ @nodes << object
22
+ else
23
+ @nodes.last << object
24
+ end
25
+ else
26
+ @nodes << object
27
+ if object.class == CssTidy::MediaSet
28
+ @in_media = true
29
+ end
30
+ end
31
+ end
32
+
33
+ def to_s(format=:one_line)
34
+ css = ''
35
+ if ! @charset.empty?
36
+ css << "@charset #{@charset};" + ((format == :multi_line) ? "\n" : '')
37
+ end
38
+
39
+ @nodes.each do |node|
40
+ css << node.to_s(format) + ((format == :multi_line) ? "\n" : '')
41
+ end
42
+ css
43
+ end
44
+
45
+ def charset=(charset)
46
+ if @charset.empty?
47
+ @charset = charset.strip
48
+ return true
49
+ else
50
+ return false
51
+ end
52
+ end
53
+
54
+ def charset
55
+ @charset
56
+ end
57
+
58
+ def optimize(options={})
59
+ keep_next_comment = false
60
+
61
+ @nodes.each_with_index do |node, idx|
62
+ if node.class == CssTidy::Comment
63
+ if node.is_special? && options[:keep_special_comments]
64
+ next # do nothing
65
+ elsif node.is_ie5_hack? && options[:keep_ie5_comment_hack] && ! options[:optimize_selectors]
66
+ node.text = '\\' # replace it
67
+ keep_next_comment = true
68
+ elsif keep_next_comment
69
+ node.text = '' # replace it
70
+ keep_next_comment = false
71
+ elsif ! options[:keep_comments]
72
+ node.printable = false # don't print this one
73
+ end
74
+ end
75
+ node.optimize(options)
76
+ end
77
+
78
+ if options[:optimize_selectors]
79
+ nodes_to_remove = []
80
+ length = @nodes.length
81
+ @nodes.each_with_index do |node, index|
82
+ if node.class == CssTidy::RuleSet
83
+ idx = index
84
+ # Check if properties also exist in another RuleSet
85
+ while idx < length -1
86
+ idx += 1 # start at the next one
87
+ # just Rulsets
88
+ if @nodes[idx].class == CssTidy::RuleSet
89
+ if ! node.empty? && node == @nodes[idx]
90
+ node += @nodes[idx]
91
+ nodes_to_remove << idx
92
+ @nodes[idx].clear
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ def inspect
103
+ indent = ' '
104
+ puts "Stylesheet"
105
+ @nodes.each_with_index do |node, idx|
106
+ case node.class.to_s
107
+ when 'CssTidy::RuleSet'
108
+ puts " + RuleSet"
109
+ when 'CssTidy::Comment'
110
+ puts " + Comment"
111
+ end
112
+ node.inspect(indent)
113
+ # puts node.class
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ end