diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..ab37a07 --- /dev/null +++ b/parse.go @@ -0,0 +1,147 @@ +package bond + +import ( + "strings" + "fmt" + "net/url" +) + +func ParseStr(encodedString string, result map[string]interface{}) error { + // build nested map. + var build func(map[string]interface{}, []string, interface{}) error + + build = func(result map[string]interface{}, keys []string, value interface{}) error { + length := len(keys) + // trim '," + key := strings.Trim(keys[0], "'\"") + if length == 1 { + result[key] = value + return nil + } + + // The end is slice. like f[], f[a][] + if keys[1] == "" && length == 2 { + // todo nested slice + if key == "" { + return nil + } + val, ok := result[key] + if !ok { + result[key] = []interface{}{value} + return nil + } + children, ok := val.([]interface{}) + if !ok { + return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) + } + result[key] = append(children, value) + return nil + } + + // The end is slice + map. like f[][a] + if keys[1] == "" && length > 2 && keys[2] != "" { + val, ok := result[key] + if !ok { + result[key] = []interface{}{} + val = result[key] + } + children, ok := val.([]interface{}) + if !ok { + return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) + } + if l := len(children); l > 0 { + if child, ok := children[l-1].(map[string]interface{}); ok { + if _, ok := child[keys[2]]; !ok { + _ = build(child, keys[2:], value) + return nil + } + } + } + child := map[string]interface{}{} + _ = build(child, keys[2:], value) + result[key] = append(children, child) + + return nil + } + + // map. like f[a], f[a][b] + val, ok := result[key] + if !ok { + result[key] = map[string]interface{}{} + val = result[key] + } + children, ok := val.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val) + } + + return build(children, keys[1:], value) + } + + // split encodedString. + parts := strings.Split(encodedString, "&") + for _, part := range parts { + pos := strings.Index(part, "=") + if pos <= 0 { + continue + } + key, err := url.QueryUnescape(part[:pos]) + if err != nil { + return err + } + for key[0] == ' ' { + key = key[1:] + } + if key == "" || key[0] == '[' { + continue + } + value, err := url.QueryUnescape(part[pos+1:]) + if err != nil { + return err + } + + // split into multiple keys + var keys []string + left := 0 + for i, k := range key { + if k == '[' && left == 0 { + left = i + } else if k == ']' { + if left > 0 { + if len(keys) == 0 { + keys = append(keys, key[:left]) + } + keys = append(keys, key[left+1:i]) + left = 0 + if i+1 < len(key) && key[i+1] != '[' { + break + } + } + } + } + if len(keys) == 0 { + keys = append(keys, key) + } + // first key + first := "" + for i, chr := range keys[0] { + if chr == ' ' || chr == '.' || chr == '[' { + first += "_" + } else { + first += string(chr) + } + if chr == '[' { + first += keys[0][i+1:] + break + } + } + keys[0] = first + + // build nested map + if err := build(result, keys, value); err != nil { + return err + } + } + + return nil +}