jpath 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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