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