csspool 4.0.0.pre → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +12 -0
  3. data/Gemfile.lock +6 -6
  4. data/Manifest.txt +10 -2
  5. data/Rakefile +1 -0
  6. data/lib/csspool/css/declaration.rb +1 -1
  7. data/lib/csspool/css/document.rb +5 -1
  8. data/lib/csspool/css/document_handler.rb +43 -11
  9. data/lib/csspool/css/fontface_rule.rb +13 -0
  10. data/lib/csspool/css/import_rule.rb +0 -5
  11. data/lib/csspool/css/media_feature.rb +14 -0
  12. data/lib/csspool/css/media_query.rb +19 -0
  13. data/lib/csspool/css/media_query_list.rb +41 -0
  14. data/lib/csspool/css/{media.rb → media_type.rb} +6 -4
  15. data/lib/csspool/css/parser.rb +1272 -671
  16. data/lib/csspool/css/parser.y +223 -53
  17. data/lib/csspool/css/rule_set.rb +4 -3
  18. data/lib/csspool/css/supports_rule.rb +14 -0
  19. data/lib/csspool/css/tokenizer.rb +92 -16
  20. data/lib/csspool/css/tokenizer.rex +42 -19
  21. data/lib/csspool/css.rb +6 -1
  22. data/lib/csspool/node.rb +22 -0
  23. data/lib/csspool/selector.rb +5 -4
  24. data/lib/csspool/selectors/pseudo.rb +2 -2
  25. data/lib/csspool/terms/function.rb +1 -1
  26. data/lib/csspool/terms/resolution.rb +13 -0
  27. data/lib/csspool/terms.rb +1 -0
  28. data/lib/csspool/visitors/children.rb +16 -2
  29. data/lib/csspool/visitors/comparable.rb +27 -8
  30. data/lib/csspool/visitors/iterator.rb +5 -3
  31. data/lib/csspool/visitors/to_css.rb +73 -20
  32. data/test/css/test_document_query.rb +37 -38
  33. data/test/css/test_font_face.rb +16 -0
  34. data/test/css/test_import_rule.rb +0 -3
  35. data/test/css/test_media_rule.rb +92 -0
  36. data/test/css/test_node_position.rb +81 -0
  37. data/test/css/test_parser.rb +12 -39
  38. data/test/css/test_supports_rule.rb +133 -0
  39. data/test/css/test_tokenizer.rb +4 -4
  40. data/test/css/test_variables.rb +33 -0
  41. data/test/helper.rb +3 -3
  42. data/test/sac/test_parser.rb +1 -0
  43. data/test/test_parser.rb +22 -9
  44. data/test/test_selector.rb +44 -4
  45. data/test/test_term.rb +29 -0
  46. data/test/visitors/test_comparable.rb +8 -0
  47. data/test/visitors/test_to_css.rb +89 -13
  48. metadata +54 -58
  49. data/test/_local_helper.rb +0 -2
@@ -7,10 +7,8 @@ macro
7
7
  w [\s]*
8
8
  nonascii [^\0-\177]
9
9
  num ([0-9]*\.[0-9]+|[0-9]+)
10
- length {num}(px|cm|mm|in|pt|pc)
10
+ length {num}(px|cm|mm|in|pt|pc|mozmm|em|ex|ch|rem|vh|vw|vmin|vmax)
11
11
  percentage {num}%
12
- ems {num}em
13
- exs {num}ex
14
12
  unicode \\[0-9A-Fa-f]{1,6}(\r\n|[\s])?
15
13
  nth ([\+\-]?[0-9]*n({w}[\+\-]{w}[0-9]+)?|[\+\-]?[0-9]+|odd|even)
16
14
  vendorprefix \-[A-Za-z]+\-
@@ -18,8 +16,7 @@ macro
18
16
  escape {unicode}|\\[^\n\r\f0-9A-Fa-f]
19
17
  nmchar [_A-Za-z0-9-]|{nonascii}|{escape}
20
18
  nmstart [_A-Za-z]|{nonascii}|{escape}
21
- ident [-@]?({nmstart})({nmchar})*
22
- func [-@]?({nmstart})({nmchar}|[.])*
19
+ ident \-?({nmstart})({nmchar})*
23
20
  name ({nmchar})+
24
21
  string1 "([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*"
25
22
  string2 '([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*'
@@ -28,24 +25,43 @@ macro
28
25
  invalid2 '([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*
29
26
  invalid ({invalid1}|{invalid2})
30
27
  comment \/\*(.|{w})*?\*\/
31
-
32
- unit ({num}|{length}|{percentage}|{ems}|{exs})
33
- product {unit}({w}(\*{w}{unit}|\/{w}{num}))*
34
- sum {product}([\s]+[\+\-][\s]+{product})*
35
- calc ({vendorprefix})?calc\({w}{sum}{w}\)
36
- math {calc}{w}
28
+ variablename \-\-{name}
37
29
 
38
30
  rule
39
31
 
40
32
  # [:state] pattern [actions]
41
33
 
34
+ # "Logical queries", such as those used by @supports and @media, include keywords that match our IDENT pattern.
35
+ # Including these as lexical words makes their use anywhere else (even inside other words!) not possible.
36
+ # So we define a new state, and list the things that can occur.
37
+ :LOGICQUERY url\({w}{string}{w}\) { [:URI, st(text)] }
38
+ :LOGICQUERY url\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:URI, st(text)] }
39
+ :LOGICQUERY {ident}\(\s* { [:FUNCTION, st(text)] }
40
+ :LOGICQUERY {w}(and|only|not|or)[\s]+ { [text.upcase.strip.intern, st(text)] }
41
+ :LOGICQUERY {w}{num}(dpi|dpcm) { [:RESOLUTION, st(text)]}
42
+ :LOGICQUERY {w}{length}{w} { [:LENGTH, st(text)] }
43
+ :LOGICQUERY {w}{num}(deg|rad|grad){w} { [:ANGLE, st(text)] }
44
+ :LOGICQUERY {w}{num}(ms|s){w} { [:TIME, st(text)] }
45
+ :LOGICQUERY {w}{num}[k]?hz{w} { [:FREQ, st(text)] }
46
+ :LOGICQUERY {w}{percentage}{w} { [:PERCENTAGE, st(text)] }
47
+ :LOGICQUERY {w}{num}{w} { [:NUMBER, st(text)] }
48
+ :LOGICQUERY {ident} { [:IDENT, st(text)] }
49
+ :LOGICQUERY {w},{w} { [:COMMA, st(',')] }
50
+ :LOGICQUERY {w}\) { [:RPAREN, st(text)] }
51
+ :LOGICQUERY {w}\( { [:LPAREN, st(text)] }
52
+ :LOGICQUERY \: { [:COLON, st(text)] }
53
+ :LOGICQUERY {w}!({w}|{w}{comment}{w})important{w} { [:IMPORTANT_SYM, st(text)] }
54
+ # this marks the end of our logical query
55
+ :LOGICQUERY {w}\{{w} { @state = nil; [:LBRACE, st(text)] }
56
+ :LOGICQUERY [\s]+ { [:S, st(text)] }
57
+
42
58
  url\({w}{string}{w}\) { [:URI, st(text)] }
43
59
  url\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:URI, st(text)] }
44
60
  U\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})? {[:UNICODE_RANGE, st(text)] }
45
61
  {w}{comment}{w} { next_token }
46
62
 
47
63
  # this one takes a selector as a parameter
48
- not\(\s* { [:NOT_PSEUDO_CLASS, st(text)] }
64
+ not\({w} { [:NOT_PSEUDO_CLASS, st(text)] }
49
65
 
50
66
  # this one takes an "nth" value
51
67
  (nth\-child|nth\-last\-child|nth\-of\-type|nth\-last\-of\-type)\({w}{nth}{w}\) { [:NTH_PSEUDO_CLASS, st(text)] }
@@ -57,17 +73,24 @@ rule
57
73
  (domain|url\-prefix)\({w}{string}{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
58
74
  (domain|url\-prefix)\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
59
75
 
60
- {w}{math}{w} { [:MATH, st(text)] }
76
+ # mozilla-specific pseudo-elements that can be used with one or two colons and can have multiple parameters
77
+ (\-moz\-non\-element|\-moz\-anonymous\-block|\-moz\-anonymous\-positioned\-block|\-moz\-mathml\-anonymous\-block|\-moz\-xul\-anonymous\-block|\-moz\-hframeset\-border|\-moz\-vframeset\-border|\-moz\-line\-frame|\-moz\-button\-content|\-moz\-buttonlabel|\-moz\-cell\-content|\-moz\-dropdown\-list|\-moz\-fieldset\-content|\-moz\-frameset\-blank|\-moz\-display\-comboboxcontrol\-frame|\-moz\-html\-canvas\-content|\-moz\-inline\-table|\-moz\-table|\-moz\-table\-cell|\-moz\-table\-column\-group|\-moz\-table\-column|\-moz\-table\-outer|\-moz\-table\-row\-group|\-moz\-table\-row|\-moz\-canvas|\-moz\-pagebreak|\-moz\-page|\-moz\-pagecontent|\-moz\-page\-sequence|\-moz\-scrolled\-content|\-moz\-scrolled\-canvas|\-moz\-scrolled\-page\-sequence|\-moz\-column\-content|\-moz\-viewport|\-moz\-viewport\-scroll|\-moz\-anonymous\-flex\-item|\-moz\-tree\-column|\-moz\-tree\-row|\-moz\-tree\-separator|\-moz\-tree\-cell|\-moz\-tree\-indentation|\-moz\-tree\-line|\-moz\-tree\-twisty|\-moz\-tree\-image|\-moz\-tree\-cell\-text|\-moz\-tree\-checkbox|\-moz\-tree\-progressmeter|\-moz\-tree\-drop\-feedback|\-moz\-svg\-outer\-svg\-anon\-child|\-moz\-svg\-foreign\-content|\-moz\-svg\-text)\( { [:MOZ_PSEUDO_ELEMENT, st(text)] }
78
+
79
+ {w}@media{w} { @state = :LOGICQUERY; [:MEDIA_SYM, st(text)] }
80
+ {w}@supports{w} { @state = :LOGICQUERY; [:SUPPORTS_SYM, st(text)] }
81
+
82
+ calc\(\s* { [:CALC_SYM, st(text)] }
83
+ {ident}\(\s* { [:FUNCTION, st(text)] }
61
84
 
62
- {func}\(\s* { [:FUNCTION, st(text)] }
63
85
  {w}@import{w} { [:IMPORT_SYM, st(text)] }
64
86
  {w}@page{w} { [:PAGE_SYM, st(text)] }
65
87
  {w}@charset{w} { [:CHARSET_SYM, st(text)] }
66
- {w}@media{w} { [:MEDIA_SYM, st(text)] }
67
88
  {w}@({vendorprefix})?document{w} { [:DOCUMENT_QUERY_SYM, st(text)] }
68
89
  {w}@namespace{w} { [:NAMESPACE_SYM, st(text)] }
90
+ {w}@font\-face{w} { [:FONTFACE_SYM, st(text)] }
69
91
  {w}@({vendorprefix})?keyframes{w} { [:KEYFRAMES_SYM, st(text)] }
70
92
  {w}!({w}|{w}{comment}{w})important{w} { [:IMPORTANT_SYM, st(text)] }
93
+ {variablename} { [:VARIABLE_NAME, st(text)] }
71
94
  {ident} { [:IDENT, st(text)] }
72
95
  \#{name} { [:HASH, st(text)] }
73
96
  {w}~={w} { [:INCLUDES, st(text)] }
@@ -78,6 +101,7 @@ rule
78
101
  {w}!={w} { [:NOT_EQUAL, st(text)] }
79
102
  {w}={w} { [:EQUAL, st(text)] }
80
103
  {w}\) { [:RPAREN, st(text)] }
104
+ {w}\( { [:LPAREN, st(text)] }
81
105
  \[{w} { [:LSQUARE, st(text)] }
82
106
  {w}\] { [:RSQUARE, st(text)] }
83
107
  {w}\+{w} { [:PLUS, st(text)] }
@@ -86,10 +110,9 @@ rule
86
110
  {w}>{w} { [:GREATER, st(text)] }
87
111
  {w},{w} { [:COMMA, st(',')] }
88
112
  {w};{w} { [:SEMI, st(';')] }
113
+ \: { [:COLON, st(text)] }
89
114
  \* { [:STAR, st(text)] }
90
115
  {w}~{w} { [:TILDE, st(text)] }
91
- {w}{ems}{w} { [:EMS, st(text)] }
92
- {w}{exs}{w} { [:EXS, st(text)] }
93
116
 
94
117
  {w}{length}{w} { [:LENGTH, st(text)] }
95
118
  {w}{num}(deg|rad|grad){w} { [:ANGLE, st(text)] }
@@ -104,8 +127,8 @@ rule
104
127
  --> { [:CDC, st(text)] }
105
128
  {w}\-(?!{ident}){w} { [:MINUS, st(text)] }
106
129
  {w}\+{w} { [:PLUS, st(text)] }
107
-
108
-
130
+
131
+
109
132
  [\s]+ { [:S, st(text)] }
110
133
  {string} { [:STRING, st(text)] }
111
134
  {invalid} { [:INVALID, st(text)] }
data/lib/csspool/css.rb CHANGED
@@ -1,12 +1,17 @@
1
1
  require 'csspool/css/tokenizer'
2
2
  require 'csspool/css/charset'
3
+ require 'csspool/css/fontface_rule'
3
4
  require 'csspool/css/import_rule'
4
- require 'csspool/css/media'
5
+ require 'csspool/css/media_query'
6
+ require 'csspool/css/media_query_list'
7
+ require 'csspool/css/media_feature'
8
+ require 'csspool/css/media_type'
5
9
  require 'csspool/css/rule_set'
6
10
  require 'csspool/css/declaration'
7
11
  require 'csspool/css/document'
8
12
  require 'csspool/css/document_handler'
9
13
  require 'csspool/css/document_query'
14
+ require 'csspool/css/supports_rule'
10
15
  require 'csspool/css/namespace_rule'
11
16
  require 'csspool/css/keyframes_rule'
12
17
  require 'csspool/css/keyframes_block'
data/lib/csspool/node.rb CHANGED
@@ -1,6 +1,28 @@
1
1
  module CSSPool
2
2
  class Node
3
3
  include Enumerable
4
+
5
+ # These give the position of this node in the source CSS. Not all
6
+ # node types are supported. "Outer" represents the start/end of
7
+ # this node, "inner" represents the start/end of this node's
8
+ # children. Outer will not contain leading or trailing comments or
9
+ # whitespace, while inner will. For example, in this code:
10
+ #
11
+ # ---
12
+ # 1 /* test1 */
13
+ # 2 @document domain('example.com') {
14
+ # 3 /* test2 */
15
+ # 4 * { color: blue; }
16
+ # 5 /* test3 */
17
+ # 6 } /* test4 */
18
+ # ---
19
+ #
20
+ # The index will represent the position:
21
+ # outer_start_pos: 2:0
22
+ # inner_start_pos: 2:33
23
+ # inner_end_pos: 6:0
24
+ # outer_end_pos: 6:1
25
+ attr_accessor :outer_start_pos, :inner_start_pos, :inner_end_pos, :outer_end_pos
4
26
 
5
27
  def accept target
6
28
  target.accept self
@@ -17,15 +17,16 @@ module CSSPool
17
17
  def specificity
18
18
  a = b = c = 0
19
19
  simple_selectors.each do |s|
20
- if !s.name.nil?
20
+ if s.is_a?(Selectors::Type) && s.name && s.name != ''
21
21
  c += 1
22
22
  end
23
23
  s.additional_selectors.each do |additional_selector|
24
- if Selectors::Id === additional_selector
24
+ case additional_selector
25
+ when Selectors::Id
25
26
  a += 1
26
- elsif Selectors::PseudoElement === additional_selector
27
+ when Selectors::PseudoElement
27
28
  c += 1
28
- else
29
+ when Selectors::Class, Selectors::PseudoClass, Selectors::Attribute
29
30
  b += 1
30
31
  end
31
32
  end
@@ -3,10 +3,10 @@ require 'csspool/selectors/pseudo_element'
3
3
 
4
4
  module CSSPool
5
5
  module Selectors
6
- def Selectors.pseudo name
6
+ def self.pseudo name
7
7
  # FIXME: This is a bit of an ugly solution. Should be able to handle it
8
8
  # more elegantly, and without calling out css2
9
- css2_pseudo_elements =
9
+ css2_pseudo_elements =
10
10
  if %w{after before first-letter first-line}.include? name
11
11
  PseudoElement.new name, true
12
12
  else
@@ -6,7 +6,7 @@ module CSSPool
6
6
  attr_accessor :parse_location
7
7
  attr_accessor :operator
8
8
 
9
- def initialize name, params, operator = nil, parse_location = nil
9
+ def initialize name, params = [], operator = nil, parse_location = nil
10
10
  @name = name
11
11
  @params = params
12
12
  @operator = operator
@@ -0,0 +1,13 @@
1
+ module CSSPool
2
+ module Terms
3
+ class Resolution < CSSPool::Node
4
+ attr_accessor :number, :unit, :parse_location
5
+
6
+ def initialize(number, unit, parse_location = {})
7
+ @number = number
8
+ @unit = unit
9
+ @parse_location = parse_location
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/csspool/terms.rb CHANGED
@@ -6,3 +6,4 @@ require 'csspool/terms/uri'
6
6
  require 'csspool/terms/rgb'
7
7
  require 'csspool/terms/hash'
8
8
  require 'csspool/terms/math'
9
+ require 'csspool/terms/resolution'
@@ -13,7 +13,7 @@ module CSSPool
13
13
  target.media_list
14
14
  end
15
15
 
16
- visitor_for CSS::Media,
16
+ visitor_for CSS::MediaType,
17
17
  CSS::Charset,
18
18
  Selectors::Id,
19
19
  Selectors::Class,
@@ -25,7 +25,8 @@ module CSSPool
25
25
  Terms::Number,
26
26
  Terms::Hash,
27
27
  Terms::Function,
28
- Terms::Rgb do |target|
28
+ Terms::Rgb,
29
+ Terms::Resolution do |target|
29
30
  []
30
31
  end
31
32
 
@@ -37,6 +38,10 @@ module CSSPool
37
38
  target.selectors + target.declarations
38
39
  end
39
40
 
41
+ visitor_for CSS::FontfaceRule do |target|
42
+ target.declarations
43
+ end
44
+
40
45
  visitor_for Selector do |target|
41
46
  target.simple_selectors
42
47
  end
@@ -44,6 +49,15 @@ module CSSPool
44
49
  visitor_for Selectors::Type, Selectors::Universal, Selectors::Simple do |target|
45
50
  target.additional_selectors
46
51
  end
52
+
53
+ visitor_for CSS::MediaQuery do |target|
54
+ [target.media_expr].concat(target.and_exprs).compact
55
+ end
56
+
57
+ visitor_for CSS::MediaQueryList do |target|
58
+ target.media_queries
59
+ end
60
+
47
61
  end
48
62
  end
49
63
  end
@@ -17,7 +17,7 @@ module CSSPool
17
17
  end
18
18
 
19
19
  visitor_for CSS::RuleSet do |target|
20
- [:selectors, :declarations, :media].all? { |m|
20
+ [:selectors, :declarations, :parent_rule].all? { |m|
21
21
  target.send(m) == @other.send(m)
22
22
  }
23
23
  end
@@ -38,13 +38,7 @@ module CSSPool
38
38
  }
39
39
  end
40
40
 
41
- visitor_for CSS::Media do |target|
42
- [:media_list].all? { |m|
43
- target.send(m) == @other.send(m)
44
- }
45
- end
46
-
47
- visitor_for Selectors::Id, Selectors::Class do |target|
41
+ visitor_for CSS::MediaType, Selectors::Id, Selectors::Class do |target|
48
42
  target.name == @other.name
49
43
  end
50
44
 
@@ -61,6 +55,24 @@ module CSSPool
61
55
  }
62
56
  end
63
57
 
58
+ visitor_for CSS::MediaFeature do |target|
59
+ [:property, :value].all? { |m|
60
+ target.send(m) == @other.send(m)
61
+ }
62
+ end
63
+
64
+ visitor_for CSS::MediaQuery do |target|
65
+ [:only_or_not, :media_expr, :and_exprs].all? { |m|
66
+ target.send(m) == @other.send(m)
67
+ }
68
+ end
69
+
70
+ visitor_for CSS::MediaQueryList do |target|
71
+ [:media_queries].all? { |m|
72
+ target.send(m) == @other.send(m)
73
+ }
74
+ end
75
+
64
76
  visitor_for Terms::Function do |target|
65
77
  [:name, :params, :operator].all? { |m|
66
78
  target.send(m) == @other.send(m)
@@ -85,6 +97,13 @@ module CSSPool
85
97
  :operator
86
98
  ].all? { |m| target.send(m) == @other.send(m) }
87
99
  end
100
+
101
+ visitor_for Terms::Resolution do |target|
102
+ [
103
+ :number,
104
+ :unit
105
+ ].all? { |m| target.send(m) == @other.send(m) }
106
+ end
88
107
  end
89
108
  end
90
109
  end
@@ -14,7 +14,7 @@ module CSSPool
14
14
  @block.call target
15
15
  end
16
16
 
17
- visitor_for Selectors::Universal, Selectors::Simple do |target|
17
+ visitor_for Selectors::Universal, Selectors::Simple, CSS::MediaQuery, CSS::MediaQueryList do |target|
18
18
  target.children.each do |node|
19
19
  node.accept self
20
20
  end
@@ -32,7 +32,7 @@ module CSSPool
32
32
  @block.call target
33
33
  end
34
34
 
35
- visitor_for CSS::Media,
35
+ visitor_for CSS::MediaType,
36
36
  Selectors::Id,
37
37
  Selectors::Class,
38
38
  Selectors::PseudoClass,
@@ -43,7 +43,9 @@ module CSSPool
43
43
  Terms::Number,
44
44
  Terms::Hash,
45
45
  Terms::Function,
46
- Terms::Rgb do |target|
46
+ Terms::Rgb,
47
+ Terms::Resolution,
48
+ CSS::MediaFeature do |target|
47
49
  @block.call target
48
50
  end
49
51
 
@@ -18,8 +18,13 @@ module CSSPool
18
18
  end
19
19
 
20
20
  visitor_for CSS::Document do |target|
21
- # Default media list is []
22
- current_media_type = []
21
+ # FIXME - this does not handle nested parent rules, like
22
+ # @document domain(example.com) {
23
+ # @media screen {
24
+ # a { color: blue; }
25
+ # }
26
+ # }
27
+ current_parent_rule = []
23
28
 
24
29
  tokens = []
25
30
 
@@ -31,34 +36,72 @@ module CSSPool
31
36
  tokens << ir.accept(self)
32
37
  end
33
38
 
39
+ target.fontface_rules.each do |ffr|
40
+ tokens << ffr.accept(self)
41
+ end
42
+
34
43
  target.rule_sets.each { |rs|
35
- if rs.media != current_media_type
36
- media = " " + rs.media.media_list.map do |medium|
37
- escape_css_identifier medium.value
38
- end.join(', ')
39
- tokens << "#{indent}@media#{media} {"
44
+ # FIXME - handle other kinds of parents
45
+ if !rs.parent_rule.nil? and rs.parent_rule != current_parent_rule
46
+ media = rs.parent_rule
47
+ tokens << "#{indent}@media #{media} {"
40
48
  @indent_level += 1
41
49
  end
42
50
 
43
51
  tokens << rs.accept(self)
44
52
 
45
- if rs.media != current_media_type
46
- current_media_type = rs.media
47
- @indent_level -= 1
48
- tokens << "#{indent}}"
53
+ if rs.parent_rule != current_parent_rule
54
+ current_parent_rule = rs.parent_rule
55
+ if !rs.parent_rule.nil?
56
+ @indent_level -= 1
57
+ tokens << "#{indent}}"
58
+ end
49
59
  end
50
60
  }
51
61
  tokens.join(line_break)
52
62
  end
53
63
 
64
+ visitor_for CSS::MediaType do |target|
65
+ escape_css_identifier(target.name)
66
+ end
67
+
68
+ visitor_for CSS::MediaFeature do |target|
69
+ "(#{escape_css_identifier(target.property)}:#{target.value})"
70
+ end
71
+
72
+ visitor_for CSS::MediaQuery do |target|
73
+ ret = ''
74
+ if target.only_or_not
75
+ ret << target.only_or_not.to_s + ' '
76
+ end
77
+ ret << target.media_expr.accept(self)
78
+ if target.and_exprs.any?
79
+ ret << ' and '
80
+ end
81
+ ret << target.and_exprs.map { |expr| expr.accept(self) }.join(' and ')
82
+ end
83
+
84
+ visitor_for CSS::MediaQueryList do |target|
85
+ target.media_queries.map do |m|
86
+ m.accept(self)
87
+ end.join(', ')
88
+ end
89
+
54
90
  visitor_for CSS::Charset do |target|
55
91
  "@charset \"#{escape_css_string target.name}\";"
56
92
  end
57
93
 
94
+ visitor_for CSS::FontfaceRule do |target|
95
+ "@font-face {#{line_break}" +
96
+ "#{indent}" +
97
+ target.declarations.map { |decl| decl.accept self }.join(line_break) +
98
+ "#{line_break}#{indent}}"
99
+ end
100
+
58
101
  visitor_for CSS::ImportRule do |target|
59
102
  media = ''
60
103
  media = " " + target.media_list.map do |medium|
61
- escape_css_identifier medium.value
104
+ escape_css_identifier medium.name
62
105
  end.join(', ') if target.media_list.length > 0
63
106
 
64
107
  "#{indent}@import #{target.uri.accept(self)}#{media};"
@@ -77,17 +120,22 @@ module CSSPool
77
120
  end
78
121
 
79
122
  visitor_for CSS::RuleSet do |target|
80
- "#{indent}" +
81
- target.selectors.map { |sel| sel.accept self }.join(", ") + " {#{line_break}" +
82
- target.declarations.map { |decl| decl.accept self }.join(line_break) +
83
- "#{line_break}#{indent}}"
123
+ if target.selectors.any?
124
+ "#{indent}" +
125
+ target.selectors.map { |sel| sel.accept self }.join(", ") + " {#{line_break}" +
126
+ target.declarations.map { |decl| decl.accept self }.join(line_break) +
127
+ "#{line_break}#{indent}}"
128
+ else
129
+ ''
130
+ end
84
131
  end
85
132
 
86
133
  visitor_for CSS::Declaration do |target|
87
134
  important = target.important? ? ' !important' : ''
88
135
 
136
+ # only output indents and semicolons if this is in a ruleset
89
137
  indent {
90
- "#{indent}#{escape_css_identifier target.property}: " + target.expressions.map { |exp|
138
+ "#{target.rule_set.nil? ? '' : indent}#{escape_css_identifier target.property}: " + target.expressions.map { |exp|
91
139
 
92
140
  op = '/' == exp.operator ? ' /' : exp.operator
93
141
 
@@ -95,7 +143,7 @@ module CSSPool
95
143
  op,
96
144
  exp.accept(self),
97
145
  ].join ' '
98
- }.join.strip + "#{important};"
146
+ }.join.strip + important + (target.rule_set.nil? ? '' : ';')
99
147
  }
100
148
  end
101
149
 
@@ -114,7 +162,7 @@ module CSSPool
114
162
  visitor_for Terms::Function do |target|
115
163
  "#{escape_css_identifier target.name}(" +
116
164
  target.params.map { |x|
117
- [
165
+ x.is_a?(String) ? x : [
118
166
  x.operator,
119
167
  x.accept(self)
120
168
  ].compact.join(' ')
@@ -145,6 +193,10 @@ module CSSPool
145
193
  ].compact.join
146
194
  end
147
195
 
196
+ visitor_for Terms::Resolution do |target|
197
+ "#{target.number}#{target.unit}"
198
+ end
199
+
148
200
  visitor_for Selector do |target|
149
201
  target.simple_selectors.map { |ss| ss.accept self }.join
150
202
  end
@@ -153,7 +205,8 @@ module CSSPool
153
205
  combo = {
154
206
  :s => ' ',
155
207
  :+ => ' + ',
156
- :> => ' > '
208
+ :> => ' > ',
209
+ :~ => ' ~ '
157
210
  }[target.combinator]
158
211
 
159
212
  name = [nil, '*'].include?(target.name) ? target.name : escape_css_identifier(target.name)
@@ -6,71 +6,70 @@ module CSSPool
6
6
 
7
7
  def test_function
8
8
  doc = CSSPool.CSS <<-eocss
9
- @document domain("example.com") {
10
- * { color: blue; }
11
- }
9
+ @document domain("example.com") {
10
+ * { color: blue; }
11
+ }
12
12
  eocss
13
13
  assert_equal 1, doc.document_queries.size
14
- assert_equal 1, doc.document_queries[0].url_functions.size
15
- assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
16
- assert_equal 'domain', doc.document_queries[0].url_functions[0].name
17
- assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
18
- assert_equal 1, doc.document_queries[0].rule_sets.size
14
+ assert_equal 1, doc.document_queries[0].url_functions.size
15
+ assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
16
+ assert_equal 'domain', doc.document_queries[0].url_functions[0].name
17
+ assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
18
+ assert_equal 1, doc.document_queries[0].rule_sets.size
19
19
  end
20
20
 
21
21
  def test_function_no_quote
22
22
  doc = CSSPool.CSS <<-eocss
23
- @document domain(example.com) {
24
- * { color: blue; }
25
- }
23
+ @document domain(example.com) {
24
+ * { color: blue; }
25
+ }
26
26
  eocss
27
27
  assert_equal 1, doc.document_queries.size
28
- assert_equal 1, doc.document_queries[0].url_functions.size
29
- assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
30
- assert_equal 'domain', doc.document_queries[0].url_functions[0].name
31
- assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
32
- assert_equal 1, doc.document_queries[0].rule_sets.size
28
+ assert_equal 1, doc.document_queries[0].url_functions.size
29
+ assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
30
+ assert_equal 'domain', doc.document_queries[0].url_functions[0].name
31
+ assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
32
+ assert_equal 1, doc.document_queries[0].rule_sets.size
33
33
  end
34
34
 
35
35
  def test_multiple
36
36
  doc = CSSPool.CSS <<-eocss
37
- @document domain(example.com), url-prefix(http://example.com) {
38
- * { color: blue; }
39
- }
37
+ @document domain(example.com), url-prefix(http://example.com) {
38
+ * { color: blue; }
39
+ }
40
40
  eocss
41
41
  assert_equal 1, doc.document_queries.size
42
- assert_equal 2, doc.document_queries[0].url_functions.size
43
- assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
44
- assert_equal 'domain', doc.document_queries[0].url_functions[0].name
45
- assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
46
- assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[1].class
47
- assert_equal 'url-prefix', doc.document_queries[0].url_functions[1].name
48
- assert_equal 'http://example.com', doc.document_queries[0].url_functions[1].params[0].value
49
- assert_equal 1, doc.document_queries[0].rule_sets.size
42
+ assert_equal 2, doc.document_queries[0].url_functions.size
43
+ assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[0].class
44
+ assert_equal 'domain', doc.document_queries[0].url_functions[0].name
45
+ assert_equal 'example.com', doc.document_queries[0].url_functions[0].params[0].value
46
+ assert_equal CSSPool::Terms::Function, doc.document_queries[0].url_functions[1].class
47
+ assert_equal 'url-prefix', doc.document_queries[0].url_functions[1].name
48
+ assert_equal 'http://example.com', doc.document_queries[0].url_functions[1].params[0].value
49
+ assert_equal 1, doc.document_queries[0].rule_sets.size
50
50
  end
51
51
 
52
52
  def test_url
53
53
  doc = CSSPool.CSS <<-eocss
54
- @document url("http://www.example.com") {
55
- * { color: blue; }
56
- }
54
+ @document url("http://www.example.com") {
55
+ * { color: blue; }
56
+ }
57
57
  eocss
58
58
  assert_equal 1, doc.document_queries.size
59
- assert_equal 1, doc.document_queries[0].url_functions.size
60
- assert_equal CSSPool::Terms::URI, doc.document_queries[0].url_functions[0].class
61
- assert_equal 'http://www.example.com', doc.document_queries[0].url_functions[0].value
62
- assert_equal 1, doc.document_queries[0].rule_sets.size
59
+ assert_equal 1, doc.document_queries[0].url_functions.size
60
+ assert_equal CSSPool::Terms::URI, doc.document_queries[0].url_functions[0].class
61
+ assert_equal 'http://www.example.com', doc.document_queries[0].url_functions[0].value
62
+ assert_equal 1, doc.document_queries[0].rule_sets.size
63
63
  end
64
64
 
65
65
  def test_empty
66
66
  doc = CSSPool.CSS <<-eocss
67
- @document url("http://www.example.com") {
68
- }
67
+ @document url("http://www.example.com") {
68
+ }
69
69
  eocss
70
70
  assert_equal 1, doc.document_queries.size
71
- assert_equal true, doc.document_queries[0].rule_sets.empty?
71
+ assert_equal true, doc.document_queries[0].rule_sets.empty?
72
72
  end
73
-
74
73
  end
75
74
  end
76
75
  end
@@ -0,0 +1,16 @@
1
+ require 'helper'
2
+
3
+ module CSSPool
4
+ module CSS
5
+ class TestFontFace < CSSPool::TestCase
6
+
7
+ def test_basic
8
+ doc = CSSPool.CSS <<-eocss
9
+ @font-face { font-weight: normal; }
10
+ eocss
11
+ assert_equal "@font-face {\n font-weight: normal;\n}", doc.to_css
12
+ end
13
+
14
+ end
15
+ end
16
+ end