Passed
Pull Request — master (#4279)
by Owen
18:14 queued 05:25
created

Biff8::readBIFF8CellAddress()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 19
ccs 8
cts 8
cp 1
rs 10
cc 3
nc 4
nop 1
crap 3
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
7
use PhpOffice\PhpSpreadsheet\Reader\Xls;
8
9
class Biff8 extends Xls
10
{
11
    /**
12
     * read BIFF8 constant value array from array data
13
     * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40]
14
     * section 2.5.8.
15
     */
16 1
    protected static function readBIFF8ConstantArray(string $arrayData): array
17
    {
18
        // offset: 0; size: 1; number of columns decreased by 1
19 1
        $nc = ord($arrayData[0]);
20
21
        // offset: 1; size: 2; number of rows decreased by 1
22 1
        $nr = self::getUInt2d($arrayData, 1);
23 1
        $size = 3; // initialize
24 1
        $arrayData = substr($arrayData, 3);
25
26
        // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
27 1
        $matrixChunks = [];
28 1
        for ($r = 1; $r <= $nr + 1; ++$r) {
29 1
            $items = [];
30 1
            for ($c = 1; $c <= $nc + 1; ++$c) {
31 1
                $constant = self::readBIFF8Constant($arrayData);
32 1
                $items[] = $constant['value'];
33 1
                $arrayData = substr($arrayData, $constant['size']);
34 1
                $size += $constant['size'];
35
            }
36 1
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
37
        }
38 1
        $matrix = '{' . implode(';', $matrixChunks) . '}';
39
40 1
        return [
41 1
            'value' => $matrix,
42 1
            'size' => $size,
43 1
        ];
44
    }
45
46
    /**
47
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
48
     * section 2.5.7
49
     * returns e.g. ['value' => '5', 'size' => 9].
50
     */
51 1
    private static function readBIFF8Constant(string $valueData): array
52
    {
53
        // offset: 0; size: 1; identifier for type of constant
54 1
        $identifier = ord($valueData[0]);
55
56
        switch ($identifier) {
57 1
            case 0x00: // empty constant (what is this?)
58
                $value = '';
59
                $size = 9;
60
61
                break;
62 1
            case 0x01: // number
63
                // offset: 1; size: 8; IEEE 754 floating-point value
64 1
                $value = self::extractNumber(substr($valueData, 1, 8));
65 1
                $size = 9;
66
67 1
                break;
68 1
            case 0x02: // string value
69
                // offset: 1; size: var; Unicode string, 16-bit string length
70 1
                $string = self::readUnicodeStringLong(substr($valueData, 1));
71 1
                $value = '"' . $string['value'] . '"';
72 1
                $size = 1 + $string['size'];
73
74 1
                break;
75 1
            case 0x04: // boolean
76
                // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
77 1
                if (ord($valueData[1])) {
78 1
                    $value = 'TRUE';
79
                } else {
80 1
                    $value = 'FALSE';
81
                }
82 1
                $size = 9;
83
84 1
                break;
85
            case 0x10: // error code
86
                // offset: 1; size: 1; error code
87
                $value = ErrorCode::lookup(ord($valueData[1]));
88
                $size = 9;
89
90
                break;
91
            default:
92
                throw new ReaderException('Unsupported BIFF8 constant');
93
        }
94
95 1
        return [
96 1
            'value' => $value,
97 1
            'size' => $size,
98 1
        ];
99
    }
100
101
    /**
102
     * Read BIFF8 cell range address list
103
     * section 2.5.15.
104
     */
105 35
    public static function readBIFF8CellRangeAddressList(string $subData): array
106
    {
107 35
        $cellRangeAddresses = [];
108
109
        // offset: 0; size: 2; number of the following cell range addresses
110 35
        $nm = self::getUInt2d($subData, 0);
111
112 35
        $offset = 2;
113
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
114 35
        for ($i = 0; $i < $nm; ++$i) {
115 35
            $cellRangeAddresses[] = self::readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
116 35
            $offset += 8;
117
        }
118
119 35
        return [
120 35
            'size' => 2 + 8 * $nm,
121 35
            'cellRangeAddresses' => $cellRangeAddresses,
122 35
        ];
123
    }
124
125
    /**
126
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
127
     * section 3.3.4.
128
     */
129 27
    protected static function readBIFF8CellAddress(string $cellAddressStructure): string
130
    {
131
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
132 27
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
133
134
        // offset: 2; size: 2; index to column or column offset + relative flags
135
        // bit: 7-0; mask 0x00FF; column index
136 27
        $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
137
138
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
139 27
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
140 11
            $column = '$' . $column;
141
        }
142
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
143 27
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
144 11
            $row = '$' . $row;
145
        }
146
147 27
        return $column . $row;
148
    }
149
150
    /**
151
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
152
     * to indicate offsets from a base cell
153
     * section 3.3.4.
154
     *
155
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
156
     */
157 4
    protected static function readBIFF8CellAddressB(string $cellAddressStructure, string $baseCell = 'A1'): string
158
    {
159 4
        [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
160 4
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
161 4
        $baseRow = (int) $baseRow;
162
163
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
164 4
        $rowIndex = self::getUInt2d($cellAddressStructure, 0);
165 4
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
166
167
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
168 4
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
169
            // offset: 2; size: 2; index to column or column offset + relative flags
170
            // bit: 7-0; mask 0x00FF; column index
171 3
            $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
172
173 3
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
174 3
            $column = '$' . $column;
175
        } else {
176
            // offset: 2; size: 2; index to column or column offset + relative flags
177
            // bit: 7-0; mask 0x00FF; column index
178 1
            $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
179 1
            $colIndex = $baseCol + $relativeColIndex;
180 1
            $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
181 1
            $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
182 1
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
183
        }
184
185
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
186 4
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
187
            $row = '$' . $row;
188
        } else {
189 4
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
190 4
            $row = $baseRow + $rowIndex;
191
        }
192
193 4
        return $column . $row;
194
    }
195
196
    /**
197
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
198
     * always fixed range
199
     * section 2.5.14.
200
     */
201 38
    protected static function readBIFF8CellRangeAddressFixed(string $subData): string
202
    {
203
        // offset: 0; size: 2; index to first row
204 38
        $fr = self::getUInt2d($subData, 0) + 1;
205
206
        // offset: 2; size: 2; index to last row
207 38
        $lr = self::getUInt2d($subData, 2) + 1;
208
209
        // offset: 4; size: 2; index to first column
210 38
        $fc = self::getUInt2d($subData, 4);
211
212
        // offset: 6; size: 2; index to last column
213 38
        $lc = self::getUInt2d($subData, 6);
214
215
        // check values
216 38
        if ($fr > $lr || $fc > $lc) {
217
            throw new ReaderException('Not a cell range address');
218
        }
219
220
        // column index to letter
221 38
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
222 38
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
223
224 38
        if ($fr == $lr && $fc == $lc) {
225 12
            return "$fc$fr";
226
        }
227
228 32
        return "$fc$fr:$lc$lr";
229
    }
230
231
    /**
232
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
233
     * there are flags indicating whether column/row index is relative
234
     * section 3.3.4.
235
     */
236 32
    protected static function readBIFF8CellRangeAddress(string $subData): string
237
    {
238
        // todo: if cell range is just a single cell, should this funciton
239
        // not just return e.g. 'A1' and not 'A1:A1' ?
240
241
        // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
242 32
        $fr = self::getUInt2d($subData, 0) + 1;
243
244
        // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
245 32
        $lr = self::getUInt2d($subData, 2) + 1;
246
247
        // offset: 4; size: 2; index to first column or column offset + relative flags
248
249
        // bit: 7-0; mask 0x00FF; column index
250 32
        $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
251
252
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
253 32
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
254 14
            $fc = '$' . $fc;
255
        }
256
257
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
258 32
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
259 14
            $fr = '$' . $fr;
260
        }
261
262
        // offset: 6; size: 2; index to last column or column offset + relative flags
263
264
        // bit: 7-0; mask 0x00FF; column index
265 32
        $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
266
267
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
268 32
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
269 14
            $lc = '$' . $lc;
270
        }
271
272
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
273 32
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
274 14
            $lr = '$' . $lr;
275
        }
276
277 32
        return "$fc$fr:$lc$lr";
278
    }
279
280
    /**
281
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
282
     * to indicate offsets from a base cell
283
     * section 3.3.4.
284
     *
285
     * @param string $baseCell Base cell
286
     *
287
     * @return string Cell range address
288
     */
289 1
    protected static function readBIFF8CellRangeAddressB(string $subData, string $baseCell = 'A1'): string
290
    {
291 1
        [$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell);
292 1
        $baseCol = $baseCol - 1;
293
294
        // TODO: if cell range is just a single cell, should this funciton
295
        // not just return e.g. 'A1' and not 'A1:A1' ?
296
297
        // offset: 0; size: 2; first row
298 1
        $frIndex = self::getUInt2d($subData, 0); // adjust below
299
300
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
301 1
        $lrIndex = self::getUInt2d($subData, 2); // adjust below
302
303
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
304 1
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
305
            // absolute column index
306
            // offset: 4; size: 2; first column with relative/absolute flags
307
            // bit: 7-0; mask 0x00FF; column index
308
            $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
309
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
310
            $fc = '$' . $fc;
311
        } else {
312
            // column offset
313
            // offset: 4; size: 2; first column with relative/absolute flags
314
            // bit: 7-0; mask 0x00FF; column index
315 1
            $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
316 1
            $fcIndex = $baseCol + $relativeFcIndex;
317 1
            $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
318 1
            $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
319 1
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
320
        }
321
322
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
323 1
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
324
            // absolute row index
325
            $fr = $frIndex + 1;
326
            $fr = '$' . $fr;
327
        } else {
328
            // row offset
329 1
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
330 1
            $fr = $baseRow + $frIndex;
331
        }
332
333
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
334 1
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
335
            // absolute column index
336
            // offset: 6; size: 2; last column with relative/absolute flags
337
            // bit: 7-0; mask 0x00FF; column index
338
            $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
339
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
340
            $lc = '$' . $lc;
341
        } else {
342
            // column offset
343
            // offset: 6; size: 2; last column with relative/absolute flags
344
            // bit: 7-0; mask 0x00FF; column index
345 1
            $relativeLcIndex = 0x00FF & self::getInt2d($subData, 6);
346 1
            $lcIndex = $baseCol + $relativeLcIndex;
347 1
            $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
348 1
            $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
349 1
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
350
        }
351
352
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
353 1
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
354
            // absolute row index
355
            $lr = $lrIndex + 1;
356
            $lr = '$' . $lr;
357
        } else {
358
            // row offset
359 1
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
360 1
            $lr = $baseRow + $lrIndex;
361
        }
362
363 1
        return "$fc$fr:$lc$lr";
364
    }
365
}
366