...

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

Documentation: github.com/clbanning/mxj/v2

     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  

View as plain text