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