PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.0
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / vendor / openspout / openspout / src / Writer / ODS / Manager / WorksheetManager.php

WorksheetManager.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.0, at vendor/openspout/openspout/src/Writer/ODS/Manager/WorksheetManager.php

265 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\OpenSpout\Writer\ODS\Manager;
4
5 use FluentCart\OpenSpout\Common\Entity\Cell;
6 use FluentCart\OpenSpout\Common\Entity\Row;
7 use FluentCart\OpenSpout\Common\Entity\Style\Style;
8 use FluentCart\OpenSpout\Common\Exception\InvalidArgumentException;
9 use FluentCart\OpenSpout\Common\Exception\IOException;
10 use FluentCart\OpenSpout\Common\Helper\Escaper\ODS as ODSEscaper;
11 use FluentCart\OpenSpout\Common\Helper\StringHelper;
12 use FluentCart\OpenSpout\Writer\Common\Entity\Worksheet;
13 use FluentCart\OpenSpout\Writer\Common\Manager\RegisteredStyle;
14 use FluentCart\OpenSpout\Writer\Common\Manager\Style\StyleMerger;
15 use FluentCart\OpenSpout\Writer\Common\Manager\WorksheetManagerInterface;
16 use FluentCart\OpenSpout\Writer\ODS\Manager\Style\StyleManager;
17 /**
18 * ODS worksheet manager, providing the interfaces to work with ODS worksheets.
19 */
20 class WorksheetManager implements WorksheetManagerInterface
21 {
22 /** @var \OpenSpout\Common\Helper\Escaper\ODS Strings escaper */
23 private $stringsEscaper;
24 /** @var StringHelper String helper */
25 private $stringHelper;
26 /** @var StyleManager Manages styles */
27 private $styleManager;
28 /** @var StyleMerger Helper to merge styles together */
29 private $styleMerger;
30 /**
31 * WorksheetManager constructor.
32 */
33 public function __construct(StyleManager $styleManager, StyleMerger $styleMerger, ODSEscaper $stringsEscaper, StringHelper $stringHelper)
34 {
35 $this->styleManager = $styleManager;
36 $this->styleMerger = $styleMerger;
37 $this->stringsEscaper = $stringsEscaper;
38 $this->stringHelper = $stringHelper;
39 }
40 /**
41 * Prepares the worksheet to accept data.
42 *
43 * @param Worksheet $worksheet The worksheet to start
44 *
45 * @throws \OpenSpout\Common\Exception\IOException If the sheet data file cannot be opened for writing
46 */
47 public function startSheet(Worksheet $worksheet)
48 {
49 $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
50 $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
51 $worksheet->setFilePointer($sheetFilePointer);
52 }
53 /**
54 * Returns the table XML root node as string.
55 *
56 * @return string "<table>" node as string
57 */
58 public function getTableElementStartAsString(Worksheet $worksheet)
59 {
60 $externalSheet = $worksheet->getExternalSheet();
61 $escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
62 $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
63 $tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
64 $tableElement .= $this->styleManager->getStyledTableColumnXMLContent($worksheet->getMaxNumColumns());
65 return $tableElement;
66 }
67 /**
68 * Adds a row to the given worksheet.
69 *
70 * @param Worksheet $worksheet The worksheet to add the row to
71 * @param Row $row The row to be added
72 *
73 * @throws InvalidArgumentException If a cell value's type is not supported
74 * @throws IOException If the data cannot be written
75 */
76 public function addRow(Worksheet $worksheet, Row $row)
77 {
78 $cells = $row->getCells();
79 $rowStyle = $row->getStyle();
80 $data = '<table:table-row table:style-name="ro1">';
81 $currentCellIndex = 0;
82 $nextCellIndex = 1;
83 for ($i = 0; $i < $row->getNumCells(); ++$i) {
84 /** @var Cell $cell */
85 $cell = $cells[$currentCellIndex];
86 /** @var null|Cell $nextCell */
87 $nextCell = $cells[$nextCellIndex] ?? null;
88 if (null === $nextCell || $cell->getValue() !== $nextCell->getValue()) {
89 $registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
90 $cellStyle = $registeredStyle->getStyle();
91 if ($registeredStyle->isMatchingRowStyle()) {
92 $rowStyle = $cellStyle;
93 // Replace actual rowStyle (possibly with null id) by registered style (with id)
94 }
95 $data .= $this->getCellXMLWithStyle($cell, $cellStyle, $currentCellIndex, $nextCellIndex);
96 $currentCellIndex = $nextCellIndex;
97 }
98 ++$nextCellIndex;
99 }
100 $data .= '</table:table-row>';
101 $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $data);
102 if (\false === $wasWriteSuccessful) {
103 throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
104 }
105 // only update the count if the write worked
106 $lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
107 $worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
108 }
109 /**
110 * Closes the worksheet.
111 */
112 public function close(Worksheet $worksheet)
113 {
114 $worksheetFilePointer = $worksheet->getFilePointer();
115 if (!\is_resource($worksheetFilePointer)) {
116 return;
117 }
118 \fclose($worksheetFilePointer);
119 }
120 /**
121 * @param null|float $width
122 */
123 public function setDefaultColumnWidth($width)
124 {
125 $this->styleManager->setDefaultColumnWidth($width);
126 }
127 /**
128 * @param null|float $height
129 */
130 public function setDefaultRowHeight($height)
131 {
132 $this->styleManager->setDefaultRowHeight($height);
133 }
134 /**
135 * @param int ...$columns One or more columns with this width
136 */
137 public function setColumnWidth(float $width, ...$columns)
138 {
139 $this->styleManager->setColumnWidth($width, ...$columns);
140 }
141 /**
142 * @param float $width The width to set
143 * @param int $start First column index of the range
144 * @param int $end Last column index of the range
145 */
146 public function setColumnWidthForRange(float $width, int $start, int $end)
147 {
148 $this->styleManager->setColumnWidthForRange($width, $start, $end);
149 }
150 /**
151 * Checks if the sheet has been sucessfully created. Throws an exception if not.
152 *
153 * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
154 *
155 * @throws IOException If the sheet data file cannot be opened for writing
156 */
157 private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
158 {
159 if (!$sheetFilePointer) {
160 throw new IOException('Unable to open sheet for writing.');
161 }
162 }
163 /**
164 * Applies styles to the given style, merging the cell's style with its row's style.
165 *
166 * @throws InvalidArgumentException If a cell value's type is not supported
167 */
168 private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
169 {
170 $isMatchingRowStyle = \false;
171 if ($cell->getStyle()->isEmpty()) {
172 $cell->setStyle($rowStyle);
173 $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
174 if ($possiblyUpdatedStyle->isUpdated()) {
175 $registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
176 } else {
177 $registeredStyle = $this->styleManager->registerStyle($rowStyle);
178 $isMatchingRowStyle = \true;
179 }
180 } else {
181 $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
182 $cell->setStyle($mergedCellAndRowStyle);
183 $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
184 if ($possiblyUpdatedStyle->isUpdated()) {
185 $newCellStyle = $possiblyUpdatedStyle->getStyle();
186 } else {
187 $newCellStyle = $mergedCellAndRowStyle;
188 }
189 $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
190 }
191 return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
192 }
193 private function getCellXMLWithStyle(Cell $cell, Style $style, int $currentCellIndex, int $nextCellIndex) : string
194 {
195 $styleIndex = $style->getId() + 1;
196 // 1-based
197 $numTimesValueRepeated = $nextCellIndex - $currentCellIndex;
198 return $this->getCellXML($cell, $styleIndex, $numTimesValueRepeated);
199 }
200 /**
201 * Returns the cell XML content, given its value.
202 *
203 * @param Cell $cell The cell to be written
204 * @param int $styleIndex Index of the used style
205 * @param int $numTimesValueRepeated Number of times the value is consecutively repeated
206 *
207 * @throws InvalidArgumentException If a cell value's type is not supported
208 *
209 * @return string The cell XML content
210 */
211 private function getCellXML(Cell $cell, $styleIndex, $numTimesValueRepeated)
212 {
213 $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
214 if (1 !== $numTimesValueRepeated) {
215 $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
216 }
217 if ($cell->isString()) {
218 $data .= ' office:value-type="string" calcext:value-type="string">';
219 $cellValueLines = \explode("\n", $cell->getValue());
220 foreach ($cellValueLines as $cellValueLine) {
221 $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
222 }
223 $data .= '</table:table-cell>';
224 } elseif ($cell->isBoolean()) {
225 $value = $cell->getValue() ? 'true' : 'false';
226 // boolean-value spec: http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-boolean
227 $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $value . '">';
228 $data .= '<text:p>' . $cell->getValue() . '</text:p>';
229 $data .= '</table:table-cell>';
230 } elseif ($cell->isNumeric()) {
231 $cellValue = $this->stringHelper->formatNumericValue($cell->getValue());
232 $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">';
233 $data .= '<text:p>' . $cellValue . '</text:p>';
234 $data .= '</table:table-cell>';
235 } elseif ($cell->isDate()) {
236 $value = $cell->getValue();
237 if ($value instanceof \DateTimeInterface) {
238 $datevalue = \substr((new \DateTimeImmutable('@' . $value->getTimestamp()))->format(\DateTimeInterface::W3C), 0, -6);
239 $data .= ' office:value-type="date" calcext:value-type="date" office:date-value="' . $datevalue . 'Z">';
240 $data .= '<text:p>' . $datevalue . 'Z</text:p>';
241 } elseif ($value instanceof \DateInterval) {
242 // workaround for missing DateInterval::format('c'), see https://stackoverflow.com/a/61088115/53538
243 static $f = ['M0S', 'H0M', 'DT0H', 'M0D', 'Y0M', 'P0Y', 'Y0M', 'P0M'];
244 static $r = ['M', 'H', 'DT', 'M', 'Y0M', 'P', 'Y', 'P'];
245 $value = \rtrim(\str_replace($f, $r, $value->format('P%yY%mM%dDT%hH%iM%sS')), 'PT') ?: 'PT0S';
246 $data .= ' office:value-type="time" office:time-value="' . $value . '">';
247 $data .= '<text:p>' . $value . '</text:p>';
248 } else {
249 throw new InvalidArgumentException('Trying to add a date value with an unsupported type: ' . \gettype($cell->getValue()));
250 }
251 $data .= '</table:table-cell>';
252 } elseif ($cell->isError() && \is_string($cell->getValueEvenIfError())) {
253 // only writes the error value if it's a string
254 $data .= ' office:value-type="string" calcext:value-type="error" office:value="">';
255 $data .= '<text:p>' . $cell->getValueEvenIfError() . '</text:p>';
256 $data .= '</table:table-cell>';
257 } elseif ($cell->isEmpty()) {
258 $data .= '/>';
259 } else {
260 throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
261 }
262 return $data;
263 }
264 }
265