Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions etree.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,20 @@ func NewDocumentWithRoot(e *Element) *Document {

// Copy returns a recursive, deep copy of the document.
func (d *Document) Copy() *Document {
return &Document{
nd := &Document{
Element: *(d.Element.dup(nil).(*Element)),
ReadSettings: d.ReadSettings.dup(),
WriteSettings: d.WriteSettings.dup(),
}
// The duplicated container was copied by value into the document.
// Rebind its direct children and attributes to the embedded element.
for _, t := range nd.Child {
t.setParent(&nd.Element)
}
for i := range nd.Attr {
nd.Attr[i].element = &nd.Element
}
return nd
}

// Root returns the root element of the document. It returns nil if there is
Expand Down Expand Up @@ -557,7 +566,9 @@ func newElement(space, tag string, parent *Element) *Element {
// another element using AddChild, or added to a document with SetRoot or
// NewDocumentWithRoot.
func (e *Element) Copy() *Element {
return e.dup(nil).(*Element)
ne := e.dup(nil).(*Element)
ne.index = -1
return ne
}

// FullTag returns the element e's complete tag, including namespace prefix if
Expand Down Expand Up @@ -1360,6 +1371,9 @@ func (e *Element) dup(parent *Element) Token {
ne.Child[i] = t.dup(ne)
}
copy(ne.Attr, e.Attr)
for i := range ne.Attr {
ne.Attr[i].element = ne
}
return ne
}

Expand Down
48 changes: 48 additions & 0 deletions etree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,54 @@ func TestSetTail(t *testing.T) {
checkIntEq(t, len(child.Child), 1)
}

func TestCopiedDocumentChildParent(t *testing.T) {
doc := newDocumentFromString(t, `<?xml version="1.0"?><!--comment--><root/>`)
doc.CreateAttr("container", "original")
copied := doc.Copy()
for _, token := range copied.Child {
checkElementEq(t, token.Parent(), &copied.Element)
}
checkElementEq(t, copied.Attr[0].Element(), &copied.Element)
if !copied.Root().Remove() {
t.Fatal("failed to remove copied root")
}
checkElementEq(t, copied.Root(), nil)
if doc.Root() == nil {
t.Fatal("removing copied root changed the original document")
}
}

func TestCopiedAttrParent(t *testing.T) {
for _, kind := range []string{"element", "document"} {
t.Run(kind, func(t *testing.T) {
doc := newDocumentFromString(t, `<root xmlns:p="urn:original" p:id="root"><child p:id="child"/></root>`)
var root *Element
if kind == "document" {
root = doc.Copy().Root()
} else {
root = doc.Root().Copy()
checkElementEq(t, root.Parent(), nil)
checkIntEq(t, root.Index(), -1)
}
child := root.SelectElement("child")
for _, element := range []*Element{root, child} {
for i := range element.Attr {
checkElementEq(t, element.Attr[i].Element(), element)
}
}

// Namespace lookup and edits through Attr.Element must stay in the copy.
root.CreateAttr("xmlns:p", "urn:copy")
checkStrEq(t, root.SelectAttr("p:id").NamespaceURI(), "urn:copy")
checkStrEq(t, child.SelectAttr("p:id").NamespaceURI(), "urn:copy")
child.SelectAttr("p:id").Element().CreateAttr("edited", "yes")
checkStrEq(t, child.SelectAttrValue("edited", ""), "yes")
checkStrEq(t, doc.Root().SelectElement("child").SelectAttrValue("edited", ""), "")
checkStrEq(t, doc.Root().SelectAttr("p:id").NamespaceURI(), "urn:original")
})
}
}

func TestAttrParent(t *testing.T) {
doc := NewDocument()
root := doc.CreateElement("root")
Expand Down