Completed
Push — master ( be0721...cced22 )
by Matthew
02:48
created

web/static/js/vendor/jquery.i18n/CLDRPluralRuleParser.js   F

Complexity

Total Complexity 80
Complexity/F 2.42

Size

Lines of Code 585
Function Count 33

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
wmc 80
nc 64
mnd 3
bc 79
fnc 33
dl 0
loc 585
rs 3.12
bpm 2.3939
cpm 2.4242
noi 22
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like web/static/js/vendor/jquery.i18n/CLDRPluralRuleParser.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
 * cldrpluralparser.js
3
 * A parser engine for CLDR plural rules.
4
 *
5
 * Copyright 2012-2014 Santhosh Thottingal and other contributors
6
 * Released under the MIT license
7
 * http://opensource.org/licenses/MIT
8
 *
9
 * @version 0.1.0
10
 * @source https://github.com/santhoshtr/CLDRPluralRuleParser
11
 * @author Santhosh Thottingal <[email protected]>
12
 * @author Timo Tijhof
13
 * @author Amir Aharoni
14
 */
15
16
/**
17
 * Evaluates a plural rule in CLDR syntax for a number
18
 * @param {string} rule
19
 * @param {integer} number
20
 * @return {boolean} true if evaluation passed, false if evaluation failed.
21
 */
22
23
// UMD returnExports https://github.com/umdjs/umd/blob/master/returnExports.js
24
(function(root, factory) {
25
	if (typeof define === 'function' && define.amd) {
26
		// AMD. Register as an anonymous module.
27
		define(factory);
28
	} else if (typeof exports === 'object') {
29
		// Node. Does not work with strict CommonJS, but
30
		// only CommonJS-like environments that support module.exports,
31
		// like Node.
32
		module.exports = factory();
33
	} else {
34
		// Browser globals (root is window)
35
		root.pluralRuleParser = factory();
36
	}
37
}(this, function() {
38
39
function pluralRuleParser(rule, number) {
40
	'use strict';
41
42
	/*
43
	Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
44
	-----------------------------------------------------------------
45
	condition     = and_condition ('or' and_condition)*
46
		('@integer' samples)?
47
		('@decimal' samples)?
48
	and_condition = relation ('and' relation)*
49
	relation      = is_relation | in_relation | within_relation
50
	is_relation   = expr 'is' ('not')? value
51
	in_relation   = expr (('not')? 'in' | '=' | '!=') range_list
52
	within_relation = expr ('not')? 'within' range_list
53
	expr          = operand (('mod' | '%') value)?
54
	operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
55
	range_list    = (range | value) (',' range_list)*
56
	value         = digit+
57
	digit         = 0|1|2|3|4|5|6|7|8|9
58
	range         = value'..'value
59
	samples       = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
60
	sampleRange   = decimalValue '~' decimalValue
61
	decimalValue  = value ('.' value)?
62
	*/
63
64
	// We don't evaluate the samples section of the rule. Ignore it.
65
	rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, '');
66
67
	if (!rule.length) {
68
		// Empty rule or 'other' rule.
69
		return true;
70
	}
71
72
	// Indicates the current position in the rule as we parse through it.
73
	// Shared among all parsing functions below.
74
	var pos = 0,
75
		operand,
76
		expression,
77
		relation,
78
		result,
79
		whitespace = makeRegexParser(/^\s+/),
80
		value = makeRegexParser(/^\d+/),
81
		_n_ = makeStringParser('n'),
82
		_i_ = makeStringParser('i'),
83
		_f_ = makeStringParser('f'),
84
		_t_ = makeStringParser('t'),
85
		_v_ = makeStringParser('v'),
86
		_w_ = makeStringParser('w'),
87
		_is_ = makeStringParser('is'),
88
		_isnot_ = makeStringParser('is not'),
89
		_isnot_sign_ = makeStringParser('!='),
90
		_equal_ = makeStringParser('='),
91
		_mod_ = makeStringParser('mod'),
92
		_percent_ = makeStringParser('%'),
93
		_not_ = makeStringParser('not'),
94
		_in_ = makeStringParser('in'),
95
		_within_ = makeStringParser('within'),
96
		_range_ = makeStringParser('..'),
97
		_comma_ = makeStringParser(','),
98
		_or_ = makeStringParser('or'),
99
		_and_ = makeStringParser('and');
100
101
	function debug() {
102
		// console.log.apply(console, arguments);
103
	}
104
105
	debug('pluralRuleParser', rule, number);
106
107
	// Try parsers until one works, if none work return null
108
	function choice(parserSyntax) {
109
		return function() {
110
			var i, result;
111
112
			for (i = 0; i < parserSyntax.length; i++) {
113
				result = parserSyntax[i]();
114
115
				if (result !== null) {
116
					return result;
117
				}
118
			}
119
120
			return null;
121
		};
122
	}
123
124
	// Try several parserSyntax-es in a row.
125
	// All must succeed; otherwise, return null.
126
	// This is the only eager one.
127
	function sequence(parserSyntax) {
128
		var i, parserRes,
129
			originalPos = pos,
130
			result = [];
131
132
		for (i = 0; i < parserSyntax.length; i++) {
133
			parserRes = parserSyntax[i]();
134
135
			if (parserRes === null) {
136
				pos = originalPos;
137
138
				return null;
139
			}
140
141
			result.push(parserRes);
142
		}
143
144
		return result;
145
	}
146
147
	// Run the same parser over and over until it fails.
148
	// Must succeed a minimum of n times; otherwise, return null.
149
	function nOrMore(n, p) {
150
		return function() {
151
			var originalPos = pos,
152
				result = [],
153
				parsed = p();
154
155
			while (parsed !== null) {
156
				result.push(parsed);
157
				parsed = p();
158
			}
159
160
			if (result.length < n) {
161
				pos = originalPos;
162
163
				return null;
164
			}
165
166
			return result;
167
		};
168
	}
169
170
	// Helpers - just make parserSyntax out of simpler JS builtin types
171
	function makeStringParser(s) {
172
		var len = s.length;
173
174
		return function() {
175
			var result = null;
176
177
			if (rule.substr(pos, len) === s) {
178
				result = s;
179
				pos += len;
180
			}
181
182
			return result;
183
		};
184
	}
185
186
	function makeRegexParser(regex) {
187
		return function() {
188
			var matches = rule.substr(pos).match(regex);
189
190
			if (matches === null) {
191
				return null;
192
			}
193
194
			pos += matches[0].length;
195
196
			return matches[0];
197
		};
198
	}
199
200
	/**
201
	 * Integer digits of n.
202
	 */
203
	function i() {
204
		var result = _i_();
205
206
		if (result === null) {
207
			debug(' -- failed i', parseInt(number, 10));
208
209
			return result;
210
		}
211
212
		result = parseInt(number, 10);
213
		debug(' -- passed i ', result);
214
215
		return result;
216
	}
217
218
	/**
219
	 * Absolute value of the source number (integer and decimals).
220
	 */
221
	function n() {
222
		var result = _n_();
223
224
		if (result === null) {
225
			debug(' -- failed n ', number);
226
227
			return result;
228
		}
229
230
		result = parseFloat(number, 10);
231
		debug(' -- passed n ', result);
232
233
		return result;
234
	}
235
236
	/**
237
	 * Visible fractional digits in n, with trailing zeros.
238
	 */
239
	function f() {
240
		var result = _f_();
241
242
		if (result === null) {
243
			debug(' -- failed f ', number);
244
245
			return result;
246
		}
247
248
		result = (number + '.').split('.')[1] || 0;
249
		debug(' -- passed f ', result);
250
251
		return result;
252
	}
253
254
	/**
255
	 * Visible fractional digits in n, without trailing zeros.
256
	 */
257
	function t() {
258
		var result = _t_();
259
260
		if (result === null) {
261
			debug(' -- failed t ', number);
262
263
			return result;
264
		}
265
266
		result = (number + '.').split('.')[1].replace(/0$/, '') || 0;
267
		debug(' -- passed t ', result);
268
269
		return result;
270
	}
271
272
	/**
273
	 * Number of visible fraction digits in n, with trailing zeros.
274
	 */
275
	function v() {
276
		var result = _v_();
277
278
		if (result === null) {
279
			debug(' -- failed v ', number);
280
281
			return result;
282
		}
283
284
		result = (number + '.').split('.')[1].length || 0;
285
		debug(' -- passed v ', result);
286
287
		return result;
288
	}
289
290
	/**
291
	 * Number of visible fraction digits in n, without trailing zeros.
292
	 */
293
	function w() {
294
		var result = _w_();
295
296
		if (result === null) {
297
			debug(' -- failed w ', number);
298
299
			return result;
300
		}
301
302
		result = (number + '.').split('.')[1].replace(/0$/, '').length || 0;
303
		debug(' -- passed w ', result);
304
305
		return result;
306
	}
307
308
	// operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
309
	operand = choice([n, i, f, t, v, w]);
310
311
	// expr          = operand (('mod' | '%') value)?
312
	expression = choice([mod, operand]);
313
314
	function mod() {
315
		var result = sequence(
316
			[operand, whitespace, choice([_mod_, _percent_]), whitespace, value]
317
		);
318
319
		if (result === null) {
320
			debug(' -- failed mod');
321
322
			return null;
323
		}
324
325
		debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10));
326
327
		return parseInt(result[0], 10) % parseInt(result[4], 10);
328
	}
329
330
	function not() {
331
		var result = sequence([whitespace, _not_]);
332
333
		if (result === null) {
334
			debug(' -- failed not');
335
336
			return null;
337
		}
338
339
		return result[1];
340
	}
341
342
	// is_relation   = expr 'is' ('not')? value
343
	function is() {
344
		var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
345
346
		if (result !== null) {
347
			debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10));
348
349
			return result[0] === parseInt(result[4], 10);
350
		}
351
352
		debug(' -- failed is');
353
354
		return null;
355
	}
356
357
	// is_relation   = expr 'is' ('not')? value
358
	function isnot() {
359
		var result = sequence(
360
			[expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]
361
		);
362
363
		if (result !== null) {
364
			debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10));
365
366
			return result[0] !== parseInt(result[4], 10);
367
		}
368
369
		debug(' -- failed isnot');
370
371
		return null;
372
	}
373
374
	function not_in() {
375
		var i, range_list,
376
			result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
377
378
		if (result !== null) {
379
			debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
380
			range_list = result[4];
381
382
			for (i = 0; i < range_list.length; i++) {
383
				if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
384
					return false;
385
				}
386
			}
387
388
			return true;
389
		}
390
391
		debug(' -- failed not_in');
392
393
		return null;
394
	}
395
396
	// range_list    = (range | value) (',' range_list)*
397
	function rangeList() {
398
		var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]),
399
			resultList = [];
400
401
		if (result !== null) {
402
			resultList = resultList.concat(result[0]);
403
404
			if (result[1][0]) {
405
				resultList = resultList.concat(result[1][0]);
406
			}
407
408
			return resultList;
409
		}
410
411
		debug(' -- failed rangeList');
412
413
		return null;
414
	}
415
416
	function rangeTail() {
417
		// ',' range_list
418
		var result = sequence([_comma_, rangeList]);
419
420
		if (result !== null) {
421
			return result[1];
422
		}
423
424
		debug(' -- failed rangeTail');
425
426
		return null;
427
	}
428
429
	// range         = value'..'value
430
	function range() {
431
		var i, array, left, right,
432
			result = sequence([value, _range_, value]);
433
434
		if (result !== null) {
435
			debug(' -- passed range');
436
437
			array = [];
438
			left = parseInt(result[0], 10);
439
			right = parseInt(result[2], 10);
440
441
			for (i = left; i <= right; i++) {
442
				array.push(i);
443
			}
444
445
			return array;
446
		}
447
448
		debug(' -- failed range');
449
450
		return null;
451
	}
452
453
	function _in() {
454
		var result, range_list, i;
455
456
		// in_relation   = expr ('not')? 'in' range_list
457
		result = sequence(
458
			[expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
459
		);
460
461
		if (result !== null) {
462
			debug(' -- passed _in:' + result);
463
464
			range_list = result[5];
465
466
			for (i = 0; i < range_list.length; i++) {
467
				if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
468
					return (result[1][0] !== 'not');
469
				}
470
			}
471
472
			return (result[1][0] === 'not');
473
		}
474
475
		debug(' -- failed _in ');
476
477
		return null;
478
	}
479
480
	/**
481
	 * The difference between "in" and "within" is that
482
	 * "in" only includes integers in the specified range,
483
	 * while "within" includes all values.
484
	 */
485
	function within() {
486
		var range_list, result;
487
488
		// within_relation = expr ('not')? 'within' range_list
489
		result = sequence(
490
			[expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
491
		);
492
493
		if (result !== null) {
494
			debug(' -- passed within');
495
496
			range_list = result[5];
497
498
			if ((result[0] >= parseInt(range_list[0], 10)) &&
499
				(result[0] < parseInt(range_list[range_list.length - 1], 10))) {
500
501
				return (result[1][0] !== 'not');
502
			}
503
504
			return (result[1][0] === 'not');
505
		}
506
507
		debug(' -- failed within ');
508
509
		return null;
510
	}
511
512
	// relation      = is_relation | in_relation | within_relation
513
	relation = choice([is, not_in, isnot, _in, within]);
514
515
	// and_condition = relation ('and' relation)*
516
	function and() {
517
		var i,
518
			result = sequence([relation, nOrMore(0, andTail)]);
519
520
		if (result) {
521
			if (!result[0]) {
522
				return false;
523
			}
524
525
			for (i = 0; i < result[1].length; i++) {
526
				if (!result[1][i]) {
527
					return false;
528
				}
529
			}
530
531
			return true;
532
		}
533
534
		debug(' -- failed and');
535
536
		return null;
537
	}
538
539
	// ('and' relation)*
540
	function andTail() {
541
		var result = sequence([whitespace, _and_, whitespace, relation]);
542
543
		if (result !== null) {
544
			debug(' -- passed andTail' + result);
545
546
			return result[3];
547
		}
548
549
		debug(' -- failed andTail');
550
551
		return null;
552
553
	}
554
	//  ('or' and_condition)*
555
	function orTail() {
556
		var result = sequence([whitespace, _or_, whitespace, and]);
557
558
		if (result !== null) {
559
			debug(' -- passed orTail: ' + result[3]);
560
561
			return result[3];
562
		}
563
564
		debug(' -- failed orTail');
565
566
		return null;
567
	}
568
569
	// condition     = and_condition ('or' and_condition)*
570
	function condition() {
571
		var i,
572
			result = sequence([and, nOrMore(0, orTail)]);
573
574
		if (result) {
575
			for (i = 0; i < result[1].length; i++) {
576
				if (result[1][i]) {
577
					return true;
578
				}
579
			}
580
581
			return result[0];
582
		}
583
584
		return false;
585
	}
586
587
	result = condition();
588
589
	/**
590
	 * For success, the pos must have gotten to the end of the rule
591
	 * and returned a non-null.
592
	 * n.b. This is part of language infrastructure,
593
	 * so we do not throw an internationalizable message.
594
	 */
595
	if (result === null) {
596
		throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule);
597
	}
598
599
	if (pos !== rule.length) {
600
		debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule);
601
	}
602
603
	return result;
604
}
605
606
return pluralRuleParser;
607
608
}));
609