123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- // mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
- // Copyright 2012-2014, 2018 Charles Banning. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file
- // remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings
- // keys can use dot-notation, keyOld can use wildcard, '*'
- //
- // Computational strategy -
- // Using the key path - []string - traverse a new map[string]interface{} and
- // insert the oldVal as the newVal when we arrive at the end of the path.
- // If the type at the end is nil, then that is newVal
- // If the type at the end is a singleton (string, float64, bool) an array is created.
- // If the type at the end is an array, newVal is just appended.
- // If the type at the end is a map, it is inserted if possible or the map value
- // is converted into an array if necessary.
- package mxj
- import (
- "errors"
- "strings"
- )
- // (Map)NewMap - create a new Map from data in the current Map.
- // 'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey'
- // should be the value for 'newKey' in the returned Map.
- // - 'oldKey' supports dot-notation as described for (Map)ValuesForPath()
- // - 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays
- // - "oldKey" is shorthand for the keypair value "oldKey:oldKey"
- // - "oldKey:" and ":newKey" are invalid keypair values
- // - if 'oldKey' does not exist in the current Map, it is not written to the new Map.
- // "null" is not supported unless it is the current Map.
- // - see newmap_test.go for several syntax examples
- // - mv.NewMap() == mxj.New()
- //
- // NOTE: "examples/partial.go" shows how to create arbitrary sub-docs of an XML doc.
- func (mv Map) NewMap(keypairs ...string) (Map, error) {
- n := make(map[string]interface{}, 0)
- if len(keypairs) == 0 {
- return n, nil
- }
- // loop through the pairs
- var oldKey, newKey string
- var path []string
- for _, v := range keypairs {
- if len(v) == 0 {
- continue // just skip over empty keypair arguments
- }
- // initialize oldKey, newKey and check
- vv := strings.Split(v, ":")
- if len(vv) > 2 {
- return n, errors.New("oldKey:newKey keypair value not valid - " + v)
- }
- if len(vv) == 1 {
- oldKey, newKey = vv[0], vv[0]
- } else {
- oldKey, newKey = vv[0], vv[1]
- }
- strings.TrimSpace(oldKey)
- strings.TrimSpace(newKey)
- if i := strings.Index(newKey, "*"); i > -1 {
- return n, errors.New("newKey value cannot contain wildcard character - " + v)
- }
- if i := strings.Index(newKey, "["); i > -1 {
- return n, errors.New("newKey value cannot contain indexed arrays - " + v)
- }
- if oldKey == "" || newKey == "" {
- return n, errors.New("oldKey or newKey is not specified - " + v)
- }
- // get oldKey value
- oldVal, err := mv.ValuesForPath(oldKey)
- if err != nil {
- return n, err
- }
- if len(oldVal) == 0 {
- continue // oldKey has no value, may not exist in mv
- }
- // break down path
- path = strings.Split(newKey, ".")
- if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
- path = path[:len(path)-1]
- }
- addNewVal(&n, path, oldVal)
- }
- return n, nil
- }
- // navigate 'n' to end of path and add val
- func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
- // newVal - either singleton or array
- var newVal interface{}
- if len(val) == 1 {
- newVal = val[0] // is type interface{}
- } else {
- newVal = interface{}(val)
- }
- // walk to the position of interest, create it if necessary
- m := (*n) // initialize map walker
- var k string // key for m
- lp := len(path) - 1 // when to stop looking
- for i := 0; i < len(path); i++ {
- k = path[i]
- if i == lp {
- break
- }
- var nm map[string]interface{} // holds position of next-map
- switch m[k].(type) {
- case nil: // need a map for next node in path, so go there
- nm = make(map[string]interface{}, 0)
- m[k] = interface{}(nm)
- m = m[k].(map[string]interface{})
- case map[string]interface{}:
- // OK - got somewhere to walk to, go there
- m = m[k].(map[string]interface{})
- case []interface{}:
- // add a map and nm points to new map unless there's already
- // a map in the array, then nm points there
- // The placement of the next value in the array is dependent
- // on the sequence of members - could land on a map or a nil
- // value first. TODO: how to test this.
- a := make([]interface{}, 0)
- var foundmap bool
- for _, vv := range m[k].([]interface{}) {
- switch vv.(type) {
- case nil: // doesn't appear that this occurs, need a test case
- if foundmap { // use the first one in array
- a = append(a, vv)
- continue
- }
- nm = make(map[string]interface{}, 0)
- a = append(a, interface{}(nm))
- foundmap = true
- case map[string]interface{}:
- if foundmap { // use the first one in array
- a = append(a, vv)
- continue
- }
- nm = vv.(map[string]interface{})
- a = append(a, vv)
- foundmap = true
- default:
- a = append(a, vv)
- }
- }
- // no map found in array
- if !foundmap {
- nm = make(map[string]interface{}, 0)
- a = append(a, interface{}(nm))
- }
- m[k] = interface{}(a) // must insert in map
- m = nm
- default: // it's a string, float, bool, etc.
- aa := make([]interface{}, 0)
- nm = make(map[string]interface{}, 0)
- aa = append(aa, m[k], nm)
- m[k] = interface{}(aa)
- m = nm
- }
- }
- // value is nil, array or a singleton of some kind
- // initially m.(type) == map[string]interface{}
- v := m[k]
- switch v.(type) {
- case nil: // initialized
- m[k] = newVal
- case []interface{}:
- a := m[k].([]interface{})
- a = append(a, newVal)
- m[k] = interface{}(a)
- default: // v exists:string, float64, bool, map[string]interface, etc.
- a := make([]interface{}, 0)
- a = append(a, v, newVal)
- m[k] = interface{}(a)
- }
- }
|