Permalink
Browse files

Implementing the string matching operators with wildcards.

  • Loading branch information...
1 parent e26f420 commit 9633de832a8206ea5e7599e00bf4594dedff9deb @jcoglan committed Jul 6, 2009
Showing with 83 additions and 8 deletions.
  1. +18 −1 README.rdoc
  2. +1 −1 Rakefile
  3. +31 −4 lib/siren/json_query.rb
  4. +5 −1 lib/siren/json_query.tt
  5. +11 −1 lib/siren/json_query_nodes.rb
  6. +17 −0 test/test_siren.rb
View
@@ -140,10 +140,27 @@ operators are supported:
* Arithmetic: <tt>+</tt>, <tt>-</tt>, <tt>*</tt>, <tt>/</tt>, <tt>%</tt>
* Comparison: <tt><</tt>, <tt><=</tt>, <tt>></tt>, <tt>>=</tt>
* Equality: <tt>=</tt> for equality, <tt>!=</tt> for inequality
+* String matching: <tt>=</tt> for case-sensitive matching, <tt>~</tt> for case-insensitive
* Logic: <tt>&</tt> for boolean AND, <tt>|</tt> boolean OR (not bitwise)
* Subexpressions are delimited using parentheses <tt>(</tt> and <tt>)</tt>
+=== String matching
+
+The <tt>=</tt> and <tt>~</tt> operators, when used with strings, perform case-sensitive
+and case-insensitive matching respectively. Within a string, <tt>*</tt> matches zero or
+more characters of any type, and <tt>?</tt> matches a single character.
+
+ Siren.query "$[? @ = 'b*']", %w[foo bar baz]
+ #=> ["bar", "baz"]
+
+ Siren.query "$[? @ = 'b?']", %w[foo bar baz]
+ #=> []
+
+ Siren.query "$[? @ ~ 'BA?']", %w[foo bar baz]
+ #=> ["bar", "baz"]
+
+
=== Field access filter
A field access filter selects a named field from an object. Fields are accessed using the
@@ -217,7 +234,7 @@ predicate expression, and return them as a flat array.
]
Siren.query "$..[? @ % 4 = 0]", data
- #=> [8, 4 ,12]
+ #=> [8, 4, 12]
=== Mapping filter
View
@@ -4,7 +4,7 @@ require 'rubygems'
require 'hoe'
require './lib/siren.rb'
-Hoe.new('siren', Siren::VERSION) do |p|
+Hoe.spec('siren') do |p|
# p.rubyforge_name = 'sirenx' # if different than lowercase project name
p.developer('James Coglan', 'jcoglan@googlemail.com')
p.extra_deps = %w[treetop eventful]
@@ -771,18 +771,24 @@ def _nt_comparator
r0 = r4
r0.extend(Comparator)
else
- r5 = _nt_lt
+ r5 = _nt_match
if r5
r0 = r5
r0.extend(Comparator)
else
- r6 = _nt_gt
+ r6 = _nt_lt
if r6
r0 = r6
r0.extend(Comparator)
else
- self.index = i0
- r0 = nil
+ r7 = _nt_gt
+ if r7
+ r0 = r7
+ r0.extend(Comparator)
+ else
+ self.index = i0
+ r0 = nil
+ end
end
end
end
@@ -816,6 +822,27 @@ def _nt_equal
return r0
end
+ def _nt_match
+ start_index = index
+ if node_cache[:match].has_key?(index)
+ cached = node_cache[:match][index]
+ @index = cached.interval.end if cached
+ return cached
+ end
+
+ if input.index("~", index) == index
+ r0 = instantiate_node(Match,input, index...(index + 1))
+ @index += 1
+ else
+ terminal_parse_failure("~")
+ r0 = nil
+ end
+
+ node_cache[:match][start_index] = r0
+
+ return r0
+ end
+
def _nt_not_equal
start_index = index
if node_cache[:not_equal].has_key?(index)
@@ -57,13 +57,17 @@ module Siren
end
rule comparator
- (not_equal / lte / gte / equal / lt / gt) <Comparator>
+ (not_equal / lte / gte / equal / match / lt / gt) <Comparator>
end
rule equal
"=" <Equal>
end
+ rule match
+ "~" <Match>
+ end
+
rule not_equal
"!=" <NotEqual>
end
@@ -245,7 +245,17 @@ module Comparator
module Equal
def value(expr1, expr2)
- expr1 == expr2
+ return expr1 == expr2 unless expr2.respond_to?(:gsub)
+ expr2 = expr2.gsub('*', '.*').gsub('?', '.')
+ expr1 =~ %r{^#{expr2}$}
+ end
+ end
+
+ module Match
+ def value(expr1, expr2)
+ return expr1 == expr2 unless expr2.respond_to?(:gsub)
+ expr2 = expr2.gsub('*', '.*').gsub('?', '.')
+ expr1 =~ %r{^#{expr2}$}i
end
end
View
@@ -129,6 +129,23 @@ def test_filters_with_cross_references
{:key => [9,5,7], :val => 1..8})
end
+ def test_wildcards
+ assert_equal ["Sayings of the Century", "The Lord of the Rings"],
+ Siren.query("$.store.book[? @.title = '* of the *'][= title]", fixtures(:store))
+
+ assert_equal [],
+ Siren.query("$.store.book[? @.title = '* Of the *'][= title]", fixtures(:store))
+
+ assert_equal ["Sayings of the Century", "The Lord of the Rings"],
+ Siren.query("$.store.book[? @.title ~ '* Of the *'][= title]", fixtures(:store))
+
+ assert_equal ["Sayings of the Century"],
+ Siren.query("$.store.book[? @.title = '??????? of the ???????'][= title]", fixtures(:store))
+
+ assert_equal [],
+ Siren.query("$.store.book[? @.title = '? of the ?'][= title]", fixtures(:store))
+ end
+
def test_bookstore
assert_equal "The Lord of the Rings",
Siren.query("$.store.book[@.length - 1]", fixtures(:store))["title"]

0 comments on commit 9633de8

Please sign in to comment.