1<?php 2 3namespace Gettext\Languages; 4 5use Exception; 6 7/** 8 * A helper class that handles a single category rules (eg 'zero', 'one', ...) and its formula and examples. 9 */ 10class Category 11{ 12 /** 13 * The category identifier (eg 'zero', 'one', ..., 'other'). 14 * 15 * @var string 16 */ 17 public $id; 18 19 /** 20 * The gettext formula that identifies this category (null if and only if the category is 'other'). 21 * 22 * @var string|null 23 */ 24 public $formula; 25 26 /** 27 * The CLDR representation of some exemplar numeric ranges that satisfy this category. 28 * 29 * @var string|null 30 */ 31 public $examples; 32 33 /** 34 * Initialize the instance and parse the formula. 35 * 36 * @param string $cldrCategoryId the CLDR category identifier (eg 'pluralRule-count-one') 37 * @param string $cldrFormulaAndExamples the CLDR formula and examples (eg 'i = 1 and v = 0 @integer 1') 38 * 39 * @throws \Exception 40 */ 41 public function __construct($cldrCategoryId, $cldrFormulaAndExamples) 42 { 43 $matches = array(); 44 if (!preg_match('/^pluralRule-count-(.+)$/', $cldrCategoryId, $matches)) { 45 throw new Exception("Invalid CLDR category: '${cldrCategoryId}'"); 46 } 47 if (!in_array($matches[1], CldrData::$categories)) { 48 throw new Exception("Invalid CLDR category: '${cldrCategoryId}'"); 49 } 50 $this->id = $matches[1]; 51 $cldrFormulaAndExamplesNormalized = trim(preg_replace('/\s+/', ' ', $cldrFormulaAndExamples)); 52 if (!preg_match('/^([^@]*)(?:@integer([^@]+))?(?:@decimal(?:[^@]+))?$/', $cldrFormulaAndExamplesNormalized, $matches)) { 53 throw new Exception("Invalid CLDR category rule: ${cldrFormulaAndExamples}"); 54 } 55 $cldrFormula = trim($matches[1]); 56 $s = isset($matches[2]) ? trim($matches[2]) : ''; 57 $this->examples = ($s === '') ? null : $s; 58 switch ($this->id) { 59 case CldrData::OTHER_CATEGORY: 60 if ($cldrFormula !== '') { 61 throw new Exception("The '" . CldrData::OTHER_CATEGORY . "' category should not have any formula, but it has '${cldrFormula}'"); 62 } 63 $this->formula = null; 64 break; 65 default: 66 if ($cldrFormula === '') { 67 throw new Exception("The '{$this->id}' category does not have a formula"); 68 } 69 $this->formula = FormulaConverter::convertFormula($cldrFormula); 70 break; 71 } 72 } 73 74 /** 75 * Return a list of numbers corresponding to the $examples value. 76 * 77 * @throws \Exception throws an Exception if we weren't able to expand the examples 78 * 79 * @return int[] 80 */ 81 public function getExampleIntegers() 82 { 83 return self::expandExamples($this->examples); 84 } 85 86 /** 87 * Expand a list of examples as defined by CLDR. 88 * 89 * @param string $examples A string like '1, 2, 5...7, …'. 90 * 91 * @throws \Exception throws an Exception if we weren't able to expand $examples 92 * 93 * @return int[] 94 */ 95 public static function expandExamples($examples) 96 { 97 $result = array(); 98 $m = null; 99 if (substr($examples, -strlen(', …')) === ', …') { 100 $examples = substr($examples, 0, strlen($examples) - strlen(', …')); 101 } 102 foreach (explode(',', str_replace(' ', '', $examples)) as $range) { 103 if (preg_match('/^\d+$/', $range)) { 104 $result[] = (int) $range; 105 } elseif (preg_match('/^(\d+)~(\d+)$/', $range, $m)) { 106 $from = (int) $m[1]; 107 $to = (int) $m[2]; 108 $delta = $to - $from; 109 $step = (int) max(1, $delta / 100); 110 for ($i = $from; $i < $to; $i += $step) { 111 $result[] = $i; 112 } 113 $result[] = $to; 114 } else { 115 throw new Exception("Unhandled test range '${range}' in '${examples}'"); 116 } 117 } 118 if (empty($result)) { 119 throw new Exception("No test numbers from '${examples}'"); 120 } 121 122 return $result; 123 } 124} 125