Completed
Pull Request — master (#715)
by
unknown
01:52 queued 19s
created

WorksheetManager::setColumnWidth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
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 43
    public function __construct(
46
        StyleManager $styleManager,
47
        StyleMerger $styleMerger,
48
        ODSEscaper $stringsEscaper,
49
        StringHelper $stringHelper,
50
        $optionsManager = null
51
    ) {
52 43
        $this->styleManager = $styleManager;
53 43
        $this->styleMerger = $styleMerger;
54 43
        $this->stringsEscaper = $stringsEscaper;
55 43
        $this->stringHelper = $stringHelper;
56
57 43
        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 43
    }
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 43
    public function startSheet(Worksheet $worksheet)
72
    {
73 43
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
74 43
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
75
76 43
        $worksheet->setFilePointer($sheetFilePointer);
77 43
    }
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 43
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
87
    {
88 43
        if (!$sheetFilePointer) {
89
            throw new IOException('Unable to open sheet for writing.');
90
        }
91 43
    }
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 40
    public function getTableElementStartAsString(Worksheet $worksheet)
100
    {
101 40
        $externalSheet = $worksheet->getExternalSheet();
102 40
        $escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
103 40
        $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
104
105 40
        $tableElement  = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
106 40
        $tableElement .= $this->styleManager->getStyledTableColumnXMLContent($worksheet->getMaxNumColumns());
107
108 40
        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 38
    public function addRow(Worksheet $worksheet, Row $row)
121
    {
122 38
        $cells = $row->getCells();
123 38
        $rowStyle = $row->getStyle();
124
125 38
        $data = '<table:table-row table:style-name="ro1">';
126
127 38
        $currentCellIndex = 0;
128 38
        $nextCellIndex = 1;
129
130 38
        for ($i = 0; $i < $row->getNumCells(); $i++) {
131
            /** @var Cell $cell */
132 38
            $cell = $cells[$currentCellIndex];
133
            /** @var Cell|null $nextCell */
134 38
            $nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
135
136 38
            if ($nextCell === null || $cell->getValue() !== $nextCell->getValue()) {
137 38
                $data .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $currentCellIndex, $nextCellIndex);
138 37
                $currentCellIndex = $nextCellIndex;
139
            }
140
141 37
            $nextCellIndex++;
142
        }
143
144 37
        $data .= '</table:table-row>';
145
146 37
        $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data);
147 37
        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 37
        $lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
153 37
        $worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
154 37
    }
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 38
    private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $currentCellIndex, $nextCellIndex)
168
    {
169
        // Apply row and extra styles
170 38
        $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
171 38
        $cell->setStyle($mergedCellAndRowStyle);
172 38
        $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
173
174 38
        $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
175 38
        $styleIndex = $registeredStyle->getId() + 1; // 1-based
176
177 38
        $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
178
179 38
        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 38
    private function getCellXML(Cell $cell, $styleIndex, $numTimesValueRepeated)
192
    {
193 38
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
194
195 38
        if ($numTimesValueRepeated !== 1) {
196 4
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
197
        }
198
199 38
        if ($cell->isString()) {
200 33
            $data .= ' office:value-type="string" calcext:value-type="string">';
201
202 33
            $cellValueLines = \explode("\n", $cell->getValue());
203 33
            foreach ($cellValueLines as $cellValueLine) {
204 33
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
205
            }
206
207 33
            $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 37
        return $data;
228
    }
229
230
    /**
231
     * Closes the worksheet
232
     *
233
     * @param Worksheet $worksheet
234
     * @return void
235
     */
236 40
    public function close(Worksheet $worksheet)
237
    {
238 40
        $worksheetFilePointer = $worksheet->getFilePointer();
239
240 40
        if (!\is_resource($worksheetFilePointer)) {
241
            return;
242
        }
243
244 40
        \fclose($worksheetFilePointer);
245 40
    }
246
247
    /**
248
     * @param float|null $width
249
     */
250 1
    public function setDefaultColumnWidth($width)
251
    {
252 1
        $this->styleManager->setDefaultColumnWidth($width);
253 1
    }
254
255
    /**
256
     * @param float|null $height
257
     */
258 1
    public function setDefaultRowHeight($height)
259
    {
260 1
        $this->styleManager->setDefaultRowHeight($height);
261 1
    }
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