Completed
Pull Request — master (#715)
by
unknown
14:17
created

WorksheetManager::setColumnWidth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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