Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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\Style\Fill; 11 use PhpOffice\PhpSpreadsheet\Style\Font; 12 use PhpOffice\PhpSpreadsheet\Worksheet\Row; 13 use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; 14 use PhpOffice\PhpSpreadsheet\Writer\Exception; 15 use PhpOffice\PhpSpreadsheet\Writer\Ods; 16 use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment; 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 const CELL_STYLE_PREFIX = 'ce'; 26 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() 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 // 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 ($i = 0; $i < $sheetCount; ++$i) { 124 $objWriter->startElement('table:table'); 125 $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle()); 126 $objWriter->writeElement('office:forms'); 127 $objWriter->startElement('table:table-column'); 128 $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); 129 $objWriter->endElement(); 130 $this->writeRows($objWriter, $spreadsheet->getSheet($i)); 131 $objWriter->endElement(); 132 } 133 } 134 135 /** 136 * Write rows of the specified sheet. 137 */ 138 private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void 139 { 140 $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; 141 $span_row = 0; 142 $rows = $sheet->getRowIterator(); 143 while ($rows->valid()) { 144 --$numberRowsRepeated; 145 $row = $rows->current(); 146 if ($row->getCellIterator()->valid()) { 147 if ($span_row) { 148 $objWriter->startElement('table:table-row'); 149 if ($span_row > 1) { 150 $objWriter->writeAttribute('table:number-rows-repeated', $span_row); 151 } 152 $objWriter->startElement('table:table-cell'); 153 $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); 154 $objWriter->endElement(); 155 $objWriter->endElement(); 156 $span_row = 0; 157 } 158 $objWriter->startElement('table:table-row'); 159 $this->writeCells($objWriter, $row); 160 $objWriter->endElement(); 161 } else { 162 ++$span_row; 163 } 164 $rows->next(); 165 } 166 } 167 168 /** 169 * Write cells of the specified row. 170 */ 171 private function writeCells(XMLWriter $objWriter, Row $row): void 172 { 173 $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; 174 $prevColumn = -1; 175 $cells = $row->getCellIterator(); 176 while ($cells->valid()) { 177 /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */ 178 $cell = $cells->current(); 179 $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; 180 181 $this->writeCellSpan($objWriter, $column, $prevColumn); 182 $objWriter->startElement('table:table-cell'); 183 $this->writeCellMerge($objWriter, $cell); 184 185 // Style XF 186 $style = $cell->getXfIndex(); 187 if ($style !== null) { 188 $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style); 189 } 190 191 switch ($cell->getDataType()) { 192 case DataType::TYPE_BOOL: 193 $objWriter->writeAttribute('office:value-type', 'boolean'); 194 $objWriter->writeAttribute('office:value', $cell->getValue()); 195 $objWriter->writeElement('text:p', $cell->getValue()); 196 197 break; 198 case DataType::TYPE_ERROR: 199 throw new Exception('Writing of error not implemented yet.'); 200 201 break; 202 case DataType::TYPE_FORMULA: 203 $formulaValue = $cell->getValue(); 204 if ($this->getParentWriter()->getPreCalculateFormulas()) { 205 try { 206 $formulaValue = $cell->getCalculatedValue(); 207 } catch (Exception $e) { 208 // don't do anything 209 } 210 } 211 $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue())); 212 if (is_numeric($formulaValue)) { 213 $objWriter->writeAttribute('office:value-type', 'float'); 214 } else { 215 $objWriter->writeAttribute('office:value-type', 'string'); 216 } 217 $objWriter->writeAttribute('office:value', $formulaValue); 218 $objWriter->writeElement('text:p', $formulaValue); 219 220 break; 221 case DataType::TYPE_INLINE: 222 throw new Exception('Writing of inline not implemented yet.'); 223 224 break; 225 case DataType::TYPE_NUMERIC: 226 $objWriter->writeAttribute('office:value-type', 'float'); 227 $objWriter->writeAttribute('office:value', $cell->getValue()); 228 $objWriter->writeElement('text:p', $cell->getValue()); 229 230 break; 231 case DataType::TYPE_STRING: 232 $objWriter->writeAttribute('office:value-type', 'string'); 233 $objWriter->writeElement('text:p', $cell->getValue()); 234 235 break; 236 } 237 Comment::write($objWriter, $cell); 238 $objWriter->endElement(); 239 $prevColumn = $column; 240 $cells->next(); 241 } 242 $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; 243 if ($numberColsRepeated > 0) { 244 if ($numberColsRepeated > 1) { 245 $objWriter->startElement('table:table-cell'); 246 $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated); 247 $objWriter->endElement(); 248 } else { 249 $objWriter->writeElement('table:table-cell'); 250 } 251 } 252 } 253 254 /** 255 * Write span. 256 * 257 * @param int $curColumn 258 * @param int $prevColumn 259 */ 260 private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void 261 { 262 $diff = $curColumn - $prevColumn - 1; 263 if (1 === $diff) { 264 $objWriter->writeElement('table:table-cell'); 265 } elseif ($diff > 1) { 266 $objWriter->startElement('table:table-cell'); 267 $objWriter->writeAttribute('table:number-columns-repeated', $diff); 268 $objWriter->endElement(); 269 } 270 } 271 272 /** 273 * Write XF cell styles. 274 */ 275 private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void 276 { 277 foreach ($spreadsheet->getCellXfCollection() as $style) { 278 $writer->startElement('style:style'); 279 $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); 280 $writer->writeAttribute('style:family', 'table-cell'); 281 $writer->writeAttribute('style:parent-style-name', 'Default'); 282 283 // style:text-properties 284 285 // Font 286 $writer->startElement('style:text-properties'); 287 288 $font = $style->getFont(); 289 290 if ($font->getBold()) { 291 $writer->writeAttribute('fo:font-weight', 'bold'); 292 $writer->writeAttribute('style:font-weight-complex', 'bold'); 293 $writer->writeAttribute('style:font-weight-asian', 'bold'); 294 } 295 296 if ($font->getItalic()) { 297 $writer->writeAttribute('fo:font-style', 'italic'); 298 } 299 300 if ($color = $font->getColor()) { 301 $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); 302 } 303 304 if ($family = $font->getName()) { 305 $writer->writeAttribute('fo:font-family', $family); 306 } 307 308 if ($size = $font->getSize()) { 309 $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size)); 310 } 311 312 if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) { 313 $writer->writeAttribute('style:text-underline-style', 'solid'); 314 $writer->writeAttribute('style:text-underline-width', 'auto'); 315 $writer->writeAttribute('style:text-underline-color', 'font-color'); 316 317 switch ($font->getUnderline()) { 318 case Font::UNDERLINE_DOUBLE: 319 $writer->writeAttribute('style:text-underline-type', 'double'); 320 321 break; 322 case Font::UNDERLINE_SINGLE: 323 $writer->writeAttribute('style:text-underline-type', 'single'); 324 325 break; 326 } 327 } 328 329 $writer->endElement(); // Close style:text-properties 330 331 // style:table-cell-properties 332 333 $writer->startElement('style:table-cell-properties'); 334 $writer->writeAttribute('style:rotation-align', 'none'); 335 336 // Fill 337 if ($fill = $style->getFill()) { 338 switch ($fill->getFillType()) { 339 case Fill::FILL_SOLID: 340 $writer->writeAttribute('fo:background-color', sprintf( 341 '#%s', 342 strtolower($fill->getStartColor()->getRGB()) 343 )); 344 345 break; 346 case Fill::FILL_GRADIENT_LINEAR: 347 case Fill::FILL_GRADIENT_PATH: 348 /// TODO :: To be implemented 349 break; 350 case Fill::FILL_NONE: 351 default: 352 } 353 } 354 355 $writer->endElement(); // Close style:table-cell-properties 356 357 // End 358 359 $writer->endElement(); // Close style:style 360 } 361 } 362 363 /** 364 * Write attributes for merged cell. 365 */ 366 private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void 367 { 368 if (!$cell->isMergeRangeValueCell()) { 369 return; 370 } 371 372 $mergeRange = Coordinate::splitRange($cell->getMergeRange()); 373 [$startCell, $endCell] = $mergeRange[0]; 374 $start = Coordinate::coordinateFromString($startCell); 375 $end = Coordinate::coordinateFromString($endCell); 376 $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1; 377 $rowSpan = $end[1] - $start[1] + 1; 378 379 $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan); 380 $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan); 381 } 382 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body