css 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +11 -0
- data/lib/css.rb +12 -0
- data/lib/css/colors.rb +153 -0
- data/lib/css/errors.rb +11 -0
- data/lib/css/normalize.rb +15 -0
- data/lib/css/parser.rb +88 -0
- data/lib/css/properties/background_property.rb +60 -0
- data/lib/css/properties/border_property.rb +64 -0
- data/lib/css/properties/font_property.rb +98 -0
- data/lib/css/properties/list_style_property.rb +55 -0
- data/lib/css/properties/margin_property.rb +99 -0
- data/lib/css/properties/outline_property.rb +7 -0
- data/lib/css/properties/padding_property.rb +7 -0
- data/lib/css/property.rb +114 -0
- data/lib/css/rule.rb +78 -0
- data/lib/css/rule_set.rb +33 -0
- data/lib/numbers.rb +11 -0
- metadata +99 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2010 by Andrew Timberlake
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
=CSS
|
2
|
+
|
3
|
+
A Ruby gem that allows the parsing and creation of css files.
|
4
|
+
|
5
|
+
==Quick Start
|
6
|
+
|
7
|
+
style = "body { background: #FFF url('image.png') no-repeat; }"
|
8
|
+
css = CSS::Parser.new.parse(style)
|
9
|
+
puts css['body'].background.color.to_s #=> #FFF
|
10
|
+
css['body'].background.position = 'top center'
|
11
|
+
puts css['body'].to_s #=> 'background: #FFF url('image.png') top center no-repeat'
|
data/lib/css.rb
ADDED
data/lib/css/colors.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
module CSS
|
2
|
+
module Colors
|
3
|
+
NAMES = {
|
4
|
+
'ALICEBLUE' => '#F0F8FF',
|
5
|
+
'ANTIQUEWHITE' => '#FAEBD7',
|
6
|
+
'AQUA' => '#00FFFF',
|
7
|
+
'AQUAMARINE' => '#7FFFD4',
|
8
|
+
'AZURE' => '#F0FFFF',
|
9
|
+
'BEIGE' => '#F5F5DC',
|
10
|
+
'BISQUE' => '#FFE4C4',
|
11
|
+
'BLACK' => '#000000',
|
12
|
+
'BLANCHEDALMOND' => '#FFEBCD',
|
13
|
+
'BLUE' => '#0000FF',
|
14
|
+
'BLUEVIOLET' => '#8A2BE2',
|
15
|
+
'BROWN' => '#A52A2A',
|
16
|
+
'BURLYWOOD' => '#DEB887',
|
17
|
+
'CADETBLUE' => '#5F9EA0',
|
18
|
+
'CHARTREUSE' => '#7FFF00',
|
19
|
+
'CHOCOLATE' => '#D2691E',
|
20
|
+
'CORAL' => '#FF7F50',
|
21
|
+
'CORNFLOWERBLUE' => '#6495ED',
|
22
|
+
'CORNSILK' => '#FFF8DC',
|
23
|
+
'CRIMSON' => '#DC143C',
|
24
|
+
'CYAN' => '#00FFFF',
|
25
|
+
'DARKBLUE' => '#00008B',
|
26
|
+
'DARKCYAN' => '#008B8B',
|
27
|
+
'DARKGOLDENROD' => '#B8860B',
|
28
|
+
'DARKGRAY' => '#A9A9A9',
|
29
|
+
'DARKGREY' => '#A9A9A9',
|
30
|
+
'DARKGREEN' => '#006400',
|
31
|
+
'DARKKHAKI' => '#BDB76B',
|
32
|
+
'DARKMAGENTA' => '#8B008B',
|
33
|
+
'DARKOLIVEGREEN' => '#556B2F',
|
34
|
+
'DARKORANGE' => '#FF8C00',
|
35
|
+
'DARKORCHID' => '#9932CC',
|
36
|
+
'DARKRED' => '#8B0000',
|
37
|
+
'DARKSALMON' => '#E9967A',
|
38
|
+
'DARKSEAGREEN' => '#8FBC8F',
|
39
|
+
'DARKSLATEBLUE' => '#483D8B',
|
40
|
+
'DARKSLATEGRAY' => '#2F4F4F',
|
41
|
+
'DARKSLATEGREY' => '#2F4F4F',
|
42
|
+
'DARKTURQUOISE' => '#00CED1',
|
43
|
+
'DARKVIOLET' => '#9400D3',
|
44
|
+
'DEEPPINK' => '#FF1493',
|
45
|
+
'DEEPSKYBLUE' => '#00BFFF',
|
46
|
+
'DIMGRAY' => '#696969',
|
47
|
+
'DIMGREY' => '#696969',
|
48
|
+
'DODGERBLUE' => '#1E90FF',
|
49
|
+
'FIREBRICK' => '#B22222',
|
50
|
+
'FLORALWHITE' => '#FFFAF0',
|
51
|
+
'FORESTGREEN' => '#228B22',
|
52
|
+
'FUCHSIA' => '#FF00FF',
|
53
|
+
'GAINSBORO' => '#DCDCDC',
|
54
|
+
'GHOSTWHITE' => '#F8F8FF',
|
55
|
+
'GOLD' => '#FFD700',
|
56
|
+
'GOLDENROD' => '#DAA520',
|
57
|
+
'GRAY' => '#808080',
|
58
|
+
'GREY' => '#808080',
|
59
|
+
'GREEN' => '#008000',
|
60
|
+
'GREENYELLOW' => '#ADFF2F',
|
61
|
+
'HONEYDEW' => '#F0FFF0',
|
62
|
+
'HOTPINK' => '#FF69B4',
|
63
|
+
'INDIANRED' => '#CD5C5C',
|
64
|
+
'INDIGO' => '#4B0082',
|
65
|
+
'IVORY' => '#FFFFF0',
|
66
|
+
'KHAKI' => '#F0E68C',
|
67
|
+
'LAVENDER' => '#E6E6FA',
|
68
|
+
'LAVENDERBLUSH' => '#FFF0F5',
|
69
|
+
'LAWNGREEN' => '#7CFC00',
|
70
|
+
'LEMONCHIFFON' => '#FFFACD',
|
71
|
+
'LIGHTBLUE' => '#ADD8E6',
|
72
|
+
'LIGHTCORAL' => '#F08080',
|
73
|
+
'LIGHTCYAN' => '#E0FFFF',
|
74
|
+
'LIGHTGOLDENRODYELLOW' => '#FAFAD2',
|
75
|
+
'LIGHTGRAY' => '#D3D3D3',
|
76
|
+
'LIGHTGREY' => '#D3D3D3',
|
77
|
+
'LIGHTGREEN' => '#90EE90',
|
78
|
+
'LIGHTPINK' => '#FFB6C1',
|
79
|
+
'LIGHTSALMON' => '#FFA07A',
|
80
|
+
'LIGHTSEAGREEN' => '#20B2AA',
|
81
|
+
'LIGHTSKYBLUE' => '#87CEFA',
|
82
|
+
'LIGHTSLATEGRAY' => '#778899',
|
83
|
+
'LIGHTSLATEGREY' => '#778899',
|
84
|
+
'LIGHTSTEELBLUE' => '#B0C4DE',
|
85
|
+
'LIGHTYELLOW' => '#FFFFE0',
|
86
|
+
'LIME' => '#00FF00',
|
87
|
+
'LIMEGREEN' => '#32CD32',
|
88
|
+
'LINEN' => '#FAF0E6',
|
89
|
+
'MAGENTA' => '#FF00FF',
|
90
|
+
'MAROON' => '#800000',
|
91
|
+
'MEDIUMAQUAMARINE' => '#66CDAA',
|
92
|
+
'MEDIUMBLUE' => '#0000CD',
|
93
|
+
'MEDIUMORCHID' => '#BA55D3',
|
94
|
+
'MEDIUMPURPLE' => '#9370D8',
|
95
|
+
'MEDIUMSEAGREEN' => '#3CB371',
|
96
|
+
'MEDIUMSLATEBLUE' => '#7B68EE',
|
97
|
+
'MEDIUMSPRINGGREEN' => '#00FA9A',
|
98
|
+
'MEDIUMTURQUOISE' => '#48D1CC',
|
99
|
+
'MEDIUMVIOLETRED' => '#C71585',
|
100
|
+
'MIDNIGHTBLUE' => '#191970',
|
101
|
+
'MINTCREAM' => '#F5FFFA',
|
102
|
+
'MISTYROSE' => '#FFE4E1',
|
103
|
+
'MOCCASIN' => '#FFE4B5',
|
104
|
+
'NAVAJOWHITE' => '#FFDEAD',
|
105
|
+
'NAVY' => '#000080',
|
106
|
+
'OLDLACE' => '#FDF5E6',
|
107
|
+
'OLIVE' => '#808000',
|
108
|
+
'OLIVEDRAB' => '#6B8E23',
|
109
|
+
'ORANGE' => '#FFA500',
|
110
|
+
'ORANGERED' => '#FF4500',
|
111
|
+
'ORCHID' => '#DA70D6',
|
112
|
+
'PALEGOLDENROD' => '#EEE8AA',
|
113
|
+
'PALEGREEN' => '#98FB98',
|
114
|
+
'PALETURQUOISE' => '#AFEEEE',
|
115
|
+
'PALEVIOLETRED' => '#D87093',
|
116
|
+
'PAPAYAWHIP' => '#FFEFD5',
|
117
|
+
'PEACHPUFF' => '#FFDAB9',
|
118
|
+
'PERU' => '#CD853F',
|
119
|
+
'PINK' => '#FFC0CB',
|
120
|
+
'PLUM' => '#DDA0DD',
|
121
|
+
'POWDERBLUE' => '#B0E0E6',
|
122
|
+
'PURPLE' => '#800080',
|
123
|
+
'RED' => '#FF0000',
|
124
|
+
'ROSYBROWN' => '#BC8F8F',
|
125
|
+
'ROYALBLUE' => '#4169E1',
|
126
|
+
'SADDLEBROWN' => '#8B4513',
|
127
|
+
'SALMON' => '#FA8072',
|
128
|
+
'SANDYBROWN' => '#F4A460',
|
129
|
+
'SEAGREEN' => '#2E8B57',
|
130
|
+
'SEASHELL' => '#FFF5EE',
|
131
|
+
'SIENNA' => '#A0522D',
|
132
|
+
'SILVER' => '#C0C0C0',
|
133
|
+
'SKYBLUE' => '#87CEEB',
|
134
|
+
'SLATEBLUE' => '#6A5ACD',
|
135
|
+
'SLATEGRAY' => '#708090',
|
136
|
+
'SLATEGREY' => '#708090',
|
137
|
+
'SNOW' => '#FFFAFA',
|
138
|
+
'SPRINGGREEN' => '#00FF7F',
|
139
|
+
'STEELBLUE' => '#4682B4',
|
140
|
+
'TAN' => '#D2B48C',
|
141
|
+
'TEAL' => '#008080',
|
142
|
+
'THISTLE' => '#D8BFD8',
|
143
|
+
'TOMATO' => '#FF6347',
|
144
|
+
'TURQUOISE' => '#40E0D0',
|
145
|
+
'VIOLET' => '#EE82EE',
|
146
|
+
'WHEAT' => '#F5DEB3',
|
147
|
+
'WHITE' => '#FFFFFF',
|
148
|
+
'WHITESMOKE' => '#F5F5F5',
|
149
|
+
'YELLOW' => '#FFFF00',
|
150
|
+
'YELLOWGREEN' => '#9ACD32',
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
data/lib/css/errors.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module CSS
|
2
|
+
module Normalize
|
3
|
+
def normalize_property_name(name)
|
4
|
+
if name.to_s =~ /[A-Z]/
|
5
|
+
name.to_s.gsub(/([A-Z])/) do |match|
|
6
|
+
"-#{match.downcase}"
|
7
|
+
end
|
8
|
+
elsif name.to_s =~ /_/
|
9
|
+
name.to_s.gsub(/_/, '-')
|
10
|
+
else
|
11
|
+
name.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/css/parser.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module CSS
|
2
|
+
class Parser
|
3
|
+
# Parse a css style by string, file or file name
|
4
|
+
#
|
5
|
+
# ==Examples
|
6
|
+
# parser = CSS::Parser.new
|
7
|
+
# css = parser.parse("body { background: #FFF }")
|
8
|
+
#
|
9
|
+
# css = parser.parse("/home/andrew/style.css")
|
10
|
+
#
|
11
|
+
# File.open("style.css") do |file|
|
12
|
+
# css = parser.parse(file)
|
13
|
+
# end
|
14
|
+
def parse(file_file_name_or_style_string)
|
15
|
+
css_style = file_file_name_or_style_string
|
16
|
+
reset
|
17
|
+
lineno = 1
|
18
|
+
if css_style.respond_to?(:size) && css_style.size < 255 && File.exists?(css_style)
|
19
|
+
File.open(css_style) do |file|
|
20
|
+
file.each_line do |line|
|
21
|
+
parse_line(line, lineno)
|
22
|
+
lineno += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
elsif css_style.respond_to?(:each_line)
|
26
|
+
css_style.each_line do |line|
|
27
|
+
parse_line(line, lineno)
|
28
|
+
lineno += 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@ruleset
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def reset
|
37
|
+
@ruleset = RuleSet.new
|
38
|
+
|
39
|
+
@state = :selector
|
40
|
+
@previous_state = nil
|
41
|
+
@previous_char = nil
|
42
|
+
@buffer = []
|
43
|
+
@selector = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_line(line, lineno)
|
47
|
+
char_count = 1
|
48
|
+
line.each_char do |char|
|
49
|
+
@buffer << char unless @state == :comment
|
50
|
+
|
51
|
+
case char
|
52
|
+
when '{'
|
53
|
+
if @state == :selector
|
54
|
+
@buffer.pop
|
55
|
+
@selector = @buffer.join.strip
|
56
|
+
@buffer.clear
|
57
|
+
@state = :ruleset
|
58
|
+
else
|
59
|
+
unless @state == :comment
|
60
|
+
raise CSSError.new(lineno, char_count, "Unexpected '{'")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
when '}'
|
64
|
+
unless @state == :comment
|
65
|
+
@buffer.pop
|
66
|
+
@ruleset << Rule.new(@selector, @buffer.join.strip)
|
67
|
+
@state = :selector
|
68
|
+
@buffer.clear
|
69
|
+
end
|
70
|
+
when '/'
|
71
|
+
if @previous_char == '*'
|
72
|
+
@buffer.pop
|
73
|
+
@state = @previous_state
|
74
|
+
end
|
75
|
+
when '*'
|
76
|
+
if @previous_char == '/'
|
77
|
+
@buffer.pop
|
78
|
+
@previous_state = @state
|
79
|
+
@state = :comment
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@previous_char = char unless char == ' '
|
84
|
+
char_count += 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module CSS
|
2
|
+
class BackgroundProperty < Property
|
3
|
+
def name
|
4
|
+
'background'
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
%w(color image repeat position attachment).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_style
|
12
|
+
[name, to_s].join(':')
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def init(name, value)
|
17
|
+
if name =~ /-/
|
18
|
+
property_name = name.sub(/[^-]+-(.*)/, '\1')
|
19
|
+
@properties[property_name] = Property.new(:p, property_name, value)
|
20
|
+
else
|
21
|
+
expand_property value if value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_properties
|
26
|
+
@@default_properties ||= {
|
27
|
+
'color' => Property.create('color', 'transparent'),
|
28
|
+
'image' => Property.create('image', 'none'),
|
29
|
+
'repeat' => Property.create('repeat', 'repeat'),
|
30
|
+
'position' => Property.create('position', 'top left'),
|
31
|
+
'attachment' => Property.create('attachment', 'scroll')
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def expand_property(value)
|
36
|
+
values = value.delete(';').split(/\s+/)
|
37
|
+
while values.size > 0
|
38
|
+
val = values.shift
|
39
|
+
if val =~ /^(#|rgb)/ || val == 'transparent' || Colors::NAMES.keys.include?(val.to_s.upcase)
|
40
|
+
@properties['color'] = Property.create('color', val)
|
41
|
+
elsif val =~ /^url/
|
42
|
+
@properties['image'] = Property.create('image', val)
|
43
|
+
elsif val =~ /repeat/
|
44
|
+
@properties['repeat'] = Property.create('repeat', val)
|
45
|
+
elsif val =~ /^(\d|top|bottom|center)/
|
46
|
+
val2 = values.shift
|
47
|
+
@properties['position'] = Property.create('position', [val, val2].join(' '))
|
48
|
+
elsif val =~ /inherit/
|
49
|
+
if values.size == 0
|
50
|
+
@properties['attachment'] = Property.create('attachment', val)
|
51
|
+
else
|
52
|
+
@properties['repeat'] = Property.create('repeat', val)
|
53
|
+
end
|
54
|
+
elsif values.size == 0
|
55
|
+
@properties['attachment'] = Property.create('attachment', val)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module CSS
|
2
|
+
class BorderProperty < Property
|
3
|
+
def name
|
4
|
+
'border'
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
value = %w(size style color).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
|
9
|
+
if value == @properties['size'].value
|
10
|
+
"#{@properties['size']}"
|
11
|
+
else
|
12
|
+
value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_style
|
17
|
+
value = %w(size style color).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
|
18
|
+
if value == @properties['size'].value
|
19
|
+
"border-size:#{@properties['size']}"
|
20
|
+
else
|
21
|
+
[name, value].join(':')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def init(name, value)
|
27
|
+
if name =~ /-/
|
28
|
+
property_name = name.sub(/[^-]+-(.*)/, '\1')
|
29
|
+
@properties[property_name] = Property.new(:p, property_name, value)
|
30
|
+
else
|
31
|
+
expand_property value if value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_properties
|
36
|
+
@@default_properties ||= {
|
37
|
+
'size' => Property.create('size', '3px'),
|
38
|
+
'style' => nil,
|
39
|
+
'color' => Property.create('color', 'black')
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def expand_property(value)
|
44
|
+
values = value.delete(';').split(/\s+/)
|
45
|
+
|
46
|
+
val = values.pop
|
47
|
+
if val =~ /^(#|rgb)/ || Colors::NAMES.include?(val.upcase)
|
48
|
+
@properties["color"] = Property.create('color', val)
|
49
|
+
else
|
50
|
+
values << val
|
51
|
+
end
|
52
|
+
|
53
|
+
val = values.pop
|
54
|
+
if val =~ /^\d/
|
55
|
+
values << val
|
56
|
+
else
|
57
|
+
@properties["style"] = Property.create('style', val) if val
|
58
|
+
end
|
59
|
+
|
60
|
+
val = values.pop
|
61
|
+
@properties["size"] = Property.create('size', val) if val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module CSS
|
2
|
+
class FontProperty < Property
|
3
|
+
def name
|
4
|
+
'font'
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
if size && family
|
9
|
+
%w(style variant weight size family).map do |prop|
|
10
|
+
if @properties[prop] != default_properties[prop]
|
11
|
+
if prop == 'size' && get('size') != nil && @properties['line-height']
|
12
|
+
[@properties[prop], @properties['line-height']].join('/')
|
13
|
+
else
|
14
|
+
@properties[prop]
|
15
|
+
end
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end.compact.join(' ')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_style
|
24
|
+
if size && family
|
25
|
+
values = %w(style variant weight size family).map do |prop|
|
26
|
+
if @properties[prop] != default_properties[prop]
|
27
|
+
if prop == 'size' && get('size') != nil && @properties['line-height']
|
28
|
+
[@properties[prop], @properties['line-height']].join('/')
|
29
|
+
else
|
30
|
+
@properties[prop]
|
31
|
+
end
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end.compact.join(' ')
|
36
|
+
[name, values].join(':')
|
37
|
+
else
|
38
|
+
@properties.map { |prop, val| "#{prop == 'line-height' ? '' : 'font-'}#{prop}:#{val}" }.join(';')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def init(name, value)
|
44
|
+
if name == 'line-height'
|
45
|
+
@properties['line-height'] = Property.new(:p, 'line-height', value)
|
46
|
+
elsif name =~ /-/
|
47
|
+
property_name = name.sub(/[^-]+-(.*)/, '\1')
|
48
|
+
@properties[property_name] = Property.new(:p, property_name, value)
|
49
|
+
else
|
50
|
+
expand_property value if value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_properties
|
55
|
+
@@default_properties ||= {
|
56
|
+
'style' => Property.new(:p, 'style', 'normal'),
|
57
|
+
'variant' => Property.new(:p, 'variant', 'normal'),
|
58
|
+
'weight' => Property.new(:p, 'weight', 'normal'),
|
59
|
+
'size' => Property.new(:p, 'size', 'inherit'),
|
60
|
+
'family' => Property.new(:p, 'family', 'inherit'),
|
61
|
+
'line-height' => nil
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def expand_property(value)
|
66
|
+
font_families = value.delete(';').split(/,/)
|
67
|
+
values = font_families.shift.split(/\s+/)
|
68
|
+
|
69
|
+
font_families.unshift values.pop
|
70
|
+
if font_families[0] =~ /"\s*$/
|
71
|
+
font_families[0] = [values.pop, font_families[0]].join(' ')
|
72
|
+
end
|
73
|
+
@properties['family'] = Property.new(:p, 'family', font_families.join(','))
|
74
|
+
|
75
|
+
val = values.pop
|
76
|
+
font_size, line_height = val.split(/\//)
|
77
|
+
@properties['size'] = Property.new(:p, 'size', font_size)
|
78
|
+
@properties['line-height'] = Property.new(:p, 'line-height', line_height) if line_height
|
79
|
+
|
80
|
+
val = values.shift
|
81
|
+
if val =~ /(inherit|italic|oblique)/
|
82
|
+
@properties['style'] = Property.new(:p, 'style', val)
|
83
|
+
else
|
84
|
+
values << val
|
85
|
+
end
|
86
|
+
|
87
|
+
val = values.shift
|
88
|
+
if val =~ /(inherit|small-caps)/
|
89
|
+
@properties['variant'] = Property.new(:p, 'variant', val)
|
90
|
+
else
|
91
|
+
values << val
|
92
|
+
end
|
93
|
+
|
94
|
+
val = values.shift
|
95
|
+
@properties['weight'] = Property.new(:p, 'weight', val) if val
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CSS
|
2
|
+
class ListStyleProperty < Property
|
3
|
+
def initialize(*args)
|
4
|
+
@properties = default_properties.clone
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
'list-style'
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
%w(type position image).map { |prop| @properties[prop] }.join(' ')
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_style
|
17
|
+
[name, to_s].join(':')
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
default_properties['type'] == @properties['type'] ? nil : @properties['type']
|
22
|
+
end
|
23
|
+
|
24
|
+
def type=(val)
|
25
|
+
@properties['type'] = val
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def init(name, value)
|
30
|
+
expand_property value if value
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_properties
|
34
|
+
@@default_properties ||= {
|
35
|
+
'type' => Property.new(:p, 'type', 'disc'),
|
36
|
+
'position' => Property.new(:p, 'position', 'outside'),
|
37
|
+
'image' => Property.new(:p, 'image', 'none')
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def expand_property(value)
|
42
|
+
values = value.delete(';').split(/\s+/)
|
43
|
+
while values.size > 0
|
44
|
+
val = values.shift
|
45
|
+
if val =~ /^url/
|
46
|
+
@properties['image'] = Property.new(:p, 'image', val)
|
47
|
+
elsif val =~ /^(inside|outside)/
|
48
|
+
@properties['position'] = Property.new(:p, 'position', val)
|
49
|
+
else
|
50
|
+
@properties['type'] = Property.new(:p, 'type', val)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module CSS
|
2
|
+
class MarginProperty < Property
|
3
|
+
def name
|
4
|
+
'margin'
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
top = @properties['top']
|
9
|
+
right = @properties['right']
|
10
|
+
bottom = @properties['bottom']
|
11
|
+
left = @properties['left']
|
12
|
+
|
13
|
+
if top && right && bottom && left
|
14
|
+
if [top, right, bottom, left] == Array.new(4) { top }
|
15
|
+
top
|
16
|
+
elsif [top, bottom] == Array.new(2) { top } && [left, right] == Array.new(2) { left }
|
17
|
+
[top, left].join(' ')
|
18
|
+
elsif [top, bottom] != Array.new(2) { top } && [left, right] == Array.new(2) { left }
|
19
|
+
[top, left, bottom].join(' ')
|
20
|
+
else
|
21
|
+
[top, right, bottom, left].join(' ')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_style
|
27
|
+
top = @properties['top']
|
28
|
+
right = @properties['right']
|
29
|
+
bottom = @properties['bottom']
|
30
|
+
left = @properties['left']
|
31
|
+
|
32
|
+
if top && right && bottom && left
|
33
|
+
value = if [top, right, bottom, left] == Array.new(4) { top }
|
34
|
+
top
|
35
|
+
elsif [top, bottom] == Array.new(2) { top } && [left, right] == Array.new(2) { left }
|
36
|
+
[top, left].join(' ')
|
37
|
+
elsif [top, bottom] != Array.new(2) { top } && [left, right] == Array.new(2) { left }
|
38
|
+
[top, left, bottom].join(' ')
|
39
|
+
else
|
40
|
+
[top, right, bottom, left].join(' ')
|
41
|
+
end
|
42
|
+
[name, value].join(':')
|
43
|
+
else
|
44
|
+
default_properties.keys.map { |prop| @properties[prop] ? ["#{name}-#{prop}", @properties[prop]].join(':') : nil }.compact.join(';')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def init(name, value)
|
50
|
+
if name =~ /-/
|
51
|
+
property_name = name.sub(/[^-]+-(.*)/, '\1')
|
52
|
+
@properties[property_name] = Property.new(:p, property_name, value)
|
53
|
+
else
|
54
|
+
expand_property value if value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_properties
|
59
|
+
@@default_properties ||= {
|
60
|
+
'top' => nil,
|
61
|
+
'right' => nil,
|
62
|
+
'bottom' => nil,
|
63
|
+
'left' => nil
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def expand_property(value)
|
68
|
+
values = value.delete(';').split(/\s+/)
|
69
|
+
|
70
|
+
top, right, bottom, left = 0, 0, 0, 0
|
71
|
+
if values.size == 1
|
72
|
+
top = values[0]
|
73
|
+
right = values[0]
|
74
|
+
bottom = values[0]
|
75
|
+
left = values[0]
|
76
|
+
elsif values.size == 2
|
77
|
+
top = values[0]
|
78
|
+
bottom = values[0]
|
79
|
+
left = values[1]
|
80
|
+
right = values[1]
|
81
|
+
elsif values.size == 3
|
82
|
+
top = values[0]
|
83
|
+
bottom = values[2]
|
84
|
+
left = values[1]
|
85
|
+
right = values[1]
|
86
|
+
else
|
87
|
+
top = values[0]
|
88
|
+
right = values[1]
|
89
|
+
bottom = values[2]
|
90
|
+
left = values[3]
|
91
|
+
end
|
92
|
+
|
93
|
+
@properties['top'] = Property.new(:p, 'top', top)
|
94
|
+
@properties['right'] = Property.new(:p, 'right', right)
|
95
|
+
@properties['bottom'] = Property.new(:p, 'bottom', bottom)
|
96
|
+
@properties['left'] = Property.new(:p, 'left', left)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/css/property.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module CSS
|
2
|
+
class Property
|
3
|
+
include Normalize
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
raise "Please use Property.create instead of Property.new" unless args[0] == :p
|
9
|
+
@properties ||= {}
|
10
|
+
init(args[1], args[2])
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create(name, value = nil)
|
14
|
+
klass = case name
|
15
|
+
when /^background/
|
16
|
+
BackgroundProperty
|
17
|
+
when /^(font|line-height)/
|
18
|
+
FontProperty
|
19
|
+
when /^border/
|
20
|
+
BorderProperty
|
21
|
+
when /^outline/
|
22
|
+
OutlineProperty
|
23
|
+
when /^margin/
|
24
|
+
MarginProperty
|
25
|
+
when /^padding/
|
26
|
+
PaddingProperty
|
27
|
+
when /^list-style/
|
28
|
+
ListStyleProperty
|
29
|
+
else
|
30
|
+
Property
|
31
|
+
end
|
32
|
+
|
33
|
+
klass.new(:p, name, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@value
|
38
|
+
end
|
39
|
+
|
40
|
+
def value
|
41
|
+
to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_style
|
49
|
+
[@name, @value].join(':')
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(val)
|
53
|
+
if val.is_a?(Property)
|
54
|
+
@value == val.value
|
55
|
+
else
|
56
|
+
@value == val
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def <<(val)
|
61
|
+
@value = val
|
62
|
+
end
|
63
|
+
|
64
|
+
def get(property_name)
|
65
|
+
@properties[property_name] == default_properties[property_name] ? nil : @properties[property_name]
|
66
|
+
end
|
67
|
+
|
68
|
+
def [](property_name)
|
69
|
+
get property_name
|
70
|
+
end
|
71
|
+
|
72
|
+
def <<(merge_property)
|
73
|
+
default_properties.keys.each do |property_name|
|
74
|
+
@properties[property_name] = merge_property[property_name] unless merge_property[property_name] == default_properties[property_name]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def method_missing(method_name, *args, &block)
|
79
|
+
if method_name.to_s[-1..-1] == '='
|
80
|
+
property_name = normalize_property_name(method_name.to_s.chop)
|
81
|
+
if default_properties.keys.include?(property_name)
|
82
|
+
@properties[property_name] = Property.new(:p, property_name, args[0])
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
else
|
87
|
+
property_name = normalize_property_name(method_name.to_s)
|
88
|
+
if default_properties.keys.include?(property_name)
|
89
|
+
get(property_name)
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def init(name, value)
|
98
|
+
@name = name
|
99
|
+
@value = value
|
100
|
+
end
|
101
|
+
|
102
|
+
def default_properties
|
103
|
+
{}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
require "css/properties/background_property.rb"
|
109
|
+
require "css/properties/font_property.rb"
|
110
|
+
require "css/properties/border_property.rb"
|
111
|
+
require "css/properties/outline_property.rb"
|
112
|
+
require "css/properties/margin_property.rb"
|
113
|
+
require "css/properties/padding_property.rb"
|
114
|
+
require "css/properties/list_style_property.rb"
|
data/lib/css/rule.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Shorthand conversions based on guide by Dustin Diaz - http://www.dustindiaz.com/css-shorthand/
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module CSS
|
5
|
+
class Rule
|
6
|
+
include Colors
|
7
|
+
include Normalize
|
8
|
+
|
9
|
+
attr_reader :selector, :properties
|
10
|
+
|
11
|
+
def initialize(selector, rule_text)
|
12
|
+
@selector = selector
|
13
|
+
@properties = ::Set.new
|
14
|
+
@rules = {}
|
15
|
+
|
16
|
+
parse_rules(@properties, @rules, rule_text)
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(rule)
|
20
|
+
rule.properties.each do |property|
|
21
|
+
if @rules[property]
|
22
|
+
@rules[property] << rule[property]
|
23
|
+
else
|
24
|
+
@properties << property
|
25
|
+
@rules[property] = rule[property]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(property_name)
|
31
|
+
@rules[normalize_property_name(property_name)]
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](property_name)
|
35
|
+
get property_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
properties.map { |prop| get(prop).to_style }.join ';'
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_style
|
43
|
+
"#{selector}{#{to_s}}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method_name, *args)
|
47
|
+
property_name = normalize_property_name(method_name)
|
48
|
+
if property_name =~ /-/
|
49
|
+
property_name_parts = property_name.split('-')
|
50
|
+
pname = property_name_parts.shift
|
51
|
+
property = nil
|
52
|
+
while property_name_parts.size > 0
|
53
|
+
property = get(pname)
|
54
|
+
break unless property.nil?
|
55
|
+
pname = [pname, property_name_parts.shift].join('-')
|
56
|
+
end
|
57
|
+
property[property_name_parts.shift]
|
58
|
+
else
|
59
|
+
get(property_name) || super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def parse_rules(properties, rules, rule_text)
|
65
|
+
rule_text.split(/;/).inject([properties, rules]) do |properties, rule|
|
66
|
+
property = rule.split(/:/).map { |el| el.strip }
|
67
|
+
name = normalize_property_name(property[0])
|
68
|
+
value = property[1]
|
69
|
+
|
70
|
+
property = Property.create(name, value)
|
71
|
+
properties[0] << property.name
|
72
|
+
properties[1][property.name] = property
|
73
|
+
|
74
|
+
properties
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/css/rule_set.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module CSS
|
2
|
+
class RuleSet
|
3
|
+
def initialize
|
4
|
+
@selectors = []
|
5
|
+
@rules = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def <<(rule)
|
9
|
+
if @selectors.include?(rule.selector)
|
10
|
+
@rules[rule.selector] << rule
|
11
|
+
else
|
12
|
+
@selectors << rule.selector
|
13
|
+
@rules[rule.selector] = rule
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](selector)
|
18
|
+
@rules[selector]
|
19
|
+
end
|
20
|
+
|
21
|
+
def selectors
|
22
|
+
@selectors
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_style
|
26
|
+
rules = []
|
27
|
+
selectors.each do |selector|
|
28
|
+
rules << @rules[selector].to_style
|
29
|
+
end
|
30
|
+
rules.join("\n")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/numbers.rb
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: css
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Timberlake
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-01 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bundler
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: "1.0"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
description: Parse, create and work with CSS files.
|
37
|
+
email: andrew@andrewtimberlake.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- LICENSE
|
44
|
+
- README.rdoc
|
45
|
+
files:
|
46
|
+
- LICENSE
|
47
|
+
- lib/css/colors.rb
|
48
|
+
- lib/css/errors.rb
|
49
|
+
- lib/css/normalize.rb
|
50
|
+
- lib/css/parser.rb
|
51
|
+
- lib/css/properties/background_property.rb
|
52
|
+
- lib/css/properties/border_property.rb
|
53
|
+
- lib/css/properties/font_property.rb
|
54
|
+
- lib/css/properties/list_style_property.rb
|
55
|
+
- lib/css/properties/margin_property.rb
|
56
|
+
- lib/css/properties/outline_property.rb
|
57
|
+
- lib/css/properties/padding_property.rb
|
58
|
+
- lib/css/property.rb
|
59
|
+
- lib/css/rule.rb
|
60
|
+
- lib/css/rule_set.rb
|
61
|
+
- lib/css.rb
|
62
|
+
- lib/numbers.rb
|
63
|
+
- README.rdoc
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/andrewtimberlake/css
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --charset=UTF-8
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.3.7
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Parse, create and work with CSS files.
|
98
|
+
test_files: []
|
99
|
+
|