Skip to content

Commit

Permalink
Add support for adding and replacing elements. (#3)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleg Neumyvakin <oleg.neumyvakin@webpros.com>
  • Loading branch information
oneumyvakin and Oleg Neumyvakin authored Jul 19, 2024
1 parent 755e4c0 commit 083785c
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 28 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,7 +23,7 @@ package main

import (
"fmt"
"github.com/maxyurk/go-xml-patch"
"github.com/jfrog/go-xml-patch"
"os"
)

Expand Down Expand Up @@ -74,13 +74,13 @@ func main() {
### [Specification](https://www.rfc-editor.org/rfc/rfc5261) Items

- [ ] 4.3. `<add>` 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. `<replace>` 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
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)

Expand Down
15 changes: 4 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
11 changes: 11 additions & 0 deletions testdata/add/element/1/diff.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<diff>
<add sel="domain/devices">
<interface type="added">
<mac address="mac3"/>
<source bridge="br-int"/>
<virtualport type="openvswitch"/>
<target dev="dev3"/>
<model type="virtio"/>
</interface>
</add>
</diff>
33 changes: 33 additions & 0 deletions testdata/add/element/1/domain.after.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="bridge">
<mac address="mac1"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid1"/>
</virtualport>
<target dev="dev1"/>
<model type="virtio"/>
<alias name="net1"/>
<address type="pci" domain="0x0001" bus="0x01" slot="0x01" function="0x1"/>
</interface>
<interface type="bridge">
<mac address="mac2"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid2"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net2"/>
<address type="pci" domain="0x0002" bus="0x02" slot="0x02" function="0x2"/>
</interface>
<interface type="added">
<mac address="mac3"/>
<source bridge="br-int"/>
<virtualport type="openvswitch"/>
<target dev="dev3"/>
<model type="virtio"/>
</interface>
</devices>
</domain>
26 changes: 26 additions & 0 deletions testdata/add/element/1/domain.before.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="bridge">
<mac address="mac1"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid1"/>
</virtualport>
<target dev="dev1"/>
<model type="virtio"/>
<alias name="net1"/>
<address type="pci" domain="0x0001" bus="0x01" slot="0x01" function="0x1"/>
</interface>
<interface type="bridge">
<mac address="mac2"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid2"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net2"/>
<address type="pci" domain="0x0002" bus="0x02" slot="0x02" function="0x2"/>
</interface>
</devices>
</domain>
11 changes: 11 additions & 0 deletions testdata/add/element/2/diff.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<diff>
<add sel="domain/devices" rejectsel="//interface/mac[@address='mac2']">
<interface type="added">
<mac address="mac2"/>
<source bridge="br-int"/>
<virtualport type="openvswitch"/>
<target dev="dev2"/>
<model type="virtio"/>
</interface>
</add>
</diff>
26 changes: 26 additions & 0 deletions testdata/add/element/2/domain.after.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="bridge">
<mac address="mac1"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid1"/>
</virtualport>
<target dev="dev1"/>
<model type="virtio"/>
<alias name="net1"/>
<address type="pci" domain="0x0001" bus="0x01" slot="0x01" function="0x1"/>
</interface>
<interface type="bridge">
<mac address="mac2"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid2"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net2"/>
<address type="pci" domain="0x0002" bus="0x02" slot="0x02" function="0x2"/>
</interface>
</devices>
</domain>
26 changes: 26 additions & 0 deletions testdata/add/element/2/domain.before.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="bridge">
<mac address="mac1"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid1"/>
</virtualport>
<target dev="dev1"/>
<model type="virtio"/>
<alias name="net1"/>
<address type="pci" domain="0x0001" bus="0x01" slot="0x01" function="0x1"/>
</interface>
<interface type="bridge">
<mac address="mac2"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="interfaceid2"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net2"/>
<address type="pci" domain="0x0002" bus="0x02" slot="0x02" function="0x2"/>
</interface>
</devices>
</domain>
11 changes: 11 additions & 0 deletions testdata/replace/element/1/diff.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<diff>
<replace sel="//interface/mac[@address='52:54:00:31:3d:3e']/..">
<interface type="replaced">
<mac address="52:54:00:31:3d:3e"/>
<source bridge="br-int"/>
<virtualport type="openvswitch" />
<target dev="dev3"/>
<model type="virtio"/>
</interface>
</replace>
</diff>
22 changes: 22 additions & 0 deletions testdata/replace/element/1/domain.after.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="replaced">
<mac address="52:54:00:31:3d:3e"/>
<source bridge="br-int"/>
<virtualport type="openvswitch"/>
<target dev="dev3"/>
<model type="virtio"/>
</interface>
<interface type="bridge">
<mac address="52:54:00:31:3d:3d"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="01fb70b1-6df4-4786-aa08-e5e9427662f0"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net0"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
</interface>
</devices>
</domain>
26 changes: 26 additions & 0 deletions testdata/replace/element/1/domain.before.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<domain type="kvm" id="98" xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0">
<devices>
<interface type="bridge">
<mac address="52:54:00:31:3d:3e"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="01fb70b1-6df4-4786-aa08-e5e9427662f0"/>
</virtualport>
<target dev="dev1"/>
<model type="virtio"/>
<alias name="net0"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
</interface>
<interface type="bridge">
<mac address="52:54:00:31:3d:3d"/>
<source bridge="br-int"/>
<virtualport type="openvswitch">
<parameters interfaceid="01fb70b1-6df4-4786-aa08-e5e9427662f0"/>
</virtualport>
<target dev="dev2"/>
<model type="virtio"/>
<alias name="net0"/>
<address type="pci" domain="0x0000" bus="0x00" slot="0x03" function="0x0"/>
</interface>
</devices>
</domain>
79 changes: 69 additions & 10 deletions xmlpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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, "/@")
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit 083785c

Please sign in to comment.