4040
4141import sys
4242import os
43+ import copy
4344import re
4445import getopt
4546import datetime
@@ -154,6 +155,30 @@ def _dews_re(re_str):
154155 return re .sub (r'\s' , '' , re_str )
155156
156157
158+ _NUM_RE = re .compile ('\d+' )
159+ _RANGE_RE = re .compile (r'\[(?P<min>\d+)-(?P<max>\d+)\]' )
160+
161+
162+ def _extract_lengths (ll ):
163+ """Extract list of possible lengths from string"""
164+ results = set ()
165+ if ll is None :
166+ return []
167+ for val in ll .split (',' ):
168+ m = _NUM_RE .match (val )
169+ if m :
170+ results .add (int (val ))
171+ else :
172+ m = _RANGE_RE .match (val )
173+ if m is None :
174+ raise Exception ("Unrecognized length specification %s" % ll )
175+ min = int (m .group ('min' ))
176+ max = int (m .group ('max' ))
177+ for ii in range (min , max + 1 ):
178+ results .add (ii )
179+ return sorted (list (results ))
180+
181+
157182def _expand_formatting_rule (rule , national_prefix ):
158183 """Formatting rules can include terms "$NP" and "$FG",
159184 These get replaced with:
@@ -216,7 +241,7 @@ def __init__(self, owning_xterr, xtag, national_prefix,
216241 if self .o .national_prefix_formatting_rule is not None :
217242 # expand abbreviations
218243 self .o .national_prefix_formatting_rule = _expand_formatting_rule (self .o .national_prefix_formatting_rule ,
219- national_prefix )
244+ national_prefix )
220245 else :
221246 # set to territory-wide formatting rule
222247 self .o .national_prefix_formatting_rule = national_prefix_formatting_rule
@@ -231,7 +256,7 @@ def __init__(self, owning_xterr, xtag, national_prefix,
231256 if self .o .domestic_carrier_code_formatting_rule is not None :
232257 # expand abbreviations
233258 self .o .domestic_carrier_code_formatting_rule = _expand_formatting_rule (self .o .domestic_carrier_code_formatting_rule ,
234- national_prefix )
259+ national_prefix )
235260 else :
236261 # set to territory-wide formatting rule
237262 self .o .domestic_carrier_code_formatting_rule = carrier_code_formatting_rule
@@ -278,11 +303,16 @@ def __unicode__(self):
278303
279304class XPhoneNumberDesc (UnicodeMixin ):
280305 """Parse PhoneNumberDesc object from XML element"""
281- def __init__ (self , xtag , template = None , fill_na = True ):
306+ def __init__ (self , xtag , template = None , fill_na = True , lengths_expected = True ):
307+ self .xtag = xtag
282308 self .o = PhoneNumberDesc ()
283309 self .o ._mutable = True
284310 self .o .national_number_pattern = None
285311 self .o .possible_number_pattern = None
312+ # Set possible length info to None for now, to mark that it wasn't specified
313+ # for this numberDesc.
314+ self .o .possible_length = None
315+ self .o .possible_length_local_only = None
286316 self .o .example_number = None
287317 if xtag is None :
288318 if fill_na :
@@ -305,6 +335,21 @@ def __init__(self, xtag, template=None, fill_na=True):
305335 example_number = _get_unique_child_value (xtag , 'exampleNumber' )
306336 if example_number is not None :
307337 self .o .example_number = example_number
338+ possible_lengths = _get_unique_child (xtag , 'possibleLengths' )
339+ if possible_lengths is not None :
340+ if not lengths_expected :
341+ raise Exception ("Found unexpected possibleLengths element in %s" % xtag .tag )
342+ national_lengths = possible_lengths .attrib ['national' ] # REQUIRED attribute
343+ if national_lengths == "-1" :
344+ # A value of -1 for possibleLengths.national is a special marker to indicate
345+ # that this sub-type of number doesn't actually exist.
346+ if fill_na :
347+ raise Exception ("Found possibleLengths -1 for unexpected number type" )
348+ self .o .possible_length = [- 1 ]
349+ return
350+ self .o .possible_length = _extract_lengths (national_lengths )
351+ local_lengths = possible_lengths .get ('localOnly' , None ) # IMPLIED attribute
352+ self .o .possible_length_local_only = _extract_lengths (local_lengths )
308353
309354 def __unicode__ (self ):
310355 return u (self .o )
@@ -374,10 +419,19 @@ def __init__(self, xterritory, short_data):
374419 # first and most important; it will be used to fill out missing fields in
375420 # many of the other PhoneNumberDesc elements.
376421 self .o .general_desc = XPhoneNumberDesc (_get_unique_child (xterritory , 'generalDesc' ),
377- fill_na = False )
422+ fill_na = False , lengths_expected = False )
423+ # As a special case, the possible lengths for the general_desc should be empty
424+ # (they will be deduced below).
425+ if self .o .general_desc .o .possible_length is not None or self .o .general_desc .o .possible_length_local_only is not None :
426+ raise Exception ("Found generalDesc for %s with unexpected possibleLength element" % self .o .general_desc .id )
427+
378428 # areaCodeOptional is in the XML but not used in the code.
379429 self .o .area_code_optional = XPhoneNumberDesc (_get_unique_child (xterritory , 'areaCodeOptional' ),
380430 template = self .o .general_desc .o )
431+ self .o .toll_free = XPhoneNumberDesc (_get_unique_child (xterritory , 'tollFree' ),
432+ template = self .o .general_desc .o )
433+ self .o .premium_rate = XPhoneNumberDesc (_get_unique_child (xterritory , 'premiumRate' ),
434+ template = self .o .general_desc .o )
381435 if not short_data :
382436 self .o .fixed_line = XPhoneNumberDesc (_get_unique_child (xterritory , 'fixedLine' ),
383437 template = self .o .general_desc .o , fill_na = False )
@@ -397,6 +451,14 @@ def __init__(self, xterritory, short_data):
397451 template = self .o .general_desc .o )
398452 self .o .no_international_dialling = XPhoneNumberDesc (_get_unique_child (xterritory , 'noInternationalDialling' ),
399453 template = self .o .general_desc .o )
454+ # Skip noInternationalDialling when combining possible length information
455+ sub_descs = (self .o .area_code_optional , self .o .toll_free , self .o .premium_rate ,
456+ self .o .fixed_line , self .o .mobile , self .o .pager , self .o .shared_cost ,
457+ self .o .personal_number , self .o .voip , self .o .uan , self .o .voicemail )
458+ all_descs = (self .o .area_code_optional , self .o .toll_free , self .o .premium_rate ,
459+ self .o .fixed_line , self .o .mobile , self .o .pager , self .o .shared_cost ,
460+ self .o .personal_number , self .o .voip , self .o .uan , self .o .voicemail ,
461+ self .o .no_international_dialling )
400462 else :
401463 self .o .standard_rate = XPhoneNumberDesc (_get_unique_child (xterritory , 'standardRate' ),
402464 template = self .o .general_desc .o )
@@ -406,10 +468,39 @@ def __init__(self, xterritory, short_data):
406468 template = self .o .general_desc .o )
407469 self .o .emergency = XPhoneNumberDesc (_get_unique_child (xterritory , 'emergency' ),
408470 template = self .o .general_desc .o )
409- self .o .toll_free = XPhoneNumberDesc (_get_unique_child (xterritory , 'tollFree' ),
410- template = self .o .general_desc .o )
411- self .o .premium_rate = XPhoneNumberDesc (_get_unique_child (xterritory , 'premiumRate' ),
412- template = self .o .general_desc .o )
471+ # For short number metadata, copy the lengths from the "short code" section only.
472+ sub_descs = (self .o .short_code ,)
473+ all_descs = (self .o .area_code_optional , self .o .toll_free , self .o .premium_rate ,
474+ self .o .standard_rate , self .o .short_code , self .o .carrier_specific ,
475+ self .o .emergency )
476+
477+ # Build the possible length information for general_desc based on all the different types of number.
478+ possible_lengths = set ()
479+ local_lengths = set ()
480+ for desc in sub_descs :
481+ if desc .o is None :
482+ continue
483+ if desc .o .possible_length is not None :
484+ possible_lengths .update (desc .o .possible_length )
485+ if desc .o .possible_length_local_only is not None :
486+ local_lengths .update (desc .o .possible_length_local_only )
487+ self .o .general_desc .o .possible_length = list (possible_lengths )
488+ self .o .general_desc .o .possible_length_local_only = list (local_lengths )
489+
490+ # Now that the union of length information is available, trickle it back down to those types
491+ # of number that didn't specify any length information (indicated by having those fields set
492+ # to None). But only if they're non
493+ for desc in all_descs :
494+ if desc .o is not None :
495+ if desc .o .national_number_pattern == DATA_NA :
496+ desc .o .possible_length = []
497+ desc .o .possible_length_local_only = []
498+ continue
499+ if desc .o .possible_length is None :
500+ desc .o .possible_length = copy .copy (self .o .general_desc .o .possible_length )
501+ if desc .o .possible_length_local_only is None :
502+ desc .o .possible_length_local_only = copy .copy (self .o .general_desc .o .possible_length_local_only )
503+
413504 # Look for available formats
414505 self .has_explicit_intl_format = False
415506 formats = _get_unique_child (xterritory , "availableFormats" )
0 commit comments