Skip to content

Commit

Permalink
Add PreserveDuplicateAttrs to ReadSettings
Browse files Browse the repository at this point in the history
When an element has two or more attributes with the same
name, they are all preserved instead of leaving only one.
This setting is disabled by default.
  • Loading branch information
Tobias Theel authored and beevik committed Jul 15, 2023
1 parent f9721ff commit f3d944a
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 7 deletions.
21 changes: 14 additions & 7 deletions etree.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ type ReadSettings struct {
// false.
PreserveCData bool

// When an element has two or more attributes with the same name,
// preserve them instead of keeping only one. Default: false.
PreserveDuplicateAttrs bool

// Entity to be passed to standard xml.Decoder. Default: nil.
Entity map[string]string
}
Expand Down Expand Up @@ -836,7 +840,7 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er
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)
e.createAttr(a.Name.Space, a.Name.Local, a.Value, e, settings.PreserveDuplicateAttrs)
}
stack.push(e)
case xml.EndElement:
Expand Down Expand Up @@ -1232,17 +1236,20 @@ 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)
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) *Attr {
for i, a := range e.Attr {
if space == a.Space && key == a.Key {
e.Attr[i].Value = value
return &e.Attr[i]
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]
}
}
}

a := Attr{
Space: space,
Key: key,
Expand Down
69 changes: 69 additions & 0 deletions etree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1394,3 +1394,72 @@ func TestReindexChildren(t *testing.T) {
}
}
}

func TestPreserveDuplicateAttrs(t *testing.T) {
s := `
<document attr="test" attr="test2"></document>
`
t.Run("Test PreserveDuplicateAttributes", func(t *testing.T) {
doc := NewDocument()

doc.ReadSettings = ReadSettings{
PreserveDuplicateAttrs: true,
}

err := doc.ReadFromString(s)
if err != nil {
t.Error("etree: unable to read document", err)
}

document := doc.FindElement("document")

if len(document.Attr) != 2 {
t.Error("etree: should have found 2 attributes")
}

if document.Attr[0].Value != "test" {
t.Errorf("etree: expected value test got %s", document.Attr[0].Value)
}

if document.Attr[0].Key != "attr" {
t.Errorf("etree: expected attribute key to be attr got %s", document.Attr[0].Key)
}

if document.Attr[1].Value != "test2" {
t.Errorf("etree: expected value test2 got %s", document.Attr[0].Value)
}

if document.Attr[1].Key != "attr" {
t.Errorf("etree: expected attribute key to be attr got %s", document.Attr[0].Key)
}

})

t.Run("Test Default Settings", func(t *testing.T) {
doc := NewDocument()

doc.ReadSettings = ReadSettings{
PreserveDuplicateAttrs: false,
}

err := doc.ReadFromString(s)
if err != nil {
t.Error("etree: unable to read document", err)
}

document := doc.FindElement("document")

if len(document.Attr) != 1 {
t.Error("etree: should have found 1 attribute")
}
if document.Attr[0].Value != "test2" {
t.Errorf("etree: expected value test got %s", document.Attr[0].Value)
}

if document.Attr[0].Key != "attr" {
t.Errorf("etree: expected attribute key to be attr got %s", document.Attr[0].Key)
}

})

}

0 comments on commit f3d944a

Please sign in to comment.