├── package.json ├── README.md ├── lib └── request.js ├── tests ├── libphonenumnber-convert.js └── fixtures │ └── PhoneNumberMetadata.xml └── index.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libphonenumber-convert", 3 | "version": "0.1.3", 4 | "description": "Convert libphonenumber XML to JavaScript", 5 | "main": "index.js", 6 | "dependencies": { 7 | "xml2js": "~0.4.0" 8 | }, 9 | "scripts": { 10 | "test": "nodeunit tests" 11 | }, 12 | "author": "Andris Reinman", 13 | "license": "Apache 2.0", 14 | "devDependencies": { 15 | "nodeunit": "~0.8.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libphonenumber-convert 2 | 3 | Fetches and converts libphonenumber XML file to a JavaScript file 4 | 5 | ## Install 6 | 7 | npm install libphonenumber-convert 8 | 9 | ## Usage 10 | 11 | var converter = require("libphonenumber-convert"); 12 | var fs = require("fs"); 13 | var url = "http://..."; // url to libphonenumber XML file 14 | converter(url, function(err, js){ 15 | if(err){ 16 | throw err; 17 | } 18 | fs.writeFile("MetaData.js", js); 19 | }); 20 | 21 | ## Tests 22 | 23 | Tests are handled by nodeunit. There are not much tests, only some basics to test if the thing even works or not. 24 | 25 | Clone the repository, install dependencies and run tests with `npm test`: 26 | 27 | git clone git://github.com/andris9/libphonenumber-convert.git 28 | cd libphonenumber-convert 29 | npm install 30 | npm test 31 | 32 | ## License 33 | 34 | **Apache 2.0** 35 | -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var http = require("http"); 4 | 5 | /** 6 | * Returns URL contents as a Buffer 7 | * 8 | * @param {String} url Url to be retrieved 9 | * @param {Function} callback Callback function with the possible error object and url contents 10 | */ 11 | module.exports = function(url, callback){ 12 | var responseSent = false, req; 13 | 14 | req = http.get(url, function(res){ 15 | var chunks = [], chunkLength = 0; 16 | 17 | if(res.statusCode >= 300){ 18 | return callback(new Error("Invalid status code " + res.statusCode)); 19 | } 20 | 21 | res.on("error", function(err){ 22 | if(responseSent){ 23 | return; 24 | } 25 | responseSent = true; 26 | return callback(err); 27 | }); 28 | 29 | res.on("data", function(chunk){ 30 | if(chunk && chunk.length){ 31 | chunks.push(chunk); 32 | chunkLength += chunk.length; 33 | } 34 | }); 35 | 36 | res.on("end", function(){ 37 | if(responseSent){ 38 | return; 39 | } 40 | responseSent = true; 41 | return callback(null, Buffer.concat(chunks, chunkLength)); 42 | }); 43 | }); 44 | 45 | req.on("error", function(err){ 46 | if(responseSent){ 47 | return; 48 | } 49 | responseSent = true; 50 | callback(err); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /tests/libphonenumnber-convert.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"), 4 | vm = require('vm'), 5 | http = require('http'), 6 | libphonenumberConvert = require("../index"), 7 | fixturePath = __dirname + "/fixtures/PhoneNumberMetadata.xml", 8 | fixture = fs.readFileSync(fixturePath); 9 | 10 | var HTTP_PORT = 1434; 11 | 12 | module.exports["Convert XML to JS"] = function(test){ 13 | libphonenumberConvert.convert(fixture, function(err, js){ 14 | test.ifError(err); 15 | var sandbox = {}; 16 | vm.runInNewContext(js, sandbox, 'PhoneNumberMetadata.js'); 17 | test.ok(sandbox.PHONE_NUMBER_META_DATA); 18 | test.equal(typeof sandbox.PHONE_NUMBER_META_DATA, "object"); 19 | test.done(); 20 | }); 21 | } 22 | 23 | module.exports["Convert to JS"] = { 24 | 25 | setUp: function(next){ 26 | var that = this; 27 | libphonenumberConvert.convert(fixture, function(err, js){ 28 | var sandbox = {}; 29 | vm.runInNewContext(js, sandbox, 'PhoneNumberMetadata.js'); 30 | that.PHONE_NUMBER_META_DATA = sandbox.PHONE_NUMBER_META_DATA; 31 | next(); 32 | }); 33 | }, 34 | 35 | "Only numeric properties": function(test){ 36 | test.ok(this.PHONE_NUMBER_META_DATA); 37 | Object.keys(this.PHONE_NUMBER_META_DATA).forEach(function(key){ 38 | test.ok(!isNaN(key)); 39 | }); 40 | 41 | test.done(); 42 | }, 43 | 44 | "372": function(test){ 45 | var expected = [ 46 | "EE", 47 | "00", 48 | null, 49 | null, 50 | null, 51 | null, 52 | "\\d{4,10}", 53 | "1\\d{3,4}|[3-9]\\d{6,7}|800\\d{6,7}", 54 | [ 55 | [ 56 | "([3-79]\\d{2})(\\d{4})", 57 | "$1 $2", 58 | "[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]", 59 | null, 60 | null 61 | ], 62 | [ 63 | "(70)(\\d{2})(\\d{4})", 64 | "$1 $2 $3", 65 | "70", 66 | null, 67 | null 68 | ], 69 | [ 70 | "(8000)(\\d{3})(\\d{3})", 71 | "$1 $2 $3", 72 | "800", 73 | null, 74 | null 75 | ], 76 | [ 77 | "([458]\\d{3})(\\d{3,4})", 78 | "$1 $2", 79 | "40|5|8(?:00|[1-5])", 80 | null, 81 | null 82 | ] 83 | ] 84 | ]; 85 | 86 | test.equal(typeof this.PHONE_NUMBER_META_DATA["372"], "string"); 87 | 88 | test.deepEqual(JSON.parse(this.PHONE_NUMBER_META_DATA["372"]), expected); 89 | test.done(); 90 | } 91 | 92 | }; 93 | 94 | module.exports["Download and Convert"] = { 95 | setUp: function(next){ 96 | this.server = http.createServer(function (req, res) { 97 | res.writeHead(200, {'Content-Type': 'text/xml'}); 98 | res.end(fixture); 99 | }).listen(HTTP_PORT, '127.0.0.1'); 100 | next(); 101 | }, 102 | 103 | tearDown: function(next){ 104 | this.server.close(next); 105 | }, 106 | 107 | convert: function(test){ 108 | libphonenumberConvert("http://localhost:" + HTTP_PORT + "/", function(err, js){ 109 | test.ifError(err); 110 | test.equal(typeof js["372"], "string"); 111 | test.done(); 112 | }); 113 | } 114 | }; 115 | 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var parseString = require('xml2js').parseString, 4 | request = require("./lib/request"); 5 | 6 | /** 7 | * Downloads the libphonenumber xml file from provided location 8 | * and returns a JavaScript file with converted data 9 | * 10 | * @param {String} xmlUrl URL to libphonenumber xml 11 | * @param {Function} callback Callback function with the error object and JavaScript file 12 | */ 13 | module.exports = function(xmlUrl, callback){ 14 | request(xmlUrl, function(err, xml){ 15 | if(err){ 16 | return callback(err); 17 | } 18 | convert(xml, callback); 19 | }); 20 | }; 21 | 22 | module.exports.convert = convert; 23 | 24 | function convert(xml, callback){ 25 | parseString(xml, function(err, result){ 26 | if(err){ 27 | return callback(err); 28 | } 29 | convertTerritories(result, function(err, data){ 30 | if(err){ 31 | return callback(err); 32 | } 33 | return callback(null, formatJS(data)); 34 | }); 35 | }); 36 | } 37 | 38 | function nodeToText(str){ 39 | return (str || "").toString().replace(/\s/g, "") || null; 40 | } 41 | 42 | function convertTerritories(data, callback){ 43 | var metadata = {}, 44 | territories = [].concat(data && data.phoneNumberMetadata && data.phoneNumberMetadata.territories || []).shift(), 45 | territoryArray = territories && territories.territory; 46 | 47 | if(!Array.isArray(territoryArray)){ 48 | return callback(new Error("Invalid XML content, 'territories' not found")); 49 | } 50 | 51 | var mainCountriesForCode = {}; 52 | territoryArray.forEach(function(territory){ 53 | if(!territory || !territory.$ || !territory.$.countryCode){ 54 | return; // skip invalid value 55 | } 56 | if(!metadata.hasOwnProperty(territory.$.countryCode)){ 57 | metadata[territory.$.countryCode] = []; 58 | } 59 | metadata[territory.$.countryCode].push(formatTerritory(territory)); 60 | if(territory.$.mainCountryForCode == "true"){ 61 | mainCountriesForCode[territory.$.countryCode] = territory.$.id; 62 | } 63 | }); 64 | 65 | // Sort the array, so the main country would always be the first array item 66 | Object.keys(mainCountriesForCode).forEach(function(countryCode){ 67 | metadata[countryCode].sort(function(a, b){ 68 | if(a[0] == mainCountriesForCode[countryCode]){ 69 | return -1; 70 | } 71 | if(b[0] == mainCountriesForCode[countryCode]){ 72 | return 1; 73 | } 74 | return 0; 75 | }); 76 | }); 77 | 78 | // Convert country info to JSON 79 | Object.keys(metadata).forEach(function(countryCode){ 80 | metadata[countryCode] = metadata[countryCode].map(function(country){ 81 | return JSON.stringify(country); 82 | }); 83 | if(metadata[countryCode].length == 1){ 84 | metadata[countryCode] = metadata[countryCode].shift(); 85 | } 86 | }); 87 | 88 | callback(null, metadata); 89 | } 90 | 91 | function formatTerritory(territory){ 92 | var res = [], 93 | generalDesc = [].concat(territory.generalDesc || []).shift(); 94 | 95 | res.push(territory.$.id); 96 | res.push(territory.$.internationalPrefix); 97 | res.push(territory.$.nationalPrefix); 98 | 99 | res.push( 100 | territory.$.id != "BR" ? 101 | nodeToText(territory.$.nationalPrefixForParsing) : 102 | "(?:0|90)(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?"); 103 | 104 | res.push(territory.$.nationalPrefixTransformRule); 105 | res.push(territory.$.nationalPrefixFormattingRule); 106 | res.push([].concat(generalDesc && generalDesc.possibleNumberPattern || []).shift()); 107 | res.push(nodeToText([].concat(generalDesc && generalDesc.nationalNumberPattern || []).shift())); 108 | 109 | // enumerate territory.availableFormats[0].numberFormat[] 110 | res.push([].concat(([].concat(territory.availableFormats || []).shift() || {}).numberFormat || []). 111 | map(function(format){ 112 | return [ 113 | format.$.pattern, 114 | [].concat(format.format || []).shift(), 115 | nodeToText([].concat(format.leadingDigits || []).shift()) || "", 116 | format.$.nationalPrefixFormattingRule, 117 | Array.isArray(format.intlFormat) && format.intlFormat.length == 1 ? format.intlFormat[0] : undefined 118 | ]; 119 | })); 120 | 121 | return res; 122 | } 123 | 124 | function formatJS(data){ 125 | return "/* Automatically generated. Do not edit. */\n"+ 126 | "var PHONE_NUMBER_META_DATA = " + JSON.stringify(data) + ";\n"; 127 | } 128 | -------------------------------------------------------------------------------- /tests/fixtures/PhoneNumberMetadata.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 8 | 9 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ]> 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | [369]| 66 | 4[3-8]| 67 | 5(?: 68 | [0-2]| 69 | 5[0-478]| 70 | 6[45] 71 | )| 72 | 7[1-9] 73 | 74 | 75 | [369]| 76 | 4[3-8]| 77 | 5(?: 78 | [02]| 79 | 1(?: 80 | [0-8]| 81 | 95 82 | )| 83 | 5[0-478]| 84 | 6(?: 85 | 4[0-4]| 86 | 5[1-589] 87 | ) 88 | )| 89 | 7[1-9] 90 | 91 | $1 $2 92 | 93 | 94 | 70 95 | $1 $2 $3 96 | 97 | 98 | 800 99 | 8000 100 | $1 $2 $3 101 | 102 | 103 | 104 | 40| 105 | 5| 106 | 8(?: 107 | 00| 108 | [1-5] 109 | ) 110 | 111 | 112 | 40| 113 | 5| 114 | 8(?: 115 | 00[1-9]| 116 | [1-5] 117 | ) 118 | 119 | $1 $2 120 | 121 | 122 | 123 | 124 | 1\d{3,4}| 125 | [3-9]\d{6,7}| 126 | 800\d{6,7} 127 | 128 | \d{4,10} 129 | 130 | 131 | 132 | 1\d{3,4}| 133 | 800[2-9]\d{3} 134 | 135 | \d{4,7} 136 | 8002123 137 | 138 | 139 | 140 | 141 | (?: 142 | 3[23589]| 143 | 4(?: 144 | 0\d| 145 | [3-8] 146 | )| 147 | 6\d| 148 | 7[1-9]| 149 | 88 150 | )\d{5} 151 | 152 | \d{7,8} 153 | 3212345 154 | 155 | 156 | 158 | 159 | (?: 160 | 5\d| 161 | 8[1-5] 162 | )\d{6}| 163 | 5(?: 164 | [02]\d{2}| 165 | 1(?: 166 | [0-8]\d| 167 | 95 168 | )| 169 | 5[0-478]\d| 170 | 64[0-4]| 171 | 65[1-589] 172 | )\d{3} 173 | 174 | \d{7,8} 175 | 51234567 176 | 177 | 178 | 179 | 800(?: 180 | 0\d{3}| 181 | 1\d| 182 | [2-9] 183 | )\d{3} 184 | 185 | \d{7,10} 186 | 80012345 187 | 188 | 189 | 900\d{4} 190 | \d{7} 191 | 9001234 192 | 193 | 194 | 70[0-2]\d{5} 195 | \d{8} 196 | 70012345 197 | 198 | 199 | 201 | 202 | 1(?: 203 | 2[01245]| 204 | 3[0-6]| 205 | 4[1-489]| 206 | 5[0-59]| 207 | 6[1-46-9]| 208 | 7[0-27-9]| 209 | 8[189]| 210 | 9[012] 211 | )\d{1,2} 212 | 213 | \d{4,5} 214 | 12123 215 | 216 | 217 | 218 | 219 | --------------------------------------------------------------------------------