Class: Prawn::SVG::CSS::SelectorParser

Inherits:
Object
  • Object
show all
Defined in:
lib/prawn/svg/css/selector_parser.rb

Defined Under Namespace

Classes: Attribute, Combinator, Identifier, Modifier

Constant Summary collapse

VALID_CSS_IDENTIFIER_CHAR =
/[a-zA-Z0-9_\u00a0-\uffff-]/.freeze

Class Method Summary collapse

Class Method Details

.parse(selector) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/prawn/svg/css/selector_parser.rb', line 3

def self.parse(selector)
  tokens = tokenise_css_selector(selector) or return

  result = [{}]
  part = nil

  tokens.each do |token|
    case token
    when Modifier
      part = token.type
      result.last[part] ||= part == :name ? +'' : []
    when Identifier
      return unless part

      result.last[part] << token.name
    when Attribute
      return unless ['=', '*=', '~=', '^=', '|=', '$=', nil].include?(token.operator)

      (result.last[:attribute] ||= []) << [token.key, token.operator, token.value]
    when Combinator
      result << { combinator: token.type }
      part = nil
    end
  end

  result
end

.tokenise_css_selector(selector) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/prawn/svg/css/selector_parser.rb', line 37

def self.tokenise_css_selector(selector)
  result = []
  brackets = false
  attribute = false
  quote = false

  selector.strip.chars do |char|
    if brackets
      result.last.name << char
      brackets = false if char == ')'
    elsif attribute
      case attribute
      when :pre_key
        if VALID_CSS_IDENTIFIER_CHAR.match(char)
          result.last.key = String.new(char)
          attribute = :key
        elsif char != ' ' && char != "\t"
          return
        end

      when :key
        if VALID_CSS_IDENTIFIER_CHAR.match(char)
          result.last.key << char
        elsif char == ']'
          attribute = nil
        elsif '=*~^|$'.include?(char)
          result.last.operator = String.new(char)
          attribute = :operator
        elsif [' ', "\t"].include?(char)
          attribute = :pre_operator
        else
          return
        end

      when :pre_operator
        if '=*~^|$'.include?(char)
          result.last.operator = String.new(char)
          attribute = :operator
        elsif char != ' ' && char != "\t"
          return
        end

      when :operator
        if '=*~^|$'.include?(char)
          result.last.operator << char
        elsif [' ', "\t"].include?(char)
          attribute = :pre_value
        elsif ['"', "'"].include?(char)
          result.last.value = +''
          attribute = String.new(char)
        else
          result.last.value = String.new(char)
          attribute = :value
        end

      when :pre_value
        if ['"', "'"].include?(char)
          result.last.value = +''
          attribute = String.new(char)
        elsif char != ' ' && char != "\t"
          result.last.value = String.new(char)
          attribute = :value
        end

      when :value
        if char == ']'
          result.last.value = String.new(result.last.value.rstrip)
          attribute = nil
        else
          result.last.value << char
        end

      when '"', "'"
        if char == '\\' && !quote
          quote = true
        elsif char == attribute && !quote
          attribute = :post_string
        else
          quote = false
          result.last.value << char
        end

      when :post_string
        if char == ']'
          attribute = nil
        elsif char != ' ' && char != "\t"
          return
        end
      end

    elsif VALID_CSS_IDENTIFIER_CHAR.match(char)
      case result.last
      when Identifier
        result.last.name << char
      else
        result << Modifier.new(:name) unless result.last.is_a?(Modifier)
        result << Identifier.new(char)
      end
    else
      case char
      when '.'
        result << Modifier.new(:class)
      when '#'
        result << Modifier.new(:id)
      when ':'
        result << Modifier.new(:pseudo_class)
      when ' ', "\t"
        result << Combinator.new(:descendant) unless result.last.is_a?(Combinator)
      when '>'
        result.pop if result.last == Combinator.new(:descendant)
        result << Combinator.new(:child)
      when '+'
        result.pop if result.last == Combinator.new(:descendant)
        result << Combinator.new(:adjacent)
      when '~'
        result.pop if result.last == Combinator.new(:descendant)
        result << Combinator.new(:siblings)
      when '*'
        return unless result.empty? || result.last.is_a?(Combinator)

        result << Modifier.new(:name)
        result << Identifier.new('*')
      when '(' # e.g. :nth-child(3n+4)
        unless result.last.is_a?(Identifier) && result[-2] && result[-2].is_a?(Modifier) && result[-2].type == :pseudo_class
          return
        end

        result.last.name << '('
        brackets = true
      when '['
        result << Attribute.new
        attribute = :pre_key
      else
        return # unsupported Combinator
      end
    end
  end

  result unless brackets || attribute
end