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