csspool 4.0.0.pre → 4.0.0

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.
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