Total Complexity | 89 |
Total Lines | 1105 |
Duplicated Lines | 0 % |
Coverage | 88.22% |
Changes | 0 |
Complex classes like Workbook often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Workbook, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class Workbook extends BIFFwriter |
||
47 | { |
||
48 | /** |
||
49 | * Formula parser. |
||
50 | * |
||
51 | * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser |
||
52 | */ |
||
53 | private $parser; |
||
54 | |||
55 | /** |
||
56 | * The BIFF file size for the workbook. |
||
57 | * |
||
58 | * @var int |
||
59 | * |
||
60 | * @see calcSheetOffsets() |
||
61 | */ |
||
62 | private $biffSize; |
||
63 | |||
64 | /** |
||
65 | * XF Writers. |
||
66 | * |
||
67 | * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[] |
||
68 | */ |
||
69 | private $xfWriters = []; |
||
70 | |||
71 | /** |
||
72 | * Array containing the colour palette. |
||
73 | * |
||
74 | * @var array |
||
75 | */ |
||
76 | private $palette; |
||
77 | |||
78 | /** |
||
79 | * The codepage indicates the text encoding used for strings. |
||
80 | * |
||
81 | * @var int |
||
82 | */ |
||
83 | private $codepage; |
||
84 | |||
85 | /** |
||
86 | * The country code used for localization. |
||
87 | * |
||
88 | * @var int |
||
89 | */ |
||
90 | private $countryCode; |
||
91 | |||
92 | /** |
||
93 | * Workbook. |
||
94 | * |
||
95 | * @var Spreadsheet |
||
96 | */ |
||
97 | private $spreadsheet; |
||
98 | |||
99 | /** |
||
100 | * Fonts writers. |
||
101 | * |
||
102 | * @var Font[] |
||
103 | */ |
||
104 | private $fontWriters = []; |
||
105 | |||
106 | /** |
||
107 | * Added fonts. Maps from font's hash => index in workbook. |
||
108 | * |
||
109 | * @var array |
||
110 | */ |
||
111 | private $addedFonts = []; |
||
112 | |||
113 | /** |
||
114 | * Shared number formats. |
||
115 | * |
||
116 | * @var array |
||
117 | */ |
||
118 | private $numberFormats = []; |
||
119 | |||
120 | /** |
||
121 | * Added number formats. Maps from numberFormat's hash => index in workbook. |
||
122 | * |
||
123 | * @var array |
||
124 | */ |
||
125 | private $addedNumberFormats = []; |
||
126 | |||
127 | /** |
||
128 | * Sizes of the binary worksheet streams. |
||
129 | * |
||
130 | * @var array |
||
131 | */ |
||
132 | private $worksheetSizes = []; |
||
133 | |||
134 | /** |
||
135 | * Offsets of the binary worksheet streams relative to the start of the global workbook stream. |
||
136 | * |
||
137 | * @var array |
||
138 | */ |
||
139 | private $worksheetOffsets = []; |
||
140 | |||
141 | /** |
||
142 | * Total number of shared strings in workbook. |
||
143 | * |
||
144 | * @var int |
||
145 | */ |
||
146 | private $stringTotal; |
||
147 | |||
148 | /** |
||
149 | * Number of unique shared strings in workbook. |
||
150 | * |
||
151 | * @var int |
||
152 | */ |
||
153 | private $stringUnique; |
||
154 | |||
155 | /** |
||
156 | * Array of unique shared strings in workbook. |
||
157 | * |
||
158 | * @var array |
||
159 | */ |
||
160 | private $stringTable; |
||
161 | |||
162 | /** |
||
163 | * Color cache. |
||
164 | */ |
||
165 | private $colors; |
||
166 | |||
167 | /** |
||
168 | * Escher object corresponding to MSODRAWINGGROUP. |
||
169 | * |
||
170 | * @var \PhpOffice\PhpSpreadsheet\Shared\Escher |
||
171 | */ |
||
172 | private $escher; |
||
173 | |||
174 | /** |
||
175 | * Class constructor. |
||
176 | * |
||
177 | * @param Spreadsheet $spreadsheet The Workbook |
||
178 | * @param int $str_total Total number of strings |
||
179 | * @param int $str_unique Total number of unique strings |
||
180 | * @param array $str_table String Table |
||
181 | * @param array $colors Colour Table |
||
182 | * @param Parser $parser The formula parser created for the Workbook |
||
183 | */ |
||
184 | 52 | public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser) |
|
185 | { |
||
186 | // It needs to call its parent's constructor explicitly |
||
187 | 52 | parent::__construct(); |
|
188 | |||
189 | 52 | $this->parser = $parser; |
|
190 | 52 | $this->biffSize = 0; |
|
191 | 52 | $this->palette = []; |
|
192 | 52 | $this->countryCode = -1; |
|
193 | |||
194 | 52 | $this->stringTotal = &$str_total; |
|
195 | 52 | $this->stringUnique = &$str_unique; |
|
196 | 52 | $this->stringTable = &$str_table; |
|
197 | 52 | $this->colors = &$colors; |
|
198 | 52 | $this->setPaletteXl97(); |
|
199 | |||
200 | 52 | $this->spreadsheet = $spreadsheet; |
|
201 | |||
202 | 52 | $this->codepage = 0x04B0; |
|
203 | |||
204 | // Add empty sheets and Build color cache |
||
205 | 52 | $countSheets = $spreadsheet->getSheetCount(); |
|
206 | 52 | for ($i = 0; $i < $countSheets; ++$i) { |
|
207 | 52 | $phpSheet = $spreadsheet->getSheet($i); |
|
208 | |||
209 | 52 | $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser |
|
210 | |||
211 | 52 | $supbook_index = 0x00; |
|
212 | 52 | $ref = pack('vvv', $supbook_index, $i, $i); |
|
213 | 52 | $this->parser->references[] = $ref; // Register reference with parser |
|
214 | |||
215 | // Sheet tab colors? |
||
216 | 52 | if ($phpSheet->isTabColorSet()) { |
|
217 | 5 | $this->addColor($phpSheet->getTabColor()->getRGB()); |
|
218 | } |
||
219 | } |
||
220 | 52 | } |
|
221 | |||
222 | /** |
||
223 | * Add a new XF writer. |
||
224 | * |
||
225 | * @param Style $style |
||
226 | * @param bool $isStyleXf Is it a style XF? |
||
227 | * |
||
228 | * @return int Index to XF record |
||
229 | */ |
||
230 | 43 | public function addXfWriter(Style $style, $isStyleXf = false) |
|
231 | { |
||
232 | 43 | $xfWriter = new Xf($style); |
|
233 | 43 | $xfWriter->setIsStyleXf($isStyleXf); |
|
234 | |||
235 | // Add the font if not already added |
||
236 | 43 | $fontIndex = $this->addFont($style->getFont()); |
|
237 | |||
238 | // Assign the font index to the xf record |
||
239 | 43 | $xfWriter->setFontIndex($fontIndex); |
|
240 | |||
241 | // Background colors, best to treat these after the font so black will come after white in custom palette |
||
242 | 43 | $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB())); |
|
243 | 43 | $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB())); |
|
244 | 43 | $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB())); |
|
245 | 43 | $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB())); |
|
246 | 43 | $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB())); |
|
247 | 43 | $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB())); |
|
248 | 43 | $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB())); |
|
249 | |||
250 | // Add the number format if it is not a built-in one and not already added |
||
251 | 43 | if ($style->getNumberFormat()->getBuiltInFormatCode() === false) { |
|
1 ignored issue
–
show
|
|||
252 | 14 | $numberFormatHashCode = $style->getNumberFormat()->getHashCode(); |
|
253 | |||
254 | 14 | if (isset($this->addedNumberFormats[$numberFormatHashCode])) { |
|
255 | 6 | $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode]; |
|
256 | } else { |
||
257 | 14 | $numberFormatIndex = 164 + count($this->numberFormats); |
|
258 | 14 | $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat(); |
|
259 | 14 | $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex; |
|
260 | } |
||
261 | } else { |
||
262 | 43 | $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode(); |
|
263 | } |
||
264 | |||
265 | // Assign the number format index to xf record |
||
266 | 43 | $xfWriter->setNumberFormatIndex($numberFormatIndex); |
|
267 | |||
268 | 43 | $this->xfWriters[] = $xfWriter; |
|
269 | |||
270 | 43 | $xfIndex = count($this->xfWriters) - 1; |
|
271 | |||
272 | 43 | return $xfIndex; |
|
273 | } |
||
274 | |||
275 | /** |
||
276 | * Add a font to added fonts. |
||
277 | * |
||
278 | * @param \PhpOffice\PhpSpreadsheet\Style\Font $font |
||
279 | * |
||
280 | * @return int Index to FONT record |
||
281 | */ |
||
282 | 43 | public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font) |
|
283 | { |
||
284 | 43 | $fontHashCode = $font->getHashCode(); |
|
285 | 43 | if (isset($this->addedFonts[$fontHashCode])) { |
|
286 | 43 | $fontIndex = $this->addedFonts[$fontHashCode]; |
|
287 | } else { |
||
288 | 43 | $countFonts = count($this->fontWriters); |
|
289 | 43 | $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1; |
|
290 | |||
291 | 43 | $fontWriter = new Font($font); |
|
292 | 43 | $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB())); |
|
293 | 43 | $this->fontWriters[] = $fontWriter; |
|
294 | |||
295 | 43 | $this->addedFonts[$fontHashCode] = $fontIndex; |
|
296 | } |
||
297 | |||
298 | 43 | return $fontIndex; |
|
299 | } |
||
300 | |||
301 | /** |
||
302 | * Alter color palette adding a custom color. |
||
303 | * |
||
304 | * @param string $rgb E.g. 'FF00AA' |
||
305 | * |
||
306 | * @return int Color index |
||
307 | */ |
||
308 | 52 | private function addColor($rgb) |
|
309 | { |
||
310 | 52 | if (!isset($this->colors[$rgb])) { |
|
311 | $color = |
||
312 | [ |
||
313 | 52 | hexdec(substr($rgb, 0, 2)), |
|
314 | 52 | hexdec(substr($rgb, 2, 2)), |
|
315 | 52 | hexdec(substr($rgb, 4)), |
|
316 | 52 | 0, |
|
317 | ]; |
||
318 | 52 | $colorIndex = array_search($color, $this->palette); |
|
319 | 52 | if ($colorIndex) { |
|
320 | 49 | $this->colors[$rgb] = $colorIndex; |
|
321 | } else { |
||
322 | 17 | if (count($this->colors) == 0) { |
|
323 | 9 | $lastColor = 7; |
|
324 | } else { |
||
325 | 15 | $lastColor = end($this->colors); |
|
326 | } |
||
327 | 17 | if ($lastColor < 57) { |
|
328 | // then we add a custom color altering the palette |
||
329 | 15 | $colorIndex = $lastColor + 1; |
|
330 | 15 | $this->palette[$colorIndex] = $color; |
|
331 | 15 | $this->colors[$rgb] = $colorIndex; |
|
332 | } else { |
||
333 | // no room for more custom colors, just map to black |
||
334 | 52 | $colorIndex = 0; |
|
335 | } |
||
336 | } |
||
337 | } else { |
||
338 | // fetch already added custom color |
||
339 | 45 | $colorIndex = $this->colors[$rgb]; |
|
340 | } |
||
341 | |||
342 | 52 | return $colorIndex; |
|
343 | } |
||
344 | |||
345 | /** |
||
346 | * Sets the colour palette to the Excel 97+ default. |
||
347 | */ |
||
348 | 52 | private function setPaletteXl97() |
|
349 | { |
||
350 | 52 | $this->palette = [ |
|
351 | 0x08 => [0x00, 0x00, 0x00, 0x00], |
||
352 | 0x09 => [0xff, 0xff, 0xff, 0x00], |
||
353 | 0x0A => [0xff, 0x00, 0x00, 0x00], |
||
354 | 0x0B => [0x00, 0xff, 0x00, 0x00], |
||
355 | 0x0C => [0x00, 0x00, 0xff, 0x00], |
||
356 | 0x0D => [0xff, 0xff, 0x00, 0x00], |
||
357 | 0x0E => [0xff, 0x00, 0xff, 0x00], |
||
358 | 0x0F => [0x00, 0xff, 0xff, 0x00], |
||
359 | 0x10 => [0x80, 0x00, 0x00, 0x00], |
||
360 | 0x11 => [0x00, 0x80, 0x00, 0x00], |
||
361 | 0x12 => [0x00, 0x00, 0x80, 0x00], |
||
362 | 0x13 => [0x80, 0x80, 0x00, 0x00], |
||
363 | 0x14 => [0x80, 0x00, 0x80, 0x00], |
||
364 | 0x15 => [0x00, 0x80, 0x80, 0x00], |
||
365 | 0x16 => [0xc0, 0xc0, 0xc0, 0x00], |
||
366 | 0x17 => [0x80, 0x80, 0x80, 0x00], |
||
367 | 0x18 => [0x99, 0x99, 0xff, 0x00], |
||
368 | 0x19 => [0x99, 0x33, 0x66, 0x00], |
||
369 | 0x1A => [0xff, 0xff, 0xcc, 0x00], |
||
370 | 0x1B => [0xcc, 0xff, 0xff, 0x00], |
||
371 | 0x1C => [0x66, 0x00, 0x66, 0x00], |
||
372 | 0x1D => [0xff, 0x80, 0x80, 0x00], |
||
373 | 0x1E => [0x00, 0x66, 0xcc, 0x00], |
||
374 | 0x1F => [0xcc, 0xcc, 0xff, 0x00], |
||
375 | 0x20 => [0x00, 0x00, 0x80, 0x00], |
||
376 | 0x21 => [0xff, 0x00, 0xff, 0x00], |
||
377 | 0x22 => [0xff, 0xff, 0x00, 0x00], |
||
378 | 0x23 => [0x00, 0xff, 0xff, 0x00], |
||
379 | 0x24 => [0x80, 0x00, 0x80, 0x00], |
||
380 | 0x25 => [0x80, 0x00, 0x00, 0x00], |
||
381 | 0x26 => [0x00, 0x80, 0x80, 0x00], |
||
382 | 0x27 => [0x00, 0x00, 0xff, 0x00], |
||
383 | 0x28 => [0x00, 0xcc, 0xff, 0x00], |
||
384 | 0x29 => [0xcc, 0xff, 0xff, 0x00], |
||
385 | 0x2A => [0xcc, 0xff, 0xcc, 0x00], |
||
386 | 0x2B => [0xff, 0xff, 0x99, 0x00], |
||
387 | 0x2C => [0x99, 0xcc, 0xff, 0x00], |
||
388 | 0x2D => [0xff, 0x99, 0xcc, 0x00], |
||
389 | 0x2E => [0xcc, 0x99, 0xff, 0x00], |
||
390 | 0x2F => [0xff, 0xcc, 0x99, 0x00], |
||
391 | 0x30 => [0x33, 0x66, 0xff, 0x00], |
||
392 | 0x31 => [0x33, 0xcc, 0xcc, 0x00], |
||
393 | 0x32 => [0x99, 0xcc, 0x00, 0x00], |
||
394 | 0x33 => [0xff, 0xcc, 0x00, 0x00], |
||
395 | 0x34 => [0xff, 0x99, 0x00, 0x00], |
||
396 | 0x35 => [0xff, 0x66, 0x00, 0x00], |
||
397 | 0x36 => [0x66, 0x66, 0x99, 0x00], |
||
398 | 0x37 => [0x96, 0x96, 0x96, 0x00], |
||
399 | 0x38 => [0x00, 0x33, 0x66, 0x00], |
||
400 | 0x39 => [0x33, 0x99, 0x66, 0x00], |
||
401 | 0x3A => [0x00, 0x33, 0x00, 0x00], |
||
402 | 0x3B => [0x33, 0x33, 0x00, 0x00], |
||
403 | 0x3C => [0x99, 0x33, 0x00, 0x00], |
||
404 | 0x3D => [0x99, 0x33, 0x66, 0x00], |
||
405 | 0x3E => [0x33, 0x33, 0x99, 0x00], |
||
406 | 0x3F => [0x33, 0x33, 0x33, 0x00], |
||
407 | ]; |
||
408 | 52 | } |
|
409 | |||
410 | /** |
||
411 | * Assemble worksheets into a workbook and send the BIFF data to an OLE |
||
412 | * storage. |
||
413 | * |
||
414 | * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams |
||
415 | * |
||
416 | * @return string Binary data for workbook stream |
||
417 | */ |
||
418 | 43 | public function writeWorkbook(array $pWorksheetSizes) |
|
419 | { |
||
420 | 43 | $this->worksheetSizes = $pWorksheetSizes; |
|
421 | |||
422 | // Calculate the number of selected worksheet tabs and call the finalization |
||
423 | // methods for each worksheet |
||
424 | 43 | $total_worksheets = $this->spreadsheet->getSheetCount(); |
|
425 | |||
426 | // Add part 1 of the Workbook globals, what goes before the SHEET records |
||
427 | 43 | $this->storeBof(0x0005); |
|
428 | 43 | $this->writeCodepage(); |
|
429 | 43 | $this->writeWindow1(); |
|
430 | |||
431 | 43 | $this->writeDateMode(); |
|
432 | 43 | $this->writeAllFonts(); |
|
433 | 43 | $this->writeAllNumberFormats(); |
|
434 | 43 | $this->writeAllXfs(); |
|
435 | 43 | $this->writeAllStyles(); |
|
436 | 43 | $this->writePalette(); |
|
437 | |||
438 | // Prepare part 3 of the workbook global stream, what goes after the SHEET records |
||
439 | 43 | $part3 = ''; |
|
440 | 43 | if ($this->countryCode != -1) { |
|
441 | $part3 .= $this->writeCountry(); |
||
442 | } |
||
443 | 43 | $part3 .= $this->writeRecalcId(); |
|
444 | |||
445 | 43 | $part3 .= $this->writeSupbookInternal(); |
|
446 | /* TODO: store external SUPBOOK records and XCT and CRN records |
||
447 | in case of external references for BIFF8 */ |
||
448 | 43 | $part3 .= $this->writeExternalsheetBiff8(); |
|
449 | 43 | $part3 .= $this->writeAllDefinedNamesBiff8(); |
|
450 | 43 | $part3 .= $this->writeMsoDrawingGroup(); |
|
451 | 43 | $part3 .= $this->writeSharedStringsTable(); |
|
452 | |||
453 | 43 | $part3 .= $this->writeEof(); |
|
454 | |||
455 | // Add part 2 of the Workbook globals, the SHEET records |
||
456 | 43 | $this->calcSheetOffsets(); |
|
457 | 43 | for ($i = 0; $i < $total_worksheets; ++$i) { |
|
458 | 43 | $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]); |
|
459 | } |
||
460 | |||
461 | // Add part 3 of the Workbook globals |
||
462 | 43 | $this->_data .= $part3; |
|
463 | |||
464 | 43 | return $this->_data; |
|
465 | } |
||
466 | |||
467 | /** |
||
468 | * Calculate offsets for Worksheet BOF records. |
||
469 | */ |
||
470 | 43 | private function calcSheetOffsets() |
|
471 | { |
||
472 | 43 | $boundsheet_length = 10; // fixed length for a BOUNDSHEET record |
|
473 | |||
474 | // size of Workbook globals part 1 + 3 |
||
475 | 43 | $offset = $this->_datasize; |
|
476 | |||
477 | // add size of Workbook globals part 2, the length of the SHEET records |
||
478 | 43 | $total_worksheets = count($this->spreadsheet->getAllSheets()); |
|
479 | 43 | foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) { |
|
480 | 43 | $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle())); |
|
481 | } |
||
482 | |||
483 | // add the sizes of each of the Sheet substreams, respectively |
||
484 | 43 | for ($i = 0; $i < $total_worksheets; ++$i) { |
|
485 | 43 | $this->worksheetOffsets[$i] = $offset; |
|
486 | 43 | $offset += $this->worksheetSizes[$i]; |
|
487 | } |
||
488 | 43 | $this->biffSize = $offset; |
|
489 | 43 | } |
|
490 | |||
491 | /** |
||
492 | * Store the Excel FONT records. |
||
493 | */ |
||
494 | 43 | private function writeAllFonts() |
|
495 | { |
||
496 | 43 | foreach ($this->fontWriters as $fontWriter) { |
|
497 | 43 | $this->append($fontWriter->writeFont()); |
|
498 | } |
||
499 | 43 | } |
|
500 | |||
501 | /** |
||
502 | * Store user defined numerical formats i.e. FORMAT records. |
||
503 | */ |
||
504 | 43 | private function writeAllNumberFormats() |
|
505 | { |
||
506 | 43 | foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) { |
|
507 | 14 | $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex); |
|
508 | } |
||
509 | 43 | } |
|
510 | |||
511 | /** |
||
512 | * Write all XF records. |
||
513 | */ |
||
514 | 43 | private function writeAllXfs() |
|
518 | } |
||
519 | 43 | } |
|
520 | |||
521 | /** |
||
522 | * Write all STYLE records. |
||
523 | */ |
||
524 | 43 | private function writeAllStyles() |
|
525 | { |
||
526 | 43 | $this->writeStyle(); |
|
527 | 43 | } |
|
528 | |||
529 | /** |
||
530 | * Writes all the DEFINEDNAME records (BIFF8). |
||
531 | * So far this is only used for repeating rows/columns (print titles) and print areas. |
||
532 | */ |
||
533 | 43 | private function writeAllDefinedNamesBiff8() |
|
534 | { |
||
535 | 43 | $chunk = ''; |
|
536 | |||
537 | // Named ranges |
||
538 | 43 | if (count($this->spreadsheet->getNamedRanges()) > 0) { |
|
539 | // Loop named ranges |
||
540 | 4 | $namedRanges = $this->spreadsheet->getNamedRanges(); |
|
541 | 4 | foreach ($namedRanges as $namedRange) { |
|
542 | // Create absolute coordinate |
||
543 | 4 | $range = Coordinate::splitRange($namedRange->getRange()); |
|
544 | 4 | $iMax = count($range); |
|
545 | 4 | for ($i = 0; $i < $iMax; ++$i) { |
|
546 | 4 | $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteCoordinate($range[$i][0]); |
|
547 | 4 | if (isset($range[$i][1])) { |
|
548 | 3 | $range[$i][1] = Coordinate::absoluteCoordinate($range[$i][1]); |
|
549 | } |
||
550 | } |
||
551 | 4 | $range = Coordinate::buildRange($range); // e.g. Sheet1!$A$1:$B$2 |
|
552 | |||
553 | // parse formula |
||
554 | try { |
||
555 | 4 | $error = $this->parser->parse($range); |
|
556 | 4 | $formulaData = $this->parser->toReversePolish(); |
|
557 | |||
558 | // make sure tRef3d is of type tRef3dR (0x3A) |
||
559 | 4 | if (isset($formulaData[0]) and ($formulaData[0] == "\x7A" or $formulaData[0] == "\x5A")) { |
|
560 | 1 | $formulaData = "\x3A" . substr($formulaData, 1); |
|
561 | } |
||
562 | |||
563 | 4 | if ($namedRange->getLocalOnly()) { |
|
564 | // local scope |
||
565 | $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1; |
||
566 | } else { |
||
567 | // global scope |
||
568 | 4 | $scope = 0; |
|
569 | } |
||
570 | 4 | $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false)); |
|
571 | 4 | } catch (PhpSpreadsheetException $e) { |
|
572 | // do nothing |
||
573 | } |
||
574 | } |
||
575 | } |
||
576 | |||
577 | // total number of sheets |
||
578 | 43 | $total_worksheets = $this->spreadsheet->getSheetCount(); |
|
579 | |||
580 | // write the print titles (repeating rows, columns), if any |
||
581 | 43 | for ($i = 0; $i < $total_worksheets; ++$i) { |
|
582 | 43 | $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup(); |
|
583 | // simultaneous repeatColumns repeatRows |
||
584 | 43 | if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) { |
|
585 | $repeat = $sheetSetup->getColumnsToRepeatAtLeft(); |
||
586 | $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1; |
||
587 | $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1; |
||
588 | |||
589 | $repeat = $sheetSetup->getRowsToRepeatAtTop(); |
||
590 | $rowmin = $repeat[0] - 1; |
||
591 | $rowmax = $repeat[1] - 1; |
||
592 | |||
593 | // construct formula data manually |
||
594 | $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc |
||
595 | $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d |
||
596 | $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d |
||
597 | $formulaData .= pack('C', 0x10); // tList |
||
598 | |||
599 | // store the DEFINEDNAME record |
||
600 | $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true)); |
||
601 | |||
602 | // (exclusive) either repeatColumns or repeatRows |
||
603 | 43 | } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) { |
|
604 | // Columns to repeat |
||
605 | 1 | if ($sheetSetup->isColumnsToRepeatAtLeftSet()) { |
|
606 | $repeat = $sheetSetup->getColumnsToRepeatAtLeft(); |
||
607 | $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1; |
||
608 | $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1; |
||
609 | } else { |
||
610 | 1 | $colmin = 0; |
|
611 | 1 | $colmax = 255; |
|
612 | } |
||
613 | // Rows to repeat |
||
614 | 1 | if ($sheetSetup->isRowsToRepeatAtTopSet()) { |
|
615 | 1 | $repeat = $sheetSetup->getRowsToRepeatAtTop(); |
|
616 | 1 | $rowmin = $repeat[0] - 1; |
|
617 | 1 | $rowmax = $repeat[1] - 1; |
|
618 | } else { |
||
619 | $rowmin = 0; |
||
620 | $rowmax = 65535; |
||
621 | } |
||
622 | |||
623 | // construct formula data manually because parser does not recognize absolute 3d cell references |
||
624 | 1 | $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax); |
|
625 | |||
626 | // store the DEFINEDNAME record |
||
627 | 1 | $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true)); |
|
628 | } |
||
629 | } |
||
630 | |||
631 | // write the print areas, if any |
||
632 | 43 | for ($i = 0; $i < $total_worksheets; ++$i) { |
|
633 | 43 | $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup(); |
|
634 | 43 | if ($sheetSetup->isPrintAreaSet()) { |
|
635 | // Print area, e.g. A3:J6,H1:X20 |
||
636 | 1 | $printArea = Coordinate::splitRange($sheetSetup->getPrintArea()); |
|
637 | 1 | $countPrintArea = count($printArea); |
|
638 | |||
639 | 1 | $formulaData = ''; |
|
640 | 1 | for ($j = 0; $j < $countPrintArea; ++$j) { |
|
641 | 1 | $printAreaRect = $printArea[$j]; // e.g. A3:J6 |
|
642 | 1 | $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]); |
|
643 | 1 | $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]); |
|
644 | |||
645 | 1 | $print_rowmin = $printAreaRect[0][1] - 1; |
|
646 | 1 | $print_rowmax = $printAreaRect[1][1] - 1; |
|
647 | 1 | $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1; |
|
648 | 1 | $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1; |
|
649 | |||
650 | // construct formula data manually because parser does not recognize absolute 3d cell references |
||
651 | 1 | $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax); |
|
652 | |||
653 | 1 | if ($j > 0) { |
|
654 | 1 | $formulaData .= pack('C', 0x10); // list operator token ',' |
|
655 | } |
||
656 | } |
||
657 | |||
658 | // store the DEFINEDNAME record |
||
659 | 1 | $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true)); |
|
660 | } |
||
661 | } |
||
662 | |||
663 | // write autofilters, if any |
||
664 | 43 | for ($i = 0; $i < $total_worksheets; ++$i) { |
|
665 | 43 | $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter(); |
|
666 | 43 | $autoFilterRange = $sheetAutoFilter->getRange(); |
|
667 | 43 | if (!empty($autoFilterRange)) { |
|
668 | 3 | $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange); |
|
669 | |||
670 | //Autofilter built in name |
||
671 | 3 | $name = pack('C', 0x0D); |
|
672 | |||
673 | 3 | $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true)); |
|
674 | } |
||
675 | } |
||
676 | |||
677 | 43 | return $chunk; |
|
678 | } |
||
679 | |||
680 | /** |
||
681 | * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data. |
||
682 | * |
||
683 | * @param string $name The name in UTF-8 |
||
684 | * @param string $formulaData The binary formula data |
||
685 | * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global |
||
686 | * @param bool $isBuiltIn Built-in name? |
||
687 | * |
||
688 | * @return string Complete binary record data |
||
689 | */ |
||
690 | 6 | private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false) |
|
691 | { |
||
692 | 6 | $record = 0x0018; |
|
693 | |||
694 | // option flags |
||
695 | 6 | $options = $isBuiltIn ? 0x20 : 0x00; |
|
696 | |||
697 | // length of the name, character count |
||
698 | 6 | $nlen = StringHelper::countCharacters($name); |
|
699 | |||
700 | // name with stripped length field |
||
701 | 6 | $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2); |
|
702 | |||
703 | // size of the formula (in bytes) |
||
704 | 6 | $sz = strlen($formulaData); |
|
705 | |||
706 | // combine the parts |
||
707 | 6 | $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0) |
|
708 | 6 | . $name . $formulaData; |
|
709 | 6 | $length = strlen($data); |
|
710 | |||
711 | 6 | $header = pack('vv', $record, $length); |
|
712 | |||
713 | 6 | return $header . $data; |
|
714 | } |
||
715 | |||
716 | /** |
||
717 | * Write a short NAME record. |
||
718 | * |
||
719 | * @param string $name |
||
720 | * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global |
||
721 | * @param integer[][] $rangeBounds range boundaries |
||
722 | * @param bool $isHidden |
||
723 | * |
||
724 | * @return string Complete binary record data |
||
725 | * */ |
||
726 | 3 | private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false) |
|
727 | { |
||
728 | 3 | $record = 0x0018; |
|
729 | |||
730 | // option flags |
||
731 | 3 | $options = ($isHidden ? 0x21 : 0x00); |
|
732 | |||
733 | 3 | $extra = pack( |
|
734 | 3 | 'Cvvvvv', |
|
735 | 3 | 0x3B, |
|
736 | 3 | $sheetIndex - 1, |
|
737 | 3 | $rangeBounds[0][1] - 1, |
|
738 | 3 | $rangeBounds[1][1] - 1, |
|
739 | 3 | $rangeBounds[0][0] - 1, |
|
740 | 3 | $rangeBounds[1][0] - 1 |
|
741 | ); |
||
742 | |||
743 | // size of the formula (in bytes) |
||
744 | 3 | $sz = strlen($extra); |
|
745 | |||
746 | // combine the parts |
||
747 | 3 | $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0) |
|
748 | 3 | . $name . $extra; |
|
749 | 3 | $length = strlen($data); |
|
750 | |||
751 | 3 | $header = pack('vv', $record, $length); |
|
752 | |||
753 | 3 | return $header . $data; |
|
754 | } |
||
755 | |||
756 | /** |
||
757 | * Stores the CODEPAGE biff record. |
||
758 | */ |
||
759 | 43 | private function writeCodepage() |
|
760 | { |
||
761 | 43 | $record = 0x0042; // Record identifier |
|
762 | 43 | $length = 0x0002; // Number of bytes to follow |
|
763 | 43 | $cv = $this->codepage; // The code page |
|
764 | |||
765 | 43 | $header = pack('vv', $record, $length); |
|
766 | 43 | $data = pack('v', $cv); |
|
767 | |||
768 | 43 | $this->append($header . $data); |
|
769 | 43 | } |
|
770 | |||
771 | /** |
||
772 | * Write Excel BIFF WINDOW1 record. |
||
773 | */ |
||
774 | 43 | private function writeWindow1() |
|
775 | { |
||
776 | 43 | $record = 0x003D; // Record identifier |
|
777 | 43 | $length = 0x0012; // Number of bytes to follow |
|
778 | |||
779 | 43 | $xWn = 0x0000; // Horizontal position of window |
|
780 | 43 | $yWn = 0x0000; // Vertical position of window |
|
781 | 43 | $dxWn = 0x25BC; // Width of window |
|
782 | 43 | $dyWn = 0x1572; // Height of window |
|
783 | |||
784 | 43 | $grbit = 0x0038; // Option flags |
|
785 | |||
786 | // not supported by PhpSpreadsheet, so there is only one selected sheet, the active |
||
787 | 43 | $ctabsel = 1; // Number of workbook tabs selected |
|
788 | |||
789 | 43 | $wTabRatio = 0x0258; // Tab to scrollbar ratio |
|
790 | |||
791 | // not supported by PhpSpreadsheet, set to 0 |
||
792 | 43 | $itabFirst = 0; // 1st displayed worksheet |
|
793 | 43 | $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet |
|
794 | |||
795 | 43 | $header = pack('vv', $record, $length); |
|
796 | 43 | $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio); |
|
797 | 43 | $this->append($header . $data); |
|
798 | 43 | } |
|
799 | |||
800 | /** |
||
801 | * Writes Excel BIFF BOUNDSHEET record. |
||
802 | * |
||
803 | * @param Worksheet $sheet Worksheet name |
||
804 | * @param int $offset Location of worksheet BOF |
||
805 | */ |
||
806 | 43 | private function writeBoundSheet($sheet, $offset) |
|
807 | { |
||
808 | 43 | $sheetname = $sheet->getTitle(); |
|
809 | 43 | $record = 0x0085; // Record identifier |
|
810 | |||
811 | // sheet state |
||
812 | 43 | switch ($sheet->getSheetState()) { |
|
813 | case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE: |
||
814 | 43 | $ss = 0x00; |
|
815 | |||
816 | 43 | break; |
|
817 | case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN: |
||
818 | $ss = 0x01; |
||
819 | |||
820 | break; |
||
821 | case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN: |
||
822 | $ss = 0x02; |
||
823 | |||
824 | break; |
||
825 | default: |
||
826 | $ss = 0x00; |
||
827 | |||
828 | break; |
||
829 | } |
||
830 | |||
831 | // sheet type |
||
832 | 43 | $st = 0x00; |
|
833 | |||
834 | 43 | $grbit = 0x0000; // Visibility and sheet type |
|
835 | |||
836 | 43 | $data = pack('VCC', $offset, $ss, $st); |
|
837 | 43 | $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname); |
|
838 | |||
839 | 43 | $length = strlen($data); |
|
840 | 43 | $header = pack('vv', $record, $length); |
|
841 | 43 | $this->append($header . $data); |
|
842 | 43 | } |
|
843 | |||
844 | /** |
||
845 | * Write Internal SUPBOOK record. |
||
846 | */ |
||
847 | 43 | private function writeSupbookInternal() |
|
848 | { |
||
849 | 43 | $record = 0x01AE; // Record identifier |
|
850 | 43 | $length = 0x0004; // Bytes to follow |
|
851 | |||
852 | 43 | $header = pack('vv', $record, $length); |
|
853 | 43 | $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401); |
|
854 | |||
855 | 43 | return $this->writeData($header . $data); |
|
856 | } |
||
857 | |||
858 | /** |
||
859 | * Writes the Excel BIFF EXTERNSHEET record. These references are used by |
||
860 | * formulas. |
||
861 | */ |
||
862 | 43 | private function writeExternalsheetBiff8() |
|
863 | { |
||
864 | 43 | $totalReferences = count($this->parser->references); |
|
865 | 43 | $record = 0x0017; // Record identifier |
|
866 | 43 | $length = 2 + 6 * $totalReferences; // Number of bytes to follow |
|
867 | |||
868 | 43 | $supbook_index = 0; // FIXME: only using internal SUPBOOK record |
|
869 | 43 | $header = pack('vv', $record, $length); |
|
870 | 43 | $data = pack('v', $totalReferences); |
|
871 | 43 | for ($i = 0; $i < $totalReferences; ++$i) { |
|
872 | 43 | $data .= $this->parser->references[$i]; |
|
873 | } |
||
874 | |||
875 | 43 | return $this->writeData($header . $data); |
|
876 | } |
||
877 | |||
878 | /** |
||
879 | * Write Excel BIFF STYLE records. |
||
880 | */ |
||
881 | 43 | private function writeStyle() |
|
882 | { |
||
883 | 43 | $record = 0x0293; // Record identifier |
|
884 | 43 | $length = 0x0004; // Bytes to follow |
|
885 | |||
886 | 43 | $ixfe = 0x8000; // Index to cell style XF |
|
887 | 43 | $BuiltIn = 0x00; // Built-in style |
|
888 | 43 | $iLevel = 0xff; // Outline style level |
|
889 | |||
890 | 43 | $header = pack('vv', $record, $length); |
|
891 | 43 | $data = pack('vCC', $ixfe, $BuiltIn, $iLevel); |
|
892 | 43 | $this->append($header . $data); |
|
893 | 43 | } |
|
894 | |||
895 | /** |
||
896 | * Writes Excel FORMAT record for non "built-in" numerical formats. |
||
897 | * |
||
898 | * @param string $format Custom format string |
||
899 | * @param int $ifmt Format index code |
||
900 | */ |
||
901 | 14 | private function writeNumberFormat($format, $ifmt) |
|
902 | { |
||
903 | 14 | $record = 0x041E; // Record identifier |
|
904 | |||
905 | 14 | $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format); |
|
906 | 14 | $length = 2 + strlen($numberFormatString); // Number of bytes to follow |
|
907 | |||
908 | 14 | $header = pack('vv', $record, $length); |
|
909 | 14 | $data = pack('v', $ifmt) . $numberFormatString; |
|
910 | 14 | $this->append($header . $data); |
|
911 | 14 | } |
|
912 | |||
913 | /** |
||
914 | * Write DATEMODE record to indicate the date system in use (1904 or 1900). |
||
915 | */ |
||
916 | 43 | private function writeDateMode() |
|
917 | { |
||
918 | 43 | $record = 0x0022; // Record identifier |
|
919 | 43 | $length = 0x0002; // Bytes to follow |
|
920 | |||
921 | 43 | $f1904 = (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904) |
|
922 | ? 1 |
||
923 | 43 | : 0; // Flag for 1904 date system |
|
924 | |||
925 | 43 | $header = pack('vv', $record, $length); |
|
926 | 43 | $data = pack('v', $f1904); |
|
927 | 43 | $this->append($header . $data); |
|
928 | 43 | } |
|
929 | |||
930 | /** |
||
931 | * Stores the COUNTRY record for localization. |
||
932 | * |
||
933 | * @return string |
||
934 | */ |
||
935 | private function writeCountry() |
||
936 | { |
||
937 | $record = 0x008C; // Record identifier |
||
938 | $length = 4; // Number of bytes to follow |
||
939 | |||
940 | $header = pack('vv', $record, $length); |
||
941 | // using the same country code always for simplicity |
||
942 | $data = pack('vv', $this->countryCode, $this->countryCode); |
||
943 | |||
944 | return $this->writeData($header . $data); |
||
945 | } |
||
946 | |||
947 | /** |
||
948 | * Write the RECALCID record. |
||
949 | * |
||
950 | * @return string |
||
951 | */ |
||
952 | 43 | private function writeRecalcId() |
|
953 | { |
||
954 | 43 | $record = 0x01C1; // Record identifier |
|
955 | 43 | $length = 8; // Number of bytes to follow |
|
956 | |||
957 | 43 | $header = pack('vv', $record, $length); |
|
958 | |||
959 | // by inspection of real Excel files, MS Office Excel 2007 writes this |
||
960 | 43 | $data = pack('VV', 0x000001C1, 0x00001E667); |
|
961 | |||
962 | 43 | return $this->writeData($header . $data); |
|
963 | } |
||
964 | |||
965 | /** |
||
966 | * Stores the PALETTE biff record. |
||
967 | */ |
||
968 | 43 | private function writePalette() |
|
969 | { |
||
970 | 43 | $aref = $this->palette; |
|
971 | |||
972 | 43 | $record = 0x0092; // Record identifier |
|
973 | 43 | $length = 2 + 4 * count($aref); // Number of bytes to follow |
|
974 | 43 | $ccv = count($aref); // Number of RGB values to follow |
|
975 | 43 | $data = ''; // The RGB data |
|
976 | |||
977 | // Pack the RGB data |
||
978 | 43 | foreach ($aref as $color) { |
|
979 | 43 | foreach ($color as $byte) { |
|
980 | 43 | $data .= pack('C', $byte); |
|
981 | } |
||
982 | } |
||
983 | |||
984 | 43 | $header = pack('vvv', $record, $length, $ccv); |
|
985 | 43 | $this->append($header . $data); |
|
986 | 43 | } |
|
987 | |||
988 | /** |
||
989 | * Handling of the SST continue blocks is complicated by the need to include an |
||
990 | * additional continuation byte depending on whether the string is split between |
||
991 | * blocks or whether it starts at the beginning of the block. (There are also |
||
992 | * additional complications that will arise later when/if Rich Strings are |
||
993 | * supported). |
||
994 | * |
||
995 | * The Excel documentation says that the SST record should be followed by an |
||
996 | * EXTSST record. The EXTSST record is a hash table that is used to optimise |
||
997 | * access to SST. However, despite the documentation it doesn't seem to be |
||
998 | * required so we will ignore it. |
||
999 | * |
||
1000 | * @return string Binary data |
||
1001 | */ |
||
1002 | 43 | private function writeSharedStringsTable() |
|
1003 | { |
||
1004 | // maximum size of record data (excluding record header) |
||
1005 | 43 | $continue_limit = 8224; |
|
1006 | |||
1007 | // initialize array of record data blocks |
||
1008 | 43 | $recordDatas = []; |
|
1009 | |||
1010 | // start SST record data block with total number of strings, total number of unique strings |
||
1011 | 43 | $recordData = pack('VV', $this->stringTotal, $this->stringUnique); |
|
1012 | |||
1013 | // loop through all (unique) strings in shared strings table |
||
1014 | 43 | foreach (array_keys($this->stringTable) as $string) { |
|
1015 | // here $string is a BIFF8 encoded string |
||
1016 | |||
1017 | // length = character count |
||
1018 | 36 | $headerinfo = unpack('vlength/Cencoding', $string); |
|
1019 | |||
1020 | // currently, this is always 1 = uncompressed |
||
1021 | 36 | $encoding = $headerinfo['encoding']; |
|
1022 | |||
1023 | // initialize finished writing current $string |
||
1024 | 36 | $finished = false; |
|
1025 | |||
1026 | 36 | while ($finished === false) { |
|
1027 | // normally, there will be only one cycle, but if string cannot immediately be written as is |
||
1028 | // there will be need for more than one cylcle, if string longer than one record data block, there |
||
1029 | // may be need for even more cycles |
||
1030 | |||
1031 | 36 | if (strlen($recordData) + strlen($string) <= $continue_limit) { |
|
1032 | // then we can write the string (or remainder of string) without any problems |
||
1033 | 36 | $recordData .= $string; |
|
1034 | |||
1035 | 36 | if (strlen($recordData) + strlen($string) == $continue_limit) { |
|
1036 | // we close the record data block, and initialize a new one |
||
1037 | $recordDatas[] = $recordData; |
||
1038 | $recordData = ''; |
||
1039 | } |
||
1040 | |||
1041 | // we are finished writing this string |
||
1042 | 36 | $finished = true; |
|
1043 | } else { |
||
1044 | // special treatment writing the string (or remainder of the string) |
||
1045 | // If the string is very long it may need to be written in more than one CONTINUE record. |
||
1046 | |||
1047 | // check how many bytes more there is room for in the current record |
||
1048 | $space_remaining = $continue_limit - strlen($recordData); |
||
1049 | |||
1050 | // minimum space needed |
||
1051 | // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character |
||
1052 | // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character |
||
1053 | $min_space_needed = ($encoding == 1) ? 5 : 4; |
||
1054 | |||
1055 | // We have two cases |
||
1056 | // 1. space remaining is less than minimum space needed |
||
1057 | // here we must waste the space remaining and move to next record data block |
||
1058 | // 2. space remaining is greater than or equal to minimum space needed |
||
1059 | // here we write as much as we can in the current block, then move to next record data block |
||
1060 | |||
1061 | // 1. space remaining is less than minimum space needed |
||
1062 | if ($space_remaining < $min_space_needed) { |
||
1063 | // we close the block, store the block data |
||
1064 | $recordDatas[] = $recordData; |
||
1065 | |||
1066 | // and start new record data block where we start writing the string |
||
1067 | $recordData = ''; |
||
1068 | |||
1069 | // 2. space remaining is greater than or equal to minimum space needed |
||
1070 | } else { |
||
1071 | // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below |
||
1072 | $effective_space_remaining = $space_remaining; |
||
1073 | |||
1074 | // for uncompressed strings, sometimes effective space remaining is reduced by 1 |
||
1075 | if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) { |
||
1076 | --$effective_space_remaining; |
||
1077 | } |
||
1078 | |||
1079 | // one block fininshed, store the block data |
||
1080 | $recordData .= substr($string, 0, $effective_space_remaining); |
||
1081 | |||
1082 | $string = substr($string, $effective_space_remaining); // for next cycle in while loop |
||
1083 | $recordDatas[] = $recordData; |
||
1084 | |||
1085 | // start new record data block with the repeated option flags |
||
1086 | $recordData = pack('C', $encoding); |
||
1087 | } |
||
1088 | } |
||
1089 | } |
||
1090 | } |
||
1091 | |||
1092 | // Store the last record data block unless it is empty |
||
1093 | // if there was no need for any continue records, this will be the for SST record data block itself |
||
1094 | 43 | if (strlen($recordData) > 0) { |
|
1095 | 43 | $recordDatas[] = $recordData; |
|
1096 | } |
||
1097 | |||
1098 | // combine into one chunk with all the blocks SST, CONTINUE,... |
||
1099 | 43 | $chunk = ''; |
|
1100 | 43 | foreach ($recordDatas as $i => $recordData) { |
|
1101 | // first block should have the SST record header, remaing should have CONTINUE header |
||
1102 | 43 | $record = ($i == 0) ? 0x00FC : 0x003C; |
|
1103 | |||
1104 | 43 | $header = pack('vv', $record, strlen($recordData)); |
|
1105 | 43 | $data = $header . $recordData; |
|
1106 | |||
1107 | 43 | $chunk .= $this->writeData($data); |
|
1108 | } |
||
1109 | |||
1110 | 43 | return $chunk; |
|
1111 | } |
||
1112 | |||
1113 | /** |
||
1114 | * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records. |
||
1115 | */ |
||
1116 | 43 | private function writeMsoDrawingGroup() |
|
1117 | { |
||
1118 | // write the Escher stream if necessary |
||
1119 | 43 | if (isset($this->escher)) { |
|
1120 | 10 | $writer = new Escher($this->escher); |
|
1121 | 10 | $data = $writer->close(); |
|
1122 | |||
1123 | 10 | $record = 0x00EB; |
|
1124 | 10 | $length = strlen($data); |
|
1125 | 10 | $header = pack('vv', $record, $length); |
|
1126 | |||
1127 | 10 | return $this->writeData($header . $data); |
|
1128 | } |
||
1129 | |||
1130 | 33 | return ''; |
|
1131 | } |
||
1132 | |||
1133 | /** |
||
1134 | * Get Escher object. |
||
1135 | * |
||
1136 | * @return \PhpOffice\PhpSpreadsheet\Shared\Escher |
||
1137 | */ |
||
1138 | public function getEscher() |
||
1141 | } |
||
1142 | |||
1143 | /** |
||
1144 | * Set Escher object. |
||
1145 | * |
||
1146 | * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue |
||
1147 | */ |
||
1148 | 10 | public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null) |
|
1151 | 10 | } |
|
1152 | } |
||
1153 |