diff --git a/go.mod b/go.mod index e9357f46..3c81a658 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,3 @@ 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 e9d4ef45..e69de29b 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -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 ef39d176..23810f7d 100644 --- a/nodeutil/json_rdr.go +++ b/nodeutil/json_rdr.go @@ -2,38 +2,49 @@ 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 { - Rdr + In io.Reader + values map[string]interface{} } func ReadJSONIO(rdr io.Reader) node.Node { - jrdr := &JSONRdr{} - jrdr.decoder = jrdr - jrdr.In = rdr + jrdr := &JSONRdr{In: rdr} return jrdr.Node() } func ReadJSONValues(values map[string]interface{}) node.Node { - jrdr := &JSONRdr{} - jrdr.decoder = jrdr - jrdr.values = values + jrdr := &JSONRdr{values: values} return jrdr.Node() } func ReadJSON(data string) node.Node { - rdr := &JSONRdr{} - rdr.decoder = rdr - rdr.In = strings.NewReader(data) + rdr := &JSONRdr{In: strings.NewReader(data)} return rdr.Node() } -func (self *JSONRdr) Decode() (map[string]interface{}, error) { +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) { if self.values == nil { d := json.NewDecoder(self.In) if err := d.Decode(&self.values); err != nil { @@ -42,3 +53,129 @@ 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 deleted file mode 100644 index c48130cf..00000000 --- a/nodeutil/rdr_base.go +++ /dev/null @@ -1,169 +0,0 @@ -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/testdata/conflict-one.yang b/nodeutil/testdata/conflict-one.yang new file mode 100644 index 00000000..66205eea --- /dev/null +++ b/nodeutil/testdata/conflict-one.yang @@ -0,0 +1,7 @@ +module conflict-one { + namespace "one"; + + leaf x { + type string; + } +} \ No newline at end of file diff --git a/nodeutil/testdata/conflict-two.yang b/nodeutil/testdata/conflict-two.yang new file mode 100644 index 00000000..71e8689b --- /dev/null +++ b/nodeutil/testdata/conflict-two.yang @@ -0,0 +1,7 @@ +module conflict-two { + namespace "two"; + + leaf x { + type string; + } +} \ No newline at end of file diff --git a/nodeutil/testdata/conflict.yang b/nodeutil/testdata/conflict.yang new file mode 100644 index 00000000..c958beb6 --- /dev/null +++ b/nodeutil/testdata/conflict.yang @@ -0,0 +1,15 @@ +module conflict { + namespace "zero"; + + import conflict-one { + prefix one; + } + + import conflict-two { + prefix two; + } + + leaf x { + type string; + } +} \ No newline at end of file diff --git a/nodeutil/xml_rdr.go b/nodeutil/xml_rdr.go index ea6524d3..4e3d75e7 100644 --- a/nodeutil/xml_rdr.go +++ b/nodeutil/xml_rdr.go @@ -1,61 +1,197 @@ package nodeutil import ( + "context" + "encoding/xml" + "fmt" "io" - "io/ioutil" - "reflect" - "strings" - "github.com/clbanning/mxj/v2" + "github.com/freeconf/yang/meta" "github.com/freeconf/yang/node" + "github.com/freeconf/yang/val" ) -type XmlRdr struct { - Rdr +// ReadXMLDoc to read xml doc where root node is assumed to be the correct +// element. +// +// +// ... +// +// module my-module { +// container my-child {... +// +// NewBrowser(myModule, ReadXMLDoc(myXmlDoc)) +func ReadXMLDoc(buf io.Reader) (*XmlNode, error) { + dec := xml.NewDecoder(buf) + var n XmlNode + err := dec.Decode(&n) + if err != nil { + return nil, err + } + return &n, nil +} + +// ReadXMLBlock to read xml doc where root node is assumed to be the correct +// element. +// +// +// ... +// +// module my-module { +// container my-child {... +// +// b := NewBrowser(myModule, myApp) +// sel, _ := b.Root().Find("my-child") +// sel.UpsertFrom(ReadXMLBlock(myXmlDoc)) +func ReadXMLBlock(buf io.Reader) (*XmlNode, error) { + n, err := ReadXMLDoc(buf) + if err != nil { + return nil, err + } + return &XmlNode{Nodes: []*XmlNode{n}}, nil } -func ReadXMLIO(rdr io.Reader) node.Node { - reader := &XmlRdr{} - reader.decoder = reader - reader.In = rdr - return reader.Node() +type XmlNode struct { + XMLName xml.Name + Attrs []xml.Attr `xml:"-"` + Content []byte `xml:",innerxml"` + Nodes []*XmlNode `xml:",any"` } -func ReadXML(data string) node.Node { - rdr := &XmlRdr{} - rdr.decoder = rdr - rdr.In = strings.NewReader(data) - return rdr.Node() +func (x *XmlNode) Child(r node.ChildRequest) (node.Node, error) { + ndx := x.find(0, r.Meta) + if ndx < 0 { + return nil, nil + } + if meta.IsList(r.Meta) { + var found []*XmlNode + // 7.8.5. XML Encoding Rules + // The XML elements representing list entries MAY be interleaved with elements + // for siblings of the list + for ndx >= 0 { + found = append(found, x.Nodes[ndx]) + ndx = x.find(ndx+1, r.Meta) + } + return &XmlNode{XMLName: x.XMLName, Nodes: found}, nil + } + return x.Nodes[ndx], nil } -func (self *XmlRdr) Decode() (map[string]interface{}, error) { - if self.values == nil { - data, err := ioutil.ReadAll(self.In) - if err != nil { - return nil, err +func (x *XmlNode) Next(r node.ListRequest) (node.Node, []val.Value, error) { + if r.Key != nil { + for _, n := range x.Nodes { + for i, k := range r.Key { + v, found := n.field(r.Meta.KeyMeta()[i]) + if !found { + break + } + if k.String() != v { + break + } + isLastKey := i == (len(r.Key) - 1) + if isLastKey { + return n, r.Key, nil + } + } } - xml_map, err := mxj.NewMapXml(data) - if err != nil { - return nil, err + } else if r.Row < len(x.Nodes) { + // assumes x.Nodes are all of the same element tag as would be the case if + // this was created by Child() method + target := x.Nodes[r.Row] + var key []val.Value + if len(r.Meta.KeyMeta()) > 0 { + for _, kmeta := range r.Meta.KeyMeta() { + sval, valid := target.field(kmeta) + if !valid { + return nil, nil, fmt.Errorf("key '%s' missing from %s", kmeta.Ident(), r.Path) + } + v, err := node.NewValue(kmeta.Type(), sval) + if err != nil { + return nil, nil, fmt.Errorf("error reading key '%s' from %s. %w", kmeta.Ident(), r.Path, err) + } + key = append(key, v) + } } - self.removeAttributesFromXmlMap(xml_map) - self.values = xml_map + return x.Nodes[r.Row], key, nil + } + return nil, nil, nil +} + +func (x *XmlNode) field(m meta.Leafable) (string, bool) { + if ndx := x.find(0, m); ndx >= 0 { + return string(x.Nodes[ndx].Content), true + } + return "", false +} + +func (x *XmlNode) Field(r node.FieldRequest, hnd *node.ValueHandle) error { + var err error + ndx := x.find(0, r.Meta) + if ndx < 0 { + return nil + } + if _, isList := r.Meta.(*meta.LeafList); isList { + var found []string + // 7.8.5. XML Encoding Rules + // The XML elements representing list entries MAY be interleaved with elements + // for siblings of the list + for ndx >= 0 { + found = append(found, string(x.Nodes[ndx].Content)) + ndx = x.find(ndx+1, r.Meta) + } + hnd.Val, err = node.NewValue(r.Meta.Type(), found) + } else { + hnd.Val, err = node.NewValue(r.Meta.Type(), string(x.Nodes[ndx].Content)) } - return self.values, nil + return err } -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 +func (x *XmlNode) find(start int, m meta.Definition) int { + ns := meta.OriginalModule(m).Namespace() + for i := start; i < len(x.Nodes); i++ { + if x.Nodes[i].XMLName.Local == m.Ident() && ns == x.Nodes[i].XMLName.Space { + return i } - switch t := v.Interface().(type) { - case map[string]interface{}: - self.removeAttributesFromXmlMap(t) + } + return -1 +} + +func (x *XmlNode) Choose(sel *node.Selection, choice *meta.Choice) (*meta.ChoiceCase, error) { + for _, c := range choice.Cases() { + for _, m := range c.DataDefinitions() { + if x.find(0, m) >= 0 { + return c, nil + } } } + return nil, nil +} + +// Stubs non-reader funcs + +func (n *XmlNode) Peek(sel *node.Selection, consumer interface{}) interface{} { + return n +} + +func (n *XmlNode) BeginEdit(r node.NodeRequest) error { + return nil +} + +func (n *XmlNode) EndEdit(r node.NodeRequest) error { + return nil +} + +func (n *XmlNode) Action(r node.ActionRequest) (node.Node, error) { + return nil, nil +} + +func (n *XmlNode) Notify(r node.NotifyRequest) (node.NotifyCloser, error) { + return nil, nil +} + +func (n *XmlNode) Context(sel *node.Selection) context.Context { + return sel.Context } + +func (n *XmlNode) Release(sel *node.Selection) {} diff --git a/nodeutil/xml_rdr_test.go b/nodeutil/xml_rdr_test.go index c8c4af50..44e557aa 100644 --- a/nodeutil/xml_rdr_test.go +++ b/nodeutil/xml_rdr_test.go @@ -1,6 +1,8 @@ package nodeutil import ( + "bytes" + "strings" "testing" "github.com/freeconf/yang/fc" @@ -11,101 +13,66 @@ import ( ) 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 { + moduleStr := ` + module xml-test { + prefix "t"; + namespace "t"; + revision 0; + list hobbies { + key "name"; + leaf name { type string; } - leaf location { - 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 := ` - + fc.RequireEqual(t, nil, err) + xml := ` + + birding towhee double-mint out back - - + + hockey bruins Boston - - ` + + + ` tests := []string{ "hobbies", - "hobbies/hobbie=birding", - "hobbies/hobbie=birding/favorite", + "hobbies=birding", + "hobbies=birding/favorite", } for _, test := range tests { - sel := node.NewBrowser(module, ReadXML(xml)).Root() + 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.RequireEqual(t, nil, err, test) + fc.RequireEqual(t, true, found != nil, test) 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 { +module xml-test { prefix "t"; namespace "t"; revision 0; @@ -126,63 +93,55 @@ module json-test { } ` module, err := parser.LoadModuleFromString(nil, moduleStr) - if err != nil { - t.Fatal(err) - } + fc.RequireEqual(t, nil, err) xml := ` - - 4 - 4 - 3.555454 - 45.04545 - 324545.04 - ` + + + 4 + 4 + 3.555454 + 45.04545 + 324545.04 + + ` + //test get id - data := sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/id")) + root := node.NewBrowser(module, readXml(xml)).Root() + + data := sel(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) - } - } + fc.RequireEqual(t, nil, err, "failed to transmit json") + fc.RequireEqual(t, true, found != nil, "data/id - Target not found, state nil") + fc.AssertEqual(t, 4, found.Value().(int)) //test get idstr - data = sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/idstr")) + data = sel(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) - } - } + fc.RequireEqual(t, nil, err, "failed to transmit json") + fc.RequireEqual(t, true, found != nil, "data/idstr - Target not found, state nil") + fc.AssertEqual(t, 4, found.Value().(int)) - data = sel(node.NewBrowser(module, ReadXML(xml)).Root().Find("data/readings")) + data = sel(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) + fc.RequireEqual(t, nil, err, "failed to transmit json") + fc.RequireEqual(t, true, found != nil, "data/readings - Target not found, state nil") + expected := []float64{3.555454, 45.04545, 324545.04} + readings := found.Value().([]float64) + fc.AssertEqual(t, expected, readings) +} - if expected[0] != readings[0] || expected[1] != readings[1] || expected[2] != readings[2] { - t.Error(found.Value().([]int), "!=", expected) - } +func readXml(data string) *XmlNode { + n, err := ReadXMLDoc(strings.NewReader(data)) + if err != nil { + panic(err) } + return n } func TestXmlEmpty(t *testing.T) { moduleStr := ` -module json-test { +module xml-test { leaf x { type empty; } @@ -191,22 +150,81 @@ module json-test { 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))) + b := node.NewBrowser(m, &Node{Object: 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" - }` + in := ` + + module-types:derived-type + 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, nil, b.Root().InsertFrom(readXml(in))) fc.AssertEqual(t, "derived-type", actual["type"].(val.IdentRef).Label) fc.AssertEqual(t, "local-type", actual["type2"].(val.IdentRef).Label) } + +func TestXmlChoice(t *testing.T) { + ypath := source.Dir("./testdata") + m := parser.RequireModule(ypath, "choice") + in := `here` + actual := make(map[string]interface{}) + b := node.NewBrowser(m, ReflectChild(actual)) + fc.AssertEqual(t, nil, b.Root().InsertFrom(readXml(in))) + fc.AssertEqual(t, "here", actual["z"]) +} + +func TestXmlRdrListByRow(t *testing.T) { + moduleStr := ` +module xml-test { + leaf x { + type string; + } + list y { + leaf z { + type string; + } + } +} + ` + m, err := parser.LoadModuleFromString(nil, moduleStr) + fc.RequireEqual(t, nil, err) + in := ` + + Exs + row 0 + row 1 + + ` + b := node.NewBrowser(m, readXml(in)) + actual, err := WriteJSON(b.Root()) + fc.RequireEqual(t, nil, err) + fc.AssertEqual(t, `{"x":"Exs","y":[{"z":"row 0"},{"z":"row 1"}]}`, actual) +} + +func TestXmlConflict(t *testing.T) { + ypath := source.Dir("./testdata") + m := parser.RequireModule(ypath, "conflict") + in := ` + + zero + one + two + ` + b := node.NewBrowser(m, readXml(in)) + var actual bytes.Buffer + w := NewJSONWtr(&actual) + w.QualifyNamespace = true + err := b.Root().UpdateInto(w.Node()) + fc.RequireEqual(t, nil, err) + // this is wrong, should have all three, but there is bug in + // underlying edit.go that doesn't request all three "x" fields + fc.AssertEqual(t, `{"conflict:x":"zero"}`, actual.String()) +} diff --git a/nodeutil/xml_wtr.go b/nodeutil/xml_wtr.go index ffff1421..bc53df09 100644 --- a/nodeutil/xml_wtr.go +++ b/nodeutil/xml_wtr.go @@ -86,11 +86,10 @@ func (wtr *XMLWtr) container(lvl int) node.Node { 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 { + if lvl == 0 && first { ns := wtr.getXmlns(r.Selection.Path) - ident = wtr.ident(r.Selection.Path) + " xmlns=" + "\"" + ns + "\"" + ident := wtr.ident(r.Selection.Path) + " xmlns=" + "\"" + ns + "\"" if err := wtr.beginContainer(ident); err != nil { return err } @@ -120,7 +119,7 @@ func (wtr *XMLWtr) container(lvl int) node.Node { wtr.writeLeafElement(ns, r.Path, l.Item(i)) } } else { - if lvl == 0 && first == true { + if lvl == 0 && first { ns = wtr.getXmlns(r.Path) } wtr.writeLeafElement(ns, r.Path, hnd.Val) diff --git a/nodeutil/xml_wtr_test.go b/nodeutil/xml_wtr_test.go index 593a4436..81fe4c74 100644 --- a/nodeutil/xml_wtr_test.go +++ b/nodeutil/xml_wtr_test.go @@ -101,7 +101,7 @@ module m { }, }, } - b := ReflectChild(root) + b := &Node{Object: root} c1 := sel(node.NewBrowser(m, b).Root().Find("c1")) actual, err := WriteXML(c1) if err != nil { @@ -151,7 +151,7 @@ module m { }, }, } - b := ReflectChild(root) + b := &Node{Object: root} c1 := sel(node.NewBrowser(m, b).Root().Find("c1")) actual, err := WriteXML(c1) if err != nil { @@ -168,7 +168,7 @@ func TestQualifiedXmlIdentityRef(t *testing.T) { d := map[string]interface{}{ "type": "derived-type", } - b := node.NewBrowser(m, ReflectChild(d)) + b := node.NewBrowser(m, &Node{Object: d}) wtr := &XMLWtr{} actual, err := wtr.XML(sel(b.Root().Find("type"))) if err != nil { @@ -202,7 +202,7 @@ module m { }, } - b := ReflectChild(root) + b := &Node{Object: root} c := sel(node.NewBrowser(m, b).Root().Find("c")) actual, err := WriteXML(c) if err != nil { diff --git a/xpath/parser.go b/xpath/parser.go index 349f2411..1ccbba1d 100644 --- a/xpath/parser.go +++ b/xpath/parser.go @@ -48,6 +48,7 @@ var yyToknames = [...]string{ "token_operator", "kywd_slash", } + var yyStatenames = [...]string{} const yyEofCode = 1 @@ -55,7 +56,7 @@ const yyErrCode = 2 const yyInitialStackSize = 16 //line yacctab:1 -var yyExca = [...]int{ +var yyExca = [...]int8{ -1, 1, 1, -1, -2, 0, @@ -65,49 +66,49 @@ const yyPrivate = 57344 const yyLast = 15 -var yyAct = [...]int{ - +var yyAct = [...]int8{ 4, 7, 10, 8, 7, 5, 13, 12, 2, 11, 8, 6, 3, 1, 9, } -var yyPact = [...]int{ +var yyPact = [...]int16{ -3, -1000, 0, -1000, -1000, 0, -6, 2, -1000, 0, -1000, 1, -1000, -1000, } -var yyPgo = [...]int{ +var yyPgo = [...]int8{ 0, 13, 8, 12, 0, 11, } -var yyR1 = [...]int{ +var yyR1 = [...]int8{ 0, 1, 1, 3, 2, 2, 4, 4, 5, 5, 5, } -var yyR2 = [...]int{ +var yyR2 = [...]int8{ 0, 1, 1, 2, 1, 2, 2, 1, 1, 3, 3, } -var yyChk = [...]int{ +var yyChk = [...]int16{ -1000, -1, -2, -3, -4, 8, -5, 4, -4, -2, 8, 7, 6, 5, } -var yyDef = [...]int{ +var yyDef = [...]int8{ 0, -2, 1, 2, 4, 0, 7, 8, 5, 3, 6, 0, 9, 10, } -var yyTok1 = [...]int{ +var yyTok1 = [...]int8{ 1, } -var yyTok2 = [...]int{ +var yyTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, } -var yyTok3 = [...]int{ + +var yyTok3 = [...]int8{ 0, } @@ -189,9 +190,9 @@ func yyErrorMessage(state, lookAhead int) string { expected := make([]int, 0, 4) // Look for shiftable tokens. - base := yyPact[state] + base := int(yyPact[state]) for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { - if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok { + if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { if len(expected) == cap(expected) { return res } @@ -201,13 +202,13 @@ func yyErrorMessage(state, lookAhead int) string { if yyDef[state] == -2 { i := 0 - for yyExca[i] != -1 || yyExca[i+1] != state { + for yyExca[i] != -1 || int(yyExca[i+1]) != state { i += 2 } // Look for tokens that we accept or reduce. for i += 2; yyExca[i] >= 0; i += 2 { - tok := yyExca[i] + tok := int(yyExca[i]) if tok < TOKSTART || yyExca[i+1] == 0 { continue } @@ -238,30 +239,30 @@ func yylex1(lex yyLexer, lval *yySymType) (char, token int) { token = 0 char = lex.Lex(lval) if char <= 0 { - token = yyTok1[0] + token = int(yyTok1[0]) goto out } if char < len(yyTok1) { - token = yyTok1[char] + token = int(yyTok1[char]) goto out } if char >= yyPrivate { if char < yyPrivate+len(yyTok2) { - token = yyTok2[char-yyPrivate] + token = int(yyTok2[char-yyPrivate]) goto out } } for i := 0; i < len(yyTok3); i += 2 { - token = yyTok3[i+0] + token = int(yyTok3[i+0]) if token == char { - token = yyTok3[i+1] + token = int(yyTok3[i+1]) goto out } } out: if token == 0 { - token = yyTok2[1] /* unknown char */ + token = int(yyTok2[1]) /* unknown char */ } if yyDebug >= 3 { __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) @@ -316,7 +317,7 @@ yystack: yyS[yyp].yys = yystate yynewstate: - yyn = yyPact[yystate] + yyn = int(yyPact[yystate]) if yyn <= yyFlag { goto yydefault /* simple state */ } @@ -327,8 +328,8 @@ yynewstate: if yyn < 0 || yyn >= yyLast { goto yydefault } - yyn = yyAct[yyn] - if yyChk[yyn] == yytoken { /* valid shift */ + yyn = int(yyAct[yyn]) + if int(yyChk[yyn]) == yytoken { /* valid shift */ yyrcvr.char = -1 yytoken = -1 yyVAL = yyrcvr.lval @@ -341,7 +342,7 @@ yynewstate: yydefault: /* default state action */ - yyn = yyDef[yystate] + yyn = int(yyDef[yystate]) if yyn == -2 { if yyrcvr.char < 0 { yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) @@ -350,18 +351,18 @@ yydefault: /* look through exception table */ xi := 0 for { - if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { break } xi += 2 } for xi += 2; ; xi += 2 { - yyn = yyExca[xi+0] + yyn = int(yyExca[xi+0]) if yyn < 0 || yyn == yytoken { break } } - yyn = yyExca[xi+1] + yyn = int(yyExca[xi+1]) if yyn < 0 { goto ret0 } @@ -383,10 +384,10 @@ yydefault: /* find a state where "error" is a legal shift action */ for yyp >= 0 { - yyn = yyPact[yyS[yyp].yys] + yyErrCode + yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode if yyn >= 0 && yyn < yyLast { - yystate = yyAct[yyn] /* simulate a shift of "error" */ - if yyChk[yystate] == yyErrCode { + yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ + if int(yyChk[yystate]) == yyErrCode { goto yystack } } @@ -422,7 +423,7 @@ yydefault: yypt := yyp _ = yypt // guard against "declared and not used" - yyp -= yyR2[yyn] + yyp -= int(yyR2[yyn]) // yyp is now the index of $0. Perform the default action. Iff the // reduced production is ε, $1 is possibly out of range. if yyp+1 >= len(yyS) { @@ -433,16 +434,16 @@ yydefault: yyVAL = yyS[yyp+1] /* consult goto table to find next state */ - yyn = yyR1[yyn] - yyg := yyPgo[yyn] + yyn = int(yyR1[yyn]) + yyg := int(yyPgo[yyn]) yyj := yyg + yyS[yyp].yys + 1 if yyj >= yyLast { - yystate = yyAct[yyg] + yystate = int(yyAct[yyg]) } else { - yystate = yyAct[yyj] - if yyChk[yystate] != -yyn { - yystate = yyAct[yyg] + yystate = int(yyAct[yyj]) + if int(yyChk[yystate]) != -yyn { + yystate = int(yyAct[yyg]) } } // dummy call; replaced with literal code diff --git a/xpath/y.output b/xpath/y.output index 5649f953..ef478f86 100644 --- a/xpath/y.output +++ b/xpath/y.output @@ -112,14 +112,14 @@ state 13 8 terminals, 6 nonterminals -11 grammar rules, 14/8000 states +11 grammar rules, 14/16000 states 0 shift/reduce, 0 reduce/reduce conflicts reported 55 working sets used -memory: parser 10/120000 +memory: parser 10/240000 0 extra closures 9 shift entries, 1 exceptions 8 goto entries 4 entries saved by goto default -Optimizer space used: output 15/120000 +Optimizer space used: output 15/240000 15 table entries, 0 zero maximum spread: 8, maximum offset: 9