Passed
Push — issue-93 ( ce707f...1e3d00 )
by Sebastian
05:06 queued 44s
created

Number::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 1
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 4
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/*
3
 * citeproc-php
4
 *
5
 * @link        http://github.com/seboettg/citeproc-php for the source repository
6
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
7
 * @license     https://opensource.org/licenses/MIT
8
 */
9
10
namespace Seboettg\CiteProc\Rendering;
11
12
use Seboettg\CiteProc\CiteProc;
13
use Seboettg\CiteProc\Styles\AffixesTrait;
14
use Seboettg\CiteProc\Styles\DisplayTrait;
15
use Seboettg\CiteProc\Styles\FormattingTrait;
16
use Seboettg\CiteProc\Styles\TextCaseTrait;
17
use Seboettg\CiteProc\Util;
18
use SimpleXMLElement;
19
use stdClass;
20
21
/**
22
 * Class Number
23
 * @package Seboettg\CiteProc\Rendering
24
 *
25
 * @author Sebastian Böttger <[email protected]>
26
 */
27
class Number implements Rendering
28
{
29
30
    const RANGE_DELIMITER_HYPHEN = "-";
31
32
    const RANGE_DELIMITER_AMPERSAND = "&";
33
34
    const RANGE_DELIMITER_COMMA = ",";
35
36
    use FormattingTrait,
0 ignored issues
show
Bug introduced by
The trait Seboettg\CiteProc\Styles\AffixesTrait requires the property $single which is not provided by Seboettg\CiteProc\Rendering\Number.
Loading history...
37
        AffixesTrait,
38
        TextCaseTrait,
39
        DisplayTrait;
40
41
    /**
42
     * @var string
43
     */
44
    private $variable;
45
46
    /**
47
     * @var string
48
     */
49
    private $form;
50
51
    /**
52
     * Number constructor.
53
     * @param SimpleXMLElement $node
54
     */
55 56
    public function __construct(SimpleXMLElement $node)
56
    {
57
        //<number variable="edition" form="ordinal"/>
58
        /** @var SimpleXMLElement $attribute */
59 56
        foreach ($node->attributes() as $attribute) {
60 56
            switch ($attribute->getName()) {
61 56
                case 'variable':
62 56
                    $this->variable = (string) $attribute;
63 56
                    break;
64 54
                case 'form':
65 56
                    $this->form = (string) $attribute;
66
            }
67
        }
68
69 56
        $this->initFormattingAttributes($node);
70 56
        $this->initAffixesAttributes($node);
71 56
        $this->initTextCaseAttributes($node);
72 56
    }
73
74
    /**
75
     * @param stdClass $data
76
     * @param int|null $citationNumber
77
     * @return string
78
     */
79 28
    public function render($data, $citationNumber = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$citationNumber" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$citationNumber"; expected 0 but found 1
Loading history...
80
    {
81 28
        $lang = (isset($data->language) && $data->language != 'en') ? $data->language : 'en';
82
83 28
        if (empty($this->variable) || empty($data->{$this->variable})) {
84 10
            return "";
85
        }
86 21
        $number = $data->{$this->variable};
87 21
        $decimalNumber = $this->toDecimalNumber($number);
88 21
        switch ($this->form) {
89 21
            case 'ordinal':
90 4
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
91 2
                    $num1 = self::ordinal($matches[1]);
92 2
                    $num2 = self::ordinal($matches[3]);
93 2
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
94
                } else {
95 2
                    $text = self::ordinal($decimalNumber);
96
                }
97 4
                break;
98 18
            case 'long-ordinal':
99 3
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
100 2
                    if ($this->textCase === "capitalize-first" || $this->textCase === "sentence") {
101 1
                        $num1 = self::longOrdinal($matches[1]);
102 1
                        $num2 = self::longOrdinal($matches[3]);
103
                    } else {
104 2
                        $num1 = $this->applyTextCase(self::longOrdinal($matches[1]));
105 2
                        $num2 = $this->applyTextCase(self::longOrdinal($matches[3]));
106
                    }
107 2
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
108
                } else {
109 1
                    $text = self::longOrdinal($decimalNumber);
110
                }
111 3
                break;
112 16
            case 'roman':
113 5
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
114 1
                    $num1 = Util\NumberHelper::dec2roman($matches[1]);
115 1
                    $num2 = Util\NumberHelper::dec2roman($matches[3]);
116 1
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
117
                } else {
118 4
                    $text = Util\NumberHelper::dec2roman($decimalNumber);
119
                }
120 5
                break;
121 12
            case 'numeric':
122
            default:
123
                /*
124
                 During the extraction, numbers separated by a hyphen are stripped of intervening spaces (“2 - 4”
125
                 becomes “2-4”). Numbers separated by a comma receive one space after the comma (“2,3” and “2 , 3”
126
                 become “2, 3”), while numbers separated by an ampersand receive one space before and one after the
127
                 ampersand (“2&3” becomes “2 & 3”).
128
                 */
129 12
                $decimalNumber = $data->{$this->variable};
130 12
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
131 9
                    $text = $this->buildNumberRangeString($matches[1], $matches[3], $matches[2]);
132
                } else {
133 3
                    $text = $decimalNumber;
134
                }
135 12
                break;
136
        }
137 21
        return $this->wrapDisplayBlock($this->addAffixes($this->format($this->applyTextCase($text, $lang))));
138
    }
139
140
    /**
141
     * @param $num
142
     * @return string
143
     */
144 4
    public static function ordinal($num)
145
    {
146 4
        if (($num / 10) % 10 == 1) {
147 1
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
148 4
        } elseif ($num % 10 == 1) {
149
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-01')->single;
150 4
        } elseif ($num % 10 == 2) {
151 2
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-02')->single;
152 4
        } elseif ($num % 10 == 3) {
153 1
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-03')->single;
154
        } else {
155 3
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-04')->single;
156
        }
157 4
        if (empty($ordinalSuffix)) {
158 3
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
159
        }
160 4
        return $num.$ordinalSuffix;
161
    }
162
163
    /**
164
     * @param $num
165
     * @return string
166
     */
167 3
    public static function longOrdinal($num)
168
    {
169 3
        $num = sprintf("%02d", $num);
170 3
        $ret = CiteProc::getContext()->getLocale()->filter('terms', 'long-ordinal-'.$num)->single;
171 3
        if (!$ret) {
172
            return self::ordinal($num);
173
        }
174 3
        return $ret;
175
    }
176
177
    /**
178
     * @param string|int $num1
179
     * @param string|int $num2
180
     * @param string $delim
181
     * @return string
182
     */
183 11
    public function buildNumberRangeString($num1, $num2, $delim)
184
    {
185
186 11
        if (self::RANGE_DELIMITER_AMPERSAND === $delim) {
187 1
            $numRange = "$num1 ".htmlentities(self::RANGE_DELIMITER_AMPERSAND)." $num2";
188
        } else {
189 11
            if (self::RANGE_DELIMITER_COMMA === $delim) {
190 1
                $numRange = $num1.htmlentities(self::RANGE_DELIMITER_COMMA)." $num2";
191
            } else {
192 11
                $numRange = $num1.self::RANGE_DELIMITER_HYPHEN.$num2;
193
            }
194
        }
195 11
        return $numRange;
196
    }
197
198
    /**
199
     * @param string $number
200
     * @return string
201
     */
202 21
    private function toDecimalNumber($number)
203
    {
204 21
        $decimalNumber = $number;
205 21
        if (Util\NumberHelper::isRomanNumber($number)) {
206 1
            $decimalNumber = Util\NumberHelper::roman2Dec($number);
207
        } else {
208 20
            $number = mb_strtolower($number);
209 20
            if (preg_match(Util\NumberHelper::PATTERN_ROMAN_RANGE, $number, $matches)) {
210 1
                $num1 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[1]));
211 1
                $num2 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[3]));
212 1
                $decimalNumber = sprintf('%d%s%d', $num1, $matches[2], $num2);
213
            }
214
        }
215 21
        return $decimalNumber;
216
    }
217
}
218