From 224b22a051051f6c2e494c3a2fb4adb42898e2d1 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Tue, 14 Jan 2014 19:24:18 +0100 Subject: Update to MediaWiki 1.22.1 --- resources/mediawiki.libs/CLDRPluralRuleParser.js | 356 ++++++++++++++++------- 1 file changed, 258 insertions(+), 98 deletions(-) (limited to 'resources') diff --git a/resources/mediawiki.libs/CLDRPluralRuleParser.js b/resources/mediawiki.libs/CLDRPluralRuleParser.js index 441bc91f..3def37c5 100644 --- a/resources/mediawiki.libs/CLDRPluralRuleParser.js +++ b/resources/mediawiki.libs/CLDRPluralRuleParser.js @@ -1,7 +1,7 @@ -/* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */ +/* This is CLDRPluralRuleParser v1.1, ported to MediaWiki ResourceLoader */ /** -* cldrpluralparser.js +* CLDRPluralRuleParser.js * A parser engine for CLDR plural rules. * * Copyright 2012 GPLV3+, Santhosh Thottingal @@ -13,59 +13,83 @@ * @author Amir Aharoni */ +( function ( mw ) { /** * Evaluates a plural rule in CLDR syntax for a number - * @param rule - * @param number - * @return true|false|null + * @param {string} rule + * @param {integer} number + * @return {boolean} true if evaluation passed, false if evaluation failed. */ -( function( mw ) { function pluralRuleParser(rule, number) { /* Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules ----------------------------------------------------------------- - condition = and_condition ('or' and_condition)* + ('@integer' samples)? + ('@decimal' samples)? and_condition = relation ('and' relation)* - relation = is_relation | in_relation | within_relation | 'n' + relation = is_relation | in_relation | within_relation is_relation = expr 'is' ('not')? value - in_relation = expr ('not')? 'in' range_list + in_relation = expr (('not')? 'in' | '=' | '!=') range_list within_relation = expr ('not')? 'within' range_list - expr = 'n' ('mod' value)? + expr = operand (('mod' | '%') value)? + operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' range_list = (range | value) (',' range_list)* value = digit+ digit = 0|1|2|3|4|5|6|7|8|9 range = value'..'value - + samples = sampleRange (',' sampleRange)* (',' ('…'|'...'))? + sampleRange = decimalValue '~' decimalValue + decimalValue = value ('.' value)? */ + + // we don't evaluate the samples section of the rule. Ignore it. + rule = rule.split('@')[0].trim(); + + if (!rule.length) { + // empty rule or 'other' rule. + return true; + } // Indicates current position in the rule as we parse through it. // Shared among all parsing functions below. - var pos = 0; - - var whitespace = makeRegexParser(/^\s+/); - var digits = makeRegexParser(/^\d+/); - - var _n_ = makeStringParser('n'); - var _is_ = makeStringParser('is'); - var _mod_ = makeStringParser('mod'); - var _not_ = makeStringParser('not'); - var _in_ = makeStringParser('in'); - var _within_ = makeStringParser('within'); - var _range_ = makeStringParser('..'); - var _comma_ = makeStringParser(','); - var _or_ = makeStringParser('or'); - var _and_ = makeStringParser('and'); + var pos = 0, + operand, + expression, + relation, + result, + whitespace = makeRegexParser(/^\s+/), + value = makeRegexParser(/^\d+/), + _n_ = makeStringParser('n'), + _i_ = makeStringParser('i'), + _f_ = makeStringParser('f'), + _t_ = makeStringParser('t'), + _v_ = makeStringParser('v'), + _w_ = makeStringParser('w'), + _is_ = makeStringParser('is'), + _isnot_ = makeStringParser('is not'), + _isnot_sign_ = makeStringParser('!='), + _equal_ = makeStringParser('='), + _mod_ = makeStringParser('mod'), + _percent_ = makeStringParser('%'), + _not_ = makeStringParser('not'), + _in_ = makeStringParser('in'), + _within_ = makeStringParser('within'), + _range_ = makeStringParser('..'), + _comma_ = makeStringParser(','), + _or_ = makeStringParser('or'), + _and_ = makeStringParser('and'); function debug() { - /* console.log.apply(console, arguments);*/ + // console.log.apply(console, arguments); } debug('pluralRuleParser', rule, number); // Try parsers until one works, if none work return null + function choice(parserSyntax) { - return function () { + return function() { for (var i = 0; i < parserSyntax.length; i++) { var result = parserSyntax[i](); if (result !== null) { @@ -79,6 +103,7 @@ function pluralRuleParser(rule, number) { // Try several parserSyntax-es in a row. // All must succeed; otherwise, return null. // This is the only eager one. + function sequence(parserSyntax) { var originalPos = pos; var result = []; @@ -95,8 +120,9 @@ function pluralRuleParser(rule, number) { // Run the same parser over and over until it fails. // Must succeed a minimum of n times; otherwise, return null. + function nOrMore(n, p) { - return function () { + return function() { var originalPos = pos; var result = []; var parsed = p(); @@ -113,21 +139,21 @@ function pluralRuleParser(rule, number) { } // Helpers -- just make parserSyntax out of simpler JS builtin types - function makeStringParser(s) { var len = s.length; - return function () { + return function() { var result = null; if (rule.substr(pos, len) === s) { result = s; pos += len; } + return result; }; } function makeRegexParser(regex) { - return function () { + return function() { var matches = rule.substr(pos).match(regex); if (matches === null) { return null; @@ -137,62 +163,166 @@ function pluralRuleParser(rule, number) { }; } + /* + * integer digits of n. + */ + function i() { + var result = _i_(); + if (result === null) { + debug(' -- failed i', parseInt(number, 10)); + return result; + } + result = parseInt(number, 10); + debug(' -- passed i ', result); + return result; + } + + /* + * absolute value of the source number (integer and decimals). + */ function n() { var result = _n_(); if (result === null) { - debug(" -- failed n"); + debug(' -- failed n ', number); return result; } - result = parseInt(number, 10); - debug(" -- passed n ", result); + result = parseFloat(number, 10); + debug(' -- passed n ', result); + return result; + } + + /* + * visible fractional digits in n, with trailing zeros. + */ + function f() { + var result = _f_(); + if (result === null) { + debug(' -- failed f ', number); + return result; + } + result = (number + '.').split('.')[1] || 0; + debug(' -- passed f ', result); + return result; + } + + /* + * visible fractional digits in n, without trailing zeros. + */ + function t() { + var result = _t_(); + if (result === null) { + debug(' -- failed t ', number); + return result; + } + result = (number + '.').split('.')[1].replace(/0$/, '') || 0; + debug(' -- passed t ', result); + return result; + } + + /* + * number of visible fraction digits in n, with trailing zeros. + */ + function v() { + var result = _v_(); + if (result === null) { + debug(' -- failed v ', number); + return result; + } + result = (number + '.').split('.')[1].length || 0; + debug(' -- passed v ', result); + return result; + } + + /* + * number of visible fraction digits in n, without trailing zeros. + */ + function w() { + var result = _w_(); + if (result === null) { + debug(' -- failed w ', number); + return result; + } + result = (number + '.').split('.')[1].replace(/0$/, '').length || 0; + debug(' -- passed w ', result); return result; } - var expression = choice([mod, n]); + // operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' + operand = choice([n, i, f, t, v, w]); + + // expr = operand (('mod' | '%') value)? + expression = choice([mod, operand]); function mod() { - var result = sequence([n, whitespace, _mod_, whitespace, digits]); + var result = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]); if (result === null) { - debug(" -- failed mod"); + debug(' -- failed mod'); return null; } - debug(" -- passed mod"); + debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10)); return parseInt(result[0], 10) % parseInt(result[4], 10); } function not() { var result = sequence([whitespace, _not_]); if (result === null) { - debug(" -- failed not"); + debug(' -- failed not'); return null; - } else { - return result[1]; } + + return result[1]; } + // is_relation = expr 'is' ('not')? value function is() { - var result = sequence([expression, whitespace, _is_, nOrMore(0, not), whitespace, digits]); + var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]); if (result !== null) { - debug(" -- passed is"); - if (result[3][0] === 'not') { - return result[0] !== parseInt(result[5], 10); - } else { - return result[0] === parseInt(result[5], 10); + debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10)); + return result[0] === parseInt(result[4], 10); + } + debug(' -- failed is'); + return null; + } + + // is_relation = expr 'is' ('not')? value + function isnot() { + var result = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]); + if (result !== null) { + debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10)); + return result[0] !== parseInt(result[4], 10); + } + debug(' -- failed isnot'); + return null; + } + + function not_in() { + var result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]); + if (result !== null) { + debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]); + var range_list = result[4]; + for (var i = 0; i < range_list.length; i++) { + if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { + return false; + } } + return true; } - debug(" -- failed is"); + debug(' -- failed not_in'); return null; } + // range_list = (range | value) (',' range_list)* function rangeList() { - // range_list = (range | value) (',' range_list)* - var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]); + var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]); var resultList = []; if (result !== null) { - resultList = resultList.concat(result[0], result[1][0]); + resultList = resultList.concat(result[0]); + if (result[1][0]) { + resultList = resultList.concat(result[1][0]); + } return resultList; } - debug(" -- failed rangeList"); + debug(' -- failed rangeList'); return null; } @@ -202,111 +332,141 @@ function pluralRuleParser(rule, number) { if (result !== null) { return result[1]; } - debug(" -- failed rangeTail"); + debug(' -- failed rangeTail'); return null; } + // range = value'..'value + function range() { var i; - var result = sequence([digits, _range_, digits]); + var result = sequence([value, _range_, value]); if (result !== null) { - debug(" -- passed range"); + debug(' -- passed range'); var array = []; var left = parseInt(result[0], 10); var right = parseInt(result[2], 10); - for ( i = left; i <= right; i++) { + for (i = left; i <= right; i++) { array.push(i); } return array; } - debug(" -- failed range"); + debug(' -- failed range'); return null; } function _in() { // in_relation = expr ('not')? 'in' range_list - var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]); + var result = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]); if (result !== null) { - debug(" -- passed _in"); + debug(' -- passed _in:' + result); var range_list = result[5]; for (var i = 0; i < range_list.length; i++) { - if (parseInt(range_list[i], 10) === result[0]) { + if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { return (result[1][0] !== 'not'); } } return (result[1][0] === 'not'); } - debug(" -- failed _in "); + debug(' -- failed _in '); return null; } + /* + * The difference between in and within is that in only includes integers in the specified range, + * while within includes all values. + */ + function within() { - var result = sequence([expression, whitespace, _within_, whitespace, rangeList]); + // within_relation = expr ('not')? 'within' range_list + var result = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]); if (result !== null) { - debug(" -- passed within "); - var range_list = result[4]; - return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10)); + debug(' -- passed within'); + var range_list = result[5]; + if ((result[0] >= parseInt(range_list[0], 10)) && + (result[0] < parseInt(range_list[range_list.length - 1], 10))) { + return (result[1][0] !== 'not'); + } + return (result[1][0] === 'not'); } - debug(" -- failed within "); + debug(' -- failed within '); return null; } + // relation = is_relation | in_relation | within_relation + relation = choice([is, not_in, isnot, _in, within]); - var relation = choice([is, _in, within]); - + // and_condition = relation ('and' relation)* function and() { - var result = sequence([relation, whitespace, _and_, whitespace, condition]); + var result = sequence([relation, nOrMore(0, andTail)]); if (result) { - debug(" -- passed and"); - return result[0] && result[4]; + if (!result[0]) { + return false; + } + for (var i = 0; i < result[1].length; i++) { + if (!result[1][i]) { + return false; + } + } + return true; } - debug(" -- failed and"); + debug(' -- failed and'); return null; } - function or() { - var result = sequence([relation, whitespace, _or_, whitespace, condition]); - if (result) { - debug(" -- passed or"); - return result[0] || result[4]; + // ('and' relation)* + function andTail() { + var result = sequence([whitespace, _and_, whitespace, relation]); + if (result !== null) { + debug(' -- passed andTail' + result); + return result[3]; } - debug(" -- failed or"); + debug(' -- failed andTail'); return null; - } - var condition = choice([and, or, relation]); + } + // ('or' and_condition)* + function orTail() { + var result = sequence([whitespace, _or_, whitespace, and]); + if (result !== null) { + debug(' -- passed orTail: ' + result[3]); + return result[3]; + } + debug(' -- failed orTail'); + return null; - function isInt(n) { - return parseFloat(n) % 1 === 0; } + // condition = and_condition ('or' and_condition)* + function condition() { + var result = sequence([and, nOrMore(0, orTail)]); + if (result) { + for (var i = 0; i < result[1].length; i++) { + if (result[1][i]) { + return true; + } + } + return result[0]; - function start() { - if (!isInt(number)) { - return false; } - var result = condition(); - return result; + return false; } - - var result = start(); - + result = condition(); /* * For success, the pos must have gotten to the end of the rule * and returned a non-null. * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. */ - if (result === null || pos !== rule.length) { - // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result); + if (result === null) { + throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule); } - return result; -} + if (pos !== rule.length) { + debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule); + } -/* For module loaders, e.g. NodeJS, NPM */ -if (typeof module !== 'undefined' && module.exports) { - module.exports = pluralRuleParser; + return result; } /* pluralRuleParser ends here */ -- cgit v1.2.2