...

Source file src/github.com/clbanning/mxj/v2/updatevalues.go

Documentation: github.com/clbanning/mxj/v2

     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  

View as plain text