Passed
Pull Request — master (#4286)
by Owen
14:54
created

ChooseRowsEtc::drop()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
dl 0
loc 20
rs 9.5222
c 1
b 0
f 0
cc 5
nc 12
nop 3
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7
8
class ChooseRowsEtc
9
{
10
    /**
11
     * Transpose 2-dimensional array.
12
     * See https://stackoverflow.com/questions/797251/transposing-multidimensional-arrays-in-php
13
     * especially the comment from user17994717.
14
     *
15
     * @param mixed[] $array
16
     *
17
     * @return mixed[]
18
     */
19
    public static function transpose(array $array): array
20
    {
21
        return empty($array) ? [] : (array_map((count($array) === 1) ? (fn ($x) => [$x]) : null, ...$array)); // @phpstan-ignore-line
22
    }
23
24
    /** @return mixed[] */
25
    private static function arrayValues(mixed $array): array
26
    {
27
        return is_array($array) ? array_values($array) : [$array];
28
    }
29
30
    /**
31
     * CHOOSECOLS.
32
     *
33
     * @param mixed $input expecting two-dimensional array
34
     *
35
     * @return mixed[]|string
36
     */
37
    public static function chooseCols(mixed $input, mixed ...$args): array|string
38
    {
39
        if (!is_array($input)) {
40
            $input = [[$input]];
41
        }
42
        $retval = self::chooseRows(self::transpose($input), ...$args);
43
44
        return is_array($retval) ? self::transpose($retval) : $retval;
45
    }
46
47
    /**
48
     * CHOOSEROWS.
49
     *
50
     * @param mixed $input expecting two-dimensional array
51
     *
52
     * @return mixed[]|string
53
     */
54
    public static function chooseRows(mixed $input, mixed ...$args): array|string
55
    {
56
        if (!is_array($input)) {
57
            $input = [[$input]];
58
        }
59
        $inputArray = [[]]; // no row 0
60
        $numRows = 0;
61
        foreach ($input as $inputRow) {
62
            $inputArray[] = self::arrayValues($inputRow);
63
            ++$numRows;
64
        }
65
        $outputArray = [];
66
        foreach (Functions::flattenArray2(...$args) as $arg) {
67
            if (!is_numeric($arg)) {
68
                return ExcelError::VALUE();
69
            }
70
            $index = (int) $arg;
71
            if ($index < 0) {
72
                $index += $numRows + 1;
73
            }
74
            if ($index <= 0 || $index > $numRows) {
75
                return ExcelError::VALUE();
76
            }
77
            $outputArray[] = $inputArray[$index];
78
        }
79
80
        return $outputArray;
81
    }
82
83
    private static function dropRows(array $array, mixed $offset): array|string
84
    {
85
        if ($offset === null) {
86
            return $array;
87
        }
88
        if (!is_numeric($offset)) {
89
            return ExcelError::VALUE();
90
        }
91
        $offset = (int) $offset;
92
        $count = count($array);
93
        if (abs($offset) >= $count) {
94
            // In theory, this should be #CALC!, but Excel treats
95
            // #CALC! as corrupt, and it's not worth figuring out why
96
            return ExcelError::VALUE();
97
        }
98
        if ($offset === 0) {
99
            return $array;
100
        }
101
        if ($offset > 0) {
102
            return array_slice($array, $offset);
103
        }
104
105
        return array_slice($array, 0, $count + $offset);
106
    }
107
108
    /**
109
     * DROP.
110
     *
111
     * @param mixed $input expect two-dimensional array
112
     *
113
     * @return mixed[]|string
114
     */
115
    public static function drop(mixed $input, mixed $rows = null, mixed $columns = null): array|string
116
    {
117
        if (!is_array($input)) {
118
            $input = [[$input]];
119
        }
120
        $inputArray = []; // no row 0
121
        foreach ($input as $inputRow) {
122
            $inputArray[] = self::arrayValues($inputRow);
123
        }
124
        $outputArray1 = self::dropRows($inputArray, $rows);
125
        if (is_string($outputArray1)) {
126
            return $outputArray1;
127
        }
128
        $outputArray2 = self::transpose($outputArray1);
129
        $outputArray3 = self::dropRows($outputArray2, $columns);
130
        if (is_string($outputArray3)) {
131
            return $outputArray3;
132
        }
133
134
        return self::transpose($outputArray3);
135
    }
136
137
    private static function takeRows(array $array, mixed $offset): array|string
138
    {
139
        if ($offset === null) {
140
            return $array;
141
        }
142
        if (!is_numeric($offset)) {
143
            return ExcelError::VALUE();
144
        }
145
        $offset = (int) $offset;
146
        if ($offset === 0) {
147
            // should be #CALC! - see above
148
            return ExcelError::VALUE();
149
        }
150
        $count = count($array);
151
        if (abs($offset) >= $count) {
152
            return $array;
153
        }
154
        if ($offset > 0) {
155
            return array_slice($array, 0, $offset);
156
        }
157
158
        return array_slice($array, $count + $offset);
159
    }
160
161
    /**
162
     * TAKE.
163
     *
164
     * @param mixed $input expecting two-dimensional array
165
     *
166
     * @return mixed[]|string
167
     */
168
    public static function take(mixed $input, mixed $rows, mixed $columns = null): array|string
169
    {
170
        if (!is_array($input)) {
171
            $input = [[$input]];
172
        }
173
        if ($rows === null && $columns === null) {
174
            return $input;
175
        }
176
        $inputArray = [];
177
        foreach ($input as $inputRow) {
178
            $inputArray[] = self::arrayValues($inputRow);
179
        }
180
        $outputArray1 = self::takeRows($inputArray, $rows);
181
        if (is_string($outputArray1)) {
182
            return $outputArray1;
183
        }
184
        $outputArray2 = self::transpose($outputArray1);
185
        $outputArray3 = self::takeRows($outputArray2, $columns);
186
        if (is_string($outputArray3)) {
187
            return $outputArray3;
188
        }
189
190
        return self::transpose($outputArray3);
191
    }
192
193
    /**
194
     * EXPAND.
195
     *
196
     * @param mixed $input expecting two-dimensional array
197
     *
198
     * @return mixed[]|string
199
     */
200
    public static function expand(mixed $input, mixed $rows, mixed $columns = null, mixed $pad = '#N/A'): array|string
201
    {
202
        if (!is_array($input)) {
203
            $input = [[$input]];
204
        }
205
        if ($rows === null && $columns === null) {
206
            return $input;
207
        }
208
        $numRows = count($input);
209
        $rows ??= $numRows;
210
        if (!is_numeric($rows)) {
211
            return ExcelError::VALUE();
212
        }
213
        $rows = (int) $rows;
214
        if ($rows < count($input)) {
215
            return ExcelError::VALUE();
216
        }
217
        $numCols = 0;
218
        foreach ($input as $inputRow) {
219
            $numCols = max($numCols, is_array($inputRow) ? count($inputRow) : 1);
220
        }
221
        $columns ??= $numCols;
222
        if (!is_numeric($columns)) {
223
            return ExcelError::VALUE();
224
        }
225
        $columns = (int) $columns;
226
        if ($columns < $numCols) {
227
            return ExcelError::VALUE();
228
        }
229
        $inputArray = [];
230
        foreach ($input as $inputRow) {
231
            $inputArray[] = array_pad(self::arrayValues($inputRow), $columns, $pad);
232
        }
233
        $outputArray = [];
234
        $padRow = array_pad([], $columns, $pad);
235
        for ($count = 0; $count < $rows; ++$count) {
236
            $outputArray[] = ($count >= $numRows) ? $padRow : $inputArray[$count];
237
        }
238
239
        return $outputArray;
240
    }
241
}
242