Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PhoneNumberSchemes.cs
1using System.Xml;
3using Waher.Events;
4using Waher.Script;
5
7{
11 public static class PhoneNumberSchemes
12 {
13 private static readonly Dictionary<string, LinkedList<PhoneNumberScheme>> schemesByCode = [];
14 private static LinkedList<PhoneNumberScheme>? defaultSchemes; // country="*"
15
16 private static void LazyLoad()
17 {
18 if (defaultSchemes is not null || schemesByCode.Count > 0)
19 return;
20
21 try
22 {
23 using MemoryStream Ms = new(Waher.Runtime.IO.Resources.LoadResource(
24 typeof(PhoneNumberSchemes).Namespace + "." + typeof(PhoneNumberSchemes).Name + ".xml"));
25
26 XmlDocument Doc = new();
27 Doc.Load(Ms);
28
29 XmlNodeList? ChildNodes = Doc.DocumentElement?.ChildNodes;
30
31 if (ChildNodes is null)
32 return;
33
34 foreach (XmlNode N in ChildNodes)
35 {
36 if (N is XmlElement E && E.LocalName == "Entry")
37 {
38 string Country = XML.Attribute(E, "country");
39 string DisplayString = XML.Attribute(E, "displayString");
40 string? Variable = null;
41 Expression? Pattern = null;
42 Expression? Check = null;
43 Expression? Normalize = null;
44
45 try
46 {
47 foreach (XmlNode N2 in E.ChildNodes)
48 {
49 if (N2 is XmlElement E2)
50 {
51 switch (E2.LocalName)
52 {
53 case "Pattern":
54 Pattern = new Expression(E2.InnerText);
55 Variable = XML.Attribute(E2, "variable");
56 break;
57
58 case "Check":
59 Check = new Expression(E2.InnerText);
60 break;
61
62 case "Normalize":
63 Normalize = new Expression(E2.InnerText);
64 break;
65 }
66 }
67 }
68 }
69 catch
70 {
71 continue;
72 }
73
74 if (Pattern is null || string.IsNullOrWhiteSpace(Variable) || string.IsNullOrWhiteSpace(DisplayString))
75 continue;
76
77 PhoneNumberScheme Scheme = new(Variable, DisplayString, Pattern, Check, Normalize);
78
79 if (Country == "*")
80 {
81 defaultSchemes ??= new LinkedList<PhoneNumberScheme>();
82 defaultSchemes.AddLast(Scheme);
83 }
84 else
85 {
86 if (!schemesByCode.TryGetValue(Country, out LinkedList<PhoneNumberScheme>? List))
87 {
88 List = new LinkedList<PhoneNumberScheme>();
89 schemesByCode[Country] = List;
90 }
91 List.AddLast(Scheme);
92 }
93 }
94 }
95 }
96 catch (Exception ex)
97 {
98 Log.Exception(ex);
99 }
100 }
101
107 public static async Task<PhoneNumberInformation> ValidateAndNormalize(string? countryCode, string rawNumber)
108 {
109 LazyLoad();
110
111 async Task<PhoneNumberInformation?> TrySchemes(LinkedList<PhoneNumberScheme>? schemes)
112 {
113 if (schemes is null)
114 return null;
115
116 foreach (var scheme in schemes)
117 {
118 var info = await scheme.Validate(rawNumber);
119 if (info.IsValid.HasValue)
120 {
121 info.DisplayString = scheme.DisplayString;
122 return info;
123 }
124 }
125 return null;
126 }
127
128 // 1) Try country-specific
129 if (!string.IsNullOrWhiteSpace(countryCode) &&
130 schemesByCode.TryGetValue(countryCode!, out var byCountry))
131 {
132 var hit = await TrySchemes(byCountry);
133 if (hit is not null)
134 return hit;
135 }
136
137 // 2) Try generic fallback(s)
138 {
139 var hit = await TrySchemes(defaultSchemes);
140 if (hit is not null)
141 return hit;
142 }
143
144 // 3) If we had country-specific but it failed => invalid; otherwise unknown
145 if (!string.IsNullOrWhiteSpace(countryCode) && schemesByCode.ContainsKey(countryCode!))
146 {
147 return new PhoneNumberInformation
148 {
149 Original = rawNumber,
150 DisplayString = string.Empty,
151 IsValid = false
152 };
153 }
154
155 return new PhoneNumberInformation
156 {
157 Original = rawNumber,
158 DisplayString = string.Empty,
159 IsValid = null
160 };
161 }
162
166 public static string? DisplayStringForCountry(string countryCode)
167 {
168 LazyLoad();
169 if (!string.IsNullOrWhiteSpace(countryCode) &&
170 schemesByCode.TryGetValue(countryCode, out var list))
171 {
172 return list?.First?.Value?.DisplayString;
173 }
174 return null;
175 }
176 }
177
178 #region Support types (mirrors your PersonalNumberScheme/NumberInformation style)
179 public sealed class PhoneNumberScheme
180 {
181 public string Variable { get; }
182 public string DisplayString { get; }
183 private readonly Expression pattern;
184 private readonly Expression? check;
185 private readonly Expression? normalize;
186
187 public PhoneNumberScheme(string variable, string displayString, Expression pattern, Expression? check, Expression? normalize)
188 {
189 this.Variable = variable;
190 this.DisplayString = displayString;
191 this.pattern = pattern;
192 this.check = check;
193 this.normalize = normalize;
194 }
195
196 public async Task<PhoneNumberInformation> Validate(string input)
197 {
198 try
199 {
200 // Setup script variables
201 Variables v = new();
202 v[this.Variable] = input;
203
204 // 1) Pattern must evaluate without exception; otherwise "not for me"
205 var patternResult = await this.pattern.EvaluateAsync(v);
206 // The Waher.Script regex "like" with named groups will populate *_STR vars into 'v'
207 // If it doesn't match, no IsValid is set -> continue to next scheme
208 if (patternResult is not bool matched || !matched)
209 return new PhoneNumberInformation { Original = input, IsValid = null };
210
211 // 2) If Check exists, it must be true
212 if (this.check is not null)
213 {
214 var checkResult = await this.check.EvaluateAsync(v);
215 if (checkResult is not bool ok || !ok)
216 return new PhoneNumberInformation { Original = input, IsValid = false };
217 }
218
219 // 3) Normalize if available; else use original
220 string normalized = input;
221 if (this.normalize is not null)
222 {
223 var norm = await this.normalize.EvaluateAsync(v);
224 if (norm is string s)
225 normalized = s;
226 }
227
228 return new PhoneNumberInformation
229 {
230 Original = input,
231 Normalized = normalized,
232 IsValid = true
233 };
234 }
235 catch (Exception)
236 {
237 // If the scheme throws, skip it silently (same pattern as your personal numbers)
238 return new PhoneNumberInformation { Original = input, IsValid = null };
239 }
240 }
241 }
242
243 public sealed class PhoneNumberInformation
244 {
245 public string? Original { get; set; }
246 public string? Normalized { get; set; }
247 public string? DisplayString { get; set; }
251 public bool? IsValid { get; set; }
252 }
253 #endregion
254}
bool? IsValid
True = valid (and Normalized set), False = invalid, Null = unknown/not applicable for this scheme set
Phone Number Schemes with country-specific pattern/check/normalize, E.164-first.
static ? string DisplayStringForCountry(string countryCode)
Gets a human-friendly example format for a country (first scheme display string).
static async Task< PhoneNumberInformation > ValidateAndNormalize(string? countryCode, string rawNumber)
Validates and normalizes a phone number according to configured schemes.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:13
static byte[] LoadResource(string ResourceName)
Loads a resource from an embedded resource.
Definition: Resources.cs:20
Class managing a script expression.
Definition: Expression.cs:41
Contains information about a variable.
Definition: Variable.cs:10
Collection of variables.
Definition: Variables.cs:25
Definition: App.xaml.cs:4