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
18package mxj
19
20import (
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.
38func (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
96func 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