/
AstroJohn
/
MailRobot
Обзор
Документация
Войти
/
AstroJohn
/
MailRobot
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ResolvePrefix/PrefixResolver.cs
676 строк
17 KB
AstroJohn
GetOblast GetTeam
11 апр 2026, 17:23
11 апр 2026, 17:23
8cd6106
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Common; using LogProcessingModels.PrefixResolving; namespace ResolvePrefix { public class PrefixResolver: IPrefixResolver { #region Slashes private static readonly SlashDescription [] Slashes = { new SlashDescription {Slash = "A", IsBadSlash = false, NeedChangePx = false}, new SlashDescription {Slash = "P", IsBadSlash = false, NeedChangePx = false}, new SlashDescription {Slash = "M", IsBadSlash = false, NeedChangePx = false}, new SlashDescription {Slash = "MM", IsBadSlash = true, NeedChangePx = false}, new SlashDescription {Slash = "AM", IsBadSlash = true, NeedChangePx = false}, new SlashDescription {Slash = "QRP", IsBadSlash = false, NeedChangePx = false}, new SlashDescription {Slash = "1", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "2", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "3", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "4", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "5", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "6", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "7", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "8", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "9", IsBadSlash = false, NeedChangePx = true}, new SlashDescription {Slash = "0", IsBadSlash = false, NeedChangePx = true} }; private class SlashDescription { public string Slash { get; set; } public bool IsBadSlash { get; set; } public bool NeedChangePx { get; set; } } #endregion private string _ctyDatPath; public string CtyDatPath { get => _ctyDatPath; set { _ctyDatPath = value; LoadCtyDat(); } } private string _oblDatPath; public string OblDatPath { get => _oblDatPath; set { _oblDatPath = value; LoadOblDat(); } } private string _teamDatPath; public string TeamDatPath { get => _teamDatPath; set { _teamDatPath = value; LoadTeamDat(); } } public IList<DxccEntity> DxccEntities { get; private set; } public IList<DxccPrefix> DxccPrefixes { get; private set; } public IList<OblastEntity> OblastEntities { get; private set; } public IList<TeamEntity> TeamEntities { get; private set; } public IList<OblastPrefix> OblastPrefixes { get; private set; } public PrefixResolver(string ctyDatPath, string oblDatPath, string teamDatPath) { CtyDatPath = ctyDatPath; OblDatPath = oblDatPath; TeamDatPath = teamDatPath; } private void LoadCtyDat() { DxccEntities = new List<DxccEntity>(); const int bufferSize = 1024; var lineNumber = 0; DxccEntity currentEntity = null; using (var fileStream = File.OpenRead(CtyDatPath)) using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, bufferSize)) { string line; while ((line = streamReader.ReadLine()) != null) { lineNumber++; var splitValues = line.Split(':'); if (splitValues.Length == 9) { /* 1 26 Country Name 27 5 CQ Zone 32 5 ITU Zone 37 5 2-letter continent abbreviation 42 9 Latitude in degrees, + for North 51 10 Longitude in degrees, + for West 61 9 Local time offset from GMT 70 6 Primary DXCC Prefix (A “*” preceding this prefix indicates that the country is on the DARC WAEDC list, and counts in CQ-sponsored contests, but not ARRL-sponsored contests). */ currentEntity = new DxccEntity { CountryName = splitValues[0].Trim(), CQZone = ParseByte(splitValues[1].Trim()) ?? 0, ITUZone = ParseByte(splitValues[2].Trim()) ?? 0, Continent = splitValues[3].Trim(), Latitude = ParseDecimal(splitValues[4]) ?? 0m, Longitude = ParseDecimal(splitValues[5]) ?? 0m, TimeOffset = ParseDecimal(splitValues[6]) ?? 0m, PrimaryPrefix = splitValues[7].Trim(), IsWaedc = false }; if (currentEntity.PrimaryPrefix.Substring(0, 1) == "*") { currentEntity.PrimaryPrefix = currentEntity.PrimaryPrefix.Replace("*", string.Empty); currentEntity.IsWaedc = true; } DxccEntities.Add(currentEntity); } else if (line.Substring(0, 4) == string.Empty.PadLeft(4)) { if (currentEntity == null) throw new Exception($"Error in line {lineNumber:D} of cty.dat file."); line = line.Trim(); if (line.Right(1) == ";") line = line.Replace(";", ""); var splitPrefixValues = line.Split(','); foreach (var item in splitPrefixValues) { if (item == string.Empty) continue; /* (#) Override CQ Zone [#] Override ITU Zone <#/#> Override latitude/longitude {aa} Override Continent ~#~ Override local time offset from GMT */ decimal? latitude = null; decimal? longitude = null; var latLong = GetSubString(item, "<", ">"); var latLongValues = latLong?.Split('/'); if (latLongValues?.Length == 2) { latitude = ParseDecimal(latLongValues[0]); longitude = ParseDecimal(latLongValues[1]); } var prefix = new DxccPrefix { DxccEntity = currentEntity, Prefix = ClearPrefix(item), CQZone = ParseByte(GetSubString(item, @"\(", @"\)")), ITUZone = ParseByte(GetSubString(item, @"\[", @"\]")), Latitude = latitude, Longitude = longitude, Continent = GetSubString(item, @"\{", @"\}"), TimeOffset = ParseByte(GetSubString(item, "~", "~")), IsExact = false }; if (item.Substring(0, 1) == "=") { prefix.IsExact = true; } currentEntity.Prefixes.Add(prefix); } } else if (line.Substring(0, 1) == "#") { if (currentEntity == null) throw new Exception($"Error in line {lineNumber:D} of cty.dat file."); while ((line = streamReader.ReadLine()) != null && line.Right(1) != ";") { lineNumber++; } } else throw new Exception($"Error in line {lineNumber:D} of cty.dat file."); } } DxccPrefixes = DxccEntities.SelectMany(x => x.Prefixes).ToList(); } private void LoadOblDat() { OblastEntities = new List<OblastEntity>(); const int bufferSize = 1024; var lineNumber = 0; OblastEntity currentEntity = null; using (var fileStream = File.OpenRead(OblDatPath)) using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, bufferSize)) { string line; while ((line = streamReader.ReadLine()) != null) { lineNumber++; var splitValues = line.Split(':'); if (splitValues.Length >= 2) { currentEntity = new OblastEntity { PrimaryPrefix = splitValues[0].Trim(), Oblast = splitValues[1].Trim(), Name = splitValues.Length > 2 ? splitValues[2].Trim() : string.Empty }; OblastEntities.Add(currentEntity); var prefix = new OblastPrefix { OblastEntity = currentEntity, Prefix = currentEntity.PrimaryPrefix, IsExact = false }; currentEntity.Prefixes.Add(prefix); } else { if (currentEntity == null) throw new Exception($"Error in line {lineNumber:D} of obl.dat file."); var splitPrefixValues = line.Split(','); foreach (var item in splitPrefixValues) { if (item == string.Empty) continue; var prefix = new OblastPrefix { OblastEntity = currentEntity, Prefix = item, IsExact = false }; if (prefix.Prefix.Substring(0, 1) == "=") { prefix.Prefix = prefix.Prefix.Replace("=", string.Empty); prefix.IsExact = true; } currentEntity.Prefixes.Add(prefix); } } } } OblastPrefixes = OblastEntities.SelectMany(x => x.Prefixes).ToList(); } private void LoadTeamDat() { TeamEntities = new List<TeamEntity>(); if (!File.Exists(TeamDatPath)) return; const int bufferSize = 1024; var lineNumber = 0; TeamEntity currentEntity = null; using (var fileStream = File.OpenRead(TeamDatPath)) using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, bufferSize)) { string line; while ((line = streamReader.ReadLine()) != null) { lineNumber++; var splitValues = line.Split('='); if (splitValues.Length == 2) { currentEntity = new TeamEntity { Callsign = splitValues[0].Trim(), Team = splitValues[1].Trim(), }; TeamEntities.Add(currentEntity); } else { if (currentEntity == null) throw new Exception($"Error in line {lineNumber:D} of team.dat file."); } } } } private static string ClearPrefix(string value) { var indexes = new List<int> { value.IndexOf("(", StringComparison.Ordinal), value.IndexOf("[", StringComparison.Ordinal), value.IndexOf("<", StringComparison.Ordinal), value.IndexOf("{", StringComparison.Ordinal), value.IndexOf("~", StringComparison.Ordinal) }; var minIndex = indexes.Any(x => x >= 0) ? indexes.Where(x => x >= 0).Min() : -1; var prefix = minIndex < 0 ? value : value.Substring(0, minIndex); return prefix.Replace(";", string.Empty).Replace("=", string.Empty).Trim(); } private static string GetSubString(string value, string start, string end) { var match = Regex.Match(value, $@"{start}([^)]*){end}"); return match.Success ? match.Groups[1].Value : null; } private static decimal? ParseDecimal(string value) { if (decimal.TryParse(value, out var result)) { return result; } return null; } private static byte? ParseByte(string value) { if (byte.TryParse(value, out var result)) { return result; } return null; } public string GetOblast(string callsign) { var region = ResolveOblastPrefix(callsign); return region?.OblastEntity?.Oblast ?? string.Empty; } public string GetTeam(string callsign) { var team = TeamEntities.FirstOrDefault(x => x.Callsign == callsign); if (team != null) return team.Team; var region = ResolveOblastPrefix(callsign); return region?.OblastEntity?.Oblast ?? string.Empty; } public DxccPrefix ResolveDxccPrefix(string callsign, out string errorMessage, out string realCallsign, out string realPrefix) { errorMessage = null; realCallsign = null; realPrefix = null; if (string.IsNullOrWhiteSpace(callsign)) { errorMessage = "Empty callsign"; return null; } callsign = callsign.Trim().ToUpper().Replace("/SWL", ""); var defisIndex = callsign.IndexOf('-'); // Обрезаем у наблюдательских позывных все, что после дефиса if (defisIndex >= 0) callsign = callsign.Substring(0, defisIndex - 1); var prefix = DxccPrefixes.FirstOrDefault(x => x.Prefix == callsign && x.IsExact); if (prefix != null) return prefix; string slashToChange = null; var regex = new Regex("^[A-Z/0-9]+$"); if (!regex.IsMatch(callsign)) { errorMessage = "Callsign contains invalid symbols."; return null; } if (callsign.Substring(0, 1) == "/" || callsign.Substring(callsign.Length - 1, 1) == "/") { errorMessage = "Invalid callsign."; return null; } var slashes = callsign.Count(x => x == '/'); var i = 0; var j = 0; var lst = new List<int>(); var needChangePx = false; var slashValues = callsign.Split('/'); foreach (var slashItem in slashValues) { var slash = Slashes.FirstOrDefault(x => x.Slash == slashValues[j]); var isBadSlash = slash?.IsBadSlash ?? false; var slashValue = slash?.Slash; needChangePx = slash?.NeedChangePx ?? false; if (slash == null) { lst.Add(slashItem.Length); j++; } else { slashes--; if (isBadSlash) { errorMessage = $"Slash indicates {slashValue}."; return null; } if (needChangePx) slashToChange = slashValues[j]; } i++; } var minLst = lst.Min(); i = lst.LastIndexOf(minLst); var bad = lst.Count(x => x == minLst) > 1; var lstI = lst[i]; if (bad || (lstI > 4 && slashes > 0)) { errorMessage = "Callsign is too strange."; return null; } var maxLst = lst.Max(); j = lst.IndexOf(maxLst); bad = lst.Count(x => x == maxLst) > 1; var lstJ = lst[j]; if (bad && lstJ < 4 && slashes > 0) { errorMessage = "Callsign is too strange."; return null; } realCallsign = slashValues[j]; var cl = slashValues[i]; i = 0; var l = cl.Length; string stTemp; while (prefix == null && i < l) { stTemp = cl.Substring(0, l - i); prefix = DxccPrefixes.FirstOrDefault(x => x.Prefix == stTemp && !x.IsExact); i++; } if (needChangePx) { if (cl.Length == 1) stTemp = cl + slashToChange; else { i = 0; var ch = (char) 0; while ((ch < 48 || ch > 57) && i < l) { ch = Convert.ToChar(cl.Substring(i, 1)); i++; } if (ch >= 48 && ch <= 57) stTemp = cl.Substring(0, i - 1) + slashToChange; else stTemp = cl.Substring(0, i) + slashToChange; } i = 0; l = stTemp.Length; while (prefix == null && i <= l) { var stTemp2 = stTemp.Substring(0, l - i); prefix = DxccPrefixes.FirstOrDefault(x => x.Prefix == stTemp2 && !x.IsExact); i ++; } } else { i = 0; var ch = (char) 0; while (!char.IsDigit(ch) && i < l) { ch = Convert.ToChar(cl.Substring(i, 1)); i++; } stTemp = char.IsDigit(ch) ? cl.Substring(0, i - 1) : cl.Substring(0, i); } realPrefix = stTemp; if (prefix != null) return prefix; errorMessage = "No such prefix in cty.dat."; return null; } public OblastPrefix ResolveOblastPrefix(string callsign) { var oblast = OblastPrefixes.FirstOrDefault(x => x.Prefix == callsign && x.IsExact); if (oblast != null) return oblast; if (char.IsDigit(Convert.ToChar(callsign.Substring(callsign.Length - 1, 1))) && callsign.Substring(callsign.Length - 2, 1) == "/") return null; var n = callsign.Length; var i = -1; var flag = false; while (i < n - 1) { i++; var ch = Convert.ToChar(callsign.Substring(i, 1)); if (!char.IsDigit(ch)) continue; flag = true; break; } if (!flag) return null; var px = n >= i + 2 ? callsign.Substring(i, 2) : string.Empty; if (px == string.Empty) return null; oblast = OblastPrefixes.FirstOrDefault(x => x.Prefix == px && !x.IsExact); return oblast; } public bool IsRussian(string callsign) { var dxccPrefix = ResolveDxccPrefix(callsign, out _, out _, out _); return dxccPrefix.DxccEntity.PrimaryPrefix.IsRussianPrefix(); } public bool IsSpLo(string callsign) { return IsSpLo(callsign, out _, out _); } public bool IsSpLo(string callsign, out string countryId, out string oblId) { var dxccPrefix = ResolveDxccPrefix(callsign, out _, out _, out _); if (dxccPrefix == null) { countryId = null; oblId = null; return false; } countryId = dxccPrefix.DxccEntity.PrimaryPrefix; if (!countryId.IsRussianPrefix()) { oblId = null; return false; } var prefix = ResolveOblastPrefix(callsign); oblId = prefix?.OblastEntity?.Oblast; var isSpLo = oblId?.IsSpLo() ?? false; return isSpLo; } } }