css_toolkit 1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/css_toolkit.rb +21 -0
- data/lib/modules/css_base.rb +17 -0
- data/lib/modules/css_comment.rb +42 -0
- data/lib/modules/css_declaration.rb +149 -0
- data/lib/modules/css_import.rb +24 -0
- data/lib/modules/css_media_set.rb +88 -0
- data/lib/modules/css_misc.rb +149 -0
- data/lib/modules/css_parser.rb +463 -0
- data/lib/modules/css_properties.rb +195 -0
- data/lib/modules/css_rule_set.rb +197 -0
- data/lib/modules/css_stylesheet.rb +119 -0
- data/lib/modules/css_tidy.rb +78 -0
- data/lib/modules/yui_compressor.rb +224 -0
- metadata +78 -0
data/lib/css_toolkit.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'modules/yui_compressor'
|
4
|
+
require 'modules/css_parser'
|
5
|
+
require 'modules/css_tidy'
|
6
|
+
|
7
|
+
module CssToolkit
|
8
|
+
include YuiCompressor
|
9
|
+
VERSION = "1.3"
|
10
|
+
|
11
|
+
def yui_compressor(css, line_length=0)
|
12
|
+
yui = Yui.new()
|
13
|
+
yui.compress(css, line_length)
|
14
|
+
end
|
15
|
+
|
16
|
+
def css_tidy(css, opts={})
|
17
|
+
tidy = Tidy.new()
|
18
|
+
tidy.tidy(css, line_length)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CssTidy
|
2
|
+
|
3
|
+
class Comment < CssBase
|
4
|
+
attr_accessor :text, :printable
|
5
|
+
|
6
|
+
def initialize(text='')
|
7
|
+
@text = text
|
8
|
+
@printable = true
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def << (text)
|
13
|
+
@text << text
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s(format=nil)
|
17
|
+
if @printable
|
18
|
+
"/*#{@text}*/"
|
19
|
+
else
|
20
|
+
''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# special comments start with a ! and should not be deleted
|
25
|
+
def is_special?
|
26
|
+
text[0,1] == '!'
|
27
|
+
end
|
28
|
+
|
29
|
+
# looks for a \ at the end of the comment, indicating
|
30
|
+
# that it is a the start of an IE5 hack
|
31
|
+
# why anyone is still doing this, is beyond me! :-)
|
32
|
+
def is_ie5_hack?
|
33
|
+
text[-1,1] == '\\'
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear
|
37
|
+
@text = ''
|
38
|
+
@printable = false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module CssTidy
|
2
|
+
|
3
|
+
class Declaration < CssBase
|
4
|
+
attr_accessor :property, :value
|
5
|
+
|
6
|
+
# sets the values
|
7
|
+
# we assume that any extraneous whitespace has been striped
|
8
|
+
def initialize(property, value)
|
9
|
+
@property = property
|
10
|
+
@value = value
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s(format=:one_line)
|
15
|
+
unless @property.empty? && @value.empty?
|
16
|
+
"#{@property}:#{@value}"
|
17
|
+
else
|
18
|
+
''
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def optimize_colors
|
23
|
+
# Shorten colors from rgb(51,102,153) to #336699
|
24
|
+
# This makes it more likely that it'll get further compressed in the next step.
|
25
|
+
@value.gsub!(/rgb\s*\(\s*([0-9,\s]+)\s*\)/) do |match|
|
26
|
+
'#' << $1.scan(/\d+/).map{|n| n.to_i.to_s(16).rjust(2, '0') }.join
|
27
|
+
end
|
28
|
+
|
29
|
+
# Shorten colors from #AABBCC to #ABC. Note that we want to make sure
|
30
|
+
# the color is not preceded by either ", " or =. Indeed, the property
|
31
|
+
# filter: chroma(color="#FFFFFF");
|
32
|
+
# would become
|
33
|
+
# filter: chroma(color="#FFF");
|
34
|
+
# which makes the filter break in IE.
|
35
|
+
if @value !~ /["'=]/
|
36
|
+
@value.gsub!(/#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3/i, '#\1\2\3')
|
37
|
+
CssTidy::SHORTEN_COLORS.each do |from,to|
|
38
|
+
@value.gsub!(/#{from}/i, to)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def fix_invalid_colors
|
44
|
+
if @value !~ /["'=]/
|
45
|
+
CssTidy::INVALID_COLORS.each do |from,to|
|
46
|
+
@value.gsub!(/#{from}/i, to)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def optimize_zeros
|
52
|
+
# Replace 0(%, em, ex, px, in, cm, mm, pt, pc) with just 0.
|
53
|
+
@value.gsub!(/(\s|\A)([+-]?0)(?:%|em|ex|px|in|cm|mm|pt|pc)/i, '\1\2')
|
54
|
+
|
55
|
+
if @property =~ /background(-position)?/i
|
56
|
+
@value.gsub!(/(0 )+0/, '0 0')
|
57
|
+
else
|
58
|
+
# Replace 0 0 0 0; with 0.
|
59
|
+
@value.gsub!(/\A(?:0 )+0\Z/, '0')
|
60
|
+
end
|
61
|
+
# Replace 0.6 with .6, but only when it is the first rule or preceded by a space.
|
62
|
+
@value.gsub!(/(\s|\A)0+\.(\d+)/, '\1.\2')
|
63
|
+
end
|
64
|
+
|
65
|
+
def optimize_filters
|
66
|
+
# shorter opacity IE filter
|
67
|
+
@value.gsub!(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, "alpha(opacity=")
|
68
|
+
end
|
69
|
+
|
70
|
+
def optimize_punctuation
|
71
|
+
@value.gsub!(/\s+([!+\(\)\],])/, '\1')
|
72
|
+
@value.gsub!(/([!+\(\[,])\s+/, '\1')
|
73
|
+
end
|
74
|
+
|
75
|
+
def optimize_font_weight
|
76
|
+
if @property =~ /font/
|
77
|
+
@value.gsub!(/bold/, '700')
|
78
|
+
@value.gsub!(/normal/, '400')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def optimize_mp_shorthands
|
83
|
+
if SHORTHANDS.has_key?(@property)
|
84
|
+
important = important! ? '!important' : ''
|
85
|
+
values = @value.split(/\s+/)
|
86
|
+
values
|
87
|
+
case values.length
|
88
|
+
when 4
|
89
|
+
# 4 to 1 margin:5px 5px 5px 5px => margin:5px
|
90
|
+
if values[0] == values[1] && values[0] == values[2] && values[0] == values[3]
|
91
|
+
@value = "#{values[0]}"
|
92
|
+
# 4 to 3 margin:0 0 10px 0 => margin:0 0 10px
|
93
|
+
elsif values[0] == values[2] && values[1] == values[3]
|
94
|
+
@value = "#{values[0]} #{values[1]}"
|
95
|
+
# 4 to 2 margin:5px 0 5px 0 => margin:5px 0
|
96
|
+
elsif values[1] == values[3]
|
97
|
+
@value = "#{values[0]} #{values[1]} #{values[2]}"
|
98
|
+
end
|
99
|
+
when 3
|
100
|
+
# 3 to 1 margin:5px 5px 5px => margin:5px
|
101
|
+
if values[0] == values[1] && values[0] == values[2]
|
102
|
+
@value = "#{values[0]}"
|
103
|
+
# 3 to 2 margin:0 10px 0 => margin:0 10px
|
104
|
+
elsif values[0] == values[2]
|
105
|
+
@value = "#{values[0]} #{values[1]}"
|
106
|
+
end
|
107
|
+
when 2
|
108
|
+
# 2 to 1 margin:5px 5px => margin:5px
|
109
|
+
if values[0] == values[1]
|
110
|
+
@value = "#{values[0]}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@value << important
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def optimize_urls
|
118
|
+
# remove the quotes - they are optional
|
119
|
+
@value.gsub!(/url\(('|")(.+?)('|")\)/, 'url(\2)')
|
120
|
+
end
|
121
|
+
|
122
|
+
def downcase_property
|
123
|
+
@property.downcase! unless @property.frozen?
|
124
|
+
end
|
125
|
+
|
126
|
+
def == (other)
|
127
|
+
@property == other.property && @value == other.value
|
128
|
+
end
|
129
|
+
|
130
|
+
def inspect(indent='')
|
131
|
+
puts indent + @property + ':' + @value
|
132
|
+
end
|
133
|
+
|
134
|
+
# remove !important and return true if it was replaced
|
135
|
+
def important!
|
136
|
+
@value.gsub!(/!important/, '')
|
137
|
+
end
|
138
|
+
|
139
|
+
def important?
|
140
|
+
@value =~ /!important/
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear
|
144
|
+
@property = ''
|
145
|
+
@value = ''
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CssTidy
|
2
|
+
|
3
|
+
class Import < CssBase
|
4
|
+
attr_accessor :import
|
5
|
+
|
6
|
+
def initialize(text='')
|
7
|
+
@import = text
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s(format=nil)
|
12
|
+
unless @import.empty?
|
13
|
+
"@import #{@import};"
|
14
|
+
else
|
15
|
+
''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear
|
20
|
+
@import = ''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module CssTidy
|
2
|
+
|
3
|
+
# media sets live as nodes in a stylesheet
|
4
|
+
# when a set is added to a style sheet, the sheet
|
5
|
+
# adds all new nodes to the last mediaset
|
6
|
+
#
|
7
|
+
class MediaSet < CssBase
|
8
|
+
attr_accessor :at_media
|
9
|
+
|
10
|
+
def initialize(at_media)
|
11
|
+
# nodes can contain:
|
12
|
+
# * ruleset
|
13
|
+
# * comment
|
14
|
+
@nodes = []
|
15
|
+
@at_media = at_media
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def << (object)
|
20
|
+
@nodes << object
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s(format=:one_line, indent='')
|
24
|
+
css = "#{@at_media}{" + ((format == :multi_line) ? "\n" : '')
|
25
|
+
@nodes.each do |node|
|
26
|
+
css << indent + node.to_s(format) + ((format == :multi_line) ? "\n" : '')
|
27
|
+
end
|
28
|
+
css << '}' + ((format == :multi_line) ? "\n" : '')
|
29
|
+
css
|
30
|
+
end
|
31
|
+
|
32
|
+
def optimize(options)
|
33
|
+
return nil if @nodes.empty? || @at_media.empty?
|
34
|
+
# clean up self first
|
35
|
+
@at_media.gsub!(/\*\/\s+\/\*/, '*//*')
|
36
|
+
|
37
|
+
# then do kids
|
38
|
+
keep_next_comment = false
|
39
|
+
|
40
|
+
@nodes.each_with_index do |node, idx|
|
41
|
+
if node.class == CssTidy::Comment
|
42
|
+
if node.is_special? && options[:keep_special_comments]
|
43
|
+
next # do nothing
|
44
|
+
elsif node.is_ie5_hack? && options[:keep_ie5_comment_hack]
|
45
|
+
node.text = '\\' # replace it
|
46
|
+
keep_next_comment = true
|
47
|
+
elsif keep_next_comment
|
48
|
+
node.text = '' # replace it
|
49
|
+
keep_next_comment = false
|
50
|
+
else
|
51
|
+
node.printable = false # don't print this one
|
52
|
+
end
|
53
|
+
end
|
54
|
+
node.optimize(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
if options[:optimize_selectors]
|
58
|
+
nodes_to_remove = []
|
59
|
+
length = @nodes.length
|
60
|
+
@nodes.each_with_index do |node, index|
|
61
|
+
if node.class == CssTidy::RuleSet
|
62
|
+
idx = index
|
63
|
+
# Check if properties also exist in another RuleSet
|
64
|
+
while idx < length -1
|
65
|
+
idx += 1 # start at the next one
|
66
|
+
# just Rulsets
|
67
|
+
if @nodes[idx].class == CssTidy::RuleSet
|
68
|
+
if ! node.empty? && node == @nodes[idx]
|
69
|
+
node += @nodes[idx]
|
70
|
+
nodes_to_remove << idx
|
71
|
+
@nodes[idx].clear
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear
|
82
|
+
@nodes = []
|
83
|
+
@at_media = ''
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module CssTidy
|
2
|
+
|
3
|
+
SHORTEN_COLORS = {
|
4
|
+
'white' => '#fff',
|
5
|
+
'black' => '#000',
|
6
|
+
'fuchsia' => '#f0f',
|
7
|
+
'yellow' => '#ff0',
|
8
|
+
'#f00' => 'red',
|
9
|
+
'#800000' => 'maroon',
|
10
|
+
'#ffa500' => 'orange',
|
11
|
+
'#808000' => 'olive',
|
12
|
+
'#800080' => 'purple',
|
13
|
+
'#008000' => 'green',
|
14
|
+
'#000080' => 'navy',
|
15
|
+
'#008080' => 'teal',
|
16
|
+
'#c0c0c0' => 'silver',
|
17
|
+
'#808080' => 'gray'
|
18
|
+
}
|
19
|
+
|
20
|
+
# A list of non-W3C color names which get replaced by their hex-codes
|
21
|
+
INVALID_COLORS = {
|
22
|
+
'aliceblue' => '#F0F8FF',
|
23
|
+
'antiquewhite' => '#FAEBD7',
|
24
|
+
'aquamarine' => '#7FFFD4',
|
25
|
+
'azure' => '#F0FFFF',
|
26
|
+
'beige' => '#F5F5DC',
|
27
|
+
'bisque' => '#FFE4C4',
|
28
|
+
'blanchedalmond' => '#FFEBCD',
|
29
|
+
'blueviolet' => '#8A2BE2',
|
30
|
+
'brown' => '#A52A2A',
|
31
|
+
'burlywood' => '#DEB887',
|
32
|
+
'cadetblue' => '#5F9EA0',
|
33
|
+
'chartreuse' => '#7FFF00',
|
34
|
+
'chocolate' => '#D2691E',
|
35
|
+
'coral' => '#FF7F50',
|
36
|
+
'cornflowerblue' => '#6495ED',
|
37
|
+
'cornsilk' => '#FFF8DC',
|
38
|
+
'crimson' => '#DC143C',
|
39
|
+
'cyan' => '#00FFFF',
|
40
|
+
'darkblue' => '#00008B',
|
41
|
+
'darkcyan' => '#008B8B',
|
42
|
+
'darkgoldenrod' => '#B8860B',
|
43
|
+
'darkgray' => '#A9A9A9',
|
44
|
+
'darkgreen' => '#006400',
|
45
|
+
'darkkhaki' => '#BDB76B',
|
46
|
+
'darkmagenta' => '#8B008B',
|
47
|
+
'darkolivegreen' => '#556B2F',
|
48
|
+
'darkorange' => '#FF8C00',
|
49
|
+
'darkorchid' => '#9932CC',
|
50
|
+
'darkred' => '#8B0000',
|
51
|
+
'darksalmon' => '#E9967A',
|
52
|
+
'darkseagreen' => '#8FBC8F',
|
53
|
+
'darkslateblue' => '#483D8B',
|
54
|
+
'darkslategray' => '#2F4F4F',
|
55
|
+
'darkturquoise' => '#00CED1',
|
56
|
+
'darkviolet' => '#9400D3',
|
57
|
+
'deeppink' => '#FF1493',
|
58
|
+
'deepskyblue' => '#00BFFF',
|
59
|
+
'dimgray' => '#696969',
|
60
|
+
'dodgerblue' => '#1E90FF',
|
61
|
+
'feldspar' => '#D19275',
|
62
|
+
'firebrick' => '#B22222',
|
63
|
+
'floralwhite' => '#FFFAF0',
|
64
|
+
'forestgreen' => '#228B22',
|
65
|
+
'gainsboro' => '#DCDCDC',
|
66
|
+
'ghostwhite' => '#F8F8FF',
|
67
|
+
'gold' => '#FFD700',
|
68
|
+
'goldenrod' => '#DAA520',
|
69
|
+
'greenyellow' => '#ADFF2F',
|
70
|
+
'honeydew' => '#F0FFF0',
|
71
|
+
'hotpink' => '#FF69B4',
|
72
|
+
'indianred' => '#CD5C5C',
|
73
|
+
'indigo' => '#4B0082',
|
74
|
+
'ivory' => '#FFFFF0',
|
75
|
+
'khaki' => '#F0E68C',
|
76
|
+
'lavender' => '#E6E6FA',
|
77
|
+
'lavenderblush' => '#FFF0F5',
|
78
|
+
'lawngreen' => '#7CFC00',
|
79
|
+
'lemonchiffon' => '#FFFACD',
|
80
|
+
'lightblue' => '#ADD8E6',
|
81
|
+
'lightcoral' => '#F08080',
|
82
|
+
'lightcyan' => '#E0FFFF',
|
83
|
+
'lightgoldenrodyellow' => '#FAFAD2',
|
84
|
+
'lightgrey' => '#D3D3D3',
|
85
|
+
'lightgreen' => '#90EE90',
|
86
|
+
'lightpink' => '#FFB6C1',
|
87
|
+
'lightsalmon' => '#FFA07A',
|
88
|
+
'lightseagreen' => '#20B2AA',
|
89
|
+
'lightskyblue' => '#87CEFA',
|
90
|
+
'lightslateblue' => '#8470FF',
|
91
|
+
'lightslategray' => '#778899',
|
92
|
+
'lightsteelblue' => '#B0C4DE',
|
93
|
+
'lightyellow' => '#FFFFE0',
|
94
|
+
'limegreen' => '#32CD32',
|
95
|
+
'linen' => '#FAF0E6',
|
96
|
+
'magenta' => '#FF00FF',
|
97
|
+
'mediumaquamarine' => '#66CDAA',
|
98
|
+
'mediumblue' => '#0000CD',
|
99
|
+
'mediumorchid' => '#BA55D3',
|
100
|
+
'mediumpurple' => '#9370D8',
|
101
|
+
'mediumseagreen' => '#3CB371',
|
102
|
+
'mediumslateblue' => '#7B68EE',
|
103
|
+
'mediumspringgreen' => '#00FA9A',
|
104
|
+
'mediumturquoise' => '#48D1CC',
|
105
|
+
'mediumvioletred' => '#C71585',
|
106
|
+
'midnightblue' => '#191970',
|
107
|
+
'mintcream' => '#F5FFFA',
|
108
|
+
'mistyrose' => '#FFE4E1',
|
109
|
+
'moccasin' => '#FFE4B5',
|
110
|
+
'navajowhite' => '#FFDEAD',
|
111
|
+
'oldlace' => '#FDF5E6',
|
112
|
+
'olivedrab' => '#6B8E23',
|
113
|
+
'orangered' => '#FF4500',
|
114
|
+
'orchid' => '#DA70D6',
|
115
|
+
'palegoldenrod' => '#EEE8AA',
|
116
|
+
'palegreen' => '#98FB98',
|
117
|
+
'paleturquoise' => '#AFEEEE',
|
118
|
+
'palevioletred' => '#D87093',
|
119
|
+
'papayawhip' => '#FFEFD5',
|
120
|
+
'peachpuff' => '#FFDAB9',
|
121
|
+
'peru' => '#CD853F',
|
122
|
+
'pink' => '#FFC0CB',
|
123
|
+
'plum' => '#DDA0DD',
|
124
|
+
'powderblue' => '#B0E0E6',
|
125
|
+
'rosybrown' => '#BC8F8F',
|
126
|
+
'royalblue' => '#4169E1',
|
127
|
+
'saddlebrown' => '#8B4513',
|
128
|
+
'salmon' => '#FA8072',
|
129
|
+
'sandybrown' => '#F4A460',
|
130
|
+
'seagreen' => '#2E8B57',
|
131
|
+
'seashell' => '#FFF5EE',
|
132
|
+
'sienna' => '#A0522D',
|
133
|
+
'skyblue' => '#87CEEB',
|
134
|
+
'slateblue' => '#6A5ACD',
|
135
|
+
'slategray' => '#708090',
|
136
|
+
'snow' => '#FFFAFA',
|
137
|
+
'springgreen' => '#00FF7F',
|
138
|
+
'steelblue' => '#4682B4',
|
139
|
+
'tan' => '#D2B48C',
|
140
|
+
'thistle' => '#D8BFD8',
|
141
|
+
'tomato' => '#FF6347',
|
142
|
+
'turquoise' => '#40E0D0',
|
143
|
+
'violet' => '#EE82EE',
|
144
|
+
'violetred' => '#D02090',
|
145
|
+
'wheat' => '#F5DEB3',
|
146
|
+
'whitesmoke' => '#F5F5F5',
|
147
|
+
'yellowgreen' => '#9ACD32',
|
148
|
+
}
|
149
|
+
end
|