static void AssertAge(TitanicPassenger pax) { if (!pax.AgeMonths.HasValue || !pax.BirthDate.HasValue) { return; } int age1 = pax.AgeMonths.Value; int age2 = pax.BirthDate.Value.GetAgeMonths(Titanic.SunkDate); if (age1 == age2) { return; } throw new ModelValidationException($"Age mismatch: '{age1}' ≠ '{age2}'"); }
static List <TitanicPassenger> ParsePassengerListings(string zipPath) { List <TitanicPassenger> passengers = new List <TitanicPassenger>(); using (var fstream = File.OpenRead(zipPath)) using (var zip = new ZipArchive(fstream, ZipArchiveMode.Read)) { foreach (var zipEntry in zip.Entries.Where(e => e.IsRootFile())) { string html = zipEntry.ReadContentAsString(); IHtmlDocument htmlDoc = new HtmlParser().ParseDocument(html); foreach (var tr in htmlDoc.QuerySelector("#manifest").QuerySelectorAll("tr")) { var pax = new TitanicPassenger(); var cells = tr.QuerySelectorAll("td").ToArray(); if (cells.Length == 0) { continue; } pax.Url = cells[0].QuerySelector("a[itemprop=\"url\"]").GetAttribute("href"); pax.Id = pax.Url.Trim('/').Split('/')[1].ToLower().Replace(".html", ""); pax.HasSurvived = !pax.Url.StartsWith("/titanic-victim/"); pax.FamilyName = cells[0].QuerySelector("*[itemprop=\"familyName\"]").TextContent; pax.GivenName = cells[0].QuerySelector("*[itemprop=\"givenName\"]").TextContent; pax.HonorificPrefix = cells[0].QuerySelector("*[itemprop=\"honorificPrefix\"]").TextContent; if (pax.HonorificPrefix.AnyOf("Doña", "Miss", "Mlle", "Mme.", "Ms", "Mrs", "Sra.")) { pax.Sex = Sex.Female; } else if (pax.HonorificPrefix.AnyOf("Captain", "Col.", "Colonel", "Don.", "Dr", "Fr", "Major", "Master", "Mr", "Rev.", "Revd", "Sir", "Sr.")) { pax.Sex = Sex.Male; } else { if (pax.FullName.EndsWith(", Lady") || pax.FullName.EndsWith(", Countess of")) { pax.Sex = Sex.Female; } } // Исключение if (pax.HonorificPrefix == "Dr" && pax.FullName == "Dr LEADER, Alice May") { pax.Sex = Sex.Female; } string ageText = cells[1].TextContent.Trim(); pax.AgeMonths = ageText.Length == 0 ? null : ageText.EndsWith("m") ? (int?)int.Parse(ageText.TrimEnd('m')) : (int?)12 * int.Parse(ageText); string classText = cells[2].TextContent; if (classText.Contains("1st Class Passenger")) { pax.Class = Class.First; } else if (classText.Contains("2nd Class Passenger")) { pax.Class = Class.Second; } else if (classText.Contains("3rd Class Passenger")) { pax.Class = Class.Third; } else if (classText.Contains("Deck Crew")) { pax.Class = Class.DeckCrew; } else if (classText.Contains("Engineering Crew")) { pax.Class = Class.EngineeringCrew; } else if (classText.Contains("Victualling Crew")) { pax.Class = Class.VictuallingCrew; } pax.IsGuaranteeGroupMember = classText.Contains("H&W Guarantee Group"); if (classText.Contains("Servant")) { pax.IsServant = true; } pax.TicketNo = cells[3].InnerHtml.Split("<br>")[0].Trim(); pax.TicketPrice = new Price(cells[3].InnerHtml.Split("<br>")[1].Trim()); pax.Boarded = (City)Enum.Parse(typeof(City), cells[4].TextContent); pax.JobTitle = cells[5].TextContent.Replace(" ", "").Trim(); if (pax.JobTitle.Length == 0) { //if (classText.Contains("Servant")) pax.Job = "Servant"; } string lifeboatText = cells[6].TextContent.Trim(); pax.Lifeboat = lifeboatText.Length > 0 && !lifeboatText.Contains("[") ? lifeboatText : null; passengers.Add(pax); } } } return(passengers); }