using System.Text.RegularExpressions; namespace AMREZ.EOP.Domain.Shared.Data; using System.Text.RegularExpressions; public record ParsedAddress( string Name, string Phone, string AddressOnly, // ที่อยู่เต็ม ไม่รวมชื่อ/เบอร์ string AddressMain, // ส่วนต้นของที่อยู่ เช่น เลขที่ / หมู่ / ซอย / ถนน string Subdistrict, // ตำบล / แขวง string District, // อำเภอ / เขต string Province, // จังหวัด string PostalCode // รหัสไปรษณีย์ 5 หลัก ); public static class ThaiAddressParser { public static ParsedAddress Parse(string? raw) { if (string.IsNullOrWhiteSpace(raw)) { return new ParsedAddress("", "", "", "", "", "", "", ""); } var original = raw; // normalize space + เอา emoji / สัญลักษณ์กวน ๆ ออก var sNorm = Regex.Replace(original, "[ \\t]+", " "); sNorm = sNorm .Replace("🏠", " ") .Replace("☎️", " ") .Replace("📍", " "); sNorm = Regex.Replace(sNorm.Trim(), "\\s*\\n\\s*", "\n"); // ====================== PHONE ====================== string? phoneDigits = null; var phoneMatches = Regex.Matches(original.Replace("-", ""), @"0\d{8,9}"); if (phoneMatches.Count > 0) { // ส่วนใหญ่เบอร์จะอยู่ท้าย เอาอันสุดท้าย phoneDigits = phoneMatches[^1].Value; } var sWithoutPhone = sNorm; if (!string.IsNullOrWhiteSpace(phoneDigits)) { // pattern ยอมให้มี "-" หรือ space คั่นตัวเลขแต่ละตัว var phoneChars = phoneDigits.ToCharArray(); var phonePattern = string.Join("[- ]?", phoneChars); var phoneRegex = new Regex(phonePattern); sWithoutPhone = phoneRegex.Replace(sWithoutPhone, ""); // ตัดคำ "โทร", "Tel", "เบอร์" ที่ติดกับเบอร์ออก sWithoutPhone = Regex.Replace( sWithoutPhone, @"(โทร\.?|Tel\.?|tel\.?|เบอร์|[Tt]el ?:)\s*", "", RegexOptions.CultureInvariant ); } // ====================== NAME ====================== var lines = sWithoutPhone .Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var firstLine = lines.Length > 0 ? lines[0] : string.Empty; // หา index สิ้นสุดชื่อ (ก่อนเจอเลขหรือคำว่า "ที่อยู่"/"ร้าน") var nameEndIndex = firstLine.Length; var digitMatch = Regex.Match(firstLine, @"\d"); if (digitMatch.Success && digitMatch.Index < nameEndIndex) { nameEndIndex = digitMatch.Index; } var addrWordMatch = Regex.Match(firstLine, "(ที่อยู่|ร้าน)"); if (addrWordMatch.Success && addrWordMatch.Index < nameEndIndex) { nameEndIndex = addrWordMatch.Index; } var name = firstLine[..nameEndIndex].Trim(' ', ':', '-', '(', ')'); // ถ้ามีวงเล็บท้ายชื่อ และไม่มีเลขข้างใน ตัดออก name = Regex.Replace(name, @"\([^0-9)]*\)$", "").Trim(); // ====================== ADDRESS TEXT (เต็ม) ====================== var allText = string.Join(" ", lines); var addrText = allText; if (!string.IsNullOrWhiteSpace(name) && addrText.StartsWith(name)) { addrText = addrText[name.Length..].Trim(' ', ',', ':', '-'); } addrText = Regex.Replace(addrText, @"\s{2,}", " "); // เคลียร์วงเล็บเปล่า ๆ "()" ที่บางแถวมี addrText = Regex.Replace(addrText, @"\(\s*\)", "").Trim(); // ====================== POSTAL CODE ====================== string postal = ""; var postalMatch = Regex.Match(addrText, @"(\d{5})(?!.*\d{5})"); if (postalMatch.Success) { postal = postalMatch.Groups[1].Value; } // helper เลือกกลุ่มแรกที่ไม่ว่าง static string Pick(params Group[] groups) { foreach (var g in groups) { if (g.Success && !string.IsNullOrWhiteSpace(g.Value)) return g.Value.Trim(); } return ""; } // ====================== SUBDISTRICT (ตำบล/แขวง) ====================== string subdistrict = ""; var subMatch = Regex.Match( addrText, @"ต\. ?(?[^ \d]+)|ตำบล ?(?[^ \d]+)|แขวง ?(?[^ \d]+)" ); if (subMatch.Success) { subdistrict = Pick( subMatch.Groups["t1"], subMatch.Groups["t2"], subMatch.Groups["t3"] ); } // ====================== DISTRICT (อำเภอ/เขต) ====================== string district = ""; var distMatch = Regex.Match( addrText, @"อ\. ?(?[^ \d]+)|อำเภอ ?(?[^ \d]+)|เขต ?(?[^ \d]+)" ); if (distMatch.Success) { district = Pick( distMatch.Groups["d1"], distMatch.Groups["d2"], distMatch.Groups["d3"] ); } // ====================== PROVINCE ====================== string province = ""; if (Regex.IsMatch(addrText, @"กรุงเทพมหานคร|กรุงเทพฯ|กทม\.?|กรุงเทพ(?![กษ])")) { province = "กรุงเทพมหานคร"; } else { var provMatch = Regex.Match( addrText, @"จ\. ?(?[^0-9 ]+)|จังหวัด ?(?[^0-9 ]+)" ); if (provMatch.Success) { province = Pick( provMatch.Groups["p1"], provMatch.Groups["p2"] ); } } // special rule: ถ้าอำเภอ = "เมือง" แล้วมีจังหวัด ให้กลายเป็น "เมือง{จังหวัด}" // เช่น เมือง + ลำพูน = เมืองลำพูน if (district == "เมือง" && !string.IsNullOrWhiteSpace(province)) { district = $"เมือง{province}"; } // ====================== ADDRESS MAIN ====================== var mainAddress = addrText; // ลบรหัสไปรษณีย์ออกจาก address main if (!string.IsNullOrEmpty(postal)) { mainAddress = mainAddress.Replace(postal, "").Trim(); } // ลบจังหวัดออก mainAddress = Regex.Replace( mainAddress, @"(กรุงเทพมหานคร|กรุงเทพฯ|กทม\.?|กรุงเทพ(?![กษ])|จ\. ?[^0-9 ]+|จังหวัด ?[^0-9 ]+)", "", RegexOptions.CultureInvariant ); // ลบตำบล/แขวง + อำเภอ/เขต ออกจาก address main mainAddress = Regex.Replace( mainAddress, @"(ต\. ?[^ \d]+|ตำบล ?[^ \d]+|แขวง ?[^ \d]+|อ\. ?[^ \d]+|อำเภอ ?[^ \d]+|เขต ?[^ \d]+)", "", RegexOptions.CultureInvariant ); // ลบ "กรุงเทพ / กทม" ที่ยังหลุดท้าย ๆ อีกที กันเคสแบบ "กรุงเทพ ." mainAddress = Regex.Replace( mainAddress, @"(กรุงเทพมหานคร|กรุงเทพฯ|กรุงเทพ|กทม\.?)\s*\.?", "", RegexOptions.CultureInvariant ); // เคลียร์ space / comma ซ้ำ mainAddress = Regex.Replace(mainAddress, @"\s{2,}", " "); mainAddress = mainAddress.Trim(' ', ',', '.'); return new ParsedAddress( Name: name, Phone: phoneDigits ?? "", AddressOnly: addrText, AddressMain: mainAddress, Subdistrict: subdistrict, District: district, Province: province, PostalCode: postal ); } }