css 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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'
@@ -0,0 +1,12 @@
1
+ require "numbers"
2
+ require "css/colors"
3
+ require "css/errors"
4
+ require "css/normalize"
5
+ require "css/parser"
6
+ require "css/property"
7
+ require "css/rule"
8
+ require "css/rule_set"
9
+
10
+ module CSS
11
+ VERSION = "0.0.1"
12
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ module CSS
2
+ class CSSError < StandardError
3
+ attr_reader :line_number, :char
4
+
5
+ def initialize(line_number, char, error_message)
6
+ @line_number = line_number
7
+ @char = char
8
+ super(error_message)
9
+ end
10
+ end
11
+ end
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ module CSS
2
+ class OutlineProperty < BorderProperty
3
+ def name
4
+ 'outline'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module CSS
2
+ class PaddingProperty < MarginProperty
3
+ def name
4
+ 'padding'
5
+ end
6
+ end
7
+ end
@@ -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"
@@ -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
@@ -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
@@ -0,0 +1,11 @@
1
+ class Numeric
2
+ %w(em ex px gd rem vw vh vm ch).each do |unit|
3
+ define_method(unit) do
4
+ "#{self}#{unit}"
5
+ end
6
+ end
7
+
8
+ def percent
9
+ "#{self}%"
10
+ end
11
+ end
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
+