diff --git a/README.md b/README.md index c7c9f29..f015727 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This library is still in early stage. API may break. Missing functionality (see ### Install ```shell -go get github.com/maxyurk/go-xml-patch +go get github.com/jfrog/go-xml-patch ``` ### Code @@ -23,7 +23,7 @@ package main import ( "fmt" - "github.com/maxyurk/go-xml-patch" + "github.com/jfrog/go-xml-patch" "os" ) @@ -74,13 +74,13 @@ func main() { ### [Specification](https://www.rfc-editor.org/rfc/rfc5261) Items - [ ] 4.3. `` Element - - [ ] 4.3.1. Adding an Element + - [x] 4.3.1. Adding an Element - [ ] 4.3.2. Adding an Attribute - [ ] 4.3.3. Adding a Prefixed Namespace Declaration - [ ] 4.3.4. Adding Node(s) with the 'pos' Attribute - [ ] 4.3.5. Adding Multiple Nodes - [ ] 4.4. `` Element (with optional auto-create, see [#2](https://github.com/jfrog/go-xml-patch/issues/2)) - - [ ] 4.4.1. Replacing an Element + - [x] 4.4.1. Replacing an Element - [x] 4.4.2. Replacing an Attribute Value - [ ] 4.4.3. Replacing a Namespace Declaration URI - [ ] 4.4.4. Replacing a Comment Node diff --git a/go.mod b/go.mod index b16c2d4..48a8800 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/jfrog/go-xml-patch -go 1.20 +go 1.22 require ( - github.com/beevik/etree v1.1.0 - github.com/stretchr/testify v1.8.2 + github.com/beevik/etree v1.4.0 + github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 ) diff --git a/go.sum b/go.sum index 33c8f34..6173d91 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,14 @@ -github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= +github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testdata/add/element/1/diff.xml b/testdata/add/element/1/diff.xml new file mode 100644 index 0000000..689510e --- /dev/null +++ b/testdata/add/element/1/diff.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/testdata/add/element/1/domain.after.xml b/testdata/add/element/1/domain.after.xml new file mode 100644 index 0000000..a3cde8b --- /dev/null +++ b/testdata/add/element/1/domain.after.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + diff --git a/testdata/add/element/1/domain.before.xml b/testdata/add/element/1/domain.before.xml new file mode 100644 index 0000000..2c1ece1 --- /dev/null +++ b/testdata/add/element/1/domain.before.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + diff --git a/testdata/add/element/2/diff.xml b/testdata/add/element/2/diff.xml new file mode 100644 index 0000000..a2a9a0a --- /dev/null +++ b/testdata/add/element/2/diff.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/testdata/add/element/2/domain.after.xml b/testdata/add/element/2/domain.after.xml new file mode 100644 index 0000000..2c1ece1 --- /dev/null +++ b/testdata/add/element/2/domain.after.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + diff --git a/testdata/add/element/2/domain.before.xml b/testdata/add/element/2/domain.before.xml new file mode 100644 index 0000000..2c1ece1 --- /dev/null +++ b/testdata/add/element/2/domain.before.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + diff --git a/testdata/replace/element/1/diff.xml b/testdata/replace/element/1/diff.xml new file mode 100644 index 0000000..2a06d44 --- /dev/null +++ b/testdata/replace/element/1/diff.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/testdata/replace/element/1/domain.after.xml b/testdata/replace/element/1/domain.after.xml new file mode 100644 index 0000000..c850086 --- /dev/null +++ b/testdata/replace/element/1/domain.after.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/testdata/replace/element/1/domain.before.xml b/testdata/replace/element/1/domain.before.xml new file mode 100644 index 0000000..7eaa06b --- /dev/null +++ b/testdata/replace/element/1/domain.before.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + diff --git a/xmlpatch.go b/xmlpatch.go index 437c60b..cbb38b2 100644 --- a/xmlpatch.go +++ b/xmlpatch.go @@ -15,14 +15,16 @@ type Diff struct { } type Replace struct { - Sel string `xml:"sel,attr"` - Text string `xml:",chardata"` + Sel string `xml:"sel,attr"` + Text string `xml:",chardata"` + Content []byte `xml:",innerxml"` } type Add struct { - Pos string `xml:"pos,attr"` - Sel string `xml:"sel,attr"` - Content []byte `xml:",innerxml"` + Pos string `xml:"pos,attr"` + Sel string `xml:"sel,attr"` + RejectSel string `xml:"rejectsel,attr"` + Content []byte `xml:",innerxml"` } type Ops int @@ -47,10 +49,47 @@ func Patch(docData, xmlDiffData []byte, options ...Ops) ([]byte, error) { return nil, err } } + + for i, add := range diff.Adds { + if err := doAdd(add, i, doc); err != nil { + return nil, err + } + } + doc.Indent(4) return doc.WriteToBytes() } +func doAdd(add Add, i int, doc *etree.Document) error { + if add.RejectSel != "" { + uniqPath, err := etree.CompilePath(add.RejectSel) + if err != nil { + return fmt.Errorf("compile sel value %q of add diff entry #%d: %w", add.Sel, i, err) + } + + exists := doc.FindElementPath(uniqPath) + if exists != nil { + return nil + } + } + + newDoc := etree.NewDocument() + if err := newDoc.ReadFromBytes(add.Content); err != nil { + return fmt.Errorf("read content of add diff entry #%d. Sel value: '%v'. Error: %w", i, add.Sel, err) + } + + path, err := etree.CompilePath(add.Sel) + if err != nil { + return fmt.Errorf("compile sel value %q of add diff entry #%d: %w", add.Sel, i, err) + } + + elem := doc.FindElementPath(path) + + elem.AddChild(newDoc.Root()) + + return nil +} + func doReplace(replace Replace, i int, doc *etree.Document, options []Ops) error { xpath := replace.Sel attributeRefIndex := strings.LastIndex(xpath, "/@") @@ -69,22 +108,42 @@ func doReplace(replace Replace, i int, doc *etree.Document, options []Ops) error } createMissing(doc, xpath) elem := doc.FindElement(xpath) - doPatch(attributeRefIndex, elem, replace) + if err := doPatch(attributeRefIndex, elem, replace); err != nil { + return fmt.Errorf("do patch: %w", err) + } case 1: - elem := elems[0] - doPatch(attributeRefIndex, elem, replace) + if err := doPatch(attributeRefIndex, elems[0], replace); err != nil { + return fmt.Errorf("do patch: %w", err) + } + default: return fmt.Errorf("expected 1 match for '%v', got %v", xpath, len(elems)) } + return nil } -func doPatch(attributeRefIndex int, elem *etree.Element, replace Replace) { +func doPatch(attributeRefIndex int, elem *etree.Element, replace Replace) error { if attributeRefIndex != -1 { elem.CreateAttr(replace.Sel[attributeRefIndex+2:], replace.Text) } else { - elem.SetText(replace.Text) // TODO [Max]: test + if len(replace.Text) > 0 { + elem.SetText(replace.Text) // TODO [Max]: test + } + if len(replace.Content) > 0 { + newDoc := etree.NewDocument() + err := newDoc.ReadFromBytes(replace.Content) + if err != nil { + return fmt.Errorf("read replace content: %w\n", err) + } + + elem.Parent().InsertChildAt(elem.Index(), newDoc.Root()) + + elem.Parent().RemoveChild(elem) + } } + + return nil } func createMissing(doc *etree.Document, xpath string) { diff --git a/xmlpatch_test.go b/xmlpatch_test.go index da32314..bfdde19 100644 --- a/xmlpatch_test.go +++ b/xmlpatch_test.go @@ -74,6 +74,27 @@ func TestPatch(t *testing.T) { expectedFilepath: "testdata/replace/attribute/3/workspace.after.xml", wantErr: true, }, + { + name: "replace element", + docDataFilepath: "testdata/replace/element/1/domain.before.xml", + xmlDiffDataFilepath: "testdata/replace/element/1/diff.xml", + expectedFilepath: "testdata/replace/element/1/domain.after.xml", + wantErr: false, + }, + { + name: "add element", + docDataFilepath: "testdata/add/element/1/domain.before.xml", + xmlDiffDataFilepath: "testdata/add/element/1/diff.xml", + expectedFilepath: "testdata/add/element/1/domain.after.xml", + wantErr: false, + }, + { + name: "add duplicated element", + docDataFilepath: "testdata/add/element/2/domain.before.xml", + xmlDiffDataFilepath: "testdata/add/element/2/diff.xml", + expectedFilepath: "testdata/add/element/2/domain.after.xml", + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {