/** * Sets the possible length fields in the metadata from the sets of data passed in. Checks that * the length is covered by the "parent" phone number description element if one is present, and * if the lengths are exactly the same as this, they are not filled in for efficiency reasons. * * @param parentDesc the "general description" element or null if desc is the generalDesc itself * @param desc the PhoneNumberDesc object that we are going to set lengths for */ private static void SetPossibleLengths(SortedSet<int> lengths, SortedSet<int> localOnlyLengths, PhoneNumberDesc parentDesc, PhoneNumberDesc.Builder desc) { // Only add the lengths to this sub-type if they aren't exactly the same as the possible // lengths in the general desc (for metadata size reasons). if (parentDesc == null || !ArePossibleLengthsEqual(lengths, parentDesc)) foreach (var length in lengths) if (parentDesc == null || parentDesc.PossibleLengthList.Contains(length)) desc.PossibleLengthList.Add(length); else throw new Exception( #if NET35 $"Out-of-range possible length found ({length}), parent lengths {string.Join(", ", parentDesc.PossibleLengthList.Select(x => x.ToString()).ToArray())}."); #else $"Out-of-range possible length found ({length}), parent lengths {string.Join(", ", parentDesc.PossibleLengthList)}."); #endif // We check that the local-only length isn't also a normal possible length (only relevant for // the general-desc, since within elements such as fixed-line we would throw an exception if we // saw this) before adding it to the collection of possible local-only lengths. foreach (var length in localOnlyLengths) if (!lengths.Contains(length)) if (parentDesc == null || parentDesc.PossibleLengthLocalOnlyList.Contains(length) || parentDesc.PossibleLengthList.Contains(length)) desc.PossibleLengthLocalOnlyList.Add(length); else throw new Exception( #if NET35 $"Out-of-range local-only possible length found ({length}), parent length {string.Join(", ", parentDesc.PossibleLengthLocalOnlyList.Select(x => x.ToString()).ToArray())}."); #else $"Out-of-range local-only possible length found ({length}), parent length {string.Join(", ", parentDesc.PossibleLengthLocalOnlyList)}."); #endif }
/** * Sets possible lengths in the general description, derived from certain child elements. */ // @VisibleForTesting static void SetPossibleLengthsGeneralDesc(PhoneNumberDesc.Builder generalDesc, string metadataId, XElement data, bool isShortNumberMetadata) { var lengths = new SortedSet <int>(); var localOnlyLengths = new SortedSet <int>(); // The general description node should *always* be present if metadata for other types is // present, aside from in some unit tests. // (However, for e.g. formatting metadata in PhoneNumberAlternateFormats, no PhoneNumberDesc // elements are present). var generalDescNodes = data.GetElementsByTagName(GENERAL_DESC).ToList(); if (generalDescNodes.Any()) { var generalDescNode = generalDescNodes.ElementAt(0); PopulatePossibleLengthSets(generalDescNode, lengths, localOnlyLengths); if (lengths.Count != 0 || localOnlyLengths.Count != 0) { // We shouldn't have anything specified at the "general desc" level: we are going to // calculate this ourselves from child elements. throw new Exception("Found possible lengths specified at general " + $"desc: this should be derived from child elements. Affected country: {metadataId}"); } } if (!isShortNumberMetadata) { // Make a copy here since we want to remove some nodes, but we don't want to do that on our // actual data. var allDescData = new XElement(data); foreach (var tag in PhoneNumberDescsWithoutMatchingTypes) { var nodesToRemove = allDescData.GetElementsByTagName(tag).ToList(); if (nodesToRemove.Any()) { // We check when we process phone number descriptions that there are only one of each // type, so this is safe to do. nodesToRemove.ElementAt(0).Remove(); } } PopulatePossibleLengthSets(allDescData, lengths, localOnlyLengths); } else { // For short number metadata, we want to copy the lengths from the "short code" section only. // This is because it's the more detailed validation pattern, it's not a sub-type of short // codes. The other lengths will be checked later to see that they are a sub-set of these // possible lengths. var shortCodeDescList = data.GetElementsByTagName(SHORT_CODE).ToList(); if (shortCodeDescList.Any()) { var shortCodeDesc = shortCodeDescList.ElementAt(0); PopulatePossibleLengthSets(shortCodeDesc, lengths, localOnlyLengths); } if (localOnlyLengths.Count > 0) { throw new Exception("Found local-only lengths in short-number metadata"); } } SetPossibleLengths(lengths, localOnlyLengths, null, generalDesc); }
/** * Processes a phone number description element from the XML file and returns it as a * PhoneNumberDesc. If the description element is a fixed line or mobile number, the parent * description will be used to fill in the whole element if necessary, or any components that are * missing. For all other types, the parent description will only be used to fill in missing * components if the type has a partial definition. For example, if no "tollFree" element exists, * we assume there are no toll free numbers for that locale, and return a phone number description * with "NA" for both the national and possible number patterns. * * @param generalDesc a generic phone number description that will be used to fill in missing * parts of the description * @param countryElement the XML element representing all the country information * @param numberType the name of the number type, corresponding to the appropriate tag in the XML * file with information about that type * @return complete description of that phone number type */ public static PhoneNumberDesc.Builder ProcessPhoneNumberDescElement(PhoneNumberDesc parentDesc, XElement countryElement, string numberType) { if (parentDesc == null) { parentDesc = new PhoneNumberDesc.Builder().Build(); } var phoneNumberDescList = countryElement.GetElementsByTagName(numberType).ToList(); var numberDesc = new PhoneNumberDesc.Builder(); if (phoneNumberDescList.Count == 0) { // -1 will never match a possible phone number length, so is safe to use to ensure this never // matches. We don't leave it empty, since for compression reasons, we use the empty list to // mean that the generalDesc possible lengths apply. numberDesc.AddPossibleLength(-1); return(numberDesc); } if (phoneNumberDescList.Count > 0) { if (phoneNumberDescList.Count > 1) { throw new Exception($"Multiple elements with type {numberType} found."); } var element = phoneNumberDescList[0]; if (parentDesc != null) { // New way of handling possible number lengths. We don't do this for the general // description, since these tags won't be present; instead we will calculate its values // based on the values for all the other number type descriptions (see // setPossibleLengthsGeneralDesc). var lengths = new SortedSet <int>(); var localOnlyLengths = new SortedSet <int>(); PopulatePossibleLengthSets(element, lengths, localOnlyLengths); SetPossibleLengths(lengths, new SortedSet <int>(), parentDesc, numberDesc); } var validPattern = element.GetElementsByTagName(NATIONAL_NUMBER_PATTERN).ToList(); if (validPattern.Any()) { numberDesc.SetNationalNumberPattern(ValidateRE(validPattern.First().Value, true)); } var exampleNumber = element.GetElementsByTagName(EXAMPLE_NUMBER).ToList(); if (exampleNumber.Any()) { numberDesc.SetExampleNumber(exampleNumber.First().Value); } } return(numberDesc); }
/** * Processes a phone number description element from the XML file and returns it as a * PhoneNumberDesc. If the description element is a fixed line or mobile number, the general * description will be used to fill in the whole element if necessary, or any components that are * missing. For all other types, the general description will only be used to fill in missing * components if the type has a partial definition. For example, if no "tollFree" element exists, * we assume there are no toll free numbers for that locale, and return a phone number description * with "NA" for both the national and possible number patterns. * * @param generalDesc a generic phone number description that will be used to fill in missing * parts of the description * @param countryElement the XML element representing all the country information * @param numberType the name of the number type, corresponding to the appropriate tag in the XML * file with information about that type * @return complete description of that phone number type */ public static PhoneNumberDesc ProcessPhoneNumberDescElement(PhoneNumberDesc generalDesc, XElement countryElement, String numberType, bool liteBuild) { if (generalDesc == null) { generalDesc = new PhoneNumberDesc.Builder().Build(); } var phoneNumberDescList = countryElement.GetElementsByTagName(numberType); var numberDesc = new PhoneNumberDesc.Builder(); if (phoneNumberDescList.Length == 0 && !IsValidNumberType(numberType)) { numberDesc.SetNationalNumberPattern("NA"); numberDesc.SetPossibleNumberPattern("NA"); return(numberDesc.Build()); } numberDesc.MergeFrom(generalDesc); if (phoneNumberDescList.Length > 0) { XElement element = phoneNumberDescList[0]; var possiblePattern = element.GetElementsByTagName(POSSIBLE_NUMBER_PATTERN); if (possiblePattern.Length > 0) { numberDesc.SetPossibleNumberPattern(ValidateRE(possiblePattern[0].Value, true)); } var validPattern = element.GetElementsByTagName(NATIONAL_NUMBER_PATTERN); if (validPattern.Length > 0) { numberDesc.SetNationalNumberPattern(ValidateRE(validPattern[0].Value, true)); } if (!liteBuild) { var exampleNumber = element.GetElementsByTagName(EXAMPLE_NUMBER); if (exampleNumber.Length > 0) { numberDesc.SetExampleNumber(exampleNumber[0].Value); } } } return(numberDesc.Build()); }
/** * Sets possible lengths in the general description, derived from certain child elements. */ private static void SetPossibleLengthsGeneralDesc(PhoneNumberDesc.Builder generalDesc, string metadataId, XElement data, bool isShortNumberMetadata) { var lengths = new SortedSet <int>(); var localOnlyLengths = new SortedSet <int>(); // The general description node should *always* be present if metadata for other types is // present, aside from in some unit tests. // (However, for e.g. formatting metadata in PhoneNumberAlternateFormats, no PhoneNumberDesc // elements are present). var generalDescNode = data.Element(GENERAL_DESC); if (generalDescNode != null) { PopulatePossibleLengthSets(generalDescNode.Elements(POSSIBLE_LENGTHS), lengths, localOnlyLengths); if (lengths.Count != 0 || localOnlyLengths.Count != 0) { throw new Exception("Found possible lengths specified at general " + $"desc: this should be derived from child elements. Affected country: {metadataId}"); } } if (!isShortNumberMetadata) { var allDescData = data.Descendants(POSSIBLE_LENGTHS).Where(e => e.Parent.Name != NO_INTERNATIONAL_DIALLING); PopulatePossibleLengthSets(allDescData, lengths, localOnlyLengths); } else { // For short number metadata, we want to copy the lengths from the "short code" section only. // This is because it's the more detailed validation pattern, it's not a sub-type of short // codes. The other lengths will be checked later to see that they are a sub-set of these // possible lengths. var shortCodeDesc = data.Element(SHORT_CODE); if (shortCodeDesc != null) { PopulatePossibleLengthSets(shortCodeDesc.Elements(POSSIBLE_LENGTHS), lengths, localOnlyLengths); } if (localOnlyLengths.Count > 0) { throw new Exception("Found local-only lengths in short-number metadata"); } } SetPossibleLengths(lengths, localOnlyLengths, null, generalDesc); }
/** * Processes a phone number description element from the XML file and returns it as a * PhoneNumberDesc. If the description element is a fixed line or mobile number, the parent * description will be used to fill in the whole element if necessary, or any components that are * missing. For all other types, the parent description will only be used to fill in missing * components if the type has a partial definition. For example, if no "tollFree" element exists, * we assume there are no toll free numbers for that locale, and return a phone number description * with no national number data and [-1] for the possible lengths. Note that the parent * description must therefore already be processed before this method is called on any child * elements. * * @param generalDesc a generic phone number description that will be used to fill in missing * parts of the description * @param countryElement the XML element representing all the country information * @param numberType the name of the number type, corresponding to the appropriate tag in the XML * file with information about that type * @return complete description of that phone number type */ public static PhoneNumberDesc.Builder ProcessPhoneNumberDescElement(PhoneNumberDesc parentDesc, XElement countryElement, string numberType) { var phoneNumberDescList = countryElement.Elements(numberType).ToList(); var numberDesc = new PhoneNumberDesc.Builder(); if (phoneNumberDescList.Count == 0) { // -1 will never match a possible phone number length, so is safe to use to ensure this never // matches. We don't leave it empty, since for compression reasons, we use the empty list to // mean that the generalDesc possible lengths apply. numberDesc.AddPossibleLength(-1); return(numberDesc); } if (phoneNumberDescList.Count > 1) { throw new Exception($"Multiple elements with type {numberType} found."); } var element = phoneNumberDescList[0]; parentDesc ??= new PhoneNumberDesc(); var lengths = new SortedSet <int>(); var localOnlyLengths = new SortedSet <int>(); PopulatePossibleLengthSets(element.Elements(POSSIBLE_LENGTHS), lengths, localOnlyLengths); SetPossibleLengths(lengths, localOnlyLengths, parentDesc, numberDesc); var validPattern = element.Element(NATIONAL_NUMBER_PATTERN); if (validPattern != null) { numberDesc.SetNationalNumberPattern(ValidateRE(validPattern.Value, true)); } var exampleNumber = element.Element(EXAMPLE_NUMBER); if (exampleNumber != null) { numberDesc.SetExampleNumber(exampleNumber.Value); } return(numberDesc); }
private PhoneNumberDesc GetFiltered(string type, PhoneNumberDesc desc) { var builder = new PhoneNumberDesc.Builder().MergeFrom(desc); if (ShouldDrop(type, "nationalNumberPattern")) { builder.ClearNationalNumberPattern(); } if (ShouldDrop(type, "possibleLength")) { builder.ClearPossibleLength(); } if (ShouldDrop(type, "possibleLengthLocalOnly")) { builder.ClearPossibleLengthLocalOnly(); } if (ShouldDrop(type, "exampleNumber")) { builder.ClearExampleNumber(); } return(builder.Build()); }
/** * Processes a phone number description element from the XML file and returns it as a * PhoneNumberDesc. If the description element is a fixed line or mobile number, the general * description will be used to fill in the whole element if necessary, or any components that are * missing. For all other types, the general description will only be used to fill in missing * components if the type has a partial definition. For example, if no "tollFree" element exists, * we assume there are no toll free numbers for that locale, and return a phone number description * with "NA" for both the national and possible number patterns. * * @param generalDesc a generic phone number description that will be used to fill in missing * parts of the description * @param countryElement the XML element representing all the country information * @param numberType the name of the number type, corresponding to the appropriate tag in the XML * file with information about that type * @return complete description of that phone number type */ public static PhoneNumberDesc ProcessPhoneNumberDescElement(PhoneNumberDesc generalDesc, XmlElement countryElement, String numberType, bool liteBuild) { if (generalDesc == null) generalDesc = new PhoneNumberDesc.Builder().Build(); var phoneNumberDescList = countryElement.GetElementsByTagName(numberType); var numberDesc = new PhoneNumberDesc.Builder(); if (phoneNumberDescList.Count == 0 && !IsValidNumberType(numberType)) { numberDesc.SetNationalNumberPattern("NA"); numberDesc.SetPossibleNumberPattern("NA"); return numberDesc.Build(); } numberDesc.MergeFrom(generalDesc); if (phoneNumberDescList.Count > 0) { XmlElement element = (XmlElement)phoneNumberDescList[0]; var possiblePattern = element.GetElementsByTagName(POSSIBLE_NUMBER_PATTERN); if (possiblePattern.Count > 0) numberDesc.SetPossibleNumberPattern(ValidateRE(possiblePattern[0].InnerText, true)); var validPattern = element.GetElementsByTagName(NATIONAL_NUMBER_PATTERN); if (validPattern.Count > 0) numberDesc.SetNationalNumberPattern(ValidateRE(validPattern[0].InnerText, true)); if (!liteBuild) { var exampleNumber = element.GetElementsByTagName(EXAMPLE_NUMBER); if (exampleNumber.Count > 0) numberDesc.SetExampleNumber(exampleNumber[0].InnerText); } } return numberDesc.Build(); }
/** * Sets the possible length fields in the metadata from the sets of data passed in. Checks that * the length is covered by the "parent" phone number description element if one is present, and * if the lengths are exactly the same as this, they are not filled in for efficiency reasons. * * @param parentDesc the "general description" element or null if desc is the generalDesc itself * @param desc the PhoneNumberDesc object that we are going to set lengths for */ private static void SetPossibleLengths(SortedSet <int> lengths, SortedSet <int> localOnlyLengths, PhoneNumberDesc parentDesc, PhoneNumberDesc.Builder desc) { // Only add the lengths to this sub-type if they aren't exactly the same as the possible // lengths in the general desc (for metadata size reasons). if (parentDesc == null || !ArePossibleLengthsEqual(lengths, parentDesc)) { foreach (var length in lengths) { if (parentDesc == null || parentDesc.PossibleLengthList.Contains(length)) { desc.PossibleLengthList.Add(length); } else { // We shouldn't have possible lengths defined in a child element that are not covered by // the general description. We check this here even though the general description is // derived from child elements because it is only derived from a subset, and we need to // ensure *all* child elements have a valid possible length. throw new Exception( $"Out-of-range possible length found ({length}), parent lengths {string.Join(", ", parentDesc.PossibleLengthList)}."); } } } // We check that the local-only length isn't also a normal possible length (only relevant for // the general-desc, since within elements such as fixed-line we would throw an exception if we // saw this) before adding it to the collection of possible local-only lengths. foreach (var length in localOnlyLengths) { if (!lengths.Contains(length)) { // We check it is covered by either of the possible length sets of the parent // PhoneNumberDesc, because for example 7 might be a valid localOnly length for mobile, but // a valid national length for fixedLine, so the generalDesc would have the 7 removed from // localOnly. if (parentDesc == null || parentDesc.PossibleLengthLocalOnlyList.Contains(length) || parentDesc.PossibleLengthList.Contains(length)) { desc.PossibleLengthLocalOnlyList.Add(length); } else { throw new Exception( $"Out-of-range local-only possible length found ({length}), parent length {string.Join(", ", parentDesc.PossibleLengthLocalOnlyList)}."); } } } }