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