lib/jsonpath/enumerable.rb in jsonpath-0.5.8 vs lib/jsonpath/enumerable.rb in jsonpath-0.7.0

- old
+ new

@@ -3,87 +3,98 @@ include ::Enumerable attr_reader :allow_eval alias_method :allow_eval?, :allow_eval def initialize(path, object, mode, options = nil) - @path, @object, @mode, @options = path.path, object, mode, options - @allow_eval = @options && @options.key?(:allow_eval) ? @options[:allow_eval] : true + @path = path.path + @object = object + @mode = mode + @options = options + @allow_eval = if @options && @options.key?(:allow_eval) + @options[:allow_eval] + else + true + end end def each(context = @object, key = nil, pos = 0, &blk) node = key ? context[key] : context @_current_node = node return yield_value(blk, context, key) if pos == @path.size case expr = @path[pos] - when '*', '..' + when '*', '..', '@' each(context, key, pos + 1, &blk) when '$' each(context, key, pos + 1, &blk) if node == @object - when '@' - each(context, key, pos + 1, &blk) when /^\[(.*)\]$/ - expr[1,expr.size - 2].split(',').each do |sub_path| - case sub_path[0] - when ?', ?" - if node.is_a?(Hash) - k = sub_path[1,sub_path.size - 2] - each(node, k, pos + 1, &blk) if node.key?(k) - end - when ?? - raise "Cannot use ?(...) unless eval is enabled" unless allow_eval? - case node - when Hash, Array - (node.is_a?(Hash) ? node.keys : (0..node.size)).each do |e| - @_current_node = node[e] - if process_function_or_literal(sub_path[1, sub_path.size - 1]) - each(@_current_node, nil, pos + 1, &blk) - end - end - else - yield node if process_function_or_literal(sub_path[1, sub_path.size - 1]) - end - else - if node.is_a?(Array) - next if node.empty? - array_args = sub_path.split(':') - if array_args[0] == ?* - start_idx = 0 - end_idx = node.size - 1 - else - start_idx = process_function_or_literal(array_args[0], 0) - next unless start_idx - end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1)) - next unless end_idx - if start_idx == end_idx - next unless start_idx < node.size - end - end - start_idx %= node.size - end_idx %= node.size - step = process_function_or_literal(array_args[2], 1) - next unless step - (start_idx..end_idx).step(step) {|i| each(node, i, pos + 1, &blk)} - end - end - end + handle_wildecard(node, expr, context, key, pos, &blk) else if pos == (@path.size - 1) && node && allow_eval? - if eval("node #{@path[pos]}") - yield_value(blk, context, key) - end + yield_value(blk, context, key) if instance_eval("node #{@path[pos]}") end end - if pos > 0 && @path[pos-1] == '..' + if pos > 0 && @path[pos - 1] == '..' || (@path[pos - 1] == '*' && @path[pos] != '..') case node - when Hash then node.each {|k, v| each(node, k, pos, &blk) } - when Array then node.each_with_index {|n, i| each(node, i, pos, &blk) } + when Hash then node.each { |k, _| each(node, k, pos, &blk) } + when Array then node.each_with_index { |_, i| each(node, i, pos, &blk) } end end end private + + def handle_wildecard(node, expr, context, key, pos, &blk) + expr[1, expr.size - 2].split(',').each do |sub_path| + case sub_path[0] + when '\'', '"' + next unless node.is_a?(Hash) + k = sub_path[1, sub_path.size - 2] + each(node, k, pos + 1, &blk) if node.key?(k) + when '?' + handle_question_mark(sub_path, node, pos, &blk) + else + next unless node.is_a?(Array) && !node.empty? + array_args = sub_path.split(':') + if array_args[0] == '*' + start_idx = 0 + end_idx = node.size - 1 + else + start_idx = process_function_or_literal(array_args[0], 0) + next unless start_idx + end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1)) + next unless end_idx + next if start_idx == end_idx && start_idx >= node.size + end + start_idx %= node.size + end_idx %= node.size + step = process_function_or_literal(array_args[2], 1) + next unless step + (start_idx..end_idx).step(step) { |i| each(node, i, pos + 1, &blk) } + end + end + end + + def handle_question_mark(sub_path, node, pos, &blk) + raise 'Cannot use ?(...) unless eval is enabled' unless allow_eval? + case node + when Array + node.size.times do |index| + @_current_node = node[index] + if process_function_or_literal(sub_path[1, sub_path.size - 1]) + each(@_current_node, nil, pos + 1, &blk) + end + end + when Hash + if process_function_or_literal(sub_path[1, sub_path.size - 1]) + each(@_current_node, nil, pos + 1, &blk) + end + else + yield node if process_function_or_literal(sub_path[1, sub_path.size - 1]) + end + end + def yield_value(blk, context, key) case @mode when nil blk.call(key ? context[key] : context) when :compact @@ -98,38 +109,45 @@ end end end def process_function_or_literal(exp, default = nil) - if exp.nil? - default - elsif exp[0] == ?( - return nil unless allow_eval? && @_current_node - identifiers = /@?(\.(\w+))+/.match(exp) + return default if exp.nil? || exp.empty? + return Integer(exp) if exp[0] != '(' + return nil unless allow_eval? && @_current_node - if !identifiers.nil? && !@_current_node.methods.include?(identifiers[2].to_sym) - exp_to_eval = exp.dup - exp_to_eval[identifiers[0]] = identifiers[0].split('.').map{|el| el == '@' ? '@_current_node' : "['#{el}']"}.join - begin - return eval(exp_to_eval) - rescue StandardError # if eval failed because of bad arguments or missing methods - return default - end - end + identifiers = /@?(\.(\w+))+/.match(exp) + # puts JsonPath.on(@_current_node, "#{identifiers}") unless identifiers.nil? || + # @_current_node + # .methods + # .include?(identifiers[2].to_sym) - # otherwise eval as is - # TODO: this eval is wrong, because hash accessor could be nil and nil cannot be compared with anything, - # for instance, @_current_node['price'] - we can't be sure that 'price' are in every node, but it's only in several nodes - # I wrapped this eval into rescue returning false when error, but this eval should be refactored. + unless identifiers.nil? || + @_current_node.methods.include?(identifiers[2].to_sym) + + exp_to_eval = exp.dup + exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el| + el == '@' ? '@_current_node' : "['#{el}']" + end.join + begin - eval(exp.gsub(/@/, '@_current_node')) - rescue - false + return instance_eval(exp_to_eval) + # if eval failed because of bad arguments or missing methods + rescue StandardError + return default end - elsif exp.empty? - default - else - Integer(exp) + end + + # otherwise eval as is + # TODO: this eval is wrong, because hash accessor could be nil and nil + # cannot be compared with anything, for instance, + # @a_current_node['price'] - we can't be sure that 'price' are in every + # node, but it's only in several nodes I wrapped this eval into rescue + # returning false when error, but this eval should be refactored. + begin + instance_eval(exp.gsub(/@/, '@_current_node')) + rescue + false end end end end