* docs: add literary works design and plan * feat(literary): add work link schema * feat(literary): add work domain primitives * feat(literary): persist work links * feat(literary): score work matches * feat(catalog): include literary work summary on item detail * feat(literary): expose work detail API * feat(literary): assemble work detail * feat(literary): add admin work linking primitives * feat(catalog): group literary items by work * feat(literary): auto-link works during book scans * fix(literary): narrow work match candidates * fix(literary): address work merge blockers --------- Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com> Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
48 lines
843 B
Go
48 lines
843 B
Go
package literaryworks
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
var leadingArticles = map[string]struct{}{
|
|
"a": {},
|
|
"an": {},
|
|
"the": {},
|
|
}
|
|
|
|
func normalizeKey(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
var b strings.Builder
|
|
lastSpace := true
|
|
for _, r := range value {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
b.WriteRune(r)
|
|
lastSpace = false
|
|
continue
|
|
}
|
|
if !lastSpace {
|
|
b.WriteByte(' ')
|
|
lastSpace = true
|
|
}
|
|
}
|
|
parts := strings.Fields(b.String())
|
|
filtered := parts[:0]
|
|
for _, part := range parts {
|
|
if _, ok := leadingArticles[part]; ok {
|
|
continue
|
|
}
|
|
filtered = append(filtered, part)
|
|
}
|
|
return strings.Join(filtered, " ")
|
|
}
|
|
|
|
func personKey(names []string) string {
|
|
for _, name := range names {
|
|
if key := normalizeKey(name); key != "" {
|
|
return key
|
|
}
|
|
}
|
|
return ""
|
|
}
|