Merge "Don't check namespace in SpecialWantedtemplates"
[lhc/web/wiklou.git] / resources / lib / jquery.i18n / src / jquery.i18n.parser.js
1 /**
2 * jQuery Internationalization library
3 *
4 * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar
5 *
6 * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do
7 * anything special to choose one license or the other and you don't have to
8 * notify anyone which license you are using. You are free to use
9 * UniversalLanguageSelector in commercial projects as long as the copyright
10 * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
11 *
12 * @licence GNU General Public Licence 2.0 or later
13 * @licence MIT License
14 */
15
16 ( function ( $ ) {
17 'use strict';
18
19 var MessageParser = function ( options ) {
20 this.options = $.extend( {}, $.i18n.parser.defaults, options );
21 this.language = $.i18n.languages[String.locale] || $.i18n.languages['default'];
22 this.emitter = $.i18n.parser.emitter;
23 };
24
25 MessageParser.prototype = {
26
27 constructor: MessageParser,
28
29 simpleParse: function ( message, parameters ) {
30 return message.replace( /\$(\d+)/g, function ( str, match ) {
31 var index = parseInt( match, 10 ) - 1;
32
33 return parameters[index] !== undefined ? parameters[index] : '$' + match;
34 } );
35 },
36
37 parse: function ( message, replacements ) {
38 if ( message.indexOf( '{{' ) < 0 ) {
39 return this.simpleParse( message, replacements );
40 }
41
42 this.emitter.language = $.i18n.languages[$.i18n().locale] ||
43 $.i18n.languages['default'];
44
45 return this.emitter.emit( this.ast( message ), replacements );
46 },
47
48 ast: function ( message ) {
49 var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral,
50 regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar,
51 escapedOrRegularLiteral, templateContents, templateName, openTemplate,
52 closeTemplate, expression, paramExpression, result,
53 pos = 0;
54
55 // Try parsers until one works, if none work return null
56 function choice( parserSyntax ) {
57 return function () {
58 var i, result;
59
60 for ( i = 0; i < parserSyntax.length; i++ ) {
61 result = parserSyntax[i]();
62
63 if ( result !== null ) {
64 return result;
65 }
66 }
67
68 return null;
69 };
70 }
71
72 // Try several parserSyntax-es in a row.
73 // All must succeed; otherwise, return null.
74 // This is the only eager one.
75 function sequence( parserSyntax ) {
76 var i, res,
77 originalPos = pos,
78 result = [];
79
80 for ( i = 0; i < parserSyntax.length; i++ ) {
81 res = parserSyntax[i]();
82
83 if ( res === null ) {
84 pos = originalPos;
85
86 return null;
87 }
88
89 result.push( res );
90 }
91
92 return result;
93 }
94
95 // Run the same parser over and over until it fails.
96 // Must succeed a minimum of n times; otherwise, return null.
97 function nOrMore( n, p ) {
98 return function () {
99 var originalPos = pos,
100 result = [],
101 parsed = p();
102
103 while ( parsed !== null ) {
104 result.push( parsed );
105 parsed = p();
106 }
107
108 if ( result.length < n ) {
109 pos = originalPos;
110
111 return null;
112 }
113
114 return result;
115 };
116 }
117
118 // Helpers -- just make parserSyntax out of simpler JS builtin types
119
120 function makeStringParser( s ) {
121 var len = s.length;
122
123 return function () {
124 var result = null;
125
126 if ( message.substr( pos, len ) === s ) {
127 result = s;
128 pos += len;
129 }
130
131 return result;
132 };
133 }
134
135 function makeRegexParser( regex ) {
136 return function () {
137 var matches = message.substr( pos ).match( regex );
138
139 if ( matches === null ) {
140 return null;
141 }
142
143 pos += matches[0].length;
144
145 return matches[0];
146 };
147 }
148
149 pipe = makeStringParser( '|' );
150 colon = makeStringParser( ':' );
151 backslash = makeStringParser( '\\' );
152 anyCharacter = makeRegexParser( /^./ );
153 dollar = makeStringParser( '$' );
154 digits = makeRegexParser( /^\d+/ );
155 regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ );
156 regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ );
157 regularLiteralWithoutSpace = makeRegexParser( /^[^{}\[\]$\s]/ );
158
159 // There is a general pattern:
160 // parse a thing;
161 // if it worked, apply transform,
162 // otherwise return null.
163 // But using this as a combinator seems to cause problems
164 // when combined with nOrMore().
165 // May be some scoping issue.
166 function transform( p, fn ) {
167 return function () {
168 var result = p();
169
170 return result === null ? null : fn( result );
171 };
172 }
173
174 // Used to define "literals" within template parameters. The pipe
175 // character is the parameter delimeter, so by default
176 // it is not a literal in the parameter
177 function literalWithoutBar() {
178 var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
179
180 return result === null ? null : result.join( '' );
181 }
182
183 function literal() {
184 var result = nOrMore( 1, escapedOrRegularLiteral )();
185
186 return result === null ? null : result.join( '' );
187 }
188
189 function escapedLiteral() {
190 var result = sequence( [ backslash, anyCharacter ] );
191
192 return result === null ? null : result[1];
193 }
194
195 choice( [ escapedLiteral, regularLiteralWithoutSpace ] );
196 escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] );
197 escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] );
198
199 function replacement() {
200 var result = sequence( [ dollar, digits ] );
201
202 if ( result === null ) {
203 return null;
204 }
205
206 return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ];
207 }
208
209 templateName = transform(
210 // see $wgLegalTitleChars
211 // not allowing : due to the need to catch "PLURAL:$1"
212 makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ),
213
214 function ( result ) {
215 return result.toString();
216 }
217 );
218
219 function templateParam() {
220 var expr,
221 result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] );
222
223 if ( result === null ) {
224 return null;
225 }
226
227 expr = result[1];
228
229 // use a "CONCAT" operator if there are multiple nodes,
230 // otherwise return the first node, raw.
231 return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[0];
232 }
233
234 function templateWithReplacement() {
235 var result = sequence( [ templateName, colon, replacement ] );
236
237 return result === null ? null : [ result[0], result[2] ];
238 }
239
240 function templateWithOutReplacement() {
241 var result = sequence( [ templateName, colon, paramExpression ] );
242
243 return result === null ? null : [ result[0], result[2] ];
244 }
245
246 templateContents = choice( [
247 function () {
248 var res = sequence( [
249 // templates can have placeholders for dynamic
250 // replacement eg: {{PLURAL:$1|one car|$1 cars}}
251 // or no placeholders eg:
252 // {{GRAMMAR:genitive|{{SITENAME}}}
253 choice( [ templateWithReplacement, templateWithOutReplacement ] ),
254 nOrMore( 0, templateParam )
255 ] );
256
257 return res === null ? null : res[0].concat( res[1] );
258 },
259 function () {
260 var res = sequence( [ templateName, nOrMore( 0, templateParam ) ] );
261
262 if ( res === null ) {
263 return null;
264 }
265
266 return [ res[0] ].concat( res[1] );
267 }
268 ] );
269
270 openTemplate = makeStringParser( '{{' );
271 closeTemplate = makeStringParser( '}}' );
272
273 function template() {
274 var result = sequence( [ openTemplate, templateContents, closeTemplate ] );
275
276 return result === null ? null : result[1];
277 }
278
279 expression = choice( [ template, replacement, literal ] );
280 paramExpression = choice( [ template, replacement, literalWithoutBar ] );
281
282 function start() {
283 var result = nOrMore( 0, expression )();
284
285 if ( result === null ) {
286 return null;
287 }
288
289 return [ 'CONCAT' ].concat( result );
290 }
291
292 result = start();
293
294 /*
295 * For success, the pos must have gotten to the end of the input
296 * and returned a non-null.
297 * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
298 */
299 if ( result === null || pos !== message.length ) {
300 throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + message );
301 }
302
303 return result;
304 }
305
306 };
307
308 $.extend( $.i18n.parser, new MessageParser() );
309 }( jQuery ) );