using System.Collections.Generic;
using System.Globalization;
using System.Collections;
using CSharpFunctionalExtensions;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Diagnostics;
public static void Main()
var culture = new CultureInfo("pt-BR");
Console.WriteLine($"After setting, CultureInfo is {culture.Name.ToString()}");
Console.WriteLine($"After setting, CultureInfo DisplayName is {culture.DisplayName}");
Console.WriteLine($"After setting, CultureInfo NativeName is {culture.NativeName}");
Console.WriteLine($"The current culture IsNeutralCulture? {culture.IsNeutralCulture}");
Console.WriteLine("------------------------------------------------------------------------------------");
CultureInfo.CurrentCulture = culture;
Console.WriteLine(System.IO.Directory.GetLogicalDrives());
public static void TestNameParts()
(string name, string culture)[] nameTests = {
("Laudo médico com afastamento por 2 meses 13042021.pdf", "pt-BR")
, ("Laudo médico com afastamento por 2 meses .pdf", "pt-BR")
, ("Laudo médico com afastamento por 100 dias.pdf", "pt-BR")
, ("Prontuário Itau com anotações entre 2007 e 2021.pdf", "pt-BR")
, ("Recibos comparecimento a Fisioterapia entre 052016 e 122019.pdf", "pt-BR")
, ("US abdominal˗cisto hepático_5f9a739a-0b2e-4fcc-9228-81035e6e50ea 23122022 v3.docx", "pt-BR")
, ("Ruy Quiroga_20230605161406.pdf", "pt-BR")
, ("Aderaldo Vieira Chaves - SOLICITAÇÃO DE EXAME_5f9a739a-0b2e-4fcc-9228-81035e6e50ea.pdf", "pt-BR")
, ("Nome do paciente D'Oliveira - RECEITUÁRIO CONTROLE ESPECIAL_5f9a739a-0b2e-4fcc-9228-81035e6e50ea durante internação Copa D'Or de 12 a 15052017 - Ciprofloxacino 500 mg 3 2017.pdf", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo 3 - 23122022.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo - 23122022 2.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo - 122022 2.docx", "pt-BR")
, ("Nome do paciente - relatório internação de 12 a 20122022 - 2 20__.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo - 23122022 v2.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo v3 - 23122022.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo - 122022 v2.docx", "pt-BR")
, ("Nome do paciente - relatório internação de 12 a 20122022 - v2 20__.docx", "pt-BR")
, ("US abdominal-cisto hepático 23122022.docx", "pt-BR")
, ("US abdominal˗cisto hepático 23122022.docx", "pt-BR")
, ("US abdominal–cisto hepático 23122022.docx", "pt-BR")
, ("US abdominal - cisto hepático 23122022.docx", "pt-BR")
, ("US abdominal - cisto hepático v3 23122022.doc", "pt-BR")
, ("US abdominal 23122022.docx", "pt-BR")
, ("US abdominal - 23122022.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo v2 - 23122022.docx", "pt-BR")
, ("Nome do paciente - v2 23122022.docx", "pt-BR")
, ("Outro paciente - exame laboratorial amplo para home-care - v2 23122022.docx", "pt-BR")
, ("Nome do paciente - relatório internação de 12 a 20122022 - v2 23122022.docx", "pt-BR")
, ("Nome do paciente - exame laboratorial amplo - 202_ v3.docx", "pt-BR")
, ("Nome do paciente D'Oliveira - RECEITUÁRIO CONTROLE ESPECIAL - Ciprofloxacino 500 mg 2017.pdf", "pt-BR")
, ("Fulano Sicrano Beltrano - relatório internação - de 1208 até 20062021 - erosão traqueal - v2 12072021.docx", "pt-BR")
, ("Paulo Afonso Zavataro - Recibo PF referente ao período de 06 a 11082023 internação no Hospital Copa Star.pdf", "pt-BR")
, ("Paulo Afonso Zavataro - Recibo PJ referente ao período de 12 a 17082023 internação no Hospital Copa Star - NFSe_00003478_02874440.pdf", "pt-BR")
, ("Paulo Afonso Zavataro - Relatório e Recibo PF referente ao período de 06 a 11082023 internação no Hospital Copa Star - 77d93944-071c-4f01-8788-838d2a204a3d.pdf", "pt-BR")
, ("Waldenice de Albuquerque Haidamus - Relatório da internação entre 3005 a 14062013.docx", "pt-BR")
, ("Miuza de Holanda Fragelli - anotação Outlook_backup_2013_abr._20__13_38_12.pdf", "pt-BR")
, ("Miuza de Holanda Fragelli - anotação Outlook_backup_2013_apr_20__13_38_12.pdf", "en-US")
, ("Miuza de Holanda Fragelli - anotação Outlook_backup_2013_abr_20__13_38_12.pdf", "pt-BR")
, ("Gilda Elena Brandão Andrade D'Oliveira - RECEITUÁRIO CONTROLE ESPECIAL - Zolpidem SL 5 mg 082021.docx", "pt-BR")
, ("Gilda Elena Brandão Andrade D'Oliveira - RECEITUÁRIO CONTROLE ESPECIAL - Zolpidem CR 6,25 mg.docx", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - trazodona retard 150 mg", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - Prolopa HBS 100_25 mg.docx", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - Prolopa orodispersível 125 mg.docx", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - Parkidopa 25_250 mg", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - Parkidopa 25_250 mg.docx", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - pramipexol Sifrol 1,5 mg.docx", "pt-BR")
, ("x - RECEITUÁRIO CONTROLE ESPECIAL - Rivastigmina Exelon Patch 9 mg.docx", "pt-BR")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - 23.05.pdf", "pt-BR")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - 05.23.pdf", "en-US")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - 23.5.pdf", "pt-BR")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - 5.23.pdf", "en-US")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - abr.2005.pdf", "pt-BR")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - abr2005.pdf", "pt-BR")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - apr2005.pdf", "en-US")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - march2021.pdf", "en-US")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - march.2021.pdf", "en-US")
, ("CARLOS ANTONIO MENEZES AMARAL BARBOSA - março2021.pdf", "pt-BR")
,("CARLOS ANTONIO MENEZES AMARAL BARBOSA - março.2021.pdf", "pt-BR")
var docs = new List<DocName>();
foreach (var fileName in nameTests)
var docName = DocName.CreateInstance(fileName.name, new MessageDialogServiceNone(), false, fileName.culture);
var orderedDocs = DocNameHelper.OrderDocsByDate(docs).ToArray();
var allDates = orderedDocs.Select(doc => doc.GetDateTimeOrLastPartOfIntervalForOrderDoc()).ToList();
foreach (var n in allDates)
Console.WriteLine($"################################################ Ordered docs by Date ################################################");
foreach (var n in orderedDocs)
Console.WriteLine($"###### {n.FullNameWithExtension} ######");
Console.WriteLine($"-------------------------------------------------------------------------------------");
Console.WriteLine($"FirstPart: {(n.FirstPartName.IsSuccess ? n.FirstPartName.Value : n.FirstPartName.Error)}");
Console.WriteLine($"MiddlePart: {(n.MiddlePartName.IsSuccess ? n.MiddlePartName.Value : n.MiddlePartName.Error)}");
Console.WriteLine($"LastPart: {(n.LastPartName.IsSuccess ? n.LastPartName.Value : n.LastPartName.Error)}");
Console.WriteLine($"Extension: {n.Extension}");
Console.WriteLine("######################################################################################"); Console.WriteLine();
public class DocName : ValueObject
private readonly IMessageDialogService _dialog;
private readonly bool _raiseEvents;
private readonly int _maxNumOfParts;
private int _maxNumDelimiter;
private string _patientNameFromDocType = NoNameFromDocType;
private const string NoNameFromDocType = "No DisplayName From DocType";
#endregion Private Fields
public const string GrabNameAndExtension = @"(?<fullname>.+?)(\.(?<extension>[^.]*)$|$)";
public const string GrabPartsOfName = @"([\w _<>+;.,']*)*";
public const string GrabDelimiterWithSpaceAround = @"( - )";
public const string GrabFakeDelimiterWithoutAnySpaceAround = @"(?<before>[\d\w' -.]*)(?<=\w)(?<FakeDelimiter>[-˗–])(?=\w)(?<after>[\d\w -.]*)";
public const string TemporarySubstitutionForFakeDelimiter = @"${before}<>${after}";
public const string GrabTemporarySubstitutionForFakeDelimiter = @"(?<before>[\d\w -.]*)(?<=\w)(?<FakeDelimiter>\<\>)(?=\w)(?<after>[\d\w -.]*)";
public const string RestorePreviousStateBeforeFakeDelimiter = @"${before}-${after}";
#endregion Public Constants
#region Constructors and Destructor
private DocName(string fullNameWithExtension, IMessageDialogService dialog, bool raiseEvents, string culture, int maxNumOfParts = 3)
if (string.IsNullOrEmpty(fullNameWithExtension))
throw new ArgumentException("Value cannot be null or empty.", nameof(fullNameWithExtension));
_raiseEvents = raiseEvents;
this._maxNumOfParts = maxNumOfParts;
CultureInfoName = culture;
OriginalNameBeforeTreat = fullNameWithExtension;
GetParts(fullNameWithExtension);
public static DocName CreateInstance(string fullNameWithExtension, IMessageDialogService dialog,
bool raiseEvents, string culture, int maxPossibleYear = int.MinValue, int minPossibleYear = int.MaxValue)
if (maxPossibleYear == int.MinValue)
maxPossibleYear = DateInSentence.GetMaxPossibleYear();
if (minPossibleYear == int.MaxValue)
return new DocName(fullNameWithExtension, dialog, raiseEvents, culture) { MaxPossibleYear = maxPossibleYear, MinPossibleYear = minPossibleYear };
public static DocName None(IMessageDialogService dialog) => new(string.Empty, dialog, false, string.Empty);
public static DocName None() => new();
#endregion Constructors and Destructor
public bool CreateElement(WhichElementItIs whichElementItIs, string elemValue, DocNamePart docNamePart, ElementRelativePosition position)
if (ElementExists(whichElementItIs)) return false;
var connector = position == ElementRelativePosition.First ? string.Empty : " " ;
FirstPartName.Value.CreateElement(whichElementItIs, elemValue, position, connector);
MiddlePartName.Value.CreateElement(whichElementItIs, elemValue, position, connector);
LastPartName.Value.CreateElement(whichElementItIs, elemValue, position, connector);
case DocNamePart.Extension:
return ElementExists(whichElementItIs);
internal bool UpdateElementAndConnectorBefore(WhichElementItIs whichElementItIs, string elemValue
, string connectorBefore = " ")
if (!ElementExists(whichElementItIs)) return false;
if (FirstPartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.First;
else if (MiddlePartName.IsSuccess && MiddlePartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.Middle;
else if (LastPartName.IsSuccess && LastPartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.Last;
FirstPartName.Value.UpdateElementAndConnectorBefore(whichElementItIs, elemValue, connectorBefore);
MiddlePartName.Value.UpdateElementAndConnectorBefore(whichElementItIs, elemValue, connectorBefore);
LastPartName.Value.UpdateElementAndConnectorBefore(whichElementItIs, elemValue, connectorBefore);
case DocNamePart.Extension:
return ElementExists(whichElementItIs);
public bool DeleteElement(WhichElementItIs whichElementItIs)
if (!ElementExists(whichElementItIs)) return false;
if (FirstPartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.First;
else if (MiddlePartName.IsSuccess && MiddlePartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.Middle;
else if (LastPartName.IsSuccess && LastPartName.Value.ElementExists(whichElementItIs))
docPart = DocNamePart.Last;
FirstPartName.Value.DeleteElementAndAdjustConnectorOfOtherAfterIfNeeded(whichElementItIs);
MiddlePartName.Value.DeleteElementAndAdjustConnectorOfOtherAfterIfNeeded(whichElementItIs);
LastPartName.Value.DeleteElementAndAdjustConnectorOfOtherAfterIfNeeded(whichElementItIs);
case DocNamePart.Extension:
return !ElementExists(whichElementItIs);
public VersionInSentence GetVersionInSentence()
if (!HasVersionInSentence) return VersionInSentence.NullVersion;
if (FirstPartName.Value.VersionInPart.HasVersion)
return FirstPartName.Value.VersionInPart;
if (MiddlePartName.IsSuccess && MiddlePartName.Value.VersionInPart.HasVersion)
return MiddlePartName.Value.VersionInPart;
if (LastPartName.IsSuccess && LastPartName.Value.VersionInPart.HasVersion)
return LastPartName.Value.VersionInPart;
return VersionInSentence.NullVersion;
public string GetVersionPrefixInSentence() => GetVersionInSentence().VersionPrefix;
public int? GetVersionNumberInSentence() => GetVersionInSentence().VersionNumber;
public bool UpdateVersion(int version, bool withPrefix, string whichPrefix = "v", string connectorBefore = " ")
if (!HasVersionInSentence) return false;
var newVersion = (withPrefix ? whichPrefix : string.Empty) + (version + GetVersionNumberInSentence()).ToString();
return UpdateElementAndConnectorBefore(WhichElementItIs.WhicheverVersion, newVersion, connectorBefore);
public bool HasVersionInSentence => FirstPartName.Value.VersionInPart.HasVersion ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.VersionInPart.HasVersion) ||
(LastPartName.IsSuccess && LastPartName.Value.VersionInPart.HasVersion);
public bool HasVersionInSentenceWithPrefix => FirstPartName.Value.VersionInPart.HasVersionWithPrefix ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.VersionInPart
.HasVersionWithPrefix) || (LastPartName.IsSuccess &&
LastPartName.Value.VersionInPart.HasVersionWithPrefix);
public bool HasVersionInSentenceNumberOnly => FirstPartName.Value.VersionInPart.HasVersionNumberOnly ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.VersionInPart
.HasVersionNumberOnly) || (LastPartName.IsSuccess &&
LastPartName.Value.VersionInPart.HasVersionNumberOnly);
public InNameGUID GetGUID()
if (!HasGUID) return InNameGUID.NullGUID;
if (FirstPartName.Value.GUIDInPart.HasGUID)
return FirstPartName.Value.GUIDInPart;
if (MiddlePartName.IsSuccess && MiddlePartName.Value.GUIDInPart.HasGUID)
return MiddlePartName.Value.GUIDInPart;
if (LastPartName.IsSuccess && LastPartName.Value.GUIDInPart.HasGUID)
return LastPartName.Value.GUIDInPart;
return InNameGUID.NullGUID;
public bool HasGUID => FirstPartName.Value.GUIDInPart.HasGUID ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.GUIDInPart.HasGUID) ||
(LastPartName.IsSuccess && LastPartName.Value.GUIDInPart.HasGUID);
public DateInSentence GetDateInSentence()
if (!HasDateInSentence) return DateInSentence.NullDate;
if (FirstPartName.Value.DateInPart.HasDate)
return FirstPartName.Value.DateInPart;
if (MiddlePartName.IsSuccess && MiddlePartName.Value.DateInPart.HasDate)
return MiddlePartName.Value.DateInPart;
if (LastPartName.IsSuccess && LastPartName.Value.DateInPart.HasDate)
return LastPartName.Value.DateInPart;
return DateInSentence.NullDate;
public DateTime GetDateTime()
return !HasDateInSentence ? DateTime.MinValue : GetDateInSentence().DateAndTime;
public DateTime GetDateTimeOrLastPartOfIntervalForOrderDoc()
return GetDateInSentence().DateAndTime;
return HasIntervalInSentence
? (GetIntervalInSentence().IntervalType == IntervalType.TemporalClue ? GetIntervalInSentence().DateBegin.DateAndTime : GetIntervalInSentence().DateEnd.DateAndTime)
public bool HasDateInSentence => FirstPartName.Value.DateInPart.HasDate ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.DateInPart.HasDate) ||
(LastPartName.IsSuccess && LastPartName.Value.DateInPart.HasDate);
public bool UpdateExistingDate(string date, string connectorBefore = "")
=> UpdateElementAndConnectorBefore(WhichElementItIs.FileNameDate, date, connectorBefore);
public bool InsertDate(string date, DocNamePart docNamePart, ElementRelativePosition position)
if (!TryCreateDate(date, out var newDate)) return false;
if (docNamePart == DocNamePart.First)
FirstPartName.Value.AddDateInSentence(newDate, position);
if (MiddlePartName.IsSuccess && docNamePart == DocNamePart.Middle)
MiddlePartName.Value.AddDateInSentence(newDate, position);
if (LastPartName.IsSuccess && docNamePart == DocNamePart.Last)
LastPartName.Value.AddDateInSentence(newDate, position);
private bool TryCreateDate(string date, out DateInSentence newDate)
newDate = DateInSentence.NullDate;
if (string.IsNullOrEmpty(date)) return false;
newDate = DateInSentence.CreateInstance(date, CultureInfoName, null);
return !newDate.ElementValue.IsFailure;
public IntervalInSentence GetIntervalInSentence()
if (!HasIntervalInSentence) return IntervalInSentence.Null;
if (FirstPartName.Value.IntervalInPart.HasInterval)
return FirstPartName.Value.IntervalInPart;
if (MiddlePartName.IsSuccess && MiddlePartName.Value.IntervalInPart.HasInterval)
return MiddlePartName.Value.IntervalInPart;
if (LastPartName.IsSuccess && LastPartName.Value.IntervalInPart.HasInterval)
return LastPartName.Value.IntervalInPart;
return IntervalInSentence.Null;
public bool HasIntervalInSentence => FirstPartName.Value.IntervalInPart.HasInterval ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.IntervalInPart.HasInterval) ||
(LastPartName.IsSuccess && LastPartName.Value.IntervalInPart.HasInterval);
#region MedicineAndDosage
public MedicineAndDosageInSentence GetDrugAndDoseInSentence()
if (!HasDrugAndDoseInSentence) return MedicineAndDosageInSentence.NullMedicine;
if (FirstPartName.Value.MedicinesAndDosesInPart.HasDrugAndDose)
return FirstPartName.Value.MedicinesAndDosesInPart;
if (MiddlePartName.IsSuccess && MiddlePartName.Value.MedicinesAndDosesInPart.HasDrugAndDose)
return MiddlePartName.Value.MedicinesAndDosesInPart;
if (LastPartName.IsSuccess && LastPartName.Value.MedicinesAndDosesInPart.HasDrugAndDose)
return LastPartName.Value.MedicinesAndDosesInPart;
return MedicineAndDosageInSentence.NullMedicine;
public bool HasDrugAndDoseInSentence => FirstPartName.Value.MedicinesAndDosesInPart.HasDrugAndDose ||
(MiddlePartName.IsSuccess && MiddlePartName.Value.MedicinesAndDosesInPart.HasDrugAndDose) ||
(LastPartName.IsSuccess && LastPartName.Value.MedicinesAndDosesInPart.HasDrugAndDose);
#endregion MedicineAndDosage
public DocName TurnIntoGeneric()
FirstPartName = Result.Success(NamePart.DefinePart(GenericPrefix, DocNamePart.First, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
return new DocName(FullName, _dialog, _raiseEvents, CultureInfoName) { MaxPossibleYear = this.MaxPossibleYear };
public DocName PromoteIntoPatientDoc(string fullName)
FirstPartName = Result.Success(NamePart.DefinePart(fullName, DocNamePart.First, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
return new DocName(FullName, _dialog, _raiseEvents, CultureInfoName) { MaxPossibleYear = this.MaxPossibleYear };
public Result<string> GetNameOfPatientFromDoc()
return Result.Failure<string>("Doc has no patient associated; it is generic");
return FileNameHasOnlyOnePart
? DecideHowToReturnPatientNameIfFileNameHasOnlyOnePart()
: Result.Success(FirstPartName.Value.PartText);
public void SetNameOfPatientFromDoc(string patientName)
throw new InvalidOperationException(
"Cannot use this method to turn Doc in Non-Generic; use PromoteIntoPatientDoc instead.");
if (!FileNameHasOnlyOnePart)
throw new InvalidOperationException(
"Cannot use this method to rename patient in FirstPart; it has to be done in DocType or upper");
_patientNameFromDocType = patientName;
#endregion Public Methods
public string CultureInfoName { get; private set; }
public bool IsGeneric => FirstPartName.IsSuccess && FirstPartName.Value.PartText == GenericPrefix;
public string GenericPrefix { get; set; } = "x";
public string Delimiter { get; set; } = "-";
public List<(string delimiter, int positionAt, DocNamePart whichPart)> WrongDelimiter { get; private set; } = new();
public int MaxPossibleYear { get; private set; } = 2025;
public int MinPossibleYear { get; private set; } = 1990;
public bool OriginalNameCannotBeRehydrated => WrongDelimiter.Any() || OriginalNumberOfParts > _maxNumOfParts || !OriginalNameBeforeTreat.Equals(FullNameWithExtension);
public bool OriginalNameCanOnlyBeRehydratedIfPutEnDashHyphensBack()
if (!WrongDelimiter.Any()) return false;
var stateBefore = RehydrateWithOriginalEnDashHyphen;
RehydrateWithOriginalEnDashHyphen = true;
var sameName = OriginalNameBeforeTreat.Equals(FullNameWithExtension);
RehydrateWithOriginalEnDashHyphen = stateBefore;
private readonly string[] _wrongDelimiters = { "˗", "–" };
public string FakeDelimiter { get; set; } = "<>";
public Result<NamePart> FirstPartName { get; private set; }
public Result<string> FirstPartNameOriginal { get; private set; }
public Result<NamePart> MiddlePartName { get; private set; }
public Result<string> MiddlePartNameOriginal { get; private set; }
public int PositionOfMiddlePart { get; private set; }
public Result<NamePart> LastPartName { get; private set; }
public Result<string> LastPartNameOriginal { get; private set; }
public int PositionOfLastPart { get; private set; }
public bool? VersionIsBeforeDate
if (!HasDateInSentence || !HasVersionInSentence) return null;
if (FirstPartName.Value.CheckIfHasBothVersionAndDate())
return FirstPartName.Value.CheckAtPartElementIfVersionIsFirst();
if (MiddlePartName.IsSuccess && MiddlePartName.Value.CheckIfHasBothVersionAndDate())
return MiddlePartName.Value.CheckAtPartElementIfVersionIsFirst();
if (LastPartName.IsSuccess && LastPartName.Value.CheckIfHasBothVersionAndDate())
return LastPartName.Value.CheckIfHasBothVersionAndDate();
public string Extension { get; private set; }
public string ExtensionWithDot => "." + this.Extension;
public bool RehydrateWithOriginalEnDashHyphen { get; set; } = false;
public string FullName => ConcatenateNameIncludingSimplePart().TrimEnd();
public string FullNameWithExtension => ConcatenateNameIncludingSimplePart().TrimEnd() + "." + Extension;
public string OriginalNameBeforeTreat { get; private set; }
public string OriginalNameBeforeTreatWithoutExtension { get; private set; }
public string OriginalNameAfterEnsureLegalNumberOfPartsWithoutExtension { get; private set; }
public bool NeededFakeDelimiter => WhichFakeDelimiter.Any();
public List<(string delimiter, int positionAt)> WhichFakeDelimiter { get; private set; } = new();
private InNameGUID PartHasGUID => GetGUID();
public override string ToString()
@$"FirstPartName: {FirstPartName}
MiddlePartName: {(MiddlePartName.IsSuccess ? MiddlePartName.Value : MiddlePartName.Error)}
MiddlePartNameOriginal: {(MiddlePartNameOriginal.IsSuccess ? MiddlePartNameOriginal.Value : MiddlePartNameOriginal.Error)}
LastPartName: {(LastPartName.IsSuccess ? LastPartName.Value : LastPartName.Error)}
LastPartNameOriginal: {(LastPartNameOriginal.IsSuccess ? LastPartNameOriginal.Value : LastPartNameOriginal.Error)}
FileVersion: {(HasVersionInSentence ? GetVersionInSentence().Version : "No file version")}
FileDate: {(HasDateInSentence ? GetDateInSentence().DateText : "No file date")}
FileDateInterval: {(HasIntervalInSentence ? GetIntervalInSentence().ElementValue.Value : "No file interval")}
MedicinesAndDoses: {(HasDrugAndDoseInSentence ? GetDrugAndDoseInSentence().ElementValue.Value : "No drug and dose in this file")}
PartHasGUID? {PartHasGUID != null && !PartHasGUID.NoGUID}
GUID: {(PartHasGUID != null ? PartHasGUID.GUID : "NO")}
ConnectorBeforeGUID: {(PartHasGUID != null && !PartHasGUID.NoGUID
? PartHasGUID.ConnectorBefore : "NO")}
HasInterval? {HasIntervalInSentence}
ConnectorBeforeInterval: {(!GetIntervalInSentence().ElementValue.IsSuccess
? "NO" : (GetIntervalInSentence().ConnectorBefore == " " ? "one empty space" : GetIntervalInSentence().ConnectorBefore))}
HasDate? {HasDateInSentence}
ConnectorBeforeDate: {(!GetDateInSentence().ElementValue.IsSuccess
? "NO" : (GetDateInSentence().ConnectorBefore == " " ? "one empty space" : GetDateInSentence().ConnectorBefore))}
HasVersion? {HasVersionInSentence}
VersionNumberOnly? {HasVersionInSentenceNumberOnly}
VersionWithPrefix? {HasVersionInSentenceWithPrefix}
ConnectorBeforeVersion: {(!GetVersionInSentence().ElementValue.IsSuccess
? "NO" : (GetVersionInSentence().ConnectorBefore == " " ? "one empty space" : GetVersionInSentence().ConnectorBefore))}
private bool ElementExists(WhichElementItIs whichElementItIs)
switch (whichElementItIs)
case WhichElementItIs.VersionWithPrefix when HasVersionInSentence:
case WhichElementItIs.VersionNumberOnly when HasVersionInSentence:
case WhichElementItIs.WhicheverVersion when HasVersionInSentence:
case WhichElementItIs.DrugNameDose when HasDrugAndDoseInSentence:
case WhichElementItIs.FileNameDate when HasDateInSentence:
case WhichElementItIs.FileNameInterval when HasIntervalInSentence:
case WhichElementItIs.GUID when HasGUID:
case WhichElementItIs.DoesNotExist:
private string FixWrongDelimiters(string originalNameBeforeTreat)
for (var i = 0; i < _wrongDelimiters.Count(); i++)
if (!originalNameBeforeTreat.Contains(_wrongDelimiters[i])) continue;
var wrong = _wrongDelimiters[i];
foreach (var position in originalNameBeforeTreat.AllIndexesOf(wrong))
WrongDelimiter.Add((wrong, position, i == 0 ? DocNamePart.Middle : DocNamePart.Last));
originalNameBeforeTreat = originalNameBeforeTreat.Replace(wrong, "-");
return originalNameBeforeTreat;
private Result<string> DecideHowToReturnPatientNameIfFileNameHasOnlyOnePart()
return _patientNameFromDocType != NoNameFromDocType
? Result.Success(_patientNameFromDocType)
: Result.Failure<string>("Doc has no patient associated; it has only one part");
private void GetParts(string fullNameWithExtension)
fullNameWithExtension = FixWrongDelimiters(fullNameWithExtension);
var input = fullNameWithExtension;
(OriginalNameBeforeTreatWithoutExtension, Extension) = DivideDocNameFromExtension(input);
List<(string delimiter, int positionAt)> temp = new();
(var matches, OriginalNameAfterEnsureLegalNumberOfPartsWithoutExtension) = EnsureLegalNumberOfParts(OriginalNameBeforeTreatWithoutExtension.Trim(), ref temp);
WhichFakeDelimiter = temp;
if (matches.Count == 0) return;
(from Match itemMatch in matches
where !string.IsNullOrEmpty(itemMatch.Value)
select itemMatch.Value).ToList();
if (!matchesList.Any()) return;
FirstPartName = Result.Success(NamePart.DefinePart(matchesList[0].Trim(), DocNamePart.First, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
FirstPartNameOriginal = matchesList[0].Trim();
OriginalNumberOfParts = matchesList.Count;
NumberOfParts = OriginalNumberOfParts;
switch (matchesList.Count)
MiddlePartNameOriginal = Result.Success(IfNeededReplaceFakeDelimiter(matchesList[i].Trim()));
MiddlePartName = Result.Success(NamePart.DefinePart(matchesList[i].Trim(), DocNamePart.Middle, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
LastPartNameOriginal = Result.Success(IfNeededReplaceFakeDelimiter(matchesList[i].Trim()));
LastPartName = Result.Success(NamePart.DefinePart(matchesList[i].Trim(), DocNamePart.Last, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
MiddlePartNameOriginal = Result.Failure<string>("No middle part");
MiddlePartName = Result.Failure<NamePart>("No middle part");
LastPartNameOriginal = Result.Success(IfNeededReplaceFakeDelimiter(matchesList[i].Trim()));
LastPartName = Result.Success(NamePart.DefinePart(matchesList[i].Trim(), DocNamePart.Last, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
MiddlePartNameOriginal = Result.Failure<string>("No middle part");
MiddlePartName = Result.Failure<NamePart>("No middle part");
LastPartNameOriginal = Result.Failure<string>("No last part");
LastPartName = Result.Failure<NamePart>("No last part");
var joinMiddleParts = string.Empty;
for (var j = 1; j < matchesList.Count - 1; j++)
joinMiddleParts += matchesList[j].Trim() + (j < matchesList.Count ? " " : string.Empty);
NumberOfParts = _maxNumOfParts;
MiddlePartNameOriginal = Result.Success(IfNeededReplaceFakeDelimiter(joinMiddleParts.Trim()));
MiddlePartName = Result.Success(NamePart.DefinePart(joinMiddleParts.Trim(), DocNamePart.Middle, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
LastPartNameOriginal = Result.Success(IfNeededReplaceFakeDelimiter(matchesList[matchesList.Count - 1].Trim()));
LastPartName = Result.Success(NamePart.DefinePart(matchesList[matchesList.Count - 1].Trim(), DocNamePart.Last, CultureInfoName, Delimiter, FakeDelimiter, MaxPossibleYear, MinPossibleYear));
if (MiddlePartNameOriginal.IsSuccess)
PositionOfMiddlePart = OriginalNameAfterEnsureLegalNumberOfPartsWithoutExtension.IndexOf(MiddlePartNameOriginal.Value, StringComparison.Ordinal);
if (LastPartNameOriginal.IsSuccess)
PositionOfLastPart = OriginalNameAfterEnsureLegalNumberOfPartsWithoutExtension.IndexOf(LastPartNameOriginal.Value, StringComparison.Ordinal);
CorrectWrongDelimitersAfterKnowHowManyPartsAre();
private void CorrectWrongDelimitersAfterKnowHowManyPartsAre()
if (!WrongDelimiter.Any()) return;
for (int i = 0; i < WrongDelimiter.Count; i++)
WrongDelimiter[i] = (WrongDelimiter[i].delimiter, WrongDelimiter[i].positionAt, DocNamePart.First);
if (WrongDelimiter.Any(x => x.whichPart == DocNamePart.Middle))
WrongDelimiter[0] = (WrongDelimiter[0].delimiter, WrongDelimiter[0].positionAt, DocNamePart.Last);
var numSpacesThatWhereTrimmedFromOriginalNameToLastPart = 3;
var numSpacesThatWhereTrimmedFromOriginalNameToMiddlePart = 2;
for (int i = 0; i < WrongDelimiter.Count; i++)
if (WrongDelimiter[i].positionAt >= PositionOfMiddlePart - numSpacesThatWhereTrimmedFromOriginalNameToMiddlePart && WrongDelimiter[i].positionAt < PositionOfLastPart - numSpacesThatWhereTrimmedFromOriginalNameToLastPart)
WrongDelimiter[i] = (WrongDelimiter[i].delimiter, WrongDelimiter[i].positionAt, DocNamePart.Middle);
else if (WrongDelimiter[i].positionAt >= PositionOfLastPart - numSpacesThatWhereTrimmedFromOriginalNameToLastPart)
WrongDelimiter[i] = (WrongDelimiter[i].delimiter, WrongDelimiter[i].positionAt, DocNamePart.Last);
private static (string name, string extension) DivideDocNameFromExtension(string input)
var rgx = DocNameHelper.ForGrabNameAndExtension.Value;
var match = rgx.Match(input);
if (match.Success) return (match.Groups["fullname"].Value, match.Groups["extension"].Value);
return (string.Empty, string.Empty);
private bool FileNameHasThreeParts => LastPartName.IsSuccess && MiddlePartName.IsSuccess;
private bool FileNameHasTwoParts => LastPartName.IsSuccess && MiddlePartName.IsFailure;
private bool FileNameHasOnlyOnePart => LastPartName.IsFailure && MiddlePartName.IsFailure;
internal int OriginalNumberOfParts { get; private set; }
internal int NumberOfParts { get; private set; }
private string GetMiddlePartOfFileName() =>
LastPartName.IsFailure ? FirstPartName.Value.PartText : LastPartName.Value.PartText;
private string GetLastPartOfFileName() =>
LastPartName.IsFailure ? FirstPartName.Value.PartText : LastPartName.Value.PartText;
private string IfNeededReplaceFakeDelimiter(List<string> matchesList, int i)
return NeededFakeDelimiter
? matchesList[i].Trim().Replace(FakeDelimiter, Delimiter)
private string IfNeededReplaceFakeDelimiter(string partToReturnToOriginal)
return NeededFakeDelimiter
? partToReturnToOriginal.Replace(FakeDelimiter, Delimiter).Trim()
: partToReturnToOriginal.Trim();
private static (MatchCollection matches, string treatedName) EnsureLegalNumberOfParts(string nameBeforeTreat, ref List<(string delimiter, int positionAt)> whichFakeDelimiter)
var rgxFakeDelimiter = DocNameHelper.ForGrabFakeDelimiterWithoutAnySpaceAround.Value;
var treatedName = nameBeforeTreat;
foreach (Match match in rgxFakeDelimiter.Matches(treatedName))
whichFakeDelimiter.Add((match.Value, match.Index));
while (rgxFakeDelimiter.Matches(treatedName).Count > 0)
treatedName = rgxFakeDelimiter.Replace(treatedName, (string)TemporarySubstitutionForFakeDelimiter);
var rgx = DocNameHelper.ForGrabPartsOfName.Value;
var matches = rgx.Matches(treatedName);
return (matches, treatedName);
private static void ShowMatches(Regex r, Match m)
var names = r.GetGroupNames();
Console.WriteLine("Named Groups:");
foreach (var name in names)
var grp = m.Groups[name];
if (!string.IsNullOrEmpty(grp.Value))
Console.WriteLine($@" {name}: '{grp.Value}'");
private string ConcatenateNameIncludingSimplePart()
$"{GeneratePartOfNameIncludingSimplePartConcatenated(FirstPartName, DocNamePart.First)}"
+ $"{GeneratePartOfNameIncludingSimplePartConcatenated(MiddlePartName, DocNamePart.Middle)}"
+ $"{GeneratePartOfNameIncludingSimplePartConcatenated(LastPartName, DocNamePart.Last)}";
private string GeneratePartOfNameIncludingSimplePartConcatenated(Result<NamePart> part, DocNamePart whichPart)
if (whichPart == DocNamePart.First)
: @$"{GetCorrectEnDashHyphen(part.Value.GetPartNameElements)}";
var delimiter = GrabChosenDelimiter(whichPart);
: @$" {delimiter}{(string.IsNullOrEmpty(part.Value.GetPartNameElements) ? "" : " ")}{GetCorrectEnDashHyphen(part.Value.GetPartNameElements.Trim())}";
private string GetCorrectEnDashHyphen(string partValue)
if (!RehydrateWithOriginalEnDashHyphen) return partValue;
for (var i = 0; i < WrongDelimiter.Count; i++)
if (partValue.Contains(Delimiter))
var toReplace = partValue.IndexOf(Delimiter, StringComparison.CurrentCulture);
partValue = partValue.SubstituteSubstringAtSpecificPositionForAnother(Delimiter, WrongDelimiter[i].delimiter, toReplace);
private string GrabChosenDelimiter(DocNamePart whichPart)
if (!RehydrateWithOriginalEnDashHyphen) return Delimiter;
foreach (var (wrongDelimiter, _, position) in WrongDelimiter)
if (position == whichPart) return wrongDelimiter;
#endregion Private Methods
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return OriginalNameBeforeTreat;
yield return FullNameWithExtension;
#endregion Overrides of ValueObject
public class NamePart : ValueObject
private string _partText;
public int MaxPossibleYear { get; set; }
public int MinPossibleYear { get; set; }
public string PartOriginalText { get; private set; }
private set => _partText = value;
public string GetOnlySimplePartNameElements => GetConcatenatedSimplePartNameElements(FakeDelimiter, FakeDelimiter);
public string GetOnlySimplePartNameElementsOriginal => GetConcatenatedSimplePartNameElements(FakeDelimiter, Delimiter);
public string GetPartNameElements => GetConcatenatedPartNameElements(FakeDelimiter, Delimiter);
public List<SimpleText> PartSimpleText { get; private set; } = new List<SimpleText>();
public IntervalInSentence IntervalInPart { get; private set; }
public DateInSentence DateInPart { get; private set; }
public VersionInSentence VersionInPart { get; private set; }
public InNameGUID GUIDInPart { get; private set; }
public MedicineAndDosageInSentence MedicinesAndDosesInPart { get; private set; }
public DocNamePart WhichPart { get; private set; }
public string CultureInfoName { get; private set; }
public bool HasVersion => HasVersionWithPrefix || HasVersionNumberOnly;
public bool HasVersionWithPrefix => VersionInPart.HasVersionWithPrefix;
public bool HasVersionNumberOnly => VersionInPart.HasVersionNumberOnly;
public bool HasDate => DateInPart.HasDate;
public bool HasInterval => IntervalInPart.HasInterval;
public bool HasDrugAndDose => MedicinesAndDosesInPart.HasDrugAndDose;
public bool HasGUID => GUIDInPart.HasGUID;
public string Delimiter { get; private set; }
public string FakeDelimiter { get; private set; }
public override string ToString()
@$"PartOriginalText: {PartOriginalText}
GetOnlySimplePartNameElementsOriginal: {GetOnlySimplePartNameElementsOriginal}
GetOnlySimplePartNameElements: {GetOnlySimplePartNameElements}
GetPartNameElements: {GetPartNameElements}
FileVersion: {(HasVersion ? VersionInPart.Version : "No file version")}
FileDate: {(HasDate ? DateInPart.DateText + " (date mask has YYYY: " + DateInPart.DateMaskProvidesYYYY + " and it is " + (DateInPart.DateYearHasWildCard ? "" : "not ") + "with wildcard)" : "No file date")}
FileDateInterval: {(HasInterval ? IntervalInPart.ElementValue.Value + " (date begin mask has YYYY: " + IntervalInPart.DateBegin.DateMaskProvidesYYYY + ")" : "No file interval")}
MedicinesAndDoses: {(HasDrugAndDose ? MedicinesAndDosesInPart.ElementValue.Value : "No drug and dose in this file")}
PartHasGUID? {HasGUID && !GUIDInPart.NoGUID}
GUID: {(HasGUID ? GUIDInPart.GUID : "NO")}
ConnectorBeforeGUID: {(HasGUID && !GUIDInPart.NoGUID
? GUIDInPart.ConnectorBefore : "NO")}
HasInterval? {HasInterval}
ConnectorBeforeInterval: {(!IntervalInPart.ElementValue.IsSuccess
? "NO" : (IntervalInPart.ConnectorBefore == " " ? "one empty space" : IntervalInPart.ConnectorBefore))}
ConnectorBeforeDate: {(!DateInPart.ElementValue.IsSuccess
? "NO" : (DateInPart.ConnectorBefore == " " ? "one empty space" : DateInPart.ConnectorBefore))}
VersionNumberOnly? {HasVersionNumberOnly}
VersionWithPrefix? {HasVersionWithPrefix}
ConnectorBeforeVersion: {(!VersionInPart.ElementValue.IsSuccess
? "NO" : (VersionInPart.ConnectorBefore == " " ? "one empty space" : VersionInPart.ConnectorBefore))}
#endregion Public Methods
#region Constructors and Destructor
internal static NamePart DefinePart(string part, DocNamePart whichPart, string culture, string delimiter, string fakeDelimiter, int maxPossibleYear, int minPossibleYear)
if (string.IsNullOrEmpty(part))
throw new ArgumentException("Value cannot be null or empty.", nameof(part));
var instance = new NamePart
CultureInfoName = culture,
FakeDelimiter = fakeDelimiter,
MaxPossibleYear = maxPossibleYear,
MinPossibleYear = minPossibleYear
instance.GetAllElementsFromPart();
public static NamePart None() => new();
#endregion Constructors and Destructor
private void GetAllElementsFromPart()
GUIDInPart = InNameGUID.CreateInstance(_partText, WhichPart);
_partText = StripOfElementAndUpdateBaseElement(GUIDInPart, _partText, PartOriginalText);
MedicinesAndDosesInPart = MedicineAndDosageInSentence.CreateInstance(_partText);
if (MedicinesAndDosesInPart != MedicineAndDosageInSentence.NullMedicine)
_partText = StripOfElementAndUpdateBaseElement(MedicinesAndDosesInPart, _partText, PartOriginalText);
var intervalInSentence = IntervalInSentence.CreateInstance(_partText, new CultureInfo(CultureInfoName), MaxPossibleYear, MinPossibleYear);
DateInSentence.CreateInstanceFromSentence(_partText, CultureInfoName, (GUIDInPart.StartIndex, GUIDInPart.EndOriginal), MaxPossibleYear, minPossibleYear: MinPossibleYear);
TreatIntervalComparingItWithDate(ref intervalInSentence, ref dateInSentence, PartOriginalText);
IntervalInPart = intervalInSentence;
if (intervalInSentence.ElementValue.IsSuccess)
_partText = StripOfElementAndUpdateBaseElement(intervalInSentence, _partText, PartOriginalText);
dateInSentence = DateInSentence.CreateInstanceFromSentence(_partText, CultureInfoName, (GUIDInPart.StartIndex, GUIDInPart.EndOriginal), MaxPossibleYear, minPossibleYear: MinPossibleYear);
if (dateInSentence.ElementValue.IsSuccess)
_partText = StripOfElementAndUpdateBaseElement(dateInSentence, _partText, PartOriginalText);
VersionInPart = VersionInSentence.CreateInstance(_partText);
DateInPart = dateInSentence;
if (VersionInPart.ElementValue.IsSuccess)
_partText = StripOfElementAndUpdateBaseElement(VersionInPart, _partText, PartOriginalText);
PartSimpleText = GetPartSimpleTextElements();
_partText = _partText.RemoveUnnecessarySpaces();
private List<SimpleText> GetPartSimpleTextElements()
var draftList = new List<SimpleText>();
var partWithoutElements = PartWithoutElements(PartOriginalText);
while (partWithoutElements.Length > 0)
for (var i = 0; i <= partWithoutElements.Length; i++)
var substring = partWithoutElements.Substring(0, i);
var start = PartOriginalText.IndexOf(substring.Trim(), StringComparison.Ordinal);
var end = start + substring.Length;
if (PartOriginalText.Contains(substring))
if (partWithoutElements.Equals(substring))
var connectorBefore = start == 0 ? string.Empty : PartOriginalText.Substring(start - 1, 1);
draftList.Add(SimpleText.CreateInstance(substring.TrimEnd(), start, connectorBefore));
partWithoutElements = partWithoutElements.Replace(substring, string.Empty).TrimEnd();
var connectorBefore = start <= 0 ? string.Empty : PartOriginalText.Substring(start - 1, 1);
substring = substring.TrimLastCharacter();
draftList.Add(SimpleText.CreateInstance(substring.TrimEnd(), start, connectorBefore));
partWithoutElements = partWithoutElements.Replace(substring, string.Empty).TrimEnd();
private string PartWithoutElements(string partOriginalToTest)
foreach (var element in ListOfElements())
var elementValue = element.ElementValue.Value;
var startIndex = partOriginalToTest.IndexOf(elementValue, StringComparison.Ordinal);
if (startIndex == -1) continue;
var endIndex = startIndex + elementValue.Length;
partOriginalToTest = partOriginalToTest.Substring(endIndex);
var partLength = partOriginalToTest.Length;
var part1 = partOriginalToTest.Substring(0, startIndex - 1);
var part2 = partOriginalToTest.Substring(endIndex);
partOriginalToTest = part1 + part2;
return partOriginalToTest;
private string StripOfElementAndUpdateBaseElement(BaseElement elem, string part, string partOriginal)
(part, var connector) = StripOffPartWithConnectorAndReturnIt(part, elem.ElementValue.Value);
elem.ConnectorBefore = connector;
elem.StartIndex = partOriginal.IndexOf(elem.ElementValue.Value, StringComparison.Ordinal);
internal bool ElementExists(WhichElementItIs whichElementItIs)
switch (whichElementItIs)
case WhichElementItIs.VersionWithPrefix when HasVersion:
case WhichElementItIs.VersionNumberOnly when HasVersion:
case WhichElementItIs.WhicheverVersion when HasVersion:
case WhichElementItIs.DrugNameDose when HasDrugAndDose:
case WhichElementItIs.FileNameDate when HasDate:
case WhichElementItIs.FileNameInterval when HasInterval:
case WhichElementItIs.GUID when HasGUID:
case WhichElementItIs.DoesNotExist:
private static WhichElementItIs TypeOfElement(BaseElement elem)
VersionInSentence => WhichElementItIs.WhicheverVersion,
MedicineAndDosageInSentence => WhichElementItIs.DrugNameDose,
DateInSentence => WhichElementItIs.FileNameDate,
IntervalInSentence => WhichElementItIs.FileNameInterval,
InNameGUID => WhichElementItIs.GUID,
SimpleText => WhichElementItIs.SimplePart,
_ => WhichElementItIs.DoesNotExist
internal bool ElementIsFirst(WhichElementItIs whichElementItIs)
var firstElement = ListOfElements().OrderBy(x => x.StartIndex).FirstOrDefault();
if (firstElement == null) return false;
return TypeOfElement(firstElement) == whichElementItIs;
private int ReturnNewIndexToPositioningElementInSentence(BaseElement elem, ElementRelativePosition elementRelativePosition)
var allElements = ListOfElements();
var orderedElements = allElements.OrderBy(x => x.StartIndex).ToArray();
var sizeOfPartWithoutElem = PartWithoutElementLength();
if (orderedElements.Length > 1)
index = elementRelativePosition switch
ElementRelativePosition.Middle => (orderedElements.Max(x => x.StartIndex) -
orderedElements.Min(x => x.StartIndex)),
ElementRelativePosition.First => 0,
ElementRelativePosition.Last => orderedElements.Max(x => x.StartIndex) + 1,
_ => throw new ArgumentOutOfRangeException(nameof(elementRelativePosition), elementRelativePosition,
else if (orderedElements.Length == 1)
index = elementRelativePosition switch
ElementRelativePosition.First => 0,
_ => orderedElements.FirstOrDefault().EndIndex + 1
else if (orderedElements.Length == 0)
index = elementRelativePosition switch
ElementRelativePosition.First => 0,
_ => sizeOfPartWithoutElem
internal bool CreateElement(WhichElementItIs whichElementItIs, string elemValue,
ElementRelativePosition position, string connectorBefore = " ", string connectorBeforeDisplacedExFirstElement = " ")
if (string.IsNullOrEmpty(elemValue)) return false;
if (position == ElementRelativePosition.First)
connectorBefore = string.Empty;
var firstElementThatWillBecomeSecond = ListOfElements().FirstOrDefault();
if (firstElementThatWillBecomeSecond != null)
firstElementThatWillBecomeSecond.ConnectorBefore = connectorBeforeDisplacedExFirstElement;
switch (whichElementItIs)
case WhichElementItIs.DoesNotExist:
case WhichElementItIs.WhicheverVersion:
case WhichElementItIs.VersionNumberOnly:
case WhichElementItIs.VersionWithPrefix:
CreateVersionInSentence(elemValue, position, connectorBefore);
case WhichElementItIs.FileNameDate:
case WhichElementItIs.FileNameInterval:
case WhichElementItIs.GUID:
case WhichElementItIs.DrugNameDose:
throw new ArgumentOutOfRangeException(nameof(whichElementItIs), whichElementItIs, null);
internal bool UpdateElementAndConnectorBefore(WhichElementItIs whichElementItIs, string elemValue, string connectorBefore = "")
if (string.IsNullOrEmpty(elemValue)) return false;
switch (whichElementItIs)
case WhichElementItIs.DoesNotExist:
case WhichElementItIs.WhicheverVersion:
case WhichElementItIs.VersionNumberOnly:
case WhichElementItIs.VersionWithPrefix:
UpdateVersionInSentence(elemValue, connectorBefore);
case WhichElementItIs.FileNameDate:
UpdateDateInSentence(elemValue, connectorBefore);
case WhichElementItIs.FileNameInterval:
case WhichElementItIs.GUID:
case WhichElementItIs.DrugNameDose:
throw new ArgumentOutOfRangeException(nameof(whichElementItIs), whichElementItIs, null);
internal bool DeleteElementAndAdjustConnectorOfOtherAfterIfNeeded(WhichElementItIs whichElementItIs)
if (!ElementExists(whichElementItIs)) return false;
if (ElementIsFirst(whichElementItIs))
var secondElement = ListOfElements().OrderBy(x => x.StartIndex).Skip(1).FirstOrDefault();
if (secondElement != null)
secondElement.ConnectorBefore = string.Empty;
switch (whichElementItIs)
case WhichElementItIs.DoesNotExist:
case WhichElementItIs.WhicheverVersion:
case WhichElementItIs.VersionNumberOnly:
case WhichElementItIs.VersionWithPrefix:
DeleteVersionInSentence();
case WhichElementItIs.FileNameDate:
case WhichElementItIs.FileNameInterval:
case WhichElementItIs.GUID:
case WhichElementItIs.DrugNameDose:
throw new ArgumentOutOfRangeException(nameof(whichElementItIs), whichElementItIs, null);
private int PartWithoutElementLength() => _partText.Length;
private int ElementEndIndex(BaseElement baseElement) => baseElement.EndIndex;
#region Decompose elements from Part
private static (string partStripped, string connector) StripOffPartWithConnectorAndReturnIt(string partToBeStrippedOff, string partToStrip)
var connector = partToBeStrippedOff.GetNumberOfCharsJustBeforeSubstring(partToStrip, 1);
partToBeStrippedOff = StripOffPart(partToBeStrippedOff, partToStrip, connector);
return (partToBeStrippedOff, string.IsNullOrEmpty(connector) ? " " : connector);
private static string StripOffPart(string partToBeStrippedOff, string partToRemove, string connectorBefore)
if (!string.IsNullOrEmpty(connectorBefore) && !string.IsNullOrWhiteSpace(connectorBefore))
partToRemove = connectorBefore + partToRemove;
if (!string.IsNullOrWhiteSpace(partToRemove))
partToBeStrippedOff = partToBeStrippedOff.Replace(partToRemove.Trim(), string.Empty);
return partToBeStrippedOff = partToBeStrippedOff.Trim();
#endregion Decompose elements from Part
#region Compose back Elements
private IEnumerable<BaseElement> ListOfElements()
var elemToReturn = new List<BaseElement>();
elemToReturn.Add(GUIDInPart);
if (DateInPart.ElementValue.IsSuccess)
elemToReturn.Add(DateInPart);
if (VersionInPart.ElementValue.IsSuccess)
elemToReturn.Add(VersionInPart);
if (IntervalInPart.ElementValue.IsSuccess)
elemToReturn.Add(IntervalInPart);
if (MedicinesAndDosesInPart.ElementValue.IsSuccess)
elemToReturn.Add(MedicinesAndDosesInPart);
return elemToReturn.OrderBy(x => x.StartIndex);
private IEnumerable<BaseElement> ListOfElementsIncludingSimpleParts()
var elemToReturn = ListOfElements().ToList();
elemToReturn.AddRange(PartSimpleText);
return elemToReturn.OrderBy(x => x.StartIndex);
private string GetConcatenatedPartNameElements(string fakeDelimiter, string delimiter)
var partText = new StringBuilder();
foreach (var element in ListOfElementsIncludingSimpleParts())
partText.Append(element.ConnectorBefore);
partText.Append(element.ElementValue.Value.Replace(fakeDelimiter, delimiter));
return Regex.Replace(partText.ToString(), @"\s{2,}", " ");
private string GetConcatenatedSimplePartNameElements(string fakeDelimiter, string delimiter)
var partText = new StringBuilder();
foreach (var element in ListOfElementsIncludingSimpleParts().Where(e => e is SimpleText))
partText.Append(element.ConnectorBefore);
partText.Append(element.ElementValue.Value.Replace(fakeDelimiter, delimiter));
return Regex.Replace(partText.ToString(), @"\s{2,}", " ");
#endregion Compose back Elements
internal bool CheckAtPartElementIfVersionIsFirst()
if (!HasVersion || !HasDate) return true;
var whereVersion = _partText.IndexOf(VersionInPart.Version, StringComparison.CurrentCulture);
var whereDate = _partText.IndexOf(DateInPart.DateText, StringComparison.CurrentCulture);
return whereVersion < whereDate;
internal bool UpdateVersionInSentence(string newVersionValue, string connectorBefore = "")
var newVersion = VersionInSentence.UpdateInstance(newVersionValue, VersionInPart, connectorBefore);
if (newVersion == VersionInSentence.NullVersion) return false;
VersionInPart = newVersion;
internal void CreateVersionInSentence(string newVersionValue, ElementRelativePosition elementRelativePosition, string connectorBefore = " ")
if (string.IsNullOrEmpty(newVersionValue)) return;
VersionInPart = VersionInSentence.CreateInstance(newVersionValue);
VersionInPart.StartIndex =
ReturnNewIndexToPositioningElementInSentence(VersionInPart, elementRelativePosition);
VersionInPart.ConnectorBefore = connectorBefore;
internal void DeleteVersionInSentence()
VersionInPart = VersionInSentence.NullVersion;
internal bool CheckIfHasBothVersionAndDate() => HasVersion && HasDate;
#region Interval and Date
private void TreatIntervalComparingItWithDate(ref IntervalInSentence intervalInSentence,
ref DateInSentence dateInSentence, string partOriginal)
if (dateInSentence == DateInSentence.NullDate) return;
if (intervalInSentence.ElementValue.IsFailure) return;
if (InvalidIntervalBecauseSupposedToBeComposedButHasOnlyOneDate(intervalInSentence, dateInSentence)
|| InvalidIntervalBecauseSupposedToBeTemporalClueButDateInvalid(intervalInSentence, dateInSentence))
intervalInSentence = IntervalInSentence.Null;
if (InvalidDateBecauseItIsIdenticalToIntervalThatIsAllowedToHaveOnlyOneDate(intervalInSentence, dateInSentence))
dateInSentence = DateInSentence.NullDate;
if (!InvalidDateBecauseItIsCapturedInsideBeginOfInterval(intervalInSentence, dateInSentence)) return;
if (CapturedElementsAreSuperposedInSamePlaceOfStringSoOneIsInvalid(intervalInSentence, dateInSentence, partOriginal))
dateInSentence = DateInSentence.NullDate;
private static bool CapturedElementsAreSuperposedInSamePlaceOfStringSoOneIsInvalid(BaseElement elem1, BaseElement elem2, string partOriginal)
return partOriginal.IndexOf(elem1.ElementValue.Value, StringComparison.Ordinal)
== partOriginal.IndexOf(elem2.ElementValue.Value, StringComparison.Ordinal);
private static bool InvalidDateBecauseItIsCapturedInsideBeginOfInterval(IntervalInSentence intervalInSentence, DateInSentence dateInSentence)
return intervalInSentence.IntervalType == IntervalType.NormalRange && dateInSentence.DateText == intervalInSentence.DateEnd.DateText;
private static bool InvalidDateBecauseItIsIdenticalToIntervalThatIsAllowedToHaveOnlyOneDate(IntervalInSentence intervalInSentence, DateInSentence dateInSentence)
return intervalInSentence.IntervalType == IntervalType.TemporalClue && dateInSentence.DateText == intervalInSentence.DateBegin.DateText;
private static bool InvalidIntervalBecauseSupposedToBeComposedButHasOnlyOneDate(IntervalInSentence intervalInSentence, DateInSentence dateInSentence)
return intervalInSentence.IntervalType == IntervalType.NormalRange && dateInSentence.DateText == intervalInSentence.ElementValue.Value;
private static bool InvalidIntervalBecauseSupposedToBeTemporalClueButDateInvalid(IntervalInSentence intervalInSentence, DateInSentence dateInSentence)
return intervalInSentence.IntervalType == IntervalType.TemporalClue
&& dateInSentence.DateText.Length > intervalInSentence.ElementValue.Value.Length;
internal void UpdateDateInSentence(DateInSentence value)
if (value == DateInSentence.NullDate) return;
internal bool UpdateDateInSentence(string value, string connectorBefore)
var newDate = DateInSentence.UpdateInstance(value, DateInPart, connectorBefore);
if (newDate == DateInSentence.NullDate) return false;
internal void AddDateInSentence(DateInSentence value, ElementRelativePosition position, string connectorBefore = " ")
if (value == DateInSentence.NullDate) return;
value.StartIndex = ReturnNewIndexToPositioningElementInSentence(value, position);
value.ConnectorBefore = connectorBefore;
internal void DeleteDateInSentence()
DateInPart = DateInSentence.NullDate;
#endregion Interval and Date
public bool ThisPartHasGUID() => !GUIDInPart.NoGUID;
#endregion Private Methods
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return PartOriginalText;
yield return VersionInPart;
yield return IntervalInPart;
yield return MedicinesAndDosesInPart;
#endregion Overrides of ValueObject
private void AdjustPartSimpleText(string partOriginal, BaseElement elem)
if (elem.StartIndex == 0)
SimpleText.CreateInstance(partOriginal.Substring(0, elem.StartIndex), 0, string.Empty);
else if (elem.EndIndex == partOriginal.Length)
switch (PartSimpleText.Count)
SimpleText.CreateInstance(partOriginal.Substring(elem.EndIndex), 0, string.Empty);
case 2 when elem.ElementValue.Value.Equals(PartSimpleText[1].ElementValue.Value):
PartSimpleText.RemoveAt(1);
SimpleText.CreateInstance(partOriginal.Substring(elem.EndIndex), 0, string.Empty);
IncreaseSimplePartNumberBySplittingOneOfThem(partOriginal, elem);
private void IncreaseSimplePartNumberBySplittingOneOfThem(string partOriginal, BaseElement elem)
if (PartSimpleText.Count == 1)
SimpleText.CreateInstance(partOriginal.Substring(0, elem.StartIndex - 1), 0, string.Empty);
SimpleText.CreateInstance(partOriginal.Substring(elem.EndIndex + 1), elem.EndIndex + 1, string.Empty);
newDistalPart.ConnectorBefore = partOriginal.Substring(elem.EndIndex, 1);
PartSimpleText.Add(newDistalPart);
else if (PartSimpleText.Count == 2)
var partWithSubstring = PartSimpleText.FirstOrDefault(p => p.OriginalText.Contains(elem.ElementValue.Value));
if (partWithSubstring != null)
var index = PartSimpleText.IndexOf(partWithSubstring);
SimpleText.CreateInstance(partOriginal.Substring(0, elem.StartIndex - 1), 0, string.Empty);
SimpleText.CreateInstance(partOriginal.Substring(elem.EndIndex + 1), 0, string.Empty);
newDistalPart.ConnectorBefore = partOriginal.Substring(elem.EndIndex, 1);
PartSimpleText.Add(newDistalPart);
throw new ArgumentOutOfRangeException(nameof(PartSimpleText), PartSimpleText.Count,
"PartSimpleText.Count is 2, but none of the SimpleTexts contains the substring");
throw new ArgumentOutOfRangeException(nameof(PartSimpleText), PartSimpleText.Count,
"PartSimpleText.Count is greater than 2");
public class InNameGUID : BaseElement
private InNameGUID(int startIndex, int end, DocNamePart part, string guid)
private InNameGUID(string originalText, DocNamePart part)
OriginalText = originalText;
public static InNameGUID CreateInstance(string originalText, DocNamePart part)
if (string.IsNullOrEmpty(originalText))
var instance = new InNameGUID(originalText, part);
return instance.ElementValue.IsFailure
public bool NoGUID => string.IsNullOrEmpty(GUID);
public bool HasGUID => !string.IsNullOrEmpty(GUID);
public static InNameGUID NullGUID => new(-1, -1, DocNamePart.NoPart, string.Empty);
var rgx = DocNameHelper.ForGrab_GUID_Treated.Value;
var match = rgx.Match(OriginalText);
if (!match.Success) return;
StartIndex = match.Groups["GUID"].Index;
EndOriginal = match.Groups["GUID"].Index + match.Groups["GUID"].Length - 4;
LengthOriginal = match.Groups["GUID"].Length - 4;
EndTreated = match.Groups["GUID"].Index + match.Groups["GUID"].Length;
LengthTreated = match.Groups["GUID"].Length;
GUID = match.Groups["GUID"].Value.Trim();
ElementValue = Result.Success(GUID);
public int EndOriginal { get; private set; }
public int LengthOriginal { get; private set; }
public int LengthTreated { get; private set; }
public int EndTreated { get; private set; }
public DocNamePart Part { get; private set; }
public string GUID { get; private set; }
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return EndOriginal;
public class MedicineAndDosageInSentence : BaseElement
#region Private Constants
#endregion Private Constants
public const string GrabDoseAndUnitOfMedicinesFromPartOfFile = @"(?<=-|- )?(?<complete>(?<medicine>(?:(?!RECEITUÁRIO CONTROLE ESPECIAL)\w+ ){1,4})(?<=[ ]{1,2})(?<dosewithunit>(?<dose>[\d,.]+|[\d]+_[\d]+)[ ]{0,2}(?<unit>mg|comp|cp|UI|g)))(?=[ .-]?)";
#endregion Public Constants
public bool HasDrugAndDose => ElementValue.IsSuccess;
public string Name { get; private set; }
public string Dosage { get; set; }
public string Unit { get; set; }
public string Form { get; set; }
private MedicineAndDosageInSentence(string originalText, string name, string dosage, string unit, string form)
OriginalText = originalText;
private MedicineAndDosageInSentence(string originalText)
OriginalText = originalText;
public static MedicineAndDosageInSentence CreateInstance(string originalText)
if (string.IsNullOrEmpty(originalText))
var instance = new MedicineAndDosageInSentence(originalText);
instance.TryFindMedicineDosageForm();
return instance.ElementValue.IsFailure
? NullMedicine : instance;
public static MedicineAndDosageInSentence NullMedicine =>
new(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
private void TryFindMedicineDosageForm()
var rgx = new Regex(GrabDoseAndUnitOfMedicinesFromPartOfFile);
var match = rgx.Match(OriginalText);
OriginalText = match.Groups["complete"].Value;
Name = match.Groups["medicine"].Value.Replace("CONTROLE ESPECIAL", string.Empty).Trim();
Dosage = match.Groups["dose"].Value;
Unit = match.Groups["unit"].Value;
ElementValue = Result.Success(OriginalText);
var criteria = new string[] { "Receita de Antibióticos", "Receita de Psicotrópicos" };
var source = SourceJSON.CreateInstance(true);
var medicines = source.GetWordsThatPointsToType(criteria);
foreach (var medicine in medicines)
if (!OriginalText.ToLowerInvariant().Contains(medicine.ToLowerInvariant()))
Name = medicine.CapitalizeFirstLetter();
ElementValue = Result.Success(Name);
#endregion Private Methods
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return OriginalText;
public class SimpleText : BaseElement
private SimpleText(string text, int startIndex, string connector)
ConnectorBefore = connector;
public static SimpleText CreateInstance(string text, int startIndex, string connector)
return new SimpleText(text, startIndex, connector);
public class VersionInSentence : BaseElement
private readonly string _originalSentenceToParseDate;
public const string GrabVersionFromFile = @"(?<version>\sv[ersionão]*[\d@]+[\w@]*|[\d@]+v[ersionão]*[\w@]*)";
#endregion Public Constants
public bool HasVersion => !string.IsNullOrEmpty(Version);
public bool HasVersionWithPrefix => !string.IsNullOrEmpty(VersionPrefix);
public bool HasVersionNumberOnly => HasVersion && !HasVersionWithPrefix;
public string Version { get; private set; } = string.Empty;
public string VersionPrefix => Version.ReturnOnlyNonNumericCharacters();
public int VersionNumber => Version.ReturnOnlyNumbersAsInt()!.Value;
private VersionInSentence(string partToTest, string parsedVersion = "")
_originalSentenceToParseDate = partToTest;
private VersionInSentence(string partToTest)
OriginalText = partToTest;
public static VersionInSentence CreateInstance(string partToTest)
if (string.IsNullOrEmpty(partToTest))
var instance = new VersionInSentence(partToTest);
return instance.ElementValue.IsFailure ? NullVersion : instance;
internal static VersionInSentence UpdateInstance(string partToTest, VersionInSentence oldVersionInSentence, string connectorBefore = "")
var oldIndex = oldVersionInSentence.StartIndex;
var connector = connectorBefore == "" ? oldVersionInSentence.ConnectorBefore : connectorBefore;
var instance = CreateInstance(partToTest);
if (instance.ElementValue.IsFailure)
return oldVersionInSentence;
CopyContextProperly(instance, oldIndex, connector);
public static VersionInSentence TryUpdateInstanceFailingIfNull(string partToTest, VersionInSentence oldVersionInSentence)
var instance = CreateInstance(partToTest);
return instance.ElementValue.IsFailure ? oldVersionInSentence : UpdateInstance(partToTest, oldVersionInSentence);
public static VersionInSentence NullVersion => new(string.Empty, string.Empty);
private void GetVersion()
if (string.IsNullOrEmpty(OriginalText) || DocNameHelper.DifficultyToTestPart(OriginalText))
version= Result.Failure<string>("No file version");
version = GetVersionFromPart(OriginalText);
if (!version.IsFailure && version.Value.IsNumber())
version = TestVersionNumberOnly(version.Value);
if (version.IsFailure && OriginalText.Length < 3 && OriginalText.IsNumber())
version = TestVersionNumberOnly(OriginalText);
if (version.IsFailure && OriginalText.ReturnOnlyNumbers().Length > 0 && OriginalText.ReturnOnlyNumbers().Length < 3 && OriginalText.ReturnOnlyNumbers().IsNumber())
version = TestVersionNumberOnly(OriginalText.ReturnOnlyNumbers());
if (ElementValue.IsSuccess)
Version = ElementValue.Value;
private static Result<string> TestVersionNumberOnly(string candidateForVersion)
var candidate = int.Parse(candidateForVersion);
if (candidate < 1 || candidate > 20)
return Result.Failure<string>("No file version");
return Result.Success(candidate.ToString());
private Result<string> GetVersionFromPart(string partToTest)
var version = DocNameHelper.ReturnResult(DocNameHelper.ForGrabVersionFromFile.Value, partToTest, "version", "No file version");
if (version.IsFailure && !char.IsWhiteSpace(partToTest, 0))
version = DocNameHelper.ReturnResult(DocNameHelper.ForGrabVersionFromFile.Value, $" {partToTest}", "version", "No file version");
public static Result<string> TryRecoverFileVersionOnlyNumber(Result<string> fileDateOrInterval, Result<string> partNameToTest)
if (fileDateOrInterval.IsSuccess)
rest = partNameToTest.Value.Replace(fileDateOrInterval.Value, string.Empty).Trim();
int.TryParse(rest, out tryVersion);
if (tryVersion is > 0 and < 20)
return Result.Success(tryVersion.ToString());
var restNum = rest.ReturnOnlyNumbersToArray();
if (restNum.Length != 1 || !int.TryParse(restNum[0].ToString(), out tryVersion)) return Result.Failure<string>("No file version");
return tryVersion is > 0 and < 20 ? Result.Success(tryVersion.ToString()) : Result.Failure<string>("No file version");
#endregion Private Methods
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return _originalSentenceToParseDate;
public class DateInSentence : BaseElement
private readonly string _cultureInfo;
private readonly int? _maxPossibleYear;
private readonly bool _forceDdmMorMmdd;
private readonly int _minPossibleYear;
private readonly bool _acceptWildCard;
private bool _useTestDateInsteadOfResult;
#endregion Private fields
#region Private Constants
private const string WildCardForDate = "_";
#endregion Private Constants
public bool PortugueseMonthErroneouslyAbbreviatedWithoutDot { get; set; }
public string DateText { get; private set; } = string.Empty;
public DateTime DateAndTime { get; private set; } = DateTime.MinValue;
public int IntervalTimeFrame => int.Parse(DateText);
public bool DateMaskProvidesYYYY => this.Mask.GetDescription().Contains("yyyy");
public bool DateYearHasWildCard => this.DateAndTime == DateTime.MaxValue;
public DateFormat Mask { get; private set; } = DateFormat.NoDate;
public CertaintyAboutElement CertaintyAbout { get; private set; } = CertaintyAboutElement.NonExisting;
public bool IsDDMM => _cultureInfo == "pt-BR";
public bool HasDate => DateAndTime != DateTime.MinValue;
private DateInSentence(string originalSentenceToParseDate, string cultureInfo, int? maxPossibleYear,
bool forceDDMMorMMDD, int minPossibleYear, bool acceptWildCard)
OriginalText = originalSentenceToParseDate;
_cultureInfo = cultureInfo;
_maxPossibleYear = maxPossibleYear ??= GetMaxPossibleYear();
_forceDdmMorMmdd = forceDDMMorMMDD;
_minPossibleYear = minPossibleYear;
_acceptWildCard = acceptWildCard;
public static DateInSentence NullDate =>
new(string.Empty, string.Empty, int.MaxValue, false, int.MinValue, false)
DateAndTime = DateTime.MinValue,
Mask = DateFormat.NoDate,
CertaintyAbout = CertaintyAboutElement.NonExisting
public static DateInSentence ChangeToYYYY(DateInSentence date, DateFormat newMask, string cultureInfo)
if (date.DateText.Length != 4) throw new ArgumentException("Can only change mask of DDMM or MMDD to YYYY");
return ChangeMask(date, DateFormat.yyyy, date._cultureInfo);
public static DateInSentence ChangeToMMDD(DateInSentence date, DateFormat newMask, string cultureInfo)
if (date.DateText.Length != 4) throw new ArgumentException("Can only change mask of DDMM or YYYY to MMDD");
return ChangeMask(date, DateFormat.MMdd, date._cultureInfo);
public static DateInSentence ChangeToDDMM(DateInSentence date, DateFormat newMask, string cultureInfo)
if (date.DateText.Length != 4) throw new ArgumentException("Can only change mask of MMDD or YYYY to DDMM");
return ChangeMask(date, DateFormat.ddMM, date._cultureInfo);
private static DateInSentence ChangeMask(DateInSentence date, DateFormat newMask, string cultureInfo)
return CreateInstanceProvidingAllCorrectElements(date.DateText, date.DateAndTime, newMask, cultureInfo
, date.CertaintyAbout, date._maxPossibleYear.HasValue ? date._maxPossibleYear.Value : GetMaxPossibleYear()
, date._forceDdmMorMmdd, date._minPossibleYear, date._acceptWildCard);
public static DateInSentence CreateInstanceProvidingAllCorrectElements(string dateText, DateTime dateTime,
DateFormat mask, string cultureInfo, CertaintyAboutElement certaintyAbout = CertaintyAboutElement.AbsolutelyCertain
, int maxPossibleYear = int.MaxValue, bool forceDDMMorMMDD = false, int minPossibleYear = 1990, bool acceptWildCard = false)
new(dateText, cultureInfo, maxPossibleYear, forceDDMMorMMDD, minPossibleYear, acceptWildCard)
CertaintyAbout = certaintyAbout
public static DateInSentence CreateInstanceFromSentence(string dateToParse, string cultureInfo
, (int start, int end) forbiddenZoneCoordinates, int? maxPossibleYear
, bool forceDDMMorMMDD = false, int minPossibleYear = 1990, bool acceptWildCard = true)
var dateBeforeParsing = new DateInSentence(dateToParse, cultureInfo, maxPossibleYear, forceDDMMorMMDD, minPossibleYear, acceptWildCard);
var hasDate = dateBeforeParsing.GetDate(dateToParse, forbiddenZoneCoordinates);
return DateInSentence.NullDate;
var instance = CreateInstance(hasDate.Value, cultureInfo, maxPossibleYear, forceDDMMorMMDD, minPossibleYear, acceptWildCard);
public static DateInSentence CreateInstance(string dateToParse, string cultureInfo, int? maxPossibleYear,
bool forceDDMMorMMDD = false, int minPossibleYear = 1990, bool acceptWildCard = true)
var dateParsing = new DateInSentence(dateToParse, cultureInfo, maxPossibleYear, forceDDMMorMMDD, minPossibleYear, acceptWildCard);
var raw = dateParsing.OriginalText;
TryParseNumericOnlyDates(cultureInfo, maxPossibleYear, forceDDMMorMMDD, minPossibleYear, raw, dateParsing);
TryParseDatesWithNonDigits(cultureInfo, raw, dateParsing);
if (string.IsNullOrEmpty(dateParsing.DateText))
Console.WriteLine($"InferMaskAndFormatForDateParsing: dateParsing {raw} is not digit, ie, MMM or MMMM or ddd or dd or tt");
if (dateParsing.DateAndTime == DateTime.MinValue) return NullDate;
dateParsing.ElementValue = Result.Success(dateParsing.DateText);
private static void TryParseDatesWithNonDigits(string cultureInfo, string raw, DateInSentence dateParsing)
if (raw.Contains(WildCardForDate))
TryParseDatesWithWildCard(cultureInfo, raw, dateParsing);
TryParseDatesWithNonDigitsOtherThanWildCard(cultureInfo, raw, dateParsing);
private static DateInSentence TryParseDatesWithWildCard(string cultureInfo, string raw, DateInSentence dateParsing)
dateParsing.Mask = DateFormat.yyyy;
dateParsing.DateAndTime = DateTime.MaxValue;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.DDMMIncorrectFormattedWithWildcardBR : DateFormat.DDMMIncorrectFormattedWithWildcardUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.FullDateIncorrectFormattedWithWildcardBR : DateFormat.FullDateIncorrectFormattedWithWildcardUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
case >= 7 and <= 14 when HasFullMonth(raw, cultureInfo):
dateParsing.Mask = DateFormat.MMMMyyyyIncorrectFormattedWithWildcard;
var couldParseDate = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(),
new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateText = couldParseDate ? parsedDate.ToString(dateParsing.Mask.GetDescription()) : raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
case 22 when cultureInfo == "pt-BR":
dateParsing.Mask = DateFormat.personalizedForAnotaçãoOutlookBackupWithDot;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
if (parsedDate == DateTime.MinValue)
_ = DateTime.TryParseExact(raw, DateFormat.personalizedForAnotaçãoOutlookBackup.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateText = raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
dateParsing.Mask = DateFormat.personalizedForAnotaçãoOutlookBackup;
var (_, nextCharIsDot) = DateInSentence.GetAbbreviatedMonthPortugueseAndTellIfNextCharIsDot(raw);
var rawTreated = string.Empty;
rawTreated = cultureInfo == "en-US" ? raw : DateInSentence.AdjustStringForMMMAddingDot(raw, cultureInfo);
var couldParseDate = DateTime.TryParseExact(rawTreated, IsCulturePortuguese(cultureInfo) ? DateFormat.personalizedForAnotaçãoOutlookBackupWithDot.GetDescription() : dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
if (parsedDate == DateTime.MinValue)
_ = DateTime.TryParseExact(rawTreated, DateFormat.personalizedForAnotaçãoOutlookBackup.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateText = raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
private static DateInSentence TryParseDatesWithNonDigitsOtherThanWildCard(string cultureInfo, string raw, DateInSentence dateParsing)
if (raw.ContainsOnlyDigitsAndThisSubstring("."))
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.FullDateIncorrectFormattedWithDotBR : DateFormat.FullDateIncorrectFormattedWithDotUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.FullDateIncorrectTwoDigitsYearAndFormattedWithDotBR : DateFormat.FullDateIncorrectTwoDigitsYearAndFormattedWithDotUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.InformedGuess;
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.DDMMIncorrectFormattedWithDotBR : DateFormat.DDMMIncorrectFormattedWithDotUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
dateParsing.Mask = IsCulturePortuguese(cultureInfo) ? DateFormat.DDMIncorrectFormattedWithDotBR : DateFormat.DDMIncorrectFormattedWithDotUS;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.CertaintyAbout = CertaintyAboutElement.NotSure;
case 19 when raw.Contains("/") && raw.Contains(":") && raw.Contains(" "):
dateParsing.Mask = DateFormat.personalizedForAnotaçãoOutlookBackup;
var couldParseDate = DateTime.TryParseExact(raw, cultureInfo == "en-US" ? DateFormat.completeUpToSecondsUS.GetDescription() : DateFormat.completeUpToSeconds.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateText = couldParseDate ? parsedDate.ToString(dateParsing.Mask.GetDescription()) : raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
case >= 7 and <= 14 when HasFullMonth(raw, cultureInfo):
dateParsing.Mask = DateFormat.MMMMyyyyWithSpace;
else if (raw.Contains("."))
dateParsing.Mask = DateFormat.MMMMyyyyIncorrectFormattedWithDot;
dateParsing.Mask = DateFormat.MMMMyyyy;
couldParseDate = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
dateParsing.DateText = couldParseDate ? parsedDate.ToString(dateParsing.Mask.GetDescription()) : raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
case 7 or 8 when HasAbbreviatedMonth(raw, cultureInfo):
dateParsing.Mask = DateFormat.MMMyyyy;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo),
DateTimeStyles.None, out parsedDate);
if (parsedDate == DateTime.MinValue)
parsedDate = AdjustStringForMonthAndDot(cultureInfo, raw, dateParsing);
dateParsing.DateText = raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
case 5 or 6 when HasAbbreviatedMonth(raw, cultureInfo):
dateParsing.Mask = DateFormat.MMMyyIncorrectTwoDigitsYear;
_ = DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo),
DateTimeStyles.None, out parsedDate);
if (parsedDate == DateTime.MinValue)
parsedDate = AdjustStringForMonthAndDot(cultureInfo, raw, dateParsing);
dateParsing.DateText = raw;
dateParsing.DateAndTime = parsedDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
private static DateTime AdjustStringForMonthAndDot(string cultureInfo, string raw, DateInSentence dateParsing)
_ = DateTime.TryParseExact(DateInSentence.AdjustStringForMMMRemovingDot(raw, cultureInfo)
, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
_ = DateTime.TryParseExact(DateInSentence.AdjustStringForMMMAddingDot(raw, cultureInfo)
, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out parsedDate);
private static void TryParseNumericOnlyDates(string cultureInfo, int? maxPossibleYear, bool forceDDMMorMMDD,
int minPossibleYear, string raw, DateInSentence dateParsing)
dateParsing.CertaintyAbout = CertaintyAboutElement.AbsolutelyCertain;
var parsedDate = DateTime.MinValue;
const int sortableLength = 14;
dateParsing.Mask = DateFormat.sortable;
dateParsing.Mask = dateParsing.IsDDMM ? DateFormat.ddMMyyyy : DateFormat.MMddyyyy;
if (DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None,
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.Mask = DateFormat.yyyyMMdd;
if (DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None,
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
dateParsing.Mask = DateFormat.NoDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.NonExisting;
dateParsing.Mask = DateFormat.MMyyyy;
var yyyyToTestIfWithinAllowedRange = DateTime.MinValue;
var maybeDDMMorMMDD = IsItPossibleToBeDDMMOrMMDD(raw, cultureInfo);
var maybeYYYY = DateTime.TryParseExact(raw, DateFormat.yyyy.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None,
out yyyyToTestIfWithinAllowedRange);
if ((maybeDDMMorMMDD && !maybeYYYY) || forceDDMMorMMDD)
dateParsing.Mask = dateParsing.IsDDMM ? DateFormat.ddMM : DateFormat.MMdd;
else if (!maybeDDMMorMMDD && maybeYYYY)
dateParsing.Mask = DateFormat.yyyy;
else if (maybeDDMMorMMDD && maybeYYYY)
dateParsing.CertaintyAbout = CertaintyAboutElement.InformedGuess;
if (yyyyToTestIfWithinAllowedRange >= new DateTime(minPossibleYear, 1, 1) &&
yyyyToTestIfWithinAllowedRange <= new DateTime(maxPossibleYear.Value, 1, 1))
dateParsing.Mask = DateFormat.yyyy;
dateParsing.Mask = dateParsing.IsDDMM ? DateFormat.ddMM : DateFormat.MMdd;
else if (maybeDDMMorMMDD && maybeYYYY)
dateParsing.Mask = DateFormat.NoDate;
dateParsing.CertaintyAbout = CertaintyAboutElement.NonExisting;
throw new NotImplementedException($"InferMaskAndFormatForDateParsing: {raw} dateParsing.Length == 2");
if (DateTime.TryParseExact(raw, dateParsing.Mask.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None,
dateParsing.DateAndTime = parsedDate;
dateParsing.DateText = raw;
public static Result<DateInSentence> Update(string date, DateInSentence valueDateInSentence)
var oldIndex = valueDateInSentence.StartIndex;
var connector = valueDateInSentence.ConnectorBefore;
var tryDate = CreateInstance(date, valueDateInSentence._cultureInfo, valueDateInSentence._maxPossibleYear
, valueDateInSentence._forceDdmMorMmdd, valueDateInSentence._minPossibleYear, valueDateInSentence._acceptWildCard);
return Result.Failure<DateInSentence>("Invalid date");
CopyContextProperly(tryDate, oldIndex, connector);
return Result.Success(tryDate);
public static DateInSentence UpdateInstance(string date, DateInSentence valueDateInSentence, string connectorBefore = "")
var oldIndex = valueDateInSentence.StartIndex;
var connector = connectorBefore == "" ? valueDateInSentence.ConnectorBefore : connectorBefore;
var tryDate = CreateInstance(date, valueDateInSentence._cultureInfo, valueDateInSentence._maxPossibleYear
, valueDateInSentence._forceDdmMorMmdd, valueDateInSentence._minPossibleYear, valueDateInSentence._acceptWildCard);
return valueDateInSentence;
CopyContextProperly(tryDate, oldIndex, connector);
private Result<string> GetDate(string partToTest, (int start, int end) forbiddenZoneCoordinates)
_useTestDateInsteadOfResult = false;
PortugueseMonthErroneouslyAbbreviatedWithoutDot = false;
if (string.IsNullOrEmpty(partToTest) || partToTest.DoesNotContainAnyNumbers() || DocNameHelper.DifficultyToTestPart(partToTest))
return Result.Failure<string>("No date stamp in this file name");
var hasDate = ReturnDateResult(DocNameHelper.ForGrabDatesUTC.Value, partToTest, "dateUTC",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatePersonalizedForAnotaçãoOutlookBackup.Value, partToTest, "date",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
Mask = DateFormat.personalizedForAnotaçãoOutlookBackup;
PortugueseMonthErroneouslyAbbreviatedWithoutDot = true;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatePersonalizedForAnotaçãoOutlookBackupWithDotAfterMMM.Value, partToTest,
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates,
_useTestDateInsteadOfResult);
Mask = DateFormat.personalizedForAnotaçãoOutlookBackupWithDot;
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabAbbreviatedMonthWithTwoOrFourDigitYearWithDotSeparatingThemOrNot.Value, partToTest, "date",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
var length = hasDate.Value.Length;
var numOfDots = hasDate.Value.CountOccurrences(".");
PortugueseMonthErroneouslyAbbreviatedWithoutDot = false;
case 10 when numOfDots == 2:
Mask = IsCulturePortuguese(_cultureInfo)
? DateFormat.FullDateIncorrectFormattedWithDotBR
: DateFormat.FullDateIncorrectFormattedWithDotUS;
case 8 when numOfDots == 2:
Mask = DateFormat.FullDateIncorrectTwoDigitsYearAndFormattedWithDotBR;
case 8 when numOfDots == 1:
Mask = DateFormat.MMMdotyyyy;
case 7 when numOfDots == 0:
Mask = DateFormat.MMMyyyy;
PortugueseMonthErroneouslyAbbreviatedWithoutDot = IsCulturePortuguese(_cultureInfo) ? true : false;
Mask = DateFormat.MMMdotIncorrectTwoDigitsYear;
Mask = DateFormat.MMMyyIncorrectTwoDigitsYear;
PortugueseMonthErroneouslyAbbreviatedWithoutDot = IsCulturePortuguese(_cultureInfo) ? true : false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatePersonalizedForAnotaçãoOutlookBackupWithDotAfterMMM.Value, partToTest,
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates,
_useTestDateInsteadOfResult);
Mask = DateFormat.personalizedForAnotaçãoOutlookBackupWithDot;
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatesIncorrectFormattedWithInvalidChars.Value, partToTest, "date",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
var length = hasDate.Value.Length;
var numOfDots = hasDate.Value.CountOccurrences(".");
PortugueseMonthErroneouslyAbbreviatedWithoutDot = false;
DateFormat.FullDateIncorrectTwoDigitsYearAndFormattedWithDotBR,
10 when numOfDots == 2 =>
IsCulturePortuguese(_cultureInfo)
? DateFormat.FullDateIncorrectFormattedWithDotBR
: DateFormat.FullDateIncorrectFormattedWithDotUS,
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatesOrPureNumbersOrDatesWithCardFromPartOfFile.Value, partToTest, "date",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabFullMonthWithTwoOrFourDigitYearWithSomeCharSeparatingThemOrNot.Value, partToTest, "date",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
_useTestDateInsteadOfResult = false;
hasDate = ReturnDateResult(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "intervalordate",
"No date stamp in this file name", _cultureInfo, forbiddenZoneCoordinates, _useTestDateInsteadOfResult);
if (hasDate.IsSuccess && !_useTestDateInsteadOfResult)
hasDate = FilterAbsurdDates(hasDate, _cultureInfo, Mask, _acceptWildCard, "No date stamp in this file name");
internal static int GetCurrentYearPlus(int toAddCurrent) => DateTime.Now.Year + toAddCurrent;
internal static int GetMaxPossibleYear(int yearsAhead = 2) => DateTime.Now.Year + yearsAhead;
internal static bool IsItPossibleToBeDDMMOrMMDD(string dateToTest, string cultureInfo)
if (dateToTest.Length != 4) return false;
return DateTime.TryParseExact(dateToTest, DateFormat.ddMM.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out _) ||
DateTime.TryParseExact(dateToTest, DateFormat.MMdd.GetDescription(), new CultureInfo(cultureInfo), DateTimeStyles.None, out _);
internal static Result<string> FilterAbsurdDates(
Result<string> hasDate, string cultureInfo, DateFormat mask, bool acceptWildCard = true
, string errorMsg = "No date found", int numberOfYearsToConsiderAbsurd = 99)
DateTime testDate = DateTime.MinValue;
string dateWithoutSpace = string.Empty;
if (hasDate.Value.DoesNotContainAnySpace())
dateWithoutSpace = hasDate.Value;
var dateCandidates = hasDate.Value.Split(' ');
foreach (var element in dateCandidates)
if (element.Length > 3 && element.Length < 8)
dateWithoutSpace = element;
if (string.IsNullOrEmpty(dateWithoutSpace)) return Result.Failure<string>(errorMsg);
if (DocNameHelper.DateTryParseExact(dateWithoutSpace, out testDate, new CultureInfo(cultureInfo)))
return TestIfDateIsInsideBoundaries(errorMsg, numberOfYearsToConsiderAbsurd, testDate, dateWithoutSpace);
else if (acceptWildCard && dateWithoutSpace.Contains(WildCardForDate))
return Result.Success(dateWithoutSpace);
else if (mask == DateFormat.MMMyyyy && !dateWithoutSpace.Contains("."))
mask = DateFormat.MMMdotyyyy;
var dateWithDot = AdjustStringForMMMAddingDot(dateWithoutSpace, cultureInfo);
if (DocNameHelper.DateTryParseExact(AdjustStringForMMMAddingDot(dateWithDot, cultureInfo), out testDate, new CultureInfo(cultureInfo)))
return TestIfDateIsInsideBoundaries(errorMsg, numberOfYearsToConsiderAbsurd, testDate, dateWithoutSpace);
return Result.Failure<string>($"Invalid date resulting in {testDate.ToString()}");
private static Result<string> TestIfDateIsInsideBoundaries(string errorMsg, int numberOfYearsToConsiderAbsurd,
DateTime testDate, string dateWithoutSpace)
if (testDate.IsThisDateNullOrDefault())
return Result.Failure<string>(errorMsg);
if (Math.Abs(testDate.YearsBetweenNowAndDate()) >= numberOfYearsToConsiderAbsurd)
return Result.Failure<string>(
$"Cannot allow set date so many years ({testDate.YearsBetweenNowAndDate()}) far from today");
return Result.Success(dateWithoutSpace);
private static bool InTimeRangeAllowed(int numberOfYearsToConsiderAbsurd, DateTime testDate)
var tested = Math.Abs(testDate.YearsBetweenNowAndDate());
return tested < numberOfYearsToConsiderAbsurd;
private Result<string> ReturnDateResult(Regex rgx, string part, string groupName, string errorMsg
, string cultureInfo, (int start, int end) forbiddenZoneCoordinates, bool useTestDateInsteadOfResult)
var match = DocNameHelper.GetMatch(rgx, part, groupName, errorMsg, out result);
if (!result.IsFailure && result.Value.Length <= 1) return Result.Failure<string>(errorMsg);
if (!result.IsFailure && DocNameHelper.OutOfForbiddenZoneIfSamePart(groupName, match, forbiddenZoneCoordinates.start, forbiddenZoneCoordinates.end)) return result;
if ((useTestDateInsteadOfResult && result.IsSuccess) || result.IsSuccess && DocNameHelper.DateTryParseExact(result.Value, out _, new CultureInfo(cultureInfo)))
if (result.Value.Length > 1) part = result.Value;
var dateParsed = (DocNameHelper.DateTryParseExact(part, out var testDate, new CultureInfo(cultureInfo)));
if (dateParsed && !testDate.IsThisDateNullOrDefault())
result = Result.Success(!useTestDateInsteadOfResult ? part : testDate.ToString(CultureInfo.CurrentCulture));
internal static string AdjustStringForMMMAddingDot(string resultValue, string cultureInfo)
if (!IsCulturePortuguese(cultureInfo) || HasMonthFullPortuguese(resultValue))
var (monthToAddDot, nextCharIsDot) = GetAbbreviatedMonthPortugueseAndTellIfNextCharIsDot(resultValue);
if (!string.IsNullOrEmpty(monthToAddDot) && !nextCharIsDot)
resultValue = resultValue.AddSubstringAfterThisFoundExcerpt(monthToAddDot, '.'.ToString());
internal static string AdjustStringForMMMRemovingDot(string resultValue, string cultureInfo)
if (!IsCulturePortuguese(cultureInfo) || HasMonthFullPortuguese(resultValue))
var (monthToAddDot, nextCharIsDot) = GetAbbreviatedMonthPortugueseAndTellIfNextCharIsDot(resultValue);
if (!string.IsNullOrEmpty(monthToAddDot) && nextCharIsDot)
resultValue = resultValue.RemoveSubstringAfterThisFoundExcerpt(monthToAddDot, '.'.ToString());
internal static (string monthToAddDot, bool nextCharIsDot) GetAbbreviatedMonthPortugueseAndTellIfNextCharIsDot(
var monthToAddDot = ReturnFirstMatchOf(DocNameHelper.MonthsPortugueseAbbreviated, resultValue);
var nextCharIsDot = resultValue.NextCharAfterThisExcerpt(monthToAddDot, dot);
return (monthToAddDot, nextCharIsDot);
private static bool HasMonthFullPortuguese(string resultValue)
return !string.IsNullOrEmpty(ReturnFirstMatchOf(DocNameHelper.MonthsPortugueseFull, resultValue));
private static bool IsCulturePortuguese(string cultureInfo) => string.Equals(cultureInfo, "pt-BR", StringComparison.Ordinal);
private static string ReturnFirstMatchOf(List<string> sequence, string textToLookupItemToMatch)
var match = string.Empty;
foreach (var item in sequence.Where(item => textToLookupItemToMatch.ToLower().Contains(item)))
internal static bool HasAbbreviatedMonth(string dateToTest, string cultureInfo)
if (HasFullMonth(dateToTest, cultureInfo))
var months = cultureInfo == "pt-BR" ? DocNameHelper.MonthsPortugueseAbbreviated : DocNameHelper.MonthsEnglishAbbreviated;
return months.Any(month => dateToTest.ToLower().Contains(month));
internal static bool HasFullMonth(string dateToTest, string cultureInfo)
var months = cultureInfo == "pt-BR" ? DocNameHelper.MonthsPortugueseFull : DocNameHelper.MonthsEnglishFull;
return months.Any(month => dateToTest.ToLower().Contains(month));
#endregion Private Methods
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return DateAndTime;
yield return CertaintyAbout;
yield return _cultureInfo;
#region Overrides of Object
public override string ToString()
return $"{nameof(DateText)}: {DateText}, {nameof(DateAndTime)}: {DateAndTime.ToString(new CultureInfo(_cultureInfo))}, {nameof(Mask)}: {Mask.GetDescription()}, {nameof(CertaintyAbout)}: {CertaintyAbout}";
RangeOnlyWithNoTemporalClue
public class IntervalInSentence : BaseElement
private readonly string _cultureInfo;
private readonly bool _sameFrameIntervalIsAllowed;
private readonly bool _forceBeginDatePreviousYearIfReasonable;
private readonly string _errorMsg;
private Result<string> _candidateBegin;
private Result<string> _candidateEnd;
#endregion Private Fields
#endregion Public Constants
public bool HasInterval => ElementValue.IsSuccess;
public IntervalType IntervalType { get; private set; }
public string TextTemporalRelation { get; private set; }
public string TextBetween { get; private set; }
public string TimeFrame { get; private set; }
public DateInSentence DateBegin { get; private set; }
public DateInSentence DateEnd { get; private set; }
public CertaintyAboutElement CertaintyAboutDateBegin => DateBegin?.CertaintyAbout ?? CertaintyAboutElement.NonExisting;
public CertaintyAboutElement CertaintyAboutDateEnd => DateEnd?.CertaintyAbout ?? CertaintyAboutElement.NonExisting;
public bool DateBeginHadToGoBackOneYear { get; private set; }
public int MaxPossibleYear { get; set; }
public int MinPossibleYear { get; set; }
public static IntervalInSentence Null
var nullInterval = new IntervalInSentence(string.Empty, CultureInfo.CurrentCulture, false, false, "No interval date stamp in this file name", int.MinValue, int.MaxValue);
nullInterval.ElementValue = Result.Failure<string>(nullInterval._errorMsg);
private IntervalInSentence()
private IntervalInSentence(string originalText, CultureInfo cultureInfo, bool sameFrameIntervalIsAllowed, bool forceBeginDatePreviousYearIfReasonable, string errorMsg, int maxPossibleYear, int minPossibleYear)
MaxPossibleYear = maxPossibleYear;
MinPossibleYear = minPossibleYear;
_cultureInfo = cultureInfo.Name;
_sameFrameIntervalIsAllowed = sameFrameIntervalIsAllowed;
_forceBeginDatePreviousYearIfReasonable = forceBeginDatePreviousYearIfReasonable;
OriginalText = originalText;
internal static IntervalInSentence CreateInstance(string interval, CultureInfo cultureInfo, int maxPossibleYear = int.MaxValue, int minPossibleYear = 1990, bool forceBeginDatePreviousYearIfReasonable = false, bool sameFrameIntervalIsAllowed = false, string errorMsg = "No interval date stamp in this file name")
var intervalInSentence = new IntervalInSentence(interval, cultureInfo, sameFrameIntervalIsAllowed, forceBeginDatePreviousYearIfReasonable, errorMsg, maxPossibleYear, minPossibleYear);
return !intervalInSentence.GetIntervalDates(interval) ? IntervalInSentence.Null : intervalInSentence;
private bool IsDDMM => _cultureInfo == "pt-BR";
private bool GetIntervalDates(string partToTest)
if (string.IsNullOrEmpty(partToTest) || DocNameHelper.DifficultyToTestPart(partToTest)) return false;
var hasInterval = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDateIntervalFromPartOfFile.Value, partToTest, "interval",
if (hasInterval.IsSuccess)
ElementValue = hasInterval;
_candidateBegin = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDateIntervalFromPartOfFile.Value, partToTest, "datebegin", _errorMsg);
_candidateEnd = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDateIntervalFromPartOfFile.Value, partToTest, "dateend", _errorMsg);
if (_candidateBegin.IsFailure || _candidateEnd.IsFailure) return false;
var intervalDates = TryCompareIntervalDates(_candidateBegin.Value, _candidateEnd.Value);
if (intervalDates.dateBegin.IsFailure && intervalDates.dateEnd.IsFailure) return false;
DateBegin = intervalDates.dateBegin.Value;
DateEnd = intervalDates.dateEnd.Value;
TextTemporalRelation = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDateIntervalFromPartOfFile.Value, partToTest, "starting", _errorMsg).Value;
TextBetween = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDateIntervalFromPartOfFile.Value, partToTest, "link", _errorMsg).Value;
IntervalType = IntervalType.NormalRange;
TimeFrame = string.Empty;
if (hasInterval.IsFailure)
var match = DocNameHelper.GetMatch(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "relationandinterval", _errorMsg, out var result);
hasInterval = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "intervalordate", _errorMsg);
if (result.Equals(hasInterval))
ElementValue = Result.Failure<string>(_errorMsg);
IntervalType = IntervalType.TemporalClue;
_candidateBegin = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "intervalordate", _errorMsg);
_candidateEnd = _candidateBegin;
if (_candidateBegin.IsFailure) return false;
TextTemporalRelation = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "temporalrelation", _errorMsg).Value;
TextBetween = string.Empty;
TimeFrame = DocNameHelper.ReturnResult(DocNameHelper.ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile.Value, partToTest, "timeframe", _errorMsg).Value;
DateBegin = DateInSentence.CreateInstanceProvidingAllCorrectElements(_candidateBegin.Value, DateTime.MinValue, DetermineTimeFrameType(result.Value), _cultureInfo);
return _candidateBegin.IsSuccess && _candidateEnd.IsSuccess;
private (Result<DateInSentence> dateBegin, Result<DateInSentence> dateEnd) TryCompareIntervalDates(string dateBegin, string dateEnd)
var errorMsg = "because BeginDate is After EndDate";
if (dateBegin.IsDigit() && dateEnd.IsDigit())
if (dateBegin.Length == DateFormat.sortable.GetDescription().Length)
var beginDDMMDateIsInPreviousYear = CompareDatesWithSameFormat(dateBegin, dateEnd, out var format, out var validDates);
if (beginDDMMDateIsInPreviousYear)
return (Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin, validDates.dateStarted, format, _cultureInfo))
, Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd, validDates.dateEnded, format, _cultureInfo)));
var dateEndIsyyyyMMdd = false;
var dateEndIsDDMM = DateTime.TryParseExact(dateEnd, formatEnd.GetDescription(),
new CultureInfo(_cultureInfo), DateTimeStyles.None, out var dateEnded);
if (dateEnded == DateTime.MinValue)
formatEnd = DateFormat.yyyyMMdd;
dateEndIsyyyyMMdd = DateTime.TryParseExact(dateEnd, formatEnd.GetDescription(),
new CultureInfo(_cultureInfo), DateTimeStyles.None, out dateEnded);
if (dateEnded == DateTime.MinValue) return (Result.Failure<DateInSentence>(errorMsg), Result.Failure<DateInSentence>(errorMsg));
if (dateEnded > DateTime.MinValue)
switch (dateBegin.Length)
case 8 when !dateEndIsyyyyMMdd:
var beginDDMMDateIsInPreviousYear = CompareDatesWithSameFormat(dateBegin, dateEnd,
out var format, out var validDates);
if (beginDDMMDateIsInPreviousYear)
DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin,
validDates.dateStarted, format, _cultureInfo))
, Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd, validDates.dateEnded, format, _cultureInfo)));
case 8 when dateEndIsyyyyMMdd:
var beginDDMMDateIsInPreviousYear = CompareDatesWithSameFormat(dateBegin, dateEnd,
out var format, out var validDates);
if (beginDDMMDateIsInPreviousYear)
DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin,
validDates.dateStarted, format, _cultureInfo))
, Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd, validDates.dateEnded, format, _cultureInfo)));
case 6 when !dateEndIsyyyyMMdd:
throw new NotImplementedException("I did not implemented 'de MMyyyy até ddMMyyyy'");
case 6 when dateEndIsyyyyMMdd:
throw new NotImplementedException("I did not implemented 'de yyyyMM até yyyyMMdd'");
case 4 when !dateEndIsyyyyMMdd:
var dateBeginIsDDMM = DateTime.TryParseExact(dateBegin + dateEnded.Year,
formatEnd.GetDescription(),
new CultureInfo(_cultureInfo), DateTimeStyles.None, out var ddMMBegin);
errorMsg = "because BeginDate is After EndDate";
else if (IsBeginDateReallyBeforeEndDate(ddMMBegin, dateEnded))
if (DateBeginHadToGoBackOneYear)
ddMMBegin = ddMMBegin.AddYears(-1);
Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin,
ddMMBegin, formatBegin, _cultureInfo)),
Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd,
dateEnded, formatEnd, _cultureInfo))
case 2 when !dateEndIsyyyyMMdd:
var dayBegin = int.Parse(dateBegin);
if ((dayBegin > 0 && dayBegin < dateEnded.Day) ||
((dayBegin == dateEnded.Day) && _sameFrameIntervalIsAllowed))
var dayInterval = Result.Success(
DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin,
new DateTime(dateEnded.Year, dateEnded.Month, dayBegin), DateFormat.dd,
Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd,
dateEnded, formatEnd, _cultureInfo))
errorMsg = "because BeginDate is After EndDate";
formatEnd = DateFormat.MMyyyy;
dateEndIsDDMM = DateTime.TryParseExact(dateEnd, formatEnd.GetDescription(),
new CultureInfo(_cultureInfo), DateTimeStyles.None, out dateEnded);
if (dateBegin.Length == 8)
throw new NotImplementedException("I did not implemented 'de ddMMyyyy até MMyyyy'");
else if (dateBegin.Length == 6)
var dateBeginIsDDMM = DateTime.TryParseExact(dateBegin, formatEnd.GetDescription(),
new CultureInfo(_cultureInfo), DateTimeStyles.None, out var ddMMBegin);
if (IsBeginDateReallyBeforeEndDate(ddMMBegin, dateEnded))
return (Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBegin, ddMMBegin, formatEnd, _cultureInfo)),
Result.Success(DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEnd, dateEnded, formatEnd, _cultureInfo))
errorMsg = "because BeginDate is After EndDate";
else if (dateBegin.Length == 4)
throw new NotImplementedException("I did not implemented 'de ddMM até MMyyyy'");
else if (dateBegin.Length == 2)
if (dateBegin.Length == 8)
else if (dateBegin.Length == 6)
else if (dateBegin.Length == 4)
var result = DecideIfDateEndIsYYYYOrDDMM(dateBegin, dateEnd, _sameFrameIntervalIsAllowed);
if (result.dateBegin.CertaintyAbout != CertaintyAboutElement.NonExisting)
return (Result.Success(result.dateBegin), Result.Success(result.dateEnd));
errorMsg = "not existing BeginDate or EndDate";
else if (dateBegin.Length == 2)
errorMsg = "No interval date stamp in this file name";
return (Result.Failure<DateInSentence>(errorMsg), Result.Failure<DateInSentence>(errorMsg));
private bool CompareDatesWithSameFormat(string dateBegin, string dateEnd, out DateFormat format,
out (bool dateBeginIsDDMM, DateTime dateStarted, bool dateEndIsDDMM, DateTime dateEnded) validDates)
var date = DateInSentence.CreateInstance(dateBegin, _cultureInfo, MaxPossibleYear, false);
(var mask, format) = (date.Mask.GetDescription(), date.Mask);
validDates = TryToParseDatesAsDDMM_MMDD(dateBegin, dateEnd, mask);
bool beginDDMMDateIsInPreviousYear = false;
if (validDates.dateStarted == DateTime.MinValue || validDates.dateEnded == DateTime.MinValue)
return beginDDMMDateIsInPreviousYear;
beginDDMMDateIsInPreviousYear = IsDDMMBeginDateReallyBeforeEndDate(dateBegin, dateEnd, format,
_sameFrameIntervalIsAllowed);
return beginDDMMDateIsInPreviousYear;
private (DateInSentence dateBegin, DateInSentence dateEnd) DecideIfDateEndIsYYYYOrDDMM(string dateBeginToTest, string dateEndToTest,
bool sameFrameIntervalIsAllowed = false)
var (isDateEndDubious, isDateBeginDubious, certaintyAboutYYYY) = DecideDegreeOfCertaintyAboutDates(dateBeginToTest, dateEndToTest);
var dateBeginTry = DateInSentence.CreateInstance(dateBeginToTest, _cultureInfo, MaxPossibleYear, false);
var (_, formatForDateBeginTry) = (dateBeginTry.Mask.GetDescription(), dateBeginTry.Mask);
var dateBegin = DateInSentence.NullDate; var dateBeginIsYYYY = false; var dateBeginIsDDMM = false;
var dateEnd = DateInSentence.NullDate; var dateEndIsYYYY = false; var dateEndIsDDMM = false;
var (dateEnded, endDateMaybeYYYY) = TryToParseDateAsYYYY(dateEndToTest);
var (dateStarted, beginDateMaybeYYYY) = TryToParseDateAsYYYY(dateBeginToTest);
endDateMaybeYYYY = IsValidEndDateYYYY(dateStarted, dateEnded, MinPossibleYear, MaxPossibleYear, sameFrameIntervalIsAllowed);
(dateBeginIsYYYY, dateEndIsYYYY) = DetermineIfIntervalDatesShouldBeYYYY(
isDateEndDubious, isDateBeginDubious, endDateMaybeYYYY, beginDateMaybeYYYY, dateEndIsYYYY,
var beginCannotBeDDMMOrMMDD = !DateInSentence.IsItPossibleToBeDDMMOrMMDD(dateBeginToTest, _cultureInfo);
if (beginCannotBeDDMMOrMMDD && !endDateMaybeYYYY)
if (dateStarted != dateEnded)
return (DateInSentence.NullDate, DateInSentence.NullDate);
if (dateStarted == dateEnded && !sameFrameIntervalIsAllowed)
return (DateInSentence.NullDate, DateInSentence.NullDate);
if (dateBeginIsYYYY || beginCannotBeDDMMOrMMDD)
(dateStarted, dateBeginIsYYYY) = TryToParseDateAsYYYY(dateBeginToTest);
formatForDateBeginTry = IsDDMM
(dateBeginIsDDMM, dateStarted, dateEndIsDDMM, dateEnded) =
TryToParseDatesAsDDMM_MMDD(dateBeginToTest, dateEndToTest, formatForDateBeginTry.ToString());
dateEndIsYYYY = DateTime.TryParseExact(dateEndToTest, DateFormat.yyyy.GetDescription(), new CultureInfo(_cultureInfo),
DateTimeStyles.None, out dateEnded);
if (!dateEndIsYYYY) return (dateBegin, dateEnd);
dateBegin = DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBeginToTest, dateStarted, DateFormat.yyyy, _cultureInfo, certaintyAboutYYYY);
dateEnd = DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEndToTest, dateEnded, DateFormat.yyyy, _cultureInfo, certaintyAboutYYYY);
else if (dateBeginIsDDMM)
if (!dateEndIsDDMM || !IsDDMMBeginDateReallyBeforeEndDate(dateBeginToTest, dateEndToTest, formatForDateBeginTry, sameFrameIntervalIsAllowed)) return (dateBegin, dateEnd);
dateBegin = DateInSentence.CreateInstanceProvidingAllCorrectElements(dateBeginToTest, dateStarted, formatForDateBeginTry, _cultureInfo, certaintyAboutYYYY);
dateEnd = DateInSentence.CreateInstanceProvidingAllCorrectElements(dateEndToTest, dateEnded, formatForDateBeginTry, _cultureInfo, certaintyAboutYYYY);
return (dateBegin, dateEnd);
private (bool isDateEndDubious, bool isDateBeginDubious, CertaintyAboutElement certaintyAboutYYYY)
DecideDegreeOfCertaintyAboutDates(string dateBeginToTest, string dateEndToTest)
var dubiousDate = _cultureInfo == "pt-BR" ? DocNameHelper.dubiousDDMMOrYYYY() : DocNameHelper.dubiousMMDDOrYYYY();
var isDateEndDubious = dubiousDate.Contains(dateEndToTest);
var isDateBeginDubious = dubiousDate.Contains(dateBeginToTest);
var certaintyAboutYYYY = isDateBeginDubious && isDateEndDubious
? CertaintyAboutElement.InformedGuess
: CertaintyAboutElement.AbsolutelyCertain;
return (isDateEndDubious, isDateBeginDubious, certaintyAboutYYYY);
private bool IsDDMMBeginDateReallyBeforeEndDate(string dateBegin, string dateEnd,
DateFormat dateFormat, bool sameFrameIntervalIsAllowed = false)
var beginDateValid = DateTime.TryParseExact(dateBegin, dateFormat.GetDescription(), new CultureInfo(_cultureInfo), DateTimeStyles.None, out var beginDate);
var endDateValid = DateTime.TryParseExact(dateEnd, dateFormat.GetDescription(), new CultureInfo(_cultureInfo), DateTimeStyles.None, out var endDate);
if (!beginDateValid || !endDateValid) return false;
return IsBeginDateReallyBeforeEndDate(beginDate, endDate);
private bool IsBeginDateReallyBeforeEndDate(DateTime dateBegin, DateTime dateEnd)
DateBeginHadToGoBackOneYear = false;
var isBefore = _sameFrameIntervalIsAllowed ? dateBegin <= dateEnd : dateBegin < dateEnd;
if (!isBefore && _forceBeginDatePreviousYearIfReasonable)
var dateBeginPreviousYear = dateBegin.AddYears(-1);
isBefore = _sameFrameIntervalIsAllowed ? dateBeginPreviousYear <= dateEnd : dateBeginPreviousYear < dateEnd;
DateBeginHadToGoBackOneYear = isBefore;
private (bool dateBeginIsYYYY, bool dateEndIsYYYY) DetermineIfIntervalDatesShouldBeYYYY(
bool isDateEndDubious, bool isDateBeginDubious
, bool endDateMaybeYYYY, bool beginDateMaybeYYYY
, bool dateEndIsYYYY, bool dateBeginIsYYYY)
if (IfNoDateIsDubious(isDateEndDubious, isDateBeginDubious))
return (endDateMaybeYYYY, beginDateMaybeYYYY);
if (AtLeastSomeDateIsDubiousAndBothDatesMaybeYYYY(endDateMaybeYYYY, beginDateMaybeYYYY))
return (dateBeginIsYYYY, dateEndIsYYYY);
private bool AtLeastSomeDateIsDubiousAndBothDatesMaybeYYYY(bool endDateMaybeYYYY, bool beginDateMaybeYYYY)
return endDateMaybeYYYY && beginDateMaybeYYYY;
private bool IfNoDateIsDubious(bool isDateEndDubious, bool isDateBeginDubious)
return !isDateEndDubious && !isDateBeginDubious;
private bool IsValidEndDateYYYY(DateTime dateBegin, DateTime dateEnd, int minPossibleYear, int maxPossibleYear, bool sameYearIntervalIsAllowed)
if (!IsBeginDateReallyBeforeEndDate(dateBegin, dateEnd)) return false;
return dateEnd.Year >= minPossibleYear && dateEnd.Year <= maxPossibleYear;
private (DateTime dateParsed, bool tryParseExact) TryToParseDateAsYYYY(string dateToTest)
var tryParseExact = DateTime.TryParseExact(dateToTest, DateFormat.yyyy.ToString(), new CultureInfo(_cultureInfo),
DateTimeStyles.None, out var dateParsed);
return (dateParsed, tryParseExact);
private (bool dateBeginIsDDMM, DateTime dateStarted, bool dateEndIsDDMM, DateTime dateEnded)
TryToParseDatesAsDDMM_MMDD(string dateBeginToTest, string dateEndToTest, string maskForDDMM)
var dateBeginIsDDMM = DateTime.TryParseExact(dateBeginToTest, maskForDDMM, new CultureInfo(_cultureInfo),
DateTimeStyles.None, out var dateStarted);
var dateEndIsDDMM = DateTime.TryParseExact(dateEndToTest, maskForDDMM, new CultureInfo(_cultureInfo),
DateTimeStyles.None, out var dateEnded);
return (dateBeginIsDDMM, dateStarted, dateEndIsDDMM, dateEnded);
#endregion Private Methods
private DateFormat DetermineTimeFrameType(string originalText)
if (originalText.Contains("dia"))
return DateFormat.NumDays;
if (originalText.Contains("mês") || originalText.Contains("meses"))
return DateFormat.NumMonths;
if (originalText.Contains("ano"))
return DateFormat.NumYears;
return DateFormat.Unknown;
private bool IsIntervalValid(string partToTest)
DocNameHelper.DateTryParseExact(_candidateBegin.Value, out var beginInterval, new CultureInfo(_cultureInfo));
DocNameHelper.DateTryParseExact(_candidateEnd.Value, out var endInterval, new CultureInfo(_cultureInfo));
var isValid = beginInterval < endInterval;
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return _cultureInfo;
yield return _sameFrameIntervalIsAllowed;
yield return _forceBeginDatePreviousYearIfReasonable;
yield return OriginalText;
yield return DateBeginHadToGoBackOneYear;
yield return TextBetween;
yield return TextTemporalRelation;
#region Overrides of Object
public override string ToString()
return @$"{nameof(IntervalType)}: {IntervalType}
, {nameof(TextTemporalRelation)}: {TextTemporalRelation}
, {nameof(TextBetween)}: {TextBetween}
, {nameof(TimeFrame)}: {TimeFrame}
, {nameof(DateBegin)}: {DateBegin}
, {nameof(CertaintyAboutDateBegin)}: {CertaintyAboutDateBegin}
, {nameof(DateEnd)}: {DateEnd}
, {nameof(CertaintyAboutDateEnd)}: {CertaintyAboutDateEnd}
, {nameof(DateBeginHadToGoBackOneYear)}: {DateBeginHadToGoBackOneYear}
, {nameof(MaxPossibleYear)}: {MaxPossibleYear}, {nameof(MinPossibleYear)}: {MinPossibleYear}";
#endregion Overrides of Object
public class BaseElement : ValueObject
private string _textValue => ElementValue.IsSuccess ? ElementValue.Value : "No value";
public int StartIndex { get; set; }
if (ElementValue.IsFailure)
return StartIndex + ElementValue.Value.Length;
public string ConnectorBefore { get; set; } = string.Empty;
public string OriginalText { get; private protected set; } = string.Empty;
public Result<string> ElementValue { get; set; } = Result.Failure<string>("No value");
public static void CopyContextProperly(BaseElement tryDate, int oldIndex, string connector)
tryDate.StartIndex = oldIndex;
tryDate.ConnectorBefore = connector;
#region Overrides of ValueObject
protected override IEnumerable<IComparable> GetEqualityComponents()
yield return ConnectorBefore;
yield return OriginalText;
[Description("mês/meses")]
MMMdotIncorrectTwoDigitsYear,
[Description("MMM.yyyy")]
[Description("MMMM_yyyy")]
MMMMyyyyIncorrectFormattedWithWildcard,
[Description("MMMM.yyyy")]
MMMMyyyyIncorrectFormattedWithDot,
[Description("MMMM yyyy")]
MMMyyIncorrectTwoDigitsYear,
[Description("yyyyMMddHHmmss")]
[Description("dd/MM/yyyy HH:mm:ss")]
[Description("MM/dd/yyyy HH:mm:ss")]
[Description("yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'")]
[Description("yyyy_MMM_dd__HH_mm_ss")]
personalizedForAnotaçãoOutlookBackup,
[Description("yyyy_MMM._dd__HH_mm_ss")]
personalizedForAnotaçãoOutlookBackupWithDot,
[Description("dd_MM_yyyy")]
FullDateIncorrectFormattedWithWildcardBR,
[Description("MM_dd_yyyy")]
FullDateIncorrectFormattedWithWildcardUS,
[Description("dd.MM.yyyy")]
FullDateIncorrectFormattedWithDotBR,
[Description("MM.dd.yyyy")]
FullDateIncorrectFormattedWithDotUS,
[Description("dd.MM.yy")]
FullDateIncorrectTwoDigitsYearAndFormattedWithDotBR,
[Description("MM.dd.yy")]
FullDateIncorrectTwoDigitsYearAndFormattedWithDotUS,
DDMMIncorrectFormattedWithWildcardBR,
DDMMIncorrectFormattedWithWildcardUS,
DDMMIncorrectFormattedWithDotBR,
DDMMIncorrectFormattedWithDotUS,
DDMIncorrectFormattedWithDotBR,
DDMIncorrectFormattedWithDotUS
public enum CertaintyAboutElement
public enum ElementRelativePosition
public enum WhichElementItIs
public static class DocNameHelper
#region private constants
private const string GrabDatesWithMonthsInLettersOrPureNumbersFromFile =
@"(?<relationandinterval>(?<temporalrelation> em | por )?(?<=\W)(?<intervalordate>(?:\d{6,8}|\d{2,3}_{1,2})|(?:\b(0?[1-9]|[12][0-9]|3[01])*(?: de )?([JFMASONDjfmasond]([a-z0-9]+)([ç]{0,1})([a-z0-9]+)*)*(?: de )?(19|20)?[0-9]{1}\b))(?<timeframe> anos[ .]{0,1}| ano[ .]{0,1}| meses[ .]{0,1}| mês[ .]{0,1}| dias[ .]{0,1}| dia[ .]{0,1})?)";
private const string GrabDatesOrPureNumbersOrDatesWithCardFromPartOfFile = @"(?<date>\d{6,8}|\d{2,3}_{1,2})";
private const string GrabDatesIncorrectFormattedWithInvalidChars = @"(?<![_.]{1})(?<date>\d{2}[_.]{1}\d{2}[_.]{1}(?:\d{4}|\d{2}))";
private const string GrabDatesUTC = @"(?<dateUTC>(?<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?<month>1[0-2]|0[1-9])-(?<day>3[01]|0[1-9]|[12][0-9])T(?<hour>2[0-3]|[01][0-9]):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?|(?<year>(?:[1-9][0-9]*)?[0-9]{4})(?<month>1[0-2]|0[1-9])(?<day>3[01]|0[1-9]|[12][0-9])(?<hour>2[0-3]|[01][0-9])(?<minute>[0-5][0-9])(?<second>[0-5][0-9])(\\.[0-9]+)?(?<zeroUTCoffse>Z)?)";
private const string GrabDateIntervalFromPartOfFile = @"(?<=\W)(?<interval>(?<starting>[ mçinícandouetr]+?)(?<datebegin>\d{2,8}|\d{14}(?!\d))(?<link>[ eatéfimnldzou]+?)(?<=\W)(?<!\w-)(?<dateend>\d{4,8}|\d{14})(?!\d))";
private const string GrabDateAndVersionDotAtEnd =
@"(?<date>\d{6,8}|\d{2,3}_{1,2}) {1,2}(?<version>[vVersionão]+[\d@]+[\w@]*|[\d@]+[vVersionão]+[\w@]*).|(?<version>[vVersionão]+[\d@]+[\w@]*|[\d@]+[vVersionão]+[\w@]*) {1,2}(?<date>\d{6,8}|\d{2,3}_{1,2})\.|(?<date>\d{6,8}|\d{2,3}_{1,2})\.";
private const string GrabDatePersonalizedForAnotaçãoOutlookBackup = @"backup_(?<date>(?<yyyy>(?:19|20)[0-9]{2})_(?<MMM>[JFMASONDjfmasond]{1}[aeiouAEIOUnNvVrRbBlLgGtTzZpPyYcC]{2})_(?<dd>0[1-9]|[12][0-9]|3[01])__(?<hh>0?[0-9]|[1][0-9]|[2][0-3])_(?<mm>[0-9]{2})_(?<ss>[0-9]{2}))";
private const string GrabDatePersonalizedForAnotaçãoOutlookBackupWithDotAfterMMM = @"backup_(?<date>(?<yyyy>(?:19|20)[0-9]{2})_(?<MMM>[JFMASONDjfmasond]{1}[aeiouAEIOUnNvVrRbBlLgGtTzZpPyYcC]{2})._(?<dd>0[1-9]|[12][0-9]|3[01])__(?<hh>0?[0-9]|[1][0-9]|[2][0-3])_(?<mm>[0-9]{2})_(?<ss>[0-9]{2}))";
private const string GrabAbbreviatedMonthWithTwoOrFourDigitYearWithDotSeparatingThemOrNot = @"(?<date>(?<datewithdot>(?<MMM>jan|fev|feb|mar|abr|apr|mai|may|jun|jul|ago|aug|set|sep|out|oct|nov|dez|dec)\.(?<yyyy>(?:19|20){0,1}[0-9]{2}))|(?<datewithoutdot>(?<MMM>jan|fev|feb|mar|abr|apr|mai|may|jun|jul|ago|aug|set|sep|out|oct|nov|dez|dec)(?<yyyy>(?:19|20){0,1}[0-9]{2})))";
private const string GrabFullMonthWithTwoOrFourDigitYearWithSomeCharSeparatingThemOrNot = @"(?<date>(?:(?<datewithsomecharseparating>(?<MMM>janeiro|january|fevereiro|february|março|march|abril|april|maio|may|junho|julho|agosto|august|setembro|september|outubro|october|novembro|november|dezembro|december)[-._ ]{1})|(?<datewithoutseparation>(?<MMM>janeiro|january|fevereiro|february|março|march|abril|april|maio|may|junho|julho|agosto|august|setembro|september|outubro|october|novembro|november|dezembro|december)))(?<yyyy>(?:19|20){0,1}[0-9]{2}))";
private const string GrabVersionFromFile = @"(?<version>\sv[ersionão]*[\d@]+[\w@]*|[\d@]+v[ersionão]*[\w@]*)";
public const string GrabNameAndExtension = @"(?<fullname>.+?)(\.(?<extension>[^.]*)$|$)";
public const string GrabPartsOfName = @"([\w _<>+;.,']*)*";
public const string GrabDelimiterWithSpaceAround = @"( - )";
public const string GrabFakeDelimiterWithoutAnySpaceAround = @"(?<before>[\d\w' -.]*)(?<=\w)(?<FakeDelimiter>[-˗–])(?=\w)(?<after>[\d\w -.]*)";
private const string Grab_GUID_Treated = @"(?<GUID>[0-9a-f]{8}<>[0-9a-f]{4}<>[0-9a-f]{4}<>[0-9a-f]{4}<>[0-9a-f]{12})";
#endregion private constants
internal static bool DifficultyToTestPart(string partToTest)
return (partToTest.Length > 10 && partToTest.DoesNotContainAnySpace());
internal static Result<string> ReturnResult(Regex rgx, string part, string groupName, string errorMsg)
_ = GetMatch(rgx, part, groupName, errorMsg, out var result);
internal static Match GetMatch(Regex rgx, string part, string groupName, string errorMsg, out Result<string> result)
var match = rgx.Match(" " + part.TrimStart());
result = match.Success ? Result.Success(match.Groups[groupName].Value.Trim()) : Result.Failure<string>(errorMsg);
internal static Match GetMatch(string pattern, string part)
var rgx = new Regex(pattern);
var match = rgx.Match(" " + part);
internal static Group GetMatchMainGroup(string pattern, string part)
var rgx = new Regex(pattern);
var match = rgx.Match(part.EnsureOneSingleSpaceAtBegin());
internal static Group GetMatch(string pattern, string part, string groupName)
var rgx = new Regex(pattern);
var match = rgx.Match(part.EnsureOneSingleSpaceAtBegin());
return match.Groups[groupName];
internal static MatchCollection GetMatches(string pattern, string part)
var rgx = new Regex(pattern, RegexOptions.Multiline);
var matches = rgx.Matches(part.EnsureOneSingleSpaceAtBegin());
internal static bool OutOfForbiddenZoneIfSamePart(string groupName, Match match, int startOfForbiddenZone, int endOfForbiddenZone)
return BeforeStartOfForbiddenZone(groupName, match, startOfForbiddenZone) && AfterEndOfForbiddenZone(groupName, match, endOfForbiddenZone);
private static bool BeforeStartOfForbiddenZone(string groupName, Match match, int startOfForbiddenZone)
return match.Groups[groupName].Index + match.Groups[groupName].Length - 1 < startOfForbiddenZone;
private static bool AfterEndOfForbiddenZone(string groupName, Match match, int endOfForbiddenZone)
return match.Groups[groupName].Index > endOfForbiddenZone;
public static readonly Lazy<Regex> ForGrabFakeDelimiterWithoutAnySpaceAround = new(() =>
new Regex(GrabFakeDelimiterWithoutAnySpaceAround, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabNameAndExtension = new(() =>
new Regex(GrabNameAndExtension, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDelimiterWithSpaceAround = new(() =>
new Regex(GrabDelimiterWithSpaceAround, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabPartsOfName = new(() =>
new Regex(GrabPartsOfName, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrab_GUID_Treated = new(() =>
new Regex(Grab_GUID_Treated, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static List<string> MonthsPortugueseFull =>
"janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro",
public static Dictionary<string, string> MonthsPortugueseFullDictionary =>
MonthsPortugueseFull.ToDictionary(month => month, month => month);
public static List<string> MonthsPortugueseAbbreviated =>
"jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez"
public static Dictionary<string, string> MonthsPortugueseAbbreviatedDictionary =>
MonthsPortugueseAbbreviated.ToDictionary(month => month, month => month);
public static List<string> MonthsPortugueseAbbreviatedWithDot =>
MonthsPortugueseAbbreviated.Select(month => month + ".").ToList();
public static List<string> MonthsEnglishFull =>
"january", "february", "march", "april", "may", "june", "july", "august",
"september", "october", "november", "december"
public static Dictionary<string, string> MonthsEnglishFullDictionary =>
MonthsEnglishFull.ToDictionary(month => month, month => month);
public static List<string> MonthsEnglishAbbreviated => new()
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug",
"sep", "oct", "nov", "dec"
public static Dictionary<string, string> MonthsEnglishAbbreviatedDictionary =>
MonthsEnglishAbbreviated.ToDictionary(month => month, month => month);
public static bool IsMonthAbbreviationEqualsInPortugueseAndEnglish(string month) =>
MonthsEnglishAbbreviated.FirstOrDefault(m => m == month) == MonthsPortugueseAbbreviated.FirstOrDefault(m => m == month);
#region Interval and Date
internal static string[] dubiousDDMMOrYYYY() => new[] { "2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012" };
internal static string[] dubiousMMDDOrYYYY() => new[] { "" };
public static readonly Lazy<Regex> ForGrabDatesWithMonthsInLettersOrPureNumbersFromFile = new(() =>
new Regex(GrabDatesWithMonthsInLettersOrPureNumbersFromFile, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDatesUTC = new(() =>
new Regex(GrabDatesUTC, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDatePersonalizedForAnotaçãoOutlookBackup = new(() =>
new Regex(GrabDatePersonalizedForAnotaçãoOutlookBackup, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDatePersonalizedForAnotaçãoOutlookBackupWithDotAfterMMM = new(() =>
new Regex(GrabDatePersonalizedForAnotaçãoOutlookBackupWithDotAfterMMM, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDatesIncorrectFormattedWithInvalidChars = new(() =>
new Regex(GrabDatesIncorrectFormattedWithInvalidChars, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDatesOrPureNumbersOrDatesWithCardFromPartOfFile = new(() =>
new Regex(GrabDatesOrPureNumbersOrDatesWithCardFromPartOfFile, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabDateIntervalFromPartOfFile = new(() =>
new Regex(GrabDateIntervalFromPartOfFile, RegexOptions.Compiled));
public static readonly Lazy<Regex> ForGrabAbbreviatedMonthWithTwoOrFourDigitYearWithDotSeparatingThemOrNot = new(() =>
new Regex(GrabAbbreviatedMonthWithTwoOrFourDigitYearWithDotSeparatingThemOrNot, RegexOptions.Compiled | RegexOptions.IgnoreCase));
public static readonly Lazy<Regex> ForGrabFullMonthWithTwoOrFourDigitYearWithSomeCharSeparatingThemOrNot = new(() =>
new Regex(GrabFullMonthWithTwoOrFourDigitYearWithSomeCharSeparatingThemOrNot, RegexOptions.Compiled | RegexOptions.IgnoreCase));
#endregion Interval and Date
public static readonly Lazy<Regex> ForGrabVersionFromFile = new(() =>
new Regex(GrabVersionFromFile, RegexOptions.Compiled | RegexOptions.IgnoreCase));
internal static bool DateWithNoYearTryParseExact(string dateWithNoYearAndWithoutSpace, out DateTime testDate, CultureInfo cultureInfo, string yearToAssume)
Guard.ThrowIfStringNullOrEmptyOrNotString(dateWithNoYearAndWithoutSpace, nameof(dateWithNoYearAndWithoutSpace));
return DateTryParseExact(dateWithNoYearAndWithoutSpace + yearToAssume, out testDate, cultureInfo, string.IsNullOrEmpty(yearToAssume));
internal static bool DateTryParseExact(string dateWithoutSpace, out DateTime testDate, CultureInfo cultureInfo
, bool forceDDMM = false)
Guard.ThrowIfStringNullOrEmptyOrNotString(dateWithoutSpace, nameof(dateWithoutSpace));
if (dateWithoutSpace.DoesNotContainAnyNumbers()) { testDate = DateTime.MinValue; return false; }
var yyyy = forceDDMM ? "dummyformat" : DateFormat.yyyy.GetDescription();
var isDDMM = cultureInfo.Name == "pt-BR";
var ddMMyyyy = isDDMM ? DateFormat.ddMMyyyy.GetDescription() : DateFormat.MMddyyyy.GetDescription();
var ddMMMyyyy = isDDMM ? DateFormat.ddMMMyyyy.GetDescription() : DateFormat.MMMddyyyy.GetDescription();
var ddMMMMyyyy = isDDMM ? DateFormat.ddMMMMyyyy.GetDescription() : DateFormat.MMMMddyyyy.GetDescription();
var ddMM = isDDMM ? DateFormat.ddMM.GetDescription() : DateFormat.MMdd.GetDescription();
var ddMMM = isDDMM ? DateFormat.ddMMM.GetDescription() : DateFormat.MMMdd.GetDescription();
var ddMMMM = isDDMM ? DateFormat.ddMMMM.GetDescription() : DateFormat.MMMMdd.GetDescription();
var ddMMyyyyHHmmssWithSlash = isDDMM ? DateFormat.completeUpToSeconds.GetDescription() : DateFormat.completeUpToSecondsUS.GetDescription();
string[] dateFormatsLength4AndOnlyDigits = { yyyy, ddMM };
string[] dateFormatsLength5 = { ddMMM };
string[] dateFormatsLength6AndOnlyDigits = { DateFormat.MMyyyy.GetDescription() };
string[] dateFormatsLength7 = { DateFormat.MMMyyyy.GetDescription() };
string[] dateFormatsLength8AndOnlyDigits = { ddMMyyyy };
string[] dateFormatsLength9 = { ddMMMyyyy };
string[] dateFormatsLength14AndOnlyDigits = { DateFormat.sortable.GetDescription() };
string[] dateFormatsLengthMoreThen10 =
ddMMyyyyHHmmssWithSlash, DateFormat.universalSortable.GetDescription(),
ddMMMM, ddMMMMyyyy, DateFormat.MMMMyyyy.GetDescription(),
DateFormat.personalizedForAnotaçãoOutlookBackup.GetDescription(),
DateFormat.personalizedForAnotaçãoOutlookBackupWithDot.GetDescription(),
if (dateWithoutSpace.StringHasOnlyNumbers())
dateFormatsAll = dateWithoutSpace.Length switch
4 => dateFormatsLength4AndOnlyDigits,
6 => dateFormatsLength6AndOnlyDigits,
8 => dateFormatsLength8AndOnlyDigits,
14 => dateFormatsLength14AndOnlyDigits,
_ => new[] { "UnforeseenCase" }
dateFormatsAll = dateWithoutSpace.Length switch
_ => dateFormatsLengthMoreThen10
var tryDate = DateTime.TryParseExact(dateWithoutSpace, dateFormatsAll, cultureInfo, DateTimeStyles.AssumeLocal, out testDate);
return tryDate || DateTime.TryParse(dateWithoutSpace, cultureInfo, DateTimeStyles.None, out testDate);
private static string[] GetProperFormatFor10OrMore(string dateWithoutSpace, string[] dateFormatsLengthMoreThen10, string[] dateFormatsForAnotaçãoOutlookBackup)
return dateWithoutSpace.CountOccurrences("_") == 6 ? dateFormatsForAnotaçãoOutlookBackup : dateFormatsLengthMoreThen10;
internal static IEnumerable<DocName> OrderDocsByDate(IEnumerable<DocName> docs)
return docs.OrderBy(x => x.GetDateTimeOrLastPartOfIntervalForOrderDoc());
List<string> wordsThatPointsToType
this.DisplayName = displayName;
this.WordsThatPointsToType = wordsThatPointsToType;
public string DisplayName { get; set; }
public IEnumerable<string> WordsThatPointsToType { get; set; }
public interface IMessageDialogService
MessageDialogResult ShowOkCancelDialog(string text, string title,
MessageDialogResult defaultResult = MessageDialogResult.Ok);
MessageDialogResult ShowYesNoDialog(string text, string title,
MessageDialogResult defaultResult = MessageDialogResult.Yes);
void ShowInfoDialog(string text, string title = "Info");
public enum MessageDialogResult
public class MessageDialogServiceNone : IMessageDialogService
#region Implementation of IMessageDialogService
public MessageDialogResult ShowOkCancelDialog(string text, string title,
MessageDialogResult defaultResult = MessageDialogResult.Ok)
public MessageDialogResult ShowYesNoDialog(string text, string title,
MessageDialogResult defaultResult = MessageDialogResult.Yes)
public void ShowInfoDialog(string text, string title = "Info")
public class WrappedCollection<T> : IList<T>
private readonly IList<T> _list = new List<T>();
public WrappedCollection(IList<T> wrappedCollection)
if (wrappedCollection == null)
throw new ArgumentNullException("wrappedCollection");
this._list = wrappedCollection;
#region Implementation of IEnumerable
public IEnumerator<T> GetEnumerator()
return _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
return this.GetEnumerator();
#region Implementation of ICollection<T>
public bool Remove(T item)
return _list.Remove(item);
public bool Contains(T item)
return _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
_list.CopyTo(array, arrayIndex);
get { return _list.Count; }
get { return _list.IsReadOnly; }
#region Implementation of IList<T>
public int IndexOf(T item)
return _list.IndexOf(item);
public void Insert(int index, T item)
_list.Insert(index, item);
public void RemoveAt(int index)
get { return _list[index]; }
set { _list[index] = value; }
public static partial class DateTimeExtensions
public static int YearsBetweenNowAndDate(this DateTime @this) => Math.Abs(DateTime.Now.HowOld(@this, out _, out _));
public static int YearsBetweenDates(this DateTime @this, DateTime that) => Math.Abs(that.HowOld(@this, out _, out _));
public static int HowOld(this DateTime initialDay, DateTime dayToCalculate, out int days, out int months)
months = dayToCalculate.Month - initialDay.Month;
int years = dayToCalculate.Year - initialDay.Year;
if (dayToCalculate.Day < initialDay.Day)
days = (dayToCalculate - initialDay.AddMonths((years * 12) + months)).Days;
public static string HowYearsOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsOldAtDate(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsMonthsOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} ";
return "Cannot calculate age";
public static string HowYearsMonthsOldAtDate(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} ";
return "Cannot calculate age";
public static string HowYearsMonthsDaysOldToday(this DateTime birthday, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(DateTime.Now, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} " + $"e {howManyDays} dia{((howManyDays == 1) ? "" : "s")}";
return "Cannot calculate age";
public static string HowYearsMonthsDaysOldAtDatey(this DateTime birthday, DateTime atDate, bool allowNegativeAge = false)
var howManyYears = birthday.HowOld(atDate, out var howManyDays, out var howManyMonths);
if (allowNegativeAge || howManyYears >= 0)
return $"{howManyYears} ano{((howManyYears == 1) ? "" : "s")}" + $", {howManyMonths} {((howManyMonths == 1) ? "mês" : "meses")} " + $"e {howManyDays} dia{((howManyDays == 1) ? "" : "s")}";
return "Cannot calculate age";
public static DateTime MinimumWinFormDateTime(this DateTime @this) => Convert.ToDateTime("1/1/1753 00:00:00");
public static DateTime NullOutlookDateTime(this DateTime @this) => Convert.ToDateTime("1/1/4501 00:00:00");
public static DateTime OutlookDefaultDateForInvalidEntry(this DateTime @this)
if (CultureInfo.CurrentCulture.Name.ToString() == "pt-BR")
return Convert.ToDateTime("30/12/1899 00:00:00");
return Convert.ToDateTime("12/30/1899 00:00:00");
public static DateTime OutlookEarliestDateThatDoesNotProvokeError(this DateTime @this)
if (CultureInfo.CurrentCulture.Name.ToString() == "pt-BR")
return Convert.ToDateTime("01/04/1601 00:00:00");
return Convert.ToDateTime("04/01/1601 00:00:00");
public static DateTime ThisDateOrAppropriateMinimalDateTime(this DateTime @this)
=> (DateTime)(@this < @this.MinimumWinFormDateTime() || @this == NullOutlookDateTime(@this)
? @this.MinimumWinFormDateTime()
public static bool IsThisDateNullOrDefault(this DateTime @this) =>
@this == @this.MinimumWinFormDateTime()
|| @this == NullOutlookDateTime(@this)
|| @this == DateTime.MaxValue
|| @this == DateTime.MinValue
|| @this == @this.OutlookDefaultDateForInvalidEntry()
|| @this < @this.OutlookEarliestDateThatDoesNotProvokeError();
public static DateTime ThisDateCandidateOrDateTimeMinimal(this string stringToBeParsed)
DateTime @this = default;
if (stringToBeParsed == null || !DateTime.TryParse(stringToBeParsed, out @this))
return DateTime.MinValue;
public static DateTime ThisDateCandidateOrAppropriateMinimalDateTime(this string stringToBeParsed)
DateTime @this = default;
if (stringToBeParsed == null || !DateTime.TryParse(stringToBeParsed, out @this))
return @this.MinimumWinFormDateTime();
return (DateTime)(@this < @this.MinimumWinFormDateTime() || @this == NullOutlookDateTime(@this)
? @this.MinimumWinFormDateTime()
public static DateTime ReturnAnteriorBetweenDates(this DateTime d1, DateTime d2) => d1 < d2 ? d1 : d2;
public static DateTime ReturnPosteriorBetweenDates(this DateTime d1, DateTime d2) => d1 < d2 ? d1 : d2;
public static DateTime AllowPastOrNowTimeButNotFuture(this DateTime? ToTest, DateTime? ToCompare = null)
ToCompare = DateTime.Now;
var value when value == null => DateTime.Now,
var value when value < ToCompare => (DateTime)ToTest,
var value when value > ToCompare => throw new ArgumentOutOfRangeException("Date cannot be in the future..."),
public static DateTime AllowOnlyFuture(this DateTime? ToTest, double numberOfHoursToAdd = 1, DateTime? ToCompare = null)
ToCompare = DateTime.Now;
var value when value == null => DateTime.Now.AddHours(numberOfHoursToAdd),
var value when value > ToCompare => (DateTime)ToTest,
var value when value < ToCompare => throw new ArgumentOutOfRangeException("Date cannot be in the past or even now..."),
public static class StringTextExtensions
private const char OneBlankSpace = ' ';
public static string RemoveUnnecessarySpaces(this string textToTrim)
return Regex.Replace(textToTrim, @"\s+", " ").Trim();
public static string RemoveAllSpaces(this string textToTrim)
return textToTrim.Replace(" ", string.Empty);
public static string RemoveAllPunctuation(this string textToTrim)
return new string (textToTrim.Where(c => !char.IsPunctuation(c)).Where(c => !char.IsSymbol(c)).ToArray());
public static string GetRidOfNonLetterCharsInKeyOrPairElements(this string rawString)
=> rawString.RemoveAllPunctuation().RemoveAllSpaces().RemoveBrackets();
public static string GetNumberOfCharsJustBeforeSubstring(this string text, string beforeThis, int numberOfChars = 1)
txtBefore = text.GetUntilOrEmpty(beforeThis);
return txtBefore.Length > 0 ? text.GetUntilOrEmpty(beforeThis).Substring(txtBefore.Length - numberOfChars) : string.Empty;
Console.WriteLine($"GetNumberOfCharsJustBeforeSubstring error {ex.Message}: tried to subtract {txtBefore.Length} – { numberOfChars }");
public static string GetUntilOrEmpty(this string text, string stopAt = "-")
if (!string.IsNullOrWhiteSpace(text))
int charLocation = text.IndexOf(stopAt, StringComparison.Ordinal);
return text.Substring(0, charLocation);
public static string GetAfterOrEmpty(this string text, string stopAt = "-")
if (!string.IsNullOrWhiteSpace(text))
int charLocation = text.IndexOf(stopAt, StringComparison.Ordinal);
return text.Substring(charLocation + stopAt.Length, text.Length - charLocation - stopAt.Length);
public static string SubstituteSubstringAtSpecificPositionForAnother(this string text,
string substringToReplace, string replacement, int position)
var sb = new StringBuilder(text);
sb.Remove(position, substringToReplace.Length).Insert(position, replacement);
public static string GetMiddleString(this string inputString, char separator = OneBlankSpace)
var input = inputString.Split(separator);
var firstToken = input[0];
var lastToken = input[input.Length - 1];
var pos1 = inputString.IndexOf(firstToken) + firstToken.Length;
var pos2 = inputString.IndexOf(lastToken);
var result = inputString.Substring(pos1, pos2 - pos1).Trim() ?? string.Empty;
public static bool NextCharAfterThisExcerpt(this string text, string uniqueExcerpt, char dot)
return text.ToLower().IndexOf(uniqueExcerpt, StringComparison.Ordinal) + uniqueExcerpt.Length < text.Length &&
text[text.ToLower().IndexOf(uniqueExcerpt, StringComparison.Ordinal) + uniqueExcerpt.Length] == dot;
public static bool NextCharAfterThisExcerpt(this string text, string uniqueExcerpt, char[] chars)
return text.ToLower().IndexOf(uniqueExcerpt, StringComparison.Ordinal) + uniqueExcerpt.Length < text.Length &&
chars.Contains(text[text.ToLower().IndexOf(uniqueExcerpt, StringComparison.Ordinal) + uniqueExcerpt.Length]);
public static string AddSubstringAfterThisFoundExcerpt(this string text, string uniqueExcerpt, string toAdd)
return text.ToLower().Replace(uniqueExcerpt, uniqueExcerpt + toAdd);
public static string RemoveSubstringAfterThisFoundExcerpt(this string text, string uniqueExcerpt,
return text.ToLower().Replace(uniqueExcerpt + toRemove, uniqueExcerpt);
public static string AddSubstringAfterThisFirstFoundExcerpt(this string text, string uniqueExcerpt, string toAdd)
var index = text.ToLower().IndexOf(uniqueExcerpt, StringComparison.Ordinal);
if (index < 0) return text;
var firstPart = text.Substring(0, index);
var secondPart = text.Substring(index + uniqueExcerpt.Length);
return firstPart + uniqueExcerpt + toAdd + secondPart;
public static (string textBefore, string textAfter) GetTextBeforeAndAfterSubstring(this string textToExtract,
string stringToSearchFor, int desiredLengthToCut, int indexToSearchedString)
indexToSearchedString - desiredLengthToCut < 0 ? 0 : indexToSearchedString - desiredLengthToCut,
indexToSearchedString - desiredLengthToCut > 0 ? desiredLengthToCut : indexToSearchedString);
indexToSearchedString + stringToSearchFor.Length
, DecideHowMuchItCanCutAfter(textToExtract.Length
, indexToSearchedString + stringToSearchFor.Length, desiredLengthToCut));
return (textBefore, textAfter);
static int DecideHowMuchItCanCutAfter(int totalLength, int beginCut, int lengthToCut)
if (beginCut + lengthToCut < totalLength)
if (beginCut + lengthToCut >= totalLength)
return totalLength - beginCut;
public static string RemoveDiacritics(this string s)
string normalizedString = s.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < normalizedString.Length; i++)
char c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
return stringBuilder.ToString();
public static string RemoveBrackets(this string s)
return s.Replace("[", string.Empty).Replace("]", string.Empty);
public static string SurroundWithDoubleQuotes(this string text)
return SurroundWith(text, "\"");
public static string SurroundWith(this string text, string ends)
return ends + text + ends;
public static string EscapeSingleQuote(this string text)
return text.Replace("'", @"\'");
public static string AddSingleQuoteAsEscapeCharacter(this string text)
return text.Replace("'", @"''");
public static string EnsureOneSingleSpaceAtBegin(this string text)
return OneBlankSpace + text.TrimStart();
public static string CapitalizeFirstLetter(this string value)
char[] array = value.ToCharArray();
if (char.IsLower(array[0]))
array[0] = char.ToUpper(array[0]);
for (int i = 1; i < array.Length; i++)
if (char.IsLower(array[i]))
array[i] = char.ToUpper(array[i]);
return new string (array);
public static string SplitCapitalizedWords(this string source)
if (String.IsNullOrEmpty(source))
var newText = new StringBuilder(source.Length * 2);
newText.Append(source[0]);
for (int i = 1; i < source.Length; i++)
if (char.IsUpper(source[i]))
newText.Append(source[i]);
return newText.ToString();
public static string TrimLastCharacter(this String str) => String.IsNullOrEmpty(str) ? str : str.TrimEnd(str[str.Length - 1]);
public static IEnumerable<int> AllIndexesOf(this string str, string searchString)
int minIndex = str.IndexOf(searchString, StringComparison.Ordinal);
minIndex = str.IndexOf(searchString, minIndex + searchString.Length, StringComparison.Ordinal);
public static bool ContainsAny(this string @this, params string[] values)
foreach (string value in values)
if (@this.IndexOf(value) != -1)
public static int CountOccurrences(this string text, string pattern)
return (text.Length - text.Replace(pattern, "").Length) / pattern.Length;
public static bool ContainsAny(this string @this, StringComparison comparisonType, params string[] values)
foreach (string value in values)
if (@this.IndexOf(value, comparisonType) != -1)
public static bool DoesNotContainAnySpace(this string @this) => !@this.Any(char.IsWhiteSpace);
public static bool DoesNotContainAnyNumbers(this string @this) => @this.ReturnOnlyNumbers().Length == 0;
public static bool StringHasOnlyNumbers(this string @this) => @this.ReturnOnlyNumbers().Length == @this.Length;
public static bool ContainsOnly(this string @this, string allowedChars) => @this.All(allowedChars.Contains);
public static List<string> ReturnOnlyInvalidChars(this string @this, string allowedChars) =>
(from c in @this where !allowedChars.Contains(c.ToString()) select c.ToString()).ToList();
public static string ReturnOnlyNumbers(this string @this) => new(@this.Where(char.IsDigit).ToArray());
public static int? ReturnOnlyNumbersAsInt(this string @this)
int.TryParse(@this.ReturnOnlyNumbers(), out var result);
public static string ReturnOnlyNonNumericCharacters(this string @this) =>
Regex.Replace(@this, @"[\d-]", string.Empty);
public static int[] ReturnOnlyNumbersToArray(this string @this) => Regex.Matches(@this, "(-?[0-9]+)").OfType<Match>().Select(m => int.Parse(m.Value)).ToArray();
public static int GetSingleDigitAtPositionInFilteredStrOnlyNumbers(this string @this, int position1Based)
if (position1Based < 1 || position1Based > @this.Length - 1)
throw new ArgumentOutOfRangeException(nameof(position1Based),
$"{nameof(position1Based)} cannot be outer of string limits");
return int.Parse(@this.ReturnOnlyNumbers()[position1Based - 1].ToString());
public static string FormatPhoneNumber(this long number, string mask) => number.ToString(mask);
public static string FormatPhoneNumber(this string number)
var mask = number.ReturnOnlyNumbers().Length switch
15 => @"0 000 (00) 00000-0000",
13 => @"+00 (00) 00000-0000",
11 => @"(00) 00000-0000",
_ => throw new ArgumentOutOfRangeException(nameof(FormatPhoneNumber), "FormatPhoneNumber only accepts certain ranges of phone numbers"),
return long.Parse(number.ReturnOnlyNumbers()).FormatPhoneNumber(mask);
public static bool IsDate(this string @this, out DateTime date) => DateTime.TryParse(@this, out date);
public static bool IsDate(this string @this) => DateTime.TryParse(@this, out var date);
public static bool IsNumber(this string @this) => int.TryParse(@this, out var number);
public static bool IsDigit(this string @this) => @this.All(c => c >= '0' && c <= '9');
public static bool ContainsOnlyDigitsAndThisSubstring(this string @this, string substring)
var resultOk = @this.Contains(substring);
if (!resultOk) return false;
@this = @this.Replace(substring, string.Empty);
public static string ReturnParentFolder(this string pathSource)
return Path.GetFullPath(Path.Combine(pathSource, @"..\"));
public static string CombinePaths(this string pathWithRoot, string pathDistal)
var networkFolder = !pathWithRoot.Contains(":");
var pathOne = pathWithRoot.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathTwo = pathDistal.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathFinal = pathOne.Concat(pathTwo);
var preFinalPath = Path.Combine(pathFinal.ToArray());
return "\\\\" + preFinalPath;
return preFinalPath.Contains(":\\") ? preFinalPath : preFinalPath.Replace(":", ":\\");
public static class EasyOfficeFolderExtensions
public static string GetParentFolder(this string pathSource)
return Path.GetFullPath(Path.Combine(pathSource, @"..\"));
public static string CombinePaths(this string pathWithRoot, string pathDistal)
var networkFolder = !pathWithRoot.Contains(":");
var pathOne = pathWithRoot.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathTwo = pathDistal.Split(@"\".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var pathFinal = pathOne.Concat(pathTwo);
var preFinalPath = Path.Combine(pathFinal.ToArray());
return "\\\\" + preFinalPath;
return preFinalPath.Contains(":\\") ? preFinalPath : preFinalPath.Replace(":", ":\\");
public static class ObjectTextExtensions
public static T ObjToEnum<T>(this object o, int unknown = 0)
T enumVal = (T)Enum.Parse(typeof(T), o.ToString());
return (T)Enum.ToObject(typeof(T), unknown);
return (T)Enum.ToObject(typeof(T), unknown);
public static bool IsDefined(this System.Enum value)
return Enum.IsDefined(value.GetType(), value);
public static int EnumToInt<TValue>(this TValue value)
where TValue : System.Enum => (int)(object)value;
public static TValue IntToEnum<TValue>(this int value)
where TValue : System.Enum => (TValue)(object)value;
public static T GetEnumValue<T>(this string value)
throw new Exception("T must be an enum");
if (Enum.IsDefined(t, value))
return (T)Enum.Parse(t, value);
public static T GetValueFromDescription<T>(this string description, bool throwException = true)
foreach (var field in typeof(T).GetFields())
if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))is DescriptionAttribute attribute)
if (attribute.Description == description)
return (T)field.GetValue(null);
if (field.Name == description)
return (T)field.GetValue(null);
throw new ArgumentException("Not found.", nameof(description));
public static string GetDescription(this Enum value)
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
public static string GetDescription(this object e, Type enumType)
var fieldValue = enumType.GetField(Enum.GetValues(enumType).Cast<object>().Where(o => o.ToString() == e.ToString()).Select(o => o.ToString()).FirstOrDefault()).ToString();
FieldInfo field = enumType.GetField(fieldValue.Contains(" ") ? fieldValue.Split().Last() : fieldValue);
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? e.ToString() : attribute.Description;
public static Dictionary<int, TValue> EnumToDictionary<TValue>(this TValue value)
where TValue : System.Enum
Type enumType = typeof(TValue);
var allEnumValues = enumType.GetEnumValues().Cast<TValue>();
var enumDL = new Dictionary<int, TValue>();
foreach (TValue val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static Dictionary<int, Enum> EnumToDictionaryNotGeneric(this Enum @enum)
Type enumType = @enum.GetType();
var allEnumValues = Enum.GetValues(@enum.GetType()).Cast<Enum>();
var enumDL = new Dictionary<int, Enum>();
foreach (var val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static Dictionary<int, T> EnumToDictionary<T>(this Type enumType)
var allEnumValues = enumType.GetEnumValues().Cast<T>();
var converter = new EnumConverter(enumType);
var enumDL = new Dictionary<int, T>();
foreach (T val in allEnumValues.ToList())
enumDL.Add(val.EnumToInt(), val);
public static IReadOnlyList<T> GetValues<T>()
return (T[])Enum.GetValues(typeof(T));
public static void ThrowIfNull(object param)
var props = param.GetType().GetProperties();
foreach (var prop in props)
var val = prop.GetValue(param, null);
_ = val ?? throw new ArgumentNullException(name);
public static void ThrowIfNull(object argumentValue, string argumentName)
if (argumentValue == null)
throw new ArgumentNullException(argumentName);
public static void ThrowIfStringNullOrEmptyOrNotString(object argumentValue, string argumentName)
throw new ArgumentNullException(argumentName);
case string { Length: 0 }:
throw new ArgumentOutOfRangeException(argumentName);
if (argumentValue is not string)
throw new ArgumentException(argumentName);
private readonly bool _fromFixString;
private readonly Lazy<List<TypeOfDoc>> _deserializeObject;
private SourceJSON(bool fromFixString)
_fromFixString = fromFixString;
_deserializeObject = GetAllTypeOfDocs();
public static SourceJSON CreateInstance(bool fromFixString)
return new SourceJSON(fromFixString);
public IEnumerable<string> GetWordsThatPointsToType(string[] criteria)
var wordsThatPointToTypes = new List<string>();
if (!_deserializeObject.IsValueCreated) return wordsThatPointToTypes;
foreach (var typeOfDoc in _deserializeObject.Value!)
if (criteria.Contains(typeOfDoc.DisplayName))
wordsThatPointToTypes.AddRange(
typeOfDoc.WordsThatPointsToType.ToList());
return wordsThatPointToTypes;
private Lazy<List<TypeOfDoc>> GetAllTypeOfDocs()
var json = _fromFixString ? TypeOfPatientDocKeyWordsFixed : TypeOfPatientDocKeyWords().Value;
var deserializeObject = JsonConvert.DeserializeObject<List<TypeOfDoc>>(json);
return new Lazy<List<TypeOfDoc>>(() => deserializeObject);
private const string TypeOfPatientDocKeyWordsFixed = "[{\"DisplayName\":\"Exames Laboratoriais\",\"WordsThatPointsToType\":[\"Revisão\",\"laboratorial\",\"laboratório\",\"fatores de risco\",\"hemograma\",\"sangue\",\"urina\",\"fezes\"]},{\"DisplayName\":\"Exames de Imagem\",\"WordsThatPointsToType\":[\"tomografia\",\"ressonância\",\"cintilografia\",\"RM\",\"TC\",\"Duplex\",\"Densitometria\",\"carótidas\",\"ecocardiograma\",\"ecocardiografia\",\"eco\",\"Tomossíntese\",\"US\",\"Ultrassonografia\",\"Ultra-sonografia\"]},{\"DisplayName\":\"Exames Especiais\",\"WordsThatPointsToType\":[\"polissonografia\",\"Mapeamento retina\",\"Campo visual\",\"fundoscopia\",\"EDA\",\"colonoscopia\",\"Holter\",\"MAPA\",\"ergometria\",\"prova de esforço\",\"prova de função respiratória\",\"ergoespirometria\",\"Escore de cálcio\",\"ergometria\",\"espirometria\",\"EMNG\"]},{\"DisplayName\":\"ECG\",\"WordsThatPointsToType\":[\"eletrocardiograma\",\"ECG\",\"Pulsebit\",\"HeartCheck Report\"]},{\"DisplayName\":\"Exames outros em geral\",\"WordsThatPointsToType\":[\"exame\",\"notas e exames\"]},{\"DisplayName\":\"Receituário\",\"WordsThatPointsToType\":[\"Receituário\",\"Receita\"]},{\"DisplayName\":\"Receita de Controlados\",\"WordsThatPointsToType\":[\"RECEITUÁRIO CONTROLE ESPECIAL\"]},{\"DisplayName\":\"Receita de Antibióticos\",\"WordsThatPointsToType\":[\"AMICACINA\",\"AMOXICILINA ;CLAVULANATO DE POTÁSSIO\",\"AMOXICILINA\",\"AMOXICILINA;SULBACTAM\",\"AMPICILINA\",\"ANFOTERICINA B\",\"ANIDULAFUNGINA\",\"AXETIL CEFUROXIMA\",\"AXETILCEFUROXIMA\",\"AZITROMICINA\",\"AZTREONAM\",\"CASPOFUNGINA\",\"CEFACLOR\",\"CEFADROXILA\",\"CEFALEXINA\",\"CEFUROXIMA\",\"CETOCONAZOL\",\"CILASTATINA;IMIPENÉM\",\"CLAVULANATO DE POTÁSSIO;AMOXICILINA\",\"CLAVULANATO DE POTÁSSIO;ÁCIDO CLAVULÂNICO;AMOXICILINA\",\"CLORANFENICOL\",\"COLISTINA\",\"Ciprofloxacina\",\"Ciprofloxacino\",\"Cipro\",\"DAPTOMICINA\",\"DI ETAMBUTOL\",\"DOXICICLINA\",\"DOXICICLINA\",\"ERTAPENÉM\",\"ESTREPTOMICINA\",\"ETIONAMIDA\",\"FLUCONAZOL\",\"FOSFOMICINA\",\"FOSFOMICINA TROMETAMOL\",\"GENTAMICINA\",\"IMIPENÉM\",\"IMIPENÉM;CILASTATINA\",\"ISAVUCONAZÔNIO\",\"ISONIAZIDA\",\"ITRACONAZOL\",\"Levofloxacina\",\"Levofloxacino\",\"LIMECICLINA\",\"LINEZOLIDA\",\"MEROPENÉM\",\"MICAFUNGINA\",\"MINOCICLINA\",\"Metronidazol\",\"Moxifloxacina\",\"Moxifloxacino\",\"NISTATINA\",\"PIPERACILINA\",\"PIPERACILINA;TAZOBACTAM\",\"PIRAZINAMIDA\",\"POLIMIXINA B\",\"POSACONAZOL\",\"RIFAMPICINA\",\"SULBACTAM\",\"SULBACTAM;AMPICILINA\",\"SULFADIAZINA\",\"TAZOBACTAM\",\"TAZOBACTAM;PIPERACILINA\",\"TEDIZOLIDA\",\"TEICOPLANINA\",\"TERBINAFINA\",\"TETRACICLINA\",\"TIANFENICOL\",\"TIGECICLINA\",\"TOBRAMICINA\",\"VANCOMICINA\",\"VORICONAZOL\"]},{\"DisplayName\":\"Receita de Psicotrópicos\",\"WordsThatPointsToType\":[\"AGOMELATINA\",\"AMITRIPTILINA\",\"BUPROPIONA\",\"CARBONATO DE LÍTIO\",\"CITALOPRAM\",\"CLOMIPRAMINA\",\"DESVENLAFAXINA\",\"DONEPEZILA\",\"DULOXETINA\",\"ESCITALOPRAM\",\"FLUOXETINA\",\"FLUVOXAMINA\",\"IMIPRAMINA\",\"LÍTIO\",\"MAPROTILINA\",\"MEMANTINA\",\"MIRTAZAPINA\",\"MIRTAZAPINA\",\"NORTRIPTILINA\",\"PAROXETINA\",\"REBOXETINA\",\"SERTRALINA\",\"TIANEPTINA\",\"TRAZODONA\",\"VENLAFAXINA\",\"VILAZODONA\",\"VORTIOXETINA\"]},{\"DisplayName\":\"Pedido de Parecer ou Segunda Opinião\",\"WordsThatPointsToType\":[\"Parecer\"]},{\"DisplayName\":\"Encaminhamento de Paciente para Emergência, Internação ou Método Complementar\",\"WordsThatPointsToType\":[\"Encaminhamento\"]},{\"DisplayName\":\"Risco Cirúrgico\",\"WordsThatPointsToType\":[\"Risco Cirúrgico\",\"Avaliação de risco\"]},{\"DisplayName\":\"Atestados ou Dispensas\",\"WordsThatPointsToType\":[\"Atestado\",\"Dispensa\",\"Afastamento de trabalho\"]},{\"DisplayName\":\"Relatório Somente\",\"WordsThatPointsToType\":[\"Relatório\",\"Sumário de Alta\"]},{\"DisplayName\":\"Emissão de Laudo\",\"WordsThatPointsToType\":[\"Laudo\",\"Pericia\",\"Pericial\"]},{\"DisplayName\":\"Recibo somente\",\"WordsThatPointsToType\":[\"Recibo\"]},{\"DisplayName\":\"Relatório com recibo\",\"WordsThatPointsToType\":[\"Relatório com recibo\",\"Relatório e recibo\",\"Relatório + recibo\"]},{\"DisplayName\":\"Sinais de Alerta\",\"WordsThatPointsToType\":[\"Sinais de Alerta\"]},{\"DisplayName\":\"Pedido Especial\",\"WordsThatPointsToType\":[\"Pedido\",\"Solicitação\"]},{\"DisplayName\":\"Pedido de Transporte\",\"WordsThatPointsToType\":[\"Transporte\",\"Ambulância\"]},{\"DisplayName\":\"Pedido de Assistência Domiciliar ou Home-Care\",\"WordsThatPointsToType\":[\"Assistência Domiciliar\",\"Home-Care\"]},{\"DisplayName\":\"Receita em Formato Grade com aprazamento e checagem\",\"WordsThatPointsToType\":[\"Receita em Formato Grade\",\"Prescrição\"]},{\"DisplayName\":\"Grade de sinais vitais\",\"WordsThatPointsToType\":[\"Grade de sinais vitais\",\"Grade sinais\"]},{\"DisplayName\":\"Pendências para próxima consulta\",\"WordsThatPointsToType\":[\"Pendências\",\"Trazer próxima consulta\"]},{\"DisplayName\":\"Minhas Anotações\",\"WordsThatPointsToType\":[\"Anotação Outlook\",\"Anotação\",\"Consulta n\",\"Consulta em\",\"Evolução\"]}]";
public Lazy<string> TypeOfPatientDocKeyWords() => new(GetTypeOfPatientDocKeyWords);
private string GetTypeOfPatientDocKeyWords()
var patientsSourceFolder = "";
using var sr = new StreamReader(docKeyWords);
Console.WriteLine($"Error reading TypeOfPatientDocKeyWords.json at {nameof(GetTypeOfPatientDocKeyWords)}");