[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 /* This is CLDRPluralRuleParser v1.1, ported to MediaWiki ResourceLoader */ 2 3 /** 4 * CLDRPluralRuleParser.js 5 * A parser engine for CLDR plural rules. 6 * 7 * Copyright 2012 GPLV3+, Santhosh Thottingal 8 * 9 * @version 0.1.0-alpha 10 * @source https://github.com/santhoshtr/CLDRPluralRuleParser 11 * @author Santhosh Thottingal <[email protected]> 12 * @author Timo Tijhof 13 * @author Amir Aharoni 14 */ 15 16 ( function ( mw ) { 17 /** 18 * Evaluates a plural rule in CLDR syntax for a number 19 * @param {string} rule 20 * @param {integer} number 21 * @return {boolean} true if evaluation passed, false if evaluation failed. 22 */ 23 24 function pluralRuleParser(rule, number) { 25 /* 26 Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules 27 ----------------------------------------------------------------- 28 condition = and_condition ('or' and_condition)* 29 ('@integer' samples)? 30 ('@decimal' samples)? 31 and_condition = relation ('and' relation)* 32 relation = is_relation | in_relation | within_relation 33 is_relation = expr 'is' ('not')? value 34 in_relation = expr (('not')? 'in' | '=' | '!=') range_list 35 within_relation = expr ('not')? 'within' range_list 36 expr = operand (('mod' | '%') value)? 37 operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' 38 range_list = (range | value) (',' range_list)* 39 value = digit+ 40 digit = 0|1|2|3|4|5|6|7|8|9 41 range = value'..'value 42 samples = sampleRange (',' sampleRange)* (',' ('…'|'...'))? 43 sampleRange = decimalValue '~' decimalValue 44 decimalValue = value ('.' value)? 45 */ 46 47 // we don't evaluate the samples section of the rule. Ignore it. 48 rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, ''); 49 50 if (!rule.length) { 51 // empty rule or 'other' rule. 52 return true; 53 } 54 // Indicates current position in the rule as we parse through it. 55 // Shared among all parsing functions below. 56 var pos = 0, 57 operand, 58 expression, 59 relation, 60 result, 61 whitespace = makeRegexParser(/^\s+/), 62 value = makeRegexParser(/^\d+/), 63 _n_ = makeStringParser('n'), 64 _i_ = makeStringParser('i'), 65 _f_ = makeStringParser('f'), 66 _t_ = makeStringParser('t'), 67 _v_ = makeStringParser('v'), 68 _w_ = makeStringParser('w'), 69 _is_ = makeStringParser('is'), 70 _isnot_ = makeStringParser('is not'), 71 _isnot_sign_ = makeStringParser('!='), 72 _equal_ = makeStringParser('='), 73 _mod_ = makeStringParser('mod'), 74 _percent_ = makeStringParser('%'), 75 _not_ = makeStringParser('not'), 76 _in_ = makeStringParser('in'), 77 _within_ = makeStringParser('within'), 78 _range_ = makeStringParser('..'), 79 _comma_ = makeStringParser(','), 80 _or_ = makeStringParser('or'), 81 _and_ = makeStringParser('and'); 82 83 function debug() { 84 // console.log.apply(console, arguments); 85 } 86 87 debug('pluralRuleParser', rule, number); 88 89 // Try parsers until one works, if none work return null 90 91 function choice(parserSyntax) { 92 return function() { 93 for (var i = 0; i < parserSyntax.length; i++) { 94 var result = parserSyntax[i](); 95 if (result !== null) { 96 return result; 97 } 98 } 99 return null; 100 }; 101 } 102 103 // Try several parserSyntax-es in a row. 104 // All must succeed; otherwise, return null. 105 // This is the only eager one. 106 107 function sequence(parserSyntax) { 108 var originalPos = pos; 109 var result = []; 110 for (var i = 0; i < parserSyntax.length; i++) { 111 var res = parserSyntax[i](); 112 if (res === null) { 113 pos = originalPos; 114 return null; 115 } 116 result.push(res); 117 } 118 return result; 119 } 120 121 // Run the same parser over and over until it fails. 122 // Must succeed a minimum of n times; otherwise, return null. 123 124 function nOrMore(n, p) { 125 return function() { 126 var originalPos = pos; 127 var result = []; 128 var parsed = p(); 129 while (parsed !== null) { 130 result.push(parsed); 131 parsed = p(); 132 } 133 if (result.length < n) { 134 pos = originalPos; 135 return null; 136 } 137 return result; 138 }; 139 } 140 141 // Helpers -- just make parserSyntax out of simpler JS builtin types 142 function makeStringParser(s) { 143 var len = s.length; 144 return function() { 145 var result = null; 146 if (rule.substr(pos, len) === s) { 147 result = s; 148 pos += len; 149 } 150 151 return result; 152 }; 153 } 154 155 function makeRegexParser(regex) { 156 return function() { 157 var matches = rule.substr(pos).match(regex); 158 if (matches === null) { 159 return null; 160 } 161 pos += matches[0].length; 162 return matches[0]; 163 }; 164 } 165 166 /* 167 * integer digits of n. 168 */ 169 function i() { 170 var result = _i_(); 171 if (result === null) { 172 debug(' -- failed i', parseInt(number, 10)); 173 return result; 174 } 175 result = parseInt(number, 10); 176 debug(' -- passed i ', result); 177 return result; 178 } 179 180 /* 181 * absolute value of the source number (integer and decimals). 182 */ 183 function n() { 184 var result = _n_(); 185 if (result === null) { 186 debug(' -- failed n ', number); 187 return result; 188 } 189 result = parseFloat(number, 10); 190 debug(' -- passed n ', result); 191 return result; 192 } 193 194 /* 195 * visible fractional digits in n, with trailing zeros. 196 */ 197 function f() { 198 var result = _f_(); 199 if (result === null) { 200 debug(' -- failed f ', number); 201 return result; 202 } 203 result = (number + '.').split('.')[1] || 0; 204 debug(' -- passed f ', result); 205 return result; 206 } 207 208 /* 209 * visible fractional digits in n, without trailing zeros. 210 */ 211 function t() { 212 var result = _t_(); 213 if (result === null) { 214 debug(' -- failed t ', number); 215 return result; 216 } 217 result = (number + '.').split('.')[1].replace(/0$/, '') || 0; 218 debug(' -- passed t ', result); 219 return result; 220 } 221 222 /* 223 * number of visible fraction digits in n, with trailing zeros. 224 */ 225 function v() { 226 var result = _v_(); 227 if (result === null) { 228 debug(' -- failed v ', number); 229 return result; 230 } 231 result = (number + '.').split('.')[1].length || 0; 232 debug(' -- passed v ', result); 233 return result; 234 } 235 236 /* 237 * number of visible fraction digits in n, without trailing zeros. 238 */ 239 function w() { 240 var result = _w_(); 241 if (result === null) { 242 debug(' -- failed w ', number); 243 return result; 244 } 245 result = (number + '.').split('.')[1].replace(/0$/, '').length || 0; 246 debug(' -- passed w ', result); 247 return result; 248 } 249 250 // operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' 251 operand = choice([n, i, f, t, v, w]); 252 253 // expr = operand (('mod' | '%') value)? 254 expression = choice([mod, operand]); 255 256 function mod() { 257 var result = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]); 258 if (result === null) { 259 debug(' -- failed mod'); 260 return null; 261 } 262 debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10)); 263 return parseInt(result[0], 10) % parseInt(result[4], 10); 264 } 265 266 function not() { 267 var result = sequence([whitespace, _not_]); 268 if (result === null) { 269 debug(' -- failed not'); 270 return null; 271 } 272 273 return result[1]; 274 } 275 276 // is_relation = expr 'is' ('not')? value 277 function is() { 278 var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]); 279 if (result !== null) { 280 debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10)); 281 return result[0] === parseInt(result[4], 10); 282 } 283 debug(' -- failed is'); 284 return null; 285 } 286 287 // is_relation = expr 'is' ('not')? value 288 function isnot() { 289 var result = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]); 290 if (result !== null) { 291 debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10)); 292 return result[0] !== parseInt(result[4], 10); 293 } 294 debug(' -- failed isnot'); 295 return null; 296 } 297 298 function not_in() { 299 var result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]); 300 if (result !== null) { 301 debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]); 302 var range_list = result[4]; 303 for (var i = 0; i < range_list.length; i++) { 304 if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { 305 return false; 306 } 307 } 308 return true; 309 } 310 debug(' -- failed not_in'); 311 return null; 312 } 313 314 // range_list = (range | value) (',' range_list)* 315 function rangeList() { 316 var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]); 317 var resultList = []; 318 if (result !== null) { 319 resultList = resultList.concat(result[0]); 320 if (result[1][0]) { 321 resultList = resultList.concat(result[1][0]); 322 } 323 return resultList; 324 } 325 debug(' -- failed rangeList'); 326 return null; 327 } 328 329 function rangeTail() { 330 // ',' range_list 331 var result = sequence([_comma_, rangeList]); 332 if (result !== null) { 333 return result[1]; 334 } 335 debug(' -- failed rangeTail'); 336 return null; 337 } 338 339 // range = value'..'value 340 341 function range() { 342 var i; 343 var result = sequence([value, _range_, value]); 344 if (result !== null) { 345 debug(' -- passed range'); 346 var array = []; 347 var left = parseInt(result[0], 10); 348 var right = parseInt(result[2], 10); 349 for (i = left; i <= right; i++) { 350 array.push(i); 351 } 352 return array; 353 } 354 debug(' -- failed range'); 355 return null; 356 } 357 358 function _in() { 359 // in_relation = expr ('not')? 'in' range_list 360 var result = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]); 361 if (result !== null) { 362 debug(' -- passed _in:' + result); 363 var range_list = result[5]; 364 for (var i = 0; i < range_list.length; i++) { 365 if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { 366 return (result[1][0] !== 'not'); 367 } 368 } 369 return (result[1][0] === 'not'); 370 } 371 debug(' -- failed _in '); 372 return null; 373 } 374 375 /* 376 * The difference between in and within is that in only includes integers in the specified range, 377 * while within includes all values. 378 */ 379 380 function within() { 381 // within_relation = expr ('not')? 'within' range_list 382 var result = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]); 383 if (result !== null) { 384 debug(' -- passed within'); 385 var range_list = result[5]; 386 if ((result[0] >= parseInt(range_list[0], 10)) && 387 (result[0] < parseInt(range_list[range_list.length - 1], 10))) { 388 return (result[1][0] !== 'not'); 389 } 390 return (result[1][0] === 'not'); 391 } 392 debug(' -- failed within '); 393 return null; 394 } 395 396 // relation = is_relation | in_relation | within_relation 397 relation = choice([is, not_in, isnot, _in, within]); 398 399 // and_condition = relation ('and' relation)* 400 function and() { 401 var result = sequence([relation, nOrMore(0, andTail)]); 402 if (result) { 403 if (!result[0]) { 404 return false; 405 } 406 for (var i = 0; i < result[1].length; i++) { 407 if (!result[1][i]) { 408 return false; 409 } 410 } 411 return true; 412 } 413 debug(' -- failed and'); 414 return null; 415 } 416 417 // ('and' relation)* 418 function andTail() { 419 var result = sequence([whitespace, _and_, whitespace, relation]); 420 if (result !== null) { 421 debug(' -- passed andTail' + result); 422 return result[3]; 423 } 424 debug(' -- failed andTail'); 425 return null; 426 427 } 428 // ('or' and_condition)* 429 function orTail() { 430 var result = sequence([whitespace, _or_, whitespace, and]); 431 if (result !== null) { 432 debug(' -- passed orTail: ' + result[3]); 433 return result[3]; 434 } 435 debug(' -- failed orTail'); 436 return null; 437 438 } 439 440 // condition = and_condition ('or' and_condition)* 441 function condition() { 442 var result = sequence([and, nOrMore(0, orTail)]); 443 if (result) { 444 for (var i = 0; i < result[1].length; i++) { 445 if (result[1][i]) { 446 return true; 447 } 448 } 449 return result[0]; 450 451 } 452 return false; 453 } 454 455 result = condition(); 456 /* 457 * For success, the pos must have gotten to the end of the rule 458 * and returned a non-null. 459 * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. 460 */ 461 if (result === null) { 462 throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule); 463 } 464 465 if (pos !== rule.length) { 466 debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule); 467 } 468 469 return result; 470 } 471 472 /* pluralRuleParser ends here */ 473 mw.libs.pluralRuleParser = pluralRuleParser; 474 475 } )( mediaWiki );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |