3using System.Collections.ObjectModel;
4using System.Globalization;
6using System.Threading.Tasks;
29 string XmlNormalized = StripNamespaces(Xml);
30 XDocument Doc = XDocument.Parse(XmlNormalized);
33 if (Doc.Root is not
null)
35 Process.Name = ParseLocalizedText(Doc.Root.Element(
"Name"));
38 foreach (XElement PageEl
in Doc.Root?.Elements(
"Page") ?? Enumerable.Empty<XElement>())
42 Id = (
string?)PageEl.Attribute(
"id") ??
string.Empty,
43 Title = ParseLocalizedText(PageEl.Element(
"Title")),
44 Description = ParseLocalizedText(PageEl.Element(
"Description")),
45 Condition = ParseCondition(PageEl.Element(
"Condition"))
49 foreach (XElement FieldEl
in PageEl.Elements(
"Field"))
52 Page.AllFields.Add(Field);
56 foreach (XElement SectionEl
in PageEl.Elements(
"Section"))
60 Label = ParseLocalizedText(SectionEl.Element(
"Label")),
61 Description = ParseLocalizedText(SectionEl.Element(
"Description"))
64 foreach (XElement FieldEl
in SectionEl.Elements(
"Field"))
73 Process.Pages.Add(Page);
78 return Task.FromResult(Process);
85 private static string StripNamespaces(
string Xml)
89 System.Text.RegularExpressions.Regex R =
new System.Text.RegularExpressions.Regex(
"\\sxmlns(:[A-Za-z0-9_\\-\\.]+)?=\"[^\"]*\"",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
90 return R.Replace(Xml,
string.Empty);
101 string TypeAttr = (
string?)El.Attribute(
"type") ??
"text";
123 Field.Id = (
string?)El.Attribute(
"id") ??
string.Empty;
125 Field.Required = (
bool?)El.Attribute(
"required") ??
false;
126 Field.Label = ParseLocalizedText(El.Element(
"Label"));
127 Field.Placeholder = ParseLocalizedText(El.Element(
"Placeholder"));
128 Field.Hint = ParseLocalizedText(El.Element(
"Hint"));
129 Field.Description = ParseLocalizedText(El.Element(
"Description"));
130 Field.SpecialType = (
string?)El.Attribute(
"specialType");
131 Field.Condition = ParseCondition(El.Element(
"Condition"));
134 if (El.Element(
"Metadata") is XElement MetadataEl)
136 foreach (XElement MetaChild
in MetadataEl.Elements())
138 string Key = MetaChild.Name.LocalName;
139 string Value = MetaChild.Value?.Trim() ??
string.Empty;
142 if (
int.TryParse(Value, out
int IntVal))
143 Field.Metadata[Key] = IntVal;
144 else if (
double.TryParse(Value, NumberStyles.Any, CultureInfo.InvariantCulture, out
double DoubleVal))
145 Field.Metadata[Key] = DoubleVal;
146 else if (
bool.TryParse(Value, out
bool BoolVal))
147 Field.Metadata[Key] = BoolVal;
148 else if (Key.Equals(
"AllowedFileTypes", StringComparison.OrdinalIgnoreCase))
149 Field.Metadata[Key] = Value.Split(
',',
';',
' ').Where(s => !
string.IsNullOrWhiteSpace(s)).ToArray();
151 Field.Metadata[Key] = Value;
155 if (Field is
ObservableImageField ImageField && Key.Equals(
"AllowUpload", StringComparison.OrdinalIgnoreCase) &&
bool.TryParse(Value, out
bool ParsedBool))
156 ImageField.AllowUpload = ParsedBool;
159 if (Key.Equals(
"Placeholder", StringComparison.OrdinalIgnoreCase) && Value.Equals(
"pnr", StringComparison.OrdinalIgnoreCase))
161 string CountryCode = Owner.
Values.TryGetValue(
"country", out
string? Cc) && !
string.IsNullOrEmpty(Cc) ? Cc :
string.Empty;
162 if (
string.IsNullOrEmpty(CountryCode))
174 if (!
string.IsNullOrEmpty(CountryCode) && !
string.IsNullOrEmpty(PnrExample))
177 Field.Placeholder.Add(CountryCode, PnrExample);
184 Field.SetOwnerProcess(Owner);
187 void TryAddLengthRules(XElement RuleEl)
189 int? Min =
null, Max =
null;
190 if (
int.TryParse((
string?)RuleEl.Attribute(
"min"), out
int MinVal)) Min = MinVal;
191 if (
int.TryParse((
string?)RuleEl.Attribute(
"max"), out
int MaxVal)) Max = MaxVal;
192 if (Min.HasValue || Max.HasValue)
194 string? Msg = ParseLocalizedText(RuleEl.Element(
"Message"))?.
Get(Lang);
199 void TryAddRegexRule(XElement RuleEl)
201 string? Pattern = RuleEl.Element(
"Regex")?.Value?.Trim();
202 if (!
string.IsNullOrEmpty(Pattern))
204 string? Msg = ParseLocalizedText(RuleEl.Element(
"Message"))?.
Get(Lang);
208 System.Text.RegularExpressions.Regex.Match(
string.Empty, Pattern);
209 Field.AddRule(
new RegexRule(Pattern, Msg));
218 void TryAddDateRangeRule(XElement RuleEl)
220 DateTime? DMin =
null, DMax =
null;
221 if (DateTime.TryParse((
string?)RuleEl.Attribute(
"min"), out DateTime DMinVal)) DMin = DMinVal;
222 if (DateTime.TryParse((
string?)RuleEl.Attribute(
"max"), out DateTime DMaxVal)) DMax = DMaxVal;
223 if (DMin.HasValue || DMax.HasValue)
225 string? Msg = ParseLocalizedText(RuleEl.Element(
"Message"))?.
Get(Lang);
230 void TryAddPnrRule(XElement RuleEl)
232 string? FieldRef = (
string?)RuleEl.Element(
"CountryFieldReference");
233 string? Msg = ParseLocalizedText(RuleEl.Element(
"Message"))?.
Get(Lang);
234 Field.AddRule(
new PersonalNumberRule(FieldRef, Msg));
237 XElement? ValidationEl = El.Element(
"ValidationRules");
238 if (ValidationEl is not
null)
240 foreach (XElement Vr
in ValidationEl.Elements())
242 switch (Vr.Name.ToString())
244 case "DateRangeRule":
245 TryAddDateRangeRule(Vr);
break;
247 TryAddRegexRule(Vr);
break;
249 TryAddLengthRules(Vr);
break;
250 case "PersonalNumberRule":
251 TryAddPnrRule(Vr);
break;
259 foreach (XElement
Map in El.Elements(
"Mapping"))
263 Key = (
string?)
Map.Attribute(
"key") ?? string.Empty
267 foreach (XElement Tx
in Map.Elements(
"Transform"))
269 string? NameAttr = (
string?)Tx.Attribute(
"name");
270 if (!
string.IsNullOrWhiteSpace(NameAttr))
272 string Clean = NameAttr.Trim();
273 if (!Mapping.
TransformNames.Any(t =>
string.Equals(t, Clean, StringComparison.OrdinalIgnoreCase)))
280 Field.Mappings.Add(Mapping);
284 bool HasPersonalNumberMapping =
false;
285 foreach (
KycMapping Mapping
in Field.Mappings)
289 HasPersonalNumberMapping =
true;
290 bool HasNormalize = Mapping.
TransformNames.Any(name =>
string.Equals(name,
"personalNumberNormalize", StringComparison.OrdinalIgnoreCase));
295 if (!HasPersonalNumberMapping &&
string.Equals(Field.Id,
"personalNumber", StringComparison.OrdinalIgnoreCase))
297 KycMapping NormalizedMapping =
new KycMapping { Key = Constants.XmppProperties.PersonalNumber };
299 Field.Mappings.Add(NormalizedMapping);
303 if (El.Element(
"Options") is XElement Opts)
305 foreach (XElement Opt
in Opts.Elements(
"Option"))
307 string Val = (
string?)Opt.Attribute(
"value") ??
string.Empty;
309 Field.Options.Add(
new KycOption(Val, Lbl));
316 string DefaultCountry;
324 DefaultCountry =
string.Empty;
328 if (CountryField.Options.Count == 0)
333 LocalizedText.
Add(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, Country.FlagAndName);
335 if (Country.Alpha2.Equals(DefaultCountry, StringComparison.OrdinalIgnoreCase))
337 Field.Options.Insert(0,
new KycOption(Country.Alpha2, LocalizedText));
341 Field.Options.Add(
new KycOption(Country.Alpha2, LocalizedText));
346 if (
string.Equals(Field.Id,
"country", StringComparison.OrdinalIgnoreCase))
348 string ExistingCountry = Owner.
Values.TryGetValue(Field.Id, out
string? StoredCountry) && !
string.IsNullOrEmpty(StoredCountry)
352 if (
string.IsNullOrEmpty(ExistingCountry))
354 string SeedCountry =
string.Empty;
358 if (!
string.IsNullOrEmpty(SelectedCountry))
359 SeedCountry = SelectedCountry;
362 string? IdentityCountry =
ServiceRef.
TagProfile.LegalIdentity?.GetPersonalInformation().Country;
363 SeedCountry =
string.IsNullOrEmpty(IdentityCountry) ? string.Empty : IdentityCountry;
368 SeedCountry =
string.Empty;
371 if (!
string.IsNullOrEmpty(SeedCountry))
373 Owner.
Values[Field.Id] = SeedCountry;
374 Field.StringValue = SeedCountry;
393 string ? Def = El.Element(
"Default")?.Value?.Trim();
394 if (!
string.IsNullOrEmpty(Def))
399 if (Def.Equals(
"now()", StringComparison.OrdinalIgnoreCase))
400 DateField.DateValue = DateTime.Today;
401 else if (DateTime.TryParse(Def, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime
Dt))
402 DateField.DateValue =
Dt;
405 if (
bool.TryParse(Def, out
bool Bv))
406 BoolField.BoolValue = Bv;
409 if (
int.TryParse(Def, NumberStyles.Integer, CultureInfo.InvariantCulture, out
int Iv))
410 IntField.IntValue = Iv;
413 if (decimal.TryParse(Def, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal Dv))
414 DecField.DecimalValue = Dv;
417 Field.StringValue = Def;
425 private static KycCondition? ParseCondition(XElement? Cond)
427 if (Cond is
null)
return null;
430 FieldRef = Cond.Element(
"FieldRef")?.Value ??
string.Empty,
431 Equals = Cond.Element(
"Equals")?.Value
437 if (Parent is
null)
return null;
439 foreach (XElement Txt
in Parent.Elements(
"Text"))
441 string Lang = (
string?)Txt.Attribute(
"lang") ??
"en";
442 string? Val = Txt.Value?.Trim();
443 if (!
string.IsNullOrEmpty(Val)) Loc.
Add(Lang, Val);
445 if (!Loc.
HasAny && !
string.IsNullOrEmpty(Parent.Value?.Trim()))
446 Loc.
Add(
"en", Parent.Value.Trim());
447 return Loc.HasAny ? Loc :
null;
XMPP Protocol Properties.
const string PersonalNumber
Personal number
A set of never changing property constants and helpful values.
Conversion between Country Names and ISO-3166-1 country codes.
static ISO_3166_Country[] Countries
This collection built from Wikipedia entry on ISO3166-1 on 9th Feb 2016
Static class containing ISO 5218 gender codes
static readonly ISO_5218_Gender[] Genders
Available gender codes
Personal Number Schemes available in different countries.
static ? string DisplayStringForCountry(string CountryCode)
Gets the expected personal number format for the given country.
Parses an XML-described KYC process into view-model objects.
static Task< KycProcess > LoadProcessAsync(string Xml, string? Lang=null)
Parses the KYC pages from an XML string.
Validates if date input is within a range.
Represents a set of localized strings, addressable by language code (e.g. "en", "sv").
string? Get(string? Lang)
Gets a localized value for a specific language.
void Add(string Lang, string Value)
Adds or replaces a localized text value.
bool HasAny
Gets a value indicating whether any localized strings have been added.
Mapping from a field value to an identity property, including optional transform pipeline.
List< string > TransformNames
Ordered list of transform identifiers to apply. Empty means no transforms.
string Key
Target identity property key.
Represents a page in a KYC process containing fields and sections.
Represents a parsed KYC process with pages, fields, and current values.
IDictionary< string, string?> Values
Gets a modifiable dictionary of field values keyed by field identifier.
Validates text length for string fields.
Validates string input against a regex.
Boolean field (true/false)
Checkbox field (multi-selection)
Generic field for text, phone, email, etc.
Label/info field (non-input)
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
FieldType FieldType
The field type (input, picker, info, etc).
Label/info field (non-input)
Picker field (single option selection)
Radio field (single option selection)
Base class that references services in the app.
static ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
class CountryCode(ScriptNode Argument, int Start, int Length, Expression Expression)
Looks up a Country Name and returns the corresponding Country Code.
class ISO_3166_Country(string Name, string Alpha2, string Alpha3, int NumericCode, string DialCode, EmojiInfo? EmojiInfo=null)
Representation of an ISO3166-1 Country
class ISO_5218_Gender(string Gender, int Code, string Letter, string LocalizedNameId, char Unicode)
Contains one record of the ISO 5218 data set.
FieldType
Enumeration of all supported field types in the KYC process.
Gender
Gender classification in a legal ID.