annotate core/lib/Drupal/Component/Gettext/PoHeader.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Component\Gettext;
Chris@0 4
Chris@0 5 /**
Chris@0 6 * Gettext PO header handler.
Chris@0 7 *
Chris@0 8 * Possible Gettext PO header elements are explained in
Chris@0 9 * http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry,
Chris@0 10 * but we only support a subset of these directly.
Chris@0 11 *
Chris@0 12 * Example header:
Chris@0 13 *
Chris@0 14 * "Project-Id-Version: Drupal core (7.11)\n"
Chris@0 15 * "POT-Creation-Date: 2012-02-12 22:59+0000\n"
Chris@0 16 * "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
Chris@0 17 * "Language-Team: Catalan\n"
Chris@0 18 * "MIME-Version: 1.0\n"
Chris@0 19 * "Content-Type: text/plain; charset=utf-8\n"
Chris@0 20 * "Content-Transfer-Encoding: 8bit\n"
Chris@0 21 * "Plural-Forms: nplurals=2; plural=(n>1);\n"
Chris@0 22 */
Chris@0 23 class PoHeader {
Chris@0 24
Chris@0 25 /**
Chris@0 26 * Language code.
Chris@0 27 *
Chris@0 28 * @var string
Chris@0 29 */
Chris@17 30 protected $langcode;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * Formula for the plural form.
Chris@0 34 *
Chris@0 35 * @var string
Chris@0 36 */
Chris@17 37 protected $pluralForms;
Chris@0 38
Chris@0 39 /**
Chris@0 40 * Author(s) of the file.
Chris@0 41 *
Chris@0 42 * @var string
Chris@0 43 */
Chris@17 44 protected $authors;
Chris@0 45
Chris@0 46 /**
Chris@0 47 * Date the po file got created.
Chris@0 48 *
Chris@0 49 * @var string
Chris@0 50 */
Chris@17 51 protected $poDate;
Chris@0 52
Chris@0 53 /**
Chris@0 54 * Human readable language name.
Chris@0 55 *
Chris@0 56 * @var string
Chris@0 57 */
Chris@17 58 protected $languageName;
Chris@0 59
Chris@0 60 /**
Chris@0 61 * Name of the project the translation belongs to.
Chris@0 62 *
Chris@0 63 * @var string
Chris@0 64 */
Chris@17 65 protected $projectName;
Chris@0 66
Chris@0 67 /**
Chris@0 68 * Constructor, creates a PoHeader with default values.
Chris@0 69 *
Chris@0 70 * @param string $langcode
Chris@0 71 * Language code.
Chris@0 72 */
Chris@0 73 public function __construct($langcode = NULL) {
Chris@17 74 $this->langcode = $langcode;
Chris@0 75 // Ignore errors when run during site installation before
Chris@0 76 // date_default_timezone_set() is called.
Chris@17 77 $this->poDate = @date("Y-m-d H:iO");
Chris@17 78 $this->pluralForms = 'nplurals=2; plural=(n > 1);';
Chris@0 79 }
Chris@0 80
Chris@0 81 /**
Chris@0 82 * Gets the plural form.
Chris@0 83 *
Chris@0 84 * @return string
Chris@0 85 * Plural form component from the header, for example:
Chris@0 86 * 'nplurals=2; plural=(n > 1);'.
Chris@0 87 */
Chris@0 88 public function getPluralForms() {
Chris@17 89 return $this->pluralForms;
Chris@0 90 }
Chris@0 91
Chris@0 92 /**
Chris@0 93 * Set the human readable language name.
Chris@0 94 *
Chris@0 95 * @param string $languageName
Chris@0 96 * Human readable language name.
Chris@0 97 */
Chris@0 98 public function setLanguageName($languageName) {
Chris@17 99 $this->languageName = $languageName;
Chris@0 100 }
Chris@0 101
Chris@0 102 /**
Chris@0 103 * Gets the human readable language name.
Chris@0 104 *
Chris@0 105 * @return string
Chris@0 106 * The human readable language name.
Chris@0 107 */
Chris@0 108 public function getLanguageName() {
Chris@17 109 return $this->languageName;
Chris@0 110 }
Chris@0 111
Chris@0 112 /**
Chris@0 113 * Set the project name.
Chris@0 114 *
Chris@0 115 * @param string $projectName
Chris@0 116 * Human readable project name.
Chris@0 117 */
Chris@0 118 public function setProjectName($projectName) {
Chris@17 119 $this->projectName = $projectName;
Chris@0 120 }
Chris@0 121
Chris@0 122 /**
Chris@0 123 * Gets the project name.
Chris@0 124 *
Chris@0 125 * @return string
Chris@0 126 * The human readable project name.
Chris@0 127 */
Chris@0 128 public function getProjectName() {
Chris@17 129 return $this->projectName;
Chris@0 130 }
Chris@0 131
Chris@0 132 /**
Chris@0 133 * Populate internal values from a string.
Chris@0 134 *
Chris@0 135 * @param string $header
Chris@0 136 * Full header string with key-value pairs.
Chris@0 137 */
Chris@0 138 public function setFromString($header) {
Chris@0 139 // Get an array of all header values for processing.
Chris@0 140 $values = $this->parseHeader($header);
Chris@0 141
Chris@0 142 // There is only one value relevant for our header implementation when
Chris@0 143 // reading, and that is the plural formula.
Chris@0 144 if (!empty($values['Plural-Forms'])) {
Chris@17 145 $this->pluralForms = $values['Plural-Forms'];
Chris@0 146 }
Chris@0 147 }
Chris@0 148
Chris@0 149 /**
Chris@0 150 * Generate a Gettext PO formatted header string based on data set earlier.
Chris@0 151 */
Chris@0 152 public function __toString() {
Chris@0 153 $output = '';
Chris@0 154
Chris@17 155 $isTemplate = empty($this->languageName);
Chris@0 156
Chris@17 157 $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->projectName) . "\n";
Chris@17 158 if (!empty($this->authors)) {
Chris@17 159 $output .= '# Generated by ' . implode("\n# ", $this->authors) . "\n";
Chris@0 160 }
Chris@0 161 $output .= "#\n";
Chris@0 162
Chris@0 163 // Add the actual header information.
Chris@0 164 $output .= "msgid \"\"\n";
Chris@0 165 $output .= "msgstr \"\"\n";
Chris@0 166 $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
Chris@17 167 $output .= "\"POT-Creation-Date: " . $this->poDate . "\\n\"\n";
Chris@17 168 $output .= "\"PO-Revision-Date: " . $this->poDate . "\\n\"\n";
Chris@0 169 $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
Chris@0 170 $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
Chris@0 171 $output .= "\"MIME-Version: 1.0\\n\"\n";
Chris@0 172 $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
Chris@0 173 $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
Chris@17 174 $output .= "\"Plural-Forms: " . $this->pluralForms . "\\n\"\n";
Chris@0 175 $output .= "\n";
Chris@0 176
Chris@0 177 return $output;
Chris@0 178 }
Chris@0 179
Chris@0 180 /**
Chris@0 181 * Parses a Plural-Forms entry from a Gettext Portable Object file header.
Chris@0 182 *
Chris@0 183 * @param string $pluralforms
Chris@0 184 * The Plural-Forms entry value.
Chris@0 185 *
Chris@0 186 * @return
Chris@0 187 * An indexed array of parsed plural formula data. Containing:
Chris@0 188 * - 'nplurals': The number of plural forms defined by the plural formula.
Chris@0 189 * - 'plurals': Array of plural positions keyed by plural value.
Chris@0 190 *
Chris@14 191 * @throws \Exception
Chris@0 192 */
Chris@0 193 public function parsePluralForms($pluralforms) {
Chris@0 194 $plurals = [];
Chris@0 195 // First, delete all whitespace.
Chris@0 196 $pluralforms = strtr($pluralforms, [" " => "", "\t" => ""]);
Chris@0 197
Chris@0 198 // Select the parts that define nplurals and plural.
Chris@0 199 $nplurals = strstr($pluralforms, "nplurals=");
Chris@0 200 if (strpos($nplurals, ";")) {
Chris@0 201 // We want the string from the 10th char, because "nplurals=" length is 9.
Chris@0 202 $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
Chris@0 203 }
Chris@0 204 else {
Chris@0 205 return FALSE;
Chris@0 206 }
Chris@0 207 $plural = strstr($pluralforms, "plural=");
Chris@0 208 if (strpos($plural, ";")) {
Chris@0 209 // We want the string from the 8th char, because "plural=" length is 7.
Chris@0 210 $plural = substr($plural, 7, strpos($plural, ";") - 7);
Chris@0 211 }
Chris@0 212 else {
Chris@0 213 return FALSE;
Chris@0 214 }
Chris@0 215
Chris@0 216 // If the number of plurals is zero, we return a default result.
Chris@0 217 if ($nplurals == 0) {
Chris@0 218 return [$nplurals, ['default' => 0]];
Chris@0 219 }
Chris@0 220
Chris@0 221 // Calculate possible plural positions of different plural values. All known
Chris@0 222 // plural formula's are repetitive above 100.
Chris@0 223 // For data compression we store the last position the array value
Chris@0 224 // changes and store it as default.
Chris@0 225 $element_stack = $this->parseArithmetic($plural);
Chris@0 226 if ($element_stack !== FALSE) {
Chris@0 227 for ($i = 0; $i <= 199; $i++) {
Chris@0 228 $plurals[$i] = $this->evaluatePlural($element_stack, $i);
Chris@0 229 }
Chris@0 230 $default = $plurals[$i - 1];
Chris@0 231 $plurals = array_filter($plurals, function ($value) use ($default) {
Chris@0 232 return ($value != $default);
Chris@0 233 });
Chris@0 234 $plurals['default'] = $default;
Chris@0 235
Chris@0 236 return [$nplurals, $plurals];
Chris@0 237 }
Chris@0 238 else {
Chris@0 239 throw new \Exception('The plural formula could not be parsed.');
Chris@0 240 }
Chris@0 241 }
Chris@0 242
Chris@0 243 /**
Chris@0 244 * Parses a Gettext Portable Object file header.
Chris@0 245 *
Chris@0 246 * @param string $header
Chris@0 247 * A string containing the complete header.
Chris@0 248 *
Chris@0 249 * @return array
Chris@0 250 * An associative array of key-value pairs.
Chris@0 251 */
Chris@0 252 private function parseHeader($header) {
Chris@0 253 $header_parsed = [];
Chris@0 254 $lines = array_map('trim', explode("\n", $header));
Chris@0 255 foreach ($lines as $line) {
Chris@0 256 if ($line) {
Chris@0 257 list($tag, $contents) = explode(":", $line, 2);
Chris@0 258 $header_parsed[trim($tag)] = trim($contents);
Chris@0 259 }
Chris@0 260 }
Chris@0 261 return $header_parsed;
Chris@0 262 }
Chris@0 263
Chris@0 264 /**
Chris@0 265 * Parses and sanitizes an arithmetic formula into a plural element stack.
Chris@0 266 *
Chris@0 267 * While parsing, we ensure, that the operators have the right
Chris@0 268 * precedence and associativity.
Chris@0 269 *
Chris@0 270 * @param string $string
Chris@0 271 * A string containing the arithmetic formula.
Chris@0 272 *
Chris@0 273 * @return
Chris@0 274 * A stack of values and operations to be evaluated.
Chris@0 275 */
Chris@0 276 private function parseArithmetic($string) {
Chris@0 277 // Operator precedence table.
Chris@0 278 $precedence = ["(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8];
Chris@0 279 // Right associativity.
Chris@0 280 $right_associativity = ["?" => 1, ":" => 1];
Chris@0 281
Chris@0 282 $tokens = $this->tokenizeFormula($string);
Chris@0 283
Chris@0 284 // Parse by converting into infix notation then back into postfix
Chris@0 285 // Operator stack - holds math operators and symbols.
Chris@0 286 $operator_stack = [];
Chris@0 287 // Element Stack - holds data to be operated on.
Chris@0 288 $element_stack = [];
Chris@0 289
Chris@0 290 foreach ($tokens as $token) {
Chris@0 291 $current_token = $token;
Chris@0 292
Chris@0 293 // Numbers and the $n variable are simply pushed into $element_stack.
Chris@0 294 if (is_numeric($token)) {
Chris@0 295 $element_stack[] = $current_token;
Chris@0 296 }
Chris@0 297 elseif ($current_token == "n") {
Chris@0 298 $element_stack[] = '$n';
Chris@0 299 }
Chris@0 300 elseif ($current_token == "(") {
Chris@0 301 $operator_stack[] = $current_token;
Chris@0 302 }
Chris@0 303 elseif ($current_token == ")") {
Chris@0 304 $topop = array_pop($operator_stack);
Chris@0 305 while (isset($topop) && ($topop != "(")) {
Chris@0 306 $element_stack[] = $topop;
Chris@0 307 $topop = array_pop($operator_stack);
Chris@0 308 }
Chris@0 309 }
Chris@0 310 elseif (!empty($precedence[$current_token])) {
Chris@0 311 // If it's an operator, then pop from $operator_stack into
Chris@0 312 // $element_stack until the precedence in $operator_stack is less
Chris@0 313 // than current, then push into $operator_stack.
Chris@0 314 $topop = array_pop($operator_stack);
Chris@0 315 while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) {
Chris@0 316 $element_stack[] = $topop;
Chris@0 317 $topop = array_pop($operator_stack);
Chris@0 318 }
Chris@0 319 if ($topop) {
Chris@0 320 // Return element to top.
Chris@0 321 $operator_stack[] = $topop;
Chris@0 322 }
Chris@0 323 // Parentheses are not needed.
Chris@0 324 $operator_stack[] = $current_token;
Chris@0 325 }
Chris@0 326 else {
Chris@0 327 return FALSE;
Chris@0 328 }
Chris@0 329 }
Chris@0 330
Chris@0 331 // Flush operator stack.
Chris@0 332 $topop = array_pop($operator_stack);
Chris@0 333 while ($topop != NULL) {
Chris@0 334 $element_stack[] = $topop;
Chris@0 335 $topop = array_pop($operator_stack);
Chris@0 336 }
Chris@0 337 $return = $element_stack;
Chris@0 338
Chris@0 339 // Now validate stack.
Chris@0 340 $previous_size = count($element_stack) + 1;
Chris@0 341 while (count($element_stack) < $previous_size) {
Chris@0 342 $previous_size = count($element_stack);
Chris@0 343 for ($i = 2; $i < count($element_stack); $i++) {
Chris@0 344 $op = $element_stack[$i];
Chris@0 345 if (!empty($precedence[$op])) {
Chris@0 346 if ($op == ":") {
Chris@0 347 $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
Chris@0 348 }
Chris@0 349 elseif ($op == "?") {
Chris@0 350 $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
Chris@0 351 }
Chris@0 352 else {
Chris@0 353 $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
Chris@0 354 }
Chris@0 355 array_splice($element_stack, $i - 2, 3, $f);
Chris@0 356 break;
Chris@0 357 }
Chris@0 358 }
Chris@0 359 }
Chris@0 360
Chris@0 361 // If only one element is left, the number of operators is appropriate.
Chris@0 362 return count($element_stack) == 1 ? $return : FALSE;
Chris@0 363 }
Chris@0 364
Chris@0 365 /**
Chris@0 366 * Tokenize the formula.
Chris@0 367 *
Chris@0 368 * @param string $formula
Chris@0 369 * A string containing the arithmetic formula.
Chris@0 370 *
Chris@0 371 * @return array
Chris@0 372 * List of arithmetic tokens identified in the formula.
Chris@0 373 */
Chris@0 374 private function tokenizeFormula($formula) {
Chris@0 375 $formula = str_replace(" ", "", $formula);
Chris@0 376 $tokens = [];
Chris@0 377 for ($i = 0; $i < strlen($formula); $i++) {
Chris@0 378 if (is_numeric($formula[$i])) {
Chris@0 379 $num = $formula[$i];
Chris@0 380 $j = $i + 1;
Chris@0 381 while ($j < strlen($formula) && is_numeric($formula[$j])) {
Chris@0 382 $num .= $formula[$j];
Chris@0 383 $j++;
Chris@0 384 }
Chris@0 385 $i = $j - 1;
Chris@0 386 $tokens[] = $num;
Chris@0 387 }
Chris@0 388 elseif ($pos = strpos(" =<>!&|", $formula[$i])) {
Chris@0 389 $next = $formula[$i + 1];
Chris@0 390 switch ($pos) {
Chris@0 391 case 1:
Chris@0 392 case 2:
Chris@0 393 case 3:
Chris@0 394 case 4:
Chris@0 395 if ($next == '=') {
Chris@0 396 $tokens[] = $formula[$i] . '=';
Chris@0 397 $i++;
Chris@0 398 }
Chris@0 399 else {
Chris@0 400 $tokens[] = $formula[$i];
Chris@0 401 }
Chris@0 402 break;
Chris@0 403 case 5:
Chris@0 404 if ($next == '&') {
Chris@0 405 $tokens[] = '&&';
Chris@0 406 $i++;
Chris@0 407 }
Chris@0 408 else {
Chris@0 409 $tokens[] = $formula[$i];
Chris@0 410 }
Chris@0 411 break;
Chris@0 412 case 6:
Chris@0 413 if ($next == '|') {
Chris@0 414 $tokens[] = '||';
Chris@0 415 $i++;
Chris@0 416 }
Chris@0 417 else {
Chris@0 418 $tokens[] = $formula[$i];
Chris@0 419 }
Chris@0 420 break;
Chris@0 421 }
Chris@0 422 }
Chris@0 423 else {
Chris@0 424 $tokens[] = $formula[$i];
Chris@0 425 }
Chris@0 426 }
Chris@0 427 return $tokens;
Chris@0 428 }
Chris@0 429
Chris@0 430 /**
Chris@0 431 * Evaluate the plural element stack using a plural value.
Chris@0 432 *
Chris@0 433 * Using an element stack, which represents a plural formula, we calculate
Chris@0 434 * which plural string should be used for a given plural value.
Chris@0 435 *
Chris@0 436 * An example of plural formula parting and evaluation:
Chris@0 437 * Plural formula: 'n!=1'
Chris@0 438 * This formula is parsed by parseArithmetic() to a stack (array) of elements:
Chris@0 439 * array(
Chris@0 440 * 0 => '$n',
Chris@0 441 * 1 => '1',
Chris@0 442 * 2 => '!=',
Chris@0 443 * );
Chris@0 444 * The evaluatePlural() method evaluates the $element_stack using the plural
Chris@0 445 * value $n. Before the actual evaluation, the '$n' in the array is replaced
Chris@0 446 * by the value of $n.
Chris@0 447 * For example: $n = 2 results in:
Chris@0 448 * array(
Chris@0 449 * 0 => '2',
Chris@0 450 * 1 => '1',
Chris@0 451 * 2 => '!=',
Chris@0 452 * );
Chris@0 453 * The stack is processed until only one element is (the result) is left. In
Chris@0 454 * every iteration the top elements of the stack, up until the first operator,
Chris@0 455 * are evaluated. After evaluation the arguments and the operator itself are
Chris@0 456 * removed and replaced by the evaluation result. This is typically 2
Chris@0 457 * arguments and 1 element for the operator.
Chris@0 458 * Because the operator is '!=' the example stack is evaluated as:
Chris@0 459 * $f = (int) 2 != 1;
Chris@0 460 * The resulting stack is:
Chris@0 461 * array(
Chris@0 462 * 0 => 1,
Chris@0 463 * );
Chris@0 464 * With only one element left in the stack (the final result) the loop is
Chris@0 465 * terminated and the result is returned.
Chris@0 466 *
Chris@0 467 * @param array $element_stack
Chris@0 468 * Array of plural formula values and operators create by parseArithmetic().
Chris@0 469 * @param int $n
Chris@0 470 * The @count number for which we are determining the right plural position.
Chris@0 471 *
Chris@0 472 * @return int
Chris@0 473 * Number of the plural string to be used for the given plural value.
Chris@0 474 *
Chris@0 475 * @see parseArithmetic()
Chris@14 476 * @throws \Exception
Chris@0 477 */
Chris@0 478 protected function evaluatePlural($element_stack, $n) {
Chris@0 479 $count = count($element_stack);
Chris@0 480 $limit = $count;
Chris@0 481 // Replace the '$n' value in the formula by the plural value.
Chris@0 482 for ($i = 0; $i < $count; $i++) {
Chris@0 483 if ($element_stack[$i] === '$n') {
Chris@0 484 $element_stack[$i] = $n;
Chris@0 485 }
Chris@0 486 }
Chris@0 487
Chris@0 488 // We process the stack until only one element is (the result) is left.
Chris@0 489 // We limit the number of evaluation cycles to prevent an endless loop in
Chris@0 490 // case the stack contains an error.
Chris@0 491 while (isset($element_stack[1])) {
Chris@0 492 for ($i = 2; $i < $count; $i++) {
Chris@0 493 // There's no point in checking non-symbols. Also, switch(TRUE) would
Chris@0 494 // match any case and so it would break.
Chris@0 495 if (is_bool($element_stack[$i]) || is_numeric($element_stack[$i])) {
Chris@0 496 continue;
Chris@0 497 }
Chris@0 498 $f = NULL;
Chris@0 499 $length = 3;
Chris@0 500 $delta = 2;
Chris@0 501 switch ($element_stack[$i]) {
Chris@0 502 case '==':
Chris@0 503 $f = $element_stack[$i - 2] == $element_stack[$i - 1];
Chris@0 504 break;
Chris@0 505 case '!=':
Chris@0 506 $f = $element_stack[$i - 2] != $element_stack[$i - 1];
Chris@0 507 break;
Chris@0 508 case '<=':
Chris@0 509 $f = $element_stack[$i - 2] <= $element_stack[$i - 1];
Chris@0 510 break;
Chris@0 511 case '>=':
Chris@0 512 $f = $element_stack[$i - 2] >= $element_stack[$i - 1];
Chris@0 513 break;
Chris@0 514 case '<':
Chris@0 515 $f = $element_stack[$i - 2] < $element_stack[$i - 1];
Chris@0 516 break;
Chris@0 517 case '>':
Chris@0 518 $f = $element_stack[$i - 2] > $element_stack[$i - 1];
Chris@0 519 break;
Chris@0 520 case '+':
Chris@0 521 $f = $element_stack[$i - 2] + $element_stack[$i - 1];
Chris@0 522 break;
Chris@0 523 case '-':
Chris@0 524 $f = $element_stack[$i - 2] - $element_stack[$i - 1];
Chris@0 525 break;
Chris@0 526 case '*':
Chris@0 527 $f = $element_stack[$i - 2] * $element_stack[$i - 1];
Chris@0 528 break;
Chris@0 529 case '/':
Chris@0 530 $f = $element_stack[$i - 2] / $element_stack[$i - 1];
Chris@0 531 break;
Chris@0 532 case '%':
Chris@0 533 $f = $element_stack[$i - 2] % $element_stack[$i - 1];
Chris@0 534 break;
Chris@0 535 case '&&':
Chris@0 536 $f = $element_stack[$i - 2] && $element_stack[$i - 1];
Chris@0 537 break;
Chris@0 538 case '||':
Chris@0 539 $f = $element_stack[$i - 2] || $element_stack[$i - 1];
Chris@0 540 break;
Chris@0 541 case ':':
Chris@0 542 $f = $element_stack[$i - 3] ? $element_stack[$i - 2] : $element_stack[$i - 1];
Chris@0 543 // This operator has 3 preceding elements, instead of the default 2.
Chris@0 544 $length = 5;
Chris@0 545 $delta = 3;
Chris@0 546 break;
Chris@0 547 }
Chris@0 548
Chris@0 549 // If the element is an operator we remove the processed elements and
Chris@0 550 // store the result.
Chris@0 551 if (isset($f)) {
Chris@0 552 array_splice($element_stack, $i - $delta, $length, $f);
Chris@0 553 break;
Chris@0 554 }
Chris@0 555 }
Chris@0 556 }
Chris@0 557 if (!$limit) {
Chris@0 558 throw new \Exception('The plural formula could not be evaluated.');
Chris@0 559 }
Chris@0 560 return (int) $element_stack[0];
Chris@0 561 }
Chris@0 562
Chris@0 563 }