This commit is contained in:
2026-06-06 14:16:30 +02:00
parent f91f9ff3b8
commit 17f7a00bd7
19 changed files with 1278 additions and 91 deletions
+272
View File
@@ -0,0 +1,272 @@
package adif
import "strings"
// This file embeds the complete ADIF 3.1.7 QSO-field dictionary. It is the
// single source of truth for:
// - the generic "ADIF fields" editor in the UI (any field becomes editable),
// - the "standard ADIF" export mode (only spec fields are emitted),
// - import diagnostics (a tag absent here is non-standard / vendor-specific).
//
// Promoted fields (those with dedicated QSO columns) are flagged Promoted=true
// so the UI can hide them from the generic editor — they already have proper
// inputs in the main tabs.
// FieldKind is a coarse input type used by the generic editor to pick a widget.
type FieldKind string
const (
KindText FieldKind = "text" // String / IntlString / MultilineString
KindNumber FieldKind = "number" // Number / PositiveInteger
KindDate FieldKind = "date" // ADIF Date (YYYYMMDD)
KindTime FieldKind = "time" // ADIF Time (HHMMSS / HHMM)
KindBool FieldKind = "boolean" // Boolean (Y/N)
KindEnum FieldKind = "enum" // Enumeration
KindLoc FieldKind = "location" // Location (e.g. "N048 09.000")
)
// FieldDef describes one ADIF QSO field.
type FieldDef struct {
Name string `json:"name"` // canonical uppercase ADIF tag
Kind FieldKind `json:"kind"` // editor widget hint
Category string `json:"category"` // grouping for the UI
Promoted bool `json:"promoted"` // has a dedicated QSO column
Deprecated bool `json:"deprecated"` // import-only per the spec
Intl bool `json:"intl"` // *_INTL UTF-8 variant
}
// adifVersion is the ADIF spec version OpsLog targets for import/export.
const adifVersion = "3.1.7"
// ADIFVersion returns the ADIF spec version OpsLog conforms to.
func ADIFVersion() string { return adifVersion }
// Fields is the full ADIF 3.1.7 QSO-field set. Order is alphabetical within
// each category; categories order is roughly "most-used first" for the UI.
var Fields = []FieldDef{
// ── Core / contact ──────────────────────────────────────────────
{Name: "CALL", Kind: KindText, Category: "Core", Promoted: true},
{Name: "QSO_DATE", Kind: KindDate, Category: "Core", Promoted: true},
{Name: "TIME_ON", Kind: KindTime, Category: "Core", Promoted: true},
{Name: "QSO_DATE_OFF", Kind: KindDate, Category: "Core", Promoted: true},
{Name: "TIME_OFF", Kind: KindTime, Category: "Core", Promoted: true},
{Name: "BAND", Kind: KindEnum, Category: "Core", Promoted: true},
{Name: "BAND_RX", Kind: KindEnum, Category: "Core", Promoted: true},
{Name: "MODE", Kind: KindEnum, Category: "Core", Promoted: true},
{Name: "SUBMODE", Kind: KindEnum, Category: "Core", Promoted: true},
{Name: "FREQ", Kind: KindNumber, Category: "Core", Promoted: true},
{Name: "FREQ_RX", Kind: KindNumber, Category: "Core", Promoted: true},
{Name: "RST_SENT", Kind: KindText, Category: "Core", Promoted: true},
{Name: "RST_RCVD", Kind: KindText, Category: "Core", Promoted: true},
{Name: "QSO_COMPLETE", Kind: KindEnum, Category: "Core", Promoted: true},
{Name: "QSO_RANDOM", Kind: KindBool, Category: "Core", Promoted: true},
{Name: "SWL", Kind: KindBool, Category: "Core", Promoted: true},
// ── Contacted station ───────────────────────────────────────────
{Name: "NAME", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "NAME_INTL", Kind: KindText, Category: "Contacted", Intl: true},
{Name: "QTH", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "QTH_INTL", Kind: KindText, Category: "Contacted", Intl: true},
{Name: "ADDRESS", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "ADDRESS_INTL", Kind: KindText, Category: "Contacted", Intl: true},
{Name: "EMAIL", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "WEB", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "GRIDSQUARE", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "GRIDSQUARE_EXT", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "VUCC_GRIDS", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "COUNTRY", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "COUNTRY_INTL", Kind: KindText, Category: "Contacted", Intl: true},
{Name: "STATE", Kind: KindEnum, Category: "Contacted", Promoted: true},
{Name: "CNTY", Kind: KindEnum, Category: "Contacted", Promoted: true},
{Name: "CNTY_ALT", Kind: KindEnum, Category: "Contacted"},
{Name: "DXCC", Kind: KindEnum, Category: "Contacted", Promoted: true},
{Name: "CONT", Kind: KindEnum, Category: "Contacted", Promoted: true},
{Name: "CQZ", Kind: KindNumber, Category: "Contacted", Promoted: true},
{Name: "ITUZ", Kind: KindNumber, Category: "Contacted", Promoted: true},
{Name: "IOTA", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "IOTA_ISLAND_ID", Kind: KindText, Category: "Contacted"},
{Name: "REGION", Kind: KindEnum, Category: "Contacted", Promoted: true},
{Name: "PFX", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "AGE", Kind: KindNumber, Category: "Contacted", Promoted: true},
{Name: "LAT", Kind: KindLoc, Category: "Contacted", Promoted: true},
{Name: "LON", Kind: KindLoc, Category: "Contacted", Promoted: true},
{Name: "ALTITUDE", Kind: KindNumber, Category: "Contacted"},
{Name: "DISTANCE", Kind: KindNumber, Category: "Contacted", Promoted: true},
{Name: "RIG", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "RIG_INTL", Kind: KindText, Category: "Contacted", Intl: true},
{Name: "ANT", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "CONTACTED_OP", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "EQ_CALL", Kind: KindText, Category: "Contacted", Promoted: true},
{Name: "GUEST_OP", Kind: KindText, Category: "Contacted", Deprecated: true},
{Name: "OWNER_CALLSIGN", Kind: KindText, Category: "Contacted"},
{Name: "SILENT_KEY", Kind: KindBool, Category: "Contacted", Promoted: true},
{Name: "USACA_COUNTIES", Kind: KindText, Category: "Contacted"},
// ── Special activity (POTA/SOTA/WWFF/SIG) ───────────────────────
{Name: "SIG", Kind: KindText, Category: "Activity", Promoted: true},
{Name: "SIG_INFO", Kind: KindText, Category: "Activity", Promoted: true},
{Name: "SIG_INTL", Kind: KindText, Category: "Activity", Intl: true},
{Name: "SIG_INFO_INTL", Kind: KindText, Category: "Activity", Intl: true},
{Name: "POTA_REF", Kind: KindText, Category: "Activity", Promoted: true},
{Name: "SOTA_REF", Kind: KindText, Category: "Activity", Promoted: true},
{Name: "WWFF_REF", Kind: KindText, Category: "Activity", Promoted: true},
// ── Power / propagation / space wx ──────────────────────────────
{Name: "TX_PWR", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "RX_PWR", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "A_INDEX", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "K_INDEX", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "SFI", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "PROP_MODE", Kind: KindEnum, Category: "Propagation", Promoted: true},
{Name: "SAT_NAME", Kind: KindText, Category: "Propagation", Promoted: true},
{Name: "SAT_MODE", Kind: KindText, Category: "Propagation", Promoted: true},
{Name: "ANT_AZ", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "ANT_EL", Kind: KindNumber, Category: "Propagation", Promoted: true},
{Name: "ANT_PATH", Kind: KindEnum, Category: "Propagation", Promoted: true},
{Name: "FORCE_INIT", Kind: KindBool, Category: "Propagation"},
{Name: "MAX_BURSTS", Kind: KindNumber, Category: "Propagation"},
{Name: "MS_SHOWER", Kind: KindText, Category: "Propagation"},
{Name: "NR_BURSTS", Kind: KindNumber, Category: "Propagation"},
{Name: "NR_PINGS", Kind: KindNumber, Category: "Propagation"},
// ── QSL / confirmations ─────────────────────────────────────────
{Name: "QSL_SENT", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "QSL_RCVD", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "QSLSDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "QSLRDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "QSL_VIA", Kind: KindText, Category: "QSL", Promoted: true},
{Name: "QSL_SENT_VIA", Kind: KindEnum, Category: "QSL"},
{Name: "QSL_RCVD_VIA", Kind: KindEnum, Category: "QSL"},
{Name: "QSLMSG", Kind: KindText, Category: "QSL", Promoted: true},
{Name: "QSLMSG_INTL", Kind: KindText, Category: "QSL", Intl: true},
{Name: "QSLMSG_RCVD", Kind: KindText, Category: "QSL", Promoted: true},
{Name: "LOTW_QSL_SENT", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "LOTW_QSL_RCVD", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "LOTW_QSLSDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "LOTW_QSLRDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "EQSL_QSL_SENT", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "EQSL_QSL_RCVD", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "EQSL_QSLSDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "EQSL_QSLRDATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "EQSL_AG", Kind: KindBool, Category: "QSL"},
{Name: "DCL_QSL_SENT", Kind: KindEnum, Category: "QSL"},
{Name: "DCL_QSL_RCVD", Kind: KindEnum, Category: "QSL"},
{Name: "DCL_QSLSDATE", Kind: KindDate, Category: "QSL"},
{Name: "DCL_QSLRDATE", Kind: KindDate, Category: "QSL"},
{Name: "CLUBLOG_QSO_UPLOAD_DATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "CLUBLOG_QSO_UPLOAD_STATUS", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "HRDLOG_QSO_UPLOAD_DATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "HRDLOG_QSO_UPLOAD_STATUS", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "QRZCOM_QSO_UPLOAD_DATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "QRZCOM_QSO_UPLOAD_STATUS", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "QRZCOM_QSO_DOWNLOAD_DATE", Kind: KindDate, Category: "QSL", Promoted: true},
{Name: "QRZCOM_QSO_DOWNLOAD_STATUS", Kind: KindEnum, Category: "QSL", Promoted: true},
{Name: "HAMLOGEU_QSO_UPLOAD_DATE", Kind: KindDate, Category: "QSL"},
{Name: "HAMLOGEU_QSO_UPLOAD_STATUS", Kind: KindEnum, Category: "QSL"},
{Name: "HAMQTH_QSO_UPLOAD_DATE", Kind: KindDate, Category: "QSL"},
{Name: "HAMQTH_QSO_UPLOAD_STATUS", Kind: KindEnum, Category: "QSL"},
// ── Awards / credits ────────────────────────────────────────────
{Name: "CREDIT_SUBMITTED", Kind: KindText, Category: "Awards", Promoted: true},
{Name: "CREDIT_GRANTED", Kind: KindText, Category: "Awards", Promoted: true},
{Name: "AWARD_SUBMITTED", Kind: KindText, Category: "Awards"},
{Name: "AWARD_GRANTED", Kind: KindText, Category: "Awards"},
// ── Contest ─────────────────────────────────────────────────────
{Name: "CONTEST_ID", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "SRX", Kind: KindNumber, Category: "Contest", Promoted: true},
{Name: "STX", Kind: KindNumber, Category: "Contest", Promoted: true},
{Name: "SRX_STRING", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "STX_STRING", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "CHECK", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "CLASS", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "PRECEDENCE", Kind: KindText, Category: "Contest", Promoted: true},
{Name: "ARRL_SECT", Kind: KindEnum, Category: "Contest", Promoted: true},
// ── Club memberships ────────────────────────────────────────────
{Name: "SKCC", Kind: KindText, Category: "Clubs", Promoted: true},
{Name: "FISTS", Kind: KindNumber, Category: "Clubs", Promoted: true},
{Name: "FISTS_CC", Kind: KindNumber, Category: "Clubs"},
{Name: "TEN_TEN", Kind: KindNumber, Category: "Clubs", Promoted: true},
{Name: "UKSMG", Kind: KindNumber, Category: "Clubs"},
{Name: "DARC_DOK", Kind: KindText, Category: "Clubs", Promoted: true},
// ── Morse key (3.1.5+) ──────────────────────────────────────────
{Name: "MORSE_KEY_TYPE", Kind: KindEnum, Category: "Morse key"},
{Name: "MORSE_KEY_INFO", Kind: KindText, Category: "Morse key"},
// ── Misc / crypto ───────────────────────────────────────────────
{Name: "COMMENT", Kind: KindText, Category: "Misc", Promoted: true},
{Name: "COMMENT_INTL", Kind: KindText, Category: "Misc", Intl: true},
{Name: "NOTES", Kind: KindText, Category: "Misc", Promoted: true},
{Name: "NOTES_INTL", Kind: KindText, Category: "Misc", Intl: true},
{Name: "PUBLIC_KEY", Kind: KindText, Category: "Misc"},
{Name: "VE_PROV", Kind: KindText, Category: "Misc", Deprecated: true},
// ── My station / operator ───────────────────────────────────────
{Name: "STATION_CALLSIGN", Kind: KindText, Category: "My station", Promoted: true},
{Name: "OPERATOR", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_NAME", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_NAME_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_GRIDSQUARE", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_GRIDSQUARE_EXT", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_VUCC_GRIDS", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_COUNTRY", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_COUNTRY_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_STATE", Kind: KindEnum, Category: "My station", Promoted: true},
{Name: "MY_CNTY", Kind: KindEnum, Category: "My station", Promoted: true},
{Name: "MY_CNTY_ALT", Kind: KindEnum, Category: "My station"},
{Name: "MY_DXCC", Kind: KindEnum, Category: "My station", Promoted: true},
{Name: "MY_CQ_ZONE", Kind: KindNumber, Category: "My station", Promoted: true},
{Name: "MY_ITU_ZONE", Kind: KindNumber, Category: "My station", Promoted: true},
{Name: "MY_IOTA", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_IOTA_ISLAND_ID", Kind: KindText, Category: "My station"},
{Name: "MY_SOTA_REF", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_POTA_REF", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_WWFF_REF", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_SIG", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_SIG_INFO", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_SIG_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_SIG_INFO_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_LAT", Kind: KindLoc, Category: "My station", Promoted: true},
{Name: "MY_LON", Kind: KindLoc, Category: "My station", Promoted: true},
{Name: "MY_ALTITUDE", Kind: KindNumber, Category: "My station"},
{Name: "MY_STREET", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_STREET_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_CITY", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_CITY_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_POSTAL_CODE", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_POSTAL_CODE_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_RIG", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_RIG_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_ANTENNA", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_ANTENNA_INTL", Kind: KindText, Category: "My station", Intl: true},
{Name: "MY_ARRL_SECT", Kind: KindEnum, Category: "My station", Promoted: true},
{Name: "MY_USACA_COUNTIES", Kind: KindText, Category: "My station"},
{Name: "MY_DARC_DOK", Kind: KindText, Category: "My station", Promoted: true},
{Name: "MY_FISTS", Kind: KindNumber, Category: "My station"},
{Name: "MY_MORSE_KEY_TYPE", Kind: KindEnum, Category: "My station"},
{Name: "MY_MORSE_KEY_INFO", Kind: KindText, Category: "My station"},
}
// fieldIndex maps an uppercase ADIF tag to its definition for O(1) lookup.
var fieldIndex = func() map[string]FieldDef {
m := make(map[string]FieldDef, len(Fields))
for _, f := range Fields {
m[f.Name] = f
}
return m
}()
// IsStandardField reports whether tag (any case) is a defined ADIF 3.1.7
// field. APP_* and USERDEF tags are non-standard and return false.
func IsStandardField(tag string) bool {
_, ok := fieldIndex[strings.ToUpper(strings.TrimSpace(tag))]
return ok
}
// LookupField returns the definition for a tag (any case), ok=false if unknown.
func LookupField(tag string) (FieldDef, bool) {
f, ok := fieldIndex[strings.ToUpper(strings.TrimSpace(tag))]
return f, ok
}