diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d5eaad5..c5a3a42 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - go-version: [ '1.16', '1.21', '1.22.x' ] + go-version: [ '1.18', '1.21', '1.22.x' ] steps: - uses: actions/checkout@v4 diff --git a/etree.go b/etree.go index 4fbebb6..815b6c4 100644 --- a/etree.go +++ b/etree.go @@ -13,7 +13,7 @@ import ( "errors" "io" "os" - "sort" + "slices" "strings" ) @@ -858,12 +858,12 @@ func (e *Element) RemoveChildAt(index int) Token { // autoClose analyzes the stack's top element and the current token to decide // whether the top element should be closed. -func (e *Element) autoClose(stack *stack, t xml.Token, tags []string) { +func (e *Element) autoClose(stack *stack[*Element], t xml.Token, tags []string) { if stack.empty() { return } - top := stack.peek().(*Element) + top := stack.peek() for _, tag := range tags { if strings.EqualFold(tag, top.FullTag()) { @@ -891,7 +891,7 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er dec := newDecoder(r, settings) - var stack stack + var stack stack[*Element] stack.push(e) for { if pr != nil { @@ -916,7 +916,7 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er return r.Bytes(), ErrXML } - top := stack.peek().(*Element) + top := stack.peek() switch t := t.(type) { case xml.StartElement: @@ -1411,25 +1411,12 @@ func (e *Element) RemoveAttr(key string) *Attr { // SortAttrs sorts this element's attributes lexicographically by key. func (e *Element) SortAttrs() { - sort.Sort(byAttr(e.Attr)) -} - -type byAttr []Attr - -func (a byAttr) Len() int { - return len(a) -} - -func (a byAttr) Swap(i, j int) { - a[i], a[j] = a[j], a[i] -} - -func (a byAttr) Less(i, j int) bool { - sp := strings.Compare(a[i].Space, a[j].Space) - if sp == 0 { - return strings.Compare(a[i].Key, a[j].Key) < 0 - } - return sp < 0 + slices.SortFunc(e.Attr, func(a, b Attr) int { + if v := strings.Compare(a.Space, b.Space); v != 0 { + return v + } + return strings.Compare(a.Key, b.Key) + }) } // FullKey returns this attribute's complete key, including namespace prefix diff --git a/go.mod b/go.mod index a7573fd..2a3fa01 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/beevik/etree -go 1.16 +go 1.18 diff --git a/helpers.go b/helpers.go index 0b7c4ec..ea789b6 100644 --- a/helpers.go +++ b/helpers.go @@ -10,37 +10,36 @@ import ( "unicode/utf8" ) -// A simple stack -type stack struct { - data []interface{} +type stack[E any] struct { + data []E } -func (s *stack) empty() bool { +func (s *stack[E]) empty() bool { return len(s.data) == 0 } -func (s *stack) push(value interface{}) { +func (s *stack[E]) push(value E) { s.data = append(s.data, value) } -func (s *stack) pop() interface{} { +func (s *stack[E]) pop() E { value := s.data[len(s.data)-1] - s.data[len(s.data)-1] = nil + var empty E + s.data[len(s.data)-1] = empty s.data = s.data[:len(s.data)-1] return value } -func (s *stack) peek() interface{} { +func (s *stack[E]) peek() E { return s.data[len(s.data)-1] } -// A fifo is a simple first-in-first-out queue. -type fifo struct { - data []interface{} +type queue[E any] struct { + data []E head, tail int } -func (f *fifo) add(value interface{}) { +func (f *queue[E]) add(value E) { if f.len()+1 >= len(f.data) { f.grow() } @@ -50,33 +49,34 @@ func (f *fifo) add(value interface{}) { } } -func (f *fifo) remove() interface{} { +func (f *queue[E]) remove() E { value := f.data[f.head] - f.data[f.head] = nil + var empty E + f.data[f.head] = empty if f.head++; f.head == len(f.data) { f.head = 0 } return value } -func (f *fifo) len() int { +func (f *queue[E]) len() int { if f.tail >= f.head { return f.tail - f.head } return len(f.data) - f.head + f.tail } -func (f *fifo) grow() { +func (f *queue[E]) grow() { c := len(f.data) * 2 if c == 0 { c = 4 } - buf, count := make([]interface{}, c), f.len() + buf, count := make([]E, c), f.len() if f.tail >= f.head { - copy(buf[0:count], f.data[f.head:f.tail]) + copy(buf[:count], f.data[f.head:f.tail]) } else { hindex := len(f.data) - f.head - copy(buf[0:hindex], f.data[f.head:]) + copy(buf[:hindex], f.data[f.head:]) copy(buf[hindex:count], f.data[:f.tail]) } f.data, f.head, f.tail = buf, 0, count diff --git a/path.go b/path.go index 491dfc6..8d63096 100644 --- a/path.go +++ b/path.go @@ -152,7 +152,7 @@ type filter interface { // a Path object. It collects and deduplicates all elements matching // the path query. type pather struct { - queue fifo + queue queue[node] results []*Element inResults map[*Element]bool candidates []*Element @@ -180,7 +180,7 @@ func newPather() *pather { // and filters. func (p *pather) traverse(e *Element, path Path) []*Element { for p.queue.add(node{e, path.segments}); p.queue.len() > 0; { - p.eval(p.queue.remove().(node)) + p.eval(p.queue.remove()) } return p.results } @@ -406,9 +406,9 @@ func (s *selectChildren) apply(e *Element, p *pather) { type selectDescendants struct{} func (s *selectDescendants) apply(e *Element, p *pather) { - var queue fifo + var queue queue[*Element] for queue.add(e); queue.len() > 0; { - e := queue.remove().(*Element) + e := queue.remove() p.candidates = append(p.candidates, e) for _, c := range e.Child { if c, ok := c.(*Element); ok {