simple_style_sheet 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +2 -0
- data/LICENSE +20 -0
- data/README.md +144 -0
- data/lib/simple_style_sheet/handler.rb +52 -0
- data/lib/simple_style_sheet/selector.rb +63 -0
- data/lib/simple_style_sheet/selector_segment.rb +38 -0
- data/lib/simple_style_sheet/selector_specificity.rb +32 -0
- data/lib/simple_style_sheet/version.rb +3 -0
- data/lib/simple_style_sheet.rb +9 -0
- metadata +67 -0
data/Changelog
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Jacek Mikrut
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
SimpleStyleSheet
|
2
|
+
================
|
3
|
+
|
4
|
+
SimpleStyleSheet is a Ruby gem that **parses a CSS-like Hash style sheet** and then **allows searching for property values of given HTML-like tags**. Tag and property names, as well as their meaning, are up to the gem user.
|
5
|
+
|
6
|
+
Usage and details
|
7
|
+
-----------------
|
8
|
+
|
9
|
+
### 1. Style sheet preparation
|
10
|
+
|
11
|
+
The style sheet is just a CSS-like Hash object. Its keys are either property names or selectors, and values are either property values or nested Hash objects.
|
12
|
+
|
13
|
+
##### Property names
|
14
|
+
|
15
|
+
Property names can be objects of any class and their meaning is up to the user. Most often, they would be String or Symbol instances with content that is some formatting-related property name, like "background-color".
|
16
|
+
|
17
|
+
##### Property values
|
18
|
+
|
19
|
+
The values can also be any objects, except for Hash objects, which are interpreted as nested style sheet definitions.
|
20
|
+
|
21
|
+
##### Selectors
|
22
|
+
|
23
|
+
A selector is a String object that contains one or more custom **tag names**, **ids** and **class names**. For example: `#content message.success`.
|
24
|
+
|
25
|
+
##### A style sheet example
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
style_sheet = {
|
29
|
+
"foreground-color" => :white,
|
30
|
+
"background-color" => :blue,
|
31
|
+
|
32
|
+
"#content message.success" => {
|
33
|
+
"background-color" => :green,
|
34
|
+
|
35
|
+
"number" => {
|
36
|
+
"background-color" => :inherit
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
### 2. Creating a style sheet handler instance
|
43
|
+
|
44
|
+
Then the style sheet should be passed to the handler:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
style_sheet_handler = SimpleStyleSheet::Handler.new(style_sheet)
|
48
|
+
```
|
49
|
+
|
50
|
+
### 3. Tag objects
|
51
|
+
|
52
|
+
Tag objects represent tags in a HTML-like structure. As a tag, any object can be used, provided that it responds to :name, :id, :class_names and :parent messages. For example:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Tag = Struct.new(:name, :id, :class_names, :parent)
|
56
|
+
|
57
|
+
# Tag objects corresponding to the structure:
|
58
|
+
# <section id="content"><message class="success">...</message></section>
|
59
|
+
content_tag = Tag.new("section", "content", [], nil)
|
60
|
+
message_tag = Tag.new("message", nil, ["success"], content_tag)
|
61
|
+
```
|
62
|
+
|
63
|
+
### 4. Getting property value for a given tag
|
64
|
+
|
65
|
+
Now, a property value for a given tag can be obtained:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
style_sheet_handler.value_for(message_tag, "background-color")
|
69
|
+
=> :green
|
70
|
+
|
71
|
+
style_sheet_handler.value_for(message_tag, "foreground-color")
|
72
|
+
=> :white
|
73
|
+
```
|
74
|
+
|
75
|
+
The #value_for method looks in the style sheet for selectors that match a given tag. If more than one matching selector is found, the one with highest specificity is used (more details below). Then the value it points to for given property name is returned.
|
76
|
+
|
77
|
+
### 5. Getting top-level property value
|
78
|
+
|
79
|
+
The #value_for method, when called without the tag argument, returns top-level property value (defined in the style sheet without a selector), which may be interpreted as default one or as a value for text not included in any tags. For example:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
style_sheet_handler.value_for("background-color")
|
83
|
+
=> :blue
|
84
|
+
```
|
85
|
+
|
86
|
+
Selector specificity
|
87
|
+
--------------------
|
88
|
+
|
89
|
+
In order to return a property value for a given tag, search for the matching selector of highest specificity is performed.
|
90
|
+
|
91
|
+
The specificity of each selector is calculated according to rules similar to the standard CSS rules.
|
92
|
+
|
93
|
+
The specificity is four numbers: **a,b,c,d**.
|
94
|
+
|
95
|
+
**The rules respected by this gem are**:
|
96
|
+
|
97
|
+
* **a** is always 0;
|
98
|
+
* **b** is the number of ID attributes in the selector;
|
99
|
+
* **c** is the number of class names in the selector;
|
100
|
+
* **d** is the number of tag names in the selector.
|
101
|
+
|
102
|
+
For example, a selector `tag#id1.class1 #id2.class2.class3` has specificity equal to `0,2,3,1`.
|
103
|
+
|
104
|
+
Two selector specificities are compared by succesively comparing their corresponding numbers, from left to right.
|
105
|
+
|
106
|
+
Property name translator
|
107
|
+
------------------------
|
108
|
+
|
109
|
+
A property name translator allows using multiple names (or aliases) for a single property name. For example, property named "background-color" can have aliases "background", "bg-color" etc.
|
110
|
+
|
111
|
+
The translator, that is supposed to be provided by the gem user, should respond to :translate method and return the translation for a given property name, for example:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
property_name_translator.translate("bg-color")
|
115
|
+
=> "background-color"
|
116
|
+
```
|
117
|
+
|
118
|
+
In order to use the translator, it should be passed as the second argument to SimpleStyleSheet::Handler.new method:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
style_sheet_handler = SimpleStyleSheet::Handler.new(style_sheet, property_name_translator)
|
122
|
+
```
|
123
|
+
|
124
|
+
Installation
|
125
|
+
------------
|
126
|
+
|
127
|
+
As a Ruby gem, SimpleStyleSheet can be installed either by running
|
128
|
+
|
129
|
+
```bash
|
130
|
+
gem install simple_style_sheet
|
131
|
+
```
|
132
|
+
|
133
|
+
or adding
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
gem "simple_style_sheet"
|
137
|
+
```
|
138
|
+
|
139
|
+
to the Gemfile and then invoking `bundle install`.
|
140
|
+
|
141
|
+
License
|
142
|
+
-------
|
143
|
+
|
144
|
+
License is included in the LICENSE file.
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SimpleStyleSheet
|
2
|
+
class Handler
|
3
|
+
|
4
|
+
def initialize(style_sheet_hash, property_name_translator=nil)
|
5
|
+
@map = {}
|
6
|
+
@property_name_translator = property_name_translator
|
7
|
+
populate(style_sheet_hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
def value_for(tag=nil, property_name)
|
11
|
+
found = if tag
|
12
|
+
(@map[final_property_name(property_name)] || [])
|
13
|
+
.select { |data| data[:selector].match?(tag) }
|
14
|
+
.max_by { |data| data[:selector].specificity }
|
15
|
+
|
16
|
+
else
|
17
|
+
(@map[final_property_name(property_name)] || [])
|
18
|
+
.select { |data| data[:selector].empty? }
|
19
|
+
.last
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
found && found[:value]
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def populate(style_sheet_hash, selector=new_selector)
|
29
|
+
|
30
|
+
style_sheet_hash.each do |key, value|
|
31
|
+
|
32
|
+
case value
|
33
|
+
when Hash
|
34
|
+
populate(value, selector + key)
|
35
|
+
else
|
36
|
+
property_name = final_property_name(key)
|
37
|
+
|
38
|
+
@map[property_name] ||= []
|
39
|
+
@map[property_name] << { :value => value, :selector => selector }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_selector
|
45
|
+
Selector.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def final_property_name(property_name)
|
49
|
+
@property_name_translator ? @property_name_translator.translate(property_name) : property_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module SimpleStyleSheet
|
2
|
+
class Selector
|
3
|
+
|
4
|
+
def initialize(string=nil)
|
5
|
+
@segments = []
|
6
|
+
@specificity = SelectorSpecificity.new
|
7
|
+
concat(string) unless string.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :specificity
|
11
|
+
|
12
|
+
def concat(string)
|
13
|
+
string.scan(/[\w.#]+/).map { |s| SelectorSegment.new(s) }.each do |segment|
|
14
|
+
@segments << segment
|
15
|
+
@specificity += segment.specificity
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(string)
|
21
|
+
duplicate.concat(string)
|
22
|
+
end
|
23
|
+
|
24
|
+
def match?(tag)
|
25
|
+
return true if @segments.none?
|
26
|
+
return false unless @segments.last.match?(tag)
|
27
|
+
|
28
|
+
index = @segments.size - 2
|
29
|
+
current_tag = tag
|
30
|
+
|
31
|
+
while index >= 0 && current_tag = current_tag.parent
|
32
|
+
if @segments[index].match?(current_tag)
|
33
|
+
index -= 1
|
34
|
+
next
|
35
|
+
end
|
36
|
+
end
|
37
|
+
index == -1
|
38
|
+
end
|
39
|
+
|
40
|
+
def empty?
|
41
|
+
@segments.none?
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
@segments.map { |segment| segment.to_s }.join(" ")
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"#<#{self.class} #{to_s.inspect}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
to_s == other.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def duplicate
|
57
|
+
d = dup
|
58
|
+
d.instance_variable_set( "@segments", @segments.dup)
|
59
|
+
d.instance_variable_set("@specificity", @specificity.dup)
|
60
|
+
d
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SimpleStyleSheet
|
2
|
+
class SelectorSegment
|
3
|
+
|
4
|
+
def initialize(string)
|
5
|
+
@tag_name = string.lstrip.slice(/^\w+/)
|
6
|
+
@id = string.slice(/(?<=\#)\w+/)
|
7
|
+
@class_names = string.scan(/(?<=\.)\w+/)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :tag_name, :id, :class_names
|
11
|
+
|
12
|
+
def specificity
|
13
|
+
@specificity ||= SelectorSpecificity.new(
|
14
|
+
0,
|
15
|
+
id ? 1 : 0,
|
16
|
+
class_names.count,
|
17
|
+
tag_name ? 1 : 0
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def match?(tag)
|
22
|
+
return false if tag_name && tag_name != tag.name
|
23
|
+
return false if id && id != tag.id
|
24
|
+
return false if (class_names - tag.class_names).any?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{tag_name}" +
|
30
|
+
(id ? "##{id}" : "") +
|
31
|
+
class_names.map { |class_name| ".#{class_name}" }.join
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"#<#{self.class} #{to_s.inspect}>"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SimpleStyleSheet
|
2
|
+
class SelectorSpecificity
|
3
|
+
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
def initialize(a=0, b=0, c=0, d=0)
|
7
|
+
@a, @b, @c, @d = a, b, c, d
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :a, :b, :c, :d
|
11
|
+
|
12
|
+
def <=>(other)
|
13
|
+
[:a, :b, :c, :d].each do |name|
|
14
|
+
result = send(name) <=> other.send(name)
|
15
|
+
return result unless result == 0
|
16
|
+
end
|
17
|
+
0
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(other)
|
21
|
+
self.class.new(a + other.a, b + other.b, c + other.c, d + other.d)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{a},#{b},#{c},#{d}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"#<#{self.class} #{to_s.inspect}>"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_style_sheet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jacek Mikrut
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-21 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &74598880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *74598880
|
25
|
+
description: Parses a CSS-like Hash style sheet and allows searching for property
|
26
|
+
values of HTML-like tags. Tag and property names, as well as their meaning, are
|
27
|
+
up to the gem user.
|
28
|
+
email: jacekmikrut.software@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/simple_style_sheet.rb
|
34
|
+
- lib/simple_style_sheet/handler.rb
|
35
|
+
- lib/simple_style_sheet/version.rb
|
36
|
+
- lib/simple_style_sheet/selector.rb
|
37
|
+
- lib/simple_style_sheet/selector_segment.rb
|
38
|
+
- lib/simple_style_sheet/selector_specificity.rb
|
39
|
+
- README.md
|
40
|
+
- LICENSE
|
41
|
+
- Changelog
|
42
|
+
homepage: http://github.com/jacekmikrut/simple_style_sheet
|
43
|
+
licenses: []
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.12
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Parses a CSS-like Hash style sheet and allows searching for property values
|
66
|
+
of HTML-like tags.
|
67
|
+
test_files: []
|