jpath 0.1
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +58 -0
- data/Rakefile +13 -0
- data/lib/jpath.rb +21 -0
- data/lib/jpath/item.rb +138 -0
- data/lib/jpath/parser.rb +128 -0
- data/lib/jpath/parser/formula.rb +225 -0
- data/lib/jpath/parser/path.rb +53 -0
- data/lib/jpath/parser/step.rb +126 -0
- data/lib/jpath/pointer.rb +65 -0
- data/spec/item_spec.rb +84 -0
- data/spec/jpath_spec.rb +59 -0
- data/spec/parser_spec.rb +36 -0
- data/spec/pointer_spec.rb +77 -0
- data/spec/spec_helper.rb +33 -0
- metadata +78 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 457110306795a1ccec58c95678abe4ffba72799a
         | 
| 4 | 
            +
              data.tar.gz: d36bbbe5ffc6d83dc9b2d48842a6edb05320c839
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: ec250f68aa9480e96ff7ed5fba174eecdecf7bb89b14ba584f1b8f945c7e81bab2a34cc287e3f4ac90b3aa04fde54fe99a418713a366d0c10abd2b61229ac539
         | 
| 7 | 
            +
              data.tar.gz: 1379625372cb7675bac5cb2c9ac400748d26559b70147ad3eec33b52fa41e027bb2ab6c7aa9b3ce724e45244523aca666a474af6773c882bc169f75efe4fe1a9
         | 
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2013 Merimond Corporation
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            # JPath
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            JPath is a Ruby library that allows to execute XPath queries on JSON documents.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ### Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            JPath doesn't use any dependencies. Simply install via rubygems:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                gem install jpath
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            or clone it from github:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                git clone https://github.com/merimond/jpath.git
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            JPath is in early beta stage. So having an up-to-date Git version is more preferable.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ## Library
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Please note that JPath works with *standard* XPath syntax, not one of XPath alternatives, such as the great [JSONPath](http://goessner.net/articles/JsonPath/) syntax by Stefan Goessner. If you prefer the latter, there's a [terrific library](https://github.com/joshbuddy/jsonpath) by Joshua Hull that you should check out.
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ### A few caveats
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            Although JSON and XML are both data containers, they are quite different when it comes to searching and manipulating data. So JPath _may_ behave somewhat differently from what you woud expect, although we tried to make some sensible assumptions while transforming the XML-oriented search paradigm into the JSON world.
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ## Features
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            JPath support all the basic and some of the more advanced XPath functions:
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Simple chains with children and ancestors:
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                /store/book/author
         | 
| 32 | 
            +
                //author
         | 
| 33 | 
            +
                /store/*
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
            Position predicates:
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                //book[last()]
         | 
| 38 | 
            +
                //book[position()<3]
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            Attribute and child predicates:
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                //book[@price<10]
         | 
| 43 | 
            +
                //book[isbn]
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            Multiple predicates:
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                //book[@price>10][last()]
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            And all of the above in non-abbreviated form:
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                /descendant::book
         | 
| 52 | 
            +
                /child::book[attribute::price="8.95"]
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            Test files are a bit of a mess at the moment, but they should give you an indication of what's currently supported.
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ## Contributing
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            Fork, send pull requests, file bug reports -- any help is welcome.
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/lib/jpath.rb
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              require 'strscan'
         | 
| 4 | 
            +
              require 'json'
         | 
| 5 | 
            +
              
         | 
| 6 | 
            +
              require_relative "jpath/item"
         | 
| 7 | 
            +
              require_relative "jpath/pointer"
         | 
| 8 | 
            +
              require_relative "jpath/parser"
         | 
| 9 | 
            +
              require_relative "jpath/parser/formula"
         | 
| 10 | 
            +
              require_relative "jpath/parser/step"
         | 
| 11 | 
            +
              require_relative "jpath/parser/path"
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
              def self.find(json, xpath)
         | 
| 14 | 
            +
                parse(xpath).from(json)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
              def self.parse(xpath)
         | 
| 18 | 
            +
                Parser.path(xpath)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
            end
         | 
    
        data/lib/jpath/item.rb
    ADDED
    
    | @@ -0,0 +1,138 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              class Item
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def initialize(hash)
         | 
| 5 | 
            +
                  @hash = hash
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                def [](pointer)
         | 
| 9 | 
            +
                  result = @hash
         | 
| 10 | 
            +
                  if pointer.is_a?(String)
         | 
| 11 | 
            +
                    pointer = Pointer.parse(pointer)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  pointer.each do |key|
         | 
| 14 | 
            +
                    unless result.is_a?(Array) or result.is_a?(Hash)
         | 
| 15 | 
            +
                      return nil
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                    if result.is_a?(Array) && !key.is_a?(Numeric)
         | 
| 18 | 
            +
                      return nil
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                    result = result[key]
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                  result
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                def parent(pointer)
         | 
| 26 | 
            +
                  if pointer.is_a?(String)
         | 
| 27 | 
            +
                    pointer = Pointer.parse(pointer)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  if pointer.top?
         | 
| 30 | 
            +
                    return nil
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  if pointer.index?
         | 
| 33 | 
            +
                    pointer.grandparent
         | 
| 34 | 
            +
                  else
         | 
| 35 | 
            +
                    pointer.parent
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                def ancestors(pointer)
         | 
| 40 | 
            +
                  if pointer.is_a?(String)
         | 
| 41 | 
            +
                    pointer = Pointer.parse(pointer)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                  obj = parent(pointer)
         | 
| 44 | 
            +
                  if obj.nil?
         | 
| 45 | 
            +
                    []
         | 
| 46 | 
            +
                  else
         | 
| 47 | 
            +
                    [obj, ancestors(obj)].flatten
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                def attributes(pointer)
         | 
| 52 | 
            +
                  enum_for(:each_attribute, pointer)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                
         | 
| 55 | 
            +
                def children(pointer)
         | 
| 56 | 
            +
                  enum_for(:each_child, pointer)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                def descendants(pointer)
         | 
| 60 | 
            +
                  enum_for(:each_descendant, pointer)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                def following(pointer)
         | 
| 64 | 
            +
                  enum_for(:each_following, pointer)
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
                
         | 
| 67 | 
            +
                def preceding(pointer)
         | 
| 68 | 
            +
                  enum_for(:each_preceding, pointer)
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
                
         | 
| 71 | 
            +
                private
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                def each_attribute(pointer)
         | 
| 74 | 
            +
                  children(pointer).select do |child|
         | 
| 75 | 
            +
                    unless self[child].is_a?(Hash)
         | 
| 76 | 
            +
                      yield child
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                def each_child(pointer, &block)
         | 
| 82 | 
            +
                  if pointer.is_a?(String)
         | 
| 83 | 
            +
                    pointer = Pointer.parse(pointer)
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                  obj = self[pointer]
         | 
| 86 | 
            +
                  if obj.is_a?(Hash)
         | 
| 87 | 
            +
                    obj.each do |key, value|
         | 
| 88 | 
            +
                      if value.is_a?(Array)
         | 
| 89 | 
            +
                        each_child(pointer + key, &block)
         | 
| 90 | 
            +
                      else
         | 
| 91 | 
            +
                        yield pointer + key
         | 
| 92 | 
            +
                      end
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                  if obj.is_a?(Array)
         | 
| 96 | 
            +
                    obj.each_with_index do |item, index|
         | 
| 97 | 
            +
                      yield pointer + index
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
                
         | 
| 102 | 
            +
                def each_descendant(pointer)
         | 
| 103 | 
            +
                  children(pointer).each do |p1|
         | 
| 104 | 
            +
                    yield p1
         | 
| 105 | 
            +
                    each_descendant(p1) do |p2|
         | 
| 106 | 
            +
                      yield p2
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
                
         | 
| 111 | 
            +
                def each_following(pointer)
         | 
| 112 | 
            +
                  flag   = false
         | 
| 113 | 
            +
                  parent = parent(pointer)
         | 
| 114 | 
            +
                  each_child(parent) do |child|
         | 
| 115 | 
            +
                    if flag
         | 
| 116 | 
            +
                      yield child
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
                    if child == pointer
         | 
| 119 | 
            +
                      flag = true
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def each_preceding(pointer)
         | 
| 125 | 
            +
                  flag   = true
         | 
| 126 | 
            +
                  parent = parent(pointer)
         | 
| 127 | 
            +
                  each_child(parent) do |child|
         | 
| 128 | 
            +
                    if child == pointer
         | 
| 129 | 
            +
                      flag = false
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                    if flag
         | 
| 132 | 
            +
                      yield child
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
                
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
            end
         | 
    
        data/lib/jpath/parser.rb
    ADDED
    
    | @@ -0,0 +1,128 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              module Parser
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def self.path(s)
         | 
| 5 | 
            +
                  unless s.is_a?(StringScanner)
         | 
| 6 | 
            +
                    s = StringScanner.new(s)
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
                  Path.new do |path|
         | 
| 9 | 
            +
                    each_step(s) do |step|
         | 
| 10 | 
            +
                      path << step
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                def self.predicates(s)
         | 
| 16 | 
            +
                  enum_for(:each_predicate, s).to_a
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                def self.steps(s)
         | 
| 20 | 
            +
                  enum_for(:each_step, s).to_a
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                def self.expressions(s)
         | 
| 24 | 
            +
                  enum_for(:each_expression, s).to_a
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                def self.next_expression(s)
         | 
| 30 | 
            +
                  if s.scan /position\(\)/
         | 
| 31 | 
            +
                    return Position.new
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                  if s.scan /(\d+\.\d+)/
         | 
| 34 | 
            +
                    return Number.new(s[1].to_f)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  if s.scan /(\d+)/
         | 
| 37 | 
            +
                    return Number.new(s[1].to_i)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  if s.scan /@([\w*]+)/
         | 
| 40 | 
            +
                    return Attribute.new(s[1])
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                  if s.scan /\=/
         | 
| 43 | 
            +
                    return Operator.new("=")
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                  if s.scan /</
         | 
| 46 | 
            +
                    return Operator.new("<")
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  if s.scan />/
         | 
| 49 | 
            +
                    return Operator.new(">")
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                  if s.scan /"([^"]+)"/
         | 
| 52 | 
            +
                    return Literal.new(s[1])
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def self.each_expression(s)
         | 
| 57 | 
            +
                  until s.eos?
         | 
| 58 | 
            +
                    obj = next_expression(s)
         | 
| 59 | 
            +
                    break if obj.nil?
         | 
| 60 | 
            +
                    yield obj
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
                
         | 
| 64 | 
            +
                def self.next_predicate(s)
         | 
| 65 | 
            +
                  if s.scan /\[(\d+)\]/
         | 
| 66 | 
            +
                    return Formula.new("=", Position.new, Number.new(s[1].to_i)).to_predicate
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                  if s.scan /\[last\(\)\]/
         | 
| 69 | 
            +
                    return Formula.new("=", Position.new, Last.new).to_predicate
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                  if s.scan /\[@?([\w*]+)\]/
         | 
| 72 | 
            +
                    return Predicate.new(Contains.new(Attribute.new(s[1])))
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                  unless s.scan(/\[/)
         | 
| 75 | 
            +
                    return
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                  exp = expressions(s).inject do |result, expression|
         | 
| 78 | 
            +
                    result + expression
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                  if exp.nil? || !exp.boolean?
         | 
| 81 | 
            +
                    raise "Invalid predicate: %s" % s.rest.inspect 
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                  unless s.scan(/\]/)
         | 
| 84 | 
            +
                    raise "Invalid predicate: %s" % s.rest.inspect 
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                  Predicate.new(exp)
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
                
         | 
| 89 | 
            +
                def self.each_predicate(s)
         | 
| 90 | 
            +
                  until s.eos?
         | 
| 91 | 
            +
                    obj = next_predicate(s)
         | 
| 92 | 
            +
                    break if obj.nil?
         | 
| 93 | 
            +
                    yield obj
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
                
         | 
| 97 | 
            +
                def self.next_step(s)
         | 
| 98 | 
            +
                  if s.scan /\.?\/\/([\w*]+)/
         | 
| 99 | 
            +
                    return Descendant.new(s[1])
         | 
| 100 | 
            +
                  elsif s.scan /descendant::([\w*]+)/
         | 
| 101 | 
            +
                    return Descendant.new(s[1])
         | 
| 102 | 
            +
                  elsif s.scan /child::([\w*]+)/
         | 
| 103 | 
            +
                    return Child.new(s[1])
         | 
| 104 | 
            +
                  elsif s.scan /([\w*]+)/
         | 
| 105 | 
            +
                    return Child.new(s[1])
         | 
| 106 | 
            +
                  elsif s.scan /.\//
         | 
| 107 | 
            +
                    return Self.new
         | 
| 108 | 
            +
                  elsif s.bol? && s.scan(/\//)
         | 
| 109 | 
            +
                    return Root.new
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
                
         | 
| 113 | 
            +
                def self.each_step(s)
         | 
| 114 | 
            +
                  until s.eos?
         | 
| 115 | 
            +
                    obj = next_step(s)
         | 
| 116 | 
            +
                    break if obj.nil?
         | 
| 117 | 
            +
                    each_predicate(s) do |p|
         | 
| 118 | 
            +
                      obj << p
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                    yield obj
         | 
| 121 | 
            +
                    unless s.match?(/\/\//)
         | 
| 122 | 
            +
                      s.scan /\//
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
                  
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
            end
         | 
| @@ -0,0 +1,225 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              module Parser
         | 
| 3 | 
            +
                class Predicate
         | 
| 4 | 
            +
                  
         | 
| 5 | 
            +
                  attr_reader :expression
         | 
| 6 | 
            +
                  
         | 
| 7 | 
            +
                  def initialize(expression)
         | 
| 8 | 
            +
                    @expression = expression
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                  
         | 
| 11 | 
            +
                  def true?(*args)
         | 
| 12 | 
            +
                    expression.true?(*args)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                  
         | 
| 15 | 
            +
                  def to_s
         | 
| 16 | 
            +
                    "[%s]" % expression
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                  
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                class Operator
         | 
| 21 | 
            +
                  
         | 
| 22 | 
            +
                  attr_reader :char
         | 
| 23 | 
            +
                  
         | 
| 24 | 
            +
                  attr_reader :parts
         | 
| 25 | 
            +
                  
         | 
| 26 | 
            +
                  def initialize(char)
         | 
| 27 | 
            +
                    @char  = char
         | 
| 28 | 
            +
                    @parts = []
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                  
         | 
| 31 | 
            +
                  def boolean?
         | 
| 32 | 
            +
                    false
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                  
         | 
| 35 | 
            +
                  def +(other)
         | 
| 36 | 
            +
                    add(other)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                  
         | 
| 39 | 
            +
                  def add(other)
         | 
| 40 | 
            +
                    @parts << other
         | 
| 41 | 
            +
                    if parts.size < 2
         | 
| 42 | 
            +
                      self
         | 
| 43 | 
            +
                    else
         | 
| 44 | 
            +
                      Formula.new(char, parts)
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  def to_s
         | 
| 49 | 
            +
                    char.to_s
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                  
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                class Formula
         | 
| 54 | 
            +
                  
         | 
| 55 | 
            +
                  attr_reader :parts
         | 
| 56 | 
            +
                  
         | 
| 57 | 
            +
                  attr_reader :type
         | 
| 58 | 
            +
                  
         | 
| 59 | 
            +
                  def initialize(type, *parts)
         | 
| 60 | 
            +
                    @type  = type
         | 
| 61 | 
            +
                    @parts = parts.flatten
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                  
         | 
| 64 | 
            +
                  def true?(*args)
         | 
| 65 | 
            +
                    f = first.value(*args)
         | 
| 66 | 
            +
                    s = second.value(*args)
         | 
| 67 | 
            +
                    if requires_boolean?
         | 
| 68 | 
            +
                      unless f === true or f === false
         | 
| 69 | 
            +
                        return false
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                      unless s === true or s === false
         | 
| 72 | 
            +
                        return false
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    else
         | 
| 75 | 
            +
                      unless f.is_a?(Numeric)
         | 
| 76 | 
            +
                        return false
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                      unless s.is_a?(Numeric)
         | 
| 79 | 
            +
                        return false
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                    case type
         | 
| 83 | 
            +
                    when "+"
         | 
| 84 | 
            +
                      f + s
         | 
| 85 | 
            +
                    when "-"
         | 
| 86 | 
            +
                      f - s
         | 
| 87 | 
            +
                    when "*"
         | 
| 88 | 
            +
                      f * s
         | 
| 89 | 
            +
                    when "/"
         | 
| 90 | 
            +
                      f / s
         | 
| 91 | 
            +
                    when ">"
         | 
| 92 | 
            +
                      f > s
         | 
| 93 | 
            +
                    when "<"
         | 
| 94 | 
            +
                      f < s
         | 
| 95 | 
            +
                    when "="
         | 
| 96 | 
            +
                      f == s
         | 
| 97 | 
            +
                    when "and"
         | 
| 98 | 
            +
                      f && s
         | 
| 99 | 
            +
                    when "or"
         | 
| 100 | 
            +
                      f || s
         | 
| 101 | 
            +
                    else
         | 
| 102 | 
            +
                      raise "Invalid formula type: %s" % type
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                  
         | 
| 106 | 
            +
                  def first
         | 
| 107 | 
            +
                    parts[0]
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
                  
         | 
| 110 | 
            +
                  def second
         | 
| 111 | 
            +
                    parts[1]
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
                  
         | 
| 114 | 
            +
                  def requires_boolean?
         | 
| 115 | 
            +
                    %w(and or).include?(type)
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                  
         | 
| 118 | 
            +
                  def boolean?
         | 
| 119 | 
            +
                    %w(> < = and or).include?(type)
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
                  
         | 
| 122 | 
            +
                  def to_predicate
         | 
| 123 | 
            +
                    Predicate.new(self)
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                  
         | 
| 126 | 
            +
                  def to_s
         | 
| 127 | 
            +
                    "%s%s%s" % [first, type, second]
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                  
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
                class Contains
         | 
| 132 | 
            +
                  
         | 
| 133 | 
            +
                  attr_reader :item
         | 
| 134 | 
            +
                  
         | 
| 135 | 
            +
                  def initialize(item)
         | 
| 136 | 
            +
                    @item = item
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                  
         | 
| 139 | 
            +
                  def true?(item, context, index, size)
         | 
| 140 | 
            +
                    item.children(context).map(&:name).include?(@item.name)
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                  
         | 
| 143 | 
            +
                  def boolean?
         | 
| 144 | 
            +
                    true
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
                  
         | 
| 147 | 
            +
                  def to_predicate
         | 
| 148 | 
            +
                    Predicate.new(self)
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
                  
         | 
| 151 | 
            +
                  def to_s
         | 
| 152 | 
            +
                    item.to_s
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                  
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
                class Position
         | 
| 157 | 
            +
                  
         | 
| 158 | 
            +
                  def to_s
         | 
| 159 | 
            +
                    "position()"
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                  
         | 
| 162 | 
            +
                  def value(hash, pointer, index, size)
         | 
| 163 | 
            +
                    index
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
                  
         | 
| 166 | 
            +
                  def +(other)
         | 
| 167 | 
            +
                    other.add(self)
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
                  
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
                class Last
         | 
| 172 | 
            +
                  
         | 
| 173 | 
            +
                  def value(hash, pointer, index, size)
         | 
| 174 | 
            +
                    size
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
                  
         | 
| 177 | 
            +
                  def to_s
         | 
| 178 | 
            +
                    "last()"
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                  
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
                class Literal
         | 
| 183 | 
            +
                  
         | 
| 184 | 
            +
                  attr_reader :string
         | 
| 185 | 
            +
                  
         | 
| 186 | 
            +
                  def initialize(string)
         | 
| 187 | 
            +
                    @string = string
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                  
         | 
| 190 | 
            +
                  def value(*args)
         | 
| 191 | 
            +
                    string
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
                  
         | 
| 194 | 
            +
                  def boolean?
         | 
| 195 | 
            +
                    false
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                  
         | 
| 198 | 
            +
                  def to_s
         | 
| 199 | 
            +
                    string.inspect
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                  
         | 
| 202 | 
            +
                end
         | 
| 203 | 
            +
                class Number
         | 
| 204 | 
            +
                  
         | 
| 205 | 
            +
                  attr_reader :number
         | 
| 206 | 
            +
                  
         | 
| 207 | 
            +
                  def initialize(number)
         | 
| 208 | 
            +
                    @number = number
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
                  
         | 
| 211 | 
            +
                  def value(*args)
         | 
| 212 | 
            +
                    number
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
                  
         | 
| 215 | 
            +
                  def boolean?
         | 
| 216 | 
            +
                    false
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
                  
         | 
| 219 | 
            +
                  def to_s
         | 
| 220 | 
            +
                    number.to_s
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
                  
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
              end
         | 
| 225 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              module Parser
         | 
| 3 | 
            +
                class Union
         | 
| 4 | 
            +
                  
         | 
| 5 | 
            +
                  def initialize(*paths)
         | 
| 6 | 
            +
                    @paths = paths
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
                  
         | 
| 9 | 
            +
                  def to_s
         | 
| 10 | 
            +
                    @paths.map(&:to_s).join("|")
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                  
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                class Path
         | 
| 15 | 
            +
                  
         | 
| 16 | 
            +
                  attr_reader :steps
         | 
| 17 | 
            +
                  
         | 
| 18 | 
            +
                  def initialize
         | 
| 19 | 
            +
                    @steps = []
         | 
| 20 | 
            +
                    if block_given?
         | 
| 21 | 
            +
                      yield self
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                  
         | 
| 25 | 
            +
                  def from(item)
         | 
| 26 | 
            +
                    unless item.is_a?(Item)
         | 
| 27 | 
            +
                      item = Item.new(item)
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                    list = [Pointer.new]
         | 
| 30 | 
            +
                    steps.each do |step|
         | 
| 31 | 
            +
                      list = list.map { |node|
         | 
| 32 | 
            +
                        step.from(item, node).to_a
         | 
| 33 | 
            +
                      }.flatten
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                    list = list.map do |pointer|
         | 
| 36 | 
            +
                      item[pointer]
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                    list
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  
         | 
| 41 | 
            +
                  def <<(step)
         | 
| 42 | 
            +
                    unless step.is_a?(Self)
         | 
| 43 | 
            +
                      @steps << step
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                  
         | 
| 47 | 
            +
                  def to_s
         | 
| 48 | 
            +
                    @steps.join("/")
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,126 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              module Parser
         | 
| 3 | 
            +
                class Step
         | 
| 4 | 
            +
                  
         | 
| 5 | 
            +
                  attr_reader :name
         | 
| 6 | 
            +
                  
         | 
| 7 | 
            +
                  attr_reader :predicates
         | 
| 8 | 
            +
                  
         | 
| 9 | 
            +
                  def initialize(name)
         | 
| 10 | 
            +
                    @name = name
         | 
| 11 | 
            +
                    @predicates = []
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  
         | 
| 14 | 
            +
                  def wildcard?
         | 
| 15 | 
            +
                    name == '*'
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  
         | 
| 18 | 
            +
                  def +(other)
         | 
| 19 | 
            +
                    other.add(self)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                  
         | 
| 22 | 
            +
                  def <<(predicate)
         | 
| 23 | 
            +
                    @predicates << predicate
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  
         | 
| 26 | 
            +
                  def to_s
         | 
| 27 | 
            +
                    "%s::%s%s" % [axis, name, predicates.map(&:to_s).join("")]
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
                class Self < Step
         | 
| 32 | 
            +
                  
         | 
| 33 | 
            +
                  def initialize
         | 
| 34 | 
            +
                    super(nil)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def to_s
         | 
| 38 | 
            +
                    "self"
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                class Root < Step
         | 
| 43 | 
            +
                  
         | 
| 44 | 
            +
                  def initialize
         | 
| 45 | 
            +
                    super(nil)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  def from(item, context)
         | 
| 49 | 
            +
                    [context]
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                  
         | 
| 52 | 
            +
                  def to_s
         | 
| 53 | 
            +
                    ""
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                class Attribute < Step
         | 
| 58 | 
            +
                  
         | 
| 59 | 
            +
                  def boolean?
         | 
| 60 | 
            +
                    false
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  
         | 
| 63 | 
            +
                  def value(item, context, index, size)
         | 
| 64 | 
            +
                    value = item[context + name]
         | 
| 65 | 
            +
                    if value.nil?
         | 
| 66 | 
            +
                      return nil
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                    if value =~ /\A\d+\.\d+\z/
         | 
| 69 | 
            +
                      value.to_f
         | 
| 70 | 
            +
                    elsif value =~ /\A\d+\z/
         | 
| 71 | 
            +
                      value.to_i
         | 
| 72 | 
            +
                    else
         | 
| 73 | 
            +
                      value
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                  
         | 
| 77 | 
            +
                  def axis
         | 
| 78 | 
            +
                    "attribute"
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
                class Node < Step
         | 
| 83 | 
            +
                  
         | 
| 84 | 
            +
                  def select(item, list)
         | 
| 85 | 
            +
                    select_by_predicates item, select_by_name(list)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                  
         | 
| 88 | 
            +
                  private
         | 
| 89 | 
            +
                  
         | 
| 90 | 
            +
                  def select_by_name(list)
         | 
| 91 | 
            +
                    wildcard? ? list : list.select { |child| child.name == @name }
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                  
         | 
| 94 | 
            +
                  def select_by_predicates(item, list)
         | 
| 95 | 
            +
                    predicates.each_with_index { |p| list = select_by_predicate(item, list, p) }; list
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                  
         | 
| 98 | 
            +
                  def select_by_predicate(item, list, p)
         | 
| 99 | 
            +
                    list.select.with_index { |context, index| p.true?(item, context, index + 1, list.size) }
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                  
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
                class Child < Node
         | 
| 104 | 
            +
                  
         | 
| 105 | 
            +
                  def from(item, context)
         | 
| 106 | 
            +
                    return select item, item.children(context)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                  
         | 
| 109 | 
            +
                  def axis
         | 
| 110 | 
            +
                    "child"
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                  
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
                class Descendant < Node
         | 
| 115 | 
            +
                  
         | 
| 116 | 
            +
                  def from(item, context)
         | 
| 117 | 
            +
                    return select item, item.descendants(context)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
                  
         | 
| 120 | 
            +
                  def axis
         | 
| 121 | 
            +
                    "descendant"
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
                  
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            module JPath
         | 
| 2 | 
            +
              class Pointer
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def self.parse(string)
         | 
| 5 | 
            +
                  new string.split(".").map { |str|
         | 
| 6 | 
            +
                    str =~ /\A\d+\z/ ? str.to_i : str
         | 
| 7 | 
            +
                  }.to_a
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
                
         | 
| 10 | 
            +
                def initialize(*ary)
         | 
| 11 | 
            +
                  @ary = ary.flatten
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
                
         | 
| 14 | 
            +
                def ==(other)
         | 
| 15 | 
            +
                  to_s == other.to_s
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
                
         | 
| 18 | 
            +
                def +(string)
         | 
| 19 | 
            +
                  self.class.new(@ary + [string])
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                def each(&block)
         | 
| 23 | 
            +
                  @ary.each(&block)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                def name
         | 
| 27 | 
            +
                  index? ? (parent.nil? ? nil : parent.name) : @ary.last
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                def grandparent
         | 
| 31 | 
            +
                  parent.nil? ? nil : parent.parent
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                def parent
         | 
| 35 | 
            +
                  top? ? nil : self.class.new(without_last)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                def size
         | 
| 39 | 
            +
                  @ary.size
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                def index?
         | 
| 43 | 
            +
                  @ary.last.is_a?(Integer)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                def top?
         | 
| 47 | 
            +
                  @ary.empty? or top_index?
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
                
         | 
| 50 | 
            +
                def to_s
         | 
| 51 | 
            +
                  @ary.join(".")
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                
         | 
| 54 | 
            +
                private
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def top_index?
         | 
| 57 | 
            +
                  index? && size == 1
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
                
         | 
| 60 | 
            +
                def without_last
         | 
| 61 | 
            +
                  @ary[0...-1]
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
                
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
    
        data/spec/item_spec.rb
    ADDED
    
    | @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe JPath::Item do
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              let(:item) do
         | 
| 6 | 
            +
                JPath::Item.new(HASH)
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
              context "#[]" do
         | 
| 10 | 
            +
                it "returns smth" do
         | 
| 11 | 
            +
                  item["store.book.0"].should == HASH["store"]["book"][0]
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              context "#parent" do
         | 
| 16 | 
            +
                it "returns smth" do
         | 
| 17 | 
            +
                  item.parent("store.book.0").to_s.should == "store"
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
                it "returns smth" do
         | 
| 20 | 
            +
                  item.parent("store.book.0.title").to_s.should == "store.book.0"
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              context "#ancestors" do
         | 
| 25 | 
            +
                it "returns smth" do
         | 
| 26 | 
            +
                  item.ancestors("store.book.0").map(&:to_s).should == ["store", ""]
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
                it "returns smth" do
         | 
| 29 | 
            +
                  item.ancestors("store.book.0.title").map(&:to_s).should == ["store.book.0", "store", ""]
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
              
         | 
| 33 | 
            +
              context "#children" do
         | 
| 34 | 
            +
                it "returns smth" do
         | 
| 35 | 
            +
                  item.children("store").map(&:to_s).should == ["store.book.0", "store.book.1", "store.book.2", "store.book.3", "store.bicycle"]
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                it "returns smth" do
         | 
| 38 | 
            +
                  item.children("store.bicycle").map(&:to_s).should == ["store.bicycle.color", "store.bicycle.price"]
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              context "#attributes" do
         | 
| 43 | 
            +
                it "returns smth" do
         | 
| 44 | 
            +
                  item.attributes("store").map(&:to_s).should == []
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                it "returns smth" do
         | 
| 47 | 
            +
                  item.attributes("store.bicycle").map(&:to_s).should == ["store.bicycle.color", "store.bicycle.price"]
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
              
         | 
| 51 | 
            +
              context "#descendants" do
         | 
| 52 | 
            +
                it "returns smth" do
         | 
| 53 | 
            +
                  item.descendants("store.bicycle").map(&:to_s).should == ["store.bicycle.color", "store.bicycle.price"]
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                it "returns smth" do
         | 
| 56 | 
            +
                  item.descendants("store").map(&:to_s).should == ["store.book.0", "store.book.0.category", "store.book.0.author", "store.book.0.title", "store.book.0.price", "store.book.1", "store.book.1.category.0", "store.book.1.author", "store.book.1.title", "store.book.1.price", "store.book.2", "store.book.2.category", "store.book.2.author", "store.book.2.title", "store.book.2.isbn", "store.book.2.price", "store.book.3", "store.book.3.category", "store.book.3.author", "store.book.3.title", "store.book.3.isbn", "store.book.3.price", "store.bicycle", "store.bicycle.color", "store.bicycle.price"]
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
              
         | 
| 60 | 
            +
              context "#following" do
         | 
| 61 | 
            +
                it "returns smth" do
         | 
| 62 | 
            +
                  item.following("store.book.0").map(&:to_s).should == ["store.book.1", "store.book.2", "store.book.3", "store.bicycle"]
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
                it "returns smth" do
         | 
| 65 | 
            +
                  item.following("store.book.0.author").map(&:to_s).should == ["store.book.0.title", "store.book.0.price"]
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
                it "returns smth" do
         | 
| 68 | 
            +
                  item.following("store.bicycle").map(&:to_s).should == []
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              context "#preceding" do
         | 
| 73 | 
            +
                it "returns smth" do
         | 
| 74 | 
            +
                  item.preceding("store.book.2").map(&:to_s).should == ["store.book.0", "store.book.1"]
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
                it "returns smth" do
         | 
| 77 | 
            +
                  item.preceding("store.book.0.author").map(&:to_s).should == ["store.book.0.category"]
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
                it "returns smth" do
         | 
| 80 | 
            +
                  item.preceding("store.bicycle").map(&:to_s).should == ["store.book.0", "store.book.1", "store.book.2", "store.book.3"]
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
              
         | 
| 84 | 
            +
            end
         | 
    
        data/spec/jpath_spec.rb
    ADDED
    
    | @@ -0,0 +1,59 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe JPath do
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              context "/store/book/author" do
         | 
| 6 | 
            +
                it "returns the authors of all books in the store" do
         | 
| 7 | 
            +
                  JPath.find(HASH, "/store/book/author").should == ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context "//author" do
         | 
| 12 | 
            +
                it "returns all authors" do
         | 
| 13 | 
            +
                  JPath.find(HASH, "//author").should == ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
              context "/store/*" do
         | 
| 18 | 
            +
                it "all things in store, which are some books and a red bicycle" do
         | 
| 19 | 
            +
                  JPath.find(HASH, "/store/*").should == [HASH["store"]["book"][0], HASH["store"]["book"][1], HASH["store"]["book"][2], HASH["store"]["book"][3], HASH["store"]["bicycle"]]
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              context "/store//price" do
         | 
| 24 | 
            +
                it "returns the price of everything in the store" do
         | 
| 25 | 
            +
                  JPath.find(HASH, "/store//price").should == [8.95, 12.99, 8.95, 22.99, 19.95]
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
              
         | 
| 29 | 
            +
              context "//book[3]" do
         | 
| 30 | 
            +
                it "returns the third book" do
         | 
| 31 | 
            +
                  JPath.find(HASH, "//book[3]").should == [HASH["store"]["book"][2]]
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
              
         | 
| 35 | 
            +
              context "//book[last()]" do
         | 
| 36 | 
            +
                it "the last book in order" do
         | 
| 37 | 
            +
                  JPath.find(HASH, "//book[last()]").should == [HASH["store"]["book"][3]]
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
              
         | 
| 41 | 
            +
              context "//book[position()<3]" do
         | 
| 42 | 
            +
                it "returns the first two books" do
         | 
| 43 | 
            +
                  JPath.find(HASH, "//book[position()<3]").should == [HASH["store"]["book"][0], HASH["store"]["book"][1]]
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
              
         | 
| 47 | 
            +
              context "//book[isbn]" do
         | 
| 48 | 
            +
                it "returns all books with isbn number" do
         | 
| 49 | 
            +
                  JPath.find(HASH, "//book[isbn]").should == [HASH["store"]["book"][2], HASH["store"]["book"][3]]
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
              
         | 
| 53 | 
            +
              context "//book[@price<10]" do
         | 
| 54 | 
            +
                it "returns all books cheapier than 10" do
         | 
| 55 | 
            +
                  JPath.find(HASH, "//book[@price<10]").should == [HASH["store"]["book"][0], HASH["store"]["book"][2]]
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
              
         | 
| 59 | 
            +
            end
         | 
    
        data/spec/parser_spec.rb
    ADDED
    
    | @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe JPath::Parser do
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              it "parses child" do
         | 
| 6 | 
            +
                JPath.parse("store").to_s.should               == "child::store"
         | 
| 7 | 
            +
                JPath.parse("child::store").to_s.should        == "child::store"
         | 
| 8 | 
            +
                JPath.parse("*").to_s.should                   == "child::*"
         | 
| 9 | 
            +
                JPath.parse("./store").to_s.should             == "child::store"
         | 
| 10 | 
            +
                JPath.parse("/store").to_s.should              == "/child::store"
         | 
| 11 | 
            +
                JPath.parse("/child::store").to_s.should       == "/child::store"
         | 
| 12 | 
            +
                JPath.parse("/*").to_s.should                  == "/child::*"
         | 
| 13 | 
            +
                JPath.parse("/store/book").to_s.should         == "/child::store/child::book"
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
              
         | 
| 16 | 
            +
              it "parses descendant" do
         | 
| 17 | 
            +
                JPath.parse("//book").to_s.should              == "descendant::book"
         | 
| 18 | 
            +
                JPath.parse("descendant::book").to_s.should    == "descendant::book"
         | 
| 19 | 
            +
                JPath.parse("//*").to_s.should                 == "descendant::*"
         | 
| 20 | 
            +
                JPath.parse(".//book").to_s.should             == "descendant::book"
         | 
| 21 | 
            +
                JPath.parse("/descendant::book").to_s.should   == "/descendant::book"
         | 
| 22 | 
            +
                JPath.parse("/*").to_s.should                  == "/child::*"
         | 
| 23 | 
            +
                JPath.parse("/store//price").to_s.should       == "/child::store/descendant::price"
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
              
         | 
| 26 | 
            +
              it "parses attributes" do
         | 
| 27 | 
            +
                JPath.parse('book[1]').to_s.should             == 'child::book[position()=1]'
         | 
| 28 | 
            +
                JPath.parse('book[last()]').to_s.should        == 'child::book[position()=last()]'
         | 
| 29 | 
            +
                JPath.parse('book[position()>1]').to_s.should  == 'child::book[position()>1]'
         | 
| 30 | 
            +
                JPath.parse('book[@price]').to_s.should        == 'child::book[attribute::price]'
         | 
| 31 | 
            +
                JPath.parse('book[@price="8.95"]').to_s.should == 'child::book[attribute::price="8.95"]'
         | 
| 32 | 
            +
                JPath.parse('book[@price=8.95]').to_s.should   == 'child::book[attribute::price=8.95]'
         | 
| 33 | 
            +
                JPath.parse('book[@price<8.95]').to_s.should   == 'child::book[attribute::price<8.95]'
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
              
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe JPath::Pointer do
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              context ".parse" do
         | 
| 6 | 
            +
                it "returns Pointer" do
         | 
| 7 | 
            +
                  JPath::Pointer.parse("store.book.0.title").to_s.should == 'store.book.0.title'
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
                it "returns Pointer" do
         | 
| 10 | 
            +
                  JPath::Pointer.parse("").to_s.should == ''
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              context "#index?" do
         | 
| 15 | 
            +
                it "returns false" do
         | 
| 16 | 
            +
                  JPath::Pointer.new().should_not be_index
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                it "returns false" do
         | 
| 19 | 
            +
                  JPath::Pointer.new("store", "book", 0, "title").should_not be_index
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                it "returns true" do
         | 
| 22 | 
            +
                  JPath::Pointer.new(0).should be_index
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                it "returns true" do
         | 
| 25 | 
            +
                  JPath::Pointer.new("store", "book", 0).should be_index
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              context "#name" do
         | 
| 30 | 
            +
                it "returns nil" do
         | 
| 31 | 
            +
                  JPath::Pointer.new().name.should be_nil
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                it "returns name" do
         | 
| 34 | 
            +
                  JPath::Pointer.new("store", "book", 0, "title").name.should == 'title'
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
                it "returns true" do
         | 
| 37 | 
            +
                  JPath::Pointer.new("store", "book").name.should == 'book'
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
              
         | 
| 41 | 
            +
              context "#top?" do
         | 
| 42 | 
            +
                it "returns true" do
         | 
| 43 | 
            +
                  JPath::Pointer.new().should be_top
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                it "returns true" do
         | 
| 46 | 
            +
                  JPath::Pointer.new(0).should be_top
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
                it "returns false" do
         | 
| 49 | 
            +
                  JPath::Pointer.new("store").should_not be_top
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
              
         | 
| 53 | 
            +
              context "#parent" do
         | 
| 54 | 
            +
                it "returns nil" do
         | 
| 55 | 
            +
                  JPath::Pointer.new(0).parent.should be_nil
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                it "returns root" do
         | 
| 58 | 
            +
                  JPath::Pointer.new("store").parent.to_s.should == ""
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
                it "returns root" do
         | 
| 61 | 
            +
                  JPath::Pointer.new("store", "0").parent.to_s.should == 'store'
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              context "#grandparent" do
         | 
| 66 | 
            +
                it "returns nil" do
         | 
| 67 | 
            +
                  JPath::Pointer.new(0).grandparent.should be_nil
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
                it "returns nil" do
         | 
| 70 | 
            +
                  JPath::Pointer.new("store").grandparent.should be_nil
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
                it "returns root" do
         | 
| 73 | 
            +
                  JPath::Pointer.new("store", "0").grandparent.to_s.should == ""
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
              
         | 
| 77 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            require 'jpath'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            HASH = {
         | 
| 4 | 
            +
              "store" => {
         | 
| 5 | 
            +
                "book" => [{
         | 
| 6 | 
            +
                  "category" => "reference",
         | 
| 7 | 
            +
                  "author" => "Nigel Rees",
         | 
| 8 | 
            +
                  "title" => "Sayings of the Century",
         | 
| 9 | 
            +
                  "price" => 8.95
         | 
| 10 | 
            +
                }, {
         | 
| 11 | 
            +
                  "category" => ["fiction"],
         | 
| 12 | 
            +
                  "author" => "Evelyn Waugh",
         | 
| 13 | 
            +
                  "title" => "Sword of Honour",
         | 
| 14 | 
            +
                  "price" => 12.99
         | 
| 15 | 
            +
                }, {
         | 
| 16 | 
            +
                  "category" => "fiction",
         | 
| 17 | 
            +
                  "author" => "Herman Melville",
         | 
| 18 | 
            +
                  "title" => "Moby Dick",
         | 
| 19 | 
            +
                  "isbn" => "0-553-21311-3",
         | 
| 20 | 
            +
                  "price" => 8.95
         | 
| 21 | 
            +
                }, {
         | 
| 22 | 
            +
                  "category" => "fiction",
         | 
| 23 | 
            +
                  "author" => "J. R. R. Tolkien",
         | 
| 24 | 
            +
                  "title" => "The Lord of the Rings",
         | 
| 25 | 
            +
                  "isbn" => "0-395-19395-8",
         | 
| 26 | 
            +
                  "price" => 22.99
         | 
| 27 | 
            +
                }],
         | 
| 28 | 
            +
                "bicycle" => {
         | 
| 29 | 
            +
                  "color" => "red",
         | 
| 30 | 
            +
                  "price" => 19.95
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
              }
         | 
| 33 | 
            +
            }
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: jpath
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: '0.1'
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Alex Serebryakov
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2014-05-12 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: rspec
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - '>='
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :development
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - '>='
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            description: JPath is a Ruby library that allows to execute XPath queries on JSON
         | 
| 28 | 
            +
              documents
         | 
| 29 | 
            +
            email:
         | 
| 30 | 
            +
            - alex@merimond.com
         | 
| 31 | 
            +
            executables: []
         | 
| 32 | 
            +
            extensions: []
         | 
| 33 | 
            +
            extra_rdoc_files: []
         | 
| 34 | 
            +
            files:
         | 
| 35 | 
            +
            - lib/jpath/item.rb
         | 
| 36 | 
            +
            - lib/jpath/parser/formula.rb
         | 
| 37 | 
            +
            - lib/jpath/parser/path.rb
         | 
| 38 | 
            +
            - lib/jpath/parser/step.rb
         | 
| 39 | 
            +
            - lib/jpath/parser.rb
         | 
| 40 | 
            +
            - lib/jpath/pointer.rb
         | 
| 41 | 
            +
            - lib/jpath.rb
         | 
| 42 | 
            +
            - Rakefile
         | 
| 43 | 
            +
            - README.md
         | 
| 44 | 
            +
            - LICENSE
         | 
| 45 | 
            +
            - spec/item_spec.rb
         | 
| 46 | 
            +
            - spec/jpath_spec.rb
         | 
| 47 | 
            +
            - spec/parser_spec.rb
         | 
| 48 | 
            +
            - spec/pointer_spec.rb
         | 
| 49 | 
            +
            - spec/spec_helper.rb
         | 
| 50 | 
            +
            homepage: https://github.com/merimond/jpath
         | 
| 51 | 
            +
            licenses: []
         | 
| 52 | 
            +
            metadata: {}
         | 
| 53 | 
            +
            post_install_message: 
         | 
| 54 | 
            +
            rdoc_options: []
         | 
| 55 | 
            +
            require_paths:
         | 
| 56 | 
            +
            - lib
         | 
| 57 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
              requirements:
         | 
| 59 | 
            +
              - - '>='
         | 
| 60 | 
            +
                - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                  version: '0'
         | 
| 62 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 63 | 
            +
              requirements:
         | 
| 64 | 
            +
              - - '>='
         | 
| 65 | 
            +
                - !ruby/object:Gem::Version
         | 
| 66 | 
            +
                  version: '0'
         | 
| 67 | 
            +
            requirements: []
         | 
| 68 | 
            +
            rubyforge_project: jpath
         | 
| 69 | 
            +
            rubygems_version: 2.0.3
         | 
| 70 | 
            +
            signing_key: 
         | 
| 71 | 
            +
            specification_version: 4
         | 
| 72 | 
            +
            summary: XPath queries for JSON documents
         | 
| 73 | 
            +
            test_files:
         | 
| 74 | 
            +
            - spec/item_spec.rb
         | 
| 75 | 
            +
            - spec/jpath_spec.rb
         | 
| 76 | 
            +
            - spec/parser_spec.rb
         | 
| 77 | 
            +
            - spec/pointer_spec.rb
         | 
| 78 | 
            +
            - spec/spec_helper.rb
         |