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.
@@ -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.
@@ -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.
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'rspec/core/rake_task'
4
+
5
+ Bundler.require
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ # nothing
11
+ end
12
+
13
+ task :default => :spec
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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