Passed
Pull Request — master (#4264)
by Owen
18:41 queued 08:04
created

Content::writeCells()   D

Complexity

Conditions 19
Paths 69

Size

Total Lines 101
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 67
CRAP Score 19.0011

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 101
ccs 67
cts 68
cp 0.9853
rs 4.5166
c 0
b 0
f 0
cc 19
nc 69
nop 2
crap 19.0011

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
7
use PhpOffice\PhpSpreadsheet\Cell\Cell;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Cell\DataType;
10
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
11
use PhpOffice\PhpSpreadsheet\Spreadsheet;
12
use PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator;
13
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
14
use PhpOffice\PhpSpreadsheet\Writer\Ods;
15
use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment;
16
use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Style;
17
18
/**
19
 * @author     Alexander Pervakov <[email protected]>
20
 */
21
class Content extends WriterPart
22
{
23
    private Formula $formulaConvertor;
24
25
    /**
26
     * Set parent Ods writer.
27
     */
28 39
    public function __construct(Ods $writer)
29
    {
30 39
        parent::__construct($writer);
31
32 39
        $this->formulaConvertor = new Formula($this->getParentWriter()->getSpreadsheet()->getDefinedNames());
33
    }
34
35
    /**
36
     * Write content.xml to XML format.
37
     *
38
     * @return string XML Output
39
     */
40 38
    public function write(): string
41
    {
42 38
        $objWriter = null;
43 38
        if ($this->getParentWriter()->getUseDiskCaching()) {
44
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
45
        } else {
46 38
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
47
        }
48
49
        // XML header
50 38
        $objWriter->startDocument('1.0', 'UTF-8');
51
52
        // Content
53 38
        $objWriter->startElement('office:document-content');
54 38
        $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
55 38
        $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
56 38
        $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
57 38
        $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
58 38
        $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
59 38
        $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
60 38
        $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
61 38
        $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
62 38
        $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
63 38
        $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
64 38
        $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
65 38
        $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
66 38
        $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
67 38
        $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
68 38
        $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
69 38
        $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
70 38
        $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
71 38
        $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
72 38
        $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
73 38
        $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
74 38
        $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
75 38
        $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
76 38
        $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
77 38
        $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
78 38
        $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
79 38
        $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
80 38
        $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
81 38
        $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
82 38
        $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
83 38
        $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
84 38
        $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
85 38
        $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
86 38
        $objWriter->writeAttribute('office:version', '1.2');
87
88 38
        $objWriter->writeElement('office:scripts');
89 38
        $objWriter->writeElement('office:font-face-decls');
90
91
        // Styles XF
92 38
        $objWriter->startElement('office:automatic-styles');
93 38
        $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
94 38
        $objWriter->endElement();
95
96 38
        $objWriter->startElement('office:body');
97 38
        $objWriter->startElement('office:spreadsheet');
98 38
        $objWriter->writeElement('table:calculation-settings');
99
100 38
        $this->writeSheets($objWriter);
101
102 38
        (new AutoFilters($objWriter, $this->getParentWriter()->getSpreadsheet()))->write();
103
        // Defined names (ranges and formulae)
104 38
        (new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write();
105
106 38
        $objWriter->endElement();
107 38
        $objWriter->endElement();
108 38
        $objWriter->endElement();
109
110 38
        return $objWriter->getData();
111
    }
112
113
    /**
114
     * Write sheets.
115
     */
116 38
    private function writeSheets(XMLWriter $objWriter): void
117
    {
118 38
        $spreadsheet = $this->getParentWriter()->getSpreadsheet();
119 38
        $sheetCount = $spreadsheet->getSheetCount();
120 38
        for ($sheetIndex = 0; $sheetIndex < $sheetCount; ++$sheetIndex) {
121 38
            $spreadsheet->getSheet($sheetIndex)->calculateArrays($this->getParentWriter()->getPreCalculateFormulas());
122 38
            $objWriter->startElement('table:table');
123 38
            $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle());
124 38
            $objWriter->writeAttribute('table:style-name', Style::TABLE_STYLE_PREFIX . (string) ($sheetIndex + 1));
125 38
            $objWriter->writeElement('office:forms');
126 38
            $lastColumn = 0;
127 38
            foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
128 6
                $thisColumn = $columnDimension->getColumnNumeric();
129 6
                $emptyColumns = $thisColumn - $lastColumn - 1;
130 6
                if ($emptyColumns > 0) {
131 4
                    $objWriter->startElement('table:table-column');
132 4
                    $objWriter->writeAttribute('table:number-columns-repeated', (string) $emptyColumns);
133 4
                    $objWriter->endElement();
134
                }
135 6
                $lastColumn = $thisColumn;
136 6
                $objWriter->startElement('table:table-column');
137 6
                $objWriter->writeAttribute(
138 6
                    'table:style-name',
139 6
                    sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric())
140 6
                );
141 6
                $objWriter->writeAttribute('table:default-cell-style-name', 'ce0');
142 6
                $objWriter->endElement();
143
            }
144 38
            $this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex);
145 38
            $objWriter->endElement();
146
        }
147
    }
148
149
    /**
150
     * Write rows of the specified sheet.
151
     */
152 38
    private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void
153
    {
154 38
        $spanRow = 0;
155 38
        $rows = $sheet->getRowIterator();
156 38
        foreach ($rows as $row) {
157 38
            $cellIterator = $row->getCellIterator(iterateOnlyExistingCells: true);
158 38
            $cellIterator->rewind();
159 38
            $rowStyleExists = $sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0;
160 38
            if ($cellIterator->valid() || $rowStyleExists) {
161 36
                if ($spanRow) {
162 6
                    $objWriter->startElement('table:table-row');
163 6
                    $objWriter->writeAttribute(
164 6
                        'table:number-rows-repeated',
165 6
                        (string) $spanRow
166 6
                    );
167 6
                    $objWriter->endElement();
168 6
                    $spanRow = 0;
169
                }
170 36
                $objWriter->startElement('table:table-row');
171 36
                if ($rowStyleExists) {
172 1
                    $objWriter->writeAttribute(
173 1
                        'table:style-name',
174 1
                        sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
175 1
                    );
176
                }
177 36
                $this->writeCells($objWriter, $cellIterator);
178 36
                $objWriter->endElement();
179
            } else {
180 8
                ++$spanRow;
181
            }
182
        }
183
    }
184
185
    /**
186
     * Write cells of the specified row.
187
     */
188 36
    private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void
189
    {
190 36
        $prevColumn = -1;
191 36
        foreach ($cells as $cell) {
192
            /** @var Cell $cell */
193 36
            $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
194 36
            $attributes = $cell->getFormulaAttributes() ?? [];
195
196 36
            $this->writeCellSpan($objWriter, $column, $prevColumn);
197 36
            $objWriter->startElement('table:table-cell');
198 36
            $this->writeCellMerge($objWriter, $cell);
199
200
            // Style XF
201 36
            $style = $cell->getXfIndex();
202 36
            if ($style !== null) {
203 36
                $objWriter->writeAttribute('table:style-name', Style::CELL_STYLE_PREFIX . $style);
204
            }
205
206 36
            switch ($cell->getDataType()) {
207
                case DataType::TYPE_BOOL:
208 3
                    $objWriter->writeAttribute('office:value-type', 'boolean');
209 3
                    $objWriter->writeAttribute('office:boolean-value', $cell->getValue() ? 'true' : 'false');
210 3
                    $objWriter->writeElement('text:p', Calculation::getInstance()->getLocaleBoolean($cell->getValue() ? 'TRUE' : 'FALSE'));
211
212 3
                    break;
213
                case DataType::TYPE_ERROR:
214 1
                    $objWriter->writeAttribute('table:formula', 'of:=#NULL!');
215 1
                    $objWriter->writeAttribute('office:value-type', 'string');
216 1
                    $objWriter->writeAttribute('office:string-value', '');
217 1
                    $objWriter->writeElement('text:p', '#NULL!');
218
219 1
                    break;
220
                case DataType::TYPE_FORMULA:
221 9
                    $formulaValue = $cell->getValueString();
222 9
                    if ($this->getParentWriter()->getPreCalculateFormulas()) {
223
                        try {
224 8
                            $formulaValue = $cell->getCalculatedValueString();
225
                        } catch (CalculationException $e) {
226
                            // don't do anything
227
                        }
228
                    }
229 9
                    if (isset($attributes['ref'])) {
230 3
                        if (preg_match('/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/', (string) $attributes['ref'], $matches) == 1) {
231 3
                            $matrixRowSpan = 1;
232 3
                            $matrixColSpan = 1;
233 3
                            if (isset($matches[3])) {
234 3
                                $minRow = (int) $matches[2];
235
                                // https://github.com/phpstan/phpstan/issues/11602
236 3
                                $maxRow = (int) $matches[5]; // @phpstan-ignore-line
237 3
                                $matrixRowSpan = $maxRow - $minRow + 1;
238 3
                                $minCol = Coordinate::columnIndexFromString($matches[1]);
239 3
                                $maxCol = Coordinate::columnIndexFromString($matches[4]); // @phpstan-ignore-line
240 3
                                $matrixColSpan = $maxCol - $minCol + 1;
241
                            }
242 3
                            $objWriter->writeAttribute('table:number-matrix-columns-spanned', "$matrixColSpan");
243 3
                            $objWriter->writeAttribute('table:number-matrix-rows-spanned', "$matrixRowSpan");
244
                        }
245
                    }
246 9
                    $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValueString()));
247 9
                    if (is_numeric($formulaValue)) {
248 6
                        $objWriter->writeAttribute('office:value-type', 'float');
249
                    } else {
250 3
                        $objWriter->writeAttribute('office:value-type', 'string');
251
                    }
252 9
                    $objWriter->writeAttribute('office:value', $formulaValue);
253 9
                    $objWriter->writeElement('text:p', $formulaValue);
254
255 9
                    break;
256
                case DataType::TYPE_NUMERIC:
257 17
                    $objWriter->writeAttribute('office:value-type', 'float');
258 17
                    $objWriter->writeAttribute('office:value', $cell->getValueString());
259 17
                    $objWriter->writeElement('text:p', $cell->getValueString());
260
261 17
                    break;
262
                case DataType::TYPE_INLINE:
263
                    // break intentionally omitted
264
                case DataType::TYPE_STRING:
265 21
                    $objWriter->writeAttribute('office:value-type', 'string');
266 21
                    $url = $cell->getHyperlink()->getUrl();
267 21
                    if (empty($url)) {
268 21
                        $objWriter->writeElement('text:p', $cell->getValueString());
269
                    } else {
270 1
                        $objWriter->startElement('text:p');
271 1
                        $objWriter->startElement('text:a');
272 1
                        $sheets = 'sheet://';
273 1
                        $lensheets = strlen($sheets);
274 1
                        if (substr($url, 0, $lensheets) === $sheets) {
275 1
                            $url = '#' . substr($url, $lensheets);
276
                        }
277 1
                        $objWriter->writeAttribute('xlink:href', $url);
278 1
                        $objWriter->writeAttribute('xlink:type', 'simple');
279 1
                        $objWriter->text($cell->getValueString());
280 1
                        $objWriter->endElement(); // text:a
281 1
                        $objWriter->endElement(); // text:p
282
                    }
283
284 21
                    break;
285
            }
286 36
            Comment::write($objWriter, $cell);
287 36
            $objWriter->endElement();
288 36
            $prevColumn = $column;
289
        }
290
    }
291
292
    /**
293
     * Write span.
294
     */
295 36
    private function writeCellSpan(XMLWriter $objWriter, int $curColumn, int $prevColumn): void
296
    {
297 36
        $diff = $curColumn - $prevColumn - 1;
298 36
        if (1 === $diff) {
299 5
            $objWriter->writeElement('table:table-cell');
300 35
        } elseif ($diff > 1) {
301 4
            $objWriter->startElement('table:table-cell');
302 4
            $objWriter->writeAttribute('table:number-columns-repeated', (string) $diff);
303 4
            $objWriter->endElement();
304
        }
305
    }
306
307
    /**
308
     * Write XF cell styles.
309
     */
310 38
    private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
311
    {
312 38
        $styleWriter = new Style($writer);
313
314 38
        $sheetCount = $spreadsheet->getSheetCount();
315 38
        for ($i = 0; $i < $sheetCount; ++$i) {
316 38
            $worksheet = $spreadsheet->getSheet($i);
317 38
            $styleWriter->writeTableStyle($worksheet, $i + 1);
318
319 38
            $worksheet->calculateColumnWidths();
320 38
            foreach ($worksheet->getColumnDimensions() as $columnDimension) {
321 6
                if ($columnDimension->getWidth() !== -1.0) {
322 6
                    $styleWriter->writeColumnStyles($columnDimension, $i);
323
                }
324
            }
325
        }
326 38
        for ($i = 0; $i < $sheetCount; ++$i) {
327 38
            $worksheet = $spreadsheet->getSheet($i);
328 38
            foreach ($worksheet->getRowDimensions() as $rowDimension) {
329 3
                if ($rowDimension->getRowHeight() > 0.0) {
330 1
                    $styleWriter->writeRowStyles($rowDimension, $i);
331
                }
332
            }
333
        }
334
335 38
        foreach ($spreadsheet->getCellXfCollection() as $style) {
336 38
            $styleWriter->write($style);
337
        }
338
    }
339
340
    /**
341
     * Write attributes for merged cell.
342
     */
343 36
    private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void
344
    {
345 36
        if (!$cell->isMergeRangeValueCell()) {
346 35
            return;
347
        }
348
349 3
        $mergeRange = Coordinate::splitRange((string) $cell->getMergeRange());
350 3
        [$startCell, $endCell] = $mergeRange[0];
351 3
        $start = Coordinate::coordinateFromString($startCell);
352 3
        $end = Coordinate::coordinateFromString($endCell);
353 3
        $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
354 3
        $rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1;
355
356 3
        $objWriter->writeAttribute('table:number-columns-spanned', (string) $columnSpan);
357 3
        $objWriter->writeAttribute('table:number-rows-spanned', (string) $rowSpan);
358
    }
359
}
360