mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-13 16:04:44 +00:00
68 lines
2.0 KiB
Go
68 lines
2.0 KiB
Go
// Package casefold makes a case-insensitive search able to quote what it found.
|
|
//
|
|
// The obvious way to search text without regard to case is to lowercase both
|
|
// sides and take strings.Index. The offset that comes back addresses the folded
|
|
// copy, not the original, and the two do not line up: strings.ToLower maps rune
|
|
// by rune, and a rune can change byte length doing it (U+0130 is two bytes and
|
|
// folds to one, U+212A is three). Past the first such rune, slicing the original
|
|
// at a folded offset lands mid-rune. It quotes bytes the writer never typed, or
|
|
// cuts one in half into invalid UTF-8.
|
|
//
|
|
// Index hands back the map that fixes it, so a match found in the fold can be
|
|
// quoted from the source exactly as it was written.
|
|
package casefold
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Index folds s to lower case and returns the folded text together with, for
|
|
// every byte offset in it, the offset it came from in s. The map has one entry
|
|
// past the end, so the end of a match at the end of the text maps too.
|
|
//
|
|
// A nil map means the two are byte-for-byte aligned and offsets need no
|
|
// translation, which is the case for ASCII text and so for almost every call.
|
|
func Index(s string) (string, []int) {
|
|
if isASCII(s) {
|
|
return strings.ToLower(s), nil
|
|
}
|
|
var b strings.Builder
|
|
b.Grow(len(s))
|
|
offsets := make([]int, 0, len(s)+1)
|
|
for i, r := range s {
|
|
before := b.Len()
|
|
b.WriteRune(unicode.ToLower(r))
|
|
for n := b.Len() - before; n > 0; n-- {
|
|
offsets = append(offsets, i)
|
|
}
|
|
}
|
|
offsets = append(offsets, len(s))
|
|
return b.String(), offsets
|
|
}
|
|
|
|
// Origin translates a byte offset in the folded copy back to the original. It
|
|
// returns -1 for an offset the map does not cover, which a caller must treat as
|
|
// "cannot quote this" rather than as position zero.
|
|
func Origin(offsets []int, at int) int {
|
|
if offsets == nil {
|
|
if at < 0 {
|
|
return -1
|
|
}
|
|
return at
|
|
}
|
|
if at < 0 || at >= len(offsets) {
|
|
return -1
|
|
}
|
|
return offsets[at]
|
|
}
|
|
|
|
func isASCII(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] >= 0x80 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|