1 // Copyright 2012-2014, 2017 Charles Banning. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file
4 5 // updatevalues.go - modify a value based on path and possibly sub-keys
6 // TODO(clb): handle simple elements with attributes and NewMapXmlSeq Map values.
7 8 package mxj
9 10 import (
11 "fmt"
12 "strconv"
13 "strings"
14 )
15 16 // Update value based on path and possible sub-key values.
17 // A count of the number of values changed and any error are returned.
18 // If the count == 0, then no path (and subkeys) matched.
19 // 'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified
20 // or a string value "key:value[:type]" where type is "bool" or "num" to cast the value.
21 // 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
22 // NOTE: 'path' spec does not currently support indexed array references.
23 // 'subkeys' are "key:value[:type]" entries that must match for path node
24 // - For attributes prefix the label with the attribute prefix character, by default a
25 // hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.)
26 // - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
27 // - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
28 // exclusion critera - e.g., "!author:William T. Gaddis".
29 //
30 // NOTES:
31 // 1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value.
32 // 2. Values in Maps created using NewMapXmlSeq are map[string]interface{} values with a "#text" key.
33 // 3. If values in 'newVal' or 'subkeys' args contain ":", use SetFieldSeparator to an unused symbol,
34 // perhaps "|".
35 func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) {
36 m := map[string]interface{}(mv)
37 38 // extract the subkeys
39 var subKeyMap map[string]interface{}
40 if len(subkeys) > 0 {
41 var err error
42 subKeyMap, err = getSubKeyMap(subkeys...)
43 if err != nil {
44 return 0, err
45 }
46 }
47 48 // extract key and value from newVal
49 var key string
50 var val interface{}
51 switch newVal.(type) {
52 case map[string]interface{}, Map:
53 switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec)
54 case Map:
55 newVal = newVal.(Map).Old()
56 }
57 if len(newVal.(map[string]interface{})) != 1 {
58 return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal)
59 }
60 for key, val = range newVal.(map[string]interface{}) {
61 }
62 case string: // split it as a key:value pair
63 ss := strings.Split(newVal.(string), fieldSep)
64 n := len(ss)
65 if n < 2 || n > 3 {
66 return 0, fmt.Errorf("unknown newVal spec - %+v", newVal)
67 }
68 key = ss[0]
69 if n == 2 {
70 val = interface{}(ss[1])
71 } else if n == 3 {
72 switch ss[2] {
73 case "bool", "boolean":
74 nv, err := strconv.ParseBool(ss[1])
75 if err != nil {
76 return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal)
77 }
78 val = interface{}(nv)
79 case "num", "numeric", "float", "int":
80 nv, err := strconv.ParseFloat(ss[1], 64)
81 if err != nil {
82 return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal)
83 }
84 val = interface{}(nv)
85 default:
86 return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal)
87 }
88 }
89 default:
90 return 0, fmt.Errorf("invalid newVal type - %+v", newVal)
91 }
92 93 // parse path
94 keys := strings.Split(path, ".")
95 96 var count int
97 updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count)
98 99 return count, nil
100 }
101 102 // navigate the path
103 func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) {
104 // ----- at end node: looking at possible node to get 'key' ----
105 if len(keys) == 1 {
106 updateValue(key, value, m, keys[0], subkeys, cnt)
107 return
108 }
109 110 // ----- here we are navigating the path thru the penultimate node --------
111 // key of interest is keys[0] - the next in the path
112 switch keys[0] {
113 case "*": // wildcard - scan all values
114 switch m.(type) {
115 case map[string]interface{}:
116 for _, v := range m.(map[string]interface{}) {
117 updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
118 }
119 case []interface{}:
120 for _, v := range m.([]interface{}) {
121 switch v.(type) {
122 // flatten out a list of maps - keys are processed
123 case map[string]interface{}:
124 for _, vv := range v.(map[string]interface{}) {
125 updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
126 }
127 default:
128 updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
129 }
130 }
131 }
132 default: // key - must be map[string]interface{}
133 switch m.(type) {
134 case map[string]interface{}:
135 if v, ok := m.(map[string]interface{})[keys[0]]; ok {
136 updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
137 }
138 case []interface{}: // may be buried in list
139 for _, v := range m.([]interface{}) {
140 switch v.(type) {
141 case map[string]interface{}:
142 if vv, ok := v.(map[string]interface{})[keys[0]]; ok {
143 updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
144 }
145 }
146 }
147 }
148 }
149 }
150 151 // change value if key and subkeys are present
152 func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) {
153 // there are two possible options for the value of 'keys0': map[string]interface, []interface{}
154 // and 'key' is a key in the map or is a key in a map in a list.
155 switch m.(type) {
156 case map[string]interface{}: // gotta have the last key
157 if keys0 == "*" {
158 for k := range m.(map[string]interface{}) {
159 updateValue(key, value, m, k, subkeys, cnt)
160 }
161 return
162 }
163 endVal, _ := m.(map[string]interface{})[keys0]
164 165 // if newV key is the end of path, replace the value for path-end
166 // may be []interface{} - means replace just an entry w/ subkeys
167 // otherwise replace the keys0 value if subkeys are there
168 // NOTE: this will replace the subkeys, also
169 if key == keys0 {
170 switch endVal.(type) {
171 case map[string]interface{}:
172 if hasSubKeys(m, subkeys) {
173 (m.(map[string]interface{}))[keys0] = value
174 (*cnt)++
175 }
176 case []interface{}:
177 // without subkeys can't select list member to modify
178 // so key:value spec is it ...
179 if hasSubKeys(m, subkeys) {
180 (m.(map[string]interface{}))[keys0] = value
181 (*cnt)++
182 break
183 }
184 nv := make([]interface{}, 0)
185 var valmodified bool
186 for _, v := range endVal.([]interface{}) {
187 // check entry subkeys
188 if hasSubKeys(v, subkeys) {
189 // replace v with value
190 nv = append(nv, value)
191 valmodified = true
192 (*cnt)++
193 continue
194 }
195 nv = append(nv, v)
196 }
197 if valmodified {
198 (m.(map[string]interface{}))[keys0] = interface{}(nv)
199 }
200 default: // anything else is a strict replacement
201 if hasSubKeys(m, subkeys) {
202 (m.(map[string]interface{}))[keys0] = value
203 (*cnt)++
204 }
205 }
206 return
207 }
208 209 // so value is for an element of endVal
210 // if endVal is a map then 'key' must be there w/ subkeys
211 // if endVal is a list then 'key' must be in a list member w/ subkeys
212 switch endVal.(type) {
213 case map[string]interface{}:
214 if !hasSubKeys(endVal, subkeys) {
215 return
216 }
217 if _, ok := (endVal.(map[string]interface{}))[key]; ok {
218 (endVal.(map[string]interface{}))[key] = value
219 (*cnt)++
220 }
221 case []interface{}: // keys0 points to a list, check subkeys
222 for _, v := range endVal.([]interface{}) {
223 // got to be a map so we can replace value for 'key'
224 vv, vok := v.(map[string]interface{})
225 if !vok {
226 continue
227 }
228 if _, ok := vv[key]; !ok {
229 continue
230 }
231 if !hasSubKeys(vv, subkeys) {
232 continue
233 }
234 vv[key] = value
235 (*cnt)++
236 }
237 }
238 case []interface{}: // key may be in a list member
239 // don't need to handle keys0 == "*"; we're looking at everything, anyway.
240 for _, v := range m.([]interface{}) {
241 // only map values - we're looking for 'key'
242 mm, ok := v.(map[string]interface{})
243 if !ok {
244 continue
245 }
246 if _, ok := mm[key]; !ok {
247 continue
248 }
249 if !hasSubKeys(mm, subkeys) {
250 continue
251 }
252 mm[key] = value
253 (*cnt)++
254 }
255 }
256 257 // return
258 }
259