diff --git a/go.mod b/go.mod
index 3c81a65..e9357f4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,4 @@
module github.com/freeconf/yang
go 1.20
+require github.com/clbanning/mxj/v2 v2.5.7
\ No newline at end of file
diff --git a/go.sum b/go.sum
index e69de29..e9d4ef4 100644
--- a/go.sum
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0=
+github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
\ No newline at end of file
diff --git a/nodeutil/json_rdr.go b/nodeutil/json_rdr.go
index 23810f7..ef39d17 100644
--- a/nodeutil/json_rdr.go
+++ b/nodeutil/json_rdr.go
@@ -2,49 +2,38 @@ package nodeutil
import (
"encoding/json"
- "errors"
- "fmt"
"io"
"strings"
"github.com/freeconf/yang/node"
- "github.com/freeconf/yang/val"
-
- "github.com/freeconf/yang/meta"
)
type JSONRdr struct {
- In io.Reader
- values map[string]interface{}
+ Rdr
}
func ReadJSONIO(rdr io.Reader) node.Node {
- jrdr := &JSONRdr{In: rdr}
+ jrdr := &JSONRdr{}
+ jrdr.decoder = jrdr
+ jrdr.In = rdr
return jrdr.Node()
}
func ReadJSONValues(values map[string]interface{}) node.Node {
- jrdr := &JSONRdr{values: values}
+ jrdr := &JSONRdr{}
+ jrdr.decoder = jrdr
+ jrdr.values = values
return jrdr.Node()
}
func ReadJSON(data string) node.Node {
- rdr := &JSONRdr{In: strings.NewReader(data)}
+ rdr := &JSONRdr{}
+ rdr.decoder = rdr
+ rdr.In = strings.NewReader(data)
return rdr.Node()
}
-func (self *JSONRdr) Node() node.Node {
- var err error
- if self.values == nil {
- self.values, err = self.decode()
- if err != nil {
- return node.ErrorNode{Err: err}
- }
- }
- return JsonContainerReader(self.values)
-}
-
-func (self *JSONRdr) decode() (map[string]interface{}, error) {
+func (self *JSONRdr) Decode() (map[string]interface{}, error) {
if self.values == nil {
d := json.NewDecoder(self.In)
if err := d.Decode(&self.values); err != nil {
@@ -53,129 +42,3 @@ func (self *JSONRdr) decode() (map[string]interface{}, error) {
}
return self.values, nil
}
-
-func leafOrLeafListJsonReader(m meta.Leafable, data interface{}) (v val.Value, err error) {
- return node.NewValue(m.Type(), data)
-}
-
-func JsonListReader(list []interface{}) node.Node {
- s := &Basic{}
- s.OnNext = func(r node.ListRequest) (next node.Node, key []val.Value, err error) {
- key = r.Key
- if r.New {
- panic("Cannot write to JSON reader")
- }
- if len(r.Key) > 0 {
- if r.First {
- keyFields := r.Meta.KeyMeta()
- for i := 0; i < len(list); i++ {
- candidate := list[i].(map[string]interface{})
- if jsonKeyMatches(keyFields, candidate, key) {
- return JsonContainerReader(candidate), r.Key, nil
- }
- }
- }
- } else {
- if r.Row < len(list) {
- container := list[r.Row].(map[string]interface{})
- if len(r.Meta.KeyMeta()) > 0 {
- keyData := make([]interface{}, len(r.Meta.KeyMeta()))
- for i, kmeta := range r.Meta.KeyMeta() {
- // Key may legitimately not exist when inserting new data
- keyData[i] = fqkGetOrNil(kmeta, container)
- }
- if key, err = node.NewValues(r.Meta.KeyMeta(), keyData...); err != nil {
- return nil, nil, err
- }
- }
- return JsonContainerReader(container), key, nil
- }
- }
- return nil, nil, nil
- }
- return s
-}
-
-func fqkGetOrNil(m meta.Definition, container map[string]interface{}) interface{} {
- v, _ := fqkGet(m, container)
- return v
-}
-
-func fqkGet(m meta.Definition, container map[string]interface{}) (interface{}, bool) {
- v, found := container[m.Ident()]
- if !found {
- mod := meta.OriginalModule(m)
- v, found = container[fmt.Sprintf("%s:%s", mod.Ident(), m.Ident())]
- }
- return v, found
-}
-
-func JsonContainerReader(container map[string]interface{}) node.Node {
- s := &Basic{}
- var divertedList node.Node
- s.OnChoose = func(state *node.Selection, choice *meta.Choice) (m *meta.ChoiceCase, err error) {
- // go thru each case and if there are any properties in the data that are not
- // part of the meta, that disqualifies that case and we move onto next case
- // until one case aligns with data. If no cases align then input in inconclusive
- // i.e. non-discriminating and we should error out.
- for _, kase := range choice.Cases() {
- for _, prop := range kase.DataDefinitions() {
- if _, found := fqkGet(prop, container); found {
- return kase, nil
- }
- // just because you didn't find a property doesnt
- // mean it's invalid, it's only if you don't find any
- // of the properties of a case
- }
- }
- // just because you didn't find any properties of any cases doesn't
- // mean it's invalid, just that *none* of the cases are there.
- return nil, nil
- }
- s.OnChild = func(r node.ChildRequest) (child node.Node, e error) {
- if r.New {
- panic("Cannot write to JSON reader")
- }
- if value, found := fqkGet(r.Meta, container); found {
- if meta.IsList(r.Meta) {
- return JsonListReader(value.([]interface{})), nil
- }
- return JsonContainerReader(value.(map[string]interface{})), nil
- }
- return
- }
- s.OnField = func(r node.FieldRequest, hnd *node.ValueHandle) (err error) {
- if r.Write {
- panic("Cannot write to JSON reader")
- }
- if val, found := fqkGet(r.Meta, container); found {
- hnd.Val, err = leafOrLeafListJsonReader(r.Meta, val)
- }
- return
- }
- s.OnNext = func(r node.ListRequest) (node.Node, []val.Value, error) {
- if divertedList != nil {
- return nil, nil, nil
- }
- // divert to list handler
- foundValues, found := fqkGet(r.Meta, container)
- list, ok := foundValues.([]interface{})
- if len(container) != 1 || !found || !ok {
- msg := fmt.Sprintf("Expected { %s: [] }", r.Meta.Ident())
- return nil, nil, errors.New(msg)
- }
- divertedList = JsonListReader(list)
- s.OnNext = divertedList.Next
- return divertedList.Next(r)
- }
- return s
-}
-
-func jsonKeyMatches(keyFields []meta.Leafable, candidate map[string]interface{}, key []val.Value) bool {
- for i, field := range keyFields {
- if fqkGetOrNil(field, candidate) != key[i].String() {
- return false
- }
- }
- return true
-}
diff --git a/nodeutil/rdr_base.go b/nodeutil/rdr_base.go
new file mode 100644
index 0000000..c48130c
--- /dev/null
+++ b/nodeutil/rdr_base.go
@@ -0,0 +1,169 @@
+package nodeutil
+
+import (
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/freeconf/yang/node"
+ "github.com/freeconf/yang/val"
+
+ "github.com/freeconf/yang/meta"
+)
+
+type Decoder interface {
+ Decode() (map[string]interface{}, error)
+}
+
+type Rdr struct {
+ decoder Decoder
+ In io.Reader
+ values map[string]interface{}
+}
+
+func (self *Rdr) Node() node.Node {
+ var err error
+ if self.values == nil {
+ self.values, err = self.decoder.Decode()
+ if err != nil {
+ return node.ErrorNode{Err: err}
+ }
+ }
+ return ContainerReader(self.values)
+}
+
+func (self *Rdr) Decode() (map[string]interface{}, error) {
+ return nil, nil
+}
+
+func leafOrLeafListReader(m meta.Leafable, data interface{}) (v val.Value, err error) {
+ return node.NewValue(m.Type(), data)
+}
+
+func ListReader(list []interface{}) node.Node {
+ s := &Basic{}
+ s.OnNext = func(r node.ListRequest) (next node.Node, key []val.Value, err error) {
+ key = r.Key
+ if r.New {
+ panic("Cannot write to reader")
+ }
+ if len(r.Key) > 0 {
+ if r.First {
+ keyFields := r.Meta.KeyMeta()
+ for i := 0; i < len(list); i++ {
+ candidate := list[i].(map[string]interface{})
+ if KeyMatches(keyFields, candidate, key) {
+ return ContainerReader(candidate), r.Key, nil
+ }
+ }
+ }
+ } else {
+ if r.Row < len(list) {
+ container := list[r.Row].(map[string]interface{})
+ if len(r.Meta.KeyMeta()) > 0 {
+ keyData := make([]interface{}, len(r.Meta.KeyMeta()))
+ for i, kmeta := range r.Meta.KeyMeta() {
+ // Key may legitimately not exist when inserting new data
+ keyData[i] = fqkGetOrNil(kmeta, container)
+ }
+ if key, err = node.NewValues(r.Meta.KeyMeta(), keyData...); err != nil {
+ return nil, nil, err
+ }
+ }
+ return ContainerReader(container), key, nil
+ }
+ }
+ return nil, nil, nil
+ }
+ return s
+}
+
+func fqkGetOrNil(m meta.Definition, container map[string]interface{}) interface{} {
+ v, _ := fqkGet(m, container)
+ return v
+}
+
+func fqkGet(m meta.Definition, container map[string]interface{}) (interface{}, bool) {
+ v, found := container[m.Ident()]
+ if !found {
+ mod := meta.OriginalModule(m)
+ v, found = container[fmt.Sprintf("%s:%s", mod.Ident(), m.Ident())]
+ }
+ return v, found
+}
+
+func ContainerReader(container map[string]interface{}) node.Node {
+ s := &Basic{}
+ var divertedList node.Node
+ s.OnChoose = func(state *node.Selection, choice *meta.Choice) (m *meta.ChoiceCase, err error) {
+ // go thru each case and if there are any properties in the data that are not
+ // part of the meta, that disqualifies that case and we move onto next case
+ // until one case aligns with data. If no cases align then input in inconclusive
+ // i.e. non-discriminating and we should error out.
+ for _, kase := range choice.Cases() {
+ for _, prop := range kase.DataDefinitions() {
+ if _, found := fqkGet(prop, container); found {
+ return kase, nil
+ }
+ // just because you didn't find a property doesnt
+ // mean it's invalid, it's only if you don't find any
+ // of the properties of a case
+ }
+ }
+ // just because you didn't find any properties of any cases doesn't
+ // mean it's invalid, just that *none* of the cases are there.
+ return nil, nil
+ }
+ s.OnChild = func(r node.ChildRequest) (child node.Node, e error) {
+ if r.New {
+ panic("Cannot write to reader")
+ }
+ if value, found := fqkGet(r.Meta, container); found {
+ if meta.IsList(r.Meta) {
+ list, ok := value.([]interface{})
+ if !ok {
+ // With XML a list with one element will be of type map[string]interface{} and not []interface{}
+ return ListReader([]interface{}{value.(map[string]interface{})}), nil
+ } else {
+ return ListReader(list), nil
+ }
+ }
+ return ContainerReader(value.(map[string]interface{})), nil
+ }
+ return
+ }
+ s.OnField = func(r node.FieldRequest, hnd *node.ValueHandle) (err error) {
+ if r.Write {
+ panic("Cannot write to reader")
+ }
+ if val, found := fqkGet(r.Meta, container); found {
+ hnd.Val, err = leafOrLeafListReader(r.Meta, val)
+ }
+ return
+ }
+ s.OnNext = func(r node.ListRequest) (node.Node, []val.Value, error) {
+ if divertedList != nil {
+ return nil, nil, nil
+ }
+ // divert to list handler
+ foundValues, found := fqkGet(r.Meta, container)
+ list, ok := foundValues.([]interface{})
+ if len(container) != 1 || !found || !ok {
+ msg := fmt.Sprintf("Expected { %s: [] }", r.Meta.Ident())
+ return nil, nil, errors.New(msg)
+ }
+ divertedList = ListReader(list)
+ s.OnNext = divertedList.Next
+ return divertedList.Next(r)
+ }
+ return s
+}
+
+func KeyMatches(keyFields []meta.Leafable, candidate map[string]interface{}, key []val.Value) bool {
+ for i, field := range keyFields {
+ if fqkGetOrNil(field, candidate) != key[i].String() {
+ return false
+ }
+ }
+ return true
+}
diff --git a/nodeutil/xml_rdr.go b/nodeutil/xml_rdr.go
new file mode 100644
index 0000000..ea6524d
--- /dev/null
+++ b/nodeutil/xml_rdr.go
@@ -0,0 +1,61 @@
+package nodeutil
+
+import (
+ "io"
+ "io/ioutil"
+ "reflect"
+ "strings"
+
+ "github.com/clbanning/mxj/v2"
+ "github.com/freeconf/yang/node"
+)
+
+type XmlRdr struct {
+ Rdr
+}
+
+func ReadXMLIO(rdr io.Reader) node.Node {
+ reader := &XmlRdr{}
+ reader.decoder = reader
+ reader.In = rdr
+ return reader.Node()
+}
+
+func ReadXML(data string) node.Node {
+ rdr := &XmlRdr{}
+ rdr.decoder = rdr
+ rdr.In = strings.NewReader(data)
+ return rdr.Node()
+}
+
+func (self *XmlRdr) Decode() (map[string]interface{}, error) {
+ if self.values == nil {
+ data, err := ioutil.ReadAll(self.In)
+ if err != nil {
+ return nil, err
+ }
+ xml_map, err := mxj.NewMapXml(data)
+ if err != nil {
+ return nil, err
+ }
+
+ self.removeAttributesFromXmlMap(xml_map)
+ self.values = xml_map
+ }
+ return self.values, nil
+}
+
+func (self *XmlRdr) removeAttributesFromXmlMap(m map[string]interface{}) {
+ val := reflect.ValueOf(m)
+ for _, e := range val.MapKeys() {
+ v := val.MapIndex(e)
+ if strings.Index(e.String(), "-") == 0 {
+ delete(m, e.String())
+ continue
+ }
+ switch t := v.Interface().(type) {
+ case map[string]interface{}:
+ self.removeAttributesFromXmlMap(t)
+ }
+ }
+}
diff --git a/nodeutil/xml_rdr_test.go b/nodeutil/xml_rdr_test.go
new file mode 100644
index 0000000..c8c4af5
--- /dev/null
+++ b/nodeutil/xml_rdr_test.go
@@ -0,0 +1,212 @@
+package nodeutil
+
+import (
+ "testing"
+
+ "github.com/freeconf/yang/fc"
+ "github.com/freeconf/yang/node"
+ "github.com/freeconf/yang/parser"
+ "github.com/freeconf/yang/source"
+ "github.com/freeconf/yang/val"
+)
+
+func TestXmlWalk(t *testing.T) {
+ moduleStr := `
+module xml-test {
+ prefix "t";
+ namespace "t";
+ revision 0;
+ container hobbies {
+ list hobbie {
+ key "name";
+ leaf name {
+ type string;
+ }
+ container favorite {
+ leaf common-name {
+ type string;
+ }
+ leaf location {
+ type string;
+ }
+ }
+ }
+ }
+}
+ `
+ module, err := parser.LoadModuleFromString(nil, moduleStr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ xml := `
+
+ birding
+
+ towhee
+ double-mint
+ out back
+
+
+
+ hockey
+
+ bruins
+ Boston
+
+
+ `
+ tests := []string{
+ "hobbies",
+ "hobbies/hobbie=birding",
+ "hobbies/hobbie=birding/favorite",
+ }
+ for _, test := range tests {
+ sel := node.NewBrowser(module, ReadXML(xml)).Root()
+ found, err := sel.Find(test)
+ fc.RequireEqual(t, nil, err, "failed to transmit xml")
+ fc.RequireEqual(t, true, found != nil, "target not found")
+ fc.AssertEqual(t, "xml-test/"+test, found.Path.String())
+ }
+}
+
+func TestXmlRdrUnion(t *testing.T) {
+ mstr := `
+ module x {
+ revision 0;
+ leaf y {
+ type union {
+ type int32;
+ type string;
+ }
+ }
+ }
+ `
+ m, err := parser.LoadModuleFromString(nil, mstr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tests := []struct {
+ in string
+ out string
+ }{
+ {in: `{"y":24}`, out: `24`},
+ {in: `{"y":"hi"}`, out: `hi`},
+ }
+ for _, xml := range tests {
+ t.Log(xml.in)
+ root := node.NewBrowser(m, ReadJSON(xml.in)).Root()
+ actual, err := WriteXML(sel(root.Find("y")))
+ if err != nil {
+ t.Error(err)
+ }
+ fc.AssertEqual(t, xml.out, actual)
+ }
+}
+
+func TestXMLNumberParse(t *testing.T) {
+ moduleStr := `
+module json-test {
+ prefix "t";
+ namespace "t";
+ revision 0;
+ container data {
+ leaf id {
+ type int32;
+ }
+ leaf idstr {
+ type int32;
+ }
+ leaf idstrwrong {
+ type int32;
+ }
+ leaf-list readings{
+ type decimal64;
+ }
+ }
+}
+ `
+ module, err := parser.LoadModuleFromString(nil, moduleStr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xml := `
+
+ 4
+ 4
+ 3.555454
+ 45.04545
+ 324545.04
+ `
+ //test get id
+ data := sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/id"))
+ found, err := data.Get()
+ if err != nil {
+ t.Error("failed to transmit json", err)
+ } else if found == nil {
+ t.Error("data/id - Target not found, state nil")
+ } else {
+ if found.Value().(int) != 4 {
+ t.Error(found.Value().(int), "!=", 4)
+ }
+ }
+
+ //test get idstr
+ data = sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/idstr"))
+ found, err = data.Get()
+ if err != nil {
+ t.Error("failed to transmit json", err)
+ } else if found == nil {
+ t.Error("data/idstr - Target not found, state nil")
+ } else {
+ if found.Value().(int) != 4 {
+ t.Error(found.Value().(int), "!=", 4)
+ }
+ }
+
+ data = sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/readings"))
+ found, err = data.Get()
+ if err != nil {
+ t.Error("failed to transmit json", err)
+ } else if found == nil {
+ t.Error("data/readings - Target not found, state nil")
+ } else {
+ expected := []float64{3.555454, 45.04545, 324545.04}
+ readings := found.Value().([]float64)
+
+ if expected[0] != readings[0] || expected[1] != readings[1] || expected[2] != readings[2] {
+ t.Error(found.Value().([]int), "!=", expected)
+ }
+ }
+}
+
+func TestXmlEmpty(t *testing.T) {
+ moduleStr := `
+module json-test {
+ leaf x {
+ type empty;
+ }
+}
+ `
+ m, err := parser.LoadModuleFromString(nil, moduleStr)
+ fc.AssertEqual(t, nil, err)
+ actual := make(map[string]interface{})
+ b := node.NewBrowser(m, ReflectChild(actual))
+ in := ``
+ fc.AssertEqual(t, nil, b.Root().InsertFrom(ReadXML(in)))
+ fc.AssertEqual(t, val.NotEmpty, actual["x"])
+}
+
+func TestReadQualifiedXmlIdentRef(t *testing.T) {
+ ypath := source.Dir("./testdata")
+ m := parser.RequireModule(ypath, "module-test")
+ in := `{
+ "module-test:type":"module-types:derived-type",
+ "module-test:type2":"local-type"
+ }`
+ actual := make(map[string]interface{})
+ b := node.NewBrowser(m, ReflectChild(actual))
+ fc.AssertEqual(t, nil, b.Root().InsertFrom(ReadJSON(in)))
+ fc.AssertEqual(t, "derived-type", actual["type"].(val.IdentRef).Label)
+ fc.AssertEqual(t, "local-type", actual["type2"].(val.IdentRef).Label)
+}
diff --git a/nodeutil/xml_wtr.go b/nodeutil/xml_wtr.go
new file mode 100644
index 0000000..ffff142
--- /dev/null
+++ b/nodeutil/xml_wtr.go
@@ -0,0 +1,254 @@
+package nodeutil
+
+import (
+ "bufio"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "strconv"
+
+ "github.com/freeconf/yang/node"
+ "github.com/freeconf/yang/val"
+
+ "bytes"
+
+ "github.com/freeconf/yang/meta"
+)
+
+const QUOTE1 = '"'
+const XML1 = '<'
+const XML_CLOSE = '/'
+const XML2 = '>'
+
+type XMLWtr struct {
+
+ // stream to write contents. contents will be flushed only at end of operation
+ Out io.Writer
+
+ // otherwise enumerations are written as their labels. it may be
+ // useful to know that json reader can accept labels or values
+ EnumAsIds bool
+
+ _out *bufio.Writer
+}
+
+func WriteXML(s *node.Selection) (string, error) {
+ buff := new(bytes.Buffer)
+ wtr := &XMLWtr{Out: buff}
+ err := s.InsertInto(wtr.Node())
+ return buff.String(), err
+}
+
+func (wtr XMLWtr) XML(s *node.Selection) (string, error) {
+ buff := new(bytes.Buffer)
+ wtr.Out = buff
+ err := s.InsertInto(wtr.Node())
+ return buff.String(), err
+}
+
+func NewXMLWtr(out io.Writer) *XMLWtr {
+ return &XMLWtr{Out: out}
+}
+
+func (wtr *XMLWtr) Node() node.Node {
+ wtr._out = bufio.NewWriter(wtr.Out)
+
+ return &Extend{
+ Base: wtr.container(0),
+ OnEndEdit: func(p node.Node, r node.NodeRequest) error {
+ ident := wtr.ident(r.Selection.Path)
+ if !meta.IsLeaf(r.Selection.Meta()) {
+ if err := wtr.endContainer(ident); err != nil {
+ return err
+ }
+ }
+ if err := wtr._out.Flush(); err != nil {
+ return err
+ }
+ return nil
+ },
+ }
+}
+
+func (wtr *XMLWtr) container(lvl int) node.Node {
+ first := true
+ s := &Basic{}
+ s.OnChild = func(r node.ChildRequest) (child node.Node, err error) {
+ if !r.New {
+ return nil, nil
+ }
+ if !meta.IsList(r.Meta) {
+ if err = wtr.beginContainer(wtr.ident(r.Path)); err != nil {
+ return nil, err
+ }
+ }
+
+ return wtr.container(lvl + 1), nil
+ }
+ s.OnBeginEdit = func(r node.NodeRequest) error {
+ ident := wtr.ident(r.Selection.Path)
+ if !meta.IsLeaf(r.Selection.Meta()) && !r.Selection.InsideList && !meta.IsList(r.Selection.Meta()) {
+ if lvl == 0 && first == true {
+ ns := wtr.getXmlns(r.Selection.Path)
+ ident = wtr.ident(r.Selection.Path) + " xmlns=" + "\"" + ns + "\""
+ if err := wtr.beginContainer(ident); err != nil {
+ return err
+ }
+ }
+ first = false
+ }
+ return nil
+ }
+ s.OnEndEdit = func(r node.NodeRequest) error {
+ if r.Selection.InsideList {
+ if err := wtr.endContainer(wtr.ident(r.Selection.Path)); err != nil {
+ return err
+ }
+ } else if !meta.IsList(r.Selection.Meta()) {
+ if err := wtr.endContainer(wtr.ident(r.Selection.Path)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+ s.OnField = func(r node.FieldRequest, hnd *node.ValueHandle) (err error) {
+ ns := ""
+
+ if l, listable := hnd.Val.(val.Listable); listable {
+ for i := 0; i < l.Len(); i++ {
+ wtr.writeLeafElement(ns, r.Path, l.Item(i))
+ }
+ } else {
+ if lvl == 0 && first == true {
+ ns = wtr.getXmlns(r.Path)
+ }
+ wtr.writeLeafElement(ns, r.Path, hnd.Val)
+ }
+
+ return nil
+ }
+ s.OnNext = func(r node.ListRequest) (next node.Node, key []val.Value, err error) {
+ if !r.New {
+ return
+ }
+
+ ident := wtr.ident(r.Selection.Path)
+
+ if err = wtr.beginContainer(ident); err != nil {
+ return
+ }
+ return wtr.container(lvl + 1), r.Key, nil
+ }
+ return s
+}
+
+func (wtr *XMLWtr) ident(p *node.Path) string {
+ s := p.Meta.(meta.Identifiable).Ident()
+ return s
+}
+
+func (wtr *XMLWtr) getXmlns(p *node.Path) string {
+ ns := ""
+ if meta.OriginalModule(p.Meta).Namespace() == "" {
+ ns = meta.OriginalModule(p.Meta).Ident()
+ } else {
+ ns = meta.OriginalModule(p.Meta).Namespace()
+ }
+ return ns
+}
+
+func (wtr *XMLWtr) beginContainer(ident string) (err error) {
+ if err = wtr.writeOpenIdent(ident); err != nil {
+ return err
+ }
+ return
+}
+
+func (wtr *XMLWtr) writeOpenIdent(ident string) (err error) {
+ if _, err = wtr._out.WriteRune(XML1); err != nil {
+ return err
+ }
+ if _, err = wtr._out.WriteString(ident); err != nil {
+ return err
+ }
+ if _, err = wtr._out.WriteRune(XML2); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (wtr *XMLWtr) writeCloseIdent(ident string) (err error) {
+ if _, err = wtr._out.WriteRune(XML1); err != nil {
+ return err
+ }
+ if _, err = wtr._out.WriteRune(XML_CLOSE); err != nil {
+ return err
+ }
+ if _, err = wtr._out.WriteString(ident); err != nil {
+ return err
+ }
+ if _, err = wtr._out.WriteRune(XML2); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (wtr *XMLWtr) endContainer(ident string) (err error) {
+ if err = wtr.writeCloseIdent(ident); err != nil {
+ return
+ }
+ return
+}
+
+func (wtr *XMLWtr) writeLeafElement(attibute string, p *node.Path, v val.Value) error {
+ var err error
+ stringValue, err := wtr.getStringValue(p, v)
+ ident := p.Meta.(meta.Identifiable).Ident()
+ test := xml.StartElement{Name: xml.Name{Local: ident, Space: attibute}}
+ xml.NewEncoder(wtr._out).EncodeElement(stringValue, test)
+
+ return err
+}
+
+func (wtr *XMLWtr) getStringValue(p *node.Path, v val.Value) (string, error) {
+ stringValue := ""
+ var err error
+ switch v.Format() {
+ case val.FmtIdentityRef:
+ stringValue = v.String()
+ leafMod := meta.OriginalModule(p.Meta)
+ bases := p.Meta.(meta.HasType).Type().Base()
+ idty := meta.FindIdentity(bases, stringValue)
+ if idty == nil {
+ err = fmt.Errorf("could not find ident '%s'", stringValue)
+ }
+ idtyMod := meta.RootModule(idty)
+ if idtyMod != leafMod {
+ stringValue = fmt.Sprint(idtyMod.Ident(), ":", stringValue)
+ }
+ case val.FmtString, val.FmtBinary, val.FmtAny:
+ stringValue = v.String()
+ case val.FmtEnum:
+ if wtr.EnumAsIds {
+ stringValue = strconv.Itoa(v.(val.Enum).Id)
+
+ } else {
+ stringValue = v.(val.Enum).Label
+ }
+ case val.FmtDecimal64:
+ f := v.Value().(float64)
+ stringValue = strconv.FormatFloat(f, 'f', -1, 64)
+ default:
+ stringValue = v.String()
+ }
+ return stringValue, err
+}
+
+func (wtr *XMLWtr) writeString(s string) error {
+ clean := bytes.NewBuffer(make([]byte, len(s)+2))
+ clean.Reset()
+ writeString(clean, s, true)
+ _, ioErr := wtr._out.Write(clean.Bytes())
+ return ioErr
+}
diff --git a/nodeutil/xml_wtr_test.go b/nodeutil/xml_wtr_test.go
new file mode 100644
index 0000000..593a443
--- /dev/null
+++ b/nodeutil/xml_wtr_test.go
@@ -0,0 +1,215 @@
+package nodeutil
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "testing"
+
+ "github.com/freeconf/yang/fc"
+ "github.com/freeconf/yang/node"
+ "github.com/freeconf/yang/parser"
+ "github.com/freeconf/yang/source"
+ "github.com/freeconf/yang/val"
+)
+
+func TestXmlWriterLeafs(t *testing.T) {
+ fc.DebugLog(true)
+ tests := []struct {
+ Yang string
+ Val val.Value
+ expected string
+ enumAsId bool
+ }{
+ {
+ Yang: `leaf x { type union { type int32; type string;}}`,
+ Val: val.String("a"),
+ expected: `a`,
+ },
+ {
+ Yang: `leaf x { type union { type int32; type string;}}`,
+ Val: val.Int32(99),
+ expected: `99`,
+ },
+ {
+ Yang: `leaf x { type enumeration { enum zero; enum one; }}`,
+ Val: val.Enum{Id: 0, Label: "zero"},
+ expected: `zero`,
+ },
+ {
+ Yang: `leaf x { type enumeration { enum five {value 5;} enum six; }}`,
+ Val: val.Enum{Id: 6, Label: "six"},
+ expected: `6`,
+ enumAsId: true,
+ },
+ }
+ for _, test := range tests {
+ m, err := parser.LoadModuleFromString(nil, fmt.Sprintf(`module m { namespace ""; %s }`, test.Yang))
+ if err != nil {
+ t.Fatal(err)
+ }
+ var actual bytes.Buffer
+ buf := bufio.NewWriter(&actual)
+ w := &XMLWtr{
+ _out: buf,
+ EnumAsIds: test.enumAsId,
+ }
+ w.writeLeafElement("m", &node.Path{Parent: &node.Path{Meta: m}, Meta: m.DataDefinitions()[0]}, test.Val)
+ buf.Flush()
+ fc.AssertEqual(t, test.expected, actual.String())
+ }
+}
+
+func TestXmlWriterListInList(t *testing.T) {
+ moduleStr := `
+module m {
+ prefix "t";
+ namespace "t";
+ revision 0000-00-00 {
+ description "x";
+ }
+ typedef td {
+ type string;
+ }
+ container c1 {
+ list l1 {
+ list l2 {
+ key "a";
+ leaf a {
+ type td;
+ }
+ leaf b {
+ type string;
+ }
+ }
+ }
+ }
+}
+ `
+ m, _ := parser.LoadModuleFromString(nil, moduleStr)
+ root := map[string]interface{}{
+ "c1": map[string]interface{}{
+ "l1": []map[string]interface{}{
+ {
+ "l2": []map[string]interface{}{
+ {
+ "a": "hi",
+ "b": "bye",
+ },
+ },
+ },
+ },
+ },
+ }
+ b := ReflectChild(root)
+ c1 := sel(node.NewBrowser(m, b).Root().Find("c1"))
+ actual, err := WriteXML(c1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := `hibye`
+ if actual != expected {
+ t.Errorf("\nExpected:%s\n Actual:%s", expected, actual)
+ }
+}
+
+func TestXmlAnyData(t *testing.T) {
+ moduleStr := `
+module m {
+ prefix "t";
+ namespace "t";
+ revision 0000-00-00 {
+ description "x";
+ }
+ container c1 {
+ list l1 {
+ list l2 {
+ key "a";
+ leaf a {
+ type any;
+ }
+ leaf b {
+ type any;
+ }
+ }
+ }
+ }
+}
+ `
+ m, _ := parser.LoadModuleFromString(nil, moduleStr)
+ root := map[string]interface{}{
+ "c1": map[string]interface{}{
+ "l1": []map[string]interface{}{
+ {
+ "l2": []map[string]interface{}{
+ {
+ "a": "hi",
+ "b": 99,
+ },
+ },
+ },
+ },
+ },
+ }
+ b := ReflectChild(root)
+ c1 := sel(node.NewBrowser(m, b).Root().Find("c1"))
+ actual, err := WriteXML(c1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := `hi99`
+ if actual != expected {
+ t.Errorf("\nExpected:%s\n Actual:%s", expected, actual)
+ }
+}
+func TestQualifiedXmlIdentityRef(t *testing.T) {
+ ypath := source.Dir("./testdata")
+ m := parser.RequireModule(ypath, "module-test")
+ d := map[string]interface{}{
+ "type": "derived-type",
+ }
+ b := node.NewBrowser(m, ReflectChild(d))
+ wtr := &XMLWtr{}
+ actual, err := wtr.XML(sel(b.Root().Find("type")))
+ if err != nil {
+ t.Fatal(err)
+ }
+ fc.AssertEqual(t, `module-types:derived-type`, actual)
+}
+
+func TestXmlLeafList(t *testing.T) {
+ moduleStr := `
+module m {
+ prefix "t";
+ namespace "t";
+ revision 0000-00-00 {
+ description "x";
+ }
+ container c {
+ leaf-list l {
+ type string;
+ }
+ }
+}
+ `
+ m, _ := parser.LoadModuleFromString(nil, moduleStr)
+ root := map[string]interface{}{
+ "c": map[string]interface{}{
+ "l": []interface{}{
+ "hi",
+ "bye",
+ },
+ },
+ }
+
+ b := ReflectChild(root)
+ c := sel(node.NewBrowser(m, b).Root().Find("c"))
+ actual, err := WriteXML(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expected := `hibye`
+ if actual != expected {
+ t.Errorf("\nExpected:%s\n Actual:%s", expected, actual)
+ }
+}
diff --git a/val/conv.go b/val/conv.go
index 788039b..ef00014 100644
--- a/val/conv.go
+++ b/val/conv.go
@@ -169,7 +169,7 @@ func Conv(f Format, val interface{}) (Value, error) {
} else {
return String(x), err
}
- case FmtStringList:
+ case FmtStringList, FmtBinaryList:
if x, err := toStringList(val); err != nil {
return nil, err
} else {
@@ -848,6 +848,8 @@ func toBinary(val interface{}) (string, error) {
case []byte:
r := b64.StdEncoding.EncodeToString(x)
return r, nil
+ case string:
+ return x, nil
}
return "", fmt.Errorf("cannot coerse '%T' to binary value", val)
@@ -893,6 +895,10 @@ func toStringList(val interface{}) ([]string, error) {
}
}
return l, err
+ case string:
+ l := make([]string, 1)
+ l[0] = x
+ return l, nil
}
// option 2: fallback on reflection