1 /* 2 * Copyright (C) 2010 The Libphonenumber Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.i18n.phonenumbers; 18 19 import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; 20 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; 21 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; 22 import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; 23 24 import java.io.BufferedWriter; 25 import java.io.FileWriter; 26 import java.io.IOException; 27 import java.util.Formatter; 28 import java.util.List; 29 import java.util.Map; 30 31 /** 32 * Tool to convert phone number metadata from the XML format to JSON format. 33 * 34 * @author Nikolaos Trogkanis 35 */ 36 public class BuildMetadataJsonFromXml extends Command { 37 private static final String NAMESPACE = "i18n.phonenumbers.metadata"; 38 39 private static final String HELP_MESSAGE = 40 "Usage:\n" + 41 "BuildMetadataJsonFromXml <inputFile> <outputFile> [<liteBuild>] [<namespace>]\n" + 42 "\n" + 43 "where:\n" + 44 " inputFile The input file containing phone number metadata in XML format.\n" + 45 " outputFile The output file to contain phone number metadata in JSON format.\n" + 46 " liteBuild Whether to generate the lite-version of the metadata (default:\n" + 47 " false). When set to true certain metadata will be omitted.\n" + 48 " At this moment, example numbers information is omitted.\n" + 49 " namespace If present, the namespace to provide the metadata with (default:\n" + 50 " " + NAMESPACE + ").\n" + 51 "\n" + 52 "Example command line invocation:\n" + 53 "BuildMetadataJsonFromXml PhoneNumberMetadata.xml metadatalite.js true i18n.phonenumbers.testmetadata\n"; 54 55 private static final String FILE_OVERVIEW = 56 "/**\n" 57 + " * @fileoverview Generated metadata for file\n" 58 + " * %s\n" 59 + " * @author Nikolaos Trogkanis\n" 60 + " */\n\n"; 61 62 private static final String COUNTRY_CODE_TO_REGION_CODE_MAP_COMMENT = 63 "/**\n" 64 + " * A mapping from a country calling code to the region codes which denote the\n" 65 + " * region represented by that country calling code. In the case of multiple\n" 66 + " * countries sharing a calling code, such as the NANPA regions, the one\n" 67 + " * indicated with \"isMainCountryForCode\" in the metadata should be first.\n" 68 + " * @type {!Object.<number, Array.<string>>}\n" 69 + " */\n"; 70 71 private static final String COUNTRY_TO_METADATA_COMMENT = 72 "/**\n" 73 + " * A mapping from a region code to the PhoneMetadata for that region.\n" 74 + " * @type {!Object.<string, Array>}\n" 75 + " */\n"; 76 77 private static final int COPYRIGHT_YEAR = 2010; 78 79 @Override getCommandName()80 public String getCommandName() { 81 return "BuildMetadataJsonFromXml"; 82 } 83 84 @Override start()85 public boolean start() { 86 String[] args = getArgs(); 87 88 if (args.length != 3 && args.length != 4 && args.length != 5) { 89 System.err.println(HELP_MESSAGE); 90 return false; 91 } 92 String inputFile = args[1]; 93 String outputFile = args[2]; 94 boolean liteBuild = args.length > 3 && args[3].equals("true"); 95 String namespace = args.length > 4 ? args[4] : NAMESPACE; 96 return start(inputFile, outputFile, liteBuild, namespace); 97 } 98 start(String inputFile, String outputFile, boolean liteBuild)99 static boolean start(String inputFile, String outputFile, boolean liteBuild) { 100 return start(inputFile, outputFile, liteBuild, NAMESPACE); 101 } 102 start(String inputFile, String outputFile, boolean liteBuild, String namespace)103 static boolean start(String inputFile, String outputFile, boolean liteBuild, String namespace) { 104 try { 105 PhoneMetadataCollection metadataCollection = 106 BuildMetadataFromXml.buildPhoneMetadataCollection(inputFile, liteBuild, false); 107 Map<Integer, List<String>> countryCodeToRegionCodeMap = 108 BuildMetadataFromXml.buildCountryCodeToRegionCodeMap(metadataCollection); 109 110 BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); 111 112 CopyrightNotice.writeTo(writer, COPYRIGHT_YEAR, true); 113 Formatter formatter = new Formatter(writer); 114 formatter.format(FILE_OVERVIEW, inputFile); 115 116 writer.write("goog.provide('" + namespace + "');\n\n"); 117 118 writer.write(COUNTRY_CODE_TO_REGION_CODE_MAP_COMMENT); 119 writer.write(namespace + ".countryCodeToRegionCodeMap = "); 120 writeCountryCodeToRegionCodeMap(countryCodeToRegionCodeMap, writer); 121 writer.write(";\n\n"); 122 123 writer.write(COUNTRY_TO_METADATA_COMMENT); 124 writer.write(namespace + ".countryToMetadata = "); 125 writeCountryToMetadataMap(metadataCollection, writer); 126 writer.write(";\n"); 127 128 writer.flush(); 129 writer.close(); 130 formatter.close(); 131 } catch (Exception e) { 132 e.printStackTrace(); 133 return false; 134 } 135 return true; 136 } 137 138 // Writes a PhoneMetadataCollection in JSON format. writeCountryToMetadataMap(PhoneMetadataCollection metadataCollection, BufferedWriter writer)139 private static void writeCountryToMetadataMap(PhoneMetadataCollection metadataCollection, 140 BufferedWriter writer) throws IOException { 141 writer.write("{\n"); 142 boolean isFirstTimeInLoop = true; 143 for (PhoneMetadata metadata : metadataCollection.getMetadataList()) { 144 if (isFirstTimeInLoop) { 145 isFirstTimeInLoop = false; 146 } else { 147 writer.write(","); 148 } 149 String key = metadata.getId(); 150 // For non-geographical country calling codes (e.g. +800), use the country calling codes 151 // instead of the region code as key in the map. 152 if (key.equals("001")) { 153 key = Integer.toString(metadata.getCountryCode()); 154 } 155 JSArrayBuilder jsArrayBuilder = new JSArrayBuilder(); 156 toJsArray(metadata, jsArrayBuilder); 157 writer.write("\""); 158 writer.write(key); 159 writer.write("\":"); 160 writer.write(jsArrayBuilder.toString()); 161 } 162 writer.write("}"); 163 } 164 165 // Writes a Map<Integer, List<String>> in JSON format. writeCountryCodeToRegionCodeMap( Map<Integer, List<String>> countryCodeToRegionCodeMap, BufferedWriter writer)166 private static void writeCountryCodeToRegionCodeMap( 167 Map<Integer, List<String>> countryCodeToRegionCodeMap, 168 BufferedWriter writer) throws IOException { 169 writer.write("{\n"); 170 boolean isFirstTimeInLoop = true; 171 for (Map.Entry<Integer, List<String>> entry : countryCodeToRegionCodeMap.entrySet()) { 172 if (isFirstTimeInLoop) { 173 isFirstTimeInLoop = false; 174 } else { 175 writer.write(","); 176 } 177 writer.write(Integer.toString(entry.getKey())); 178 writer.write(":"); 179 JSArrayBuilder jsArrayBuilder = new JSArrayBuilder(); 180 jsArrayBuilder.beginArray(); 181 jsArrayBuilder.appendIterator(entry.getValue().iterator()); 182 jsArrayBuilder.endArray(); 183 writer.write(jsArrayBuilder.toString()); 184 } 185 writer.write("}"); 186 } 187 188 // Converts NumberFormat to JSArray. toJsArray(NumberFormat format, JSArrayBuilder jsArrayBuilder)189 private static void toJsArray(NumberFormat format, JSArrayBuilder jsArrayBuilder) { 190 jsArrayBuilder.beginArray(); 191 192 // missing 0 193 jsArrayBuilder.append(null); 194 // required string pattern = 1; 195 jsArrayBuilder.append(format.getPattern()); 196 // required string format = 2; 197 jsArrayBuilder.append(format.getFormat()); 198 // repeated string leading_digits_pattern = 3; 199 int leadingDigitsPatternSize = format.leadingDigitsPatternSize(); 200 if (leadingDigitsPatternSize > 0) { 201 jsArrayBuilder.beginArray(); 202 for (int i = 0; i < leadingDigitsPatternSize; i++) { 203 jsArrayBuilder.append(format.getLeadingDigitsPattern(i)); 204 } 205 jsArrayBuilder.endArray(); 206 } else { 207 jsArrayBuilder.append(null); 208 } 209 // optional string national_prefix_formatting_rule = 4; 210 if (format.hasNationalPrefixFormattingRule()) { 211 jsArrayBuilder.append(format.getNationalPrefixFormattingRule()); 212 } else { 213 jsArrayBuilder.append(null); 214 } 215 // optional string domestic_carrier_code_formatting_rule = 5; 216 if (format.hasDomesticCarrierCodeFormattingRule()) { 217 jsArrayBuilder.append(format.getDomesticCarrierCodeFormattingRule()); 218 } else { 219 jsArrayBuilder.append(null); 220 } 221 // optional bool national_prefix_optional_when_formatting = 6 [default = false]; 222 if (format.hasNationalPrefixOptionalWhenFormatting()) { 223 jsArrayBuilder.append(format.getNationalPrefixOptionalWhenFormatting()); 224 } else { 225 jsArrayBuilder.append(null); 226 } 227 228 jsArrayBuilder.endArray(); 229 } 230 231 // Converts PhoneNumberDesc to JSArray. toJsArray(PhoneNumberDesc desc, JSArrayBuilder jsArrayBuilder)232 private static void toJsArray(PhoneNumberDesc desc, JSArrayBuilder jsArrayBuilder) { 233 if (desc == null) { 234 // Some descriptions are optional; in these cases we just append null and return if they are 235 // absent. 236 jsArrayBuilder.append(null); 237 return; 238 } 239 jsArrayBuilder.beginArray(); 240 241 // missing 0 242 jsArrayBuilder.append(null); 243 // missing 1 244 jsArrayBuilder.append(null); 245 // optional string national_number_pattern = 2; 246 if (desc.hasNationalNumberPattern()) { 247 jsArrayBuilder.append(desc.getNationalNumberPattern()); 248 } else { 249 jsArrayBuilder.append(null); 250 } 251 // missing 3 252 jsArrayBuilder.append(null); 253 // missing 4 254 jsArrayBuilder.append(null); 255 // missing 5 256 jsArrayBuilder.append(null); 257 // optional string example_number = 6; 258 if (desc.hasExampleNumber()) { 259 jsArrayBuilder.append(desc.getExampleNumber()); 260 } else { 261 jsArrayBuilder.append(null); 262 } 263 // missing 7 264 jsArrayBuilder.append(null); 265 // missing 8 266 jsArrayBuilder.append(null); 267 // repeated int32 possible_length = 9; 268 int possibleLengthSize = desc.getPossibleLengthCount(); 269 if (possibleLengthSize > 0) { 270 jsArrayBuilder.beginArray(); 271 for (int i = 0; i < possibleLengthSize; i++) { 272 jsArrayBuilder.append(desc.getPossibleLength(i)); 273 } 274 jsArrayBuilder.endArray(); 275 } else { 276 jsArrayBuilder.append(null); 277 } 278 // repeated int32 possible_length = 10; 279 int possibleLengthLocalOnlySize = desc.getPossibleLengthLocalOnlyCount(); 280 if (possibleLengthLocalOnlySize > 0) { 281 jsArrayBuilder.beginArray(); 282 for (int i = 0; i < possibleLengthLocalOnlySize; i++) { 283 jsArrayBuilder.append(desc.getPossibleLengthLocalOnly(i)); 284 } 285 jsArrayBuilder.endArray(); 286 } else { 287 jsArrayBuilder.append(null); 288 } 289 290 jsArrayBuilder.endArray(); 291 } 292 293 // Converts PhoneMetadata to JSArray. toJsArray(PhoneMetadata metadata, JSArrayBuilder jsArrayBuilder)294 private static void toJsArray(PhoneMetadata metadata, JSArrayBuilder jsArrayBuilder) { 295 jsArrayBuilder.beginArray(); 296 297 // missing 0 298 jsArrayBuilder.append(null); 299 // optional PhoneNumberDesc general_desc = 1; 300 toJsArray(metadata.getGeneralDesc(), jsArrayBuilder); 301 // optional PhoneNumberDesc fixed_line = 2; 302 toJsArray(metadata.getFixedLine(), jsArrayBuilder); 303 // optional PhoneNumberDesc mobile = 3; 304 toJsArray(metadata.getMobile(), jsArrayBuilder); 305 // optional PhoneNumberDesc toll_free = 4; 306 toJsArray(metadata.getTollFree(), jsArrayBuilder); 307 // optional PhoneNumberDesc premium_rate = 5; 308 toJsArray(metadata.getPremiumRate(), jsArrayBuilder); 309 // optional PhoneNumberDesc shared_cost = 6; 310 toJsArray(metadata.getSharedCost(), jsArrayBuilder); 311 // optional PhoneNumberDesc personal_number = 7; 312 toJsArray(metadata.getPersonalNumber(), jsArrayBuilder); 313 // optional PhoneNumberDesc voip = 8; 314 toJsArray(metadata.getVoip(), jsArrayBuilder); 315 // required string id = 9; 316 jsArrayBuilder.append(metadata.getId()); 317 // optional int32 country_code = 10; 318 if (metadata.hasCountryCode()) { 319 jsArrayBuilder.append(metadata.getCountryCode()); 320 } else { 321 jsArrayBuilder.append(null); 322 } 323 // optional string international_prefix = 11; 324 if (metadata.hasInternationalPrefix()) { 325 jsArrayBuilder.append(metadata.getInternationalPrefix()); 326 } else { 327 jsArrayBuilder.append(null); 328 } 329 330 // optional string national_prefix = 12; 331 if (metadata.hasNationalPrefix()) { 332 jsArrayBuilder.append(metadata.getNationalPrefix()); 333 } else { 334 jsArrayBuilder.append(null); 335 } 336 // optional string preferred_extn_prefix = 13; 337 if (metadata.hasPreferredExtnPrefix()) { 338 jsArrayBuilder.append(metadata.getPreferredExtnPrefix()); 339 } else { 340 jsArrayBuilder.append(null); 341 } 342 // missing 14 343 jsArrayBuilder.append(null); 344 // optional string national_prefix_for_parsing = 15; 345 if (metadata.hasNationalPrefixForParsing()) { 346 jsArrayBuilder.append(metadata.getNationalPrefixForParsing()); 347 } else { 348 jsArrayBuilder.append(null); 349 } 350 // optional string national_prefix_transform_rule = 16; 351 if (metadata.hasNationalPrefixTransformRule()) { 352 jsArrayBuilder.append(metadata.getNationalPrefixTransformRule()); 353 } else { 354 jsArrayBuilder.append(null); 355 } 356 // optional string preferred_international_prefix = 17; 357 if (metadata.hasPreferredInternationalPrefix()) { 358 jsArrayBuilder.append(metadata.getPreferredInternationalPrefix()); 359 } else { 360 jsArrayBuilder.append(null); 361 } 362 // optional bool same_mobile_and_fixed_line_pattern = 18 [default=false]; 363 if (metadata.hasSameMobileAndFixedLinePattern()) { 364 jsArrayBuilder.append(metadata.getSameMobileAndFixedLinePattern()); 365 } else { 366 jsArrayBuilder.append(null); 367 } 368 // repeated NumberFormat number_format = 19; 369 int numberFormatSize = metadata.numberFormatSize(); 370 if (numberFormatSize > 0) { 371 jsArrayBuilder.beginArray(); 372 for (int i = 0; i < numberFormatSize; i++) { 373 toJsArray(metadata.getNumberFormat(i), jsArrayBuilder); 374 } 375 jsArrayBuilder.endArray(); 376 } else { 377 jsArrayBuilder.append(null); 378 } 379 // repeated NumberFormat intl_number_format = 20; 380 int intlNumberFormatSize = metadata.intlNumberFormatSize(); 381 if (intlNumberFormatSize > 0) { 382 jsArrayBuilder.beginArray(); 383 for (int i = 0; i < intlNumberFormatSize; i++) { 384 toJsArray(metadata.getIntlNumberFormat(i), jsArrayBuilder); 385 } 386 jsArrayBuilder.endArray(); 387 } else { 388 jsArrayBuilder.append(null); 389 } 390 // optional PhoneNumberDesc pager = 21; 391 toJsArray(metadata.getPager(), jsArrayBuilder); 392 // optional bool main_country_for_code = 22 [default=false]; 393 if (metadata.isMainCountryForCode()) { 394 jsArrayBuilder.append(1); 395 } else { 396 jsArrayBuilder.append(null); 397 } 398 // optional string leading_digits = 23; 399 if (metadata.hasLeadingDigits()) { 400 jsArrayBuilder.append(metadata.getLeadingDigits()); 401 } else { 402 jsArrayBuilder.append(null); 403 } 404 // optional PhoneNumberDesc no_international_dialling = 24; 405 toJsArray(metadata.getNoInternationalDialling(), jsArrayBuilder); 406 // optional PhoneNumberDesc uan = 25; 407 toJsArray(metadata.getUan(), jsArrayBuilder); 408 // optional bool leading_zero_possible = 26 [default=false]; 409 if (metadata.isLeadingZeroPossible()) { 410 jsArrayBuilder.append(1); 411 } else { 412 jsArrayBuilder.append(null); 413 } 414 // optional PhoneNumberDesc emergency = 27; 415 toJsArray(metadata.getEmergency(), jsArrayBuilder); 416 // optional PhoneNumberDesc voicemail = 28; 417 toJsArray(metadata.getVoicemail(), jsArrayBuilder); 418 // optional PhoneNumberDesc short_code = 29; 419 toJsArray(metadata.getShortCode(), jsArrayBuilder); 420 // optional PhoneNumberDesc standard_rate = 30; 421 toJsArray(metadata.getStandardRate(), jsArrayBuilder); 422 // optional PhoneNumberDesc carrier_specific = 31; 423 toJsArray(metadata.getCarrierSpecific(), jsArrayBuilder); 424 // optional bool mobile_number_portable_region = 32 [default=false]; 425 // left as null because this data is not used in the current JS API's. 426 jsArrayBuilder.append(null); 427 // optional PhoneNumberDesc sms_services = 33; 428 toJsArray(metadata.getSmsServices(), jsArrayBuilder); 429 430 jsArrayBuilder.endArray(); 431 } 432 } 433