diff --git a/parse.go b/parse.go index db58647..d218550 100644 --- a/parse.go +++ b/parse.go @@ -7,6 +7,10 @@ import ( "sort" "strconv" "strings" + "fmt" + "regexp" + "go/types" + "go/token" ) type Applicator interface { @@ -114,7 +118,7 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { rval, err := applyNext(w.NextNode, tv[key]) // Don't add anything that causes an error or returns nil. if err == nil || rval != nil { - ret = append(ret, rval) + ret = flattenAppend(ret, rval) } } return ret, nil @@ -124,7 +128,7 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { rval, err := applyNext(w.NextNode, val) // Don't add anything that causes an error or returns nil. if err == nil || rval != nil { - ret = append(ret, rval) + ret = flattenAppend(ret, rval) } } return ret, nil @@ -134,6 +138,52 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { } } +type WildCardFilterSelection struct { + RootNode + Key string +} + +func (w *WildCardFilterSelection) Apply(v interface{}) (interface{}, error) { + arv, ok := v.([]interface{}) + if !ok { + return v, ArrayTypeError + } + var ret []interface{} + for _, val := range arv { + _, ok := val.(map[string]interface{}) + if !ok { + return v, MapTypeError + } + + re, err := regexp.Compile(`[\S]+`) + if err != nil { + return v, err + } + ops := re.FindAllString(w.Key, -1) + wa, _ := Parse(strings.Replace(ops[0], "@", "$", 1)) + subv, _ := wa.Apply(val) + if subv == nil { + continue + } + + if len(ops) == 3 { + isOk, _ := cmp_any(subv, ops[2], ops[1]) + if !isOk { + continue + } + } + + rval, err := applyNext(w.NextNode, val) + + // Don't add anything that causes an error or returns nil. + if err == nil || rval != nil { + ret = append(ret, rval) + } + } + return ret, nil +} + + // DescentSelection is a filter that recursively descends applying it's NextNode and // corrlating the results. type DescentSelection struct { @@ -291,7 +341,7 @@ func getNode(s string) (node, string, error) { case "[.": return &DescentSelection{}, rs, nil case "[?", "[(": - return nil, rs, NotSupportedError + return &WildCardFilterSelection{Key: s[3 : n-1]}, rs, nil default: // Assume it's a array index otherwise. i, err := strconv.Atoi(s[1:n]) if err != nil { @@ -322,3 +372,40 @@ func Parse(s string) (Applicator, error) { } return &rt, nil } + + +func cmp_any(obj1, obj2 interface{}, op string) (bool, error) { + switch op { + case "<", "<=", "==", ">=", ">", "!=": + default: + return false, fmt.Errorf("op should only be <, <=, ==, !=, >= and >") + } + var sobj1 string + switch obj1.(type) { + case string: + sobj1 = fmt.Sprintf("\"%v\"", obj1) + default: + sobj1 = fmt.Sprintf("%v", obj1) + } + var sobj2 string + switch obj1.(type) { + case string: + sobj2 = fmt.Sprintf("\"%v\"", obj2) + default: + sobj2 = fmt.Sprintf("%v", obj2) + } + + exp := fmt.Sprintf("%v %s %v", sobj1, op, sobj2) + fset := token.NewFileSet() + res, err := types.Eval(fset, nil, 0, exp) + if err != nil { + return false, err + } + if res.IsValue() == false || (res.Value.String() != "false" && res.Value.String() != "true") { + return false, fmt.Errorf("result should only be true or false") + } + if res.Value.String() == "true" { + return true, nil + } + return false, nil +} \ No newline at end of file diff --git a/parse_test.go b/parse_test.go index 210f8e8..805b837 100644 --- a/parse_test.go +++ b/parse_test.go @@ -86,6 +86,14 @@ func isSameWildcardSelectionNode(n *WildCardSelection, m node) bool { return false } } +func isSameWildcardFilterSelectionNode(n *WildCardFilterSelection, m node) bool { + switch mv := m.(type) { + case *WildCardFilterSelection: + return isSameNode(n.NextNode, mv.NextNode) + default: + return false + } +} func isSameDescentSelectionNode(n *DescentSelection, m node) bool { switch mv := m.(type) { case *DescentSelection: @@ -110,6 +118,8 @@ func isSameNode(n, m node) bool { return isSameDescentSelectionNode(nv, m) case *WildCardSelection: return isSameWildcardSelectionNode(nv, m) + case *WildCardFilterSelection: + return isSameWildcardFilterSelectionNode(nv, m) default: return false } @@ -126,8 +136,8 @@ func TestGetNode(t *testing.T) { {t: `[10]`, n: &ArraySelection{Key: 10}}, {t: `[*]`, n: &WildCardSelection{}}, {t: `[..]`, n: &DescentSelection{}}, - {t: `[?(@.lenght())]`, n: nil, err: NotSupportedError}, - {t: `[(@.foo)]`, n: nil, err: NotSupportedError}, + {t: `[?(@.lenght())]`, n: &WildCardFilterSelection{Key: "@.lenght()"}}, + {t: `[(@.foo)]`, n: &WildCardFilterSelection{Key: "@.foo"}}, {t: `[0:10:2]`, n: nil, err: SyntaxError}, } for i, test := range testcases { @@ -257,6 +267,37 @@ func TestParse(t *testing.T) { t: "..author..", expected: []interface{}{"Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"}, }, + { + t: "store.book[?(@.price < 10)]", + expected: []interface{}{ map[string]interface{}{ + "category": "reference", + "category.sub": "quotes", + "author": "Nigel Rees", + "title": "Saying of the Century", + "price": 8.95, + }, map[string]interface{}{ + "category" : "fiction", + "author" : "Herman Melville", + "title" : "Moby Dick", + "isbn" : "0-553-21311-3", + "price" : 8.99, + }, + + }, + }, + { + t: `store.book[?(@.category == reference)]`, + expected: []interface{}{ map[string]interface{}{ + "category": "reference", + "category.sub": "quotes", + "author": "Nigel Rees", + "title": "Saying of the Century", + "price": 8.95, + }, + + }, + }, + } for i, test := range testcases { a, err := Parse(test.t)