Completed
Push — develop ( 782b4e...557e80 )
by Adrien
43:38
created

Workbook::writeSheet()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 9
CRAP Score 4.0961

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 5
dl 16
loc 16
ccs 9
cts 11
cp 0.8182
crap 4.0961
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Cell;
6
use PhpOffice\PhpSpreadsheet\NamedRange;
7
use PhpOffice\PhpSpreadsheet\Shared\Date;
8
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
9
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
12
13
class Workbook extends WriterPart
14
{
15
    /**
16
     * Write workbook to XML format.
17
     *
18
     * @param Spreadsheet $spreadsheet
19
     * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
20
     *
21
     * @throws WriterException
22
     *
23
     * @return string XML Output
24
     */
25 56
    public function writeWorkbook(Spreadsheet $spreadsheet, $recalcRequired = false)
26
    {
27
        // Create XML writer
28 56 View Code Duplication
        if ($this->getParentWriter()->getUseDiskCaching()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
29
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
30
        } else {
31 56
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
32
        }
33
34
        // XML header
35 56
        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
36
37
        // workbook
38 56
        $objWriter->startElement('workbook');
39 56
        $objWriter->writeAttribute('xml:space', 'preserve');
40 56
        $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
41 56
        $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
42
43
        // fileVersion
44 56
        $this->writeFileVersion($objWriter);
45
46
        // workbookPr
47 56
        $this->writeWorkbookPr($objWriter);
48
49
        // workbookProtection
50 56
        $this->writeWorkbookProtection($objWriter, $spreadsheet);
51
52
        // bookViews
53 56
        if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
54 56
            $this->writeBookViews($objWriter, $spreadsheet);
55
        }
56
57
        // sheets
58 56
        $this->writeSheets($objWriter, $spreadsheet);
59
60
        // definedNames
61 56
        $this->writeDefinedNames($objWriter, $spreadsheet);
62
63
        // calcPr
64 56
        $this->writeCalcPr($objWriter, $recalcRequired);
65
66 56
        $objWriter->endElement();
67
68
        // Return
69 56
        return $objWriter->getData();
70
    }
71
72
    /**
73
     * Write file version.
74
     *
75
     * @param XMLWriter $objWriter XML Writer
76
     *
77
     * @throws WriterException
78
     */
79 56
    private function writeFileVersion(XMLWriter $objWriter)
80
    {
81 56
        $objWriter->startElement('fileVersion');
82 56
        $objWriter->writeAttribute('appName', 'xl');
83 56
        $objWriter->writeAttribute('lastEdited', '4');
84 56
        $objWriter->writeAttribute('lowestEdited', '4');
85 56
        $objWriter->writeAttribute('rupBuild', '4505');
86 56
        $objWriter->endElement();
87 56
    }
88
89
    /**
90
     * Write WorkbookPr.
91
     *
92
     * @param XMLWriter $objWriter XML Writer
93
     *
94
     * @throws WriterException
95
     */
96 56
    private function writeWorkbookPr(XMLWriter $objWriter)
97
    {
98 56
        $objWriter->startElement('workbookPr');
99
100 56
        if (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904) {
101
            $objWriter->writeAttribute('date1904', '1');
102
        }
103
104 56
        $objWriter->writeAttribute('codeName', 'ThisWorkbook');
105
106 56
        $objWriter->endElement();
107 56
    }
108
109
    /**
110
     * Write BookViews.
111
     *
112
     * @param XMLWriter $objWriter XML Writer
113
     * @param Spreadsheet $spreadsheet
114
     *
115
     * @throws WriterException
116
     */
117 56
    private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet)
118
    {
119
        // bookViews
120 56
        $objWriter->startElement('bookViews');
121
122
        // workbookView
123 56
        $objWriter->startElement('workbookView');
124
125 56
        $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
126 56
        $objWriter->writeAttribute('autoFilterDateGrouping', '1');
127 56
        $objWriter->writeAttribute('firstSheet', '0');
128 56
        $objWriter->writeAttribute('minimized', '0');
129 56
        $objWriter->writeAttribute('showHorizontalScroll', '1');
130 56
        $objWriter->writeAttribute('showSheetTabs', '1');
131 56
        $objWriter->writeAttribute('showVerticalScroll', '1');
132 56
        $objWriter->writeAttribute('tabRatio', '600');
133 56
        $objWriter->writeAttribute('visibility', 'visible');
134
135 56
        $objWriter->endElement();
136
137 56
        $objWriter->endElement();
138 56
    }
139
140
    /**
141
     * Write WorkbookProtection.
142
     *
143
     * @param XMLWriter $objWriter XML Writer
144
     * @param Spreadsheet $spreadsheet
145
     *
146
     * @throws WriterException
147
     */
148 56
    private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet)
149
    {
150 56
        if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
151 2
            $objWriter->startElement('workbookProtection');
152 2
            $objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
153 2
            $objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
154 2
            $objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));
155
156 2
            if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
157
                $objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
158
            }
159
160 2
            if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
161 2
                $objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
162
            }
163
164 2
            $objWriter->endElement();
165
        }
166 56
    }
167
168
    /**
169
     * Write calcPr.
170
     *
171
     * @param XMLWriter $objWriter XML Writer
172
     * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
173
     *
174
     * @throws WriterException
175
     */
176 56
    private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true)
177
    {
178 56
        $objWriter->startElement('calcPr');
179
180
        //    Set the calcid to a higher value than Excel itself will use, otherwise Excel will always recalc
181
        //  If MS Excel does do a recalc, then users opening a file in MS Excel will be prompted to save on exit
182
        //     because the file has changed
183 56
        $objWriter->writeAttribute('calcId', '999999');
184 56
        $objWriter->writeAttribute('calcMode', 'auto');
185
        //    fullCalcOnLoad isn't needed if we've recalculating for the save
186 56
        $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0);
187 56
        $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1);
188
189 56
        $objWriter->endElement();
190 56
    }
191
192
    /**
193
     * Write sheets.
194
     *
195
     * @param XMLWriter $objWriter XML Writer
196
     * @param Spreadsheet $spreadsheet
197
     *
198
     * @throws WriterException
199
     */
200 56
    private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet)
201
    {
202
        // Write sheets
203 56
        $objWriter->startElement('sheets');
204 56
        $sheetCount = $spreadsheet->getSheetCount();
205 56
        for ($i = 0; $i < $sheetCount; ++$i) {
206
            // sheet
207 56
            $this->writeSheet(
208 56
                $objWriter,
209 56
                $spreadsheet->getSheet($i)->getTitle(),
210 56
                ($i + 1),
211 56
                ($i + 1 + 3),
212 56
                $spreadsheet->getSheet($i)->getSheetState()
213
            );
214
        }
215
216 56
        $objWriter->endElement();
217 56
    }
218
219
    /**
220
     * Write sheet.
221
     *
222
     * @param XMLWriter $objWriter XML Writer
223
     * @param string $pSheetname Sheet name
224
     * @param int $pSheetId Sheet id
225
     * @param int $pRelId Relationship ID
226
     * @param string $sheetState Sheet state (visible, hidden, veryHidden)
227
     *
228
     * @throws WriterException
229
     */
230 56 View Code Duplication
    private function writeSheet(XMLWriter $objWriter, $pSheetname, $pSheetId = 1, $pRelId = 1, $sheetState = 'visible')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
    {
232 56
        if ($pSheetname != '') {
233
            // Write sheet
234 56
            $objWriter->startElement('sheet');
235 56
            $objWriter->writeAttribute('name', $pSheetname);
236 56
            $objWriter->writeAttribute('sheetId', $pSheetId);
237 56
            if ($sheetState != 'visible' && $sheetState != '') {
238
                $objWriter->writeAttribute('state', $sheetState);
239
            }
240 56
            $objWriter->writeAttribute('r:id', 'rId' . $pRelId);
241 56
            $objWriter->endElement();
242
        } else {
243
            throw new WriterException('Invalid parameters passed.');
244
        }
245 56
    }
246
247
    /**
248
     * Write Defined Names.
249
     *
250
     * @param XMLWriter $objWriter XML Writer
251
     * @param Spreadsheet $spreadsheet
252
     *
253
     * @throws WriterException
254
     */
255 56
    private function writeDefinedNames(XMLWriter $objWriter, Spreadsheet $spreadsheet)
256
    {
257
        // Write defined names
258 56
        $objWriter->startElement('definedNames');
259
260
        // Named ranges
261 56
        if (count($spreadsheet->getNamedRanges()) > 0) {
262
            // Named ranges
263 4
            $this->writeNamedRanges($objWriter, $spreadsheet);
264
        }
265
266
        // Other defined names
267 56
        $sheetCount = $spreadsheet->getSheetCount();
268 56
        for ($i = 0; $i < $sheetCount; ++$i) {
269
            // definedName for autoFilter
270 56
            $this->writeDefinedNameForAutofilter($objWriter, $spreadsheet->getSheet($i), $i);
271
272
            // definedName for Print_Titles
273 56
            $this->writeDefinedNameForPrintTitles($objWriter, $spreadsheet->getSheet($i), $i);
274
275
            // definedName for Print_Area
276 56
            $this->writeDefinedNameForPrintArea($objWriter, $spreadsheet->getSheet($i), $i);
277
        }
278
279 56
        $objWriter->endElement();
280 56
    }
281
282
    /**
283
     * Write named ranges.
284
     *
285
     * @param XMLWriter $objWriter XML Writer
286
     * @param Spreadsheet $spreadsheet
287
     *
288
     * @throws WriterException
289
     */
290 4
    private function writeNamedRanges(XMLWriter $objWriter, Spreadsheet $spreadsheet)
291
    {
292
        // Loop named ranges
293 4
        $namedRanges = $spreadsheet->getNamedRanges();
294 4
        foreach ($namedRanges as $namedRange) {
295 4
            $this->writeDefinedNameForNamedRange($objWriter, $namedRange);
296
        }
297 4
    }
298
299
    /**
300
     * Write Defined Name for named range.
301
     *
302
     * @param XMLWriter $objWriter XML Writer
303
     * @param NamedRange $pNamedRange
304
     *
305
     * @throws WriterException
306
     */
307 4
    private function writeDefinedNameForNamedRange(XMLWriter $objWriter, NamedRange $pNamedRange)
308
    {
309
        // definedName for named range
310 4
        $objWriter->startElement('definedName');
311 4
        $objWriter->writeAttribute('name', $pNamedRange->getName());
312 4
        if ($pNamedRange->getLocalOnly()) {
313
            $objWriter->writeAttribute('localSheetId', $pNamedRange->getScope()->getParent()->getIndex($pNamedRange->getScope()));
314
        }
315
316
        // Create absolute coordinate and write as raw text
317 4
        $range = Cell::splitRange($pNamedRange->getRange());
318 4 View Code Duplication
        for ($i = 0; $i < count($range); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
319 4
            $range[$i][0] = '\'' . str_replace("'", "''", $pNamedRange->getWorksheet()->getTitle()) . '\'!' . Cell::absoluteReference($range[$i][0]);
320 4
            if (isset($range[$i][1])) {
321 3
                $range[$i][1] = Cell::absoluteReference($range[$i][1]);
322
            }
323
        }
324 4
        $range = Cell::buildRange($range);
325
326 4
        $objWriter->writeRawData($range);
327
328 4
        $objWriter->endElement();
329 4
    }
330
331
    /**
332
     * Write Defined Name for autoFilter.
333
     *
334
     * @param XMLWriter $objWriter XML Writer
335
     * @param Worksheet $pSheet
336
     * @param int $pSheetId
337
     *
338
     * @throws WriterException
339
     */
340 56
    private function writeDefinedNameForAutofilter(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0)
341
    {
342
        // definedName for autoFilter
343 56
        $autoFilterRange = $pSheet->getAutoFilter()->getRange();
344 56
        if (!empty($autoFilterRange)) {
345 3
            $objWriter->startElement('definedName');
346 3
            $objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
347 3
            $objWriter->writeAttribute('localSheetId', $pSheetId);
348 3
            $objWriter->writeAttribute('hidden', '1');
349
350
            // Create absolute coordinate and write as raw text
351 3
            $range = Cell::splitRange($autoFilterRange);
352 3
            $range = $range[0];
353
            //    Strip any worksheet ref so we can make the cell ref absolute
354 3 View Code Duplication
            if (strpos($range[0], '!') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
                list($ws, $range[0]) = explode('!', $range[0]);
0 ignored issues
show
Unused Code introduced by
The assignment to $ws is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
356
            }
357
358 3
            $range[0] = Cell::absoluteCoordinate($range[0]);
359 3
            $range[1] = Cell::absoluteCoordinate($range[1]);
360 3
            $range = implode(':', $range);
361
362 3
            $objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range);
363
364 3
            $objWriter->endElement();
365
        }
366 56
    }
367
368
    /**
369
     * Write Defined Name for PrintTitles.
370
     *
371
     * @param XMLWriter $objWriter XML Writer
372
     * @param Worksheet $pSheet
373
     * @param int $pSheetId
374
     *
375
     * @throws WriterException
376
     */
377 56
    private function writeDefinedNameForPrintTitles(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0)
378
    {
379
        // definedName for PrintTitles
380 56
        if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
381 1
            $objWriter->startElement('definedName');
382 1
            $objWriter->writeAttribute('name', '_xlnm.Print_Titles');
383 1
            $objWriter->writeAttribute('localSheetId', $pSheetId);
384
385
            // Setting string
386 1
            $settingString = '';
387
388
            // Columns to repeat
389 1
            if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
390
                $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft();
391
392
                $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
393
            }
394
395
            // Rows to repeat
396 1
            if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
397 1
                if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
398
                    $settingString .= ',';
399
                }
400
401 1
                $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop();
402
403 1
                $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
404
            }
405
406 1
            $objWriter->writeRawData($settingString);
407
408 1
            $objWriter->endElement();
409
        }
410 56
    }
411
412
    /**
413
     * Write Defined Name for PrintTitles.
414
     *
415
     * @param XMLWriter $objWriter XML Writer
416
     * @param Worksheet $pSheet
417
     * @param int $pSheetId
418
     *
419
     * @throws WriterException
420
     */
421 56
    private function writeDefinedNameForPrintArea(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0)
422
    {
423
        // definedName for PrintArea
424 56
        if ($pSheet->getPageSetup()->isPrintAreaSet()) {
425
            $objWriter->startElement('definedName');
426
            $objWriter->writeAttribute('name', '_xlnm.Print_Area');
427
            $objWriter->writeAttribute('localSheetId', $pSheetId);
428
429
            // Print area
430
            $printArea = Cell::splitRange($pSheet->getPageSetup()->getPrintArea());
431
432
            $chunks = [];
433
            foreach ($printArea as $printAreaRect) {
434
                $printAreaRect[0] = Cell::absoluteReference($printAreaRect[0]);
435
                $printAreaRect[1] = Cell::absoluteReference($printAreaRect[1]);
436
                $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
437
            }
438
439
            $objWriter->writeRawData(implode(',', $chunks));
440
441
            $objWriter->endElement();
442
        }
443 56
    }
444
}
445