builder.go (430 lines of code) (raw):

package phonenumbers import ( "encoding/xml" "fmt" "regexp" "sort" "strconv" "strings" ) // ---------------------------------------------------------------------------- // Golang port of: // https://github.com/googlei18n/libphonenumber/blob/master/tools/java/common/src/com/google/i18n/phonenumbers/BuildMetadataFromXml.java // ---------------------------------------------------------------------------- func sp(value string) *string { if value == "" { return nil } return &value } func bp(value bool) *bool { return &value } func ip(value int32) *int32 { return &value } func BuildPhoneMetadataCollection(inputXML []byte, liteBuild bool, specialBuild bool, isShortNumberMetadata bool) (*PhoneMetadataCollection, error) { metadata := &PhoneNumberMetadataE{} err := xml.Unmarshal(inputXML, metadata) if err != nil { panic(fmt.Sprintf("Error unmarshalling XML: %s", err)) } isAlternateFormatsMetadata := false return buildPhoneMetadataFromElement(metadata, liteBuild, specialBuild, isShortNumberMetadata, isAlternateFormatsMetadata) } func buildPhoneMetadataFromElement(document *PhoneNumberMetadataE, liteBuild bool, specialBuild bool, isShortNumberMetadata bool, isAlternateFormatsMetadata bool) (*PhoneMetadataCollection, error) { collection := PhoneMetadataCollection{} numOfTerritories := len(document.Territories) for i := 0; i < numOfTerritories; i++ { territoryElement := document.Territories[i] regionCode := territoryElement.ID metadata := loadCountryMetadata(regionCode, &territoryElement, isShortNumberMetadata, isAlternateFormatsMetadata) collection.Metadata = append(collection.Metadata, metadata) } return &collection, nil } // Build a mapping from a country calling code to the region codes which denote the country/region // represented by that country code. In the case of multiple countries sharing a calling code, // such as the NANPA countries, the one indicated with "isMainCountryForCode" in the metadata // should be first. func BuildCountryCodeToRegionMap(metadataCollection *PhoneMetadataCollection) map[int][]string { countryCodeToRegionCodeMap := make(map[int][]string) for _, metadata := range metadataCollection.Metadata { regionCode := metadata.GetId() countryCode := int(metadata.GetCountryCode()) _, present := countryCodeToRegionCodeMap[countryCode] if present { phoneList := countryCodeToRegionCodeMap[countryCode] if metadata.GetMainCountryForCode() { phoneList = append([]string{regionCode}, phoneList...) } else { phoneList = append(phoneList, regionCode) } countryCodeToRegionCodeMap[countryCode] = phoneList } else { // For most countries, there will be only one region code for the country calling code. phoneList := []string{} if regionCode != "" { // For alternate formats, there are no region codes at all. phoneList = append(phoneList, regionCode) } countryCodeToRegionCodeMap[countryCode] = phoneList } } return countryCodeToRegionCodeMap } func validateRE(re string, removeWhitespace bool) string { // Removes all the whitespace and newline from the regexp. Not Ming pattern compile options to // make it work across programming languages. if removeWhitespace { re = string(regexp.MustCompile(`\s`).ReplaceAllLiteralString(re, "")) } _, err := regexp.Compile(re) if err != nil { panic(err) } return re } func loadTerritoryTagMetadata(regionCode string, territory *TerritoryE, nationalPrefix string) *PhoneMetadata { metadata := &PhoneMetadata{} metadata.Id = sp(regionCode) if territory.CountryCode != 0 { metadata.CountryCode = ip(territory.CountryCode) } if territory.LeadingDigits != "" { metadata.LeadingDigits = sp(validateRE(territory.LeadingDigits, false)) } if territory.InternationalPrefix != "" { metadata.InternationalPrefix = sp(validateRE(territory.InternationalPrefix, false)) } if territory.PreferredInternationalPrefix != "" { metadata.PreferredInternationalPrefix = sp(territory.PreferredInternationalPrefix) } if territory.NationalPrefixForParsing != "" { metadata.NationalPrefixForParsing = sp(validateRE(territory.NationalPrefixForParsing, true)) if territory.NationalPrefixTransformRule != "" { metadata.NationalPrefixTransformRule = sp(validateRE(territory.NationalPrefixTransformRule, false)) } } if nationalPrefix != "" { metadata.NationalPrefix = sp(nationalPrefix) if metadata.NationalPrefixForParsing == nil { metadata.NationalPrefixForParsing = sp(nationalPrefix) } } if territory.PreferredExtnPrefix != "" { metadata.PreferredExtnPrefix = sp(territory.PreferredExtnPrefix) } if territory.MainCountryForCode { metadata.MainCountryForCode = bp(true) } if territory.MobileNumberPortableRegion { metadata.MobileNumberPortableRegion = bp(true) } return metadata } func setLeadingDigitsPatterns(numberFormatElement *NumberFormatE, format *NumberFormat) { if len(numberFormatElement.LeadingDigits) > 0 { for i := 0; i < len(numberFormatElement.LeadingDigits); i++ { format.LeadingDigitsPattern = append(format.LeadingDigitsPattern, validateRE(numberFormatElement.LeadingDigits[i], true)) } } } /** * Extracts the pattern for international format. If there is no intlFormat, default to using the * national format. If the intlFormat is set to "NA" the intlFormat should be ignored. * * @throws RuntimeException if multiple intlFormats have been encountered. * @return whether an international number format is defined. */ func loadInternationalFormat(metadata *PhoneMetadata, numberFormatElement *NumberFormatE, nationalFormat *NumberFormat) bool { intlFormat := &NumberFormat{} intlFormatPattern := numberFormatElement.InternationalFormat hasExplicitIntlFormatDefined := false if len(intlFormatPattern) > 1 { panic("Invalid number of intlFormat patterns for country: " + metadata.GetId()) } else if len(intlFormatPattern) == 0 { // Default to use the same as the national pattern if none is defined. intlFormat.merge(nationalFormat) } else { intlFormat.Pattern = sp(numberFormatElement.Pattern) setLeadingDigitsPatterns(numberFormatElement, intlFormat) intlFormatPatternValue := intlFormatPattern[0] if intlFormatPatternValue != "NA" { intlFormat.Format = sp(intlFormatPatternValue) } hasExplicitIntlFormatDefined = true } if intlFormat.Format != nil { metadata.IntlNumberFormat = append(metadata.IntlNumberFormat, intlFormat) } return hasExplicitIntlFormatDefined } /** * Extracts the pattern for the national format. * * @throws RuntimeException if multiple or no formats have been encountered. */ // @VisibleForTesting func loadNationalFormat(metadata *PhoneMetadata, numberFormatElement *NumberFormatE, format *NumberFormat) { setLeadingDigitsPatterns(numberFormatElement, format) format.Pattern = sp(validateRE(numberFormatElement.Pattern, false)) format.Format = sp(numberFormatElement.Format) } func getDomesticCarrierCodeFormattingRule(carrierCodeFormattingRule string, nationalPrefix string) string { // Replace $FG with the first group ($1) and $NP with the national prefix. carrierCodeFormattingRule = strings.Replace(carrierCodeFormattingRule, "$FG", "$1", 1) carrierCodeFormattingRule = strings.Replace(carrierCodeFormattingRule, "$NP", nationalPrefix, 1) return carrierCodeFormattingRule } func getNationalPrefixFormattingRule(nationalPrefixFormattingRule string, nationalPrefix string) string { // Replace $NP with national prefix and $FG with the first group ($1). nationalPrefixFormattingRule = strings.Replace(nationalPrefixFormattingRule, "$NP", nationalPrefix, 1) nationalPrefixFormattingRule = strings.Replace(nationalPrefixFormattingRule, "$FG", "$1", 1) return nationalPrefixFormattingRule } /** * Extracts the available formats from the provided DOM element. If it does not contain any * nationalPrefixFormattingRule, the one passed-in is retained; similarly for * nationalPrefixOptionalWhenFormatting. The nationalPrefix, nationalPrefixFormattingRule and * nationalPrefixOptionalWhenFormatting values are provided from the parent (territory) element. */ // @VisibleForTesting func loadAvailableFormats(metadata *PhoneMetadata, element *TerritoryE, nationalPrefix string, nationalPrefixFormattingRule string, nationalPrefixOptionalWhenFormatting bool) { carrierCodeFormattingRule := "" if element.CarrierCodeFormattingRule != "" { carrierCodeFormattingRule = validateRE(getDomesticCarrierCodeFormattingRule(element.CarrierCodeFormattingRule, nationalPrefix), false) } numberFormatElements := element.AvailableFormats hasExplicitIntlFormatDefined := false if len(numberFormatElements) > 0 { for i := 0; i < len(numberFormatElements); i++ { numberFormatElement := numberFormatElements[i] format := NumberFormat{} if numberFormatElement.NationalPrefixFormattingRule != "" { format.NationalPrefixFormattingRule = sp(getNationalPrefixFormattingRule(numberFormatElement.NationalPrefixFormattingRule, nationalPrefix)) } else { format.NationalPrefixFormattingRule = sp(nationalPrefixFormattingRule) } if numberFormatElement.NationalPrefixOptionalWhenFormatting != nil { format.NationalPrefixOptionalWhenFormatting = numberFormatElement.NationalPrefixOptionalWhenFormatting } else if nationalPrefixOptionalWhenFormatting { format.NationalPrefixOptionalWhenFormatting = bp(nationalPrefixOptionalWhenFormatting) } if numberFormatElement.CarrierCodeFormattingRule != "" { format.DomesticCarrierCodeFormattingRule = sp(validateRE(getDomesticCarrierCodeFormattingRule(numberFormatElement.CarrierCodeFormattingRule, nationalPrefix), false)) } else if carrierCodeFormattingRule != "" { format.DomesticCarrierCodeFormattingRule = sp(carrierCodeFormattingRule) } loadNationalFormat(metadata, &numberFormatElement, &format) metadata.NumberFormat = append(metadata.NumberFormat, &format) if loadInternationalFormat(metadata, &numberFormatElement, &format) { hasExplicitIntlFormatDefined = true } } // Only a small number of regions need to specify the intlFormats in the xml. For the majority // of countries the intlNumberFormat metadata is an exact copy of the national NumberFormat // metadata. To minimize the size of the metadata file, we only keep intlNumberFormats that // actually differ in some way to the national formats. if !hasExplicitIntlFormatDefined { metadata.IntlNumberFormat = nil } } } /** * Checks if the possible lengths provided as a sorted set are equal to the possible lengths * stored already in the description pattern. Note that possibleLengths may be empty but must not * be null, and the PhoneNumberDesc passed in should also not be null. */ func arePossibleLengthsEqual(possibleLengths map[int32]bool, desc *PhoneNumberDesc) bool { if len(possibleLengths) != len(desc.PossibleLength) { return false } // check whether the same elements exist for _, val := range desc.PossibleLength { _, exists := possibleLengths[val] if !exists { return false } } return true } /** * Parses a possible length string into a set of the integers that are covered. * * @param possibleLengthString a string specifying the possible lengths of phone numbers. Follows * this syntax: ranges or elements are separated by commas, and ranges are specified in * [min-max] notation, inclusive. For example, [3-5],7,9,[11-14] should be parsed to * 3,4,5,7,9,11,12,13,14. */ func parsePossibleLengthStringToSet(possibleLengthString string) map[int32]bool { if possibleLengthString == "" { panic("Empty possibleLength string found.") } lengths := strings.Split(possibleLengthString, ",") lengthSet := make(map[int32]bool) for i := 0; i < len(lengths); i++ { lengthSubstring := lengths[i] if lengthSubstring == "" { panic("Leading, trailing or adjacent commas in possible length string %s, these should only separate numbers or ranges.") } else if lengthSubstring[0] == '[' { if lengthSubstring[len(lengthSubstring)-1] != ']' { panic(fmt.Sprintf("Missing end of range character in possible length string %s.", possibleLengthString)) } // Strip the leading and trailing [], and split on the -. minMax := strings.Split(lengthSubstring[1:len(lengthSubstring)-1], "-") if len(minMax) != 2 { panic(fmt.Sprintf("Ranges must have exactly one - character: missing for %s.", possibleLengthString)) } min, _ := strconv.Atoi(minMax[0]) max, _ := strconv.Atoi(minMax[1]) // We don't even accept [6-7] since we prefer the shorter 6,7 variant; for a range to be in // use the hyphen needs to replace at least one digit. if max-min < 2 { panic(fmt.Sprintf("The first number in a range should be two or more digits lower than the second. Culprit possibleLength string: %s", possibleLengthString)) } for j := min; j <= max; j++ { lengthSet[int32(j)] = true } } else { length, _ := strconv.Atoi(lengthSubstring) lengthSet[int32(length)] = true } } return lengthSet } /** * Reads the possible lengths present in the metadata and splits them into two sets: one for * full-length numbers, one for local numbers. * * @param data one or more phone number descriptions, represented as XML nodes * @param lengths a set to which to add possible lengths of full phone numbers * @param localOnlyLengths a set to which to add possible lengths of phone numbers only diallable * locally (e.g. within a province) */ func populatePossibleLengthSets(data []*PhoneNumberDescE, lengths map[int32]bool, localOnlyLengths map[int32]bool) { for i := 0; i < len(data); i++ { desc := data[i] if desc == nil || desc.PossibleLengths == nil { continue } element := desc.PossibleLengths nationalLengths := element.National // We don't add to the phone metadata yet, since we want to sort length elements found under // different nodes first, make sure there are no duplicates between them and that the // localOnly lengths don't overlap with the others. thisElementLengths := parsePossibleLengthStringToSet(nationalLengths) if element.LocalOnly != "" { thisElementLocalOnlyLengths := parsePossibleLengthStringToSet(element.LocalOnly) // intersect our two maps intersection := make(map[int32]bool) for k := range thisElementLengths { if thisElementLocalOnlyLengths[k] { intersection[k] = true } } if len(intersection) != 0 { panic(fmt.Sprintf("Possible length(s) found specified as a normal and local-only length: %v", intersection)) } // We check again when we set these lengths on the metadata itself in setPossibleLengths // that the elements in localOnly are not also in lengths. For e.g. the generalDesc, it // might have a local-only length for one type that is a normal length for another type. We // don't consider this an error, but we do want to remove the local-only lengths. for k := range thisElementLocalOnlyLengths { localOnlyLengths[k] = true } } // It is okay if at this time we have duplicates, because the same length might be possible // for e.g. fixed-line and for mobile numbers, and this method operates potentially on // multiple phoneNumberDesc XML elements. for k := range thisElementLengths { lengths[k] = true } } } /** * 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. Note that the parent description * must therefore already be processed before this method is called on any child elements. * * @param parentDesc a generic phone number description that will be used to fill in missing * parts of the description, or null if this is the root node. This must be processed before * this is run on any child elements. * @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 */ // @VisibleForTesting func processPhoneNumberDescElement(parentDesc *PhoneNumberDesc, element *PhoneNumberDescE) *PhoneNumberDesc { numberDesc := PhoneNumberDesc{} if element == nil { numberDesc.NationalNumberPattern = sp("NA") return &numberDesc } if parentDesc != nil { // 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). lengths := make(map[int32]bool) localOnlyLengths := make(map[int32]bool) populatePossibleLengthSets([]*PhoneNumberDescE{element}, lengths, localOnlyLengths) setPossibleLengths(lengths, localOnlyLengths, parentDesc, &numberDesc) } validPattern := element.NationalNumberPattern if validPattern != "" { numberDesc.NationalNumberPattern = sp(validateRE(validPattern, true)) } exampleNumber := element.ExampleNumber if exampleNumber != "" { numberDesc.ExampleNumber = sp(exampleNumber) } return &numberDesc } /** * 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 */ func setPossibleLengths(lengths map[int32]bool, localOnlyLengths map[int32]bool, parentDesc *PhoneNumberDesc, desc *PhoneNumberDesc) { // We clear these fields since the metadata tends to inherit from the parent element for other // fields (via a mergeFrom). desc.PossibleLength = nil desc.PossibleLengthLocalOnly = nil // 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 == nil || !arePossibleLengthsEqual(lengths, parentDesc) { for length := range lengths { if parentDesc == nil || parentDesc.hasPossibleLength(length) { desc.PossibleLength = append(desc.PossibleLength, 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. panic(fmt.Sprintf("Out-of-range possible length found (%d), parent lengths %v.", length, parentDesc.PossibleLength)) } } } // 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. for length := range localOnlyLengths { if !lengths[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 == nil || parentDesc.hasPossibleLength(length) || parentDesc.hasPossibleLengthLocalOnly(length) { desc.PossibleLengthLocalOnly = append(desc.PossibleLengthLocalOnly, length) } else { panic(fmt.Sprintf("Out-of-range local-only possible length found (%d), parent length %v.", length, parentDesc.PossibleLengthLocalOnly)) } } } // Need to sort both lists, possible lengths need to be ordered sort.Slice(desc.PossibleLength, func(i, j int) bool { return desc.PossibleLength[i] < desc.PossibleLength[j] }) sort.Slice(desc.PossibleLengthLocalOnly, func(i, j int) bool { return desc.PossibleLengthLocalOnly[i] < desc.PossibleLengthLocalOnly[j] }) } /** * Sets possible lengths in the general description, derived from certain child elements. */ func setPossibleLengthsGeneralDesc(generalDesc *PhoneNumberDesc, metadataId string, data *TerritoryE, isShortNumberMetadata bool) { lengths := make(map[int32]bool) localOnlyLengths := make(map[int32]bool) // 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). generalDescNode := data.GeneralDesc populatePossibleLengthSets([]*PhoneNumberDescE{generalDescNode}, lengths, localOnlyLengths) if len(lengths) != 0 || len(localOnlyLengths) != 0 { // We shouldn't have anything specified at the "general desc" level: we are going to // calculate this ourselves from child elements. panic(fmt.Sprintf("Found possible lengths specified at general desc: this should be derived from child elements. Affected country: %s", 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. // We remove no-international dialing trimmedDescs := []*PhoneNumberDescE{data.GeneralDesc, data.FixedLine, data.Mobile, data.Pager, data.TollFree, data.PremiumRate, data.SharedCost, data.PersonalNumber, data.VOIP, data.UAN, data.VoiceMail, data.StandardRate, data.ShortCode, data.Emergency, data.CarrierSpecific} populatePossibleLengthSets(trimmedDescs, lengths, localOnlyLengths) } else { populatePossibleLengthSets([]*PhoneNumberDescE{data.ShortCode}, lengths, localOnlyLengths) if len(localOnlyLengths) > 0 { panic(fmt.Errorf("found local-only lengths in short-number metadata")) } } setPossibleLengths(lengths, localOnlyLengths, nil, generalDesc) } func loadCountryMetadata(regionCode string, element *TerritoryE, isShortNumberMetadata bool, isAlternateFormatsMetadata bool) *PhoneMetadata { nationalPrefix := element.NationalPrefix metadata := loadTerritoryTagMetadata(regionCode, element, nationalPrefix) nationalPrefixFormattingRule := getNationalPrefixFormattingRule(element.NationalPrefixFormattingRule, nationalPrefix) loadAvailableFormats(metadata, element, nationalPrefix, nationalPrefixFormattingRule, element.NationalPrefixOptionalWhenFormatting) if !isAlternateFormatsMetadata { // The alternate formats metadata does not need most of the patterns to be set. setRelevantDescPatterns(metadata, element, isShortNumberMetadata) } return metadata } func setRelevantDescPatterns(metadata *PhoneMetadata, element *TerritoryE, isShortNumberMetadata bool) { generalDesc := processPhoneNumberDescElement(nil, element.GeneralDesc) // Calculate the possible lengths for the general description. This will be based on the // possible lengths of the child elements. setPossibleLengthsGeneralDesc(generalDesc, metadata.GetId(), element, isShortNumberMetadata) metadata.GeneralDesc = generalDesc if !isShortNumberMetadata { // Set fields used by regular length phone numbers. metadata.FixedLine = processPhoneNumberDescElement(generalDesc, element.FixedLine) metadata.Mobile = processPhoneNumberDescElement(generalDesc, element.Mobile) metadata.SharedCost = processPhoneNumberDescElement(generalDesc, element.SharedCost) metadata.Voip = processPhoneNumberDescElement(generalDesc, element.VOIP) metadata.PersonalNumber = processPhoneNumberDescElement(generalDesc, element.PersonalNumber) metadata.Pager = processPhoneNumberDescElement(generalDesc, element.Pager) metadata.Uan = processPhoneNumberDescElement(generalDesc, element.UAN) metadata.Voicemail = processPhoneNumberDescElement(generalDesc, element.VoiceMail) metadata.NoInternationalDialling = processPhoneNumberDescElement(generalDesc, element.NoInternationalDialing) mobileAndFixedAreSame := *metadata.Mobile.NationalNumberPattern == *metadata.FixedLine.NationalNumberPattern if metadata.GetSameMobileAndFixedLinePattern() != mobileAndFixedAreSame { metadata.SameMobileAndFixedLinePattern = bp(mobileAndFixedAreSame) } metadata.TollFree = processPhoneNumberDescElement(generalDesc, element.TollFree) metadata.PremiumRate = processPhoneNumberDescElement(generalDesc, element.PremiumRate) } else { // Set fields used by short numbers. metadata.StandardRate = processPhoneNumberDescElement(generalDesc, element.StandardRate) metadata.ShortCode = processPhoneNumberDescElement(generalDesc, element.ShortCode) metadata.CarrierSpecific = processPhoneNumberDescElement(generalDesc, element.CarrierSpecific) metadata.Emergency = processPhoneNumberDescElement(generalDesc, element.Emergency) metadata.TollFree = processPhoneNumberDescElement(generalDesc, element.TollFree) metadata.PremiumRate = processPhoneNumberDescElement(generalDesc, element.PremiumRate) } } // <!ELEMENT phoneNumberMetadata (territories)> type PhoneNumberMetadataE struct { // <!ELEMENT territories (territory+)> Territories []TerritoryE `xml:"territories>territory"` } // <!ELEMENT territory (references?, availableFormats?, generalDesc, noInternationalDialling?, //fixedLine?, mobile?, pager?, tollFree?, premiumRate?, //sharedCost?, personalNumber?, voip?, uan?, voicemail?)> type TerritoryE struct { // <!ATTLIST territory id CDATA #REQUIRED> ID string `xml:"id,attr"` // <!ATTLIST territory mainCountryForCode (true) #IMPLIED> MainCountryForCode bool `xml:"mainCountryForCode,attr"` // <!ATTLIST territory leadingDigits CDATA #IMPLIED> LeadingDigits string `xml:"leadingDigits,attr"` // <!ATTLIST territory countryCode CDATA #REQUIRED> CountryCode int32 `xml:"countryCode,attr"` // <!ATTLIST territory nationalPrefix CDATA #IMPLIED> NationalPrefix string `xml:"nationalPrefix,attr"` // <!ATTLIST territory internationalPrefix CDATA #IMPLIED> InternationalPrefix string `xml:"internationalPrefix,attr"` // <!ATTLIST territory preferredInternationalPrefix CDATA #IMPLIED> PreferredInternationalPrefix string `xml:"preferredInternationalPrefix,attr"` // <!ATTLIST territory nationalPrefixFormattingRule CDATA #IMPLIED> NationalPrefixFormattingRule string `xml:"nationalPrefixFormattingRule,attr"` // <!ATTLIST territory mobileNumberPortableRegion (true) #IMPLIED> MobileNumberPortableRegion bool `xml:"mobileNumberPortableRegion,attr"` // <!ATTLIST territory nationalPrefixForParsing CDATA #IMPLIED> NationalPrefixForParsing string `xml:"nationalPrefixForParsing,attr"` // <!ATTLIST territory nationalPrefixTransformRule CDATA #IMPLIED> NationalPrefixTransformRule string `xml:"nationalPrefixTransformRule,attr"` // <!ATTLIST territory preferredExtnPrefix CDATA #IMPLIED> PreferredExtnPrefix string `xml:"PreferredExtnPrefix"` // <!ATTLIST territory nationalPrefixOptionalWhenFormatting (true) #IMPLIED> NationalPrefixOptionalWhenFormatting bool `xml:"nationalPrefixOptionalWhenFormatting,attr"` // <!ATTLIST territory carrierCodeFormattingRule CDATA #IMPLIED> CarrierCodeFormattingRule string `xml:"carrierCodeFormattingRule,attr"` // <!ELEMENT references (sourceUrl+)> // <!ELEMENT sourceUrl (#PCDATA)> References []string `xml:"references>sourceUrl"` // <!ELEMENT availableFormats (numberFormat+)> AvailableFormats []NumberFormatE `xml:"availableFormats>numberFormat"` // <!ELEMENT generalDesc (nationalNumberPattern)> GeneralDesc *PhoneNumberDescE `xml:"generalDesc"` // <!ELEMENT noInternationalDialling (nationalNumberPattern, possibleLengths, exampleNumber)> NoInternationalDialing *PhoneNumberDescE `xml:"noInternationalDialing"` // <!ELEMENT fixedLine (nationalNumberPattern, possibleLengths, exampleNumber)> FixedLine *PhoneNumberDescE `xml:"fixedLine"` // <!ELEMENT mobile (nationalNumberPattern, possibleLengths, exampleNumber)> Mobile *PhoneNumberDescE `xml:"mobile"` // <!ELEMENT pager (nationalNumberPattern, possibleLengths, exampleNumber)> Pager *PhoneNumberDescE `xml:"pager"` // <!ELEMENT tollFree (nationalNumberPattern, possibleLengths, exampleNumber)> TollFree *PhoneNumberDescE `xml:"tollFree"` // <!ELEMENT premiumRate (nationalNumberPattern, possibleLengths, exampleNumber)> PremiumRate *PhoneNumberDescE `xml:"premiumRate"` // <!ELEMENT sharedCost (nationalNumberPattern, possibleLengths, exampleNumber)> SharedCost *PhoneNumberDescE `xml:"sharedCost"` // <!ELEMENT personalNumber (nationalNumberPattern, possibleLengths, exampleNumber)> PersonalNumber *PhoneNumberDescE `xml:"personalNumber"` // <!ELEMENT voip (nationalNumberPattern, possibleLengths, exampleNumber)> VOIP *PhoneNumberDescE `xml:"voip"` // <!ELEMENT uan (nationalNumberPattern, possibleLengths, exampleNumber)> UAN *PhoneNumberDescE `xml:"uan"` // <!ELEMENT voicemail (nationalNumberPattern, possibleLengths, exampleNumber)> VoiceMail *PhoneNumberDescE `xml:"voicemail"` // <!ELEMENT uan (nationalNumberPattern, possibleLengths, exampleNumber)> StandardRate *PhoneNumberDescE `xml:"standardRate"` // <!ELEMENT voicemail (nationalNumberPattern, possibleLengths, exampleNumber)> ShortCode *PhoneNumberDescE `xml:"shortCode"` // <!ELEMENT uan (nationalNumberPattern, possibleLengths, exampleNumber)> Emergency *PhoneNumberDescE `xml:"emergency"` // <!ELEMENT voicemail (nationalNumberPattern, possibleLengths, exampleNumber)> CarrierSpecific *PhoneNumberDescE `xml:"carrierSpecific"` } // <!ELEMENT numberFormat (leadingDigits*, format, intlFormat*)> type NumberFormatE struct { // <!ELEMENT leadingDigits (#PCDATA)> LeadingDigits []string `xml:"leadingDigits"` // <!ELEMENT format (#PCDATA)> Format string `xml:"format"` // <!ELEMENT intlFormat (#PCDATA)> InternationalFormat []string `xml:"intlFormat"` // <!ATTLIST numberFormat nationalPrefixFormattingRule CDATA #IMPLIED> NationalPrefixFormattingRule string `xml:"nationalPrefixFormattingRule,attr"` // <!ATTLIST numberFormat nationalPrefixOptionalWhenFormatting (true) #IMPLIED> NationalPrefixOptionalWhenFormatting *bool `xml:"nationalPrefixOptionalWhenFormatting,attr"` // <!ATTLIST numberFormat carrierCodeFormattingRule CDATA #IMPLIED> CarrierCodeFormattingRule string `xml:"carrierCodeFormattingRule,attr"` // <!ATTLIST numberFormat pattern CDATA #REQUIRED> Pattern string `xml:"pattern,attr" validate:"required"` } type PossibleLengthE struct { // <!ATTLIST possibleLengths national CDATA #REQUIRED> National string `xml:"national,attr"` // <!ATTLIST possibleLengths localOnly CDATA #IMPLIED> LocalOnly string `xml:"localOnly,attr"` } type PhoneNumberDescE struct { // <!ELEMENT nationalNumberPattern (#PCDATA)> NationalNumberPattern string `xml:"nationalNumberPattern"` // <!ELEMENT possibleLengths EMPTY> PossibleLengths *PossibleLengthE `xml:"possibleLengths"` // <!ELEMENT exampleNumber (#PCDATA)> ExampleNumber string `xml:"exampleNumber"` }