Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
502 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
This is a simple search query parser. | ||
Search query is usually get from a browser's input box, and used for searching in a database. After receive the search query, we need to do some basic process on it and transform it to a structured variable. This package is for doing the job. | ||
Syntax of search query: | ||
1. All queries have a key and one or more values. | ||
2. Key and values are separate by a colon (":"). And there's no space before and after the colon. Eg. key:value | ||
3. If a key or value contains punctuations or spaces, they should be placed in quotation marks. Eg. from:"one@example.com" | ||
4. Single quotes could placed within double quotes, and double quotes could placed within single quotes. Eg. "'key'":'"value"' | ||
5. One key could have more than one values which are separate by comma. Eg. key:value1,value2,"value3" | ||
6. Put a minus before key means negative. Eg. -name:"not this" | ||
7. If a query does not contains a key, it's key is supposed to space. Eg. "a value not contains a key" | ||
After process by function Parse, the search query will be transformd to a type of "Nodes" variable. | ||
Examples: | ||
key:value -> &[{key [value] false}] | ||
key1:value1 key2:value2 -> &[{key1 [value1] false} {key2 [value2] false}] | ||
key:v1,v2 -> &[{key [v1 v2] false}] | ||
"key has space":v -> &[{key has space [v] false}] | ||
key:"value's space" -> &[{key [value's space] false}] | ||
-key:"negative value" -> &[{key [negative value] true}] | ||
"only value" -> &[{ [only value] false}] | ||
two values -> &[{ [two] false} { [values] false}] | ||
k1:v1 k1:v1,v2 -> &[{k1 [v1] false} {k1 [v1 v2] false}] | ||
*/ | ||
package queryparser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
package queryparser | ||
|
||
import "fmt" | ||
import "strings" | ||
import "unicode" | ||
|
||
|
||
/* | ||
scan state. | ||
out means out of key or value. | ||
eg. "abc":123 | ||
when the scanned char is a、b、c or 1、2、3,the state is in | ||
when the scanned char is quote, colon or comma,the state is out | ||
*/ | ||
type scanState bool | ||
const s_out scanState = false | ||
const s_in scanState = true | ||
|
||
type valueType bool | ||
const v_key valueType = true | ||
const v_value valueType = false | ||
|
||
type quoteType string | ||
const q_single quoteType = `'` | ||
const q_double quoteType = `"` | ||
const q_none quoteType = `` | ||
|
||
|
||
type Node struct { | ||
Key string | ||
Values []string | ||
Negative bool | ||
} | ||
|
||
|
||
type Nodes []Node | ||
|
||
|
||
func (nodes *Nodes) append(node Node) { | ||
if len(node.Values) > 0 { | ||
var n Node | ||
n.Key = node.Key | ||
n.Negative = node.Negative | ||
n.Values = reduceDupString(node.Values) | ||
if nodes != nil { | ||
*nodes = append(*nodes, n) | ||
} | ||
} | ||
} | ||
|
||
|
||
type InvalidCharError struct { | ||
Char string | ||
Pos int | ||
Msg string | ||
} | ||
|
||
|
||
func (e *InvalidCharError) Error() string { | ||
return fmt.Sprintf("%s: \"%s\" at position %d", e.Msg, e.Char, e.Pos) | ||
} | ||
|
||
|
||
func reduceDupString(s []string) (n []string) { | ||
var in = func(all []string, element string) bool { | ||
for _, i := range all { | ||
if i == element { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
for _, i := range s { | ||
if !in(n, i) { | ||
n = append(n, i) | ||
} | ||
} | ||
|
||
return | ||
} | ||
|
||
|
||
func isSpecialChar(s string) (t bool, err error) { | ||
|
||
r := []rune(s) | ||
|
||
l := len(r) | ||
if l == 0 { | ||
return | ||
} | ||
if l != 1 { | ||
err = fmt.Errorf("string must contains only one character, got %s", s) | ||
return | ||
} | ||
|
||
if unicode.IsPunct(r[0]) || unicode.IsSymbol(r[0]) || unicode.IsSpace(r[0]) || unicode.IsControl(r[0]) { | ||
t = true | ||
return | ||
} | ||
return | ||
} | ||
|
||
|
||
func Parse(s string) (nodes *Nodes, err error) { | ||
|
||
defer func() { | ||
if e := recover(); e != nil { | ||
err = fmt.Errorf("PANIC: %s", e) | ||
return | ||
} | ||
}() | ||
|
||
var phrase []string | ||
var values []string | ||
var node Node | ||
nodes = &Nodes{} | ||
state := s_out | ||
vType := v_key | ||
quote := q_none | ||
|
||
for pos, item := range []rune(s) { | ||
|
||
c := string(item) | ||
|
||
if state == s_out { | ||
|
||
switch c { | ||
|
||
case `"`: | ||
quote = q_double | ||
state = s_in | ||
|
||
case `'`: | ||
quote = q_single | ||
state = s_in | ||
|
||
case `,`: continue | ||
case `:`: continue | ||
|
||
case `-`: | ||
node.Negative = true | ||
|
||
case ` `: | ||
if len(values) > 0 { | ||
node.Values = values | ||
values = []string{} | ||
} else if len(node.Key) > 0 { | ||
node.Values = []string{node.Key} | ||
node.Key = "" | ||
} | ||
if len(node.Values) > 0 { | ||
nodes.append(node) | ||
} | ||
node = Node{} | ||
vType = v_key | ||
quote = q_none | ||
|
||
default: | ||
special, e := isSpecialChar(c) | ||
if e != nil { | ||
err = e | ||
return | ||
} | ||
if special { | ||
err = &InvalidCharError{c, pos, "Invalid character"} | ||
return | ||
} | ||
phrase = append(phrase, c) | ||
state = s_in | ||
|
||
} // end of switch | ||
|
||
} else { | ||
|
||
switch c { | ||
|
||
case `"`: fallthrough | ||
case `'`: | ||
if quote == q_none { | ||
quote = quoteType(c) | ||
if len(phrase) == 0 { | ||
continue | ||
} | ||
switch phrase[len(phrase) - 1] { | ||
case `,`: | ||
vType = v_value | ||
values = append(values, strings.Join(phrase, "")) | ||
phrase = []string{} | ||
case `:`: | ||
vType = v_value | ||
default: | ||
err = &InvalidCharError{c, pos, "Invalid character"} | ||
return | ||
} | ||
} else if quote == quoteType(c) { | ||
if vType == v_value { | ||
values = append(values, strings.Join(phrase, "")) | ||
phrase = []string{} | ||
state = s_out | ||
} | ||
quote = q_none | ||
} else { | ||
phrase = append(phrase, c) | ||
} | ||
|
||
case `,`: | ||
if vType == v_key { | ||
if quote == q_none { | ||
vType = v_value | ||
values = append(values, strings.Join(phrase, "")) | ||
phrase = []string{} | ||
} else { | ||
phrase = append(phrase, c) | ||
} | ||
} else { | ||
if quote == q_none { | ||
values = append(values, strings.Join(phrase, "")) | ||
phrase = []string{} | ||
state = s_out | ||
} else { | ||
phrase = append(phrase, c) | ||
} | ||
} | ||
|
||
case `:`: | ||
if quote != q_none { | ||
phrase = append(phrase, c) | ||
} else { | ||
if vType == v_key { | ||
vType = v_value | ||
node.Key = strings.Join(phrase, "") | ||
phrase = []string{} | ||
state = s_out | ||
} else { | ||
err = &InvalidCharError{c, pos, "Cannot appear more than once"} | ||
return | ||
} | ||
} | ||
|
||
case `-`: | ||
phrase = append(phrase, c) | ||
|
||
case ` `: | ||
if quote != q_none { | ||
phrase = append(phrase, c) | ||
} else { | ||
|
||
if vType == v_key { | ||
node.Values = []string{strings.Join(phrase, "")} | ||
} else { | ||
if len(phrase) > 0 { | ||
node.Values = append(values, strings.Join(phrase, "")) | ||
} else { | ||
node.Values = values | ||
} | ||
} | ||
|
||
if (vType == v_key && node.Key == "") || vType == v_value { | ||
nodes.append(node) | ||
node = Node{} | ||
} | ||
|
||
state = s_out | ||
vType = v_key | ||
values = []string{} | ||
phrase = []string{} | ||
} | ||
|
||
default: | ||
if quote == q_none { | ||
special, e := isSpecialChar(c) | ||
if e != nil { | ||
err = e | ||
return | ||
} | ||
if special { | ||
err = &InvalidCharError{c, pos, "Invalid character"} | ||
return | ||
} | ||
} | ||
phrase = append(phrase, c) | ||
|
||
} // end of switch | ||
} // end of else | ||
|
||
// DEBUG | ||
//fmt.Println("c:", c) | ||
//fmt.Println("phrase:", phrase) | ||
//fmt.Println("values:", values) | ||
//fmt.Println("node", node) | ||
//fmt.Println("nodes", nodes) | ||
//if state { | ||
// fmt.Println("state:", "in") | ||
//} else { | ||
// fmt.Println("state:", "out") | ||
//} | ||
//if vType { | ||
// fmt.Println("vType:", "key") | ||
//} else { | ||
// fmt.Println("vType:", "value") | ||
//} | ||
//fmt.Println("quote:", quote) | ||
//fmt.Println("=====") | ||
|
||
} // end of for | ||
|
||
if len(phrase) > 0 { | ||
values = append(values, strings.Join(phrase, "")) | ||
} | ||
|
||
if len(values) > 0 { | ||
node.Values = values | ||
nodes.append(node) | ||
} | ||
|
||
return | ||
} | ||
|
Oops, something went wrong.