Files
MeBox/internal/service/recognition_words_rules.go
T
truewhile b855e00345 优化
2026-09-10 16:58:41 +08:00

194 lines
5.3 KiB
Go

package service
import (
"fmt"
"regexp"
"strconv"
"strings"
)
func parseRecognitionWordRules(raw string) []recognitionWordRule {
var out []recognitionWordRule
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
continue
}
out = append(out, parseRecognitionWordRule(line))
}
return out
}
func parseRecognitionWordRule(line string) recognitionWordRule {
rule := recognitionWordRule{raw: line}
for _, part := range strings.Split(line, "&&") {
part = strings.TrimSpace(part)
switch {
case strings.Contains(part, "=>"):
pieces := strings.SplitN(part, "=>", 2)
rule.replaceFrom = strings.TrimSpace(pieces[0])
rule.replaceTo = normalizeRecognitionReplacement(strings.TrimSpace(pieces[1]))
case strings.Contains(part, "<>") && strings.Contains(part, ">>"):
beforeAfter := strings.SplitN(part, ">>", 2)
bounds := strings.SplitN(beforeAfter[0], "<>", 2)
// A malformed rule without "<>" would otherwise index past the
// slice; word lists are fetched from the network, so stay defensive.
if len(bounds) < 2 {
continue
}
rule.offsetLeft = strings.TrimSpace(bounds[0])
rule.offsetRight = strings.TrimSpace(bounds[1])
rule.offsetExpr = strings.TrimSpace(beforeAfter[1])
default:
rule.block = part
}
}
compileRecognitionWordRule(&rule)
return rule
}
var recognitionReplacementRE = regexp.MustCompile(`\\([0-9]+)`)
func normalizeRecognitionReplacement(value string) string {
return recognitionReplacementRE.ReplaceAllString(value, "$$$1")
}
// compileRecognitionWordRule pre-compiles every pattern in a rule so the hot
// clean-query path never recompiles regexes per candidate.
func compileRecognitionWordRule(rule *recognitionWordRule) {
if rule == nil {
return
}
if rule.block != "" {
if re, err := regexp.Compile(rule.block); err == nil {
rule.blockRE = re
}
}
if rule.replaceFrom != "" {
if re, err := regexp.Compile(rule.replaceFrom); err == nil {
rule.replaceRE = re
}
}
if rule.offsetExpr != "" && (rule.offsetLeft != "" || rule.offsetRight != "") {
if re, err := compileRecognitionOffsetRE(rule.offsetLeft, rule.offsetRight); err == nil {
rule.offsetRE = re
}
}
}
func compileRecognitionOffsetRE(left, right string) (*regexp.Regexp, error) {
leftPattern := firstNonEmpty(left, `^`)
rightPattern := firstNonEmpty(right, `$`)
return regexp.Compile(`(?i)(` + leftPattern + `)(\d{1,5})(` + rightPattern + `)`)
}
func applyRecognitionWordRules(raw string, rules []recognitionWordRule) string {
out := strings.TrimSpace(raw)
for _, rule := range rules {
if rule.block != "" {
out = applyRecognitionBlock(out, rule)
}
if rule.replaceFrom != "" {
out = applyRecognitionReplace(out, rule)
}
if rule.offsetLeft != "" || rule.offsetRight != "" {
out = applyRecognitionOffset(out, rule)
}
}
return strings.Join(strings.Fields(out), " ")
}
func applyRecognitionBlock(raw string, rule recognitionWordRule) string {
if rule.blockRE != nil {
return rule.blockRE.ReplaceAllString(raw, " ")
}
return strings.ReplaceAll(raw, rule.block, " ")
}
func applyRecognitionReplace(raw string, rule recognitionWordRule) string {
if rule.replaceRE != nil {
return rule.replaceRE.ReplaceAllString(raw, rule.replaceTo)
}
return strings.ReplaceAll(raw, rule.replaceFrom, rule.replaceTo)
}
func applyRecognitionOffset(raw string, rule recognitionWordRule) string {
if strings.TrimSpace(rule.offsetExpr) == "" {
return raw
}
re := rule.offsetRE
if re == nil {
compiled, err := compileRecognitionOffsetRE(rule.offsetLeft, rule.offsetRight)
if err != nil {
return raw
}
re = compiled
}
return re.ReplaceAllStringFunc(raw, func(match string) string {
return applyRecognitionOffsetMatch(re, match, rule.offsetExpr)
})
}
func applyRecognitionOffsetMatch(re *regexp.Regexp, match, expr string) string {
parts := re.FindStringSubmatch(match)
if len(parts) < 4 {
return match
}
ep, err := strconv.Atoi(parts[2])
if err != nil {
return match
}
next, ok := evalRecognitionEpisodeExpr(expr, ep)
if !ok || next < 0 {
return match
}
format := "%d"
if width := len(parts[2]); width > 1 && width <= 2 {
format = "%0" + strconv.Itoa(width) + "d"
}
return parts[1] + fmt.Sprintf(format, next) + parts[3]
}
func evalRecognitionEpisodeExpr(expr string, ep int) (int, bool) {
value := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(expr), " ", ""))
if value == "" || value == "EP" {
return ep, true
}
if n, err := strconv.Atoi(value); err == nil {
return n, true
}
if out, ok := evalRecognitionEpisodeAddSub(value, ep); ok {
return out, true
}
return evalRecognitionEpisodeMul(value, ep)
}
func evalRecognitionEpisodeAddSub(value string, ep int) (int, bool) {
for _, op := range []string{"+", "-"} {
if !strings.HasPrefix(value, "EP"+op) {
continue
}
n, err := strconv.Atoi(strings.TrimPrefix(value, "EP"+op))
if err != nil {
return 0, false
}
if op == "+" {
return ep + n, true
}
return ep - n, true
}
return 0, false
}
func evalRecognitionEpisodeMul(value string, ep int) (int, bool) {
if strings.HasPrefix(value, "EP*") {
n, err := strconv.Atoi(strings.TrimPrefix(value, "EP*"))
return ep * n, err == nil
}
if strings.HasSuffix(value, "*EP") {
n, err := strconv.Atoi(strings.TrimSuffix(value, "*EP"))
return ep * n, err == nil
}
return 0, false
}