package common import ( "strings" "unicode" ) // NormalizeKgTerm 知识图谱术语归一化(实体名与关系谓词共用,别名表由调用方传入)。 // 幂等:NormalizeKgTerm(NormalizeKgTerm(x)) == NormalizeKgTerm(x)。 // 规则保守,只做高置信合并,不做模糊相似度合并,防误合并("人民法院"与"最高人民法院"绝不合并)。 // 返回空串表示归一化后无意义(调用方应丢弃);原本合法但归一化后为空时保留原名(如"中华人民共和国")。 func NormalizeKgTerm(name string, aliasMap map[string]string) string { orig := name s := strings.TrimSpace(name) s = fullToHalf(s) s = strings.Join(strings.Fields(s), " ") s = strings.Trim(s, "《》") // 去版本注记括号及内容:内容含 年/修正/修订/施行/数字 才剥(人名限定语"张三(北京分公司)"不剥) s = stripVersionParens(s) s = strings.TrimRight(s, "。,;、!?,;!?.·。、") s = strings.TrimSpace(s) // 去"中华人民共和国"前缀(仅前缀;去完为空则保留原名,防实体"中华人民共和国"被删) if strings.HasPrefix(s, "中华人民共和国") { if rest := strings.TrimSpace(strings.TrimPrefix(s, "中华人民共和国")); rest != "" { s = rest } } // 别名全串精确映射(非 contains 替换,防"民诉法"吃掉"民诉法解释") if std, ok := aliasMap[s]; ok && std != "" { s = std } if s == "" && orig != "" { return orig } return s } // fullToHalf 全角转半角(ASCII 可见区 U+FF01~U+FF5E → U+21~U+7E,全角空格 → 半角空格) func fullToHalf(s string) string { return strings.Map(func(r rune) rune { switch { case r == ' ': return ' ' case r >= '!' && r <= '~': return r - '!' + '\x21' } return r }, s) } // stripVersionParens 剥去含版本注记的括号及内容。注记判定:内容含 年/修正/修订/施行 或任一数字。 func stripVersionParens(s string) string { pairs := [][2]rune{{'(', ')'}, {'(', ')'}, {'【', '】'}, {'[', ']'}, {'〔', '〕'}} var out strings.Builder runes := []rune(s) for i := 0; i < len(runes); { stripped := false for _, p := range pairs { if runes[i] == p[0] { end := findCloseParen(runes, i, p[1]) if end > i && isVersionNote(runes[i+1:end]) { i = end + 1 stripped = true break } } } if !stripped { out.WriteRune(runes[i]) i++ } } return out.String() } func findCloseParen(runes []rune, start int, close rune) int { for j := start + 1; j < len(runes); j++ { if runes[j] == close { return j } } return -1 } // isVersionNote 括号内容是否版本注记(年/修正/修订/施行 或数字) func isVersionNote(content []rune) bool { for _, r := range content { if unicode.IsDigit(r) { return true } } text := string(content) for _, kw := range []string{"年", "修正", "修订", "施行"} { if strings.Contains(text, kw) { return true } } return false }