86 lines
2.6 KiB
Go
86 lines
2.6 KiB
Go
// Package normalize provides string normalization helpers used for
|
|
// fuzzy matching across NaviWatcher (artist names, album titles, etc.).
|
|
//
|
|
// It is the single shared home for normalization logic; previously this
|
|
// lived inside the musicbrainz package but is needed by the scanner engine
|
|
// and any other consumer that compares strings.
|
|
package normalize
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Precompiled regexes — compiled once at package init.
|
|
var (
|
|
bracketRe = regexp.MustCompile(`\[[^\]]*\]`)
|
|
parenRe = regexp.MustCompile(`\([^)]*\)`)
|
|
yearRe = regexp.MustCompile(`\b(1[0-9]{3}|2[0-9]{3})\b`)
|
|
spaceRe = regexp.MustCompile(`\s+`)
|
|
)
|
|
|
|
// NormalizeString normalizes a string for fuzzy matching by:
|
|
// - Converting to lowercase
|
|
// - Removing special characters (keeping only letters, digits, and spaces)
|
|
// - Removing years (4-digit numbers that look like years)
|
|
// - Removing bracketed keywords (e.g., [Deluxe], [Remastered])
|
|
// - Collapsing multiple spaces into one
|
|
// - Trimming leading/trailing whitespace
|
|
func NormalizeString(s string) string {
|
|
// Convert to lowercase
|
|
s = strings.ToLower(s)
|
|
|
|
// Remove bracketed content first (e.g., [Deluxe Edition], [Remastered 2020])
|
|
s = bracketRe.ReplaceAllString(s, "")
|
|
|
|
// Remove parenthesized content (e.g., (Deluxe), (Remastered))
|
|
s = parenRe.ReplaceAllString(s, "")
|
|
|
|
// Remove years (4-digit numbers between 1000-2999). If stripping the
|
|
// year would empty the entire string (e.g. an album literally titled
|
|
// "1989" or "2112"), keep the original form so the title can still match.
|
|
stripped := yearRe.ReplaceAllString(s, "")
|
|
if strings.TrimSpace(stripped) != "" {
|
|
s = stripped
|
|
}
|
|
|
|
// Replace common separators with spaces before stripping other special chars
|
|
s = strings.ReplaceAll(s, "-", " ")
|
|
s = strings.ReplaceAll(s, "_", " ")
|
|
|
|
// Keep only letters, digits, and spaces
|
|
var b strings.Builder
|
|
for _, r := range s {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsSpace(r) {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
s = b.String()
|
|
|
|
// Collapse multiple spaces
|
|
s = spaceRe.ReplaceAllString(s, " ")
|
|
|
|
// Trim
|
|
s = strings.TrimSpace(s)
|
|
|
|
return s
|
|
}
|
|
|
|
// NormalizeArtistName normalizes an artist name for comparison.
|
|
// It applies NormalizeString and additionally handles common prefixes.
|
|
func NormalizeArtistName(name string) string {
|
|
name = NormalizeString(name)
|
|
|
|
// Remove common leading articles for better matching
|
|
prefixes := []string{"the ", "a ", "an "}
|
|
for _, prefix := range prefixes {
|
|
if strings.HasPrefix(name, prefix) {
|
|
name = strings.TrimPrefix(name, prefix)
|
|
break
|
|
}
|
|
}
|
|
|
|
return strings.TrimSpace(name)
|
|
}
|