This commit is contained in:
2026-06-06 01:21:24 +02:00
parent 922a185208
commit b4e104f5a2
9 changed files with 381 additions and 42 deletions
+54 -11
View File
@@ -139,7 +139,15 @@ func (db *DB) Lookup(callsign string) (Match, bool) {
if e, ok := db.exact[call]; ok {
return materialize(e), true
}
// KG4 special case: Guantanamo Bay (DXCC 105) is "KG4" followed by EXACTLY
// two characters (KG4XX). "KG4", "KG4X", "KG4XYZ"… are continental USA.
// cty.dat carries a bare "KG4" prefix for Guantanamo, so for the other
// suffix lengths we must skip it and fall through to the USA prefixes.
skipKG4 := strings.HasPrefix(call, "KG4") && len(call) != len("KG4")+2
for _, p := range db.byPrefix {
if skipKG4 && p.prefix == "KG4" {
continue
}
if strings.HasPrefix(call, p.prefix) {
return materialize(p), true
}
@@ -262,22 +270,33 @@ func stripAnnotation(s string, open, close rune, cb func(string)) string {
}
// suffixModifiers are non-DXCC-relevant callsign suffixes we strip before
// matching. /P /M /MM /AM /QRP /A and single-digit area changes (/5 …) all
// keep the operator's home DXCC.
// matching. /P /M /QRP /A and single-digit area changes (/5 …) all keep the
// operator's home DXCC. NOTE: "MM" and "AM" are NOT here — a TRAILING /MM or
// /AM (maritime/aeronautical mobile) means "no DXCC entity", while a LEADING
// "MM" is the Scotland operating prefix; both are handled in normalizeCallsign.
var suffixModifiers = map[string]bool{
"P": true, "M": true, "MM": true, "AM": true, "QRP": true, "A": true,
"P": true, "M": true, "QRP": true, "A": true,
"PM": true, "LH": true,
}
// normalizeCallsign uppercases, trims, and resolves the "active" call when
// the operator uses slashes (DL/F4NIE → DL; F4NIE/P → F4NIE).
// the operator uses slashes (DL/F4NIE → DL; F4NIE/P → F4NIE). Returns "" for
// maritime/aeronautical mobile (.../MM, .../AM), which count for no DXCC.
func normalizeCallsign(s string) string {
s = strings.ToUpper(strings.TrimSpace(s))
if !strings.ContainsRune(s, '/') {
return s
}
parts := strings.Split(s, "/")
// A trailing /MM or /AM is maritime/aeronautical mobile → no DXCC entity.
// (A leading "MM" is the Scotland prefix and must NOT trigger this.)
for i, p := range parts {
if i > 0 && (p == "MM" || p == "AM") {
return ""
}
}
keep := parts[:0]
var areaDigit byte // a single-digit "/N" re-homes the call to call area N
for _, p := range parts {
if p == "" {
continue
@@ -285,21 +304,45 @@ func normalizeCallsign(s string) string {
if suffixModifiers[p] {
continue
}
if len(p) == 1 && p >= "0" && p <= "9" {
if len(p) == 1 && p[0] >= '0' && p[0] <= '9' {
areaDigit = p[0]
continue
}
keep = append(keep, p)
}
var main string
switch len(keep) {
case 0:
return s
case 1:
return keep[0]
main = keep[0]
default:
// Two non-modifier parts → operating-from prefix wins (shorter one).
// DL/F4NIE: DL is shorter → use DL (Germany). F4NIE/W6: W6 → W6.
if len(keep[0]) <= len(keep[1]) {
main = keep[0]
} else {
main = keep[1]
}
}
// Two non-modifier parts → operating-from prefix wins (shorter one).
// DL/F4NIE: DL is shorter → use DL (Germany). F4NIE/W6: W6 shorter → W6.
if len(keep[0]) <= len(keep[1]) {
return keep[0]
// Apply the call-area digit: "/N" replaces the area digit of the base call,
// which can change the DXCC entity (HD5MW/8 → HD8MW → Galápagos, not
// Ecuador). This is the same class of rule as KG4 and /MM.
if areaDigit != 0 {
main = replaceFirstDigit(main, areaDigit)
}
return keep[1]
return main
}
// replaceFirstDigit substitutes the first 0-9 digit of a call with d (used to
// apply a "/N" call-area change). Returns the call unchanged if it has no digit.
func replaceFirstDigit(call string, d byte) string {
b := []byte(call)
for i := range b {
if b[i] >= '0' && b[i] <= '9' {
b[i] = d
return string(b)
}
}
return call
}