leafnode.go raw

   1  package mxj
   2  
   3  // leafnode.go - return leaf nodes with paths and values for the Map
   4  // inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw
   5  
   6  import (
   7  	"strconv"
   8  	"strings"
   9  )
  10  
  11  const (
  12  	NoAttributes = true // suppress LeafNode values that are attributes
  13  )
  14  
  15  // LeafNode - a terminal path value in a Map.
  16  // For XML Map values it represents an attribute or simple element value  - of type
  17  // string unless Map was created using Cast flag. For JSON Map values it represents
  18  // a string, numeric, boolean, or null value.
  19  type LeafNode struct {
  20  	Path  string      // a dot-notation representation of the path with array subscripting
  21  	Value interface{} // the value at the path termination
  22  }
  23  
  24  // LeafNodes - returns an array of all LeafNode values for the Map.
  25  // The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-')
  26  // as well as the "#text" key for the associated simple element value.
  27  //
  28  // PrependAttrWithHypen(false) will result in attributes having .attr-name as 
  29  // terminal node in 'path' while the path for the element value, itself, will be 
  30  // the base path w/o "#text". 
  31  //
  32  // LeafUseDotNotation(true) causes list members to be identified using ".N" syntax
  33  // rather than "[N]" syntax.
  34  func (mv Map) LeafNodes(no_attr ...bool) []LeafNode {
  35  	var a bool
  36  	if len(no_attr) == 1 {
  37  		a = no_attr[0]
  38  	}
  39  
  40  	l := make([]LeafNode, 0)
  41  	getLeafNodes("", "", map[string]interface{}(mv), &l, a)
  42  	return l
  43  }
  44  
  45  func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) {
  46  	// if stripping attributes, then also strip "#text" key
  47  	if !noattr || node != textK {
  48  		if path != "" && node[:1] != "[" {
  49  			path += "."
  50  		}
  51  		path += node
  52  	}
  53  	switch mv.(type) {
  54  	case map[string]interface{}:
  55  		for k, v := range mv.(map[string]interface{}) {
  56  			// if noattr && k[:1] == "-" {
  57  			if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 {
  58  				continue
  59  			}
  60  			getLeafNodes(path, k, v, l, noattr)
  61  		}
  62  	case []interface{}:
  63  		for i, v := range mv.([]interface{}) {
  64  			if useDotNotation {
  65  				getLeafNodes(path, strconv.Itoa(i), v, l, noattr)
  66  			} else {
  67  				getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr)
  68  			}
  69  		}
  70  	default:
  71  		// can't walk any further, so create leaf
  72  		n := LeafNode{path, mv}
  73  		*l = append(*l, n)
  74  	}
  75  }
  76  
  77  // LeafPaths - all paths that terminate in LeafNode values.
  78  func (mv Map) LeafPaths(no_attr ...bool) []string {
  79  	ln := mv.LeafNodes()
  80  	ss := make([]string, len(ln))
  81  	for i := 0; i < len(ln); i++ {
  82  		ss[i] = ln[i].Path
  83  	}
  84  	return ss
  85  }
  86  
  87  // LeafValues - all terminal values in the Map.
  88  func (mv Map) LeafValues(no_attr ...bool) []interface{} {
  89  	ln := mv.LeafNodes()
  90  	vv := make([]interface{}, len(ln))
  91  	for i := 0; i < len(ln); i++ {
  92  		vv[i] = ln[i].Value
  93  	}
  94  	return vv
  95  }
  96  
  97  // ====================== utilities ======================
  98  
  99  // https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I
 100  var useDotNotation bool
 101  
 102  // LeafUseDotNotation sets a flag that list members in LeafNode paths
 103  // should be identified using ".N" syntax rather than the default "[N]"
 104  // syntax.  Calling LeafUseDotNotation with no arguments toggles the 
 105  // flag on/off; otherwise, the argument sets the flag value 'true'/'false'.
 106  func LeafUseDotNotation(b ...bool) {
 107  	if len(b) == 0 {
 108  		useDotNotation = !useDotNotation
 109  		return
 110  	}
 111  	useDotNotation = b[0]
 112  }
 113