/
AstroJohn
/
MailRobot
Обзор
Документация
Войти
/
AstroJohn
/
MailRobot
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
CsParser/EdiParser.cs
1 188 строк
55 KB
AstroJohn
мелочи 3
15 июн 2026, 13:20
15 июн 2026, 13:20
44ff3fa
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Common; using CsHelpers.SimpleHelpers; using LogProcessingModels.Parsing; namespace CsParser { /// <summary> /// Класс EDI-парсинга /// </summary> /// <remarks></remarks> public abstract class EdiParser : ParserBase, IParser { public abstract bool IsSerialNrAllowedEmpty { get; } public Stream LogStream { get; set; } // ввод потока с отчетом public ParsedLog ParsedLog { get; set; } // вывод результатов парсинга public string FileName { get; set; } public DateTime? EmailDate{ get; set; } public string EmailAddress { get; set; } public List<ParsedLog> ParsedLogs { get; set; } protected IList<ParsedHeader> HeaderFields { get; set; } private int QsoCount { get; set; } #region Константы private const int MaximumTotalErrorsWarnings = 15; private const char HeaderDelimiter = '='; private const string StartOfLog = @"\[REG1TEST;.*\]"; private const string StartOfQsos = @"\[QSORecords;\d+\]"; private const string StartOfRemarks = @"\[Remarks;?.*\]"; private const string EndOfLog = @"\[END;.*\]"; private const string EndOfLog2 = @"\[END\]"; /// <summary> /// Разделы EDI-отчета /// </summary> /// <remarks></remarks> private enum EdiLogParts { None = 0, Headers = 1, Remarks = 2, QsOs = 3, EndOfLog = 4 } /// <summary> /// Теги Ермака, требующие капитализации /// </summary> /// <remarks></remarks> private readonly string[] _ediTagsCapitalize = [ "PCall", "PWWLo", "PSect", "RCall", "X-RUSOBL", "X-LOCATION" ]; /// <summary> /// Массив значений PBand /// </summary> /// <remarks></remarks> private readonly string[] _pbandValues = [ "50 MHz", "70 MHz", "144 MHz", "432 MHz", "1,3 GHz", "2,3 GHz", "3,4 GHz", "5,7 GHz", "10 GHz", "24 GHz", "47 GHz", "76 GHz", "120 GHz", "144 GHz", "248 GHz" ]; #endregion protected virtual bool IsCorrectContestName(string contestName) { return Parameters.ContestName == contestName; } /// <summary> /// Парсинг значений строки заголовка /// </summary> /// <param name="header"></param> /// <param name="lineNumber"></param> /// <returns></returns> /// <remarks></remarks> private ParseResult ParseHeaderValues(ParsedHeader header, int lineNumber) { var parseResult = new ParseResult(); switch (header.Name) { case "TName": { if (!IsCorrectContestName(header.Value)) { parseResult.Type = PARSE_ERROR_TYPE.WARNING; parseResult.AddWarning("Check that you have sent a log not for another contest. Contest must be " + Parameters.ContestName + ".", lineNumber); parseResult.AddWarningRu("Проверьте, что вы не отослали отчет за другой контест. Строка с ключевым словом CONTEST должна содержать значение " + Parameters.ContestName + ".", lineNumber); } break; } case "MOpe1": case "MOpe2": case "MOpe3": case "MOpe4": case "MOpe5": case "MOpe6": { if ( IsRussianCallsign(ParsedLog.Callsign) && CheckOperatorsTagForRussianCallsigns ) { if (!string.IsNullOrEmpty(header.Value)) { var oPs = header.Value.Replace("разряд", "").Replace(";", ",").Replace(" ,", ", ").Replace(" ", ", ").Replace(",, ", ",").Replace(" ", " ").Split(','); var flag = oPs.Length == 6 || (oPs.Length == 7 && oPs[6] == "Тренер") || (oPs.Length == 7 && oPs[6] == "Техник"); if (flag) // год рождения flag = IsNumeric(oPs[5]) || DateTime.TryParseExact(oPs[5], "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out _); if (!flag) { parseResult.Type = PARSE_ERROR_TYPE.WARNING; parseResult.AddWarning("Line with tag " + header.Name + " is invalid. See comment in russian above.", lineNumber); parseResult.AddWarningRu("Строки с ключевыми словами MOpe1, MOpe2 и т. п. должны содержать" + Environment.NewLine + new string(' ', 4) + "данные в следующем порядке: личный позывной, фамилия, имя, отчество," + Environment.NewLine + new string(' ', 4) + "спортивный разряд/звание, год рождения." + Environment.NewLine + new string(' ', 4) + "Пример:" + Environment.NewLine + new string(' ', 4) + "MOpe1=UA8AAA;Иванов Иван Иванович;КМС;1966", lineNumber); } } } break; } case "TDate": { break; } case "PCall": { break; } case "PWWLo": { break; } case "PExch": { break; } case "PAdr1": { break; } case "PAdr2": { break; } case "PSect": { break; } case "PBand": { var band = header.Value.Trim(); band = band.Replace("145", "144"); band = band.Replace("435", "432"); band = band.Replace("430", "432"); band = band.Replace("433", "432"); band = band.Replace(".", ","); band = band.Replace("1,2", "1,3"); band = band.Replace("1296 MHz", "1,3 GHz"); if (band.Contains("MHz") & !band.Contains(" MHz")) band = band.Replace("MHz", " MHz"); if (band.Contains("GHz") & !band.Contains(" GHz")) band = band.Replace("GHz", " GHz"); if (Array.IndexOf(_pbandValues, band) < 0) parseResult.Type = PARSE_ERROR_TYPE.BAD; else { System.Text.RegularExpressions.Regex rx = new System.Text.RegularExpressions.Regex(@"\s*"); band = rx.Replace(band, ""); band = band.Replace("MHz", "").Replace("GHz", "G").Trim(); ParsedLog.Band = band; } break; } case "PClub": { break; } case "RName": { if ( IsRussianCallsign(ParsedLog.Callsign) && CheckOperatorsTagForRussianCallsigns ) { var flag = false; if (!string.IsNullOrEmpty(header.Value)) { var oPs = header.Value.Replace("разряд", "").Replace(";", ",").Replace(" ,", ", ").Replace(" ", ", ").Replace(",, ", ",").Replace(" ", " ").Split(','); flag = oPs.Length == 5; if (flag) // год рождения flag = IsNumeric(oPs[4]) || DateTime.TryParseExact(oPs[4], "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out _); } if (!flag) { parseResult.Type = PARSE_ERROR_TYPE.WARNING; parseResult.AddWarning("Line with tag " + header.Name + " is invalid. See comment in russian above."); parseResult.AddWarningRu("Строка с ключевыми словами RName должна содержать данные в следующем порядке: фамилия, имя, отчество," + Environment.NewLine + new string(' ', 4) + "спортивный разряд/звание, год рождения. Пример: RName=Иванов Иван Иванович;КМС;1966"); } } break; } case "RCall": { break; } case "RAdr1": { break; } case "RAdr2": { break; } case "RPoCo": { break; } case "RCity": { break; } case "RCoun": { break; } case "RPhon": { break; } case "RHBBS": { break; } case "STXEq": { break; } case "SPowe": { break; } case "SRXEq": { break; } case "SAnte": { break; } case "SAntH": { break; } case "CQSOs": { break; } case "CQSOP": { break; } case "CWWLs": { break; } case "CWWLB": { break; } case "CExcs": { break; } case "CExcB": { break; } case "CDXCs": { break; } case "CDXCB": { break; } case "CToSc": { break; } case "CODXC": { break; } default: { parseResult.Type = PARSE_ERROR_TYPE.WARNING; parseResult.AddWarning("Tag " + header.Name + " is not known for the system."); parseResult.AddWarningRu("Ключевое слово " + header.Name + " неизвестно системе."); break; } } if (parseResult.Type == PARSE_ERROR_TYPE.BAD) { if (!parseResult.Messages.Any()) { if (header.Name == "PBand") { parseResult.AddError("Invalid band " + header.Value + ". Look at line with \"PBand\" tag."); parseResult.AddErrorRu("Неверный диапазон " + header.Value + ". Обратите внимание на строку отчета с ключевым словом \"PBand\"."); } else { parseResult.AddError("Line with tag " + header.Name + " contains invalid value " + header.Value + "."); parseResult.AddErrorRu("Строка с ключевым словом " + header.Name + " содержит неверное значение " + header.Value + "."); } } } return parseResult; } /// <summary> /// Парсинг строки заголовка /// </summary> /// <param name="headerString"></param> /// <param name="lineNumber"></param> /// <returns></returns> /// <remarks></remarks> private ParsedHeader ParseHeaderString(string headerString, int lineNumber) { var parsedHeader = new ParsedHeader(); var doubleDotPosition = headerString.IndexOf(HeaderDelimiter); if (doubleDotPosition < 0) { parsedHeader.ParseResult.Type = PARSE_ERROR_TYPE.BAD; parsedHeader.ParseResult.AddError("Tag not found.", lineNumber); parsedHeader.ParseResult.AddError("Ключевое слово не найдено.", lineNumber); } else { parsedHeader.ParseResult.Type = PARSE_ERROR_TYPE.NONE; parsedHeader.Name = Left(headerString, doubleDotPosition); parsedHeader.Value = doubleDotPosition == headerString.Length ? string.Empty : Trim(Replace(Right(headerString, headerString.Length - doubleDotPosition - 1), new string((char) 9, 1), " ")); if (_ediTagsCapitalize.Contains(parsedHeader.Name)) { parsedHeader.Value = parsedHeader.Value.ToUpper(); } } return parsedHeader; } /// <summary> /// Парсинг строки раздела EDI-отчета вида [Header;Value] /// </summary> /// <param name="divisionString"></param> /// <returns></returns> /// <remarks></remarks> private ParsedHeader ParseDivisionString(string divisionString) { var parsedHeader = new ParsedHeader(); var delimiterPosition = divisionString.IndexOf(';'); if (delimiterPosition < 0) parsedHeader.Value = string.Empty; else { parsedHeader.ParseResult.Type = PARSE_ERROR_TYPE.NONE; parsedHeader.Name = Mid(divisionString, 1, delimiterPosition - 1); parsedHeader.Value = Mid(divisionString, delimiterPosition + 1, divisionString.Length - delimiterPosition - 2); } return parsedHeader; } /// <summary> /// Парсинг строки QSO новым методом (сплит, важен порядок данных, точное расположение не имеет значения) /// </summary> /// <param name="qsoString"></param> /// <param name="log"></param> /// <param name="lineNumber"></param> /// <returns></returns> /// <remarks></remarks> private ParsedQSO ParseQsoString(string qsoString, ParsedLog log, int lineNumber) { var parsedQso = new ParsedQSO(); var callSign = log.Callsign; try { var qsoParams = qsoString.Split(';'); var columnsNumber = qsoParams.Length; if (columnsNumber < 10) { parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.BAD; parsedQso.ParseResult.AddError("Invalid QSO line format.", lineNumber); parsedQso.ParseResult.AddErrorRu("Неверный формат строки QSO.", lineNumber); return parsedQso; } var tempString = qsoParams[3]; if (IsValidMode(tempString, out var mode)) parsedQso.Mode = mode; else { var rstSent = qsoParams[4]; if (tempString == "0") mode = "FT8"; else if (rstSent.Length == 2) mode = callSign == "ES6FX" ? "FM" : "SSB"; else if (qsoParams[4].Length == 3) mode = "CW"; if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; parsedQso.ParseResult.AddError("Mode is incorrect or not specified. It will be assumed " + mode + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu("Вид работы неверный или не указан. Будет предполагаться " + mode + ". ", lineNumber); parsedQso.Mode = mode; } tempString = qsoParams[0]; if (!CheckDateFormat(tempString, parsedQso)) { parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.BAD; parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid date: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверная дата: " + tempString + ". ", lineNumber); } tempString = qsoParams[1]; if (!CheckTimeFormat(tempString, parsedQso)) { parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.BAD; parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid time: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверное время: " + tempString + ". ", lineNumber); } tempString = qsoParams[4]; if (mode == "FT8" && IsValidReportFT8(tempString, out var parsedReport)) parsedQso.SentRst = parsedReport; else if (!IsValidReport(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; if (string.IsNullOrEmpty(tempString)) { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Empty sent RST. ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Не указан переданный рапорт. ", lineNumber); } else { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid sent RST: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный переданный рапорт: " + tempString + ". ", lineNumber); } } else parsedQso.SentRst = tempString; if (!IsSerialNrAllowedEmpty) { tempString = qsoParams[5].Replace("o", "0").Replace("O", "0"); if (string.IsNullOrEmpty(tempString) || !IsNumeric(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; if (string.IsNullOrEmpty(tempString)) { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Empty sent exchange. ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Не указан переданный контрольный номер. ", lineNumber); } else { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid sent exchange: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный переданный контрольный номер: " + tempString + ". ", lineNumber); } } else parsedQso.SentNr = FormatSerialNr(tempString); } tempString = qsoParams[2].Trim(); if (string.IsNullOrEmpty(tempString) || !IsValidCallsign(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid station callsign: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный позывной корреспондента: " + tempString + ". ", lineNumber); } else parsedQso.Stn = tempString; tempString = qsoParams[6]; if (mode == "FT8" && IsValidReportFT8(tempString, out var parsedRcvdReport)) parsedQso.RcvdRst = parsedRcvdReport; else if (!IsValidReport(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; if (string.IsNullOrEmpty(tempString)) { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Empty received RST. ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Не указан принятый рапорт. ", lineNumber); } else { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid received RST: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный принятый рапорт: " + tempString + ". ", lineNumber); } } else parsedQso.RcvdRst = tempString; if (!IsSerialNrAllowedEmpty) { tempString = qsoParams[7]; if (string.IsNullOrEmpty(tempString) || !IsNumeric(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; if (string.IsNullOrEmpty(tempString)) { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Empty received exchange. ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Не указан принятый контрольный номер. ", lineNumber); } else { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid received exchange: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный принятый контрольный номер: " + tempString + ". ", lineNumber); } } else parsedQso.RcvdNr = FormatSerialNr(tempString); } tempString = qsoParams[9].ToUpper(); if (!IsValidLocator(tempString)) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.WARNING; if (string.IsNullOrEmpty(tempString)) { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Empty received QTH locator. ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Не указан принятый QTH локатор. ", lineNumber); } else { parsedQso.ParseResult.AddError(Environment.NewLine + " " + "Invalid received QTH locator: " + tempString + ". ", lineNumber); parsedQso.ParseResult.AddErrorRu(Environment.NewLine + " " + "Неверный принятый QTH локатор: " + tempString + ". ", lineNumber); } } else parsedQso.RcvdLocator = tempString; CheckQsoTimeDuringContest(parsedQso, FailedIfNotCheckedQsoTimeDuringContest, lineNumber); CheckQso(parsedQso, log, lineNumber); if (parsedQso.ParseResult.Messages.Any(x => x.Type == PARSE_ERROR_TYPE.BAD) && parsedQso.ParseResult.Type != PARSE_ERROR_TYPE.WARNING) { parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.BAD; } parsedQso.Band = ParsedLog.Band; parsedQso.CallSign = ParsedLog.Callsign; } catch (Exception ex) { parsedQso.ParseResult.Type = PARSE_ERROR_TYPE.BAD; parsedQso.ParseResult.AddError("System error: " + Replace(ex.Message, Environment.NewLine, ""), lineNumber); parsedQso.ParseResult.AddErrorRu("Системная ошибка: " + Replace(ex.Message, Environment.NewLine, ""), lineNumber); } return parsedQso; } /// <summary> /// Парсинг отчета из потока байтов /// </summary> /// <remarks></remarks> public void ParseLog() { Encoding encoding = null; var currentLogPart = EdiLogParts.None; var totalQsoCount = 0; ParsedLog = new ParsedLog(); ParsedLogs = [ParsedLog]; QsoCount = 0; ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.NONE; if (LogStream == null || LogStream.Length == 0) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; else { var endOfLogFound = false; LogStream.Position = 0; encoding = FileEncoding.DetectFileEncoding(LogStream, Encoding.GetEncoding(1251)); if (encoding == null || encoding.CodePage == 1252) encoding = Encoding.GetEncoding(1251); HeaderFields = new List<ParsedHeader>(); LogStream.Position = 0; var reader = new StreamReader(LogStream, encoding); var i = 0; var j = 0; var qsoParsingErrors = 0; var qsoParsingWarnings = 0; string logLine; do { i += 1; logLine = reader.ReadLine(); ParsedLog.Lines.Add(logLine); if (Left(logLine, 10) == "==========") { currentLogPart = EdiLogParts.None; totalQsoCount = 0; ParsedLog = new ParsedLog(); ParsedLogs.Add(ParsedLog); QsoCount = 0; ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.NONE; j = 0; continue; } if (!string.IsNullOrWhiteSpace(logLine)) { j += 1; System.Text.RegularExpressions.Regex rxStartOfLog = new System.Text.RegularExpressions.Regex(StartOfLog); System.Text.RegularExpressions.Regex rxStartOfQsos = new System.Text.RegularExpressions.Regex(StartOfQsos); System.Text.RegularExpressions.Regex rxStartOfRemarks = new System.Text.RegularExpressions.Regex(StartOfRemarks); System.Text.RegularExpressions.Regex rxEndOfLog = new System.Text.RegularExpressions.Regex(EndOfLog); System.Text.RegularExpressions.Regex rxEndOfLog2 = new System.Text.RegularExpressions.Regex(EndOfLog2); if (currentLogPart == EdiLogParts.None) { if (rxStartOfLog.Matches(logLine).Count == 1) currentLogPart = EdiLogParts.Headers; else if (j > 5) { // Начало отчета ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.FORMATTED_LOG_NOT_FOUND; ParsedLog.ParseResult.AddError("First line of a log must be [REG1TEST;1]."); ParsedLog.ParseResult.AddErrorRu("Первая строка отчета должна быть [REG1TEST;1]."); break; } } else if (rxStartOfRemarks.Matches(logLine).Count == 1) currentLogPart = EdiLogParts.Remarks; else if (rxStartOfQsos.Matches(logLine).Count == 1) { currentLogPart = EdiLogParts.QsOs; var parsedHeader = ParseDivisionString(logLine); if (parsedHeader.ParseResult.Type == PARSE_ERROR_TYPE.NONE) { if (IsNumeric(parsedHeader.Value)) totalQsoCount = Convert.ToInt32(parsedHeader.Value); } } else if (rxEndOfLog.Matches(logLine).Count == 1 | rxEndOfLog2.Matches(logLine).Count == 1) { // ReSharper disable once RedundantAssignment currentLogPart = EdiLogParts.EndOfLog; // Конец отчета var parsedHeader = ParseDivisionString(logLine); if (parsedHeader.ParseResult.Type == PARSE_ERROR_TYPE.NONE) { parsedHeader.Name = "Created-by"; HeaderFields.Add(parsedHeader); } endOfLogFound = true; break; } else if (currentLogPart == EdiLogParts.Remarks) { var parsedHeader = ParseHeaderString(logLine, i); if (parsedHeader.ParseResult.Type != PARSE_ERROR_TYPE.NONE) continue; if (parsedHeader.Name == "X-RUSOBL" && !parsedHeader.Value.IsNullOrEmpty()) { if (parsedHeader.Value.IsOblId()) { ParsedLog.TeamOblId = parsedHeader.Value; } else { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError($"Invalid russian region idetificator: {parsedHeader.Value}."); ParsedLog.ParseResult.AddErrorRu($"Неверный идентификатор Субъекта РФ в строке X-RUSOBL: {parsedHeader.Value}."); } } else if (parsedHeader.Name == "X-LOCATION" && !parsedHeader.Value.IsNullOrEmpty()) { if (parsedHeader.Value.IsRda()) { ParsedLog.TeamOblId = parsedHeader.Value.Replace("-", ""); } else { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError($"Invalid RDA identificator: {parsedHeader.Value}."); ParsedLog.ParseResult.AddErrorRu($"Неверный идентификатор района RDA в строке X-LOCATION: {parsedHeader.Value}."); } } } else if (currentLogPart == EdiLogParts.Headers) { var parsedHeader = ParseHeaderString(logLine, i); if (parsedHeader.ParseResult.Type != PARSE_ERROR_TYPE.NONE) continue; HeaderFields.Add(parsedHeader); var headerParseResult = ParseHeaderValues(parsedHeader, i); if (headerParseResult.Type == PARSE_ERROR_TYPE.NONE) { if (parsedHeader.Name == "PCall") { if (string.IsNullOrEmpty(ParsedLog.Callsign)) { var cl = Replace(Trim(parsedHeader.Value), new string((char) 9, 1), ""); if (IsValidCallsign(cl)) { ParsedLog.Callsign = cl; } else { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Invalid own callsign. Callsign must include only latin letters, digits and slash." + Environment.NewLine, i); ParsedLog.ParseResult.AddErrorRu("Неверный свой позывной " + parsedHeader.Value + ". Позывной может содержать лишь прописные латинские буквы, цифры и символы дроби." + Environment.NewLine, i); } } else { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Several tags PCall found.", i); ParsedLog.ParseResult.AddErrorRu("Найдено несколько ключевых слов PCall.", i); } } else if (parsedHeader.Name == "PWWLo") { if (parsedHeader.Value.Length != 6) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Line " + i + ": " + "Wrong QTH Locator.", i); ParsedLog.ParseResult.AddErrorRu("Строка " + i + ": " + "Неверный QTH локатор.", i); } else if (string.IsNullOrEmpty(ParsedLog.GridLocator)) ParsedLog.GridLocator = parsedHeader.Value.ToUpper(); else { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Line " + i + ": " + "Several tags PWWLo found.", i); ParsedLog.ParseResult.AddErrorRu("Строка " + i + ": " + "Найдено несколько ключевых слов PWWLo.", i); } } else if (parsedHeader.Name == "RHBBS") { if (parsedHeader.Value.IsNullOrEmpty() && ParsedLog.EMail.IsNullOrEmpty()) { if (!EmailAddress.IsNullOrEmpty()) ParsedLog.EMail = EmailAddress; } else if (ParsedLog.EMail.IsNullOrEmpty()) ParsedLog.EMail = parsedHeader.Value; } } else { ParsedLog.ParseResult.Messages.AddRange(headerParseResult.Messages); } if (headerParseResult.Type == PARSE_ERROR_TYPE.CRITICAL) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; if (headerParseResult.Type == PARSE_ERROR_TYPE.OTHER_CONTEST) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.OTHER_CONTEST; break; } if (headerParseResult.Type == PARSE_ERROR_TYPE.BAD) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; if (headerParseResult.Type == PARSE_ERROR_TYPE.WARNING) { if (ParsedLog.ParseResult.Type == LOG_PARSE_ERROR_TYPE.NONE) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.WARNING; } if (parsedHeader.ParseResult.Type == PARSE_ERROR_TYPE.WARNING | parsedHeader.ParseResult.Type == PARSE_ERROR_TYPE.NONE) ParsedLog.Headers.Add(parsedHeader); } else if (currentLogPart == EdiLogParts.QsOs) { // Строка QSO QsoCount +=1; var parsedQso = ParseQsoString(logLine, ParsedLog, i); if (parsedQso != null && parsedQso.CallSign?.ToUpper() == "ERROR") { QsoCount -= 1; } else if (parsedQso != null) { if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.WARNING) { if (qsoParsingWarnings <= MaximumTotalErrorsWarnings) { ParsedLog.ParseResult.Messages.AddRange(parsedQso.ParseResult.Messages); } else if (qsoParsingWarnings == MaximumTotalErrorsWarnings + 1) { ParsedLog.ParseResult.AddWarning($"The number of QSOs with warnings exceeded {MaximumTotalErrorsWarnings:N}. The output of warnings was breaked."); ParsedLog.ParseResult.AddWarningRu($"Число радиосвязей с замечаниями превысило {MaximumTotalErrorsWarnings:N}. Вывод замечаний прерван."); } } else if (parsedQso.ParseResult.Type != PARSE_ERROR_TYPE.NONE) { if (qsoParsingErrors <= MaximumTotalErrorsWarnings) { ParsedLog.ParseResult.Messages.AddRange(parsedQso.ParseResult.Messages); } else if (qsoParsingErrors == MaximumTotalErrorsWarnings + 1) { ParsedLog.ParseResult.AddError($"The number of QSOs with errors exceeded {MaximumTotalErrorsWarnings:N}. The output of errors was breaked."); ParsedLog.ParseResult.AddErrorRu($"Число радиосвязей с ошибками превысило {MaximumTotalErrorsWarnings:N}. Вывод ошибок прерван."); } } if ( ( parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE || parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.WARNING ) && ParsedLog.ContestDate == null ) { ParsedLog.ContestDate = new DateTime(parsedQso.Time.Year, parsedQso.Time.Month, parsedQso.Time.Day); } if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.CRITICAL) { // ReSharper disable once RedundantAssignment qsoParsingErrors += 1; ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; endOfLogFound = true; break; } if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.BAD) { if (QsoCount <= totalQsoCount) { qsoParsingErrors += 1; ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; } } if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.WARNING) { qsoParsingWarnings += 1; if (ParsedLog.ParseResult.Type == LOG_PARSE_ERROR_TYPE.NONE) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.WARNING; } if (parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.WARNING | parsedQso.ParseResult.Type == PARSE_ERROR_TYPE.NONE) ParsedLog.QSOs.Add(parsedQso); } } } } while (logLine != null); // reader.Close(); if (QsoCount == 0) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("There are no QSOs in the log."); ParsedLog.ParseResult.AddErrorRu("В отчете нет ни одной связи."); } if (totalQsoCount != QsoCount) { if (ParsedLog.ParseResult.Type == LOG_PARSE_ERROR_TYPE.NONE) ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.WARNING; ParsedLog.ParseResult.AddWarning("Number of QSOs is not corresponds to number from [QSORecords;] line."); ParsedLog.ParseResult.AddWarningRu("Число связей не соответствует указанному в строке [QSORecords;]."); } if (!endOfLogFound & ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.FORMATTED_LOG_NOT_FOUND & ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.OTHER_CONTEST & ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.ERRORS_FOUND) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.WARNING; ParsedLog.ParseResult.AddWarning("Tag [END;] not found."); ParsedLog.ParseResult.AddWarningRu("Ключевое слово [END;] не найдено."); } if (ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.FORMATTED_LOG_NOT_FOUND) { if (string.IsNullOrEmpty(ParsedLog.GetHeaderValue("PBand"))) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("PBand tag not found."); ParsedLog.ParseResult.AddErrorRu("Ключевое слово PBand не найдено, не указан диапазон."); } } if (ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.FORMATTED_LOG_NOT_FOUND & ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.OTHER_CONTEST) { CheckHeaders(); GetCategory(); CheckOperatorsCountByCategory(); if (string.IsNullOrEmpty(ParsedLog.Callsign)) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("PCall tag not found or your callsign is incorrect."); ParsedLog.ParseResult.AddErrorRu("Ключевое слово PCall не найдено или же позывной указан некорректно."); } else { ParsedLog.ParseResult.AddInfo("Your callsign is detected as: " + ParsedLog.Callsign); ParsedLog.ParseResult.AddInfoRu("Определен ваш позывной: " + ParsedLog.Callsign); } } } if (ParsedLog.ParseResult.Type != LOG_PARSE_ERROR_TYPE.NONE) { if (!string.IsNullOrEmpty(Parameters.HelpText)) { ParsedLog.ParseResult.AddInfoRu(Parameters.HelpText, priority: 1); } if (!string.IsNullOrEmpty(Parameters.HelpTextEn)) { ParsedLog.ParseResult.AddInfo(Parameters.HelpTextEn, priority: 1); } } if ( ( ParsedLog.ParseResult.Type == LOG_PARSE_ERROR_TYPE.NONE || ParsedLog.ParseResult.Type == LOG_PARSE_ERROR_TYPE.WARNING ) && !string.IsNullOrEmpty(Parameters.ResultsLink) ) { ParsedLog.ParseResult.AddInfo("Check results at " + Parameters.ResultsLink); ParsedLog.ParseResult.AddInfoRu("Ознакомьтесь с результатами по адресу " + Parameters.ResultsLink); } ParsedLog.Encoding = encoding; ParsedLog.QsoCount = QsoCount; ParsedLog.Format = LOG_FORMAT.EDI; var tourIndicator = GetTourIndicatorForFileName(ParsedLog); var categoryIndicator = GetCategoryIndicatorForFileName(ParsedLog); ParsedLog.FileName = ParsedLog.Callsign == null ? null : tourIndicator + Replace(ParsedLog.Callsign, "/", "-") + categoryIndicator; ParsedLog.CategoryIndicator = categoryIndicator; } protected virtual void CheckOperatorsCountByCategory() { if (!ParsedLog.Callsign.IsRussianCallsign()) return; var mainOpHeader = ParsedLog.GetHeaderValue_All("RCall") + ";" + ParsedLog.GetHeaderValue("RName"); var opsHeaders = mainOpHeader + Environment.NewLine + ParsedLog.GetHeaderValue_All("MOpe"); var opsHeadersLines = opsHeaders .Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries) .ToArray(); var isMultiOp = IsMultiOpCategory(ParsedLog); if (opsHeadersLines.Length > 1 && !isMultiOp) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Several operators in SINGLE-OP category."); ParsedLog.ParseResult.AddErrorRu("При заявке в подгруппу с одним оператором не следует указывать несколько операторов."); } else if (opsHeadersLines.Length == 1 && isMultiOp) { ParsedLog.ParseResult.Type = LOG_PARSE_ERROR_TYPE.ERRORS_FOUND; ParsedLog.ParseResult.AddError("Just one operator in MULTI-OP category."); ParsedLog.ParseResult.AddErrorRu("При заявке в подгруппу с несколькими операторами необходимо указать минимум двух операторов."); } } /// <summary> /// Поиск значения тега по тегу и вывод количества строк с данным тегом /// </summary> // ReSharper disable once UnusedMethodReturnValue.Local protected int GetHeaderValue(IEnumerable<ParsedHeader> headerFields, string name, int count, out string value) { var res = 0; value = string.Empty; foreach (var header in headerFields) { if (string.Equals(header.Name, name, StringComparison.CurrentCultureIgnoreCase)) { res += 1; if (res > count && count != 0) continue; if (string.IsNullOrEmpty(header.Value)) continue; if (string.IsNullOrEmpty(value)) value = header.Value; else value += Environment.NewLine + header.Value; } } return res; } /// <summary> /// Проверка заголовков в зависимости от контеста /// </summary> protected virtual void CheckHeaders() { } /// <summary> /// Проверка QSO в зависимости от контеста /// </summary> protected abstract void CheckQso(ParsedQSO qso, ParsedLog log, int lineNumber); /// <summary> /// Парсинг категории в зависимости от контеста /// </summary> protected abstract bool ParseCategory(out string category); /// <summary> /// Получение и проверка категории в зависимости от контеста /// </summary> protected virtual void GetCategory() { if (ParseCategory(out var category)) { ParsedLog.Category = category; ParsedLog.ParseResult.AddInfo("Your category is detected as: " + ParsedLog.Category); ParsedLog.ParseResult.AddInfoRu("Определена ваша заявленная подгруппа: " + ParsedLog.Category) ; } } public static bool CheckDateFormat(string dateString, ParsedQSO qso) { if (DateTime.TryParseExact(dateString, "yyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOut)) { qso.Time = dateOut; return true; } return false; } public static bool CheckTimeFormat(string timeString, ParsedQSO qso) { if (DateTime.TryParseExact(timeString, "HHmm", CultureInfo.InvariantCulture, DateTimeStyles.None, out var timeOut)) { qso.Time = qso.Time.AddMinutes(timeOut.Minute); qso.Time = qso.Time.AddHours(timeOut.Hour); return true; } return false; } } }