updatevalues.go raw

   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