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