Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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 foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) { 130 $objWriter->startElement('table:table-column'); 131 $objWriter->writeAttribute( 132 'table:style-name', 133 sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric()) 134 ); 135 $objWriter->writeAttribute('table:default-cell-style-name', 'ce0'); 136 // $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); 137 $objWriter->endElement(); 138 } 139 $this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex); 140 $objWriter->endElement(); 141 } 142 } 143 144 /** 145 * Write rows of the specified sheet. 146 */ 147 private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void 148 { 149 $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; 150 $span_row = 0; 151 $rows = $sheet->getRowIterator(); 152 foreach ($rows as $row) { 153 $cellIterator = $row->getCellIterator(); 154 --$numberRowsRepeated; 155 if ($cellIterator->valid()) { 156 $objWriter->startElement('table:table-row'); 157 if ($span_row) { 158 if ($span_row > 1) { 159 $objWriter->writeAttribute('table:number-rows-repeated', (string) $span_row); 160 } 161 $objWriter->startElement('table:table-cell'); 162 $objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX); 163 $objWriter->endElement(); 164 $span_row = 0; 165 } else { 166 if ($sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) { 167 $objWriter->writeAttribute( 168 'table:style-name', 169 sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) 170 ); 171 } 172 $this->writeCells($objWriter, $cellIterator); 173 } 174 $objWriter->endElement(); 175 } else { 176 ++$span_row; 177 } 178 } 179 } 180 181 /** 182 * Write cells of the specified row. 183 */ 184 private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void 185 { 186 $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; 187 $prevColumn = -1; 188 foreach ($cells as $cell) { 189 /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */ 190 $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; 191 192 $this->writeCellSpan($objWriter, $column, $prevColumn); 193 $objWriter->startElement('table:table-cell'); 194 $this->writeCellMerge($objWriter, $cell); 195 196 // Style XF 197 $style = $cell->getXfIndex(); 198 if ($style !== null) { 199 $objWriter->writeAttribute('table:style-name', Style::CELL_STYLE_PREFIX . $style); 200 } 201 202 switch ($cell->getDataType()) { 203 case DataType::TYPE_BOOL: 204 $objWriter->writeAttribute('office:value-type', 'boolean'); 205 $objWriter->writeAttribute('office:value', $cell->getValue()); 206 $objWriter->writeElement('text:p', $cell->getValue()); 207 208 break; 209 case DataType::TYPE_ERROR: 210 $objWriter->writeAttribute('table:formula', 'of:=#NULL!'); 211 $objWriter->writeAttribute('office:value-type', 'string'); 212 $objWriter->writeAttribute('office:string-value', ''); 213 $objWriter->writeElement('text:p', '#NULL!'); 214 215 break; 216 case DataType::TYPE_FORMULA: 217 $formulaValue = $cell->getValue(); 218 if ($this->getParentWriter()->getPreCalculateFormulas()) { 219 try { 220 $formulaValue = $cell->getCalculatedValue(); 221 } catch (Exception $e) { 222 // don't do anything 223 } 224 } 225 $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue())); 226 if (is_numeric($formulaValue)) { 227 $objWriter->writeAttribute('office:value-type', 'float'); 228 } else { 229 $objWriter->writeAttribute('office:value-type', 'string'); 230 } 231 $objWriter->writeAttribute('office:value', $formulaValue); 232 $objWriter->writeElement('text:p', $formulaValue); 233 234 break; 235 case DataType::TYPE_NUMERIC: 236 $objWriter->writeAttribute('office:value-type', 'float'); 237 $objWriter->writeAttribute('office:value', $cell->getValue()); 238 $objWriter->writeElement('text:p', $cell->getValue()); 239 240 break; 241 case DataType::TYPE_INLINE: 242 // break intentionally omitted 243 case DataType::TYPE_STRING: 244 $objWriter->writeAttribute('office:value-type', 'string'); 245 $objWriter->writeElement('text:p', $cell->getValue()); 246 247 break; 248 } 249 Comment::write($objWriter, $cell); 250 $objWriter->endElement(); 251 $prevColumn = $column; 252 } 253 254 $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; 255 if ($numberColsRepeated > 0) { 256 if ($numberColsRepeated > 1) { 257 $objWriter->startElement('table:table-cell'); 258 $objWriter->writeAttribute('table:number-columns-repeated', (string) $numberColsRepeated); 259 $objWriter->endElement(); 260 } else { 261 $objWriter->writeElement('table:table-cell'); 262 } 263 } 264 } 265 266 /** 267 * Write span. 268 * 269 * @param int $curColumn 270 * @param int $prevColumn 271 */ 272 private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void 273 { 274 $diff = $curColumn - $prevColumn - 1; 275 if (1 === $diff) { 276 $objWriter->writeElement('table:table-cell'); 277 } elseif ($diff > 1) { 278 $objWriter->startElement('table:table-cell'); 279 $objWriter->writeAttribute('table:number-columns-repeated', (string) $diff); 280 $objWriter->endElement(); 281 } 282 } 283 284 /** 285 * Write XF cell styles. 286 */ 287 private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void 288 { 289 $styleWriter = new Style($writer); 290 291 $sheetCount = $spreadsheet->getSheetCount(); 292 for ($i = 0; $i < $sheetCount; ++$i) { 293 $worksheet = $spreadsheet->getSheet($i); 294 $styleWriter->writeTableStyle($worksheet, $i + 1); 295 296 $worksheet->calculateColumnWidths(); 297 foreach ($worksheet->getColumnDimensions() as $columnDimension) { 298 if ($columnDimension->getWidth() !== -1.0) { 299 $styleWriter->writeColumnStyles($columnDimension, $i); 300 } 301 } 302 } 303 for ($i = 0; $i < $sheetCount; ++$i) { 304 $worksheet = $spreadsheet->getSheet($i); 305 foreach ($worksheet->getRowDimensions() as $rowDimension) { 306 if ($rowDimension->getRowHeight() > 0.0) { 307 $styleWriter->writeRowStyles($rowDimension, $i); 308 } 309 } 310 } 311 312 foreach ($spreadsheet->getCellXfCollection() as $style) { 313 $styleWriter->write($style); 314 } 315 } 316 317 /** 318 * Write attributes for merged cell. 319 */ 320 private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void 321 { 322 if (!$cell->isMergeRangeValueCell()) { 323 return; 324 } 325 326 $mergeRange = Coordinate::splitRange((string) $cell->getMergeRange()); 327 [$startCell, $endCell] = $mergeRange[0]; 328 $start = Coordinate::coordinateFromString($startCell); 329 $end = Coordinate::coordinateFromString($endCell); 330 $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1; 331 $rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1; 332 333 $objWriter->writeAttribute('table:number-columns-spanned', (string) $columnSpan); 334 $objWriter->writeAttribute('table:number-rows-spanned', (string) $rowSpan); 335 } 336 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body