Passed
Push — issue-93 ( 64da1a...ce707f )
by Sebastian
03:57
created

Number   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Test Coverage

Coverage 97.7%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 100
c 2
b 0
f 0
dl 0
loc 190
ccs 85
cts 87
cp 0.977
rs 9.76
wmc 33

6 Methods

Rating   Name   Duplication   Size   Complexity  
A buildNumberRangeString() 0 13 3
A longOrdinal() 0 8 2
A ordinal() 0 17 6
A __construct() 0 17 4
C render() 0 60 15
A toDecimalNumber() 0 14 3
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
                echo "\n$decimalNumber\n";
91 4
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
92 2
                    $num1 = self::ordinal($matches[1]);
93 2
                    $num2 = self::ordinal($matches[3]);
94 2
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
95
                } else {
96 2
                    $text = self::ordinal($decimalNumber);
97
                }
98 4
                break;
99 18
            case 'long-ordinal':
100 3
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
101 2
                    if ($this->textCase === "capitalize-first" || $this->textCase === "sentence") {
102 1
                        $num1 = self::longOrdinal($matches[1]);
103 1
                        $num2 = self::longOrdinal($matches[3]);
104
                    } else {
105 2
                        $num1 = $this->applyTextCase(self::longOrdinal($matches[1]));
106 2
                        $num2 = $this->applyTextCase(self::longOrdinal($matches[3]));
107
                    }
108 2
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
109
                } else {
110 1
                    $text = self::longOrdinal($decimalNumber);
111
                }
112 3
                break;
113 16
            case 'roman':
114 5
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
115 1
                    $num1 = Util\NumberHelper::dec2roman($matches[1]);
116 1
                    $num2 = Util\NumberHelper::dec2roman($matches[3]);
117 1
                    $text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
118
                } else {
119 4
                    $text = Util\NumberHelper::dec2roman($decimalNumber);
120
                }
121 5
                break;
122 12
            case 'numeric':
123
            default:
124
                /*
125
                 During the extraction, numbers separated by a hyphen are stripped of intervening spaces (“2 - 4”
126
                 becomes “2-4”). Numbers separated by a comma receive one space after the comma (“2,3” and “2 , 3”
127
                 become “2, 3”), while numbers separated by an ampersand receive one space before and one after the
128
                 ampersand (“2&3” becomes “2 & 3”).
129
                 */
130 12
                $decimalNumber = $data->{$this->variable};
131 12
                if (preg_match("/\s*(\d+)\s*([\-\–&,])\s*(\d+)\s*/", $decimalNumber, $matches)) {
132 9
                    $text = $this->buildNumberRangeString($matches[1], $matches[3], $matches[2]);
133
                } else {
134 3
                    $text = $decimalNumber;
135
                }
136 12
                break;
137
        }
138 21
        return $this->wrapDisplayBlock($this->addAffixes($this->format($this->applyTextCase($text, $lang))));
139
    }
140
141
    /**
142
     * @param $num
143
     * @return string
144
     */
145 4
    public static function ordinal($num)
146
    {
147 4
        if (($num / 10) % 10 == 1) {
148 1
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
149 4
        } elseif ($num % 10 == 1) {
150
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-01')->single;
151 4
        } elseif ($num % 10 == 2) {
152 2
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-02')->single;
153 4
        } elseif ($num % 10 == 3) {
154 1
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-03')->single;
155
        } else {
156 3
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-04')->single;
157
        }
158 4
        if (empty($ordinalSuffix)) {
159 3
            $ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
160
        }
161 4
        return $num.$ordinalSuffix;
162
    }
163
164
    /**
165
     * @param $num
166
     * @return string
167
     */
168 3
    public static function longOrdinal($num)
169
    {
170 3
        $num = sprintf("%02d", $num);
171 3
        $ret = CiteProc::getContext()->getLocale()->filter('terms', 'long-ordinal-'.$num)->single;
172 3
        if (!$ret) {
173
            return self::ordinal($num);
174
        }
175 3
        return $ret;
176
    }
177
178
    /**
179
     * @param string|int $num1
180
     * @param string|int $num2
181
     * @param string $delim
182
     * @return string
183
     */
184 11
    public function buildNumberRangeString($num1, $num2, $delim)
185
    {
186
187 11
        if (self::RANGE_DELIMITER_AMPERSAND === $delim) {
188 1
            $numRange = "$num1 ".htmlentities(self::RANGE_DELIMITER_AMPERSAND)." $num2";
189
        } else {
190 11
            if (self::RANGE_DELIMITER_COMMA === $delim) {
191 1
                $numRange = $num1.htmlentities(self::RANGE_DELIMITER_COMMA)." $num2";
192
            } else {
193 11
                $numRange = $num1.self::RANGE_DELIMITER_HYPHEN.$num2;
194
            }
195
        }
196 11
        return $numRange;
197
    }
198
199
    /**
200
     * @param string $number
201
     * @return string
202
     */
203 21
    private function toDecimalNumber($number)
204
    {
205 21
        $decimalNumber = $number;
206 21
        if (Util\NumberHelper::isRomanNumber($number)) {
207 1
            $decimalNumber = Util\NumberHelper::roman2Dec($number);
208
        } else {
209 20
            $number = mb_strtolower($number);
210 20
            if (preg_match(Util\NumberHelper::PATTERN_ROMAN_RANGE, $number, $matches)) {
211 1
                $num1 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[1]));
212 1
                $num2 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[3]));
213 1
                $decimalNumber = sprintf('%d%s%d', $num1, $matches[2], $num2);
214
            }
215
        }
216 21
        return $decimalNumber;
217
    }
218
}
219