Skip to content

Commit

Permalink
etreeutils: sort attrs with matching namespaces by their NS URIs
Browse files Browse the repository at this point in the history
In several of the XML Canonicalization specifications it states that
attributes with matching namespaces need to be sorted by their
Namespace URIs and not the prefixes.

> However, the namespace URI was selected as the primary key because
> this is closer to the intent of the Namespaces in XML specification,
> which is to identify namespaces by URI and local name, not by a prefix
> and local name.

The specifications call out that sorting by prefix is easier, but incorrect.

Further, the specifications state:

> An element's attribute nodes are sorted lexicographically with
> namespace URI as the primary key and local name as the secondary
> key (an empty namespace URI is lexicographically least).

Sources:

- https://www.w3.org/TR/xml-c14n11/#SortByNSURI
- https://www.w3.org/TR/2007/CR-xml-c14n11-20070621/#SortByNSURI
- https://www.w3.org/TR/2001/REC-xml-c14n-20010315#SortByNSURI
  • Loading branch information
adamdecaf committed Apr 11, 2023
1 parent 1abeb1b commit 436aac5
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 4 deletions.
21 changes: 17 additions & 4 deletions etreeutils/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,25 @@ func (a SortedAttrs) Less(i, j int) bool {
return false
}

// Wow. We're still going. Finally, attributes in the same namespace should be
// sorted by key. Attributes in different namespaces should be sorted by the
// actual namespace (_not_ the prefix). For now just use the prefix.
// Attributes with the same prefix should be sorted by their keys.
if a[i].Space == a[j].Space {
return a[i].Key < a[j].Key
}

return a[i].Space < a[j].Space
// Attributes in the same namespace are sorted by their Namespace URI, not the prefix.
if a[i].Key == a[j].Key {
var leftNS, rightNS etree.Attr
for n := range a {
if a[i].Space == a[n].Key {
leftNS = a[n]
}
if a[j].Space == a[n].Key {
rightNS = a[n]
}
}
// Sort based on the NS URIs
return leftNS.Value < rightNS.Value
}

return a[i].Key < a[j].Key
}
30 changes: 30 additions & 0 deletions etreeutils/sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package etreeutils

import (
"sort"
"testing"

"github.com/beevik/etree"
"github.com/stretchr/testify/require"
)

func TestSortedAttrs(t *testing.T) {
// Adapted from https://www.w3.org/TR/2001/REC-xml-c14n-20010315#Example-SETags
input := `<e5 a:attr="out" b:attr="sorted" attr2="all" attr="I m" xmlns:b="http://www.ietf.org" xmlns:a="http://www.w3.org" xmlns="http://example.org"></e5>`
expected := `<e5 xmlns="http://example.org" xmlns:a="http://www.w3.org" xmlns:b="http://www.ietf.org" attr="I m" attr2="all" b:attr="sorted" a:attr="out"></e5>`

inDoc := etree.NewDocument()
inDoc.ReadFromString(input)

outElm := inDoc.Root().Copy()
sort.Sort(SortedAttrs(outElm.Attr))
outDoc := etree.NewDocument()
outDoc.SetRoot(outElm)
outDoc.WriteSettings = etree.WriteSettings{
CanonicalEndTags: true,
}

outStr, err := outDoc.WriteToString()
require.NoError(t, err)
require.Equal(t, expected, outStr)
}

0 comments on commit 436aac5

Please sign in to comment.