Class: JsonPath::Parser

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

Overview

Parser parses and evaluates an expression passed to @_current_node.

Constant Summary collapse

REGEX =
/\A\/(.+)\/([imxnesu]*)\z|\A%r{(.+)}([imxnesu]*)\z/

Instance Method Summary collapse

Methods included from Dig

#dig, #dig_as_hash, #dig_one, #yield_if_diggable

Constructor Details

#initialize(node, options) ⇒ Parser

Returns a new instance of Parser.



12
13
14
15
16
# File 'lib/jsonpath/parser.rb', line 12

def initialize(node, options)
  @_current_node = node
  @_expr_map = {}
  @options = options
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



44
45
46
47
48
49
50
51
# File 'lib/jsonpath/parser.rb', line 44

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)


29
30
31
32
33
34
35
36
37
# File 'lib/jsonpath/parser.rb', line 29

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.



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

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(/\['((?<!\\).)+'\]/))
      elements << t.gsub(/^\['(.*)'\]$/, '\1')
    elsif (t = scanner.scan(/\.[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
        elsif operator.to_s.strip == '=~'
          parse_regex(t)
        else
          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)
         dig(@_current_node, *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