/* eslint-disable no-tabs */ /*! * jQuery Internationalization library * * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar * * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do * anything special to choose one license or the other and you don't have to * notify anyone which license you are using. You are free to use * UniversalLanguageSelector in commercial projects as long as the copyright * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. * * @licence GNU General Public Licence 2.0 or later * @licence MIT License */ (function ($) { "use strict"; var MessageParser = function (options) { this.options = $.extend({}, $.i18n.parser.defaults, options); this.language = $.i18n.languages[String.locale] || $.i18n.languages.default; this.emitter = $.i18n.parser.emitter; }; MessageParser.prototype = { constructor: MessageParser, simpleParse: function (message, parameters) { return message.replace(/\$(\d+)/g, function (str, match) { var index = parseInt(match, 10) - 1; return parameters[index] !== undefined ? parameters[index] : "$" + match; }); }, parse: function (message, replacements) { if (message.indexOf("{{") < 0) { return this.simpleParse(message, replacements); } this.emitter.language = $.i18n.languages[$.i18n().locale] || $.i18n.languages.default; return this.emitter.emit(this.ast(message), replacements); }, ast: function (message) { var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral, templateContents, templateName, openTemplate, closeTemplate, expression, paramExpression, result, pos = 0; // Try parsers until one works, if none work return null function choice(parserSyntax) { return function () { var i, result; for (i = 0; i < parserSyntax.length; i++) { result = parserSyntax[i](); if (result !== null) { return result; } } return null; }; } // Try several parserSyntax-es in a row. // All must succeed; otherwise, return null. // This is the only eager one. function sequence(parserSyntax) { var i, res, originalPos = pos, result = []; for (i = 0; i < parserSyntax.length; i++) { res = parserSyntax[i](); if (res === null) { pos = originalPos; return null; } result.push(res); } return result; } // 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 () { var originalPos = pos, result = [], parsed = p(); while (parsed !== null) { result.push(parsed); parsed = p(); } if (result.length < n) { pos = originalPos; return null; } return result; }; } // Helpers -- just make parserSyntax out of simpler JS builtin types function makeStringParser(s) { var len = s.length; return function () { var result = null; if (message.slice(pos, pos + len) === s) { result = s; pos += len; } return result; }; } function makeRegexParser(regex) { return function () { var matches = message.slice(pos).match(regex); if (matches === null) { return null; } pos += matches[0].length; return matches[0]; }; } pipe = makeStringParser("|"); colon = makeStringParser(":"); backslash = makeStringParser("\\"); anyCharacter = makeRegexParser(/^./); dollar = makeStringParser("$"); digits = makeRegexParser(/^\d+/); regularLiteral = makeRegexParser(/^[^{}[\]$\\]/); regularLiteralWithoutBar = makeRegexParser(/^[^{}[\]$\\|]/); regularLiteralWithoutSpace = makeRegexParser(/^[^{}[\]$\s]/); // There is a general pattern: // parse a thing; // if it worked, apply transform, // otherwise return null. // But using this as a combinator seems to cause problems // when combined with nOrMore(). // May be some scoping issue. function transform(p, fn) { return function () { var result = p(); return result === null ? null : fn(result); }; } // Used to define "literals" within template parameters. The pipe // character is the parameter delimeter, so by default // it is not a literal in the parameter function literalWithoutBar() { var result = nOrMore(1, escapedOrLiteralWithoutBar)(); return result === null ? null : result.join(""); } function literal() { var result = nOrMore(1, escapedOrRegularLiteral)(); return result === null ? null : result.join(""); } function escapedLiteral() { var result = sequence([backslash, anyCharacter]); return result === null ? null : result[1]; } choice([escapedLiteral, regularLiteralWithoutSpace]); escapedOrLiteralWithoutBar = choice([escapedLiteral, regularLiteralWithoutBar]); escapedOrRegularLiteral = choice([escapedLiteral, regularLiteral]); function replacement() { var result = sequence([dollar, digits]); if (result === null) { return null; } return ["REPLACE", parseInt(result[1], 10) - 1]; } templateName = transform( // see $wgLegalTitleChars // not allowing : due to the need to catch "PLURAL:$1" makeRegexParser(/^[ !"$&'()*,./0-9;=?@A-Z^_`a-z~\x80-\xFF+-]+/), function (result) { return result.toString(); } ); function templateParam() { var expr, result = sequence([pipe, nOrMore(0, paramExpression)]); if (result === null) { return null; } expr = result[1]; // use a "CONCAT" operator if there are multiple nodes, // otherwise return the first node, raw. return expr.length > 1 ? ["CONCAT"].concat(expr) : expr[0]; } function templateWithReplacement() { var result = sequence([templateName, colon, replacement]); return result === null ? null : [result[0], result[2]]; } function templateWithOutReplacement() { var result = sequence([templateName, colon, paramExpression]); return result === null ? null : [result[0], result[2]]; } templateContents = choice([ function () { var res = sequence([ // templates can have placeholders for dynamic // replacement eg: {{PLURAL:$1|one car|$1 cars}} // or no placeholders eg: // {{GRAMMAR:genitive|{{SITENAME}}} choice([templateWithReplacement, templateWithOutReplacement]), nOrMore(0, templateParam) ]); return res === null ? null : res[0].concat(res[1]); }, function () { var res = sequence([templateName, nOrMore(0, templateParam)]); if (res === null) { return null; } return [res[0]].concat(res[1]); } ]); openTemplate = makeStringParser("{{"); closeTemplate = makeStringParser("}}"); function template() { var result = sequence([openTemplate, templateContents, closeTemplate]); return result === null ? null : result[1]; } expression = choice([template, replacement, literal]); paramExpression = choice([template, replacement, literalWithoutBar]); function start() { var result = nOrMore(0, expression)(); if (result === null) { return null; } return ["CONCAT"].concat(result); } result = start(); /* * For success, the pos must have gotten to the end of the input * 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 !== message.length) { throw new Error("Parse error at position " + pos.toString() + " in input: " + message); } return result; } }; $.extend($.i18n.parser, new MessageParser()); }(jQuery));