NumberHelper   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 76
c 6
b 0
f 0
dl 0
loc 177
rs 9.76
wmc 33

9 Methods

Rating   Name   Duplication   Size   Complexity  
A extractNumber() 0 6 2
A isRomanRange() 0 3 1
A getCompareNumber() 0 12 6
A evaluateStringPluralism() 0 14 5
A isRange() 0 13 6
A dec2roman() 0 12 3
A splitByRangeDelimiter() 0 3 1
A roman2Dec() 0 22 6
A isRomanNumber() 0 10 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\Util;
11
12
use Closure;
13
14
/**
15
 * Class Number
16
 * @package Seboettg\CiteProc\Util
17
 *
18
 * @author Sebastian Böttger <[email protected]>
19
 */
20
class NumberHelper
21
{
22
23
    const PATTERN_ORDINAL = "/\d+((st|nd|rd|th)\.?|\.)$/";
24
25
    const PATTERN_ROMAN = "/^[ivxlcdm]+\.?$/i";
26
27
    const PATTERN_ROMAN_RANGE = "/^([ivxlcdm]+\.*)\s*([*\–\-&+,;])\s*([ivxlcdm]+\.?)$/i";
28
29
    const PATTERN_AFFIXES = "/^[a-z]?\d+[a-z]?$/i";
30
31
    const PATTERN_COMMA_AMPERSAND_RANGE = "/\d+([\s?\-&+,;\s])+\d+/";
32
33
    const ROMAN_NUMERALS = [
34
        ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"],
35
        ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"],
36
        ["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"],
37
        ["", "m", "mm", "mmm", "mmmm", "mmmmm"]
38
    ];
39
40
    const ROMAN_DIGITS = [
41
        "M" => 1000,
42
        "D" => 500,
43
        "C" => 100,
44
        "L" => 50,
45
        "X" => 10,
46
        "V" => 5,
47
        "I" => 1
48
    ];
49
50
    /**
51
     * @return Closure
52
     */
53
    public static function getCompareNumber()
54
    {
55
        return function ($numA, $numB, $order) {
56
            if (is_numeric($numA) && is_numeric($numB)) {
57
                $ret = $numA - $numB;
58
            } else {
59
                $ret = strcasecmp($numA, $numB);
60
            }
61
            if ("descending" === $order) {
62
                return $ret > 0 ? -1 : 1;
63
            }
64
            return $ret > 0 ? 1 : -1;
65
        };
66
    }
67
68
    /**
69
     * @param $num
70
     * @return string
71
     */
72
    public static function dec2roman($num)
73
    {
74
        $ret = "";
75
        if ($num < 6000) {
76
            $numStr = strrev($num);
77
            $len = strlen($numStr);
78
            for ($pos = 0; $pos < $len; $pos++) {
79
                $n = $numStr[$pos];
80
                $ret = self::ROMAN_NUMERALS[$pos][$n] . $ret;
81
            }
82
        }
83
        return $ret;
84
    }
85
86
    /**
87
     * @param $romanNumber
88
     * @return int|mixed
89
     */
90
    public static function roman2Dec($romanNumber)
91
    {
92
        $romanNumber = trim($romanNumber);
93
        if (is_numeric($romanNumber)) {
94
            return 0;
95
        }
96
97
        $values = [];
98
        // Convert the string to an array of roman values:
99
        for ($i = 0; $i < mb_strlen($romanNumber); ++$i) {
100
            $char = mb_strtoupper($romanNumber[$i]);
101
            if (isset(self::ROMAN_DIGITS[$char])) {
102
                $values[] = self::ROMAN_DIGITS[$char];
103
            }
104
        }
105
106
        $sum = 0;
107
        while ($current = current($values)) {
108
            $next = next($values);
109
            $next > $current ? $sum += $next - $current + 0 * next($values) : $sum += $current;
110
        }
111
        return $sum;
112
    }
113
114
    /**
115
     * @param $str
116
     * @return bool
117
     */
118
    public static function isRomanNumber($str)
119
    {
120
        $number = trim($str);
121
        for ($i = 0; $i < mb_strlen($number); ++$i) {
122
            $char = mb_strtoupper($number[$i]);
123
            if (!in_array($char, array_keys(self::ROMAN_DIGITS))) {
124
                return false;
125
            }
126
        }
127
        return true;
128
    }
129
130
    /**
131
     * @param $str
132
     * @return string
133
     */
134
    public static function evaluateStringPluralism($str)
135
    {
136
        $plural = 'single';
137
        if (!empty($str)) {
138
            $isRange = self::isRange($str);
139
            if ($isRange) {
140
                return 'multiple';
141
            } else {
142
                if (is_numeric($str) || NumberHelper::isRomanNumber($str)) {
143
                    return 'single';
144
                }
145
            }
146
        }
147
        return $plural;
148
    }
149
150
    /**
151
     * @param $string
152
     * @return mixed
153
     */
154
    public static function extractNumber($string)
155
    {
156
        if (preg_match("/(\d+)[^\d]*$/", $string, $match)) {
157
            return $match[1];
158
        }
159
        return $string;
160
    }
161
162
    /**
163
     * @param $str
164
     * @return array[]|false|string[]
165
     */
166
    public static function splitByRangeDelimiter($str)
167
    {
168
        return preg_split("/[-–&,]/", $str);
169
    }
170
171
    /**
172
     * @param string $str
173
     * @return bool
174
     */
175
    private static function isRange($str)
176
    {
177
        $rangeParts = self::splitByRangeDelimiter($str);
178
        $isRange = false;
179
        if (count($rangeParts) > 1) {
180
            $isRange = true;
181
            foreach ($rangeParts as $range) {
182
                if (NumberHelper::isRomanNumber(trim($range)) || is_numeric(trim($range))) {
183
                    $isRange = $isRange && true;
184
                }
185
            }
186
        }
187
        return $isRange;
188
    }
189
190
    /**
191
     * @param int|string $number
192
     * @return bool
193
     */
194
    public static function isRomanRange($number)
195
    {
196
        return preg_match(self::PATTERN_ROMAN_RANGE, $number);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match(self::...N_ROMAN_RANGE, $number) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
197
    }
198
}
199