updatevalues.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. // updatevalues.go - modify a value based on path and possibly sub-keys
  5. // TODO(clb): handle simple elements with attributes and NewMapXmlSeq Map values.
  6. package mxj
  7. import (
  8. "fmt"
  9. "strconv"
  10. "strings"
  11. )
  12. // Update value based on path and possible sub-key values.
  13. // A count of the number of values changed and any error are returned.
  14. // If the count == 0, then no path (and subkeys) matched.
  15. // 'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified
  16. // or a string value "key:value[:type]" where type is "bool" or "num" to cast the value.
  17. // 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
  18. // NOTE: 'path' spec does not currently support indexed array references.
  19. // 'subkeys' are "key:value[:type]" entries that must match for path node
  20. // - For attributes prefix the label with the attribute prefix character, by default a
  21. // hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.)
  22. // - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
  23. // - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
  24. // exclusion critera - e.g., "!author:William T. Gaddis".
  25. //
  26. // NOTES:
  27. // 1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value.
  28. // 2. Values in Maps created using NewMapXmlSeq are map[string]interface{} values with a "#text" key.
  29. // 3. If values in 'newVal' or 'subkeys' args contain ":", use SetFieldSeparator to an unused symbol,
  30. // perhaps "|".
  31. func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) {
  32. m := map[string]interface{}(mv)
  33. // extract the subkeys
  34. var subKeyMap map[string]interface{}
  35. if len(subkeys) > 0 {
  36. var err error
  37. subKeyMap, err = getSubKeyMap(subkeys...)
  38. if err != nil {
  39. return 0, err
  40. }
  41. }
  42. // extract key and value from newVal
  43. var key string
  44. var val interface{}
  45. switch newVal.(type) {
  46. case map[string]interface{}, Map:
  47. switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec)
  48. case Map:
  49. newVal = newVal.(Map).Old()
  50. }
  51. if len(newVal.(map[string]interface{})) != 1 {
  52. return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal)
  53. }
  54. for key, val = range newVal.(map[string]interface{}) {
  55. }
  56. case string: // split it as a key:value pair
  57. ss := strings.Split(newVal.(string), fieldSep)
  58. n := len(ss)
  59. if n < 2 || n > 3 {
  60. return 0, fmt.Errorf("unknown newVal spec - %+v", newVal)
  61. }
  62. key = ss[0]
  63. if n == 2 {
  64. val = interface{}(ss[1])
  65. } else if n == 3 {
  66. switch ss[2] {
  67. case "bool", "boolean":
  68. nv, err := strconv.ParseBool(ss[1])
  69. if err != nil {
  70. return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal)
  71. }
  72. val = interface{}(nv)
  73. case "num", "numeric", "float", "int":
  74. nv, err := strconv.ParseFloat(ss[1], 64)
  75. if err != nil {
  76. return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal)
  77. }
  78. val = interface{}(nv)
  79. default:
  80. return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal)
  81. }
  82. }
  83. default:
  84. return 0, fmt.Errorf("invalid newVal type - %+v", newVal)
  85. }
  86. // parse path
  87. keys := strings.Split(path, ".")
  88. var count int
  89. updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count)
  90. return count, nil
  91. }
  92. // navigate the path
  93. func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) {
  94. // ----- at end node: looking at possible node to get 'key' ----
  95. if len(keys) == 1 {
  96. updateValue(key, value, m, keys[0], subkeys, cnt)
  97. return
  98. }
  99. // ----- here we are navigating the path thru the penultimate node --------
  100. // key of interest is keys[0] - the next in the path
  101. switch keys[0] {
  102. case "*": // wildcard - scan all values
  103. switch m.(type) {
  104. case map[string]interface{}:
  105. for _, v := range m.(map[string]interface{}) {
  106. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  107. }
  108. case []interface{}:
  109. for _, v := range m.([]interface{}) {
  110. switch v.(type) {
  111. // flatten out a list of maps - keys are processed
  112. case map[string]interface{}:
  113. for _, vv := range v.(map[string]interface{}) {
  114. updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
  115. }
  116. default:
  117. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  118. }
  119. }
  120. }
  121. default: // key - must be map[string]interface{}
  122. switch m.(type) {
  123. case map[string]interface{}:
  124. if v, ok := m.(map[string]interface{})[keys[0]]; ok {
  125. updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
  126. }
  127. case []interface{}: // may be buried in list
  128. for _, v := range m.([]interface{}) {
  129. switch v.(type) {
  130. case map[string]interface{}:
  131. if vv, ok := v.(map[string]interface{})[keys[0]]; ok {
  132. updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
  133. }
  134. }
  135. }
  136. }
  137. }
  138. }
  139. // change value if key and subkeys are present
  140. func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) {
  141. // there are two possible options for the value of 'keys0': map[string]interface, []interface{}
  142. // and 'key' is a key in the map or is a key in a map in a list.
  143. switch m.(type) {
  144. case map[string]interface{}: // gotta have the last key
  145. if keys0 == "*" {
  146. for k := range m.(map[string]interface{}) {
  147. updateValue(key, value, m, k, subkeys, cnt)
  148. }
  149. return
  150. }
  151. endVal, _ := m.(map[string]interface{})[keys0]
  152. // if newV key is the end of path, replace the value for path-end
  153. // may be []interface{} - means replace just an entry w/ subkeys
  154. // otherwise replace the keys0 value if subkeys are there
  155. // NOTE: this will replace the subkeys, also
  156. if key == keys0 {
  157. switch endVal.(type) {
  158. case map[string]interface{}:
  159. if hasSubKeys(m, subkeys) {
  160. (m.(map[string]interface{}))[keys0] = value
  161. (*cnt)++
  162. }
  163. case []interface{}:
  164. // without subkeys can't select list member to modify
  165. // so key:value spec is it ...
  166. if hasSubKeys(m, subkeys) {
  167. (m.(map[string]interface{}))[keys0] = value
  168. (*cnt)++
  169. break
  170. }
  171. nv := make([]interface{}, 0)
  172. var valmodified bool
  173. for _, v := range endVal.([]interface{}) {
  174. // check entry subkeys
  175. if hasSubKeys(v, subkeys) {
  176. // replace v with value
  177. nv = append(nv, value)
  178. valmodified = true
  179. (*cnt)++
  180. continue
  181. }
  182. nv = append(nv, v)
  183. }
  184. if valmodified {
  185. (m.(map[string]interface{}))[keys0] = interface{}(nv)
  186. }
  187. default: // anything else is a strict replacement
  188. if hasSubKeys(m, subkeys) {
  189. (m.(map[string]interface{}))[keys0] = value
  190. (*cnt)++
  191. }
  192. }
  193. return
  194. }
  195. // so value is for an element of endVal
  196. // if endVal is a map then 'key' must be there w/ subkeys
  197. // if endVal is a list then 'key' must be in a list member w/ subkeys
  198. switch endVal.(type) {
  199. case map[string]interface{}:
  200. if !hasSubKeys(endVal, subkeys) {
  201. return
  202. }
  203. if _, ok := (endVal.(map[string]interface{}))[key]; ok {
  204. (endVal.(map[string]interface{}))[key] = value
  205. (*cnt)++
  206. }
  207. case []interface{}: // keys0 points to a list, check subkeys
  208. for _, v := range endVal.([]interface{}) {
  209. // got to be a map so we can replace value for 'key'
  210. vv, vok := v.(map[string]interface{})
  211. if !vok {
  212. continue
  213. }
  214. if _, ok := vv[key]; !ok {
  215. continue
  216. }
  217. if !hasSubKeys(vv, subkeys) {
  218. continue
  219. }
  220. vv[key] = value
  221. (*cnt)++
  222. }
  223. }
  224. case []interface{}: // key may be in a list member
  225. // don't need to handle keys0 == "*"; we're looking at everything, anyway.
  226. for _, v := range m.([]interface{}) {
  227. // only map values - we're looking for 'key'
  228. mm, ok := v.(map[string]interface{})
  229. if !ok {
  230. continue
  231. }
  232. if _, ok := mm[key]; !ok {
  233. continue
  234. }
  235. if !hasSubKeys(mm, subkeys) {
  236. continue
  237. }
  238. mm[key] = value
  239. (*cnt)++
  240. }
  241. }
  242. // return
  243. }