Failed Conditions
Pull Request — master (#715)
by
unknown
03:04
created

throwIfSheetFilePointerIsNotAvailable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.0625
1
<?php
2
3
namespace Box\Spout\Writer\ODS\Manager;
4
5
use Box\Spout\Common\Entity\Cell;
6
use Box\Spout\Common\Entity\Row;
7
use Box\Spout\Common\Entity\Style\Style;
8
use Box\Spout\Common\Exception\InvalidArgumentException;
9
use Box\Spout\Common\Exception\IOException;
10
use Box\Spout\Common\Helper\Escaper\ODS as ODSEscaper;
11
use Box\Spout\Common\Helper\StringHelper;
12
use Box\Spout\Writer\Common\Entity\Options;
13
use Box\Spout\Writer\Common\Entity\Worksheet;
14
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
15
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
16
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
17
18
/**
19
 * Class WorksheetManager
20
 * ODS worksheet manager, providing the interfaces to work with ODS worksheets.
21
 */
22
class WorksheetManager implements WorksheetManagerInterface
23
{
24
    /** @var \Box\Spout\Common\Helper\Escaper\ODS Strings escaper */
25
    private $stringsEscaper;
26
27
    /** @var StringHelper String helper */
28
    private $stringHelper;
29
30
    /** @var StyleManager Manages styles */
31
    private $styleManager;
32
33
    /** @var StyleMerger Helper to merge styles together */
34
    private $styleMerger;
35
36
    /**
37
     * WorksheetManager constructor.
38
     *
39
     * @param StyleManager $styleManager
40
     * @param StyleMerger $styleMerger
41
     * @param ODSEscaper $stringsEscaper
42
     * @param StringHelper $stringHelper
43
     * @param OptionsManager|null $optionsManager
44
     */
45 42
    public function __construct(
46
        StyleManager $styleManager,
47
        StyleMerger $styleMerger,
48
        ODSEscaper $stringsEscaper,
49
        StringHelper $stringHelper,
50
        $optionsManager = null
51
    ) {
52 42
        $this->styleManager = $styleManager;
53 42
        $this->styleMerger = $styleMerger;
54 42
        $this->stringsEscaper = $stringsEscaper;
55 42
        $this->stringHelper = $stringHelper;
56
57 42
        if ($optionsManager) {
58
            $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH));
59
            $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT));
60
            $this->columnWidths = $optionsManager->getOption(Options::COLUMN_WIDTHS) ?? [];
0 ignored issues
show
Bug introduced by
The property columnWidths does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
61
        }
62 42
    }
63
64
    /**
65
     * Prepares the worksheet to accept data
66
     *
67
     * @param Worksheet $worksheet The worksheet to start
68
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
69
     * @return void
70
     */
71 42
    public function startSheet(Worksheet $worksheet)
72
    {
73 42
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
74 42
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
75
76 42
        $worksheet->setFilePointer($sheetFilePointer);
77 42
    }
78
79
    /**
80
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
81
     *
82
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
83
     * @throws IOException If the sheet data file cannot be opened for writing
84
     * @return void
85
     */
86 42
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
87
    {
88 42
        if (!$sheetFilePointer) {
89
            throw new IOException('Unable to open sheet for writing.');
90
        }
91 42
    }
92
93
    /**
94
     * Returns the table XML root node as string.
95
     *
96
     * @param Worksheet $worksheet
97
     * @return string <table> node as string
98
     */
99 39
    public function getTableElementStartAsString(Worksheet $worksheet)
100
    {
101 39
        $externalSheet = $worksheet->getExternalSheet();
102 39
        $escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
103 39
        $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
104
105 39
        $tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
106 39
        $tableElement .= $this->styleManager->getStyledTableColumnXMLContent($worksheet->getMaxNumColumns());
107
108 39
        return $tableElement;
109
    }
110
111
    /**
112
     * Adds a row to the given worksheet.
113
     *
114
     * @param Worksheet $worksheet The worksheet to add the row to
115
     * @param Row $row The row to be added
116
     * @throws IOException If the data cannot be written
117
     * @throws InvalidArgumentException If a cell value's type is not supported
118
     * @return void
119
     */
120 37
    public function addRow(Worksheet $worksheet, Row $row)
121
    {
122 37
        $cells = $row->getCells();
123 37
        $rowStyle = $row->getStyle();
124
125 37
        $data = '<table:table-row table:style-name="ro1">';
126
127 37
        $currentCellIndex = 0;
128 37
        $nextCellIndex = 1;
129
130 37
        for ($i = 0; $i < $row->getNumCells(); $i++) {
131
            /** @var Cell $cell */
132 37
            $cell = $cells[$currentCellIndex];
133
            /** @var Cell|null $nextCell */
134 37
            $nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
135
136 37
            if ($nextCell === null || $cell->getValue() !== $nextCell->getValue()) {
137 37
                $data .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $currentCellIndex, $nextCellIndex);
138 36
                $currentCellIndex = $nextCellIndex;
139
            }
140
141 36
            $nextCellIndex++;
142
        }
143
144 36
        $data .= '</table:table-row>';
145
146 36
        $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data);
147 36
        if ($wasWriteSuccessful === false) {
148
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
149
        }
150
151
        // only update the count if the write worked
152 36
        $lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
153 36
        $worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
154 36
    }
155
156
    /**
157
     * Applies styles to the given style, merging the cell's style with its row's style
158
     * Then builds and returns xml for the cell.
159
     *
160
     * @param Cell $cell
161
     * @param Style $rowStyle
162
     * @param int $currentCellIndex
163
     * @param int $nextCellIndex
164
     * @throws InvalidArgumentException If a cell value's type is not supported
165
     * @return string
166
     */
167 37
    private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $currentCellIndex, $nextCellIndex)
168
    {
169
        // Apply row and extra styles
170 37
        $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
171 37
        $cell->setStyle($mergedCellAndRowStyle);
172 37
        $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
173
174 37
        $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
175 37
        $styleIndex = $registeredStyle->getId() + 1; // 1-based
176
177 37
        $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
178
179 37
        return $this->getCellXML($cell, $styleIndex, $numTimesValueRepeated);
180
    }
181
182
    /**
183
     * Returns the cell XML content, given its value.
184
     *
185
     * @param Cell $cell The cell to be written
186
     * @param int $styleIndex Index of the used style
187
     * @param int $numTimesValueRepeated Number of times the value is consecutively repeated
188
     * @throws InvalidArgumentException If a cell value's type is not supported
189
     * @return string The cell XML content
190
     */
191 37
    private function getCellXML(Cell $cell, $styleIndex, $numTimesValueRepeated)
192
    {
193 37
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
194
195 37
        if ($numTimesValueRepeated !== 1) {
196 4
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
197
        }
198
199 37
        if ($cell->isString()) {
200 32
            $data .= ' office:value-type="string" calcext:value-type="string">';
201
202 32
            $cellValueLines = \explode("\n", $cell->getValue());
203 32
            foreach ($cellValueLines as $cellValueLine) {
204 32
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
205
            }
206
207 32
            $data .= '</table:table-cell>';
208 7
        } elseif ($cell->isBoolean()) {
209 2
            $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
210 2
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
211 2
            $data .= '</table:table-cell>';
212 6
        } elseif ($cell->isNumeric()) {
213 2
            $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
214 2
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
215 2
            $data .= '</table:table-cell>';
216 5
        } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
217
            // only writes the error value if it's a string
218 1
            $data .= ' office:value-type="string" calcext:value-type="error" office:value="">';
219 1
            $data .= '<text:p>' . $cell->getValueEvenIfError() . '</text:p>';
220 1
            $data .= '</table:table-cell>';
221 4
        } elseif ($cell->isEmpty()) {
222 2
            $data .= '/>';
223
        } else {
224 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
225
        }
226
227 36
        return $data;
228
    }
229
230
    /**
231
     * Closes the worksheet
232
     *
233
     * @param Worksheet $worksheet
234
     * @return void
235
     */
236 39
    public function close(Worksheet $worksheet)
237
    {
238 39
        $worksheetFilePointer = $worksheet->getFilePointer();
239
240 39
        if (!\is_resource($worksheetFilePointer)) {
241
            return;
242
        }
243
244 39
        \fclose($worksheetFilePointer);
245 39
    }
246
247
    /**
248
     * @param float|null $width
249
     */
250
    public function setDefaultColumnWidth($width)
251
    {
252
        $this->styleManager->setDefaultColumnWidth($width);
253
    }
254
255
    /**
256
     * @param float|null $height
257
     */
258
    public function setDefaultRowHeight($height)
259
    {
260
        $this->styleManager->setDefaultRowHeight($height);
261
    }
262
263
    /**
264
     * @param float $width
265
     * @param array $columns One or more columns with this width
266
     */
267 3
    public function setColumnWidth(float $width, ...$columns)
268
    {
269 3
        $this->styleManager->setColumnWidth($width, ...$columns);
270 3
    }
271
272
    /**
273
     * @param float $width The width to set
274
     * @param int $start First column index of the range
275
     * @param int $end Last column index of the range
276
     */
277 1
    public function setColumnWidthForRange(float $width, int $start, int $end)
278
    {
279 1
        $this->styleManager->setColumnWidthForRange($width, $start, $end);
280 1
    }
281
}
282