/*! * 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 ) );