parslet-css 0.0.1

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,19 @@
1
+ # encoding: UTF-8
2
+ require 'parslet'
3
+
4
+ class ParsletCSS
5
+ VERSION = "0.0.1"
6
+
7
+ autoload :Parser, 'parslet-css/parser'
8
+
9
+ def self.compile(str)
10
+ parser = ParsletCSS::Parser.new
11
+ parser.parse(str)
12
+ rescue Parslet::ParseFailed => error
13
+ puts parser.root.error_tree
14
+ end
15
+ end
16
+
17
+ if __FILE__ == $0
18
+ ParsletCSS.compile(ARGF.read)
19
+ end
@@ -0,0 +1,153 @@
1
+ # encoding: UTF-8
2
+ class ParsletCSS::Parser < Parslet::Parser
3
+ rule(:selectors) {
4
+ ignore >> selector_chars >>
5
+ ((space? >> comma >> space? | space) >> selector_chars).repeat
6
+ }
7
+ rule(:selector_chars) {
8
+ match('[a-zA-Z0-9\-\._~\/*"$^:=#>+|\[\]]').repeat(1) >>
9
+ (str('(') >> match['a-zA-Z0-9\-+:*\[\]'].repeat(1) >> str(')')).maybe
10
+ }
11
+
12
+ rule(:lcurly) { ignore >> str('{') >> ignore }
13
+ rule(:declarations) { (ignore >> declaration >> semicolon? >> ignore).repeat }
14
+ rule(:declaration) {
15
+ name >> ignore >> str(':') >> ignore >> property_values
16
+ }
17
+
18
+ rule(:property_values) {
19
+ property_value >> (semicolon.absent? >> space >> property_value).repeat(0, 5)
20
+ }
21
+ # TODO: be more precise for margin, padding... property values
22
+ rule(:property_value) {
23
+ font_height | property_value_keywords | uri | percent | color | size |
24
+ float | font_family_list
25
+ }
26
+ rule(:property_value_keywords) {
27
+ str('no-repeat') | str('scroll') | str('inherit') |
28
+ str('baseline') | str('block') | str('both') | str('bold') |
29
+ str('inline-block') | str('!important') | str('inline')
30
+ }
31
+
32
+ # URL
33
+ # http://labs.apache.org/webarch/uri/rev-2002/rfc2396bis.html#collected-abnf
34
+ rule(:uri) { str('url(') >> quote? >> url.as(:url) >> quote? >> str(')') }
35
+ rule(:url) { protocol? >> domain? >> path }
36
+ rule(:protocol) {
37
+ (str('https') | str('http') | str('ftp') | str('gopher') | str('mailto') |
38
+ str('news') | str('nntp') | str('telnet') | str('wais') | str('file') |
39
+ str('prospero')) >> str('://')
40
+ }
41
+ rule(:protocol?) { protocol.maybe }
42
+ rule(:domain) { match('[a-zA-Z0-9\-\._\/]').repeat >> (str(':') >> integer).maybe }
43
+ rule(:domain?) { domain.maybe }
44
+ rule(:path) { path_chars >> query? }
45
+ rule(:path_chars) { (str('/').maybe >> match('[a-zA-Z0-9\-\._~]')).repeat }
46
+ rule(:query) {
47
+ str('?') >> (query_key_value >> (query_sep >> query_key_value).repeat).maybe
48
+ }
49
+ rule(:query_sep) { str('&') | semicolon }
50
+ rule(:query?) { query.maybe }
51
+ rule(:query_key_value) { name >> str('=') >> name }
52
+
53
+ rule(:percent) { sign? >> integer >> str('%') }
54
+ rule(:size) { sign? >> (float | integer) >> length_unit }
55
+ rule(:length_unit) {
56
+ str('em') | str('ex') | str('px') | str('in') |
57
+ str('cm') | str('mm') | str('pt') | str('pc')
58
+ }
59
+
60
+ # http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-family-prop
61
+ rule(:font_family_list) {
62
+ font_family >> ((comma >> space? >> font_family).repeat).maybe
63
+ }
64
+ # Font family names containing whitespace [link to syntax module] should be quoted.
65
+ rule(:font_family) {
66
+ quoted | name
67
+ }
68
+ rule(:font_height) {
69
+ size >> str('/') >> size
70
+ }
71
+
72
+ # COLORS
73
+ rule(:color) { color_keywords | hex_value | rgb }
74
+ rule(:color_keywords) {
75
+ str('aqua') | str('black') | str('blue') | str('fuchsia') | str('gray') |
76
+ str('green') | str('lime') | str('maroon') | str('navy') | str('olive') |
77
+ str('purple') | str('red') | str('silver') | str('teal') | str('white') |
78
+ str('yellow') | str('transparent')
79
+ }
80
+ rule(:hex_value) {
81
+ str('#') >> (digit_hex.repeat(6,6) | digit_hex.repeat(3,3))
82
+ }
83
+ rule(:digit_hex) { match('[0-9a-fA-F]') }
84
+ rule(:rgb) {
85
+ (str('rgb(') | str('rgba(') | str('hsl(')) >>
86
+ space? >> rgb_value >> space? >> comma >>
87
+ space? >> rgb_value >> space? >> comma >>
88
+ (space? >> rgb_value >> space? >> comma).maybe >>
89
+ space? >> rgb_value >> space? >> str(')')
90
+ }
91
+ rule(:rgb_value) { percent | float | integer }
92
+
93
+ # TODO: http://www.w3.org/TR/css3-2d-transforms/
94
+
95
+ rule(:rcurly) { ignore >> str('}') >> ignore }
96
+
97
+ rule(:space) { match('\s').repeat(1) }
98
+ rule(:space?) { space.maybe }
99
+ rule(:quote) { str('"') | str("'") }
100
+ rule(:quote?) { quote.maybe }
101
+ rule(:name) { match['_a-zA-Z0-9-'].repeat(1) }
102
+ rule(:quoted) { quote >> (quote.absent? >> any).repeat >> quote }
103
+ rule(:integer) { match('[0-9]').repeat }
104
+ rule(:float) { integer >> str('.') >> integer }
105
+ rule(:semicolon) { str(';') }
106
+ rule(:semicolon?) { semicolon.maybe }
107
+ rule(:comma) { str(',') }
108
+ rule(:sign) { str('+') | str('-') }
109
+ rule(:sign?) { sign.maybe }
110
+ rule(:comment) { space? >> (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')) >> space? }
111
+ rule(:ignore) { comment | space? }
112
+
113
+ # @charset
114
+ # http://www.w3.org/TR/CSS21/syndata.html#charset
115
+ # http://www.iana.org/assignments/character-sets
116
+ @@charsets_file = File.join(File.dirname(__FILE__), '..', '..', 'data', 'iana_character_sets.txt')
117
+ @@charsets = open(@@charsets_file).read.split
118
+ def charsets
119
+ Parslet::Atoms::Alternative.new(*@@charsets.map {|c| str(c)})
120
+ end
121
+ rule(:charset) {
122
+ str('@charset') >> space >> quote >>
123
+ charsets >>
124
+ quote >> semicolon >> ignore
125
+ }
126
+
127
+ # @import
128
+ # http://www.w3.org/TR/CSS2/cascade.html#at-import
129
+ rule(:import) {
130
+ str('@import') >> space >> (quote >> name >> str('.css') >> quote | uri) >>
131
+ media_type_list.maybe >> semicolon >> ignore
132
+ }
133
+
134
+ # @media
135
+ # http://www.w3.org/TR/CSS2/media.html#media-intro
136
+ rule(:media) {
137
+ ignore >> str('@media') >> media_type_list >> space? >>
138
+ lcurly >> ruleset.repeat >> rcurly
139
+ }
140
+ rule(:media_type_list) {
141
+ space >> media_types >> ((comma >> space? >> media_types).repeat).maybe
142
+ }
143
+ rule(:media_types) {
144
+ str('all') | str('braille') | str('embossed') | str('handheld') |
145
+ str('print') | str('projection') | str('screen') | str('speech') |
146
+ str('tty') | str('tv')
147
+ }
148
+
149
+ rule(:ruleset) { selectors >> lcurly >> declarations >> rcurly }
150
+ # TODO @namespace at-rule
151
+ rule(:stylesheet) { charset.maybe >> import.repeat.maybe >> (media | ruleset).repeat }
152
+ root :stylesheet
153
+ end
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+ require 'minitest/autorun'
3
+ require 'parslet-css'
4
+
5
+ describe ParsletCSS::Parser do
6
+ before do
7
+ @parser = ParsletCSS::Parser.new
8
+ end
9
+
10
+ describe "import parse" do
11
+ it "can import other css" do
12
+ @parser.parse('@import "mystyle.css";')
13
+ @parser.parse('@import url("mystyle.css");')
14
+ @parser.parse('@import url("bluish.css") projection, tv;')
15
+ @parser.parse('@import url("fineprint.css") print; @import url("bluish.css") projection, tv; body {}')
16
+ end
17
+ end
18
+
19
+ describe "charset" do
20
+ it '@charset rule must place the rule at the very beginning of the style sheet' do
21
+ @parser.parse('@charset "ISO-8859-1";')
22
+ @parser.parse('@charset "UTF-8"; @import "mystyle.css"; body {}')
23
+ end
24
+ end
25
+
26
+ describe '@media rule' do
27
+ it 'valid statement' do
28
+ @parser.parse('@media print { body { font-size: 10pt } }')
29
+ @parser.parse('@media screen, print { body { font-size: 13px } }')
30
+ @parser.parse('/* print */ @media print { body { font-size: 10pt } }
31
+ /* screen */ @media screen { body { font-size: 13px } }')
32
+ end
33
+ end
34
+
35
+ describe "parse success" do
36
+ it "parse with percent" do
37
+ @parser.parse("body { height: 100%; width: 100%; }")
38
+ end
39
+ it "with url" do
40
+ @parser.parse("body { background: url(/images/plop.png); }")
41
+ @parser.parse("body { background: url(https://localhost:3000/images/plop.png); }")
42
+ @parser.parse("body { background: url(/~spk/images/plop.png?size=30;toto=tata); }")
43
+ @parser.parse("body { background: url(/~spk/images/plop.png?size=30&toto=tata); }")
44
+ u = @parser.parse("body { background: url(/up.png); } nav { background: url(/nav.png) }")
45
+ assert_equal(2, u.size)
46
+ end
47
+ it "with comments" do
48
+ @parser.parse("body { /* comment */ padding: 0; /* comment */}")
49
+ end
50
+ it "with no space" do
51
+ @parser.parse(":focus{outline:0;/*comment*/}")
52
+ @parser.parse(":focus{outline:0}")
53
+ end
54
+ it "with no last semicolon" do
55
+ @parser.parse("body { height: 100%; width: 100% }")
56
+ end
57
+ it "with one property and no semicolon" do
58
+ @parser.parse("body { width: 100% }")
59
+ end
60
+ it "with blank property" do
61
+ @parser.parse("body { }")
62
+ end
63
+ it 'with color' do
64
+ @parser.parse("body { color: #1a171b; }")
65
+ @parser.parse("body { color: #fff; }")
66
+ end
67
+ it 'with font family' do
68
+ @parser.parse("body { font: 12px monospace, serif; }")
69
+ @parser.parse("body { font: bold 14px/28px Arial; }")
70
+ end
71
+ it 'with multiple property values' do
72
+ @parser.parse("body { margin: 10px 10px 10px 10em;}")
73
+ @parser.parse("body { background: url(/images/ui/down.png) no-repeat scroll 140px 0px transparent; }")
74
+ @parser.parse("body { background: url(/images/ui/up.png) no-repeat scroll 140px 0px #1a171b; }")
75
+ end
76
+ it "float size" do
77
+ @parser.parse(".date_selector .nav { width: 17.5em; }")
78
+ end
79
+ it 'property priority' do
80
+ @parser.parse(".date_selector .nav { width: 17.5em !important; }")
81
+ end
82
+
83
+ describe('CSS selectors') do
84
+ # http://www.w3.org/TR/CSS2/selector.html
85
+ it "CSS2 w3c examples" do
86
+ # 5.2.1 Grouping
87
+ @parser.parse("h1, h2, h3 { font-family: sans-serif }")
88
+ # 5.6 Child selectors
89
+ @parser.parse("body > P { line-height: 1.3 }")
90
+ @parser.parse("div ol>li p { font: 2px }")
91
+ # 5.7 Adjacent sibling selectors
92
+ @parser.parse("math + p { text-indent: 0 } ")
93
+ @parser.parse("h1 + h2 { margin-top: -5mm }")
94
+ @parser.parse("h1.opener + h2 { margin-top: -5mm }")
95
+ # 5.8.1 Matching attributes and attribute values
96
+ @parser.parse("h1[title] { color: blue; }")
97
+ @parser.parse("span[class=example] { color: blue; }")
98
+ @parser.parse('span[hello="Cleveland"][goodbye="Columbus"] { color: blue; }')
99
+ @parser.parse('a[rel~="copyright"] {}')
100
+ @parser.parse('a[href="http://www.w3.org/"] {}')
101
+ @parser.parse("*[lang=fr] { display : none }")
102
+ @parser.parse('*[lang|="en"] { color : red }')
103
+ @parser.parse('DIALOGUE[character=juliet] { voice-family: "Vivien Leigh", victoria, female }')
104
+ # 5.8.3 Class selectors
105
+ @parser.parse("*.pastoral { color: green }")
106
+ @parser.parse("H1.pastoral { color: green }")
107
+ @parser.parse('p.marine.pastoral { color: green }')
108
+ # 5.9 ID selectors
109
+ @parser.parse("h1#chapter1 { text-align: center }")
110
+ # 5.11 Pseudo-classes
111
+ @parser.parse('div > p:first-child { text-indent: 0 }')
112
+ @parser.parse('p:first-child em { font-weight : bold }')
113
+ @parser.parse('* > a:first-child {} /* A is first child of any element */')
114
+ @parser.parse('a:first-child {} /* Same */')
115
+ @parser.parse(':link { color: red }')
116
+ @parser.parse('a.external:visited { color: blue }')
117
+ @parser.parse('a:focus:hover { background: white }')
118
+ # 5.11.4 The language pseudo-class: :lang
119
+ @parser.parse("html:lang(fr-ca) { quotes: '« ' ' »' }")
120
+ @parser.parse(":lang(fr) > Q { quotes: '« ' ' »' }")
121
+ # 5.12.1 The :first-line pseudo-element
122
+ @parser.parse("p:first-line { text-transform: uppercase }")
123
+ @parser.parse('p:first-letter { font-size: 3em; font-weight: normal }')
124
+ # 5.12.3 The :before and :after pseudo-elements
125
+ @parser.parse('p.special:before {content: "Special! "}')
126
+ @parser.parse('p.special:first-letter {color: #ffd800}')
127
+ end
128
+
129
+ # http://www.w3.org/TR/css3-selectors/
130
+ it "CSS3 w3c examples" do
131
+ @parser.parse('object[type^="image/"] {}')
132
+ @parser.parse('a[href$=".html"] {}')
133
+ @parser.parse('tr:nth-child(2n+1) {} /* represents every odd row of an HTML table */')
134
+ @parser.parse('p:nth-child(4n+1) { color: navy; }')
135
+ @parser.parse('button:not([DISABLED]) {}')
136
+ @parser.parse('p::first-line { text-transform: uppercase }')
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "parse fail" do
142
+ # http://www.w3.org/TR/CSS21/syndata.html#parsing-errors
143
+ @raises = [
144
+ {:msg => "with extra semicolon", :css => "body { height: 100%; ; width: 100%; }"},
145
+ {:msg => "with extra curly", :css => "body { height: 100%; width: 100%; }}"},
146
+ {:msg => "& is not valid token", :css => "h3, h4 & h5 {color: red }"},
147
+ {:msg => "1 malformed declaration missing ':', value", :css => "p { color:green; color }"},
148
+ {:msg => "2 malformed declaration missing value", :css => "p { color:green; color: }"},
149
+ {:msg => "unexpected tokens { }", :css => "p { color:green; color{;color:maroon} }"},
150
+ {:msg => "ruleset with unexpected at-keyword @here", :css => "p @here {color: red}"},
151
+ {:msg => "at-rule with unexpected at-keyword @bar", :css => "@foo @bar;"},
152
+ {:msg => "ruleset with unexpected right brace", :css => "}} {{ - }}"},
153
+ {:msg => "ruleset with unexpected right parenthesis", :css => ") ( {} ) p {color: red }"},
154
+
155
+ # http://www.w3.org/TR/CSS2/syndata.html#at-rules
156
+ {:msg => "must ignore any '@import' rule that occurs inside a block",
157
+ :css => '@import "subs.css"; h1 { color: blue } @import "list.css";'},
158
+ {:msg => 'non valid charset', :css => '@charset "none";'},
159
+ {:msg => '@charset rule not at the beginning of the style sheet',
160
+ :css => '@import "awesome.css"; @charset "UTF-8";'},
161
+
162
+ {:msg => 'identifier must not be empty. (Otherwise, the selector is invalid.)',
163
+ :css => 'html:lang() {}'},
164
+ ]
165
+ @raises.each do |r|
166
+ class_eval do
167
+ it r[:msg] do
168
+ assert_raises Parslet::ParseFailed do
169
+ @parser.parse(r[:css])
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ end
176
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parslet-css
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Laurent Arnoud
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-06-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: parslet
16
+ requirement: &14494580 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *14494580
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &14493660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *14493660
36
+ description: CSS parser with Parslet
37
+ email: laurent@spkdev.net
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files:
41
+ - README.markdown
42
+ files:
43
+ - LICENSE
44
+ - README.markdown
45
+ - Gemfile
46
+ - data/iana_character_sets.txt
47
+ - data/character-sets
48
+ - lib/parslet-css/parser.rb
49
+ - lib/parslet-css.rb
50
+ - test/parser_test.rb
51
+ homepage: http://github.com/spk/parslet-css
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.7.2
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: CSS parser with Parslet grammar tool
75
+ test_files:
76
+ - test/parser_test.rb