I18n::_pluralGuess()   F
last analyzed

Complexity

Conditions 72
Paths 279

Size

Total Lines 41
Code Lines 32

Duplication

Lines 6
Ratio 14.63 %

Importance

Changes 0
Metric Value
cc 72
eloc 32
c 0
b 0
f 0
nc 279
nop 2
dl 6
loc 41
rs 3.3333

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Internationalization
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.I18n
15
 * @since         CakePHP(tm) v 1.2.0.4116
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18
19
App::uses('CakePlugin', 'Core');
20
App::uses('L10n', 'I18n');
21
App::uses('Multibyte', 'I18n');
22
App::uses('CakeSession', 'Model/Datasource');
23
24
/**
25
 * I18n handles translation of Text and time format strings.
26
 *
27
 * @package       Cake.I18n
28
 */
29
class I18n {
30
31
/**
32
 * Instance of the L10n class for localization
33
 *
34
 * @var L10n
35
 */
36
	public $l10n = null;
37
38
/**
39
 * Default domain of translation
40
 *
41
 * @var string
42
 */
43
	public static $defaultDomain = 'default';
44
45
/**
46
 * Current domain of translation
47
 *
48
 * @var string
49
 */
50
	public $domain = null;
51
52
/**
53
 * Current category of translation
54
 *
55
 * @var string
56
 */
57
	public $category = 'LC_MESSAGES';
58
59
/**
60
 * Current language used for translations
61
 *
62
 * @var string
63
 */
64
	protected $_lang = null;
65
66
/**
67
 * Translation strings for a specific domain read from the .mo or .po files
68
 *
69
 * @var array
70
 */
71
	protected $_domains = array();
72
73
/**
74
 * Set to true when I18N::_bindTextDomain() is called for the first time.
75
 * If a translation file is found it is set to false again
76
 *
77
 * @var boolean
78
 */
79
	protected $_noLocale = false;
80
81
/**
82
 * Translation categories
83
 *
84
 * @var array
85
 */
86
	protected $_categories = array(
87
		'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES'
88
	);
89
90
/**
91
 * Escape string
92
 *
93
 * @var string
94
 */
95
	protected $_escape = null;
96
97
/**
98
 * Constructor, use I18n::getInstance() to get the i18n translation object.
99
 */
100
	public function __construct() {
101
		$this->l10n = new L10n();
102
	}
103
104
/**
105
 * Return a static instance of the I18n class
106
 *
107
 * @return I18n
108
 */
109
	public static function getInstance() {
110
		static $instance = array();
111
		if (!$instance) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $instance of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
112
			$instance[0] = new I18n();
113
		}
114
		return $instance[0];
115
	}
116
117
/**
118
 * Used by the translation functions in basics.php
119
 * Returns a translated string based on current language and translation files stored in locale folder
120
 *
121
 * @param string $singular String to translate
122
 * @param string $plural Plural string (if any)
123
 * @param string $domain Domain The domain of the translation. Domains are often used by plugin translations.
124
 *    If null, the default domain will be used.
125
 * @param string $category Category The integer value of the category to use.
126
 * @param integer $count Count Count is used with $plural to choose the correct plural form.
127
 * @param string $language Language to translate string to.
128
 *    If null it checks for language in session followed by Config.language configuration variable.
129
 * @return string translated string.
130
 * @throws CakeException When '' is provided as a domain.
131
 */
132
	public static function translate($singular, $plural = null, $domain = null, $category = 6, $count = null, $language = null) {
133
		$_this = I18n::getInstance();
134
135
		if (strpos($singular, "\r\n") !== false) {
136
			$singular = str_replace("\r\n", "\n", $singular);
137
		}
138
		if ($plural !== null && strpos($plural, "\r\n") !== false) {
139
			$plural = str_replace("\r\n", "\n", $plural);
140
		}
141
142
		if (is_numeric($category)) {
143
			$_this->category = $_this->_categories[$category];
144
		}
145
146
		if (empty($language)) {
147
			if (CakeSession::started()) {
148
				$language = CakeSession::read('Config.language');
149
			}
150
			if (empty($language)) {
151
				$language = Configure::read('Config.language');
152
			}
153
		}
154
155
		if (($_this->_lang && $_this->_lang !== $language) || !$_this->_lang) {
156
			$lang = $_this->l10n->get($language);
157
			$_this->_lang = $lang;
158
		}
159
160
		if ($domain === null) {
161
			$domain = self::$defaultDomain;
162
		}
163
		if ($domain === '') {
164
			throw new CakeException(__d('cake_dev', 'You cannot use "" as a domain.'));
165
		}
166
167
		$_this->domain = $domain . '_' . $_this->l10n->lang;
168
169
		if (!isset($_this->_domains[$domain][$_this->_lang])) {
170
			$_this->_domains[$domain][$_this->_lang] = Cache::read($_this->domain, '_cake_core_');
171
		}
172
173
		if (!isset($_this->_domains[$domain][$_this->_lang][$_this->category])) {
174
			$_this->_bindTextDomain($domain);
175
			Cache::write($_this->domain, $_this->_domains[$domain][$_this->_lang], '_cake_core_');
176
		}
177
178
		if ($_this->category === 'LC_TIME') {
179
			return $_this->_translateTime($singular, $domain);
180
		}
181
182
		if (!isset($count)) {
183
			$plurals = 0;
184
		} elseif (!empty($_this->_domains[$domain][$_this->_lang][$_this->category]["%plural-c"]) && $_this->_noLocale === false) {
185
			$header = $_this->_domains[$domain][$_this->_lang][$_this->category]["%plural-c"];
186
			$plurals = $_this->_pluralGuess($header, $count);
187
		} else {
188
			if ($count != 1) {
189
				$plurals = 1;
190
			} else {
191
				$plurals = 0;
192
			}
193
		}
194
195
		if (!empty($_this->_domains[$domain][$_this->_lang][$_this->category][$singular])) {
196
			if (($trans = $_this->_domains[$domain][$_this->_lang][$_this->category][$singular]) || ($plurals) && ($trans = $_this->_domains[$domain][$_this->_lang][$_this->category][$plural])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plurals of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
197
				if (is_array($trans)) {
198
					if (isset($trans[$plurals])) {
199
						$trans = $trans[$plurals];
200
					} else {
201
						trigger_error(
202
							__d('cake_dev',
203
								'Missing plural form translation for "%s" in "%s" domain, "%s" locale. ' .
204
								' Check your po file for correct plurals and valid Plural-Forms header.',
205
								$singular,
206
								$domain,
207
								$_this->_lang
208
							),
209
							E_USER_WARNING
210
						);
211
						$trans = $trans[0];
212
					}
213
				}
214
				if (strlen($trans)) {
215
					return $trans;
216
				}
217
			}
218
		}
219
220
		if (!empty($plurals)) {
221
			return $plural;
222
		}
223
		return $singular;
224
	}
225
226
/**
227
 * Clears the domains internal data array. Useful for testing i18n.
228
 *
229
 * @return void
230
 */
231
	public static function clear() {
232
		$self = I18n::getInstance();
233
		$self->_domains = array();
234
	}
235
236
/**
237
 * Get the loaded domains cache.
238
 *
239
 * @return array
240
 */
241
	public static function domains() {
242
		$self = I18n::getInstance();
243
		return $self->_domains;
244
	}
245
246
/**
247
 * Attempts to find the plural form of a string.
248
 *
249
 * @param string $header Type
250
 * @param integer $n Number
251
 * @return integer plural match
252
 */
253
	protected function _pluralGuess($header, $n) {
254
		if (!is_string($header) || $header === "nplurals=1;plural=0;" || !isset($header[0])) {
255
			return 0;
256
		}
257
258
		if ($header === "nplurals=2;plural=n!=1;") {
259
			return $n != 1 ? 1 : 0;
260
		} elseif ($header === "nplurals=2;plural=n>1;") {
261
			return $n > 1 ? 1 : 0;
262
		}
263
264
		if (strpos($header, "plurals=3")) {
265
			if (strpos($header, "100!=11")) {
266
				if (strpos($header, "10<=4")) {
267
					return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
268
				} elseif (strpos($header, "100<10")) {
269
					return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
270
				}
271
				return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n != 0 ? 1 : 2);
272
			} elseif (strpos($header, "n==2")) {
273
				return $n == 1 ? 0 : ($n == 2 ? 1 : 2);
274
			} elseif (strpos($header, "n==0")) {
275
				return $n == 1 ? 0 : ($n == 0 || ($n % 100 > 0 && $n % 100 < 20) ? 1 : 2);
276
			} elseif (strpos($header, "n>=2")) {
277
				return $n == 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
278 View Code Duplication
			} elseif (strpos($header, "10>=2")) {
279
				return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
280
			}
281
			return $n % 10 == 1 ? 0 : ($n % 10 == 2 ? 1 : 2);
282
		} elseif (strpos($header, "plurals=4")) {
283
			if (strpos($header, "100==2")) {
284
				return $n % 100 == 1 ? 0 : ($n % 100 == 2 ? 1 : ($n % 100 == 3 || $n % 100 == 4 ? 2 : 3));
285
			} elseif (strpos($header, "n>=3")) {
286
				return $n == 1 ? 0 : ($n == 2 ? 1 : ($n == 0 || ($n >= 3 && $n <= 10) ? 2 : 3));
287 View Code Duplication
			} elseif (strpos($header, "100>=1")) {
288
				return $n == 1 ? 0 : ($n == 0 || ($n % 100 >= 1 && $n % 100 <= 10) ? 1 : ($n % 100 >= 11 && $n % 100 <= 20 ? 2 : 3));
289
			}
290
		} elseif (strpos($header, "plurals=5")) {
291
			return $n == 1 ? 0 : ($n == 2 ? 1 : ($n >= 3 && $n <= 6 ? 2 : ($n >= 7 && $n <= 10 ? 3 : 4)));
292
		}
293
	}
294
295
/**
296
 * Binds the given domain to a file in the specified directory.
297
 *
298
 * @param string $domain Domain to bind
299
 * @return string Domain binded
300
 */
301
	protected function _bindTextDomain($domain) {
302
		$this->_noLocale = true;
303
		$core = true;
304
		$merge = array();
305
		$searchPaths = App::path('locales');
306
		$plugins = CakePlugin::loaded();
307
308
		if (!empty($plugins)) {
309
			foreach ($plugins as $plugin) {
0 ignored issues
show
Bug introduced by
The expression $plugins of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
310
				$pluginDomain = Inflector::underscore($plugin);
311
				if ($pluginDomain === $domain) {
312
					$searchPaths[] = CakePlugin::path($plugin) . 'Locale' . DS;
313
					$searchPaths = array_reverse($searchPaths);
314
					break;
315
				}
316
			}
317
		}
318
319
		foreach ($searchPaths as $directory) {
320
			foreach ($this->l10n->languagePath as $lang) {
321
				$localeDef = $directory . $lang . DS . $this->category;
322
				if (is_file($localeDef)) {
323
					$definitions = self::loadLocaleDefinition($localeDef);
324 View Code Duplication
					if ($definitions !== false) {
325
						$this->_domains[$domain][$this->_lang][$this->category] = $definitions;
326
						$this->_noLocale = false;
327
						return $domain;
328
					}
329
				}
330
331
				if ($core) {
332
					$app = $directory . $lang . DS . $this->category . DS . 'core';
333
					$translations = false;
334
335
					if (is_file($app . '.mo')) {
336
						$translations = self::loadMo($app . '.mo');
337
					}
338 View Code Duplication
					if ($translations === false && is_file($app . '.po')) {
339
						$translations = self::loadPo($app . '.po');
340
					}
341
342
					if ($translations !== false) {
343
						$this->_domains[$domain][$this->_lang][$this->category] = $translations;
344
						$merge[$domain][$this->_lang][$this->category] = $this->_domains[$domain][$this->_lang][$this->category];
345
						$this->_noLocale = false;
346
						$core = null;
347
					}
348
				}
349
350
				$file = $directory . $lang . DS . $this->category . DS . $domain;
351
				$translations = false;
352
353
				if (is_file($file . '.mo')) {
354
					$translations = self::loadMo($file . '.mo');
355
				}
356 View Code Duplication
				if ($translations === false && is_file($file . '.po')) {
357
					$translations = self::loadPo($file . '.po');
358
				}
359
360 View Code Duplication
				if ($translations !== false) {
361
					$this->_domains[$domain][$this->_lang][$this->category] = $translations;
362
					$this->_noLocale = false;
363
					break 2;
364
				}
365
			}
366
		}
367
368
		if (empty($this->_domains[$domain][$this->_lang][$this->category])) {
369
			$this->_domains[$domain][$this->_lang][$this->category] = array();
370
			return $domain;
371
		}
372
373
		if (isset($this->_domains[$domain][$this->_lang][$this->category][""])) {
374
			$head = $this->_domains[$domain][$this->_lang][$this->category][""];
375
376
			foreach (explode("\n", $head) as $line) {
377
				$header = strtok($line, ':');
378
				$line = trim(strtok("\n"));
379
				$this->_domains[$domain][$this->_lang][$this->category]["%po-header"][strtolower($header)] = $line;
380
			}
381
382
			if (isset($this->_domains[$domain][$this->_lang][$this->category]["%po-header"]["plural-forms"])) {
383
				$switch = preg_replace("/(?:[() {}\\[\\]^\\s*\\]]+)/", "", $this->_domains[$domain][$this->_lang][$this->category]["%po-header"]["plural-forms"]);
384
				$this->_domains[$domain][$this->_lang][$this->category]["%plural-c"] = $switch;
385
				unset($this->_domains[$domain][$this->_lang][$this->category]["%po-header"]);
386
			}
387
			$this->_domains = Hash::mergeDiff($this->_domains, $merge);
388
389
			if (isset($this->_domains[$domain][$this->_lang][$this->category][null])) {
390
				unset($this->_domains[$domain][$this->_lang][$this->category][null]);
391
			}
392
		}
393
394
		return $domain;
395
	}
396
397
/**
398
 * Loads the binary .mo file and returns array of translations
399
 *
400
 * @param string $filename Binary .mo file to load
401
 * @return mixed Array of translations on success or false on failure
402
 */
403
	public static function loadMo($filename) {
404
		$translations = false;
405
406
		// @codingStandardsIgnoreStart
407
		// Binary files extracted makes non-standard local variables
408
		if ($data = file_get_contents($filename)) {
409
			$translations = array();
410
			$header = substr($data, 0, 20);
411
			$header = unpack('L1magic/L1version/L1count/L1o_msg/L1o_trn', $header);
412
			extract($header);
413
414
			if ((dechex($magic) === '950412de' || dechex($magic) === 'ffffffff950412de') && !$version) {
415
				for ($n = 0; $n < $count; $n++) {
416
					$r = unpack("L1len/L1offs", substr($data, $o_msg + $n * 8, 8));
417
					$msgid = substr($data, $r["offs"], $r["len"]);
418
					unset($msgid_plural);
419
420
					if (strpos($msgid, "\000")) {
421
						list($msgid, $msgid_plural) = explode("\000", $msgid);
422
					}
423
					$r = unpack("L1len/L1offs", substr($data, $o_trn + $n * 8, 8));
424
					$msgstr = substr($data, $r["offs"], $r["len"]);
425
426
					if (strpos($msgstr, "\000")) {
427
						$msgstr = explode("\000", $msgstr);
428
					}
429
					$translations[$msgid] = $msgstr;
430
431
					if (isset($msgid_plural)) {
432
						$translations[$msgid_plural] =& $translations[$msgid];
433
					}
434
				}
435
			}
436
		}
437
		// @codingStandardsIgnoreEnd
438
439
		return $translations;
440
	}
441
442
/**
443
 * Loads the text .po file and returns array of translations
444
 *
445
 * @param string $filename Text .po file to load
446
 * @return mixed Array of translations on success or false on failure
447
 */
448
	public static function loadPo($filename) {
449
		if (!$file = fopen($filename, 'r')) {
450
			return false;
451
		}
452
453
		$type = 0;
454
		$translations = array();
455
		$translationKey = '';
456
		$plural = 0;
457
		$header = '';
458
459
		do {
460
			$line = trim(fgets($file));
461
			if ($line === '' || $line[0] === '#') {
462
				continue;
463
			}
464
			if (preg_match("/msgid[[:space:]]+\"(.+)\"$/i", $line, $regs)) {
465
				$type = 1;
466
				$translationKey = stripcslashes($regs[1]);
467
			} elseif (preg_match("/msgid[[:space:]]+\"\"$/i", $line, $regs)) {
468
				$type = 2;
469
				$translationKey = '';
470 View Code Duplication
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && ($type == 1 || $type == 2 || $type == 3)) {
471
				$type = 3;
472
				$translationKey .= stripcslashes($regs[1]);
473
			} elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
474
				$translations[$translationKey] = stripcslashes($regs[1]);
475
				$type = 4;
476 View Code Duplication
			} elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
477
				$type = 4;
478
				$translations[$translationKey] = '';
479
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 4 && $translationKey) {
480
				$translations[$translationKey] .= stripcslashes($regs[1]);
481
			} elseif (preg_match("/msgid_plural[[:space:]]+\".*\"$/i", $line, $regs)) {
482
				$type = 6;
483
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 6 && $translationKey) {
484
				$type = 6;
485
			} elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
486
				$plural = $regs[1];
487
				$translations[$translationKey][$plural] = stripcslashes($regs[2]);
488
				$type = 7;
489
			} elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
490
				$plural = $regs[1];
491
				$translations[$translationKey][$plural] = '';
492
				$type = 7;
493
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 7 && $translationKey) {
494
				$translations[$translationKey][$plural] .= stripcslashes($regs[1]);
495 View Code Duplication
			} elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && $type == 2 && !$translationKey) {
496
				$header .= stripcslashes($regs[1]);
497
				$type = 5;
498
			} elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && !$translationKey) {
499
				$header = '';
500
				$type = 5;
501 View Code Duplication
			} elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 5) {
502
				$header .= stripcslashes($regs[1]);
503
			} else {
504
				unset($translations[$translationKey]);
505
				$type = 0;
506
				$translationKey = '';
507
				$plural = 0;
508
			}
509
		} while (!feof($file));
510
		fclose($file);
511
512
		$merge[''] = $header;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$merge was never initialized. Although not strictly required by PHP, it is generally a good practice to add $merge = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
513
		return array_merge($merge, $translations);
514
	}
515
516
/**
517
 * Parses a locale definition file following the POSIX standard
518
 *
519
 * @param string $filename Locale definition filename
520
 * @return mixed Array of definitions on success or false on failure
521
 */
522
	public static function loadLocaleDefinition($filename) {
523
		if (!$file = fopen($filename, 'r')) {
524
			return false;
525
		}
526
527
		$definitions = array();
528
		$comment = '#';
529
		$escape = '\\';
530
		$currentToken = false;
531
		$value = '';
532
		$_this = I18n::getInstance();
533
		while ($line = fgets($file)) {
534
			$line = trim($line);
535
			if (empty($line) || $line[0] === $comment) {
536
				continue;
537
			}
538
			$parts = preg_split("/[[:space:]]+/", $line);
539
			if ($parts[0] === 'comment_char') {
540
				$comment = $parts[1];
541
				continue;
542
			}
543
			if ($parts[0] === 'escape_char') {
544
				$escape = $parts[1];
545
				continue;
546
			}
547
			$count = count($parts);
548
			if ($count === 2) {
549
				$currentToken = $parts[0];
550
				$value = $parts[1];
551
			} elseif ($count === 1) {
552
				$value = is_array($value) ? $parts[0] : $value . $parts[0];
553
			} else {
554
				continue;
555
			}
556
557
			$len = strlen($value) - 1;
558
			if ($value[$len] === $escape) {
559
				$value = substr($value, 0, $len);
560
				continue;
561
			}
562
563
			$mustEscape = array($escape . ',', $escape . ';', $escape . '<', $escape . '>', $escape . $escape);
564
			$replacements = array_map('crc32', $mustEscape);
565
			$value = str_replace($mustEscape, $replacements, $value);
566
			$value = explode(';', $value);
567
			$_this->_escape = $escape;
568
			foreach ($value as $i => $val) {
569
				$val = trim($val, '"');
570
				$val = preg_replace_callback('/(?:<)?(.[^>]*)(?:>)?/', array(&$_this, '_parseLiteralValue'), $val);
571
				$val = str_replace($replacements, $mustEscape, $val);
572
				$value[$i] = $val;
573
			}
574
			if (count($value) === 1) {
575
				$definitions[$currentToken] = array_pop($value);
576
			} else {
577
				$definitions[$currentToken] = $value;
578
			}
579
		}
580
581
		return $definitions;
582
	}
583
584
/**
585
 * Auxiliary function to parse a symbol from a locale definition file
586
 *
587
 * @param string $string Symbol to be parsed
588
 * @return string parsed symbol
589
 */
590
	protected function _parseLiteralValue($string) {
591
		$string = $string[1];
592 View Code Duplication
		if (substr($string, 0, 2) === $this->_escape . 'x') {
593
			$delimiter = $this->_escape . 'x';
594
			return implode('', array_map('chr', array_map('hexdec', array_filter(explode($delimiter, $string)))));
595
		}
596 View Code Duplication
		if (substr($string, 0, 2) === $this->_escape . 'd') {
597
			$delimiter = $this->_escape . 'd';
598
			return implode('', array_map('chr', array_filter(explode($delimiter, $string))));
599
		}
600
		if ($string[0] === $this->_escape && isset($string[1]) && is_numeric($string[1])) {
601
			$delimiter = $this->_escape;
602
			return implode('', array_map('chr', array_filter(explode($delimiter, $string))));
603
		}
604
		if (substr($string, 0, 3) === 'U00') {
605
			$delimiter = 'U00';
606
			return implode('', array_map('chr', array_map('hexdec', array_filter(explode($delimiter, $string)))));
607
		}
608
		if (preg_match('/U([0-9a-fA-F]{4})/', $string, $match)) {
609
			return Multibyte::ascii(array(hexdec($match[1])));
610
		}
611
		return $string;
612
	}
613
614
/**
615
 * Returns a Time format definition from corresponding domain
616
 *
617
 * @param string $format Format to be translated
618
 * @param string $domain Domain where format is stored
619
 * @return mixed translated format string if only value or array of translated strings for corresponding format.
620
 */
621
	protected function _translateTime($format, $domain) {
622
		if (!empty($this->_domains[$domain][$this->_lang]['LC_TIME'][$format])) {
623
			if (($trans = $this->_domains[$domain][$this->_lang][$this->category][$format])) {
624
				return $trans;
625
			}
626
		}
627
		return $format;
628
	}
629
630
}
631