Class: JsonPath::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/jsonpath/parser.rb

Overview

Parser parses and evaluates an expression passed to @_current_node.

Instance Method Summary collapse

Constructor Details

#initialize(node) ⇒ Parser

Returns a new instance of Parser.



9
10
11
12
# File 'lib/jsonpath/parser.rb', line 9

def initialize(node)
  @_current_node = node
  @_expr_map = {}
end

Instance Method Details

#construct_expression_map(exps) ⇒ Object

Construct a map for which the keys are the expressions  and the values are the corresponding parsed results. Exp.: =~ /herman|lukyanenko/i)”=>0 “@[‘isTrue’]”=>true



40
41
42
43
44
45
46
47
# File 'lib/jsonpath/parser.rb', line 40

def construct_expression_map(exps)
  exps.each_with_index do |item, _index|
    next if item == '&&' || item == '||'

    item = item.strip.gsub(/\)*$/, '').gsub(/^\(*/, '')
    @_expr_map[item] = parse_exp(item)
  end
end

#parse(exp) ⇒ Object

parse will parse an expression in the following way. Split the expression up into an array of legs for && and || operators. Parse this array into a map for which the keys are the parsed legs  of the split. This map is then used to replace the expression with their corresponding boolean or numeric value. This might look something like this: ((false || false) && (false || true))  Once this string is assembled… we proceed to evaluate from left to right.  The above string is broken down like this: (false && (false || true)) (false && true)  false

Raises:

  • (ArgumentError)


25
26
27
28
29
30
31
32
33
# File 'lib/jsonpath/parser.rb', line 25

def parse(exp)
  exps = exp.split(/(&&)|(\|\|)/)
  construct_expression_map(exps)
  @_expr_map.each { |k, v| exp.sub!(k, v.to_s) }
  raise ArgumentError, "unmatched parenthesis in expression: #{exp}" unless check_parenthesis_count(exp)

  exp = parse_parentheses(exp) while exp.include?('(')
  bool_or_exp(exp)
end

#parse_exp(exp) ⇒ Object

Using a scanner break down the individual expressions and determine if there is a match in the JSON for it or not.



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
# File 'lib/jsonpath/parser.rb', line 51

def parse_exp(exp)
  exp = exp.sub(/@/, '').gsub(/^\(/, '').gsub(/\)$/, '').tr('"', '\'').strip
  exp.scan(/^\[(\d+)\]/) do |i|
    next if i.empty?

    index = Integer(i[0])
    raise ArgumentError, 'Node does not appear to be an array.' unless @_current_node.is_a?(Array)
    raise ArgumentError, "Index out of bounds for nested array. Index: #{index}" if @_current_node.size < index

    @_current_node = @_current_node[index]
    # Remove the extra '' and the index.
    exp = exp.gsub(/^\[\d+\]|\[''\]/, '')
  end
  scanner = StringScanner.new(exp)
  elements = []
  until scanner.eos?
    if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?!]?/))
      elements << t.gsub(/[\[\]'.]|\s+/, '')
    elsif (t = scanner.scan(/(\s+)?[<>=!\-+][=~]?(\s+)?/))
      operator = t
    elsif (t = scanner.scan(/(\s+)?'?.*'?(\s+)?/))
      # If we encounter a node which does not contain `'` it means
      #  that we are dealing with a boolean type.
      operand = if t == 'true'
                  true
                elsif t == 'false'
                  false
                else
                  operator.to_s.strip == '=~' ? t.to_regexp : t.gsub(%r{^'|'$}, '').strip
                end
    elsif (t = scanner.scan(/\/\w+\//))
    elsif (t = scanner.scan(/.*/))
      raise "Could not process symbol: #{t}"
    end
  end

  el = if elements.empty?
         @_current_node
       elsif @_current_node.is_a?(Hash)
         @_current_node.dig(*elements)
       else
         elements.inject(@_current_node, &:__send__)
       end

  return (el ? true : false) if el.nil? || operator.nil?

  el = Float(el) rescue el
  operand = Float(operand) rescue operand

  el.__send__(operator.strip, operand)
end