2017-09-19 11:37:03 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2017-09-19 11:37:03 +03:00
package base
import (
2024-06-17 09:45:12 +03:00
"unicode/utf8"
2024-03-05 18:13:35 +03:00
"golang.org/x/text/collate"
"golang.org/x/text/language"
2017-09-19 11:37:03 +03:00
)
2024-06-17 09:45:12 +03:00
func naturalSortGetRune ( str string , pos int ) ( r rune , size int , has bool ) {
if pos >= len ( str ) {
return 0 , 0 , false
}
r , size = utf8 . DecodeRuneInString ( str [ pos : ] )
if r == utf8 . RuneError {
r , size = rune ( str [ pos ] ) , 1 // if invalid input, treat it as a single byte ascii
}
return r , size , true
}
func naturalSortAdvance ( str string , pos int ) ( end int , isNumber bool ) {
end = pos
for {
r , size , has := naturalSortGetRune ( str , end )
if ! has {
break
}
isCurRuneNum := '0' <= r && r <= '9'
if end == pos {
isNumber = isCurRuneNum
end += size
} else if isCurRuneNum == isNumber {
end += size
} else {
break
}
}
return end , isNumber
}
2017-09-19 11:37:03 +03:00
// NaturalSortLess compares two strings so that they could be sorted in natural order
func NaturalSortLess ( s1 , s2 string ) bool {
2024-06-17 09:45:12 +03:00
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
// So we need to handle the number parts by ourselves
2024-03-05 18:13:35 +03:00
c := collate . New ( language . English , collate . Numeric )
2024-06-17 09:45:12 +03:00
pos1 , pos2 := 0 , 0
for pos1 < len ( s1 ) && pos2 < len ( s2 ) {
end1 , isNum1 := naturalSortAdvance ( s1 , pos1 )
end2 , isNum2 := naturalSortAdvance ( s2 , pos2 )
part1 , part2 := s1 [ pos1 : end1 ] , s2 [ pos2 : end2 ]
if isNum1 && isNum2 {
if part1 != part2 {
if len ( part1 ) != len ( part2 ) {
return len ( part1 ) < len ( part2 )
}
return part1 < part2
}
} else {
if cmp := c . CompareString ( part1 , part2 ) ; cmp != 0 {
return cmp < 0
}
}
pos1 , pos2 = end1 , end2
}
return len ( s1 ) < len ( s2 )
2017-09-19 11:37:03 +03:00
}