package mxj // leafnode.go - return leaf nodes with paths and values for the Map // inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw import ( "strconv" "strings" ) const ( NoAttributes = true // suppress LeafNode values that are attributes ) // LeafNode - a terminal path value in a Map. // For XML Map values it represents an attribute or simple element value - of type // string unless Map was created using Cast flag. For JSON Map values it represents // a string, numeric, boolean, or null value. type LeafNode struct { Path string // a dot-notation representation of the path with array subscripting Value interface{} // the value at the path termination } // LeafNodes - returns an array of all LeafNode values for the Map. // The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-') // as well as the "#text" key for the associated simple element value. // // PrependAttrWithHypen(false) will result in attributes having .attr-name as // terminal node in 'path' while the path for the element value, itself, will be // the base path w/o "#text". // // LeafUseDotNotation(true) causes list members to be identified using ".N" syntax // rather than "[N]" syntax. func (mv Map) LeafNodes(no_attr ...bool) []LeafNode { var a bool if len(no_attr) == 1 { a = no_attr[0] } l := make([]LeafNode, 0) getLeafNodes("", "", map[string]interface{}(mv), &l, a) return l } func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) { // if stripping attributes, then also strip "#text" key if !noattr || node != textK { if path != "" && node[:1] != "[" { path += "." } path += node } switch mv.(type) { case map[string]interface{}: for k, v := range mv.(map[string]interface{}) { // if noattr && k[:1] == "-" { if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { continue } getLeafNodes(path, k, v, l, noattr) } case []interface{}: for i, v := range mv.([]interface{}) { if useDotNotation { getLeafNodes(path, strconv.Itoa(i), v, l, noattr) } else { getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr) } } default: // can't walk any further, so create leaf n := LeafNode{path, mv} *l = append(*l, n) } } // LeafPaths - all paths that terminate in LeafNode values. func (mv Map) LeafPaths(no_attr ...bool) []string { ln := mv.LeafNodes() ss := make([]string, len(ln)) for i := 0; i < len(ln); i++ { ss[i] = ln[i].Path } return ss } // LeafValues - all terminal values in the Map. func (mv Map) LeafValues(no_attr ...bool) []interface{} { ln := mv.LeafNodes() vv := make([]interface{}, len(ln)) for i := 0; i < len(ln); i++ { vv[i] = ln[i].Value } return vv } // ====================== utilities ====================== // https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I var useDotNotation bool // LeafUseDotNotation sets a flag that list members in LeafNode paths // should be identified using ".N" syntax rather than the default "[N]" // syntax. Calling LeafUseDotNotation with no arguments toggles the // flag on/off; otherwise, the argument sets the flag value 'true'/'false'. func LeafUseDotNotation(b ...bool) { if len(b) == 0 { useDotNotation = !useDotNotation return } useDotNotation = b[0] }