forked from clbanning/mxj
-
Notifications
You must be signed in to change notification settings - Fork 0
/
newmap.go
184 lines (171 loc) · 5.72 KB
/
newmap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2014, 2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings
// keys can use dot-notation, keyOld can use wildcard, '*'
//
// Computational strategy -
// Using the key path - []string - traverse a new map[string]interface{} and
// insert the oldVal as the newVal when we arrive at the end of the path.
// If the type at the end is nil, then that is newVal
// If the type at the end is a singleton (string, float64, bool) an array is created.
// If the type at the end is an array, newVal is just appended.
// If the type at the end is a map, it is inserted if possible or the map value
// is converted into an array if necessary.
package mxj
import (
"errors"
"strings"
)
// (Map)NewMap - create a new Map from data in the current Map.
// 'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey'
// should be the value for 'newKey' in the returned Map.
// - 'oldKey' supports dot-notation as described for (Map)ValuesForPath()
// - 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays
// - "oldKey" is shorthand for the keypair value "oldKey:oldKey"
// - "oldKey:" and ":newKey" are invalid keypair values
// - if 'oldKey' does not exist in the current Map, it is not written to the new Map.
// "null" is not supported unless it is the current Map.
// - see newmap_test.go for several syntax examples
// - mv.NewMap() == mxj.New()
//
// NOTE: "examples/partial.go" shows how to create arbitrary sub-docs of an XML doc.
func (mv Map) NewMap(keypairs ...string) (Map, error) {
n := make(map[string]interface{}, 0)
if len(keypairs) == 0 {
return n, nil
}
// loop through the pairs
var oldKey, newKey string
var path []string
for _, v := range keypairs {
if len(v) == 0 {
continue // just skip over empty keypair arguments
}
// initialize oldKey, newKey and check
vv := strings.Split(v, ":")
if len(vv) > 2 {
return n, errors.New("oldKey:newKey keypair value not valid - " + v)
}
if len(vv) == 1 {
oldKey, newKey = vv[0], vv[0]
} else {
oldKey, newKey = vv[0], vv[1]
}
strings.TrimSpace(oldKey)
strings.TrimSpace(newKey)
if i := strings.Index(newKey, "*"); i > -1 {
return n, errors.New("newKey value cannot contain wildcard character - " + v)
}
if i := strings.Index(newKey, "["); i > -1 {
return n, errors.New("newKey value cannot contain indexed arrays - " + v)
}
if oldKey == "" || newKey == "" {
return n, errors.New("oldKey or newKey is not specified - " + v)
}
// get oldKey value
oldVal, err := mv.ValuesForPath(oldKey)
if err != nil {
return n, err
}
if len(oldVal) == 0 {
continue // oldKey has no value, may not exist in mv
}
// break down path
path = strings.Split(newKey, ".")
if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
path = path[:len(path)-1]
}
addNewVal(&n, path, oldVal)
}
return n, nil
}
// navigate 'n' to end of path and add val
func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
// newVal - either singleton or array
var newVal interface{}
if len(val) == 1 {
newVal = val[0] // is type interface{}
} else {
newVal = interface{}(val)
}
// walk to the position of interest, create it if necessary
m := (*n) // initialize map walker
var k string // key for m
lp := len(path) - 1 // when to stop looking
for i := 0; i < len(path); i++ {
k = path[i]
if i == lp {
break
}
var nm map[string]interface{} // holds position of next-map
switch m[k].(type) {
case nil: // need a map for next node in path, so go there
nm = make(map[string]interface{}, 0)
m[k] = interface{}(nm)
m = m[k].(map[string]interface{})
case map[string]interface{}:
// OK - got somewhere to walk to, go there
m = m[k].(map[string]interface{})
case []interface{}:
// add a map and nm points to new map unless there's already
// a map in the array, then nm points there
// The placement of the next value in the array is dependent
// on the sequence of members - could land on a map or a nil
// value first. TODO: how to test this.
a := make([]interface{}, 0)
var foundmap bool
for _, vv := range m[k].([]interface{}) {
switch vv.(type) {
case nil: // doesn't appear that this occurs, need a test case
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
foundmap = true
case map[string]interface{}:
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = vv.(map[string]interface{})
a = append(a, vv)
foundmap = true
default:
a = append(a, vv)
}
}
// no map found in array
if !foundmap {
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
}
m[k] = interface{}(a) // must insert in map
m = nm
default: // it's a string, float, bool, etc.
aa := make([]interface{}, 0)
nm = make(map[string]interface{}, 0)
aa = append(aa, m[k], nm)
m[k] = interface{}(aa)
m = nm
}
}
// value is nil, array or a singleton of some kind
// initially m.(type) == map[string]interface{}
v := m[k]
switch v.(type) {
case nil: // initialized
m[k] = newVal
case []interface{}:
a := m[k].([]interface{})
a = append(a, newVal)
m[k] = interface{}(a)
default: // v exists:string, float64, bool, map[string]interface, etc.
a := make([]interface{}, 0)
a = append(a, v, newVal)
m[k] = interface{}(a)
}
}