diff --git a/etree.go b/etree.go
index fedf2ca..536e4fc 100644
--- a/etree.go
+++ b/etree.go
@@ -887,6 +887,7 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er
r = newXmlSimpleReader(ri)
}
+ attrCheck := make(map[xml.Name]int)
dec := newDecoder(r, settings)
var stack stack[*Element]
@@ -919,8 +920,19 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er
switch t := t.(type) {
case xml.StartElement:
e := newElement(t.Name.Space, t.Name.Local, top)
- for _, a := range t.Attr {
- e.createAttr(a.Name.Space, a.Name.Local, a.Value, e, settings.PreserveDuplicateAttrs)
+ if settings.PreserveDuplicateAttrs || len(t.Attr) < 2 {
+ for _, a := range t.Attr {
+ e.addAttr(a.Name.Space, a.Name.Local, a.Value)
+ }
+ } else {
+ for _, a := range t.Attr {
+ if i, contains := attrCheck[a.Name]; contains {
+ e.Attr[i].Value = a.Value
+ } else {
+ attrCheck[a.Name] = e.addAttr(a.Name.Space, a.Name.Local, a.Value)
+ }
+ }
+ clear(attrCheck)
}
stack.push(e)
case xml.EndElement:
@@ -1363,28 +1375,29 @@ func (e *Element) addChild(t Token) {
// prefix followed by a colon.
func (e *Element) CreateAttr(key, value string) *Attr {
space, skey := spaceDecompose(key)
- return e.createAttr(space, skey, value, e, false)
-}
-// createAttr is a helper function that creates attributes.
-func (e *Element) createAttr(space, key, value string, parent *Element, preserveDups bool) *Attr {
- if !preserveDups {
- for i, a := range e.Attr {
- if space == a.Space && key == a.Key {
- e.Attr[i].Value = value
- return &e.Attr[i]
- }
+ for i, a := range e.Attr {
+ if space == a.Space && skey == a.Key {
+ e.Attr[i].Value = value
+ return &e.Attr[i]
}
}
+ i := e.addAttr(space, skey, value)
+ return &e.Attr[i]
+}
+
+// addAttr is a helper function that adds an attribute to an element. Returns
+// the index of the added attribute.
+func (e *Element) addAttr(space, key, value string) int {
a := Attr{
Space: space,
Key: key,
Value: value,
- element: parent,
+ element: e,
}
e.Attr = append(e.Attr, a)
- return &e.Attr[len(e.Attr)-1]
+ return len(e.Attr) - 1
}
// RemoveAttr removes the first attribute of this element whose key matches
diff --git a/etree_test.go b/etree_test.go
index b984c7e..d34a800 100644
--- a/etree_test.go
+++ b/etree_test.go
@@ -1469,7 +1469,7 @@ func TestReindexChildren(t *testing.T) {
}
func TestPreserveDuplicateAttrs(t *testing.T) {
- s := ``
+ s := ``
checkAttrCount := func(e *Element, n int) {
if len(e.Attr) != n {
@@ -1492,23 +1492,20 @@ func TestPreserveDuplicateAttrs(t *testing.T) {
t.Run("enabled", func(t *testing.T) {
doc := newDocumentFromString2(t, s, ReadSettings{PreserveDuplicateAttrs: true})
e := doc.FindElement("element")
- checkAttrCount(e, 2)
- checkAttr(e, 0, "attr", "test")
- checkAttr(e, 1, "attr", "test2")
+ checkAttrCount(e, 5)
+ checkAttr(e, 0, "x", "value1")
+ checkAttr(e, 1, "y", "value2")
+ checkAttr(e, 2, "x", "value3")
+ checkAttr(e, 3, "x", "value4")
+ checkAttr(e, 4, "y", "value5")
})
t.Run("disabled", func(t *testing.T) {
- doc := newDocumentFromString2(t, s, ReadSettings{PreserveDuplicateAttrs: false})
+ doc := newDocumentFromString2(t, s, ReadSettings{})
e := doc.FindElement("element")
- checkAttrCount(e, 1)
- checkAttr(e, 0, "attr", "test2")
- })
-
- t.Run("default", func(t *testing.T) {
- doc := newDocumentFromString(t, s)
- e := doc.FindElement("element")
- checkAttrCount(e, 1)
- checkAttr(e, 0, "attr", "test2")
+ checkAttrCount(e, 2)
+ checkAttr(e, 0, "x", "value4")
+ checkAttr(e, 1, "y", "value5")
})
}