diff options
Diffstat (limited to '')
46 files changed, 21146 insertions, 0 deletions
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php new file mode 100644 index 0000000..2d23a5f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -0,0 +1,131 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+abstract class BaseWriter implements IWriter
+{
+ /**
+ * Write charts that are defined in the workbook?
+ * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object;.
+ *
+ * @var bool
+ */
+ protected $includeCharts = false;
+
+ /**
+ * Pre-calculate formulas
+ * Forces PhpSpreadsheet to recalculate all formulae in a workbook when saving, so that the pre-calculated values are
+ * immediately available to MS Excel or other office spreadsheet viewer when opening the file.
+ *
+ * @var bool
+ */
+ protected $preCalculateFormulas = true;
+
+ /**
+ * Use disk caching where possible?
+ *
+ * @var bool
+ */
+ private $useDiskCaching = false;
+
+ /**
+ * Disk caching directory.
+ *
+ * @var string
+ */
+ private $diskCachingDirectory = './';
+
+ /**
+ * @var resource
+ */
+ protected $fileHandle;
+
+ /**
+ * @var bool
+ */
+ private $shouldCloseFile;
+
+ public function getIncludeCharts()
+ {
+ return $this->includeCharts;
+ }
+
+ public function setIncludeCharts($pValue)
+ {
+ $this->includeCharts = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getPreCalculateFormulas()
+ {
+ return $this->preCalculateFormulas;
+ }
+
+ public function setPreCalculateFormulas($pValue)
+ {
+ $this->preCalculateFormulas = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getUseDiskCaching()
+ {
+ return $this->useDiskCaching;
+ }
+
+ public function setUseDiskCaching($pValue, $pDirectory = null)
+ {
+ $this->useDiskCaching = $pValue;
+
+ if ($pDirectory !== null) {
+ if (is_dir($pDirectory)) {
+ $this->diskCachingDirectory = $pDirectory;
+ } else {
+ throw new Exception("Directory does not exist: $pDirectory");
+ }
+ }
+
+ return $this;
+ }
+
+ public function getDiskCachingDirectory()
+ {
+ return $this->diskCachingDirectory;
+ }
+
+ /**
+ * Open file handle.
+ *
+ * @param resource|string $filename
+ */
+ public function openFileHandle($filename): void
+ {
+ if (is_resource($filename)) {
+ $this->fileHandle = $filename;
+ $this->shouldCloseFile = false;
+
+ return;
+ }
+
+ $fileHandle = $filename ? fopen($filename, 'wb+') : false;
+ if ($fileHandle === false) {
+ throw new Exception('Could not open file "' . $filename . '" for writing.');
+ }
+
+ $this->fileHandle = $fileHandle;
+ $this->shouldCloseFile = true;
+ }
+
+ /**
+ * Close file handle only if we opened it ourselves.
+ */
+ protected function maybeCloseFileHandle(): void
+ {
+ if ($this->shouldCloseFile) {
+ if (!fclose($this->fileHandle)) {
+ throw new Exception('Could not close file after writing.');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php new file mode 100644 index 0000000..b3a6de5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php @@ -0,0 +1,352 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Csv extends BaseWriter
+{
+ /**
+ * PhpSpreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Delimiter.
+ *
+ * @var string
+ */
+ private $delimiter = ',';
+
+ /**
+ * Enclosure.
+ *
+ * @var string
+ */
+ private $enclosure = '"';
+
+ /**
+ * Line ending.
+ *
+ * @var string
+ */
+ private $lineEnding = PHP_EOL;
+
+ /**
+ * Sheet index to write.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Whether to write a BOM (for UTF8).
+ *
+ * @var bool
+ */
+ private $useBOM = false;
+
+ /**
+ * Whether to write a Separator line as the first line of the file
+ * sep=x.
+ *
+ * @var bool
+ */
+ private $includeSeparatorLine = false;
+
+ /**
+ * Whether to write a fully Excel compatible CSV file.
+ *
+ * @var bool
+ */
+ private $excelCompatibility = false;
+
+ /**
+ * Create a new CSV.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Fetch sheet
+ $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ if ($this->excelCompatibility) {
+ $this->setUseBOM(true); // Enforce UTF-8 BOM Header
+ $this->setIncludeSeparatorLine(true); // Set separator line
+ $this->setEnclosure('"'); // Set enclosure to "
+ $this->setDelimiter(';'); // Set delimiter to a semi-colon
+ $this->setLineEnding("\r\n");
+ }
+
+ if ($this->useBOM) {
+ // Write the UTF-8 BOM code if required
+ fwrite($this->fileHandle, "\xEF\xBB\xBF");
+ }
+
+ if ($this->includeSeparatorLine) {
+ // Write the separator line if required
+ fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
+ }
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $sheet->getHighestDataColumn();
+ $maxRow = $sheet->getHighestDataRow();
+
+ // Write rows to file
+ for ($row = 1; $row <= $maxRow; ++$row) {
+ // Convert the row to an array...
+ $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas);
+ // ... and write to the file
+ $this->writeLine($this->fileHandle, $cellsArray[0]);
+ }
+
+ $this->maybeCloseFileHandle();
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Get delimiter.
+ *
+ * @return string
+ */
+ public function getDelimiter()
+ {
+ return $this->delimiter;
+ }
+
+ /**
+ * Set delimiter.
+ *
+ * @param string $pValue Delimiter, defaults to ','
+ *
+ * @return $this
+ */
+ public function setDelimiter($pValue)
+ {
+ $this->delimiter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get enclosure.
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ return $this->enclosure;
+ }
+
+ /**
+ * Set enclosure.
+ *
+ * @param string $pValue Enclosure, defaults to "
+ *
+ * @return $this
+ */
+ public function setEnclosure($pValue = '"')
+ {
+ $this->enclosure = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get line ending.
+ *
+ * @return string
+ */
+ public function getLineEnding()
+ {
+ return $this->lineEnding;
+ }
+
+ /**
+ * Set line ending.
+ *
+ * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL)
+ *
+ * @return $this
+ */
+ public function setLineEnding($pValue)
+ {
+ $this->lineEnding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether BOM should be used.
+ *
+ * @return bool
+ */
+ public function getUseBOM()
+ {
+ return $this->useBOM;
+ }
+
+ /**
+ * Set whether BOM should be used.
+ *
+ * @param bool $pValue Use UTF-8 byte-order mark? Defaults to false
+ *
+ * @return $this
+ */
+ public function setUseBOM($pValue)
+ {
+ $this->useBOM = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether a separator line should be included.
+ *
+ * @return bool
+ */
+ public function getIncludeSeparatorLine()
+ {
+ return $this->includeSeparatorLine;
+ }
+
+ /**
+ * Set whether a separator line should be included as the first line of the file.
+ *
+ * @param bool $pValue Use separator line? Defaults to false
+ *
+ * @return $this
+ */
+ public function setIncludeSeparatorLine($pValue)
+ {
+ $this->includeSeparatorLine = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the file should be saved with full Excel Compatibility.
+ *
+ * @return bool
+ */
+ public function getExcelCompatibility()
+ {
+ return $this->excelCompatibility;
+ }
+
+ /**
+ * Set whether the file should be saved with full Excel Compatibility.
+ *
+ * @param bool $pValue Set the file to be written as a fully Excel compatible csv file
+ * Note that this overrides other settings such as useBOM, enclosure and delimiter
+ *
+ * @return $this
+ */
+ public function setExcelCompatibility($pValue)
+ {
+ $this->excelCompatibility = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ private $enclosureRequired = true;
+
+ public function setEnclosureRequired(bool $value): self
+ {
+ $this->enclosureRequired = $value;
+
+ return $this;
+ }
+
+ public function getEnclosureRequired(): bool
+ {
+ return $this->enclosureRequired;
+ }
+
+ /**
+ * Write line to CSV file.
+ *
+ * @param resource $pFileHandle PHP filehandle
+ * @param array $pValues Array containing values in a row
+ */
+ private function writeLine($pFileHandle, array $pValues): void
+ {
+ // No leading delimiter
+ $delimiter = '';
+
+ // Build the line
+ $line = '';
+
+ foreach ($pValues as $element) {
+ // Add delimiter
+ $line .= $delimiter;
+ $delimiter = $this->delimiter;
+ // Escape enclosures
+ $enclosure = $this->enclosure;
+ if ($enclosure) {
+ // If enclosure is not required, use enclosure only if
+ // element contains newline, delimiter, or enclosure.
+ if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) {
+ $enclosure = '';
+ } else {
+ $element = str_replace($enclosure, $enclosure . $enclosure, $element);
+ }
+ }
+ // Add enclosed string
+ $line .= $enclosure . $element . $enclosure;
+ }
+
+ // Add line ending
+ $line .= $this->lineEnding;
+
+ // Write to file
+ fwrite($pFileHandle, $line);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php new file mode 100644 index 0000000..8d1067c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php @@ -0,0 +1,9 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Exception extends PhpSpreadsheetException
+{
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php new file mode 100644 index 0000000..2153a69 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php @@ -0,0 +1,1864 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Html extends BaseWriter
+{
+ /**
+ * Spreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ protected $spreadsheet;
+
+ /**
+ * Sheet index to write.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Images root.
+ *
+ * @var string
+ */
+ private $imagesRoot = '';
+
+ /**
+ * embed images, or link to images.
+ *
+ * @var bool
+ */
+ private $embedImages = false;
+
+ /**
+ * Use inline CSS?
+ *
+ * @var bool
+ */
+ private $useInlineCss = false;
+
+ /**
+ * Use embedded CSS?
+ *
+ * @var bool
+ */
+ private $useEmbeddedCSS = true;
+
+ /**
+ * Array of CSS styles.
+ *
+ * @var array
+ */
+ private $cssStyles;
+
+ /**
+ * Array of column widths in points.
+ *
+ * @var array
+ */
+ private $columnWidths;
+
+ /**
+ * Default font.
+ *
+ * @var Font
+ */
+ private $defaultFont;
+
+ /**
+ * Flag whether spans have been calculated.
+ *
+ * @var bool
+ */
+ private $spansAreCalculated = false;
+
+ /**
+ * Excel cells that should not be written as HTML cells.
+ *
+ * @var array
+ */
+ private $isSpannedCell = [];
+
+ /**
+ * Excel cells that are upper-left corner in a cell merge.
+ *
+ * @var array
+ */
+ private $isBaseCell = [];
+
+ /**
+ * Excel rows that should not be written as HTML rows.
+ *
+ * @var array
+ */
+ private $isSpannedRow = [];
+
+ /**
+ * Is the current writer creating PDF?
+ *
+ * @var bool
+ */
+ protected $isPdf = false;
+
+ /**
+ * Generate the Navigation block.
+ *
+ * @var bool
+ */
+ private $generateSheetNavigationBlock = true;
+
+ /**
+ * Callback for editing generated html.
+ *
+ * @var null|callable
+ */
+ private $editHtmlCallback;
+
+ /**
+ * Create a new HTML.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+ $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ // Write html
+ fwrite($this->fileHandle, $this->generateHTMLAll());
+
+ // Close file
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Save Spreadsheet as html to variable.
+ *
+ * @return string
+ */
+ public function generateHtmlAll()
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Build CSS
+ $this->buildCSS(!$this->useInlineCss);
+
+ $html = '';
+
+ // Write headers
+ $html .= $this->generateHTMLHeader(!$this->useInlineCss);
+
+ // Write navigation (tabs)
+ if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
+ $html .= $this->generateNavigation();
+ }
+
+ // Write data
+ $html .= $this->generateSheetData();
+
+ // Write footer
+ $html .= $this->generateHTMLFooter();
+ $callback = $this->editHtmlCallback;
+ if ($callback) {
+ $html = $callback($html);
+ }
+
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ return $html;
+ }
+
+ /**
+ * Set a callback to edit the entire HTML.
+ *
+ * The callback must accept the HTML as string as first parameter,
+ * and it must return the edited HTML as string.
+ */
+ public function setEditHtmlCallback(?callable $callback): void
+ {
+ $this->editHtmlCallback = $callback;
+ }
+
+ const VALIGN_ARR = [
+ Alignment::VERTICAL_BOTTOM => 'bottom',
+ Alignment::VERTICAL_TOP => 'top',
+ Alignment::VERTICAL_CENTER => 'middle',
+ Alignment::VERTICAL_JUSTIFY => 'middle',
+ ];
+
+ /**
+ * Map VAlign.
+ *
+ * @param string $vAlign Vertical alignment
+ *
+ * @return string
+ */
+ private function mapVAlign($vAlign)
+ {
+ return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline';
+ }
+
+ const HALIGN_ARR = [
+ Alignment::HORIZONTAL_LEFT => 'left',
+ Alignment::HORIZONTAL_RIGHT => 'right',
+ Alignment::HORIZONTAL_CENTER => 'center',
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center',
+ Alignment::HORIZONTAL_JUSTIFY => 'justify',
+ ];
+
+ /**
+ * Map HAlign.
+ *
+ * @param string $hAlign Horizontal alignment
+ *
+ * @return string
+ */
+ private function mapHAlign($hAlign)
+ {
+ return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : '';
+ }
+
+ const BORDER_ARR = [
+ Border::BORDER_NONE => 'none',
+ Border::BORDER_DASHDOT => '1px dashed',
+ Border::BORDER_DASHDOTDOT => '1px dotted',
+ Border::BORDER_DASHED => '1px dashed',
+ Border::BORDER_DOTTED => '1px dotted',
+ Border::BORDER_DOUBLE => '3px double',
+ Border::BORDER_HAIR => '1px solid',
+ Border::BORDER_MEDIUM => '2px solid',
+ Border::BORDER_MEDIUMDASHDOT => '2px dashed',
+ Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted',
+ Border::BORDER_SLANTDASHDOT => '2px dashed',
+ Border::BORDER_THICK => '3px solid',
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param int $borderStyle Sheet index
+ *
+ * @return string
+ */
+ private function mapBorderStyle($borderStyle)
+ {
+ return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid';
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return bool
+ */
+ public function getGenerateSheetNavigationBlock()
+ {
+ return $this->generateSheetNavigationBlock;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not
+ *
+ * @return $this
+ */
+ public function setGenerateSheetNavigationBlock($pValue)
+ {
+ $this->generateSheetNavigationBlock = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Write all sheets (resets sheetIndex to NULL).
+ *
+ * @return $this
+ */
+ public function writeAllSheets()
+ {
+ $this->sheetIndex = null;
+
+ return $this;
+ }
+
+ private static function generateMeta($val, $desc)
+ {
+ return $val ? (' <meta name="' . $desc . '" content="' . htmlspecialchars($val) . '" />' . PHP_EOL) : '';
+ }
+
+ /**
+ * Generate HTML header.
+ *
+ * @param bool $pIncludeStyles Include styles?
+ *
+ * @return string
+ */
+ public function generateHTMLHeader($pIncludeStyles = false)
+ {
+ // Construct HTML
+ $properties = $this->spreadsheet->getProperties();
+ $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . PHP_EOL;
+ $html .= '<html xmlns="http://www.w3.org/1999/xhtml">' . PHP_EOL;
+ $html .= ' <head>' . PHP_EOL;
+ $html .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . PHP_EOL;
+ $html .= ' <meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" />' . PHP_EOL;
+ $html .= ' <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL;
+ $html .= self::generateMeta($properties->getCreator(), 'author');
+ $html .= self::generateMeta($properties->getTitle(), 'title');
+ $html .= self::generateMeta($properties->getDescription(), 'description');
+ $html .= self::generateMeta($properties->getSubject(), 'subject');
+ $html .= self::generateMeta($properties->getKeywords(), 'keywords');
+ $html .= self::generateMeta($properties->getCategory(), 'category');
+ $html .= self::generateMeta($properties->getCompany(), 'company');
+ $html .= self::generateMeta($properties->getManager(), 'manager');
+
+ $html .= $pIncludeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
+
+ $html .= ' </head>' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+ $html .= ' <body>' . PHP_EOL;
+
+ return $html;
+ }
+
+ private function generateSheetPrep()
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)];
+ }
+
+ return $sheets;
+ }
+
+ private function generateSheetStarts($sheet, $rowMin)
+ {
+ // calculate start of <tbody>, <thead>
+ $tbodyStart = $rowMin;
+ $theadStart = $theadEnd = 0; // default: no <thead> no </thead>
+ if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ // we can only support repeating rows that start at top row
+ if ($rowsToRepeatAtTop[0] == 1) {
+ $theadStart = $rowsToRepeatAtTop[0];
+ $theadEnd = $rowsToRepeatAtTop[1];
+ $tbodyStart = $rowsToRepeatAtTop[1] + 1;
+ }
+ }
+
+ return [$theadStart, $theadEnd, $tbodyStart];
+ }
+
+ private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart)
+ {
+ // <thead> ?
+ $startTag = ($row == $theadStart) ? (' <thead>' . PHP_EOL) : '';
+ if (!$startTag) {
+ $startTag = ($row == $tbodyStart) ? (' <tbody>' . PHP_EOL) : '';
+ }
+ $endTag = ($row == $theadEnd) ? (' </thead>' . PHP_EOL) : '';
+ $cellType = ($row >= $tbodyStart) ? 'td' : 'th';
+
+ return [$cellType, $startTag, $endTag];
+ }
+
+ /**
+ * Generate sheet data.
+ *
+ * @return string
+ */
+ public function generateSheetData()
+ {
+ $sheets = $this->generateSheetPrep();
+
+ // Construct HTML
+ $html = '';
+
+ // Loop all sheets
+ $sheetId = 0;
+ foreach ($sheets as $sheet) {
+ // Write table header
+ $html .= $this->generateTableHeader($sheet);
+
+ // Get worksheet dimension
+ [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension());
+ [$minCol, $minRow] = Coordinate::coordinateFromString($min);
+ $minCol = Coordinate::columnIndexFromString($minCol);
+ [$maxCol, $maxRow] = Coordinate::coordinateFromString($max);
+ $maxCol = Coordinate::columnIndexFromString($maxCol);
+
+ [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow);
+
+ // Loop through cells
+ $row = $minRow - 1;
+ while ($row++ < $maxRow) {
+ [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart);
+ $html .= $startTag;
+
+ // Write row if there are HTML table cells in it
+ if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
+ // Start a new rowData
+ $rowData = [];
+ // Loop through columns
+ $column = $minCol;
+ while ($column <= $maxCol) {
+ // Cell exists?
+ if ($sheet->cellExistsByColumnAndRow($column, $row)) {
+ $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row;
+ } else {
+ $rowData[$column] = '';
+ }
+ ++$column;
+ }
+ $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
+ }
+
+ $html .= $endTag;
+ }
+ $html .= $this->extendRowsForChartsAndImages($sheet, $row);
+
+ // Write table footer
+ $html .= $this->generateTableFooter();
+ // Writing PDF?
+ if ($this->isPdf && $this->useInlineCss) {
+ if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
+ $html .= '<div style="page-break-before:always" ></div>';
+ }
+ }
+
+ // Next sheet
+ ++$sheetId;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate sheet tabs.
+ *
+ * @return string
+ */
+ public function generateNavigation()
+ {
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $html = '';
+
+ // Only if there are more than 1 sheets
+ if (count($sheets) > 1) {
+ // Loop all sheets
+ $sheetId = 0;
+
+ $html .= '<ul class="navigation">' . PHP_EOL;
+
+ foreach ($sheets as $sheet) {
+ $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
+ ++$sheetId;
+ }
+
+ $html .= '</ul>' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $row Row to check for charts
+ *
+ * @return array
+ *
+ * @codeCoverageIgnore
+ */
+ private function extendRowsForCharts(Worksheet $pSheet, int $row)
+ {
+ $rowMax = $row;
+ $colMax = 'A';
+ $anyfound = false;
+ if ($this->includeCharts) {
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $anyfound = true;
+ $chartCoordinates = $chart->getTopLeftPosition();
+ $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']);
+ $chartCol = Coordinate::columnIndexFromString($chartTL[0]);
+ if ($chartTL[1] > $rowMax) {
+ $rowMax = $chartTL[1];
+ if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $chartTL[0];
+ }
+ }
+ }
+ }
+ }
+
+ return [$rowMax, $colMax, $anyfound];
+ }
+
+ private function extendRowsForChartsAndImages(Worksheet $pSheet, int $row): string
+ {
+ [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($pSheet, $row);
+
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ $anyfound = true;
+ $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates());
+ $imageCol = Coordinate::columnIndexFromString($imageTL[0]);
+ if ($imageTL[1] > $rowMax) {
+ $rowMax = $imageTL[1];
+ if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $imageTL[0];
+ }
+ }
+ }
+
+ // Don't extend rows if not needed
+ if ($row === $rowMax || !$anyfound) {
+ return '';
+ }
+
+ $html = '';
+ ++$colMax;
+ ++$row;
+ while ($row <= $rowMax) {
+ $html .= '<tr>';
+ for ($col = 'A'; $col != $colMax; ++$col) {
+ $htmlx = $this->writeImageInCell($pSheet, $col . $row);
+ $htmlx .= $this->includeCharts ? $this->writeChartInCell($pSheet, $col . $row) : '';
+ if ($htmlx) {
+ $html .= "<td class='style0' style='position: relative;'>$htmlx</td>";
+ } else {
+ $html .= "<td class='style0'></td>";
+ }
+ }
+ ++$row;
+ $html .= '</tr>' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Convert Windows file name to file protocol URL.
+ *
+ * @param string $filename file name on local system
+ *
+ * @return string
+ */
+ public static function winFileToUrl($filename)
+ {
+ // Windows filename
+ if (substr($filename, 1, 2) === ':\\') {
+ $filename = 'file:///' . str_replace('\\', '/', $filename);
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Generate image tag in cell.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ */
+ private function writeImageInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write images
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ if ($drawing->getCoordinates() != $coordinates) {
+ continue;
+ }
+ $filedesc = $drawing->getDescription();
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image';
+ if ($drawing instanceof Drawing) {
+ $filename = $drawing->getPath();
+
+ // Strip off eventual '.'
+ $filename = preg_replace('/^[.]/', '', $filename);
+
+ // Prepend images root
+ $filename = $this->getImagesRoot() . $filename;
+
+ // Strip off eventual '.' if followed by non-/
+ $filename = preg_replace('@^[.]([^/])@', '$1', $filename);
+
+ // Convert UTF8 data to PCDATA
+ $filename = htmlspecialchars($filename);
+
+ $html .= PHP_EOL;
+ $imageData = self::winFileToUrl($filename);
+
+ if ($this->embedImages && !$this->isPdf) {
+ $picture = @file_get_contents($filename);
+ if ($picture !== false) {
+ $imageDetails = getimagesize($filename);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+ }
+ }
+
+ $html .= '<img style="position: absolute; z-index: 1; left: ' .
+ $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' .
+ $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
+ $imageData . '" alt="' . $filedesc . '" />';
+ } elseif ($drawing instanceof MemoryDrawing) {
+ ob_start(); // Let's start output buffering.
+ imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't.
+ $contents = ob_get_contents(); // Instead, output above is saved to $contents
+ ob_end_clean(); // End the output buffer.
+
+ $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
+
+ // Because of the nature of tables, width is more important than height.
+ // max-width: 100% ensures that image doesnt overflow containing cell
+ // width: X sets width of supplied image.
+ // As a result, images bigger than cell will be contained and images smaller will not get stretched
+ $html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate chart tag in cell.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private function writeChartInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write charts
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $chartCoordinates = $chart->getTopLeftPosition();
+ if ($chartCoordinates['cell'] == $coordinates) {
+ $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
+ if (!$chart->render($chartFileName)) {
+ return;
+ }
+
+ $html .= PHP_EOL;
+ $imageDetails = getimagesize($chartFileName);
+ $filedesc = $chart->getTitle();
+ $filedesc = $filedesc ? self::getChartCaption($filedesc->getCaption()) : '';
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart';
+ if ($fp = fopen($chartFileName, 'rb', 0)) {
+ $picture = fread($fp, filesize($chartFileName));
+ fclose($fp);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+
+ $html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" alt="' . $filedesc . '" />' . PHP_EOL;
+
+ unlink($chartFileName);
+ }
+ }
+ }
+ }
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ * Caption is described in documentation as fixed,
+ * but in 32_Chart it is somehow an array of RichText.
+ *
+ * @param mixed $cap
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private static function getChartCaption($cap)
+ {
+ return is_array($cap) ? implode(' ', $cap) : $cap;
+ }
+
+ /**
+ * Generate CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>)
+ *
+ * @return string
+ */
+ public function generateStyles($generateSurroundingHTML = true)
+ {
+ // Build CSS
+ $css = $this->buildCSS($generateSurroundingHTML);
+
+ // Construct HTML
+ $html = '';
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ $html .= ' <style type="text/css">' . PHP_EOL;
+ $html .= (array_key_exists('html', $css)) ? (' html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL) : '';
+ }
+
+ // Write all other styles
+ foreach ($css as $styleName => $styleDefinition) {
+ if ($styleName != 'html') {
+ $html .= ' ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL;
+ }
+ }
+ $html .= $this->generatePageDeclarations(false);
+
+ // End styles
+ if ($generateSurroundingHTML) {
+ $html .= ' </style>' . PHP_EOL;
+ }
+
+ // Return
+ return $html;
+ }
+
+ private function buildCssRowHeights(Worksheet $sheet, array &$css, int $sheetIndex): void
+ {
+ // Calculate row heights
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ $row = $rowDimension->getRowIndex() - 1;
+
+ // table.sheetN tr.rowYYYYYY { }
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
+
+ if ($rowDimension->getRowHeight() != -1) {
+ $pt_height = $rowDimension->getRowHeight();
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
+ }
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
+ }
+ }
+ }
+
+ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
+ {
+ // Calculate hash code
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ // Build styles
+ // Calculate column widths
+ $sheet->calculateColumnWidths();
+
+ // col elements, initialize
+ $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
+ $column = -1;
+ while ($column++ < $highestColumnIndex) {
+ $this->columnWidths[$sheetIndex][$column] = 42; // approximation
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
+ }
+
+ // col elements, loop through columnDimensions and set width
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
+ $width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont);
+ $width = SharedDrawing::pixelsToPoints($width);
+ if ($columnDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none';
+ }
+ if ($width >= 0) {
+ $this->columnWidths[$sheetIndex][$column] = $width;
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
+ }
+ }
+
+ // Default row height
+ $rowDimension = $sheet->getDefaultRowDimension();
+
+ // table.sheetN tr { }
+ $css['table.sheet' . $sheetIndex . ' tr'] = [];
+
+ if ($rowDimension->getRowHeight() == -1) {
+ $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
+ } else {
+ $pt_height = $rowDimension->getRowHeight();
+ }
+ $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
+ }
+
+ $this->buildCssRowHeights($sheet, $css, $sheetIndex);
+ }
+
+ /**
+ * Build CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { })
+ *
+ * @return array
+ */
+ public function buildCSS($generateSurroundingHTML = true)
+ {
+ // Cached?
+ if ($this->cssStyles !== null) {
+ return $this->cssStyles;
+ }
+
+ // Ensure that spans have been calculated
+ $this->calculateSpans();
+
+ // Construct CSS
+ $css = [];
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ // html { }
+ $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
+ $css['html']['font-size'] = '11pt';
+ $css['html']['background-color'] = 'white';
+ }
+
+ // CSS for comments as found in LibreOffice
+ $css['a.comment-indicator:hover + div.comment'] = [
+ 'background' => '#ffd',
+ 'position' => 'absolute',
+ 'display' => 'block',
+ 'border' => '1px solid black',
+ 'padding' => '0.5em',
+ ];
+
+ $css['a.comment-indicator'] = [
+ 'background' => 'red',
+ 'display' => 'inline-block',
+ 'border' => '1px solid black',
+ 'width' => '0.5em',
+ 'height' => '0.5em',
+ ];
+
+ $css['div.comment']['display'] = 'none';
+
+ // table { }
+ $css['table']['border-collapse'] = 'collapse';
+
+ // .b {}
+ $css['.b']['text-align'] = 'center'; // BOOL
+
+ // .e {}
+ $css['.e']['text-align'] = 'center'; // ERROR
+
+ // .f {}
+ $css['.f']['text-align'] = 'right'; // FORMULA
+
+ // .inlineStr {}
+ $css['.inlineStr']['text-align'] = 'left'; // INLINE
+
+ // .n {}
+ $css['.n']['text-align'] = 'right'; // NUMERIC
+
+ // .s {}
+ $css['.s']['text-align'] = 'left'; // STRING
+
+ // Calculate cell style hashes
+ foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
+ $css['td.style' . $index] = $this->createCSSStyle($style);
+ $css['th.style' . $index] = $this->createCSSStyle($style);
+ }
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Build styles per sheet
+ foreach ($sheets as $sheet) {
+ $this->buildCssPerSheet($sheet, $css);
+ }
+
+ // Cache
+ if ($this->cssStyles === null) {
+ $this->cssStyles = $css;
+ }
+
+ // Return
+ return $css;
+ }
+
+ /**
+ * Create CSS style.
+ *
+ * @return array
+ */
+ private function createCSSStyle(Style $pStyle)
+ {
+ // Create CSS
+ return array_merge(
+ $this->createCSSStyleAlignment($pStyle->getAlignment()),
+ $this->createCSSStyleBorders($pStyle->getBorders()),
+ $this->createCSSStyleFont($pStyle->getFont()),
+ $this->createCSSStyleFill($pStyle->getFill())
+ );
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
+ *
+ * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment
+ *
+ * @return array
+ */
+ private function createCSSStyleAlignment(Alignment $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
+ $textAlign = $this->mapHAlign($pStyle->getHorizontal());
+ if ($textAlign) {
+ $css['text-align'] = $textAlign;
+ if (in_array($textAlign, ['left', 'right'])) {
+ $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
+ }
+ }
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
+ *
+ * @return array
+ */
+ private function createCSSStyleFont(Font $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ if ($pStyle->getBold()) {
+ $css['font-weight'] = 'bold';
+ }
+ if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'underline line-through';
+ } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) {
+ $css['text-decoration'] = 'underline';
+ } elseif ($pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'line-through';
+ }
+ if ($pStyle->getItalic()) {
+ $css['font-style'] = 'italic';
+ }
+
+ $css['color'] = '#' . $pStyle->getColor()->getRGB();
+ $css['font-family'] = '\'' . $pStyle->getName() . '\'';
+ $css['font-size'] = $pStyle->getSize() . 'pt';
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Borders).
+ *
+ * @param Borders $pStyle Borders
+ *
+ * @return array
+ */
+ private function createCSSStyleBorders(Borders $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
+ $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
+ $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
+ $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Border).
+ *
+ * @param Border $pStyle Border
+ *
+ * @return string
+ */
+ private function createCSSStyleBorder(Border $pStyle)
+ {
+ // Create CSS - add !important to non-none border styles for merged cells
+ $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
+
+ return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
+ }
+
+ /**
+ * Create CSS style (Fill).
+ *
+ * @param Fill $pStyle Fill
+ *
+ * @return array
+ */
+ private function createCSSStyleFill(Fill $pStyle)
+ {
+ // Construct HTML
+ $css = [];
+
+ // Create CSS
+ $value = $pStyle->getFillType() == Fill::FILL_NONE ?
+ 'white' : '#' . $pStyle->getStartColor()->getRGB();
+ $css['background-color'] = $value;
+
+ return $css;
+ }
+
+ /**
+ * Generate HTML footer.
+ */
+ public function generateHTMLFooter()
+ {
+ // Construct HTML
+ $html = '';
+ $html .= ' </body>' . PHP_EOL;
+ $html .= '</html>' . PHP_EOL;
+
+ return $html;
+ }
+
+ private function generateTableTagInline($pSheet, $id)
+ {
+ $style = isset($this->cssStyles['table']) ?
+ $this->assembleCSS($this->cssStyles['table']) : '';
+
+ $prntgrid = $pSheet->getPrintGridlines();
+ $viewgrid = $this->isPdf ? $prntgrid : $pSheet->getShowGridlines();
+ if ($viewgrid && $prntgrid) {
+ $html = " <table border='1' cellpadding='1' $id cellspacing='1' style='$style' class='gridlines gridlinesp'>" . PHP_EOL;
+ } elseif ($viewgrid) {
+ $html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlines'>" . PHP_EOL;
+ } elseif ($prntgrid) {
+ $html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlinesp'>" . PHP_EOL;
+ } else {
+ $html = " <table border='0' cellpadding='1' $id cellspacing='0' style='$style'>" . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateTableTag($pSheet, $id, &$html, $sheetIndex): void
+ {
+ if (!$this->useInlineCss) {
+ $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
+ $gridlinesp = $pSheet->getPrintGridlines() ? ' gridlinesp' : '';
+ $html .= " <table border='0' cellpadding='0' cellspacing='0' $id class='sheet$sheetIndex$gridlines$gridlinesp'>" . PHP_EOL;
+ } else {
+ $html .= $this->generateTableTagInline($pSheet, $id);
+ }
+ }
+
+ /**
+ * Generate table header.
+ *
+ * @param Worksheet $pSheet The worksheet for the table we are writing
+ * @param bool $showid whether or not to add id to table tag
+ *
+ * @return string
+ */
+ private function generateTableHeader($pSheet, $showid = true)
+ {
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+
+ // Construct HTML
+ $html = '';
+ $id = $showid ? "id='sheet$sheetIndex'" : '';
+ if ($showid) {
+ $html .= "<div style='page: page$sheetIndex'>\n";
+ } else {
+ $html .= "<div style='page: page$sheetIndex' class='scrpgbrk'>\n";
+ }
+
+ $this->generateTableTag($pSheet, $id, $html, $sheetIndex);
+
+ // Write <col> elements
+ $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1;
+ $i = -1;
+ while ($i++ < $highestColumnIndex) {
+ if (!$this->useInlineCss) {
+ $html .= ' <col class="col' . $i . '" />' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
+ $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
+ $html .= ' <col style="' . $style . '" />' . PHP_EOL;
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate table footer.
+ */
+ private function generateTableFooter()
+ {
+ return ' </tbody></table>' . PHP_EOL . '</div>' . PHP_EOL;
+ }
+
+ /**
+ * Generate row start.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $sheetIndex Sheet index (0-based)
+ * @param int $pRow row number
+ *
+ * @return string
+ */
+ private function generateRowStart(Worksheet $pSheet, $sheetIndex, $pRow)
+ {
+ $html = '';
+ if (count($pSheet->getBreaks()) > 0) {
+ $breaks = $pSheet->getBreaks();
+
+ // check if a break is needed before this row
+ if (isset($breaks['A' . $pRow])) {
+ // close table: </table>
+ $html .= $this->generateTableFooter();
+ if ($this->isPdf && $this->useInlineCss) {
+ $html .= '<div style="page-break-before:always" />';
+ }
+
+ // open table again: <table> + <col> etc.
+ $html .= $this->generateTableHeader($pSheet, false);
+ $html .= '<tbody>' . PHP_EOL;
+ }
+ }
+
+ // Write row start
+ if (!$this->useInlineCss) {
+ $html .= ' <tr class="row' . $pRow . '">' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
+ ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
+
+ $html .= ' <tr style="' . $style . '">' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum)
+ {
+ $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : '';
+ $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
+ if (!$this->useInlineCss) {
+ $cssClass = 'column' . $colNum;
+ } else {
+ $cssClass = [];
+ // The statements below do nothing.
+ // Commenting out the code rather than deleting it
+ // in case someone can figure out what their intent was.
+ //if ($cellType == 'th') {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
+ // }
+ //} else {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
+ // }
+ //}
+ // End of mystery statements.
+ }
+
+ return [$cell, $cssClass, $coordinate];
+ }
+
+ private function generateRowCellDataValueRich($cell, &$cellData): void
+ {
+ // Loop through rich text elements
+ $elements = $cell->getValue()->getRichTextElements();
+ foreach ($elements as $element) {
+ // Rich text start?
+ if ($element instanceof Run) {
+ $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
+
+ $cellEnd = '';
+ if ($element->getFont()->getSuperscript()) {
+ $cellData .= '<sup>';
+ $cellEnd = '</sup>';
+ } elseif ($element->getFont()->getSubscript()) {
+ $cellData .= '<sub>';
+ $cellEnd = '</sub>';
+ }
+
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+
+ $cellData .= $cellEnd;
+
+ $cellData .= '</span>';
+ } else {
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+ }
+ }
+ }
+
+ private function generateRowCellDataValue($pSheet, $cell, &$cellData): void
+ {
+ if ($cell->getValue() instanceof RichText) {
+ $this->generateRowCellDataValueRich($cell, $cellData);
+ } else {
+ $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
+ $cellData = NumberFormat::toFormattedString(
+ $origData,
+ $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
+ [$this, 'formatColor']
+ );
+ if ($cellData === $origData) {
+ $cellData = htmlspecialchars($cellData);
+ }
+ if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
+ $cellData = '<sup>' . $cellData . '</sup>';
+ } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
+ $cellData = '<sub>' . $cellData . '</sub>';
+ }
+ }
+ }
+
+ private function generateRowCellData($pSheet, $cell, &$cssClass, $cellType)
+ {
+ $cellData = ' ';
+ if ($cell instanceof Cell) {
+ $cellData = '';
+ // Don't know what this does, and no test cases.
+ //if ($cell->getParent() === null) {
+ // $cell->attach($pSheet);
+ //}
+ // Value
+ $this->generateRowCellDataValue($pSheet, $cell, $cellData);
+
+ // Converts the cell content so that spaces occuring at beginning of each new line are replaced by
+ // Example: " Hello\n to the world" is converted to " Hello\n to the world"
+ $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData);
+
+ // convert newline "\n" to '<br>'
+ $cellData = nl2br($cellData);
+
+ // Extend CSS class?
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $cell->getXfIndex();
+ $cssClass .= ' ' . $cell->getDataType();
+ } else {
+ if ($cellType == 'th') {
+ if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
+ }
+ } else {
+ if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
+ }
+ }
+
+ // General horizontal alignment: Actual horizontal alignment depends on dataType
+ $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
+ if (
+ $sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
+ && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
+ ) {
+ $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
+ }
+ }
+ } else {
+ // Use default borders for empty cell
+ if (is_string($cssClass)) {
+ $cssClass .= ' style0';
+ }
+ }
+
+ return $cellData;
+ }
+
+ private function generateRowIncludeCharts($pSheet, $coordinate)
+ {
+ return $this->includeCharts ? $this->writeChartInCell($pSheet, $coordinate) : '';
+ }
+
+ private function generateRowSpans($html, $rowSpan, $colSpan)
+ {
+ $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : '';
+ $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : '';
+
+ return $html;
+ }
+
+ private function generateRowWriteCell(&$html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow): void
+ {
+ // Image?
+ $htmlx = $this->writeImageInCell($pSheet, $coordinate);
+ // Chart?
+ $htmlx .= $this->generateRowIncludeCharts($pSheet, $coordinate);
+ // Column start
+ $html .= ' <' . $cellType;
+ if (!$this->useInlineCss && !$this->isPdf) {
+ $html .= ' class="' . $cssClass . '"';
+ if ($htmlx) {
+ $html .= " style='position: relative;'";
+ }
+ } else {
+ //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
+ // We must explicitly write the width of the <td> element because TCPDF
+ // does not recognize e.g. <col style="width:42pt">
+ if ($this->useInlineCss) {
+ $xcssClass = $cssClass;
+ } else {
+ $html .= ' class="' . $cssClass . '"';
+ $xcssClass = [];
+ }
+ $width = 0;
+ $i = $colNum - 1;
+ $e = $colNum + $colSpan - 1;
+ while ($i++ < $e) {
+ if (isset($this->columnWidths[$sheetIndex][$i])) {
+ $width += $this->columnWidths[$sheetIndex][$i];
+ }
+ }
+ $xcssClass['width'] = $width . 'pt';
+
+ // We must also explicitly write the height of the <td> element because TCPDF
+ // does not recognize e.g. <tr style="height:50pt">
+ if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
+ $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
+ $xcssClass['height'] = $height;
+ }
+ //** end of redundant code **
+
+ if ($htmlx) {
+ $xcssClass['position'] = 'relative';
+ }
+ $html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
+ }
+ $html = $this->generateRowSpans($html, $rowSpan, $colSpan);
+
+ $html .= '>';
+ $html .= $htmlx;
+
+ $html .= $this->writeComment($pSheet, $coordinate);
+
+ // Cell data
+ $html .= $cellData;
+
+ // Column end
+ $html .= '</' . $cellType . '>' . PHP_EOL;
+ }
+
+ /**
+ * Generate row.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param array $pValues Array containing cells in a row
+ * @param int $pRow Row number (0-based)
+ * @param string $cellType eg: 'td'
+ *
+ * @return string
+ */
+ private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType)
+ {
+ // Sheet index
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+ $html = $this->generateRowStart($pSheet, $sheetIndex, $pRow);
+
+ // Write cells
+ $colNum = 0;
+ foreach ($pValues as $cellAddress) {
+ [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum);
+
+ $colSpan = 1;
+ $rowSpan = 1;
+
+ // Cell Data
+ $cellData = $this->generateRowCellData($pSheet, $cell, $cssClass, $cellType);
+
+ // Hyperlink?
+ if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) {
+ $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>';
+ }
+
+ // Should the cell be written or is it swallowed by a rowspan or colspan?
+ $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])
+ && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]);
+
+ // Colspan and Rowspan
+ $colspan = 1;
+ $rowspan = 1;
+ if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) {
+ $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum];
+ $rowSpan = $spans['rowspan'];
+ $colSpan = $spans['colspan'];
+
+ // Also apply style from last cell in merge to fix borders -
+ // relies on !important for non-none border declarations in createCSSStyleBorder
+ $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex();
+ }
+ }
+
+ // Write
+ if ($writeCell) {
+ $this->generateRowWriteCell($html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow);
+ }
+
+ // Next column
+ ++$colNum;
+ }
+
+ // Write row end
+ $html .= ' </tr>' . PHP_EOL;
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Takes array where of CSS properties / values and converts to CSS string.
+ *
+ * @return string
+ */
+ private function assembleCSS(array $pValue = [])
+ {
+ $pairs = [];
+ foreach ($pValue as $property => $value) {
+ $pairs[] = $property . ':' . $value;
+ }
+ $string = implode('; ', $pairs);
+
+ return $string;
+ }
+
+ /**
+ * Get images root.
+ *
+ * @return string
+ */
+ public function getImagesRoot()
+ {
+ return $this->imagesRoot;
+ }
+
+ /**
+ * Set images root.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setImagesRoot($pValue)
+ {
+ $this->imagesRoot = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get embed images.
+ *
+ * @return bool
+ */
+ public function getEmbedImages()
+ {
+ return $this->embedImages;
+ }
+
+ /**
+ * Set embed images.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setEmbedImages($pValue)
+ {
+ $this->embedImages = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use inline CSS?
+ *
+ * @return bool
+ */
+ public function getUseInlineCss()
+ {
+ return $this->useInlineCss;
+ }
+
+ /**
+ * Set use inline CSS?
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setUseInlineCss($pValue)
+ {
+ $this->useInlineCss = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use embedded CSS?
+ *
+ * @return bool
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no longer used
+ */
+ public function getUseEmbeddedCSS()
+ {
+ return $this->useEmbeddedCSS;
+ }
+
+ /**
+ * Set use embedded CSS?
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no longer used
+ */
+ public function setUseEmbeddedCSS($pValue)
+ {
+ $this->useEmbeddedCSS = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add color to formatted string as inline style.
+ *
+ * @param string $pValue Plain formatted value without color
+ * @param string $pFormat Format code
+ *
+ * @return string
+ */
+ public function formatColor($pValue, $pFormat)
+ {
+ // Color information, e.g. [Red] is always at the beginning
+ $color = null; // initialize
+ $matches = [];
+
+ $color_regex = '/^\\[[a-zA-Z]+\\]/';
+ if (preg_match($color_regex, $pFormat, $matches)) {
+ $color = str_replace(['[', ']'], '', $matches[0]);
+ $color = strtolower($color);
+ }
+
+ // convert to PCDATA
+ $value = htmlspecialchars($pValue);
+
+ // color span tag
+ if ($color !== null) {
+ $value = '<span style="color:' . $color . '">' . $value . '</span>';
+ }
+
+ return $value;
+ }
+
+ /**
+ * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
+ */
+ private function calculateSpans(): void
+ {
+ if ($this->spansAreCalculated) {
+ return;
+ }
+ // Identify all cells that should be omitted in HTML due to cell merge.
+ // In HTML only the upper-left cell should be written and it should have
+ // appropriate rowspan / colspan attribute
+ $sheetIndexes = $this->sheetIndex !== null ?
+ [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
+
+ foreach ($sheetIndexes as $sheetIndex) {
+ $sheet = $this->spreadsheet->getSheet($sheetIndex);
+
+ $candidateSpannedRow = [];
+
+ // loop through all Excel merged cells
+ foreach ($sheet->getMergeCells() as $cells) {
+ [$cells] = Coordinate::splitRange($cells);
+ $first = $cells[0];
+ $last = $cells[1];
+
+ [$fc, $fr] = Coordinate::coordinateFromString($first);
+ $fc = Coordinate::columnIndexFromString($fc) - 1;
+
+ [$lc, $lr] = Coordinate::coordinateFromString($last);
+ $lc = Coordinate::columnIndexFromString($lc) - 1;
+
+ // loop through the individual cells in the individual merge
+ $r = $fr - 1;
+ while ($r++ < $lr) {
+ // also, flag this row as a HTML row that is candidate to be omitted
+ $candidateSpannedRow[$r] = $r;
+
+ $c = $fc - 1;
+ while ($c++ < $lc) {
+ if (!($c == $fc && $r == $fr)) {
+ // not the upper-left cell (should not be written in HTML)
+ $this->isSpannedCell[$sheetIndex][$r][$c] = [
+ 'baseCell' => [$fr, $fc],
+ ];
+ } else {
+ // upper-left is the base cell that should hold the colspan/rowspan attribute
+ $this->isBaseCell[$sheetIndex][$r][$c] = [
+ 'xlrowspan' => $lr - $fr + 1, // Excel rowspan
+ 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
+ 'xlcolspan' => $lc - $fc + 1, // Excel colspan
+ 'colspan' => $lc - $fc + 1, // HTML colspan, value may change
+ ];
+ }
+ }
+ }
+ }
+
+ $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow);
+
+ // TODO: Same for columns
+ }
+
+ // We have calculated the spans
+ $this->spansAreCalculated = true;
+ }
+
+ private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void
+ {
+ // Identify which rows should be omitted in HTML. These are the rows where all the cells
+ // participate in a merge and the where base cells are somewhere above.
+ $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
+ foreach ($candidateSpannedRow as $rowIndex) {
+ if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
+ if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
+ $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
+ }
+ }
+ }
+
+ // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
+ if (isset($this->isSpannedRow[$sheetIndex])) {
+ foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
+ $adjustedBaseCells = [];
+ $c = -1;
+ $e = $countColumns - 1;
+ while ($c++ < $e) {
+ $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
+
+ if (!in_array($baseCell, $adjustedBaseCells)) {
+ // subtract rowspan by 1
+ --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
+ $adjustedBaseCells[] = $baseCell;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Write a comment in the same format as LibreOffice.
+ *
+ * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092
+ *
+ * @param string $coordinate
+ *
+ * @return string
+ */
+ private function writeComment(Worksheet $pSheet, $coordinate)
+ {
+ $result = '';
+ if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) {
+ $result .= '<a class="comment-indicator"></a>';
+ $result .= '<div class="comment">' . nl2br($pSheet->getComment($coordinate)->getText()->getPlainText()) . '</div>';
+ $result .= PHP_EOL;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate @page declarations.
+ *
+ * @param bool $generateSurroundingHTML
+ *
+ * @return string
+ */
+ private function generatePageDeclarations($generateSurroundingHTML)
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $htmlPage = $generateSurroundingHTML ? ('<style type="text/css">' . PHP_EOL) : '';
+
+ // Loop all sheets
+ $sheetId = 0;
+ foreach ($sheets as $pSheet) {
+ $htmlPage .= "@page page$sheetId { ";
+ $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; ';
+ $htmlPage .= 'margin-left: ' . $left;
+ $right = StringHelper::FormatNumber($pSheet->getPageMargins()->getRight()) . 'in; ';
+ $htmlPage .= 'margin-right: ' . $right;
+ $top = StringHelper::FormatNumber($pSheet->getPageMargins()->getTop()) . 'in; ';
+ $htmlPage .= 'margin-top: ' . $top;
+ $bottom = StringHelper::FormatNumber($pSheet->getPageMargins()->getBottom()) . 'in; ';
+ $htmlPage .= 'margin-bottom: ' . $bottom;
+ $orientation = $pSheet->getPageSetup()->getOrientation();
+ if ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE) {
+ $htmlPage .= 'size: landscape; ';
+ } elseif ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT) {
+ $htmlPage .= 'size: portrait; ';
+ }
+ $htmlPage .= "}\n";
+ ++$sheetId;
+ }
+ $htmlPage .= <<<EOF
+.navigation {page-break-after: always;}
+.scrpgbrk, div + div {page-break-before: always;}
+@media screen {
+ .gridlines td {border: 1px solid black;}
+ .gridlines th {border: 1px solid black;}
+ body>div {margin-top: 5px;}
+ body>div:first-child {margin-top: 0;}
+ .scrpgbrk {margin-top: 1px;}
+}
+@media print {
+ .gridlinesp td {border: 1px solid black;}
+ .gridlinesp th {border: 1px solid black;}
+ .navigation {display: none;}
+}
+
+EOF;
+ $htmlPage .= $generateSurroundingHTML ? ('</style>' . PHP_EOL) : '';
+
+ return $htmlPage;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php new file mode 100644 index 0000000..7f1b404 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php @@ -0,0 +1,87 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+interface IWriter
+{
+ /**
+ * IWriter constructor.
+ */
+ public function __construct(Spreadsheet $spreadsheet);
+
+ /**
+ * Write charts in workbook?
+ * If this is true, then the Writer will write definitions for any charts that exist in the PhpSpreadsheet object.
+ * If false (the default) it will ignore any charts defined in the PhpSpreadsheet object.
+ *
+ * @return bool
+ */
+ public function getIncludeCharts();
+
+ /**
+ * Set write charts in workbook
+ * Set to true, to advise the Writer to include any charts that exist in the PhpSpreadsheet object.
+ * Set to false (the default) to ignore charts.
+ *
+ * @param bool $pValue
+ *
+ * @return IWriter
+ */
+ public function setIncludeCharts($pValue);
+
+ /**
+ * Get Pre-Calculate Formulas flag
+ * If this is true (the default), then the writer will recalculate all formulae in a workbook when saving,
+ * so that the pre-calculated values are immediately available to MS Excel or other office spreadsheet
+ * viewer when opening the file
+ * If false, then formulae are not calculated on save. This is faster for saving in PhpSpreadsheet, but slower
+ * when opening the resulting file in MS Excel, because Excel has to recalculate the formulae itself.
+ *
+ * @return bool
+ */
+ public function getPreCalculateFormulas();
+
+ /**
+ * Set Pre-Calculate Formulas
+ * Set to true (the default) to advise the Writer to calculate all formulae on save
+ * Set to false to prevent precalculation of formulae on save.
+ *
+ * @param bool $pValue Pre-Calculate Formulas?
+ *
+ * @return IWriter
+ */
+ public function setPreCalculateFormulas($pValue);
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename Name of the file to save
+ */
+ public function save($pFilename);
+
+ /**
+ * Get use disk caching where possible?
+ *
+ * @return bool
+ */
+ public function getUseDiskCaching();
+
+ /**
+ * Set use disk caching where possible?
+ *
+ * @param bool $pValue
+ * @param string $pDirectory Disk caching directory
+ *
+ * @return IWriter
+ */
+ public function setUseDiskCaching($pValue, $pDirectory = null);
+
+ /**
+ * Get disk caching directory.
+ *
+ * @return string
+ */
+ public function getDiskCachingDirectory();
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php new file mode 100644 index 0000000..82f3b51 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php @@ -0,0 +1,156 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Content;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Meta;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\MetaInf;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Mimetype;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Settings;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Styles;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails;
+use ZipStream\Exception\OverflowException;
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class Ods extends BaseWriter
+{
+ /**
+ * Private writer parts.
+ *
+ * @var Ods\WriterPart[]
+ */
+ private $writerParts = [];
+
+ /**
+ * Private PhpSpreadsheet.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadSheet;
+
+ /**
+ * Create a new Ods.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'content' => Content::class,
+ 'meta' => Meta::class,
+ 'meta_inf' => MetaInf::class,
+ 'mimetype' => Mimetype::class,
+ 'settings' => Settings::class,
+ 'styles' => Styles::class,
+ 'thumbnails' => Thumbnails::class,
+ ];
+
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return null|Ods\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ if (!$this->spreadSheet) {
+ throw new WriterException('PhpSpreadsheet object unassigned.');
+ }
+
+ // garbage collect
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $zip = $this->createZip();
+
+ $zip->addFile('META-INF/manifest.xml', $this->getWriterPart('meta_inf')->writeManifest());
+ $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPart('thumbnails')->writeThumbnail());
+ $zip->addFile('content.xml', $this->getWriterPart('content')->write());
+ $zip->addFile('meta.xml', $this->getWriterPart('meta')->write());
+ $zip->addFile('mimetype', $this->getWriterPart('mimetype')->write());
+ $zip->addFile('settings.xml', $this->getWriterPart('settings')->write());
+ $zip->addFile('styles.xml', $this->getWriterPart('styles')->write());
+
+ // Close file
+ try {
+ $zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Create zip object.
+ *
+ * @return ZipStream
+ */
+ private function createZip()
+ {
+ // Try opening the ZIP file
+ if (!is_resource($this->fileHandle)) {
+ throw new WriterException('Could not open resource for writing.');
+ }
+
+ // Create new ZIP stream
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ return new ZipStream(null, $options);
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ if ($this->spreadSheet !== null) {
+ return $this->spreadSheet;
+ }
+
+ throw new WriterException('No PhpSpreadsheet assigned.');
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php new file mode 100644 index 0000000..ffee89f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php @@ -0,0 +1,30 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods\Cell;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+/**
+ * @author Alexander Pervakov <frost-nzcr4@jagmort.com>
+ */
+class Comment
+{
+ public static function write(XMLWriter $objWriter, Cell $cell): void
+ {
+ $comments = $cell->getWorksheet()->getComments();
+ if (!isset($comments[$cell->getCoordinate()])) {
+ return;
+ }
+ $comment = $comments[$cell->getCoordinate()];
+
+ $objWriter->startElement('office:annotation');
+ $objWriter->writeAttribute('svg:width', $comment->getWidth());
+ $objWriter->writeAttribute('svg:height', $comment->getHeight());
+ $objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
+ $objWriter->writeAttribute('svg:y', $comment->getMarginTop());
+ $objWriter->writeElement('dc:creator', $comment->getAuthor());
+ $objWriter->writeElement('text:p', $comment->getText()->getPlainText());
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php new file mode 100644 index 0000000..fe9118f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php @@ -0,0 +1,382 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Worksheet\Row;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception;
+use PhpOffice\PhpSpreadsheet\Writer\Ods;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment;
+
+/**
+ * @author Alexander Pervakov <frost-nzcr4@jagmort.com>
+ */
+class Content extends WriterPart
+{
+ const NUMBER_COLS_REPEATED_MAX = 1024;
+ const NUMBER_ROWS_REPEATED_MAX = 1048576;
+ const CELL_STYLE_PREFIX = 'ce';
+
+ private $formulaConvertor;
+
+ /**
+ * Set parent Ods writer.
+ */
+ public function __construct(Ods $writer)
+ {
+ parent::__construct($writer);
+
+ $this->formulaConvertor = new Formula($this->getParentWriter()->getSpreadsheet()->getDefinedNames());
+ }
+
+ /**
+ * Write content.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function write()
+ {
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Content
+ $objWriter->startElement('office:document-content');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
+ $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
+ $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:scripts');
+ $objWriter->writeElement('office:font-face-decls');
+
+ // Styles XF
+ $objWriter->startElement('office:automatic-styles');
+ $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
+ $objWriter->endElement();
+
+ $objWriter->startElement('office:body');
+ $objWriter->startElement('office:spreadsheet');
+ $objWriter->writeElement('table:calculation-settings');
+
+ $this->writeSheets($objWriter);
+
+ // Defined names (ranges and formulae)
+ (new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write sheets.
+ */
+ private function writeSheets(XMLWriter $objWriter): void
+ {
+ $spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->startElement('table:table');
+ $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
+ $objWriter->writeElement('office:forms');
+ $objWriter->startElement('table:table-column');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $this->writeRows($objWriter, $spreadsheet->getSheet($i));
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write rows of the specified sheet.
+ */
+ private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void
+ {
+ $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
+ $span_row = 0;
+ $rows = $sheet->getRowIterator();
+ while ($rows->valid()) {
+ --$numberRowsRepeated;
+ $row = $rows->current();
+ if ($row->getCellIterator()->valid()) {
+ if ($span_row) {
+ $objWriter->startElement('table:table-row');
+ if ($span_row > 1) {
+ $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
+ }
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $span_row = 0;
+ }
+ $objWriter->startElement('table:table-row');
+ $this->writeCells($objWriter, $row);
+ $objWriter->endElement();
+ } else {
+ ++$span_row;
+ }
+ $rows->next();
+ }
+ }
+
+ /**
+ * Write cells of the specified row.
+ */
+ private function writeCells(XMLWriter $objWriter, Row $row): void
+ {
+ $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
+ $prevColumn = -1;
+ $cells = $row->getCellIterator();
+ while ($cells->valid()) {
+ /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */
+ $cell = $cells->current();
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ $this->writeCellSpan($objWriter, $column, $prevColumn);
+ $objWriter->startElement('table:table-cell');
+ $this->writeCellMerge($objWriter, $cell);
+
+ // Style XF
+ $style = $cell->getXfIndex();
+ if ($style !== null) {
+ $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style);
+ }
+
+ switch ($cell->getDataType()) {
+ case DataType::TYPE_BOOL:
+ $objWriter->writeAttribute('office:value-type', 'boolean');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_ERROR:
+ throw new Exception('Writing of error not implemented yet.');
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $formulaValue = $cell->getValue();
+ if ($this->getParentWriter()->getPreCalculateFormulas()) {
+ try {
+ $formulaValue = $cell->getCalculatedValue();
+ } catch (Exception $e) {
+ // don't do anything
+ }
+ }
+ $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue()));
+ if (is_numeric($formulaValue)) {
+ $objWriter->writeAttribute('office:value-type', 'float');
+ } else {
+ $objWriter->writeAttribute('office:value-type', 'string');
+ }
+ $objWriter->writeAttribute('office:value', $formulaValue);
+ $objWriter->writeElement('text:p', $formulaValue);
+
+ break;
+ case DataType::TYPE_INLINE:
+ throw new Exception('Writing of inline not implemented yet.');
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $objWriter->writeAttribute('office:value-type', 'float');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_STRING:
+ $objWriter->writeAttribute('office:value-type', 'string');
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ }
+ Comment::write($objWriter, $cell);
+ $objWriter->endElement();
+ $prevColumn = $column;
+ $cells->next();
+ }
+ $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
+ if ($numberColsRepeated > 0) {
+ if ($numberColsRepeated > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated);
+ $objWriter->endElement();
+ } else {
+ $objWriter->writeElement('table:table-cell');
+ }
+ }
+ }
+
+ /**
+ * Write span.
+ *
+ * @param int $curColumn
+ * @param int $prevColumn
+ */
+ private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void
+ {
+ $diff = $curColumn - $prevColumn - 1;
+ if (1 === $diff) {
+ $objWriter->writeElement('table:table-cell');
+ } elseif ($diff > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $diff);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write XF cell styles.
+ */
+ private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
+ {
+ foreach ($spreadsheet->getCellXfCollection() as $style) {
+ $writer->startElement('style:style');
+ $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex());
+ $writer->writeAttribute('style:family', 'table-cell');
+ $writer->writeAttribute('style:parent-style-name', 'Default');
+
+ // style:text-properties
+
+ // Font
+ $writer->startElement('style:text-properties');
+
+ $font = $style->getFont();
+
+ if ($font->getBold()) {
+ $writer->writeAttribute('fo:font-weight', 'bold');
+ $writer->writeAttribute('style:font-weight-complex', 'bold');
+ $writer->writeAttribute('style:font-weight-asian', 'bold');
+ }
+
+ if ($font->getItalic()) {
+ $writer->writeAttribute('fo:font-style', 'italic');
+ }
+
+ if ($color = $font->getColor()) {
+ $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB()));
+ }
+
+ if ($family = $font->getName()) {
+ $writer->writeAttribute('fo:font-family', $family);
+ }
+
+ if ($size = $font->getSize()) {
+ $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size));
+ }
+
+ if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) {
+ $writer->writeAttribute('style:text-underline-style', 'solid');
+ $writer->writeAttribute('style:text-underline-width', 'auto');
+ $writer->writeAttribute('style:text-underline-color', 'font-color');
+
+ switch ($font->getUnderline()) {
+ case Font::UNDERLINE_DOUBLE:
+ $writer->writeAttribute('style:text-underline-type', 'double');
+
+ break;
+ case Font::UNDERLINE_SINGLE:
+ $writer->writeAttribute('style:text-underline-type', 'single');
+
+ break;
+ }
+ }
+
+ $writer->endElement(); // Close style:text-properties
+
+ // style:table-cell-properties
+
+ $writer->startElement('style:table-cell-properties');
+ $writer->writeAttribute('style:rotation-align', 'none');
+
+ // Fill
+ if ($fill = $style->getFill()) {
+ switch ($fill->getFillType()) {
+ case Fill::FILL_SOLID:
+ $writer->writeAttribute('fo:background-color', sprintf(
+ '#%s',
+ strtolower($fill->getStartColor()->getRGB())
+ ));
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ case Fill::FILL_GRADIENT_PATH:
+ /// TODO :: To be implemented
+ break;
+ case Fill::FILL_NONE:
+ default:
+ }
+ }
+
+ $writer->endElement(); // Close style:table-cell-properties
+
+ // End
+
+ $writer->endElement(); // Close style:style
+ }
+ }
+
+ /**
+ * Write attributes for merged cell.
+ */
+ private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void
+ {
+ if (!$cell->isMergeRangeValueCell()) {
+ return;
+ }
+
+ $mergeRange = Coordinate::splitRange($cell->getMergeRange());
+ [$startCell, $endCell] = $mergeRange[0];
+ $start = Coordinate::coordinateFromString($startCell);
+ $end = Coordinate::coordinateFromString($endCell);
+ $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
+ $rowSpan = $end[1] - $start[1] + 1;
+
+ $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
+ $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php new file mode 100644 index 0000000..7f02898 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php @@ -0,0 +1,119 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\DefinedName;
+
+class Formula
+{
+ private $definedNames = [];
+
+ /**
+ * @param DefinedName[] $definedNames
+ */
+ public function __construct(array $definedNames)
+ {
+ foreach ($definedNames as $definedName) {
+ $this->definedNames[] = $definedName->getName();
+ }
+ }
+
+ public function convertFormula(string $formula, string $worksheetName = ''): string
+ {
+ $formula = $this->convertCellReferences($formula, $worksheetName);
+ $formula = $this->convertDefinedNames($formula);
+
+ if (substr($formula, 0, 1) !== '=') {
+ $formula = '=' . $formula;
+ }
+
+ return 'of:' . $formula;
+ }
+
+ private function convertDefinedNames(string $formula): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '/mui',
+ $formula,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+ $values = array_column($splitRanges[0], 0);
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $value = $values[$splitCount];
+
+ if (in_array($value, $this->definedNames, true)) {
+ $formula = substr($formula, 0, $offset) . '$$' . $value . substr($formula, $offset + $length);
+ }
+ }
+
+ return $formula;
+ }
+
+ private function convertCellReferences(string $formula, string $worksheetName): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $formula,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ // Replace any commas in the formula with semi-colons for Ods
+ // If by chance there are commas in worksheet names, then they will be "fixed" again in the loop
+ // because we've already extracted worksheet names with our preg_match_all()
+ $formula = str_replace(',', ';', $formula);
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($formula[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $worksheetName;
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "['" . str_replace("'", "''", $worksheet) . "'";
+ } elseif (substr($formula, $offset - 1, 1) !== ':') {
+ $newRange = '[';
+ }
+ $newRange .= '.';
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+ // close the wrapping [] unless this is the first part of a range
+ $newRange .= substr($formula, $offset + $length, 1) !== ':' ? ']' : '';
+
+ $formula = substr($formula, 0, $offset) . $newRange . substr($formula, $offset + $length);
+ }
+
+ return $formula;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php new file mode 100644 index 0000000..67eafdb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php @@ -0,0 +1,75 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Meta extends WriterPart
+{
+ /**
+ * Write meta.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ if (!$spreadsheet) {
+ $spreadsheet = $this->getParentWriter()->getSpreadsheet();
+ }
+
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Meta
+ $objWriter->startElement('office:document-meta');
+
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:meta');
+
+ $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('meta:creation-date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+ $keywords = explode(' ', $spreadsheet->getProperties()->getKeywords());
+ foreach ($keywords as $keyword) {
+ $objWriter->writeElement('meta:keyword', $keyword);
+ }
+
+ //<meta:document-statistic meta:table-count="XXX" meta:cell-count="XXX" meta:object-count="XXX"/>
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'Company');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCompany());
+ $objWriter->endElement();
+
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'category');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCategory());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php new file mode 100644 index 0000000..aafa58b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php @@ -0,0 +1,60 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+class MetaInf extends WriterPart
+{
+ /**
+ * Write META-INF/manifest.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeManifest()
+ {
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Manifest
+ $objWriter->startElement('manifest:manifest');
+ $objWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', '/');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+ $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'meta.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'settings.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'content.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'Thumbnails/thumbnail.png');
+ $objWriter->writeAttribute('manifest:media-type', 'image/png');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'styles.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php new file mode 100644 index 0000000..2840a60 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php @@ -0,0 +1,20 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Mimetype extends WriterPart
+{
+ /**
+ * Write mimetype to plain text format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ return 'application/vnd.oasis.opendocument.spreadsheet';
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php new file mode 100644 index 0000000..8e9a896 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php @@ -0,0 +1,126 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\DefinedName;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class NamedExpressions
+{
+ private $objWriter;
+
+ private $spreadsheet;
+
+ private $formulaConvertor;
+
+ public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet, $formulaConvertor)
+ {
+ $this->objWriter = $objWriter;
+ $this->spreadsheet = $spreadsheet;
+ $this->formulaConvertor = $formulaConvertor;
+ }
+
+ public function write(): void
+ {
+ $this->objWriter->startElement('table:named-expressions');
+ $this->writeExpressions();
+ $this->objWriter->endElement();
+ }
+
+ private function writeExpressions(): void
+ {
+ $definedNames = $this->spreadsheet->getDefinedNames();
+
+ foreach ($definedNames as $definedName) {
+ if ($definedName->isFormula()) {
+ $this->objWriter->startElement('table:named-expression');
+ $this->writeNamedFormula($definedName, $this->spreadsheet->getActiveSheet());
+ } else {
+ $this->objWriter->startElement('table:named-range');
+ $this->writeNamedRange($definedName);
+ }
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ private function writeNamedFormula(DefinedName $definedName, Worksheet $defaultWorksheet): void
+ {
+ $this->objWriter->writeAttribute('table:name', $definedName->getName());
+ $this->objWriter->writeAttribute(
+ 'table:expression',
+ $this->formulaConvertor->convertFormula($definedName->getValue(), $definedName->getWorksheet()->getTitle())
+ );
+ $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress(
+ $definedName,
+ "'" . (($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle()) . "'!\$A\$1"
+ ));
+ }
+
+ private function writeNamedRange(DefinedName $definedName): void
+ {
+ $this->objWriter->writeAttribute('table:name', $definedName->getName());
+ $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress(
+ $definedName,
+ "'" . $definedName->getWorksheet()->getTitle() . "'!\$A\$1"
+ ));
+ $this->objWriter->writeAttribute('table:cell-range-address', $this->convertAddress($definedName, $definedName->getValue()));
+ }
+
+ private function convertAddress(DefinedName $definedName, string $address): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $address,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($address[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $definedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'.";
+ }
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+
+ $address = substr($address, 0, $offset) . $newRange . substr($address, $offset + $length);
+ }
+
+ if (substr($address, 0, 1) === '=') {
+ $address = substr($address, 1);
+ }
+
+ return $address;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php new file mode 100644 index 0000000..4b562e5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php @@ -0,0 +1,52 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Settings extends WriterPart
+{
+ /**
+ * Write settings.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Settings
+ $objWriter->startElement('office:document-settings');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:settings');
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:view-settings');
+ $objWriter->startElement('config:config-item-map-indexed');
+ $objWriter->writeAttribute('config:name', 'Views');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:configuration-settings');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php new file mode 100644 index 0000000..e492a7a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php @@ -0,0 +1,68 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Styles extends WriterPart
+{
+ /**
+ * Write styles.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Content
+ $objWriter->startElement('office:document-styles');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:font-face-decls');
+ $objWriter->writeElement('office:styles');
+ $objWriter->writeElement('office:automatic-styles');
+ $objWriter->writeElement('office:master-styles');
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php new file mode 100644 index 0000000..76d1395 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php @@ -0,0 +1,20 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Thumbnails extends WriterPart
+{
+ /**
+ * Write Thumbnails/thumbnail.png to PNG format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function writeThumbnail(?Spreadsheet $spreadsheet = null)
+ {
+ return '';
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php new file mode 100644 index 0000000..ec0772a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php @@ -0,0 +1,33 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+abstract class WriterPart
+{
+ /**
+ * Parent Ods object.
+ *
+ * @var Ods
+ */
+ private $parentWriter;
+
+ /**
+ * Get Ods writer.
+ *
+ * @return Ods
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Set parent Ods writer.
+ */
+ public function __construct(Ods $writer)
+ {
+ $this->parentWriter = $writer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php new file mode 100644 index 0000000..7777f40 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php @@ -0,0 +1,253 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+abstract class Pdf extends Html
+{
+ /**
+ * Temporary storage directory.
+ *
+ * @var string
+ */
+ protected $tempDir = '';
+
+ /**
+ * Font.
+ *
+ * @var string
+ */
+ protected $font = 'freesans';
+
+ /**
+ * Orientation (Over-ride).
+ *
+ * @var string
+ */
+ protected $orientation;
+
+ /**
+ * Paper size (Over-ride).
+ *
+ * @var int
+ */
+ protected $paperSize;
+
+ /**
+ * Paper Sizes xRef List.
+ *
+ * @var array
+ */
+ protected static $paperSizes = [
+ PageSetup::PAPERSIZE_LETTER => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_LETTER_SMALL => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_TABLOID => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_LEDGER => [1224.00, 792.00], // (17 in. by 11 in.)
+ PageSetup::PAPERSIZE_LEGAL => 'LEGAL', // (8.5 in. by 14 in.)
+ PageSetup::PAPERSIZE_STATEMENT => [396.00, 612.00], // (5.5 in. by 8.5 in.)
+ PageSetup::PAPERSIZE_EXECUTIVE => 'EXECUTIVE', // (7.25 in. by 10.5 in.)
+ PageSetup::PAPERSIZE_A3 => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A4 => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A4_SMALL => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A5 => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5 => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_FOLIO => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_QUARTO => [609.45, 779.53], // (215 mm by 275 mm)
+ PageSetup::PAPERSIZE_STANDARD_1 => [720.00, 1008.00], // (10 in. by 14 in.)
+ PageSetup::PAPERSIZE_STANDARD_2 => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_NOTE => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO9_ENVELOPE => [279.00, 639.00], // (3.875 in. by 8.875 in.)
+ PageSetup::PAPERSIZE_NO10_ENVELOPE => [297.00, 684.00], // (4.125 in. by 9.5 in.)
+ PageSetup::PAPERSIZE_NO11_ENVELOPE => [324.00, 747.00], // (4.5 in. by 10.375 in.)
+ PageSetup::PAPERSIZE_NO12_ENVELOPE => [342.00, 792.00], // (4.75 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO14_ENVELOPE => [360.00, 828.00], // (5 in. by 11.5 in.)
+ PageSetup::PAPERSIZE_C => [1224.00, 1584.00], // (17 in. by 22 in.)
+ PageSetup::PAPERSIZE_D => [1584.00, 2448.00], // (22 in. by 34 in.)
+ PageSetup::PAPERSIZE_E => [2448.00, 3168.00], // (34 in. by 44 in.)
+ PageSetup::PAPERSIZE_DL_ENVELOPE => [311.81, 623.62], // (110 mm by 220 mm)
+ PageSetup::PAPERSIZE_C5_ENVELOPE => 'C5', // (162 mm by 229 mm)
+ PageSetup::PAPERSIZE_C3_ENVELOPE => 'C3', // (324 mm by 458 mm)
+ PageSetup::PAPERSIZE_C4_ENVELOPE => 'C4', // (229 mm by 324 mm)
+ PageSetup::PAPERSIZE_C6_ENVELOPE => 'C6', // (114 mm by 162 mm)
+ PageSetup::PAPERSIZE_C65_ENVELOPE => [323.15, 649.13], // (114 mm by 229 mm)
+ PageSetup::PAPERSIZE_B4_ENVELOPE => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5_ENVELOPE => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_B6_ENVELOPE => [498.90, 354.33], // (176 mm by 125 mm)
+ PageSetup::PAPERSIZE_ITALY_ENVELOPE => [311.81, 651.97], // (110 mm by 230 mm)
+ PageSetup::PAPERSIZE_MONARCH_ENVELOPE => [279.00, 540.00], // (3.875 in. by 7.5 in.)
+ PageSetup::PAPERSIZE_6_3_4_ENVELOPE => [261.00, 468.00], // (3.625 in. by 6.5 in.)
+ PageSetup::PAPERSIZE_US_STANDARD_FANFOLD => [1071.00, 792.00], // (14.875 in. by 11 in.)
+ PageSetup::PAPERSIZE_GERMAN_STANDARD_FANFOLD => [612.00, 864.00], // (8.5 in. by 12 in.)
+ PageSetup::PAPERSIZE_GERMAN_LEGAL_FANFOLD => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_ISO_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_JAPANESE_DOUBLE_POSTCARD => [566.93, 419.53], // (200 mm by 148 mm)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_1 => [648.00, 792.00], // (9 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_2 => [720.00, 792.00], // (10 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_3 => [1080.00, 792.00], // (15 in. by 11 in.)
+ PageSetup::PAPERSIZE_INVITE_ENVELOPE => [623.62, 623.62], // (220 mm by 220 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_LEGAL_EXTRA_PAPER => [667.80, 1080.00], // (9.275 in. by 15 in.)
+ PageSetup::PAPERSIZE_TABLOID_EXTRA_PAPER => [841.68, 1296.00], // (11.69 in. by 18 in.)
+ PageSetup::PAPERSIZE_A4_EXTRA_PAPER => [668.98, 912.76], // (236 mm by 322 mm)
+ PageSetup::PAPERSIZE_LETTER_TRANSVERSE_PAPER => [595.80, 792.00], // (8.275 in. by 11 in.)
+ PageSetup::PAPERSIZE_A4_TRANSVERSE_PAPER => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_SUPERA_SUPERA_A4_PAPER => [643.46, 1009.13], // (227 mm by 356 mm)
+ PageSetup::PAPERSIZE_SUPERB_SUPERB_A3_PAPER => [864.57, 1380.47], // (305 mm by 487 mm)
+ PageSetup::PAPERSIZE_LETTER_PLUS_PAPER => [612.00, 913.68], // (8.5 in. by 12.69 in.)
+ PageSetup::PAPERSIZE_A4_PLUS_PAPER => [595.28, 935.43], // (210 mm by 330 mm)
+ PageSetup::PAPERSIZE_A5_TRANSVERSE_PAPER => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_JIS_B5_TRANSVERSE_PAPER => [515.91, 728.50], // (182 mm by 257 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ PageSetup::PAPERSIZE_A5_EXTRA_PAPER => [493.23, 666.14], // (174 mm by 235 mm)
+ PageSetup::PAPERSIZE_ISO_B5_EXTRA_PAPER => [569.76, 782.36], // (201 mm by 276 mm)
+ PageSetup::PAPERSIZE_A2_PAPER => 'A2', // (420 mm by 594 mm)
+ PageSetup::PAPERSIZE_A3_TRANSVERSE_PAPER => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ ];
+
+ /**
+ * Create a new PDF Writer instance.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ parent::__construct($spreadsheet);
+ //$this->setUseInlineCss(true);
+ $this->tempDir = File::sysGetTempDir() . '/phpsppdf';
+ $this->isPdf = true;
+ }
+
+ /**
+ * Get Font.
+ *
+ * @return string
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font. Examples:
+ * 'arialunicid0-chinese-simplified'
+ * 'arialunicid0-chinese-traditional'
+ * 'arialunicid0-korean'
+ * 'arialunicid0-japanese'.
+ *
+ * @param string $fontName
+ *
+ * @return $this
+ */
+ public function setFont($fontName)
+ {
+ $this->font = $fontName;
+
+ return $this;
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param string $pValue Paper size see PageSetup::PAPERSIZE_*
+ *
+ * @return self
+ */
+ public function setPaperSize($pValue)
+ {
+ $this->paperSize = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $pValue Page orientation see PageSetup::ORIENTATION_*
+ *
+ * @return self
+ */
+ public function setOrientation($pValue)
+ {
+ $this->orientation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get temporary storage directory.
+ *
+ * @return string
+ */
+ public function getTempDir()
+ {
+ return $this->tempDir;
+ }
+
+ /**
+ * Set temporary storage directory.
+ *
+ * @param string $pValue Temporary storage directory
+ *
+ * @return self
+ */
+ public function setTempDir($pValue)
+ {
+ if (is_dir($pValue)) {
+ $this->tempDir = $pValue;
+ } else {
+ throw new WriterException("Directory does not exist: $pValue");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Save Spreadsheet to PDF file, pre-save.
+ *
+ * @param string $pFilename Name of the file to save as
+ *
+ * @return resource
+ */
+ protected function prepareForSave($pFilename)
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ return $this->fileHandle;
+ }
+
+ /**
+ * Save PhpSpreadsheet to PDF file, post-save.
+ */
+ protected function restoreStateAfterSave(): void
+ {
+ $this->maybeCloseFileHandle();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php new file mode 100644 index 0000000..721eead --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -0,0 +1,72 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Dompdf extends Pdf
+{
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @return \Dompdf\Dompdf implementation
+ */
+ protected function createExternalWriterInstance()
+ {
+ return new \Dompdf\Dompdf();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if ($this->getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+
+ $orientation = ($orientation == 'L') ? 'landscape' : 'portrait';
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance();
+ $pdf->setPaper(strtolower($paperSize), $orientation);
+
+ $pdf->loadHtml($this->generateHTMLAll());
+ $pdf->render();
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output());
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php new file mode 100644 index 0000000..b0b4c03 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -0,0 +1,106 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Mpdf extends Pdf
+{
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @param array $config Configuration array
+ *
+ * @return \Mpdf\Mpdf implementation
+ */
+ protected function createExternalWriterInstance($config)
+ {
+ return new \Mpdf\Mpdf($config);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if (null === $this->getSheetIndex()) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+ $this->setOrientation($orientation);
+
+ // Override Page Orientation
+ if (null !== $this->getOrientation()) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ $orientation = strtoupper($orientation);
+
+ // Override Paper Size
+ if (null !== $this->getPaperSize()) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $config = ['tempDir' => $this->tempDir . '/mpdf'];
+ $pdf = $this->createExternalWriterInstance($config);
+ $ortmp = $orientation;
+ $pdf->_setPageSize(strtoupper($paperSize), $ortmp);
+ $pdf->DefOrientation = $orientation;
+ $pdf->AddPageByArray([
+ 'orientation' => $orientation,
+ 'margin-left' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getLeft()),
+ 'margin-right' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getRight()),
+ 'margin-top' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getTop()),
+ 'margin-bottom' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getBottom()),
+ ]);
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ $html = $this->generateHTMLAll();
+ foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) {
+ $pdf->WriteHTML(\implode(PHP_EOL, $lines));
+ }
+
+ // Write to file
+ fwrite($fileHandle, $pdf->Output('', 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+
+ /**
+ * Convert inches to mm.
+ *
+ * @param float $inches
+ *
+ * @return float
+ */
+ private function inchesToMm($inches)
+ {
+ return $inches * 25.4;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php new file mode 100644 index 0000000..1ebae0e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -0,0 +1,104 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Tcpdf extends Pdf
+{
+ /**
+ * Create a new PDF Writer instance.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ parent::__construct($spreadsheet);
+ $this->setUseInlineCss(true);
+ }
+
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @param string $orientation Page orientation
+ * @param string $unit Unit measure
+ * @param string $paperSize Paper size
+ *
+ * @return \TCPDF implementation
+ */
+ protected function createExternalWriterInstance($orientation, $unit, $paperSize)
+ {
+ return new \TCPDF($orientation, $unit, $paperSize);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if ($this->getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet(0)->getPageMargins();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageMargins();
+ }
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
+ ? 'L'
+ : 'P';
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize);
+ $pdf->setFontSubsetting(false);
+ // Set margins, converting inches to points (using 72 dpi)
+ $pdf->SetMargins($printMargins->getLeft() * 72, $printMargins->getTop() * 72, $printMargins->getRight() * 72);
+ $pdf->SetAutoPageBreak(true, $printMargins->getBottom() * 72);
+
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ $pdf->AddPage();
+
+ // Set the appropriate font
+ $pdf->SetFont($this->getFont());
+ $pdf->writeHTML($this->generateHTMLAll());
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output($pFilename, 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php new file mode 100644 index 0000000..ceadc8b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php @@ -0,0 +1,901 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
+use PhpOffice\PhpSpreadsheet\Shared\Escher;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+
+class Xls extends BaseWriter
+{
+ /**
+ * PhpSpreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Total number of shared strings in workbook.
+ *
+ * @var int
+ */
+ private $strTotal = 0;
+
+ /**
+ * Number of unique shared strings in workbook.
+ *
+ * @var int
+ */
+ private $strUnique = 0;
+
+ /**
+ * Array of unique shared strings in workbook.
+ *
+ * @var array
+ */
+ private $strTable = [];
+
+ /**
+ * Color cache. Mapping between RGB value and color index.
+ *
+ * @var array
+ */
+ private $colors;
+
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
+ *
+ * @var array
+ */
+ private $IDCLs;
+
+ /**
+ * Basic OLE object summary information.
+ *
+ * @var array
+ */
+ private $summaryInformation;
+
+ /**
+ * Extended OLE object document summary information.
+ *
+ * @var array
+ */
+ private $documentSummaryInformation;
+
+ /**
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook
+ */
+ private $writerWorkbook;
+
+ /**
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[]
+ */
+ private $writerWorksheets;
+
+ /**
+ * Create a new Xls Writer.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+
+ $this->parser = new Xls\Parser($spreadsheet);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // initialize colors array
+ $this->colors = [];
+
+ // Initialise workbook writer
+ $this->writerWorkbook = new Xls\Workbook($this->spreadsheet, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser);
+
+ // Initialise worksheet writers
+ $countSheets = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i] = new Xls\Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->spreadsheet->getSheet($i));
+ }
+
+ // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
+ $this->buildWorksheetEschers();
+ $this->buildWorkbookEscher();
+
+ // add 15 identical cell style Xfs
+ // for now, we use the first cellXf instead of cellStyleXf
+ $cellXfCollection = $this->spreadsheet->getCellXfCollection();
+ for ($i = 0; $i < 15; ++$i) {
+ $this->writerWorkbook->addXfWriter($cellXfCollection[0], true);
+ }
+
+ // add all the cell Xfs
+ foreach ($this->spreadsheet->getCellXfCollection() as $style) {
+ $this->writerWorkbook->addXfWriter($style, false);
+ }
+
+ // add fonts from rich text eleemnts
+ for ($i = 0; $i < $countSheets; ++$i) {
+ foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) {
+ $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate);
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ if ($element instanceof Run) {
+ $font = $element->getFont();
+ $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font);
+ }
+ }
+ }
+ }
+ }
+
+ // initialize OLE file
+ $workbookStreamName = 'Workbook';
+ $OLE = new File(OLE::ascToUcs($workbookStreamName));
+
+ // Write the worksheet streams before the global workbook stream,
+ // because the byte sizes of these are needed in the global workbook stream
+ $worksheetSizes = [];
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i]->close();
+ $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize;
+ }
+
+ // add binary data for global workbook stream
+ $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes));
+
+ // add binary data for sheet streams
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $OLE->append($this->writerWorksheets[$i]->getData());
+ }
+
+ $this->documentSummaryInformation = $this->writeDocumentSummaryInformation();
+ // initialize OLE Document Summary Information
+ if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) {
+ $OLE_DocumentSummaryInformation = new File(OLE::ascToUcs(chr(5) . 'DocumentSummaryInformation'));
+ $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation);
+ }
+
+ $this->summaryInformation = $this->writeSummaryInformation();
+ // initialize OLE Summary Information
+ if (isset($this->summaryInformation) && !empty($this->summaryInformation)) {
+ $OLE_SummaryInformation = new File(OLE::ascToUcs(chr(5) . 'SummaryInformation'));
+ $OLE_SummaryInformation->append($this->summaryInformation);
+ }
+
+ // define OLE Parts
+ $arrRootData = [$OLE];
+ // initialize OLE Properties file
+ if (isset($OLE_SummaryInformation)) {
+ $arrRootData[] = $OLE_SummaryInformation;
+ }
+ // initialize OLE Extended Properties file
+ if (isset($OLE_DocumentSummaryInformation)) {
+ $arrRootData[] = $OLE_DocumentSummaryInformation;
+ }
+
+ $root = new Root(time(), time(), $arrRootData);
+ // save the OLE file
+ $this->openFileHandle($pFilename);
+ $root->save($this->fileHandle);
+ $this->maybeCloseFileHandle();
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Build the Worksheet Escher objects.
+ */
+ private function buildWorksheetEschers(): void
+ {
+ // 1-based index to BstoreContainer
+ $blipIndex = 0;
+ $lastReducedSpId = 0;
+ $lastSpId = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ // sheet index
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ $escher = null;
+
+ // check if there are any shapes for this sheet
+ $filterRange = $sheet->getAutoFilter()->getRange();
+ if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
+ continue;
+ }
+
+ // create intermediate Escher object
+ $escher = new Escher();
+
+ // dgContainer
+ $dgContainer = new DgContainer();
+
+ // set the drawing index (we use sheet index + 1)
+ $dgId = $sheet->getParent()->getIndex($sheet) + 1;
+ $dgContainer->setDgId($dgId);
+ $escher->setDgContainer($dgContainer);
+
+ // spgrContainer
+ $spgrContainer = new SpgrContainer();
+ $dgContainer->setSpgrContainer($spgrContainer);
+
+ // add one shape which is the group shape
+ $spContainer = new SpContainer();
+ $spContainer->setSpgr(true);
+ $spContainer->setSpType(0);
+ $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
+ $spgrContainer->addChild($spContainer);
+
+ // add the shapes
+
+ $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
+
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ ++$blipIndex;
+
+ ++$countShapes[$sheetIndex];
+
+ // add the shape
+ $spContainer = new SpContainer();
+
+ // set the shape type
+ $spContainer->setSpType(0x004B);
+ // set the shape flag
+ $spContainer->setSpFlag(0x02);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ // set the BLIP index
+ $spContainer->setOPT(0x4104, $blipIndex);
+
+ // set coordinates and offsets, client anchor
+ $coordinates = $drawing->getCoordinates();
+ $offsetX = $drawing->getOffsetX();
+ $offsetY = $drawing->getOffsetY();
+ $width = $drawing->getWidth();
+ $height = $drawing->getHeight();
+
+ $twoAnchor = \PhpOffice\PhpSpreadsheet\Shared\Xls::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
+
+ $spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
+ $spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
+ $spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
+ $spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
+ $spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
+ $spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
+
+ $spgrContainer->addChild($spContainer);
+ }
+
+ // AutoFilters
+ if (!empty($filterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($filterRange);
+ $iNumColStart = $rangeBounds[0][0];
+ $iNumColEnd = $rangeBounds[1][0];
+
+ $iInc = $iNumColStart;
+ while ($iInc <= $iNumColEnd) {
+ ++$countShapes[$sheetIndex];
+
+ // create an Drawing Object for the dropdown
+ $oDrawing = new BaseDrawing();
+ // get the coordinates of drawing
+ $cDrawing = Coordinate::stringFromColumnIndex($iInc) . $rangeBounds[0][1];
+ $oDrawing->setCoordinates($cDrawing);
+ $oDrawing->setWorksheet($sheet);
+
+ // add the shape
+ $spContainer = new SpContainer();
+ // set the shape type
+ $spContainer->setSpType(0x00C9);
+ // set the shape flag
+ $spContainer->setSpFlag(0x01);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
+ $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
+ $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
+ $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
+ $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
+
+ // set coordinates and offsets, client anchor
+ $endCoordinates = Coordinate::stringFromColumnIndex($iInc);
+ $endCoordinates .= $rangeBounds[0][1] + 1;
+
+ $spContainer->setStartCoordinates($cDrawing);
+ $spContainer->setStartOffsetX(0);
+ $spContainer->setStartOffsetY(0);
+ $spContainer->setEndCoordinates($endCoordinates);
+ $spContainer->setEndOffsetX(0);
+ $spContainer->setEndOffsetY(0);
+
+ $spgrContainer->addChild($spContainer);
+ ++$iInc;
+ }
+ }
+
+ // identifier clusters, used for workbook Escher object
+ $this->IDCLs[$dgId] = $lastReducedSpId;
+
+ // set last shape index
+ $dgContainer->setLastSpId($lastSpId);
+
+ // set the Escher object
+ $this->writerWorksheets[$sheetIndex]->setEscher($escher);
+ }
+ }
+
+ private function processMemoryDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing, string $renderingFunctionx): void
+ {
+ switch ($renderingFunctionx) {
+ case MemoryDrawing::RENDERING_JPEG:
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $renderingFunction = 'imagejpeg';
+
+ break;
+ default:
+ $blipType = BSE::BLIPTYPE_PNG;
+ $renderingFunction = 'imagepng';
+
+ break;
+ }
+
+ ob_start();
+ call_user_func($renderingFunction, $drawing->getImageResource());
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ }
+
+ private function processDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void
+ {
+ $blipData = '';
+ $filename = $drawing->getPath();
+
+ [$imagesx, $imagesy, $imageFormat] = getimagesize($filename);
+
+ switch ($imageFormat) {
+ case 1: // GIF, not supported by BIFF8, we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(imagecreatefromgif($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ case 2: // JPEG
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 3: // PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 6: // Windows DIB (BMP), we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(SharedDrawing::imagecreatefrombmp($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ }
+ if ($blipData) {
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ }
+ }
+
+ private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void
+ {
+ if ($drawing instanceof Drawing) {
+ $this->processDrawing($bstoreContainer, $drawing);
+ } elseif ($drawing instanceof MemoryDrawing) {
+ $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction());
+ }
+ }
+
+ private function checkForDrawings(): bool
+ {
+ // any drawings in this workbook?
+ $found = false;
+ foreach ($this->spreadsheet->getAllSheets() as $sheet) {
+ if (count($sheet->getDrawingCollection()) > 0) {
+ $found = true;
+
+ break;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Build the Escher object corresponding to the MSODRAWINGGROUP record.
+ */
+ private function buildWorkbookEscher(): void
+ {
+ // nothing to do if there are no drawings
+ if (!$this->checkForDrawings()) {
+ return;
+ }
+
+ // if we reach here, then there are drawings in the workbook
+ $escher = new Escher();
+
+ // dggContainer
+ $dggContainer = new DggContainer();
+ $escher->setDggContainer($dggContainer);
+
+ // set IDCLs (identifier clusters)
+ $dggContainer->setIDCLs($this->IDCLs);
+
+ // this loop is for determining maximum shape identifier of all drawing
+ $spIdMax = 0;
+ $totalCountShapes = 0;
+ $countDrawings = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
+
+ $addCount = 0;
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ $addCount = 1;
+ ++$sheetCountShapes;
+ ++$totalCountShapes;
+
+ $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10;
+ $spIdMax = max($spId, $spIdMax);
+ }
+ $countDrawings += $addCount;
+ }
+
+ $dggContainer->setSpIdMax($spIdMax + 1);
+ $dggContainer->setCDgSaved($countDrawings);
+ $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
+
+ // bstoreContainer
+ $bstoreContainer = new BstoreContainer();
+ $dggContainer->setBstoreContainer($bstoreContainer);
+
+ // the BSE's (all the images)
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ $this->processBaseDrawing($bstoreContainer, $drawing);
+ }
+ }
+
+ // Set the Escher object
+ $this->writerWorkbook->setEscher($escher);
+ }
+
+ /**
+ * Build the OLE Part for DocumentSummary Information.
+ *
+ * @return string
+ */
+ private function writeDocumentSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
+ $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // GKPIDDSI_CODEPAGE: CodePage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_CATEGORY : Category
+ $dataProp = $this->spreadsheet->getProperties()->getCategory();
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x02],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // GKPIDDSI_VERSION :Version of the application that wrote the property storage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x17],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03],
+ 'data' => ['pack' => 'V', 'data' => 0x000C0000],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SCALE : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0B],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x10],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SHAREDOC : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x16],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_DOCSPARTS
+ // MS-OSHARED p75 (2.3.3.2.2.1)
+ // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
+ // cElements
+ $dataProp = pack('v', 0x0001);
+ $dataProp .= pack('v', 0x0000);
+ // array of UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x000A);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Worksheet' . chr(0);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0D],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x101E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_HEADINGPAIR
+ // VtVecHeadingPairValue
+ // cElements
+ $dataProp = pack('v', 0x0002);
+ $dataProp .= pack('v', 0x0000);
+ // Array of vtHeadingPair
+ // vtUnalignedString - headingString
+ // stringType
+ $dataProp .= pack('v', 0x001E);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x0013);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Feuilles de calcul';
+ // vtUnalignedString - headingParts
+ // wType : 0x0003 = 32 bit signed integer
+ $dataProp .= pack('v', 0x0300);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= pack('v', 0x0100);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0C],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x100C],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean
+ $dataSection_Content .= pack('V', (int) $dataProp['data']['data']);
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ // Condition below can never be true
+ //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ // $dataSection_Content .= $dataProp['data']['data'];
+
+ // $dataSection_Content_Offset += 4 + 8;
+ } else {
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + $dataProp['data']['length'];
+ }
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+
+ private function writeSummaryPropOle(int $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void
+ {
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => $sumdata],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length
+ 'data' => ['data' => OLE::localDateToOLE($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ }
+
+ private function writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void
+ {
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => $sumdata],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ }
+
+ /**
+ * Build the OLE Part for Summary Information.
+ *
+ * @return string
+ */
+ private function writeSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
+ $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // CodePage : CP-1252
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ $props = $this->spreadsheet->getProperties();
+ $this->writeSummaryProp($props->getTitle(), $dataSection_NumProps, $dataSection, 0x02, 0x1e);
+ $this->writeSummaryProp($props->getSubject(), $dataSection_NumProps, $dataSection, 0x03, 0x1e);
+ $this->writeSummaryProp($props->getCreator(), $dataSection_NumProps, $dataSection, 0x04, 0x1e);
+ $this->writeSummaryProp($props->getKeywords(), $dataSection_NumProps, $dataSection, 0x05, 0x1e);
+ $this->writeSummaryProp($props->getDescription(), $dataSection_NumProps, $dataSection, 0x06, 0x1e);
+ $this->writeSummaryProp($props->getLastModifiedBy(), $dataSection_NumProps, $dataSection, 0x08, 0x1e);
+ $this->writeSummaryPropOle($props->getCreated(), $dataSection_NumProps, $dataSection, 0x0c, 0x40);
+ $this->writeSummaryPropOle($props->getModified(), $dataSection_NumProps, $dataSection, 0x0d, 0x40);
+
+ // Security
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03], // 4 byte signed integer
+ 'data' => ['data' => 0x00],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 8;
+ }
+ // Data Type Not Used at the moment
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php new file mode 100644 index 0000000..f989a0e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php @@ -0,0 +1,224 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_BIFFwriter (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class BIFFwriter
+{
+ /**
+ * The byte order of this architecture. 0 => little endian, 1 => big endian.
+ *
+ * @var int
+ */
+ private static $byteOrder;
+
+ /**
+ * The string containing the data of the BIFF stream.
+ *
+ * @var string
+ */
+ public $_data;
+
+ /**
+ * The size of the data in bytes. Should be the same as strlen($this->_data).
+ *
+ * @var int
+ */
+ public $_datasize;
+
+ /**
+ * The maximum length for a BIFF record (excluding record header and length field). See addContinue().
+ *
+ * @var int
+ *
+ * @see addContinue()
+ */
+ private $limit = 8224;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_data = '';
+ $this->_datasize = 0;
+ }
+
+ /**
+ * Determine the byte order and store it as class data to avoid
+ * recalculating it for each call to new().
+ *
+ * @return int
+ */
+ public static function getByteOrder()
+ {
+ if (!isset(self::$byteOrder)) {
+ // Check if "pack" gives the required IEEE 64bit float
+ $teststr = pack('d', 1.2345);
+ $number = pack('C8', 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
+ if ($number == $teststr) {
+ $byte_order = 0; // Little Endian
+ } elseif ($number == strrev($teststr)) {
+ $byte_order = 1; // Big Endian
+ } else {
+ // Give up. I'll fix this in a later version.
+ throw new WriterException('Required floating point format not supported on this platform.');
+ }
+ self::$byteOrder = $byte_order;
+ }
+
+ return self::$byteOrder;
+ }
+
+ /**
+ * General storage function.
+ *
+ * @param string $data binary data to append
+ */
+ protected function append($data): void
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_data .= $data;
+ $this->_datasize += strlen($data);
+ }
+
+ /**
+ * General storage function like append, but returns string instead of modifying $this->_data.
+ *
+ * @param string $data binary data to write
+ *
+ * @return string
+ */
+ public function writeData($data)
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_datasize += strlen($data);
+
+ return $data;
+ }
+
+ /**
+ * Writes Excel BOF record to indicate the beginning of a stream or
+ * sub-stream in the BIFF file.
+ *
+ * @param int $type type of BIFF file to write: 0x0005 Workbook,
+ * 0x0010 Worksheet
+ */
+ protected function storeBof($type): void
+ {
+ $record = 0x0809; // Record identifier (BIFF5-BIFF8)
+ $length = 0x0010;
+
+ // by inspection of real files, MS Office Excel 2007 writes the following
+ $unknown = pack('VV', 0x000100D1, 0x00000406);
+
+ $build = 0x0DBB; // Excel 97
+ $year = 0x07CC; // Excel 97
+
+ $version = 0x0600; // BIFF8
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $version, $type, $build, $year);
+ $this->append($header . $data . $unknown);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ protected function storeEof(): void
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $this->append($header);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ public function writeEof()
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header);
+ }
+
+ /**
+ * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
+ * Excel 97 the limit is 8228 bytes. Records that are longer than these limits
+ * must be split up into CONTINUE blocks.
+ *
+ * This function takes a long BIFF record and inserts CONTINUE records as
+ * necessary.
+ *
+ * @param string $data The original binary data to be written
+ *
+ * @return string A very convenient string of continue blocks
+ */
+ private function addContinue($data)
+ {
+ $limit = $this->limit;
+ $record = 0x003C; // Record identifier
+
+ // The first 2080/8224 bytes remain intact. However, we have to change
+ // the length field of the record.
+ $tmp = substr($data, 0, 2) . pack('v', $limit) . substr($data, 4, $limit);
+
+ $header = pack('vv', $record, $limit); // Headers for continue records
+
+ // Retrieve chunks of 2080/8224 bytes +4 for the header.
+ $data_length = strlen($data);
+ for ($i = $limit + 4; $i < ($data_length - $limit); $i += $limit) {
+ $tmp .= $header;
+ $tmp .= substr($data, $i, $limit);
+ }
+
+ // Retrieve the last chunk of data
+ $header = pack('vv', $record, strlen($data) - $i);
+ $tmp .= $header;
+ $tmp .= substr($data, $i);
+
+ return $tmp;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php new file mode 100644 index 0000000..f8af656 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php @@ -0,0 +1,510 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
+
+class Escher
+{
+ /**
+ * The object we are writing.
+ */
+ private $object;
+
+ /**
+ * The written binary data.
+ */
+ private $data;
+
+ /**
+ * Shape offsets. Positions in binary stream where a new shape record begins.
+ *
+ * @var array
+ */
+ private $spOffsets;
+
+ /**
+ * Shape types.
+ *
+ * @var array
+ */
+ private $spTypes;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $object
+ */
+ public function __construct($object)
+ {
+ $this->object = $object;
+ }
+
+ /**
+ * Process the object to be written.
+ *
+ * @return string
+ */
+ public function close()
+ {
+ // initialize
+ $this->data = '';
+
+ switch (get_class($this->object)) {
+ case \PhpOffice\PhpSpreadsheet\Shared\Escher::class:
+ if ($dggContainer = $this->object->getDggContainer()) {
+ $writer = new self($dggContainer);
+ $this->data = $writer->close();
+ } elseif ($dgContainer = $this->object->getDgContainer()) {
+ $writer = new self($dgContainer);
+ $this->data = $writer->close();
+ $this->spOffsets = $writer->getSpOffsets();
+ $this->spTypes = $writer->getSpTypes();
+ }
+
+ break;
+ case DggContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dgg
+ $recVer = 0x0;
+ $recInstance = 0x0000;
+ $recType = 0xF006;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ // dgg data
+ $dggData =
+ pack(
+ 'VVVV',
+ $this->object->getSpIdMax(), // maximum shape identifier increased by one
+ $this->object->getCDgSaved() + 1, // number of file identifier clusters increased by one
+ $this->object->getCSpSaved(),
+ $this->object->getCDgSaved() // count total number of drawings saved
+ );
+
+ // add file identifier clusters (one per drawing)
+ $IDCLs = $this->object->getIDCLs();
+
+ foreach ($IDCLs as $dgId => $maxReducedSpId) {
+ $dggData .= pack('VV', $dgId, $maxReducedSpId + 1);
+ }
+
+ $header = pack('vvV', $recVerInstance, $recType, strlen($dggData));
+ $innerData .= $header . $dggData;
+
+ // write the bstoreContainer
+ if ($bstoreContainer = $this->object->getBstoreContainer()) {
+ $writer = new self($bstoreContainer);
+ $innerData .= $writer->close();
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF000;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BstoreContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // treat the inner data
+ if ($BSECollection = $this->object->getBSECollection()) {
+ foreach ($BSECollection as $BSE) {
+ $writer = new self($BSE);
+ $innerData .= $writer->close();
+ }
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = count($this->object->getBSECollection());
+ $recType = 0xF001;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BSE::class:
+ // this is a semi-container record
+
+ // initialize
+ $innerData = '';
+
+ // here we treat the inner data
+ if ($blip = $this->object->getBlip()) {
+ $writer = new self($blip);
+ $innerData .= $writer->close();
+ }
+
+ // initialize
+ $data = '';
+
+ $btWin32 = $this->object->getBlipType();
+ $btMacOS = $this->object->getBlipType();
+ $data .= pack('CC', $btWin32, $btMacOS);
+
+ $rgbUid = pack('VVVV', 0, 0, 0, 0); // todo
+ $data .= $rgbUid;
+
+ $tag = 0;
+ $size = strlen($innerData);
+ $cRef = 1;
+ $foDelay = 0; //todo
+ $unused1 = 0x0;
+ $cbName = 0x0;
+ $unused2 = 0x0;
+ $unused3 = 0x0;
+ $data .= pack('vVVVCCCC', $tag, $size, $cRef, $foDelay, $unused1, $cbName, $unused2, $unused3);
+
+ $data .= $innerData;
+
+ // write the record
+ $recVer = 0x2;
+ $recInstance = $this->object->getBlipType();
+ $recType = 0xF007;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $data;
+
+ break;
+ case Blip::class:
+ // this is an atom record
+
+ // write the record
+ switch ($this->object->getParent()->getBlipType()) {
+ case BSE::BLIPTYPE_JPEG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x46A;
+ $recType = 0xF01D;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ case BSE::BLIPTYPE_PNG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x6E0;
+ $recType = 0xF01E;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ }
+
+ break;
+ case DgContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dg
+ $recVer = 0x0;
+ $recInstance = $this->object->getDgId();
+ $recType = 0xF008;
+ $length = 8;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ // number of shapes in this drawing (including group shape)
+ $countShapes = count($this->object->getSpgrContainer()->getChildren());
+ $innerData .= $header . pack('VV', $countShapes, $this->object->getLastSpId());
+
+ // write the spgrContainer
+ if ($spgrContainer = $this->object->getSpgrContainer()) {
+ $writer = new self($spgrContainer);
+ $innerData .= $writer->close();
+
+ // get the shape offsets relative to the spgrContainer record
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+
+ // save the shape offsets relative to dgContainer
+ foreach ($spOffsets as &$spOffset) {
+ $spOffset += 24; // add length of dgContainer header data (8 bytes) plus dg data (16 bytes)
+ }
+
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF002;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case SpgrContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // initialize spape offsets
+ $totalSize = 8;
+ $spOffsets = [];
+ $spTypes = [];
+
+ // treat the inner data
+ foreach ($this->object->getChildren() as $spContainer) {
+ $writer = new self($spContainer);
+ $spData = $writer->close();
+ $innerData .= $spData;
+
+ // save the shape offsets (where new shape records begin)
+ $totalSize += strlen($spData);
+ $spOffsets[] = $totalSize;
+
+ $spTypes = array_merge($spTypes, $writer->getSpTypes());
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF003;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+
+ break;
+ case SpContainer::class:
+ // initialize
+ $data = '';
+
+ // build the data
+
+ // write group shape record, if necessary?
+ if ($this->object->getSpgr()) {
+ $recVer = 0x1;
+ $recInstance = 0x0000;
+ $recType = 0xF009;
+ $length = 0x00000010;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VVVV', 0, 0, 0, 0);
+ }
+ $this->spTypes[] = ($this->object->getSpType());
+
+ // write the shape record
+ $recVer = 0x2;
+ $recInstance = $this->object->getSpType(); // shape type
+ $recType = 0xF00A;
+ $length = 0x00000008;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VV', $this->object->getSpId(), $this->object->getSpgr() ? 0x0005 : 0x0A00);
+
+ // the options
+ if ($this->object->getOPTCollection()) {
+ $optData = '';
+
+ $recVer = 0x3;
+ $recInstance = count($this->object->getOPTCollection());
+ $recType = 0xF00B;
+ foreach ($this->object->getOPTCollection() as $property => $value) {
+ $optData .= pack('vV', $property, $value);
+ }
+ $length = strlen($optData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $optData;
+ }
+
+ // the client anchor
+ if ($this->object->getStartCoordinates()) {
+ $clientAnchorData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF010;
+
+ // start coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getStartCoordinates());
+ $c1 = Coordinate::columnIndexFromString($column) - 1;
+ $r1 = $row - 1;
+
+ // start offsetX
+ $startOffsetX = $this->object->getStartOffsetX();
+
+ // start offsetY
+ $startOffsetY = $this->object->getStartOffsetY();
+
+ // end coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getEndCoordinates());
+ $c2 = Coordinate::columnIndexFromString($column) - 1;
+ $r2 = $row - 1;
+
+ // end offsetX
+ $endOffsetX = $this->object->getEndOffsetX();
+
+ // end offsetY
+ $endOffsetY = $this->object->getEndOffsetY();
+
+ $clientAnchorData = pack('vvvvvvvvv', $this->object->getSpFlag(), $c1, $startOffsetX, $r1, $startOffsetY, $c2, $endOffsetX, $r2, $endOffsetY);
+
+ $length = strlen($clientAnchorData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientAnchorData;
+ }
+
+ // the client data, just empty for now
+ if (!$this->object->getSpgr()) {
+ $clientDataData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF011;
+
+ $length = strlen($clientDataData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientDataData;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF004;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $data;
+
+ break;
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Gets the shape offsets.
+ *
+ * @return array
+ */
+ public function getSpOffsets()
+ {
+ return $this->spOffsets;
+ }
+
+ /**
+ * Gets the shape types.
+ *
+ * @return array
+ */
+ public function getSpTypes()
+ {
+ return $this->spTypes;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php new file mode 100644 index 0000000..501f03b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php @@ -0,0 +1,147 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class Font
+{
+ /**
+ * Color index.
+ *
+ * @var int
+ */
+ private $colorIndex;
+
+ /**
+ * Font.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Style\Font
+ */
+ private $font;
+
+ /**
+ * Constructor.
+ */
+ public function __construct(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ $this->colorIndex = 0x7FFF;
+ $this->font = $font;
+ }
+
+ /**
+ * Set the color index.
+ *
+ * @param int $colorIndex
+ */
+ public function setColorIndex($colorIndex): void
+ {
+ $this->colorIndex = $colorIndex;
+ }
+
+ /**
+ * Get font record data.
+ *
+ * @return string
+ */
+ public function writeFont()
+ {
+ $font_outline = 0;
+ $font_shadow = 0;
+
+ $icv = $this->colorIndex; // Index to color palette
+ if ($this->font->getSuperscript()) {
+ $sss = 1;
+ } elseif ($this->font->getSubscript()) {
+ $sss = 2;
+ } else {
+ $sss = 0;
+ }
+ $bFamily = 0; // Font family
+ $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName($this->font->getName()); // Character set
+
+ $record = 0x31; // Record identifier
+ $reserved = 0x00; // Reserved
+ $grbit = 0x00; // Font attributes
+ if ($this->font->getItalic()) {
+ $grbit |= 0x02;
+ }
+ if ($this->font->getStrikethrough()) {
+ $grbit |= 0x08;
+ }
+ if ($font_outline) {
+ $grbit |= 0x10;
+ }
+ if ($font_shadow) {
+ $grbit |= 0x20;
+ }
+
+ $data = pack(
+ 'vvvvvCCCC',
+ // Fontsize (in twips)
+ $this->font->getSize() * 20,
+ $grbit,
+ // Colour
+ $icv,
+ // Font weight
+ self::mapBold($this->font->getBold()),
+ // Superscript/Subscript
+ $sss,
+ self::mapUnderline($this->font->getUnderline()),
+ $bFamily,
+ $bCharSet,
+ $reserved
+ );
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($this->font->getName());
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Map to BIFF5-BIFF8 codes for bold.
+ *
+ * @param bool $bold
+ *
+ * @return int
+ */
+ private static function mapBold($bold)
+ {
+ if ($bold) {
+ return 0x2BC; // 700 = Bold font weight
+ }
+
+ return 0x190; // 400 = Normal font weight
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for underline styles.
+ *
+ * @var array of int
+ */
+ private static $mapUnderline = [
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE => 0x01,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE => 0x02,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING => 0x21,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING => 0x22,
+ ];
+
+ /**
+ * Map underline.
+ *
+ * @param string $underline
+ *
+ * @return int
+ */
+ private static function mapUnderline($underline)
+ {
+ if (isset(self::$mapUnderline[$underline])) {
+ return self::$mapUnderline[$underline];
+ }
+
+ return 0x00;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php new file mode 100644 index 0000000..b5a3758 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -0,0 +1,1483 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Parser (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// * Class for parsing Excel formulas
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Parser
+{
+ /** Constants */
+ // Sheet title in unquoted form
+ // Invalid sheet title characters cannot occur in the sheet title:
+ // *:/\?[]
+ // Moreover, there are valid sheet title characters that cannot occur in unquoted form (there may be more?)
+ // +-% '^&<>=,;#()"{}
+ const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+';
+
+ // Sheet title in quoted form (without surrounding quotes)
+ // Invalid sheet title characters cannot occur in the sheet title:
+ // *:/\?[] (usual invalid sheet title characters)
+ // Single quote is represented as a pair ''
+ const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+';
+
+ /**
+ * The index of the character we are currently looking at.
+ *
+ * @var int
+ */
+ public $currentCharacter;
+
+ /**
+ * The token we are working on.
+ *
+ * @var string
+ */
+ public $currentToken;
+
+ /**
+ * The formula to parse.
+ *
+ * @var string
+ */
+ private $formula;
+
+ /**
+ * The character ahead of the current char.
+ *
+ * @var string
+ */
+ public $lookAhead;
+
+ /**
+ * The parse tree to be generated.
+ *
+ * @var string
+ */
+ public $parseTree;
+
+ /**
+ * Array of external sheets.
+ *
+ * @var array
+ */
+ private $externalSheets;
+
+ /**
+ * Array of sheet references in the form of REF structures.
+ *
+ * @var array
+ */
+ public $references;
+
+ /**
+ * The Excel ptg indices.
+ *
+ * @var array
+ */
+ private $ptg = [
+ 'ptgExp' => 0x01,
+ 'ptgTbl' => 0x02,
+ 'ptgAdd' => 0x03,
+ 'ptgSub' => 0x04,
+ 'ptgMul' => 0x05,
+ 'ptgDiv' => 0x06,
+ 'ptgPower' => 0x07,
+ 'ptgConcat' => 0x08,
+ 'ptgLT' => 0x09,
+ 'ptgLE' => 0x0A,
+ 'ptgEQ' => 0x0B,
+ 'ptgGE' => 0x0C,
+ 'ptgGT' => 0x0D,
+ 'ptgNE' => 0x0E,
+ 'ptgIsect' => 0x0F,
+ 'ptgUnion' => 0x10,
+ 'ptgRange' => 0x11,
+ 'ptgUplus' => 0x12,
+ 'ptgUminus' => 0x13,
+ 'ptgPercent' => 0x14,
+ 'ptgParen' => 0x15,
+ 'ptgMissArg' => 0x16,
+ 'ptgStr' => 0x17,
+ 'ptgAttr' => 0x19,
+ 'ptgSheet' => 0x1A,
+ 'ptgEndSheet' => 0x1B,
+ 'ptgErr' => 0x1C,
+ 'ptgBool' => 0x1D,
+ 'ptgInt' => 0x1E,
+ 'ptgNum' => 0x1F,
+ 'ptgArray' => 0x20,
+ 'ptgFunc' => 0x21,
+ 'ptgFuncVar' => 0x22,
+ 'ptgName' => 0x23,
+ 'ptgRef' => 0x24,
+ 'ptgArea' => 0x25,
+ 'ptgMemArea' => 0x26,
+ 'ptgMemErr' => 0x27,
+ 'ptgMemNoMem' => 0x28,
+ 'ptgMemFunc' => 0x29,
+ 'ptgRefErr' => 0x2A,
+ 'ptgAreaErr' => 0x2B,
+ 'ptgRefN' => 0x2C,
+ 'ptgAreaN' => 0x2D,
+ 'ptgMemAreaN' => 0x2E,
+ 'ptgMemNoMemN' => 0x2F,
+ 'ptgNameX' => 0x39,
+ 'ptgRef3d' => 0x3A,
+ 'ptgArea3d' => 0x3B,
+ 'ptgRefErr3d' => 0x3C,
+ 'ptgAreaErr3d' => 0x3D,
+ 'ptgArrayV' => 0x40,
+ 'ptgFuncV' => 0x41,
+ 'ptgFuncVarV' => 0x42,
+ 'ptgNameV' => 0x43,
+ 'ptgRefV' => 0x44,
+ 'ptgAreaV' => 0x45,
+ 'ptgMemAreaV' => 0x46,
+ 'ptgMemErrV' => 0x47,
+ 'ptgMemNoMemV' => 0x48,
+ 'ptgMemFuncV' => 0x49,
+ 'ptgRefErrV' => 0x4A,
+ 'ptgAreaErrV' => 0x4B,
+ 'ptgRefNV' => 0x4C,
+ 'ptgAreaNV' => 0x4D,
+ 'ptgMemAreaNV' => 0x4E,
+ 'ptgMemNoMemNV' => 0x4F,
+ 'ptgFuncCEV' => 0x58,
+ 'ptgNameXV' => 0x59,
+ 'ptgRef3dV' => 0x5A,
+ 'ptgArea3dV' => 0x5B,
+ 'ptgRefErr3dV' => 0x5C,
+ 'ptgAreaErr3dV' => 0x5D,
+ 'ptgArrayA' => 0x60,
+ 'ptgFuncA' => 0x61,
+ 'ptgFuncVarA' => 0x62,
+ 'ptgNameA' => 0x63,
+ 'ptgRefA' => 0x64,
+ 'ptgAreaA' => 0x65,
+ 'ptgMemAreaA' => 0x66,
+ 'ptgMemErrA' => 0x67,
+ 'ptgMemNoMemA' => 0x68,
+ 'ptgMemFuncA' => 0x69,
+ 'ptgRefErrA' => 0x6A,
+ 'ptgAreaErrA' => 0x6B,
+ 'ptgRefNA' => 0x6C,
+ 'ptgAreaNA' => 0x6D,
+ 'ptgMemAreaNA' => 0x6E,
+ 'ptgMemNoMemNA' => 0x6F,
+ 'ptgFuncCEA' => 0x78,
+ 'ptgNameXA' => 0x79,
+ 'ptgRef3dA' => 0x7A,
+ 'ptgArea3dA' => 0x7B,
+ 'ptgRefErr3dA' => 0x7C,
+ 'ptgAreaErr3dA' => 0x7D,
+ ];
+
+ /**
+ * Thanks to Michael Meeks and Gnumeric for the initial arg values.
+ *
+ * The following hash was generated by "function_locale.pl" in the distro.
+ * Refer to function_locale.pl for non-English function names.
+ *
+ * The array elements are as follow:
+ * ptg: The Excel function ptg code.
+ * args: The number of arguments that the function takes:
+ * >=0 is a fixed number of arguments.
+ * -1 is a variable number of arguments.
+ * class: The reference, value or array class of the function args.
+ * vol: The function is volatile.
+ *
+ * @var array
+ */
+ private $functions = [
+ // function ptg args class vol
+ 'COUNT' => [0, -1, 0, 0],
+ 'IF' => [1, -1, 1, 0],
+ 'ISNA' => [2, 1, 1, 0],
+ 'ISERROR' => [3, 1, 1, 0],
+ 'SUM' => [4, -1, 0, 0],
+ 'AVERAGE' => [5, -1, 0, 0],
+ 'MIN' => [6, -1, 0, 0],
+ 'MAX' => [7, -1, 0, 0],
+ 'ROW' => [8, -1, 0, 0],
+ 'COLUMN' => [9, -1, 0, 0],
+ 'NA' => [10, 0, 0, 0],
+ 'NPV' => [11, -1, 1, 0],
+ 'STDEV' => [12, -1, 0, 0],
+ 'DOLLAR' => [13, -1, 1, 0],
+ 'FIXED' => [14, -1, 1, 0],
+ 'SIN' => [15, 1, 1, 0],
+ 'COS' => [16, 1, 1, 0],
+ 'TAN' => [17, 1, 1, 0],
+ 'ATAN' => [18, 1, 1, 0],
+ 'PI' => [19, 0, 1, 0],
+ 'SQRT' => [20, 1, 1, 0],
+ 'EXP' => [21, 1, 1, 0],
+ 'LN' => [22, 1, 1, 0],
+ 'LOG10' => [23, 1, 1, 0],
+ 'ABS' => [24, 1, 1, 0],
+ 'INT' => [25, 1, 1, 0],
+ 'SIGN' => [26, 1, 1, 0],
+ 'ROUND' => [27, 2, 1, 0],
+ 'LOOKUP' => [28, -1, 0, 0],
+ 'INDEX' => [29, -1, 0, 1],
+ 'REPT' => [30, 2, 1, 0],
+ 'MID' => [31, 3, 1, 0],
+ 'LEN' => [32, 1, 1, 0],
+ 'VALUE' => [33, 1, 1, 0],
+ 'TRUE' => [34, 0, 1, 0],
+ 'FALSE' => [35, 0, 1, 0],
+ 'AND' => [36, -1, 0, 0],
+ 'OR' => [37, -1, 0, 0],
+ 'NOT' => [38, 1, 1, 0],
+ 'MOD' => [39, 2, 1, 0],
+ 'DCOUNT' => [40, 3, 0, 0],
+ 'DSUM' => [41, 3, 0, 0],
+ 'DAVERAGE' => [42, 3, 0, 0],
+ 'DMIN' => [43, 3, 0, 0],
+ 'DMAX' => [44, 3, 0, 0],
+ 'DSTDEV' => [45, 3, 0, 0],
+ 'VAR' => [46, -1, 0, 0],
+ 'DVAR' => [47, 3, 0, 0],
+ 'TEXT' => [48, 2, 1, 0],
+ 'LINEST' => [49, -1, 0, 0],
+ 'TREND' => [50, -1, 0, 0],
+ 'LOGEST' => [51, -1, 0, 0],
+ 'GROWTH' => [52, -1, 0, 0],
+ 'PV' => [56, -1, 1, 0],
+ 'FV' => [57, -1, 1, 0],
+ 'NPER' => [58, -1, 1, 0],
+ 'PMT' => [59, -1, 1, 0],
+ 'RATE' => [60, -1, 1, 0],
+ 'MIRR' => [61, 3, 0, 0],
+ 'IRR' => [62, -1, 0, 0],
+ 'RAND' => [63, 0, 1, 1],
+ 'MATCH' => [64, -1, 0, 0],
+ 'DATE' => [65, 3, 1, 0],
+ 'TIME' => [66, 3, 1, 0],
+ 'DAY' => [67, 1, 1, 0],
+ 'MONTH' => [68, 1, 1, 0],
+ 'YEAR' => [69, 1, 1, 0],
+ 'WEEKDAY' => [70, -1, 1, 0],
+ 'HOUR' => [71, 1, 1, 0],
+ 'MINUTE' => [72, 1, 1, 0],
+ 'SECOND' => [73, 1, 1, 0],
+ 'NOW' => [74, 0, 1, 1],
+ 'AREAS' => [75, 1, 0, 1],
+ 'ROWS' => [76, 1, 0, 1],
+ 'COLUMNS' => [77, 1, 0, 1],
+ 'OFFSET' => [78, -1, 0, 1],
+ 'SEARCH' => [82, -1, 1, 0],
+ 'TRANSPOSE' => [83, 1, 1, 0],
+ 'TYPE' => [86, 1, 1, 0],
+ 'ATAN2' => [97, 2, 1, 0],
+ 'ASIN' => [98, 1, 1, 0],
+ 'ACOS' => [99, 1, 1, 0],
+ 'CHOOSE' => [100, -1, 1, 0],
+ 'HLOOKUP' => [101, -1, 0, 0],
+ 'VLOOKUP' => [102, -1, 0, 0],
+ 'ISREF' => [105, 1, 0, 0],
+ 'LOG' => [109, -1, 1, 0],
+ 'CHAR' => [111, 1, 1, 0],
+ 'LOWER' => [112, 1, 1, 0],
+ 'UPPER' => [113, 1, 1, 0],
+ 'PROPER' => [114, 1, 1, 0],
+ 'LEFT' => [115, -1, 1, 0],
+ 'RIGHT' => [116, -1, 1, 0],
+ 'EXACT' => [117, 2, 1, 0],
+ 'TRIM' => [118, 1, 1, 0],
+ 'REPLACE' => [119, 4, 1, 0],
+ 'SUBSTITUTE' => [120, -1, 1, 0],
+ 'CODE' => [121, 1, 1, 0],
+ 'FIND' => [124, -1, 1, 0],
+ 'CELL' => [125, -1, 0, 1],
+ 'ISERR' => [126, 1, 1, 0],
+ 'ISTEXT' => [127, 1, 1, 0],
+ 'ISNUMBER' => [128, 1, 1, 0],
+ 'ISBLANK' => [129, 1, 1, 0],
+ 'T' => [130, 1, 0, 0],
+ 'N' => [131, 1, 0, 0],
+ 'DATEVALUE' => [140, 1, 1, 0],
+ 'TIMEVALUE' => [141, 1, 1, 0],
+ 'SLN' => [142, 3, 1, 0],
+ 'SYD' => [143, 4, 1, 0],
+ 'DDB' => [144, -1, 1, 0],
+ 'INDIRECT' => [148, -1, 1, 1],
+ 'CALL' => [150, -1, 1, 0],
+ 'CLEAN' => [162, 1, 1, 0],
+ 'MDETERM' => [163, 1, 2, 0],
+ 'MINVERSE' => [164, 1, 2, 0],
+ 'MMULT' => [165, 2, 2, 0],
+ 'IPMT' => [167, -1, 1, 0],
+ 'PPMT' => [168, -1, 1, 0],
+ 'COUNTA' => [169, -1, 0, 0],
+ 'PRODUCT' => [183, -1, 0, 0],
+ 'FACT' => [184, 1, 1, 0],
+ 'DPRODUCT' => [189, 3, 0, 0],
+ 'ISNONTEXT' => [190, 1, 1, 0],
+ 'STDEVP' => [193, -1, 0, 0],
+ 'VARP' => [194, -1, 0, 0],
+ 'DSTDEVP' => [195, 3, 0, 0],
+ 'DVARP' => [196, 3, 0, 0],
+ 'TRUNC' => [197, -1, 1, 0],
+ 'ISLOGICAL' => [198, 1, 1, 0],
+ 'DCOUNTA' => [199, 3, 0, 0],
+ 'USDOLLAR' => [204, -1, 1, 0],
+ 'FINDB' => [205, -1, 1, 0],
+ 'SEARCHB' => [206, -1, 1, 0],
+ 'REPLACEB' => [207, 4, 1, 0],
+ 'LEFTB' => [208, -1, 1, 0],
+ 'RIGHTB' => [209, -1, 1, 0],
+ 'MIDB' => [210, 3, 1, 0],
+ 'LENB' => [211, 1, 1, 0],
+ 'ROUNDUP' => [212, 2, 1, 0],
+ 'ROUNDDOWN' => [213, 2, 1, 0],
+ 'ASC' => [214, 1, 1, 0],
+ 'DBCS' => [215, 1, 1, 0],
+ 'RANK' => [216, -1, 0, 0],
+ 'ADDRESS' => [219, -1, 1, 0],
+ 'DAYS360' => [220, -1, 1, 0],
+ 'TODAY' => [221, 0, 1, 1],
+ 'VDB' => [222, -1, 1, 0],
+ 'MEDIAN' => [227, -1, 0, 0],
+ 'SUMPRODUCT' => [228, -1, 2, 0],
+ 'SINH' => [229, 1, 1, 0],
+ 'COSH' => [230, 1, 1, 0],
+ 'TANH' => [231, 1, 1, 0],
+ 'ASINH' => [232, 1, 1, 0],
+ 'ACOSH' => [233, 1, 1, 0],
+ 'ATANH' => [234, 1, 1, 0],
+ 'DGET' => [235, 3, 0, 0],
+ 'INFO' => [244, 1, 1, 1],
+ 'DB' => [247, -1, 1, 0],
+ 'FREQUENCY' => [252, 2, 0, 0],
+ 'ERROR.TYPE' => [261, 1, 1, 0],
+ 'REGISTER.ID' => [267, -1, 1, 0],
+ 'AVEDEV' => [269, -1, 0, 0],
+ 'BETADIST' => [270, -1, 1, 0],
+ 'GAMMALN' => [271, 1, 1, 0],
+ 'BETAINV' => [272, -1, 1, 0],
+ 'BINOMDIST' => [273, 4, 1, 0],
+ 'CHIDIST' => [274, 2, 1, 0],
+ 'CHIINV' => [275, 2, 1, 0],
+ 'COMBIN' => [276, 2, 1, 0],
+ 'CONFIDENCE' => [277, 3, 1, 0],
+ 'CRITBINOM' => [278, 3, 1, 0],
+ 'EVEN' => [279, 1, 1, 0],
+ 'EXPONDIST' => [280, 3, 1, 0],
+ 'FDIST' => [281, 3, 1, 0],
+ 'FINV' => [282, 3, 1, 0],
+ 'FISHER' => [283, 1, 1, 0],
+ 'FISHERINV' => [284, 1, 1, 0],
+ 'FLOOR' => [285, 2, 1, 0],
+ 'GAMMADIST' => [286, 4, 1, 0],
+ 'GAMMAINV' => [287, 3, 1, 0],
+ 'CEILING' => [288, 2, 1, 0],
+ 'HYPGEOMDIST' => [289, 4, 1, 0],
+ 'LOGNORMDIST' => [290, 3, 1, 0],
+ 'LOGINV' => [291, 3, 1, 0],
+ 'NEGBINOMDIST' => [292, 3, 1, 0],
+ 'NORMDIST' => [293, 4, 1, 0],
+ 'NORMSDIST' => [294, 1, 1, 0],
+ 'NORMINV' => [295, 3, 1, 0],
+ 'NORMSINV' => [296, 1, 1, 0],
+ 'STANDARDIZE' => [297, 3, 1, 0],
+ 'ODD' => [298, 1, 1, 0],
+ 'PERMUT' => [299, 2, 1, 0],
+ 'POISSON' => [300, 3, 1, 0],
+ 'TDIST' => [301, 3, 1, 0],
+ 'WEIBULL' => [302, 4, 1, 0],
+ 'SUMXMY2' => [303, 2, 2, 0],
+ 'SUMX2MY2' => [304, 2, 2, 0],
+ 'SUMX2PY2' => [305, 2, 2, 0],
+ 'CHITEST' => [306, 2, 2, 0],
+ 'CORREL' => [307, 2, 2, 0],
+ 'COVAR' => [308, 2, 2, 0],
+ 'FORECAST' => [309, 3, 2, 0],
+ 'FTEST' => [310, 2, 2, 0],
+ 'INTERCEPT' => [311, 2, 2, 0],
+ 'PEARSON' => [312, 2, 2, 0],
+ 'RSQ' => [313, 2, 2, 0],
+ 'STEYX' => [314, 2, 2, 0],
+ 'SLOPE' => [315, 2, 2, 0],
+ 'TTEST' => [316, 4, 2, 0],
+ 'PROB' => [317, -1, 2, 0],
+ 'DEVSQ' => [318, -1, 0, 0],
+ 'GEOMEAN' => [319, -1, 0, 0],
+ 'HARMEAN' => [320, -1, 0, 0],
+ 'SUMSQ' => [321, -1, 0, 0],
+ 'KURT' => [322, -1, 0, 0],
+ 'SKEW' => [323, -1, 0, 0],
+ 'ZTEST' => [324, -1, 0, 0],
+ 'LARGE' => [325, 2, 0, 0],
+ 'SMALL' => [326, 2, 0, 0],
+ 'QUARTILE' => [327, 2, 0, 0],
+ 'PERCENTILE' => [328, 2, 0, 0],
+ 'PERCENTRANK' => [329, -1, 0, 0],
+ 'MODE' => [330, -1, 2, 0],
+ 'TRIMMEAN' => [331, 2, 0, 0],
+ 'TINV' => [332, 2, 1, 0],
+ 'CONCATENATE' => [336, -1, 1, 0],
+ 'POWER' => [337, 2, 1, 0],
+ 'RADIANS' => [342, 1, 1, 0],
+ 'DEGREES' => [343, 1, 1, 0],
+ 'SUBTOTAL' => [344, -1, 0, 0],
+ 'SUMIF' => [345, -1, 0, 0],
+ 'COUNTIF' => [346, 2, 0, 0],
+ 'COUNTBLANK' => [347, 1, 0, 0],
+ 'ISPMT' => [350, 4, 1, 0],
+ 'DATEDIF' => [351, 3, 1, 0],
+ 'DATESTRING' => [352, 1, 1, 0],
+ 'NUMBERSTRING' => [353, 2, 1, 0],
+ 'ROMAN' => [354, -1, 1, 0],
+ 'GETPIVOTDATA' => [358, -1, 0, 0],
+ 'HYPERLINK' => [359, -1, 1, 0],
+ 'PHONETIC' => [360, 1, 0, 0],
+ 'AVERAGEA' => [361, -1, 0, 0],
+ 'MAXA' => [362, -1, 0, 0],
+ 'MINA' => [363, -1, 0, 0],
+ 'STDEVPA' => [364, -1, 0, 0],
+ 'VARPA' => [365, -1, 0, 0],
+ 'STDEVA' => [366, -1, 0, 0],
+ 'VARA' => [367, -1, 0, 0],
+ 'BAHTTEXT' => [368, 1, 0, 0],
+ ];
+
+ private $spreadsheet;
+
+ /**
+ * The class constructor.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+
+ $this->currentCharacter = 0;
+ $this->currentToken = ''; // The token we are working on.
+ $this->formula = ''; // The formula to parse.
+ $this->lookAhead = ''; // The character ahead of the current char.
+ $this->parseTree = ''; // The parse tree to be generated.
+ $this->externalSheets = [];
+ $this->references = [];
+ }
+
+ /**
+ * Convert a token to the proper ptg value.
+ *
+ * @param mixed $token the token to convert
+ *
+ * @return mixed the converted token on success
+ */
+ private function convert($token)
+ {
+ if (preg_match('/"([^"]|""){0,255}"/', $token)) {
+ return $this->convertString($token);
+ } elseif (is_numeric($token)) {
+ return $this->convertNumber($token);
+ // match references like A1 or $A$1
+ } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
+ return $this->convertRef2d($token);
+ // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?(\\d+)$/u', $token)) {
+ return $this->convertRef3d($token);
+ // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\\d+)$/u", $token)) {
+ return $this->convertRef3d($token);
+ // match ranges like A1:B2 or $A$1:$B$2
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
+ return $this->convertRange2d($token);
+ // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)\\:\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)$/u', $token)) {
+ return $this->convertRange3d($token);
+ // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)\\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)$/u", $token)) {
+ return $this->convertRange3d($token);
+ // operators (including parentheses)
+ } elseif (isset($this->ptg[$token])) {
+ return pack('C', $this->ptg[$token]);
+ // match error codes
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token == '#N/A') {
+ return $this->convertError($token);
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $token) && $this->spreadsheet->getDefinedName($token) !== null) {
+ return $this->convertDefinedName($token);
+ // commented so argument number can be processed correctly. See toReversePolish().
+ /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token))
+ {
+ return($this->convertFunction($token, $this->_func_args));
+ }*/
+ // if it's an argument, ignore the token (the argument remains)
+ } elseif ($token == 'arg') {
+ return '';
+ }
+
+ // TODO: use real error codes
+ throw new WriterException("Unknown token $token");
+ }
+
+ /**
+ * Convert a number token to ptgInt or ptgNum.
+ *
+ * @param mixed $num an integer or double for conversion to its ptg value
+ *
+ * @return string
+ */
+ private function convertNumber($num)
+ {
+ // Integer in the range 0..2**16-1
+ if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) {
+ return pack('Cv', $this->ptg['ptgInt'], $num);
+ }
+
+ // A float
+ if (BIFFwriter::getByteOrder()) { // if it's Big Endian
+ $num = strrev($num);
+ }
+
+ return pack('Cd', $this->ptg['ptgNum'], $num);
+ }
+
+ /**
+ * Convert a string token to ptgStr.
+ *
+ * @param string $string a string for conversion to its ptg value
+ *
+ * @return mixed the converted token on success
+ */
+ private function convertString($string)
+ {
+ // chop away beggining and ending quotes
+ $string = substr($string, 1, -1);
+ if (strlen($string) > 255) {
+ throw new WriterException('String is too long');
+ }
+
+ return pack('C', $this->ptg['ptgStr']) . StringHelper::UTF8toBIFF8UnicodeShort($string);
+ }
+
+ /**
+ * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
+ * args that it takes.
+ *
+ * @param string $token the name of the function for convertion to ptg value
+ * @param int $num_args the number of arguments the function receives
+ *
+ * @return string The packed ptg for the function
+ */
+ private function convertFunction($token, $num_args)
+ {
+ $args = $this->functions[$token][1];
+
+ // Fixed number of args eg. TIME($i, $j, $k).
+ if ($args >= 0) {
+ return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
+ }
+ // Variable number of args eg. SUM($i, $j, $k, ..).
+ if ($args == -1) {
+ return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]);
+ }
+ }
+
+ /**
+ * Convert an Excel range such as A1:D4 to a ptgRefV.
+ *
+ * @param string $range An Excel range in the A1:A2
+ * @param int $class
+ *
+ * @return string
+ */
+ private function convertRange2d($range, $class = 0)
+ {
+ // TODO: possible class value 0,1,2 check Formula.pm
+ // Split the range into 2 cell refs
+ if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
+ [$cell1, $cell2] = explode(':', $range);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException('Unknown range separator');
+ }
+
+ // Convert the cell references
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+
+ // The ptg value depends on the class of the ptg.
+ if ($class == 0) {
+ $ptgArea = pack('C', $this->ptg['ptgArea']);
+ } elseif ($class == 1) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaV']);
+ } elseif ($class == 2) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaA']);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException("Unknown class $class");
+ }
+
+ return $ptgArea . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
+ * a ptgArea3d.
+ *
+ * @param string $token an Excel range in the Sheet1!A1:A2 format
+ *
+ * @return mixed the packed ptgArea3d token on success
+ */
+ private function convertRange3d($token)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Split the range into 2 cell refs
+ [$cell1, $cell2] = explode(':', $range);
+
+ // Convert the cell references
+ if (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\\d+)$/', $cell1)) {
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+ } else { // It's a rows range (like 26:27)
+ [$row1, $col1, $row2, $col2] = $this->rangeToPackedRange($cell1 . ':' . $cell2);
+ }
+
+ // The ptg value depends on the class of the ptg.
+ $ptgArea = pack('C', $this->ptg['ptgArea3d']);
+
+ return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return string The cell in packed() format with the corresponding ptg
+ */
+ private function convertRef2d($cell)
+ {
+ // Convert the cell reference
+ $cell_array = $this->cellToPackedRowcol($cell);
+ [$row, $col] = $cell_array;
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRefA']);
+
+ return $ptgRef . $row . $col;
+ }
+
+ /**
+ * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
+ * ptgRef3d.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return mixed the packed ptgRef3d token on success
+ */
+ private function convertRef3d($cell)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Convert the cell reference part
+ [$row, $col] = $this->cellToPackedRowcol($cell);
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
+
+ return $ptgRef . $ext_ref . $row . $col;
+ }
+
+ /**
+ * Convert an error code to a ptgErr.
+ *
+ * @param string $errorCode The error code for conversion to its ptg value
+ *
+ * @return string The error code ptgErr
+ */
+ private function convertError($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return pack('C', 0x00);
+ case '#DIV/0!':
+ return pack('C', 0x07);
+ case '#VALUE!':
+ return pack('C', 0x0F);
+ case '#REF!':
+ return pack('C', 0x17);
+ case '#NAME?':
+ return pack('C', 0x1D);
+ case '#NUM!':
+ return pack('C', 0x24);
+ case '#N/A':
+ return pack('C', 0x2A);
+ }
+
+ return pack('C', 0xFF);
+ }
+
+ private function convertDefinedName(string $name): void
+ {
+ if (strlen($name) > 255) {
+ throw new WriterException('Defined Name is too long');
+ }
+
+ $nameReference = 1;
+ foreach ($this->spreadsheet->getDefinedNames() as $definedName) {
+ if ($name === $definedName->getName()) {
+ break;
+ }
+ ++$nameReference;
+ }
+
+ $ptgRef = pack('Cvxx', $this->ptg['ptgName'], $nameReference);
+
+ throw new WriterException('Cannot yet write formulae with defined names to Xls');
+// return $ptgRef;
+ }
+
+ /**
+ * Look up the REF index that corresponds to an external sheet name
+ * (or range). If it doesn't exist yet add it to the workbook's references
+ * array. It assumes all sheet names given must exist.
+ *
+ * @param string $ext_ref The name of the external reference
+ *
+ * @return mixed The reference index in packed() format on success
+ */
+ private function getRefIndex($ext_ref)
+ {
+ $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
+ $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
+ $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
+
+ // Check if there is a sheet range eg., Sheet1:Sheet2.
+ if (preg_match('/:/', $ext_ref)) {
+ [$sheet_name1, $sheet_name2] = explode(':', $ext_ref);
+
+ $sheet1 = $this->getSheetIndex($sheet_name1);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name1 in formula");
+ }
+ $sheet2 = $this->getSheetIndex($sheet_name2);
+ if ($sheet2 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name2 in formula");
+ }
+
+ // Reverse max and min sheet numbers if necessary
+ if ($sheet1 > $sheet2) {
+ [$sheet1, $sheet2] = [$sheet2, $sheet1];
+ }
+ } else { // Single sheet name only.
+ $sheet1 = $this->getSheetIndex($ext_ref);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $ext_ref in formula");
+ }
+ $sheet2 = $sheet1;
+ }
+
+ // assume all references belong to this document
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
+ $totalreferences = count($this->references);
+ $index = -1;
+ for ($i = 0; $i < $totalreferences; ++$i) {
+ if ($ref == $this->references[$i]) {
+ $index = $i;
+
+ break;
+ }
+ }
+ // if REF was not found add it to references array
+ if ($index == -1) {
+ $this->references[$totalreferences] = $ref;
+ $index = $totalreferences;
+ }
+
+ return pack('v', $index);
+ }
+
+ /**
+ * Look up the index that corresponds to an external sheet name. The hash of
+ * sheet names is updated by the addworksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @param string $sheet_name Sheet name
+ *
+ * @return int The sheet index, -1 if the sheet was not found
+ */
+ private function getSheetIndex($sheet_name)
+ {
+ if (!isset($this->externalSheets[$sheet_name])) {
+ return -1;
+ }
+
+ return $this->externalSheets[$sheet_name];
+ }
+
+ /**
+ * This method is used to update the array of sheet names. It is
+ * called by the addWorksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet()
+ *
+ * @param string $name The name of the worksheet being added
+ * @param int $index The index of the worksheet being added
+ */
+ public function setExtSheet($name, $index): void
+ {
+ $this->externalSheets[$name] = $index;
+ }
+
+ /**
+ * pack() row and column into the required 3 or 4 byte format.
+ *
+ * @param string $cell The Excel cell reference to be packed
+ *
+ * @return array Array containing the row and column in packed() format
+ */
+ private function cellToPackedRowcol($cell)
+ {
+ $cell = strtoupper($cell);
+ [$row, $col, $row_rel, $col_rel] = $this->cellToRowcol($cell);
+ if ($col >= 256) {
+ throw new WriterException("Column in: $cell greater than 255");
+ }
+ if ($row >= 65536) {
+ throw new WriterException("Row in: $cell greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if row or col are relative.
+ $col |= $col_rel << 14;
+ $col |= $row_rel << 15;
+ $col = pack('v', $col);
+
+ $row = pack('v', $row);
+
+ return [$row, $col];
+ }
+
+ /**
+ * pack() row range into the required 3 or 4 byte format.
+ * Just using maximum col/rows, which is probably not the correct solution.
+ *
+ * @param string $range The Excel range to be packed
+ *
+ * @return array Array containing (row1,col1,row2,col2) in packed() format
+ */
+ private function rangeToPackedRange($range)
+ {
+ preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
+ // return absolute rows if there is a $ in the ref
+ $row1_rel = empty($match[1]) ? 1 : 0;
+ $row1 = $match[2];
+ $row2_rel = empty($match[3]) ? 1 : 0;
+ $row2 = $match[4];
+ // Convert 1-index to zero-index
+ --$row1;
+ --$row2;
+ // Trick poor inocent Excel
+ $col1 = 0;
+ $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
+
+ // FIXME: this changes for BIFF8
+ if (($row1 >= 65536) || ($row2 >= 65536)) {
+ throw new WriterException("Row in: $range greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if rows are relative.
+ $col1 |= $row1_rel << 15;
+ $col2 |= $row2_rel << 15;
+ $col1 = pack('v', $col1);
+ $col2 = pack('v', $col2);
+
+ $row1 = pack('v', $row1);
+ $row2 = pack('v', $row2);
+
+ return [$row1, $col1, $row2, $col2];
+ }
+
+ /**
+ * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
+ * indexed row and column number. Also returns two (0,1) values to indicate
+ * whether the row or column are relative references.
+ *
+ * @param string $cell the Excel cell reference in A1 format
+ *
+ * @return array
+ */
+ private function cellToRowcol($cell)
+ {
+ preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
+ // return absolute column if there is a $ in the ref
+ $col_rel = empty($match[1]) ? 1 : 0;
+ $col_ref = $match[2];
+ $row_rel = empty($match[3]) ? 1 : 0;
+ $row = $match[4];
+
+ // Convert base26 column string to a number.
+ $expn = strlen($col_ref) - 1;
+ $col = 0;
+ $col_ref_length = strlen($col_ref);
+ for ($i = 0; $i < $col_ref_length; ++$i) {
+ $col += (ord($col_ref[$i]) - 64) * 26 ** $expn;
+ --$expn;
+ }
+
+ // Convert 1-index to zero-index
+ --$row;
+ --$col;
+
+ return [$row, $col, $row_rel, $col_rel];
+ }
+
+ /**
+ * Advance to the next valid token.
+ */
+ private function advance()
+ {
+ $i = $this->currentCharacter;
+ $formula_length = strlen($this->formula);
+ // eat up white spaces
+ if ($i < $formula_length) {
+ while ($this->formula[$i] == ' ') {
+ ++$i;
+ }
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ }
+ $token = '';
+ }
+
+ while ($i < $formula_length) {
+ $token .= $this->formula[$i];
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ } else {
+ $this->lookAhead = '';
+ }
+
+ if ($this->match($token) != '') {
+ $this->currentCharacter = $i + 1;
+ $this->currentToken = $token;
+
+ return 1;
+ }
+
+ if ($i < ($formula_length - 2)) {
+ $this->lookAhead = $this->formula[$i + 2];
+ } else { // if we run out of characters lookAhead becomes empty
+ $this->lookAhead = '';
+ }
+ ++$i;
+ }
+ //die("Lexical error ".$this->currentCharacter);
+ }
+
+ /**
+ * Checks if it's a valid token.
+ *
+ * @param mixed $token the token to check
+ *
+ * @return mixed The checked token or false on failure
+ */
+ private function match($token)
+ {
+ switch ($token) {
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '(':
+ case ')':
+ case ',':
+ case ';':
+ case '>=':
+ case '<=':
+ case '=':
+ case '<>':
+ case '^':
+ case '&':
+ case '%':
+ return $token;
+
+ break;
+ case '>':
+ if ($this->lookAhead === '=') { // it's a GE token
+ break;
+ }
+
+ return $token;
+
+ break;
+ case '<':
+ // it's a LE or a NE token
+ if (($this->lookAhead === '=') || ($this->lookAhead === '>')) {
+ break;
+ }
+
+ return $token;
+
+ break;
+ default:
+ // if it's a reference A1 or $A$1 or $A1 or A$1
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.') && ($this->lookAhead !== '!')) {
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ return $token;
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // if it's a range A1:A2 or $A$1:$A$2
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ return $token;
+ } elseif (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) {
+ // If it's a number (check that it's not a sheet name or range)
+ return $token;
+ } elseif (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) {
+ // If it's a string (of maximum 255 characters)
+ return $token;
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token === '#N/A') {
+ // If it's an error code
+ return $token;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) && ($this->lookAhead === '(')) {
+ // if it's a function call
+ return $token;
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token) && $this->spreadsheet->getDefinedName($token) !== null) {
+ return $token;
+ } elseif (substr($token, -1) === ')') {
+ // It's an argument of some description (e.g. a named range),
+ // precise nature yet to be determined
+ return $token;
+ }
+
+ return '';
+ }
+ }
+
+ /**
+ * The parsing method. It parses a formula.
+ *
+ * @param string $formula the formula to parse, without the initial equal
+ * sign (=)
+ *
+ * @return mixed true on success
+ */
+ public function parse($formula)
+ {
+ $this->currentCharacter = 0;
+ $this->formula = (string) $formula;
+ $this->lookAhead = $formula[1] ?? '';
+ $this->advance();
+ $this->parseTree = $this->condition();
+
+ return true;
+ }
+
+ /**
+ * It parses a condition. It assumes the following rule:
+ * Cond -> Expr [(">" | "<") Expr].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function condition()
+ {
+ $result = $this->expression();
+ if ($this->currentToken == '<') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLT', $result, $result2);
+ } elseif ($this->currentToken == '>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGT', $result, $result2);
+ } elseif ($this->currentToken == '<=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLE', $result, $result2);
+ } elseif ($this->currentToken == '>=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGE', $result, $result2);
+ } elseif ($this->currentToken == '=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgEQ', $result, $result2);
+ } elseif ($this->currentToken == '<>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgNE', $result, $result2);
+ } elseif ($this->currentToken == '&') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgConcat', $result, $result2);
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a expression. It assumes the following rule:
+ * Expr -> Term [("+" | "-") Term]
+ * -> "string"
+ * -> "-" Term : Negative value
+ * -> "+" Term : Positive value
+ * -> Error code.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function expression()
+ {
+ // If it's a string return a string node
+ if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
+ $tmp = str_replace('""', '"', $this->currentToken);
+ if (($tmp == '"') || ($tmp == '')) {
+ // Trap for "" that has been used for an empty string
+ $tmp = '""';
+ }
+ $result = $this->createTree($tmp, '', '');
+ $this->advance();
+
+ return $result;
+ // If it's an error code
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') {
+ $result = $this->createTree($this->currentToken, 'ptgErr', '');
+ $this->advance();
+
+ return $result;
+ // If it's a negative value
+ } elseif ($this->currentToken == '-') {
+ // catch "-" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUminus', $result2, '');
+ // If it's a positive value
+ } elseif ($this->currentToken == '+') {
+ // catch "+" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUplus', $result2, '');
+ }
+ $result = $this->term();
+ while (
+ ($this->currentToken == '+') ||
+ ($this->currentToken == '-') ||
+ ($this->currentToken == '^')
+ ) {
+ if ($this->currentToken == '+') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgAdd', $result, $result2);
+ } elseif ($this->currentToken == '-') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgSub', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgPower', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * This function just introduces a ptgParen element in the tree, so that Excel
+ * doesn't get confused when working with a parenthesized formula afterwards.
+ *
+ * @see fact()
+ *
+ * @return array The parsed ptg'd tree
+ */
+ private function parenthesizedExpression()
+ {
+ return $this->createTree('ptgParen', $this->expression(), '');
+ }
+
+ /**
+ * It parses a term. It assumes the following rule:
+ * Term -> Fact [("*" | "/") Fact].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function term()
+ {
+ $result = $this->fact();
+ while (
+ ($this->currentToken == '*') ||
+ ($this->currentToken == '/')
+ ) {
+ if ($this->currentToken == '*') {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgMul', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgDiv', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a factor. It assumes the following rule:
+ * Fact -> ( Expr )
+ * | CellRef
+ * | CellRange
+ * | Number
+ * | Function.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function fact()
+ {
+ if ($this->currentToken === '(') {
+ $this->advance(); // eat the "("
+ $result = $this->parenthesizedExpression();
+ if ($this->currentToken !== ')') {
+ throw new WriterException("')' token expected.");
+ }
+ $this->advance(); // eat the ")"
+
+ return $result;
+ }
+ // if it's a reference
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $this->currentToken)) {
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (
+ preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) ||
+ preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken)
+ ) {
+ // if it's a range A1:B2 or $A$1:$B$2
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (is_numeric($this->currentToken)) {
+ // If it's a number or a percent
+ if ($this->lookAhead === '%') {
+ $result = $this->createTree('ptgPercent', $this->currentToken, '');
+ $this->advance(); // Skip the percentage operator once we've pre-built that tree
+ } else {
+ $result = $this->createTree($this->currentToken, '', '');
+ }
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken) && ($this->lookAhead === '(')) {
+ // if it's a function call
+ return $this->func();
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $this->currentToken) && $this->spreadsheet->getDefinedName($this->currentToken) !== null) {
+ $result = $this->createTree('ptgName', $this->currentToken, '');
+ $this->advance();
+
+ return $result;
+ }
+
+ throw new WriterException('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter);
+ }
+
+ /**
+ * It parses a function call. It assumes the following rule:
+ * Func -> ( Expr [,Expr]* ).
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function func()
+ {
+ $num_args = 0; // number of arguments received
+ $function = strtoupper($this->currentToken);
+ $result = ''; // initialize result
+ $this->advance();
+ $this->advance(); // eat the "("
+ while ($this->currentToken !== ')') {
+ if ($num_args > 0) {
+ if ($this->currentToken === ',' || $this->currentToken === ';') {
+ $this->advance(); // eat the "," or ";"
+ } else {
+ throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}");
+ }
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', $result, $result2);
+ } else { // first argument
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', '', $result2);
+ }
+ ++$num_args;
+ }
+ if (!isset($this->functions[$function])) {
+ throw new WriterException("Function $function() doesn't exist");
+ }
+ $args = $this->functions[$function][1];
+ // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.
+ if (($args >= 0) && ($args != $num_args)) {
+ throw new WriterException("Incorrect number of arguments in function $function() ");
+ }
+
+ $result = $this->createTree($function, $result, $num_args);
+ $this->advance(); // eat the ")"
+
+ return $result;
+ }
+
+ /**
+ * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
+ * as elements.
+ *
+ * @param mixed $value the value of this node
+ * @param mixed $left the left array (sub-tree) or a final node
+ * @param mixed $right the right array (sub-tree) or a final node
+ *
+ * @return array A tree
+ */
+ private function createTree($value, $left, $right)
+ {
+ return ['value' => $value, 'left' => $left, 'right' => $right];
+ }
+
+ /**
+ * Builds a string containing the tree in reverse polish notation (What you
+ * would use in a HP calculator stack).
+ * The following tree:.
+ *
+ * +
+ * / \
+ * 2 3
+ *
+ * produces: "23+"
+ *
+ * The following tree:
+ *
+ * +
+ * / \
+ * 3 *
+ * / \
+ * 6 A1
+ *
+ * produces: "36A1*+"
+ *
+ * In fact all operands, functions, references, etc... are written as ptg's
+ *
+ * @param array $tree the optional tree to convert
+ *
+ * @return string The tree in reverse polish notation
+ */
+ public function toReversePolish($tree = [])
+ {
+ $polish = ''; // the string we are going to return
+ if (empty($tree)) { // If it's the first call use parseTree
+ $tree = $this->parseTree;
+ }
+
+ if (is_array($tree['left'])) {
+ $converted_tree = $this->toReversePolish($tree['left']);
+ $polish .= $converted_tree;
+ } elseif ($tree['left'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['left']);
+ $polish .= $converted_tree;
+ }
+ if (is_array($tree['right'])) {
+ $converted_tree = $this->toReversePolish($tree['right']);
+ $polish .= $converted_tree;
+ } elseif ($tree['right'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['right']);
+ $polish .= $converted_tree;
+ }
+ // if it's a function convert it here (so we can set it's arguments)
+ if (
+ preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) &&
+ !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) &&
+ !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) &&
+ !is_numeric($tree['value']) &&
+ !isset($this->ptg[$tree['value']])
+ ) {
+ // left subtree for a function is always an array.
+ if ($tree['left'] != '') {
+ $left_tree = $this->toReversePolish($tree['left']);
+ } else {
+ $left_tree = '';
+ }
+ // add it's left subtree and return.
+ return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
+ }
+ $converted_tree = $this->convert($tree['value']);
+
+ return $polish . $converted_tree;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php new file mode 100644 index 0000000..9b6f6de --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -0,0 +1,1191 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\DefinedName;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Workbook extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * The BIFF file size for the workbook.
+ *
+ * @var int
+ *
+ * @see calcSheetOffsets()
+ */
+ private $biffSize;
+
+ /**
+ * XF Writers.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[]
+ */
+ private $xfWriters = [];
+
+ /**
+ * Array containing the colour palette.
+ *
+ * @var array
+ */
+ private $palette;
+
+ /**
+ * The codepage indicates the text encoding used for strings.
+ *
+ * @var int
+ */
+ private $codepage;
+
+ /**
+ * The country code used for localization.
+ *
+ * @var int
+ */
+ private $countryCode;
+
+ /**
+ * Workbook.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Fonts writers.
+ *
+ * @var Font[]
+ */
+ private $fontWriters = [];
+
+ /**
+ * Added fonts. Maps from font's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedFonts = [];
+
+ /**
+ * Shared number formats.
+ *
+ * @var array
+ */
+ private $numberFormats = [];
+
+ /**
+ * Added number formats. Maps from numberFormat's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedNumberFormats = [];
+
+ /**
+ * Sizes of the binary worksheet streams.
+ *
+ * @var array
+ */
+ private $worksheetSizes = [];
+
+ /**
+ * Offsets of the binary worksheet streams relative to the start of the global workbook stream.
+ *
+ * @var array
+ */
+ private $worksheetOffsets = [];
+
+ /**
+ * Total number of shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Number of unique shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Array of unique shared strings in workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Escher object corresponding to MSODRAWINGGROUP.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Class constructor.
+ *
+ * @param Spreadsheet $spreadsheet The Workbook
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array $str_table String Table
+ * @param array $colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ */
+ public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->parser = $parser;
+ $this->biffSize = 0;
+ $this->palette = [];
+ $this->countryCode = -1;
+
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->setPaletteXl97();
+
+ $this->spreadsheet = $spreadsheet;
+
+ $this->codepage = 0x04B0;
+
+ // Add empty sheets and Build color cache
+ $countSheets = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $phpSheet = $spreadsheet->getSheet($i);
+
+ $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
+
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $i, $i);
+ $this->parser->references[] = $ref; // Register reference with parser
+
+ // Sheet tab colors?
+ if ($phpSheet->isTabColorSet()) {
+ $this->addColor($phpSheet->getTabColor()->getRGB());
+ }
+ }
+ }
+
+ /**
+ * Add a new XF writer.
+ *
+ * @param bool $isStyleXf Is it a style XF?
+ *
+ * @return int Index to XF record
+ */
+ public function addXfWriter(Style $style, $isStyleXf = false)
+ {
+ $xfWriter = new Xf($style);
+ $xfWriter->setIsStyleXf($isStyleXf);
+
+ // Add the font if not already added
+ $fontIndex = $this->addFont($style->getFont());
+
+ // Assign the font index to the xf record
+ $xfWriter->setFontIndex($fontIndex);
+
+ // Background colors, best to treat these after the font so black will come after white in custom palette
+ $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
+ $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
+ $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
+ $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
+ $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
+ $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
+ $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
+
+ // Add the number format if it is not a built-in one and not already added
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
+
+ if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
+ $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
+ } else {
+ $numberFormatIndex = 164 + count($this->numberFormats);
+ $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
+ $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
+ }
+ } else {
+ $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
+ }
+
+ // Assign the number format index to xf record
+ $xfWriter->setNumberFormatIndex($numberFormatIndex);
+
+ $this->xfWriters[] = $xfWriter;
+
+ return count($this->xfWriters) - 1;
+ }
+
+ /**
+ * Add a font to added fonts.
+ *
+ * @return int Index to FONT record
+ */
+ public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ $fontHashCode = $font->getHashCode();
+ if (isset($this->addedFonts[$fontHashCode])) {
+ $fontIndex = $this->addedFonts[$fontHashCode];
+ } else {
+ $countFonts = count($this->fontWriters);
+ $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
+
+ $fontWriter = new Font($font);
+ $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
+ $this->fontWriters[] = $fontWriter;
+
+ $this->addedFonts[$fontHashCode] = $fontIndex;
+ }
+
+ return $fontIndex;
+ }
+
+ /**
+ * Alter color palette adding a custom color.
+ *
+ * @param string $rgb E.g. 'FF00AA'
+ *
+ * @return int Color index
+ */
+ private function addColor($rgb)
+ {
+ if (!isset($this->colors[$rgb])) {
+ $color =
+ [
+ hexdec(substr($rgb, 0, 2)),
+ hexdec(substr($rgb, 2, 2)),
+ hexdec(substr($rgb, 4)),
+ 0,
+ ];
+ $colorIndex = array_search($color, $this->palette);
+ if ($colorIndex) {
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ if (count($this->colors) === 0) {
+ $lastColor = 7;
+ } else {
+ $lastColor = end($this->colors);
+ }
+ if ($lastColor < 57) {
+ // then we add a custom color altering the palette
+ $colorIndex = $lastColor + 1;
+ $this->palette[$colorIndex] = $color;
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ // no room for more custom colors, just map to black
+ $colorIndex = 0;
+ }
+ }
+ } else {
+ // fetch already added custom color
+ $colorIndex = $this->colors[$rgb];
+ }
+
+ return $colorIndex;
+ }
+
+ /**
+ * Sets the colour palette to the Excel 97+ default.
+ */
+ private function setPaletteXl97(): void
+ {
+ $this->palette = [
+ 0x08 => [0x00, 0x00, 0x00, 0x00],
+ 0x09 => [0xff, 0xff, 0xff, 0x00],
+ 0x0A => [0xff, 0x00, 0x00, 0x00],
+ 0x0B => [0x00, 0xff, 0x00, 0x00],
+ 0x0C => [0x00, 0x00, 0xff, 0x00],
+ 0x0D => [0xff, 0xff, 0x00, 0x00],
+ 0x0E => [0xff, 0x00, 0xff, 0x00],
+ 0x0F => [0x00, 0xff, 0xff, 0x00],
+ 0x10 => [0x80, 0x00, 0x00, 0x00],
+ 0x11 => [0x00, 0x80, 0x00, 0x00],
+ 0x12 => [0x00, 0x00, 0x80, 0x00],
+ 0x13 => [0x80, 0x80, 0x00, 0x00],
+ 0x14 => [0x80, 0x00, 0x80, 0x00],
+ 0x15 => [0x00, 0x80, 0x80, 0x00],
+ 0x16 => [0xc0, 0xc0, 0xc0, 0x00],
+ 0x17 => [0x80, 0x80, 0x80, 0x00],
+ 0x18 => [0x99, 0x99, 0xff, 0x00],
+ 0x19 => [0x99, 0x33, 0x66, 0x00],
+ 0x1A => [0xff, 0xff, 0xcc, 0x00],
+ 0x1B => [0xcc, 0xff, 0xff, 0x00],
+ 0x1C => [0x66, 0x00, 0x66, 0x00],
+ 0x1D => [0xff, 0x80, 0x80, 0x00],
+ 0x1E => [0x00, 0x66, 0xcc, 0x00],
+ 0x1F => [0xcc, 0xcc, 0xff, 0x00],
+ 0x20 => [0x00, 0x00, 0x80, 0x00],
+ 0x21 => [0xff, 0x00, 0xff, 0x00],
+ 0x22 => [0xff, 0xff, 0x00, 0x00],
+ 0x23 => [0x00, 0xff, 0xff, 0x00],
+ 0x24 => [0x80, 0x00, 0x80, 0x00],
+ 0x25 => [0x80, 0x00, 0x00, 0x00],
+ 0x26 => [0x00, 0x80, 0x80, 0x00],
+ 0x27 => [0x00, 0x00, 0xff, 0x00],
+ 0x28 => [0x00, 0xcc, 0xff, 0x00],
+ 0x29 => [0xcc, 0xff, 0xff, 0x00],
+ 0x2A => [0xcc, 0xff, 0xcc, 0x00],
+ 0x2B => [0xff, 0xff, 0x99, 0x00],
+ 0x2C => [0x99, 0xcc, 0xff, 0x00],
+ 0x2D => [0xff, 0x99, 0xcc, 0x00],
+ 0x2E => [0xcc, 0x99, 0xff, 0x00],
+ 0x2F => [0xff, 0xcc, 0x99, 0x00],
+ 0x30 => [0x33, 0x66, 0xff, 0x00],
+ 0x31 => [0x33, 0xcc, 0xcc, 0x00],
+ 0x32 => [0x99, 0xcc, 0x00, 0x00],
+ 0x33 => [0xff, 0xcc, 0x00, 0x00],
+ 0x34 => [0xff, 0x99, 0x00, 0x00],
+ 0x35 => [0xff, 0x66, 0x00, 0x00],
+ 0x36 => [0x66, 0x66, 0x99, 0x00],
+ 0x37 => [0x96, 0x96, 0x96, 0x00],
+ 0x38 => [0x00, 0x33, 0x66, 0x00],
+ 0x39 => [0x33, 0x99, 0x66, 0x00],
+ 0x3A => [0x00, 0x33, 0x00, 0x00],
+ 0x3B => [0x33, 0x33, 0x00, 0x00],
+ 0x3C => [0x99, 0x33, 0x00, 0x00],
+ 0x3D => [0x99, 0x33, 0x66, 0x00],
+ 0x3E => [0x33, 0x33, 0x99, 0x00],
+ 0x3F => [0x33, 0x33, 0x33, 0x00],
+ ];
+ }
+
+ /**
+ * Assemble worksheets into a workbook and send the BIFF data to an OLE
+ * storage.
+ *
+ * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
+ *
+ * @return string Binary data for workbook stream
+ */
+ public function writeWorkbook(array $pWorksheetSizes)
+ {
+ $this->worksheetSizes = $pWorksheetSizes;
+
+ // Calculate the number of selected worksheet tabs and call the finalization
+ // methods for each worksheet
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // Add part 1 of the Workbook globals, what goes before the SHEET records
+ $this->storeBof(0x0005);
+ $this->writeCodepage();
+ $this->writeWindow1();
+
+ $this->writeDateMode();
+ $this->writeAllFonts();
+ $this->writeAllNumberFormats();
+ $this->writeAllXfs();
+ $this->writeAllStyles();
+ $this->writePalette();
+
+ // Prepare part 3 of the workbook global stream, what goes after the SHEET records
+ $part3 = '';
+ if ($this->countryCode !== -1) {
+ $part3 .= $this->writeCountry();
+ }
+ $part3 .= $this->writeRecalcId();
+
+ $part3 .= $this->writeSupbookInternal();
+ /* TODO: store external SUPBOOK records and XCT and CRN records
+ in case of external references for BIFF8 */
+ $part3 .= $this->writeExternalsheetBiff8();
+ $part3 .= $this->writeAllDefinedNamesBiff8();
+ $part3 .= $this->writeMsoDrawingGroup();
+ $part3 .= $this->writeSharedStringsTable();
+
+ $part3 .= $this->writeEof();
+
+ // Add part 2 of the Workbook globals, the SHEET records
+ $this->calcSheetOffsets();
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
+ }
+
+ // Add part 3 of the Workbook globals
+ $this->_data .= $part3;
+
+ return $this->_data;
+ }
+
+ /**
+ * Calculate offsets for Worksheet BOF records.
+ */
+ private function calcSheetOffsets(): void
+ {
+ $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
+
+ // size of Workbook globals part 1 + 3
+ $offset = $this->_datasize;
+
+ // add size of Workbook globals part 2, the length of the SHEET records
+ $total_worksheets = count($this->spreadsheet->getAllSheets());
+ foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
+ $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
+ }
+
+ // add the sizes of each of the Sheet substreams, respectively
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->worksheetOffsets[$i] = $offset;
+ $offset += $this->worksheetSizes[$i];
+ }
+ $this->biffSize = $offset;
+ }
+
+ /**
+ * Store the Excel FONT records.
+ */
+ private function writeAllFonts(): void
+ {
+ foreach ($this->fontWriters as $fontWriter) {
+ $this->append($fontWriter->writeFont());
+ }
+ }
+
+ /**
+ * Store user defined numerical formats i.e. FORMAT records.
+ */
+ private function writeAllNumberFormats(): void
+ {
+ foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
+ $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
+ }
+ }
+
+ /**
+ * Write all XF records.
+ */
+ private function writeAllXfs(): void
+ {
+ foreach ($this->xfWriters as $xfWriter) {
+ $this->append($xfWriter->writeXf());
+ }
+ }
+
+ /**
+ * Write all STYLE records.
+ */
+ private function writeAllStyles(): void
+ {
+ $this->writeStyle();
+ }
+
+ private function parseDefinedNameValue(DefinedName $pDefinedName): string
+ {
+ $definedRange = $pDefinedName->getValue();
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF . '/mui',
+ $definedRange,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $pDefinedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
+ }
+
+ if (!empty($column)) {
+ $newRange .= "\${$column}";
+ }
+ if (!empty($row)) {
+ $newRange .= "\${$row}";
+ }
+
+ $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
+ }
+
+ return $definedRange;
+ }
+
+ /**
+ * Writes all the DEFINEDNAME records (BIFF8).
+ * So far this is only used for repeating rows/columns (print titles) and print areas.
+ */
+ private function writeAllDefinedNamesBiff8()
+ {
+ $chunk = '';
+
+ // Named ranges
+ $definedNames = $this->spreadsheet->getDefinedNames();
+ if (count($definedNames) > 0) {
+ // Loop named ranges
+ foreach ($definedNames as $definedName) {
+ $range = $this->parseDefinedNameValue($definedName);
+
+ // parse formula
+ try {
+ $error = $this->parser->parse($range);
+ $formulaData = $this->parser->toReversePolish();
+
+ // make sure tRef3d is of type tRef3dR (0x3A)
+ if (isset($formulaData[0]) && ($formulaData[0] == "\x7A" || $formulaData[0] == "\x5A")) {
+ $formulaData = "\x3A" . substr($formulaData, 1);
+ }
+
+ if ($definedName->getLocalOnly()) {
+ // local scope
+ $scope = $this->spreadsheet->getIndex($definedName->getScope()) + 1;
+ } else {
+ // global scope
+ $scope = 0;
+ }
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8($definedName->getName(), $formulaData, $scope, false));
+ } catch (PhpSpreadsheetException $e) {
+ // do nothing
+ }
+ }
+ }
+
+ // total number of sheets
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // write the print titles (repeating rows, columns), if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ // simultaneous repeatColumns repeatRows
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+
+ // construct formula data manually
+ $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
+ $formulaData .= pack('C', 0x10); // tList
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+
+ // (exclusive) either repeatColumns or repeatRows
+ } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
+ // Columns to repeat
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+ } else {
+ $colmin = 0;
+ $colmax = 255;
+ }
+ // Rows to repeat
+ if ($sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+ } else {
+ $rowmin = 0;
+ $rowmax = 65535;
+ }
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write the print areas, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ if ($sheetSetup->isPrintAreaSet()) {
+ // Print area, e.g. A3:J6,H1:X20
+ $printArea = Coordinate::splitRange($sheetSetup->getPrintArea());
+ $countPrintArea = count($printArea);
+
+ $formulaData = '';
+ for ($j = 0; $j < $countPrintArea; ++$j) {
+ $printAreaRect = $printArea[$j]; // e.g. A3:J6
+ $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]);
+
+ $print_rowmin = $printAreaRect[0][1] - 1;
+ $print_rowmax = $printAreaRect[1][1] - 1;
+ $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1;
+ $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1;
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
+
+ if ($j > 0) {
+ $formulaData .= pack('C', 0x10); // list operator token ','
+ }
+ }
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write autofilters, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
+ $autoFilterRange = $sheetAutoFilter->getRange();
+ if (!empty($autoFilterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange);
+
+ //Autofilter built in name
+ $name = pack('C', 0x0D);
+
+ $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
+ }
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data.
+ *
+ * @param string $name The name in UTF-8
+ * @param string $formulaData The binary formula data
+ * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param bool $isBuiltIn Built-in name?
+ *
+ * @return string Complete binary record data
+ */
+ private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = $isBuiltIn ? 0x20 : 0x00;
+
+ // length of the name, character count
+ $nlen = StringHelper::countCharacters($name);
+
+ // name with stripped length field
+ $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
+
+ // size of the formula (in bytes)
+ $sz = strlen($formulaData);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
+ . $name . $formulaData;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Write a short NAME record.
+ *
+ * @param string $name
+ * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param integer[][] $rangeBounds range boundaries
+ * @param bool $isHidden
+ *
+ * @return string Complete binary record data
+ * */
+ private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = ($isHidden ? 0x21 : 0x00);
+
+ $extra = pack(
+ 'Cvvvvv',
+ 0x3B,
+ $sheetIndex - 1,
+ $rangeBounds[0][1] - 1,
+ $rangeBounds[1][1] - 1,
+ $rangeBounds[0][0] - 1,
+ $rangeBounds[1][0] - 1
+ );
+
+ // size of the formula (in bytes)
+ $sz = strlen($extra);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
+ . $name . $extra;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Stores the CODEPAGE biff record.
+ */
+ private function writeCodepage(): void
+ {
+ $record = 0x0042; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+ $cv = $this->codepage; // The code page
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cv);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF WINDOW1 record.
+ */
+ private function writeWindow1(): void
+ {
+ $record = 0x003D; // Record identifier
+ $length = 0x0012; // Number of bytes to follow
+
+ $xWn = 0x0000; // Horizontal position of window
+ $yWn = 0x0000; // Vertical position of window
+ $dxWn = 0x25BC; // Width of window
+ $dyWn = 0x1572; // Height of window
+
+ $grbit = 0x0038; // Option flags
+
+ // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
+ $ctabsel = 1; // Number of workbook tabs selected
+
+ $wTabRatio = 0x0258; // Tab to scrollbar ratio
+
+ // not supported by PhpSpreadsheet, set to 0
+ $itabFirst = 0; // 1st displayed worksheet
+ $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel BIFF BOUNDSHEET record.
+ *
+ * @param Worksheet $sheet Worksheet name
+ * @param int $offset Location of worksheet BOF
+ */
+ private function writeBoundSheet($sheet, $offset): void
+ {
+ $sheetname = $sheet->getTitle();
+ $record = 0x0085; // Record identifier
+
+ // sheet state
+ switch ($sheet->getSheetState()) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE:
+ $ss = 0x00;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN:
+ $ss = 0x01;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN:
+ $ss = 0x02;
+
+ break;
+ default:
+ $ss = 0x00;
+
+ break;
+ }
+
+ // sheet type
+ $st = 0x00;
+
+ $grbit = 0x0000; // Visibility and sheet type
+
+ $data = pack('VCC', $offset, $ss, $st);
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Internal SUPBOOK record.
+ */
+ private function writeSupbookInternal()
+ {
+ $record = 0x01AE; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Writes the Excel BIFF EXTERNSHEET record. These references are used by
+ * formulas.
+ */
+ private function writeExternalsheetBiff8()
+ {
+ $totalReferences = count($this->parser->references);
+ $record = 0x0017; // Record identifier
+ $length = 2 + 6 * $totalReferences; // Number of bytes to follow
+
+ $supbook_index = 0; // FIXME: only using internal SUPBOOK record
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $totalReferences);
+ for ($i = 0; $i < $totalReferences; ++$i) {
+ $data .= $this->parser->references[$i];
+ }
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF STYLE records.
+ */
+ private function writeStyle(): void
+ {
+ $record = 0x0293; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $ixfe = 0x8000; // Index to cell style XF
+ $BuiltIn = 0x00; // Built-in style
+ $iLevel = 0xff; // Outline style level
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel FORMAT record for non "built-in" numerical formats.
+ *
+ * @param string $format Custom format string
+ * @param int $ifmt Format index code
+ */
+ private function writeNumberFormat($format, $ifmt): void
+ {
+ $record = 0x041E; // Record identifier
+
+ $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format);
+ $length = 2 + strlen($numberFormatString); // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $ifmt) . $numberFormatString;
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write DATEMODE record to indicate the date system in use (1904 or 1900).
+ */
+ private function writeDateMode(): void
+ {
+ $record = 0x0022; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904)
+ ? 1
+ : 0; // Flag for 1904 date system
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $f1904);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Stores the COUNTRY record for localization.
+ *
+ * @return string
+ */
+ private function writeCountry()
+ {
+ $record = 0x008C; // Record identifier
+ $length = 4; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ // using the same country code always for simplicity
+ $data = pack('vv', $this->countryCode, $this->countryCode);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write the RECALCID record.
+ *
+ * @return string
+ */
+ private function writeRecalcId()
+ {
+ $record = 0x01C1; // Record identifier
+ $length = 8; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+
+ // by inspection of real Excel files, MS Office Excel 2007 writes this
+ $data = pack('VV', 0x000001C1, 0x00001E667);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Stores the PALETTE biff record.
+ */
+ private function writePalette(): void
+ {
+ $aref = $this->palette;
+
+ $record = 0x0092; // Record identifier
+ $length = 2 + 4 * count($aref); // Number of bytes to follow
+ $ccv = count($aref); // Number of RGB values to follow
+ $data = ''; // The RGB data
+
+ // Pack the RGB data
+ foreach ($aref as $color) {
+ foreach ($color as $byte) {
+ $data .= pack('C', $byte);
+ }
+ }
+
+ $header = pack('vvv', $record, $length, $ccv);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Handling of the SST continue blocks is complicated by the need to include an
+ * additional continuation byte depending on whether the string is split between
+ * blocks or whether it starts at the beginning of the block. (There are also
+ * additional complications that will arise later when/if Rich Strings are
+ * supported).
+ *
+ * The Excel documentation says that the SST record should be followed by an
+ * EXTSST record. The EXTSST record is a hash table that is used to optimise
+ * access to SST. However, despite the documentation it doesn't seem to be
+ * required so we will ignore it.
+ *
+ * @return string Binary data
+ */
+ private function writeSharedStringsTable()
+ {
+ // maximum size of record data (excluding record header)
+ $continue_limit = 8224;
+
+ // initialize array of record data blocks
+ $recordDatas = [];
+
+ // start SST record data block with total number of strings, total number of unique strings
+ $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
+
+ // loop through all (unique) strings in shared strings table
+ foreach (array_keys($this->stringTable) as $string) {
+ // here $string is a BIFF8 encoded string
+
+ // length = character count
+ $headerinfo = unpack('vlength/Cencoding', $string);
+
+ // currently, this is always 1 = uncompressed
+ $encoding = $headerinfo['encoding'];
+
+ // initialize finished writing current $string
+ $finished = false;
+
+ while ($finished === false) {
+ // normally, there will be only one cycle, but if string cannot immediately be written as is
+ // there will be need for more than one cylcle, if string longer than one record data block, there
+ // may be need for even more cycles
+
+ if (strlen($recordData) + strlen($string) <= $continue_limit) {
+ // then we can write the string (or remainder of string) without any problems
+ $recordData .= $string;
+
+ if (strlen($recordData) + strlen($string) == $continue_limit) {
+ // we close the record data block, and initialize a new one
+ $recordDatas[] = $recordData;
+ $recordData = '';
+ }
+
+ // we are finished writing this string
+ $finished = true;
+ } else {
+ // special treatment writing the string (or remainder of the string)
+ // If the string is very long it may need to be written in more than one CONTINUE record.
+
+ // check how many bytes more there is room for in the current record
+ $space_remaining = $continue_limit - strlen($recordData);
+
+ // minimum space needed
+ // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
+ // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character
+ $min_space_needed = ($encoding == 1) ? 5 : 4;
+
+ // We have two cases
+ // 1. space remaining is less than minimum space needed
+ // here we must waste the space remaining and move to next record data block
+ // 2. space remaining is greater than or equal to minimum space needed
+ // here we write as much as we can in the current block, then move to next record data block
+
+ // 1. space remaining is less than minimum space needed
+ if ($space_remaining < $min_space_needed) {
+ // we close the block, store the block data
+ $recordDatas[] = $recordData;
+
+ // and start new record data block where we start writing the string
+ $recordData = '';
+
+ // 2. space remaining is greater than or equal to minimum space needed
+ } else {
+ // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
+ $effective_space_remaining = $space_remaining;
+
+ // for uncompressed strings, sometimes effective space remaining is reduced by 1
+ if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
+ --$effective_space_remaining;
+ }
+
+ // one block fininshed, store the block data
+ $recordData .= substr($string, 0, $effective_space_remaining);
+
+ $string = substr($string, $effective_space_remaining); // for next cycle in while loop
+ $recordDatas[] = $recordData;
+
+ // start new record data block with the repeated option flags
+ $recordData = pack('C', $encoding);
+ }
+ }
+ }
+ }
+
+ // Store the last record data block unless it is empty
+ // if there was no need for any continue records, this will be the for SST record data block itself
+ if (strlen($recordData) > 0) {
+ $recordDatas[] = $recordData;
+ }
+
+ // combine into one chunk with all the blocks SST, CONTINUE,...
+ $chunk = '';
+ foreach ($recordDatas as $i => $recordData) {
+ // first block should have the SST record header, remaing should have CONTINUE header
+ $record = ($i == 0) ? 0x00FC : 0x003C;
+
+ $header = pack('vv', $record, strlen($recordData));
+ $data = $header . $recordData;
+
+ $chunk .= $this->writeData($data);
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
+ */
+ private function writeMsoDrawingGroup()
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+
+ $record = 0x00EB;
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header . $data);
+ }
+
+ return '';
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php new file mode 100644 index 0000000..a5680e3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -0,0 +1,4490 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\Xls;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Worksheet (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Worksheet extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * Maximum number of characters for a string (LABEL record in BIFF5).
+ *
+ * @var int
+ */
+ private $xlsStringMaxLength;
+
+ /**
+ * Array containing format information for columns.
+ *
+ * @var array
+ */
+ private $columnInfo;
+
+ /**
+ * Array containing the selected area for the worksheet.
+ *
+ * @var array
+ */
+ private $selection;
+
+ /**
+ * The active pane for the worksheet.
+ *
+ * @var int
+ */
+ private $activePane;
+
+ /**
+ * Whether to use outline.
+ *
+ * @var int
+ */
+ private $outlineOn;
+
+ /**
+ * Auto outline styles.
+ *
+ * @var bool
+ */
+ private $outlineStyle;
+
+ /**
+ * Whether to have outline summary below.
+ *
+ * @var bool
+ */
+ private $outlineBelow;
+
+ /**
+ * Whether to have outline summary at the right.
+ *
+ * @var bool
+ */
+ private $outlineRight;
+
+ /**
+ * Reference to the total number of strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Reference to the number of unique strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Reference to the array containing all the unique strings in the workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Index of first used row (at least 0).
+ *
+ * @var int
+ */
+ private $firstRowIndex;
+
+ /**
+ * Index of last used row. (no used rows means -1).
+ *
+ * @var int
+ */
+ private $lastRowIndex;
+
+ /**
+ * Index of first used column (at least 0).
+ *
+ * @var int
+ */
+ private $firstColumnIndex;
+
+ /**
+ * Index of last used column (no used columns means -1).
+ *
+ * @var int
+ */
+ private $lastColumnIndex;
+
+ /**
+ * Sheet object.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ */
+ public $phpSheet;
+
+ /**
+ * Count cell style Xfs.
+ *
+ * @var int
+ */
+ private $countCellStyleXfs;
+
+ /**
+ * Escher object corresponding to MSODRAWING.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Array of font hashes associated to FONT records index.
+ *
+ * @var array
+ */
+ public $fontHashIndex;
+
+ /**
+ * @var bool
+ */
+ private $preCalculateFormulas;
+
+ /**
+ * @var int
+ */
+ private $printHeaders;
+
+ /**
+ * Constructor.
+ *
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array &$str_table String Table
+ * @param array &$colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write
+ */
+ public function __construct(&$str_total, &$str_unique, &$str_table, &$colors, Parser $parser, $preCalculateFormulas, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->preCalculateFormulas = $preCalculateFormulas;
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->parser = $parser;
+
+ $this->phpSheet = $phpSheet;
+
+ $this->xlsStringMaxLength = 255;
+ $this->columnInfo = [];
+ $this->selection = [0, 0, 0, 0];
+ $this->activePane = 3;
+
+ $this->printHeaders = 0;
+
+ $this->outlineStyle = 0;
+ $this->outlineBelow = 1;
+ $this->outlineRight = 1;
+ $this->outlineOn = 1;
+
+ $this->fontHashIndex = [];
+
+ // calculate values for DIMENSIONS record
+ $minR = 1;
+ $minC = 'A';
+
+ $maxR = $this->phpSheet->getHighestRow();
+ $maxC = $this->phpSheet->getHighestColumn();
+
+ // Determine lowest and highest column and row
+ $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR;
+
+ $this->firstColumnIndex = Coordinate::columnIndexFromString($minC);
+ $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC);
+
+// if ($this->firstColumnIndex > 255) $this->firstColumnIndex = 255;
+ if ($this->lastColumnIndex > 255) {
+ $this->lastColumnIndex = 255;
+ }
+
+ $this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection());
+ }
+
+ /**
+ * Add data to the beginning of the workbook (note the reverse order)
+ * and to the end of the workbook.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook()
+ */
+ public function close(): void
+ {
+ $phpSheet = $this->phpSheet;
+
+ // Storing selected cells and active sheet because it changes while parsing cells with formulas.
+ $selectedCells = $this->phpSheet->getSelectedCells();
+ $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex();
+
+ // Write BOF record
+ $this->storeBof(0x0010);
+
+ // Write PRINTHEADERS
+ $this->writePrintHeaders();
+
+ // Write PRINTGRIDLINES
+ $this->writePrintGridlines();
+
+ // Write GRIDSET
+ $this->writeGridset();
+
+ // Calculate column widths
+ $phpSheet->calculateColumnWidths();
+
+ // Column dimensions
+ if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) {
+ $defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont());
+ }
+
+ $columnDimensions = $phpSheet->getColumnDimensions();
+ $maxCol = $this->lastColumnIndex - 1;
+ for ($i = 0; $i <= $maxCol; ++$i) {
+ $hidden = 0;
+ $level = 0;
+ $xfIndex = 15; // there are 15 cell style Xfs
+
+ $width = $defaultWidth;
+
+ $columnLetter = Coordinate::stringFromColumnIndex($i + 1);
+ if (isset($columnDimensions[$columnLetter])) {
+ $columnDimension = $columnDimensions[$columnLetter];
+ if ($columnDimension->getWidth() >= 0) {
+ $width = $columnDimension->getWidth();
+ }
+ $hidden = $columnDimension->getVisible() ? 0 : 1;
+ $level = $columnDimension->getOutlineLevel();
+ $xfIndex = $columnDimension->getXfIndex() + 15; // there are 15 cell style Xfs
+ }
+
+ // Components of columnInfo:
+ // $firstcol first column on the range
+ // $lastcol last column on the range
+ // $width width to set
+ // $xfIndex The optional cell style Xf index to apply to the columns
+ // $hidden The optional hidden atribute
+ // $level The optional outline level
+ $this->columnInfo[] = [$i, $i, $width, $xfIndex, $hidden, $level];
+ }
+
+ // Write GUTS
+ $this->writeGuts();
+
+ // Write DEFAULTROWHEIGHT
+ $this->writeDefaultRowHeight();
+ // Write WSBOOL
+ $this->writeWsbool();
+ // Write horizontal and vertical page breaks
+ $this->writeBreaks();
+ // Write page header
+ $this->writeHeader();
+ // Write page footer
+ $this->writeFooter();
+ // Write page horizontal centering
+ $this->writeHcenter();
+ // Write page vertical centering
+ $this->writeVcenter();
+ // Write left margin
+ $this->writeMarginLeft();
+ // Write right margin
+ $this->writeMarginRight();
+ // Write top margin
+ $this->writeMarginTop();
+ // Write bottom margin
+ $this->writeMarginBottom();
+ // Write page setup
+ $this->writeSetup();
+ // Write sheet protection
+ $this->writeProtect();
+ // Write SCENPROTECT
+ $this->writeScenProtect();
+ // Write OBJECTPROTECT
+ $this->writeObjectProtect();
+ // Write sheet password
+ $this->writePassword();
+ // Write DEFCOLWIDTH record
+ $this->writeDefcol();
+
+ // Write the COLINFO records if they exist
+ if (!empty($this->columnInfo)) {
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $this->writeColinfo($this->columnInfo[$i]);
+ }
+ }
+ $autoFilterRange = $phpSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ // Write AUTOFILTERINFO
+ $this->writeAutoFilterInfo();
+ }
+
+ // Write sheet dimensions
+ $this->writeDimensions();
+
+ // Row dimensions
+ foreach ($phpSheet->getRowDimensions() as $rowDimension) {
+ $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs
+ $this->writeRow($rowDimension->getRowIndex() - 1, $rowDimension->getRowHeight(), $xfIndex, ($rowDimension->getVisible() ? '0' : '1'), $rowDimension->getOutlineLevel());
+ }
+
+ // Write Cells
+ foreach ($phpSheet->getCoordinates() as $coordinate) {
+ $cell = $phpSheet->getCell($coordinate);
+ $row = $cell->getRow() - 1;
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ // Don't break Excel break the code!
+ if ($row > 65535 || $column > 255) {
+ throw new WriterException('Rows or columns overflow! Excel5 has limit to 65535 rows and 255 columns. Use XLSX instead.');
+ }
+
+ // Write cell value
+ $xfIndex = $cell->getXfIndex() + 15; // there are 15 cell style Xfs
+
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $arrcRun = [];
+ $str_len = StringHelper::countCharacters($cVal->getPlainText(), 'UTF-8');
+ $str_pos = 0;
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ // FONT Index
+ if ($element instanceof Run) {
+ $str_fontidx = $this->fontHashIndex[$element->getFont()->getHashCode()];
+ } else {
+ $str_fontidx = 0;
+ }
+ $arrcRun[] = ['strlen' => $str_pos, 'fontidx' => $str_fontidx];
+ // Position FROM
+ $str_pos += StringHelper::countCharacters($element->getText(), 'UTF-8');
+ }
+ $this->writeRichTextString($row, $column, $cVal->getPlainText(), $xfIndex, $arrcRun);
+ } else {
+ switch ($cell->getDatatype()) {
+ case DataType::TYPE_STRING:
+ case DataType::TYPE_NULL:
+ if ($cVal === '' || $cVal === null) {
+ $this->writeBlank($row, $column, $xfIndex);
+ } else {
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $this->writeNumber($row, $column, $cVal, $xfIndex);
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $calculatedValue = $this->preCalculateFormulas ?
+ $cell->getCalculatedValue() : null;
+ if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) {
+ if ($calculatedValue === null) {
+ $calculatedValue = $cell->getCalculatedValue();
+ }
+ $calctype = gettype($calculatedValue);
+ switch ($calctype) {
+ case 'integer':
+ case 'double':
+ $this->writeNumber($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'string':
+ $this->writeString($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'boolean':
+ $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex);
+
+ break;
+ default:
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+ }
+
+ break;
+ case DataType::TYPE_BOOL:
+ $this->writeBoolErr($row, $column, $cVal, 0, $xfIndex);
+
+ break;
+ case DataType::TYPE_ERROR:
+ $this->writeBoolErr($row, $column, self::mapErrorCode($cVal), 1, $xfIndex);
+
+ break;
+ }
+ }
+ }
+
+ // Append
+ $this->writeMsoDrawing();
+
+ // Restoring active sheet.
+ $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex);
+
+ // Write WINDOW2 record
+ $this->writeWindow2();
+
+ // Write PLV record
+ $this->writePageLayoutView();
+
+ // Write ZOOM record
+ $this->writeZoom();
+ if ($phpSheet->getFreezePane()) {
+ $this->writePanes();
+ }
+
+ // Restoring selected cells.
+ $this->phpSheet->setSelectedCells($selectedCells);
+
+ // Write SELECTION record
+ $this->writeSelection();
+
+ // Write MergedCellsTable Record
+ $this->writeMergedCells();
+
+ // Hyperlinks
+ foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) {
+ [$column, $row] = Coordinate::coordinateFromString($coordinate);
+
+ $url = $hyperlink->getUrl();
+
+ if (strpos($url, 'sheet://') !== false) {
+ // internal to current workbook
+ $url = str_replace('sheet://', 'internal:', $url);
+ } elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) {
+ // URL
+ } else {
+ // external (local file)
+ $url = 'external:' . $url;
+ }
+
+ $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url);
+ }
+
+ $this->writeDataValidity();
+ $this->writeSheetLayout();
+
+ // Write SHEETPROTECTION record
+ $this->writeSheetProtection();
+ $this->writeRangeProtection();
+
+ $arrConditionalStyles = $phpSheet->getConditionalStylesCollection();
+ if (!empty($arrConditionalStyles)) {
+ $arrConditional = [];
+ // @TODO CFRule & CFHeader
+ // Write CFHEADER record
+ $this->writeCFHeader();
+ // Write ConditionalFormattingTable records
+ foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ ) {
+ if (!isset($arrConditional[$conditional->getHashCode()])) {
+ // This hash code has been handled
+ $arrConditional[$conditional->getHashCode()] = true;
+
+ // Write CFRULE record
+ $this->writeCFRule($conditional);
+ }
+ }
+ }
+ }
+ }
+
+ $this->storeEof();
+ }
+
+ /**
+ * Write a cell range address in BIFF8
+ * always fixed range
+ * See section 2.5.14 in OpenOffice.org's Documentation of the Microsoft Excel File Format.
+ *
+ * @param string $range E.g. 'A1' or 'A1:B6'
+ *
+ * @return string Binary data
+ */
+ private function writeBIFF8CellRangeAddressFixed($range)
+ {
+ $explodes = explode(':', $range);
+
+ // extract first cell, e.g. 'A1'
+ $firstCell = $explodes[0];
+
+ // extract last cell, e.g. 'B6'
+ if (count($explodes) == 1) {
+ $lastCell = $firstCell;
+ } else {
+ $lastCell = $explodes[1];
+ }
+
+ $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1]
+ $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6]
+
+ return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1);
+ }
+
+ /**
+ * Retrieves data from memory in one chunk, or from disk in $buffer
+ * sized chunks.
+ *
+ * @return string The data
+ */
+ public function getData()
+ {
+ $buffer = 4096;
+
+ // Return data stored in memory
+ if (isset($this->_data)) {
+ $tmp = $this->_data;
+ $this->_data = null;
+
+ return $tmp;
+ }
+
+ // No data to return
+ return false;
+ }
+
+ /**
+ * Set the option to print the row and column headers on the printed page.
+ *
+ * @param int $print Whether to print the headers or not. Defaults to 1 (print).
+ */
+ public function printRowColHeaders($print = 1): void
+ {
+ $this->printHeaders = $print;
+ }
+
+ /**
+ * This method sets the properties for outlining and grouping. The defaults
+ * correspond to Excel's defaults.
+ *
+ * @param bool $visible
+ * @param bool $symbols_below
+ * @param bool $symbols_right
+ * @param bool $auto_style
+ */
+ public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void
+ {
+ $this->outlineOn = $visible;
+ $this->outlineBelow = $symbols_below;
+ $this->outlineRight = $symbols_right;
+ $this->outlineStyle = $auto_style;
+
+ // Ensure this is a boolean vale for Window2
+ if ($this->outlineOn) {
+ $this->outlineOn = 1;
+ }
+ }
+
+ /**
+ * Write a double to the specified row and column (zero indexed).
+ * An integer can be written as a double. Excel will display an
+ * integer. $format is optional.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param float $num The number to write
+ * @param mixed $xfIndex The optional XF format
+ *
+ * @return int
+ */
+ private function writeNumber($row, $col, $num, $xfIndex)
+ {
+ $record = 0x0203; // Record identifier
+ $length = 0x000E; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $xl_double = pack('d', $num);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $xl_double = strrev($xl_double);
+ }
+
+ $this->append($header . $data . $xl_double);
+
+ return 0;
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex Index to XF record
+ */
+ private function writeString($row, $col, $str, $xfIndex): void
+ {
+ $this->writeLabelSst($row, $col, $str, $xfIndex);
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version
+ * It differs from writeString by the writing of rich text strings.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex The XF format index for the cell
+ * @param array $arrcRun Index to Font record and characters beginning
+ */
+ private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+ $str = StringHelper::UTF8toBIFF8UnicodeShort($str, $arrcRun);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a string to the specified row and column (zero indexed).
+ * This is the BIFF8 version (no 255 chars limit).
+ * $format is optional.
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $str The string to write
+ * @param mixed $xfIndex The XF format index for the cell
+ */
+ private function writeLabelSst($row, $col, $str, $xfIndex): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+
+ $str = StringHelper::UTF8toBIFF8UnicodeLong($str);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a blank cell to the specified row and column (zero indexed).
+ * A blank cell is used to specify formatting without adding a string
+ * or a number.
+ *
+ * A blank cell without a format serves no purpose. Therefore, we don't write
+ * a BLANK record unless a format is specified.
+ *
+ * Returns 0 : normal termination (including no format)
+ * -1 : insufficient number of arguments
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param mixed $xfIndex The XF format index
+ *
+ * @return int
+ */
+ public function writeBlank($row, $col, $xfIndex)
+ {
+ $record = 0x0201; // Record identifier
+ $length = 0x0006; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * Write a boolean or an error type to the specified row and column (zero indexed).
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param int $value
+ * @param bool $isError Error or Boolean?
+ * @param int $xfIndex
+ *
+ * @return int
+ */
+ private function writeBoolErr($row, $col, $value, $isError, $xfIndex)
+ {
+ $record = 0x0205;
+ $length = 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvCC', $row, $col, $xfIndex, $value, $isError);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ const WRITE_FORMULA_NORMAL = 0;
+ const WRITE_FORMULA_ERRORS = -1;
+ const WRITE_FORMULA_RANGE = -2;
+ const WRITE_FORMULA_EXCEPTION = -3;
+
+ /**
+ * Write a formula to the specified row and column (zero indexed).
+ * The textual representation of the formula is passed to the parser in
+ * Parser.php which returns a packed binary string.
+ *
+ * Returns 0 : WRITE_FORMULA_NORMAL normal termination
+ * -1 : WRITE_FORMULA_ERRORS formula errors (bad formula)
+ * -2 : WRITE_FORMULA_RANGE row or column out of range
+ * -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $formula The formula text string
+ * @param mixed $xfIndex The XF format index
+ * @param mixed $calculatedValue Calculated value
+ *
+ * @return int
+ */
+ private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue)
+ {
+ $record = 0x0006; // Record identifier
+ // Initialize possible additional value for STRING record that should be written after the FORMULA record?
+ $stringValue = null;
+
+ // calculated value
+ if (isset($calculatedValue)) {
+ // Since we can't yet get the data type of the calculated value,
+ // we use best effort to determine data type
+ if (is_bool($calculatedValue)) {
+ // Boolean value
+ $num = pack('CCCvCv', 0x01, 0x00, (int) $calculatedValue, 0x00, 0x00, 0xFFFF);
+ } elseif (is_int($calculatedValue) || is_float($calculatedValue)) {
+ // Numeric value
+ $num = pack('d', $calculatedValue);
+ } elseif (is_string($calculatedValue)) {
+ $errorCodes = DataType::getErrorCodes();
+ if (isset($errorCodes[$calculatedValue])) {
+ // Error value
+ $num = pack('CCCvCv', 0x02, 0x00, self::mapErrorCode($calculatedValue), 0x00, 0x00, 0xFFFF);
+ } elseif ($calculatedValue === '') {
+ // Empty string (and BIFF8)
+ $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ } else {
+ // Non-empty string value (or empty string BIFF5)
+ $stringValue = $calculatedValue;
+ $num = pack('CCCvCv', 0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ }
+ } else {
+ // We are really not supposed to reach here
+ $num = pack('d', 0x00);
+ }
+ } else {
+ $num = pack('d', 0x00);
+ }
+
+ $grbit = 0x03; // Option flags
+ $unknown = 0x0000; // Must be zero
+
+ // Strip the '=' or '@' sign at the beginning of the formula string
+ if ($formula[0] == '=') {
+ $formula = substr($formula, 1);
+ } else {
+ // Error handling
+ $this->writeString($row, $col, 'Unrecognised character for formula', 0);
+
+ return self::WRITE_FORMULA_ERRORS;
+ }
+
+ // Parse the formula using the parser in Parser.php
+ try {
+ $error = $this->parser->parse($formula);
+ $formula = $this->parser->toReversePolish();
+
+ $formlen = strlen($formula); // Length of the binary string
+ $length = 0x16 + $formlen; // Length of the record data
+
+ $header = pack('vv', $record, $length);
+
+ $data = pack('vvv', $row, $col, $xfIndex)
+ . $num
+ . pack('vVv', $grbit, $unknown, $formlen);
+ $this->append($header . $data . $formula);
+
+ // Append also a STRING record if necessary
+ if ($stringValue !== null) {
+ $this->writeStringRecord($stringValue);
+ }
+
+ return self::WRITE_FORMULA_NORMAL;
+ } catch (PhpSpreadsheetException $e) {
+ return self::WRITE_FORMULA_EXCEPTION;
+ }
+ }
+
+ /**
+ * Write a STRING record. This.
+ *
+ * @param string $stringValue
+ */
+ private function writeStringRecord($stringValue): void
+ {
+ $record = 0x0207; // Record identifier
+ $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a hyperlink.
+ * This is comprised of two elements: the visible label and
+ * the invisible link. The visible label is the same as the link unless an
+ * alternative string is specified. The label is written using the
+ * writeString() method. Therefore the 255 characters string limit applies.
+ * $string and $format are optional.
+ *
+ * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
+ * directory url.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ * -3 : long string truncated to 255 chars
+ *
+ * @param int $row Row
+ * @param int $col Column
+ * @param string $url URL string
+ *
+ * @return int
+ */
+ private function writeUrl($row, $col, $url)
+ {
+ // Add start row and col to arg list
+ return $this->writeUrlRange($row, $col, $row, $col, $url);
+ }
+
+ /**
+ * This is the more general form of writeUrl(). It allows a hyperlink to be
+ * written to a range of cells. This function also decides the type of hyperlink
+ * to be written. These are either, Web (http, ftp, mailto), Internal
+ * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlRange($row1, $col1, $row2, $col2, $url)
+ {
+ // Check for internal/external sheet links or default to web link
+ if (preg_match('[^internal:]', $url)) {
+ return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url);
+ }
+ if (preg_match('[^external:]', $url)) {
+ return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url);
+ }
+
+ return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url);
+ }
+
+ /**
+ * Used to write http, ftp and mailto hyperlinks.
+ * The link type ($options) is 0x03 is the same as absolute dir ref without
+ * sheet. However it is differentiated by the $unknown2 data stream.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlWeb($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', 'E0C9EA79F9BACE118C8200AA004BA90B');
+
+ // Pack the option flags
+ $options = pack('V', 0x03);
+
+ // Convert URL to a null terminated wchar string
+ $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
+ $url = $url . "\0\0\0";
+
+ // Pack the length of the URL
+ $url_len = pack('V', strlen($url));
+
+ // Calculate the data length
+ $length = 0x34 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Used to write internal reference hyperlinks such as "Sheet1!A1".
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlInternal($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type
+ $url = preg_replace('/^internal:/', '', $url);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+
+ // Pack the option flags
+ $options = pack('V', 0x08);
+
+ // Convert the URL type and to a null terminated wchar string
+ $url .= "\0";
+
+ // character count
+ $url_len = StringHelper::countCharacters($url);
+ $url_len = pack('V', $url_len);
+
+ $url = StringHelper::convertEncoding($url, 'UTF-16LE', 'UTF-8');
+
+ // Calculate the data length
+ $length = 0x24 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Write links to external directory names such as 'c:\foo.xls',
+ * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
+ *
+ * Note: Excel writes some relative links with the $dir_long string. We ignore
+ * these cases for the sake of simpler code.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlExternal($row1, $col1, $row2, $col2, $url)
+ {
+ // Network drives are different. We will handle them separately
+ // MS/Novell network drives and shares start with \\
+ if (preg_match('[^external:\\\\]', $url)) {
+ return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
+ }
+
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type and change Unix dir separator to Dos style (if needed)
+ //
+ $url = preg_replace('/^external:/', '', $url);
+ $url = preg_replace('/\//', '\\', $url);
+
+ // Determine if the link is relative or absolute:
+ // relative if link contains no dir separator, "somefile.xls"
+ // relative if link starts with up-dir, "..\..\somefile.xls"
+ // otherwise, absolute
+
+ $absolute = 0x00; // relative path
+ if (preg_match('/^[A-Z]:/', $url)) {
+ $absolute = 0x02; // absolute path on Windows, e.g. C:\...
+ }
+ $link_type = 0x01 | $absolute;
+
+ // Determine if the link contains a sheet reference and change some of the
+ // parameters accordingly.
+ // Split the dir name and sheet name (if it exists)
+ $dir_long = $url;
+ if (preg_match('/\\#/', $url)) {
+ $link_type |= 0x08;
+ }
+
+ // Pack the link type
+ $link_type = pack('V', $link_type);
+
+ // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
+ $up_count = preg_match_all('/\\.\\.\\\\/', $dir_long, $useless);
+ $up_count = pack('v', $up_count);
+
+ // Store the short dos dir name (null terminated)
+ $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
+
+ // Store the long dir name as a wchar string (non-null terminated)
+ $dir_long = $dir_long . "\0";
+
+ // Pack the lengths of the dir strings
+ $dir_short_len = pack('V', strlen($dir_short));
+ $dir_long_len = pack('V', strlen($dir_long));
+ $stream_len = pack('V', 0); //strlen($dir_long) + 0x06);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', '0303000000000000C000000000000046');
+ $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000');
+ $unknown4 = pack('v', 0x03);
+
+ // Pack the main data stream
+ $data = pack('vvvv', $row1, $row2, $col1, $col2) .
+ $unknown1 .
+ $link_type .
+ $unknown2 .
+ $up_count .
+ $dir_short_len .
+ $dir_short .
+ $unknown3 .
+ $stream_len; /*.
+ $dir_long_len .
+ $unknown4 .
+ $dir_long .
+ $sheet_len .
+ $sheet ;*/
+
+ // Pack the header data
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ // Write the packed data
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * This method is used to set the height and format for a row.
+ *
+ * @param int $row The row to set
+ * @param int $height Height we are giving to the row.
+ * Use null to set XF without setting height
+ * @param int $xfIndex The optional cell style Xf index to apply to the columns
+ * @param bool $hidden The optional hidden attribute
+ * @param int $level The optional outline level for row, in range [0,7]
+ */
+ private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void
+ {
+ $record = 0x0208; // Record identifier
+ $length = 0x0010; // Number of bytes to follow
+
+ $colMic = 0x0000; // First defined column
+ $colMac = 0x0000; // Last defined column
+ $irwMac = 0x0000; // Used by Excel to optimise loading
+ $reserved = 0x0000; // Reserved
+ $grbit = 0x0000; // Option flags
+ $ixfe = $xfIndex;
+
+ if ($height < 0) {
+ $height = null;
+ }
+
+ // Use writeRow($row, null, $XF) to set XF format without setting height
+ if ($height != null) {
+ $miyRw = $height * 20; // row height
+ } else {
+ $miyRw = 0xff; // default row height is 256
+ }
+
+ // Set the options flags. fUnsynced is used to show that the font and row
+ // heights are not compatible. This is usually the case for WriteExcel.
+ // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
+ // is collapsed. Instead it is used to indicate that the previous row is
+ // collapsed. The zero height flag, 0x20, is used to collapse a row.
+
+ $grbit |= $level;
+ if ($hidden) {
+ $grbit |= 0x0030;
+ }
+ if ($height !== null) {
+ $grbit |= 0x0040; // fUnsynced
+ }
+ if ($xfIndex !== 0xF) {
+ $grbit |= 0x0080;
+ }
+ $grbit |= 0x0100;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvv', $row, $colMic, $colMac, $miyRw, $irwMac, $reserved, $grbit, $ixfe);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel DIMENSIONS to define the area in which there is data.
+ */
+ private function writeDimensions(): void
+ {
+ $record = 0x0200; // Record identifier
+
+ $length = 0x000E;
+ $data = pack('VVvvv', $this->firstRowIndex, $this->lastRowIndex + 1, $this->firstColumnIndex, $this->lastColumnIndex + 1, 0x0000); // reserved
+
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record Window2.
+ */
+ private function writeWindow2(): void
+ {
+ $record = 0x023E; // Record identifier
+ $length = 0x0012;
+
+ $grbit = 0x00B6; // Option flags
+ $rwTop = 0x0000; // Top row visible in window
+ $colLeft = 0x0000; // Leftmost column visible in window
+
+ // The options flags that comprise $grbit
+ $fDspFmla = 0; // 0 - bit
+ $fDspGrid = $this->phpSheet->getShowGridlines() ? 1 : 0; // 1
+ $fDspRwCol = $this->phpSheet->getShowRowColHeaders() ? 1 : 0; // 2
+ $fFrozen = $this->phpSheet->getFreezePane() ? 1 : 0; // 3
+ $fDspZeros = 1; // 4
+ $fDefaultHdr = 1; // 5
+ $fArabic = $this->phpSheet->getRightToLeft() ? 1 : 0; // 6
+ $fDspGuts = $this->outlineOn; // 7
+ $fFrozenNoSplit = 0; // 0 - bit
+ // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet
+ $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0;
+ $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW;
+
+ $grbit = $fDspFmla;
+ $grbit |= $fDspGrid << 1;
+ $grbit |= $fDspRwCol << 2;
+ $grbit |= $fFrozen << 3;
+ $grbit |= $fDspZeros << 4;
+ $grbit |= $fDefaultHdr << 5;
+ $grbit |= $fArabic << 6;
+ $grbit |= $fDspGuts << 7;
+ $grbit |= $fFrozenNoSplit << 8;
+ $grbit |= $fSelected << 9; // Selected sheets.
+ $grbit |= $fSelected << 10; // Active sheet.
+ $grbit |= $fPageBreakPreview << 11;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $grbit, $rwTop, $colLeft);
+
+ // FIXME !!!
+ $rgbHdr = 0x0040; // Row/column heading and gridline color index
+ $zoom_factor_page_break = ($fPageBreakPreview ? $this->phpSheet->getSheetView()->getZoomScale() : 0x0000);
+ $zoom_factor_normal = $this->phpSheet->getSheetView()->getZoomScaleNormal();
+
+ $data .= pack('vvvvV', $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFAULTROWHEIGHT.
+ */
+ private function writeDefaultRowHeight(): void
+ {
+ $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight();
+
+ if ($defaultRowHeight < 0) {
+ return;
+ }
+
+ // convert to twips
+ $defaultRowHeight = (int) 20 * $defaultRowHeight;
+
+ $record = 0x0225; // Record identifier
+ $length = 0x0004; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', 1, $defaultRowHeight);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
+ */
+ private function writeDefcol(): void
+ {
+ $defaultColWidth = 8;
+
+ $record = 0x0055; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $defaultColWidth);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record COLINFO to define column widths.
+ *
+ * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
+ * length record.
+ *
+ * @param array $col_array This is the only parameter received and is composed of the following:
+ * 0 => First formatted column,
+ * 1 => Last formatted column,
+ * 2 => Col width (8.43 is Excel default),
+ * 3 => The optional XF format of the column,
+ * 4 => Option flags.
+ * 5 => Optional outline level
+ */
+ private function writeColinfo($col_array): void
+ {
+ if (isset($col_array[0])) {
+ $colFirst = $col_array[0];
+ }
+ if (isset($col_array[1])) {
+ $colLast = $col_array[1];
+ }
+ if (isset($col_array[2])) {
+ $coldx = $col_array[2];
+ } else {
+ $coldx = 8.43;
+ }
+ if (isset($col_array[3])) {
+ $xfIndex = $col_array[3];
+ } else {
+ $xfIndex = 15;
+ }
+ if (isset($col_array[4])) {
+ $grbit = $col_array[4];
+ } else {
+ $grbit = 0;
+ }
+ if (isset($col_array[5])) {
+ $level = $col_array[5];
+ } else {
+ $level = 0;
+ }
+ $record = 0x007D; // Record identifier
+ $length = 0x000C; // Number of bytes to follow
+
+ $coldx *= 256; // Convert to units of 1/256 of a char
+
+ $ixfe = $xfIndex;
+ $reserved = 0x0000; // Reserved
+
+ $level = max(0, min($level, 7));
+ $grbit |= $level << 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvv', $colFirst, $colLast, $coldx, $ixfe, $grbit, $reserved);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record SELECTION.
+ */
+ private function writeSelection(): void
+ {
+ // look up the selected cell range
+ $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells());
+ $selectedCells = $selectedCells[0];
+ if (count($selectedCells) == 2) {
+ [$first, $last] = $selectedCells;
+ } else {
+ $first = $selectedCells[0];
+ $last = $selectedCells[0];
+ }
+
+ [$colFirst, $rwFirst] = Coordinate::coordinateFromString($first);
+ $colFirst = Coordinate::columnIndexFromString($colFirst) - 1; // base 0 column index
+ --$rwFirst; // base 0 row index
+
+ [$colLast, $rwLast] = Coordinate::coordinateFromString($last);
+ $colLast = Coordinate::columnIndexFromString($colLast) - 1; // base 0 column index
+ --$rwLast; // base 0 row index
+
+ // make sure we are not out of bounds
+ $colFirst = min($colFirst, 255);
+ $colLast = min($colLast, 255);
+
+ $rwFirst = min($rwFirst, 65535);
+ $rwLast = min($rwLast, 65535);
+
+ $record = 0x001D; // Record identifier
+ $length = 0x000F; // Number of bytes to follow
+
+ $pnn = $this->activePane; // Pane position
+ $rwAct = $rwFirst; // Active row
+ $colAct = $colFirst; // Active column
+ $irefAct = 0; // Active cell ref
+ $cref = 1; // Number of refs
+
+ if (!isset($rwLast)) {
+ $rwLast = $rwFirst; // Last row in reference
+ }
+ if (!isset($colLast)) {
+ $colLast = $colFirst; // Last col in reference
+ }
+
+ // Swap last row/col for first row/col as necessary
+ if ($rwFirst > $rwLast) {
+ [$rwFirst, $rwLast] = [$rwLast, $rwFirst];
+ }
+
+ if ($colFirst > $colLast) {
+ [$colFirst, $colLast] = [$colLast, $colFirst];
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('CvvvvvvCC', $pnn, $rwAct, $colAct, $irefAct, $cref, $rwFirst, $rwLast, $colFirst, $colLast);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the MERGEDCELLS records for all ranges of merged cells.
+ */
+ private function writeMergedCells(): void
+ {
+ $mergeCells = $this->phpSheet->getMergeCells();
+ $countMergeCells = count($mergeCells);
+
+ if ($countMergeCells == 0) {
+ return;
+ }
+
+ // maximum allowed number of merged cells per record
+ $maxCountMergeCellsPerRecord = 1027;
+
+ // record identifier
+ $record = 0x00E5;
+
+ // counter for total number of merged cells treated so far by the writer
+ $i = 0;
+
+ // counter for number of merged cells written in record currently being written
+ $j = 0;
+
+ // initialize record data
+ $recordData = '';
+
+ // loop through the merged cells
+ foreach ($mergeCells as $mergeCell) {
+ ++$i;
+ ++$j;
+
+ // extract the row and column indexes
+ $range = Coordinate::splitRange($mergeCell);
+ [$first, $last] = $range[0];
+ [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first);
+ [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last);
+
+ $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1);
+
+ // flush record if we have reached limit for number of merged cells, or reached final merged cell
+ if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) {
+ $recordData = pack('v', $j) . $recordData;
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+
+ // initialize for next record, if any
+ $recordData = '';
+ $j = 0;
+ }
+ }
+ }
+
+ /**
+ * Write SHEETLAYOUT record.
+ */
+ private function writeSheetLayout(): void
+ {
+ if (!$this->phpSheet->isTabColorSet()) {
+ return;
+ }
+
+ $recordData = pack(
+ 'vvVVVvv',
+ 0x0862,
+ 0x0000, // unused
+ 0x00000000, // unused
+ 0x00000000, // unused
+ 0x00000014, // size of record data
+ $this->colors[$this->phpSheet->getTabColor()->getRGB()], // color index
+ 0x0000 // unused
+ );
+
+ $length = strlen($recordData);
+
+ $record = 0x0862; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write SHEETPROTECTION.
+ */
+ private function writeSheetProtection(): void
+ {
+ // record identifier
+ $record = 0x0867;
+
+ // prepare options
+ $options = (int) !$this->phpSheet->getProtection()->getObjects()
+ | (int) !$this->phpSheet->getProtection()->getScenarios() << 1
+ | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2
+ | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3
+ | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4
+ | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5
+ | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6
+ | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7
+ | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8
+ | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9
+ | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10
+ | (int) !$this->phpSheet->getProtection()->getSort() << 11
+ | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12
+ | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13
+ | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14;
+
+ // record data
+ $recordData = pack(
+ 'vVVCVVvv',
+ 0x0867, // repeated record identifier
+ 0x0000, // not used
+ 0x0000, // not used
+ 0x00, // not used
+ 0x01000200, // unknown data
+ 0xFFFFFFFF, // unknown data
+ $options, // options
+ 0x0000 // not used
+ );
+
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write BIFF record RANGEPROTECTION.
+ *
+ * Openoffice.org's Documentaion of the Microsoft Excel File Format uses term RANGEPROTECTION for these records
+ * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records
+ */
+ private function writeRangeProtection(): void
+ {
+ foreach ($this->phpSheet->getProtectedCells() as $range => $password) {
+ // number of ranges, e.g. 'A1:B3 C20:D25'
+ $cellRanges = explode(' ', $range);
+ $cref = count($cellRanges);
+
+ $recordData = pack(
+ 'vvVVvCVvVv',
+ 0x0868,
+ 0x00,
+ 0x0000,
+ 0x0000,
+ 0x02,
+ 0x0,
+ 0x0000,
+ $cref,
+ 0x0000,
+ 0x00
+ );
+
+ foreach ($cellRanges as $cellRange) {
+ $recordData .= $this->writeBIFF8CellRangeAddressFixed($cellRange);
+ }
+
+ // the rgbFeat structure
+ $recordData .= pack(
+ 'VV',
+ 0x0000,
+ hexdec($password)
+ );
+
+ $recordData .= StringHelper::UTF8toBIFF8UnicodeLong('p' . md5($recordData));
+
+ $length = strlen($recordData);
+
+ $record = 0x0868; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+ }
+
+ /**
+ * Writes the Excel BIFF PANE record.
+ * The panes can either be frozen or thawed (unfrozen).
+ * Frozen panes are specified in terms of an integer number of rows and columns.
+ * Thawed panes are specified in terms of Excel's units for rows and columns.
+ */
+ private function writePanes(): void
+ {
+ $panes = [];
+ if ($this->phpSheet->getFreezePane()) {
+ [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane());
+ $panes[0] = Coordinate::columnIndexFromString($column) - 1;
+ $panes[1] = $row - 1;
+
+ [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell());
+ //Coordinates are zero-based in xls files
+ $panes[2] = $topRow - 1;
+ $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1;
+ } else {
+ // thaw panes
+ return;
+ }
+
+ $x = $panes[0] ?? null;
+ $y = $panes[1] ?? null;
+ $rwTop = $panes[2] ?? null;
+ $colLeft = $panes[3] ?? null;
+ if (count($panes) > 4) { // if Active pane was received
+ $pnnAct = $panes[4];
+ } else {
+ $pnnAct = null;
+ }
+ $record = 0x0041; // Record identifier
+ $length = 0x000A; // Number of bytes to follow
+
+ // Code specific to frozen or thawed panes.
+ if ($this->phpSheet->getFreezePane()) {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = $y;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = $x;
+ }
+ } else {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = 0;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = 0;
+ }
+
+ // Convert Excel's row and column units to the internal units.
+ // The default row height is 12.75
+ // The default column width is 8.43
+ // The following slope and intersection values were interpolated.
+ //
+ $y = 20 * $y + 255;
+ $x = 113.879 * $x + 390;
+ }
+
+ // Determine which pane should be active. There is also the undocumented
+ // option to override this should it be necessary: may be removed later.
+ //
+ if (!isset($pnnAct)) {
+ if ($x != 0 && $y != 0) {
+ $pnnAct = 0; // Bottom right
+ }
+ if ($x != 0 && $y == 0) {
+ $pnnAct = 1; // Top right
+ }
+ if ($x == 0 && $y != 0) {
+ $pnnAct = 2; // Bottom left
+ }
+ if ($x == 0 && $y == 0) {
+ $pnnAct = 3; // Top left
+ }
+ }
+
+ $this->activePane = $pnnAct; // Used in writeSelection
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvv', $x, $y, $rwTop, $colLeft, $pnnAct);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the page setup SETUP BIFF record.
+ */
+ private function writeSetup(): void
+ {
+ $record = 0x00A1; // Record identifier
+ $length = 0x0022; // Number of bytes to follow
+
+ $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size
+
+ $iScale = $this->phpSheet->getPageSetup()->getScale() ?
+ $this->phpSheet->getPageSetup()->getScale() : 100; // Print scaling factor
+
+ $iPageStart = 0x01; // Starting page number
+ $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide
+ $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high
+ $grbit = 0x00; // Option flags
+ $iRes = 0x0258; // Print resolution
+ $iVRes = 0x0258; // Vertical print resolution
+
+ $numHdr = $this->phpSheet->getPageMargins()->getHeader(); // Header Margin
+
+ $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin
+ $iCopies = 0x01; // Number of copies
+
+ // Order of printing pages
+ $fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER
+ ? 0x1 : 0x0;
+ // Page orientation
+ $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
+ ? 0x0 : 0x1;
+
+ $fNoPls = 0x0; // Setup not read from printer
+ $fNoColor = 0x0; // Print black and white
+ $fDraft = 0x0; // Print draft quality
+ $fNotes = 0x0; // Print notes
+ $fNoOrient = 0x0; // Orientation not set
+ $fUsePage = 0x0; // Use custom starting page
+
+ $grbit = $fLeftToRight;
+ $grbit |= $fLandscape << 1;
+ $grbit |= $fNoPls << 2;
+ $grbit |= $fNoColor << 3;
+ $grbit |= $fDraft << 4;
+ $grbit |= $fNotes << 5;
+ $grbit |= $fNoOrient << 6;
+ $grbit |= $fUsePage << 7;
+
+ $numHdr = pack('d', $numHdr);
+ $numFtr = pack('d', $numFtr);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $numHdr = strrev($numHdr);
+ $numFtr = strrev($numFtr);
+ }
+
+ $header = pack('vv', $record, $length);
+ $data1 = pack('vvvvvvvv', $iPaperSize, $iScale, $iPageStart, $iFitWidth, $iFitHeight, $grbit, $iRes, $iVRes);
+ $data2 = $numHdr . $numFtr;
+ $data3 = pack('v', $iCopies);
+ $this->append($header . $data1 . $data2 . $data3);
+ }
+
+ /**
+ * Store the header caption BIFF record.
+ */
+ private function writeHeader(): void
+ {
+ $record = 0x0014; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddHeader()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddHeader(); // header string
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddHeader());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the footer caption BIFF record.
+ */
+ private function writeFooter(): void
+ {
+ $record = 0x0015; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddFooter()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddFooter();
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddFooter());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the horizontal centering HCENTER BIFF record.
+ */
+ private function writeHcenter(): void
+ {
+ $record = 0x0083; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fHCenter = $this->phpSheet->getPageSetup()->getHorizontalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fHCenter);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the vertical centering VCENTER BIFF record.
+ */
+ private function writeVcenter(): void
+ {
+ $record = 0x0084; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fVCenter = $this->phpSheet->getPageSetup()->getVerticalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fVCenter);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the LEFTMARGIN BIFF record.
+ */
+ private function writeMarginLeft(): void
+ {
+ $record = 0x0026; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getLeft(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the RIGHTMARGIN BIFF record.
+ */
+ private function writeMarginRight(): void
+ {
+ $record = 0x0027; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getRight(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the TOPMARGIN BIFF record.
+ */
+ private function writeMarginTop(): void
+ {
+ $record = 0x0028; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getTop(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the BOTTOMMARGIN BIFF record.
+ */
+ private function writeMarginBottom(): void
+ {
+ $record = 0x0029; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getBottom(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTHEADERS BIFF record.
+ */
+ private function writePrintHeaders(): void
+ {
+ $record = 0x002a; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintRwCol = $this->printHeaders; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintRwCol);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
+ * GRIDSET record.
+ */
+ private function writePrintGridlines(): void
+ {
+ $record = 0x002b; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintGrid = $this->phpSheet->getPrintGridlines() ? 1 : 0; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintGrid);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GRIDSET BIFF record. Must be used in conjunction with the
+ * PRINTGRIDLINES record.
+ */
+ private function writeGridset(): void
+ {
+ $record = 0x0082; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fGridSet = !$this->phpSheet->getPrintGridlines(); // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fGridSet);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet.
+ */
+ private function writeAutoFilterInfo(): void
+ {
+ $record = 0x009D; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $rangeBounds = Coordinate::rangeBoundaries($this->phpSheet->getAutoFilter()->getRange());
+ $iNumFilters = 1 + $rangeBounds[1][0] - $rangeBounds[0][0];
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $iNumFilters);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GUTS BIFF record. This is used to configure the gutter margins
+ * where Excel outline symbols are displayed. The visibility of the gutters is
+ * controlled by a flag in WSBOOL.
+ *
+ * @see writeWsbool()
+ */
+ private function writeGuts(): void
+ {
+ $record = 0x0080; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $dxRwGut = 0x0000; // Size of row gutter
+ $dxColGut = 0x0000; // Size of col gutter
+
+ // determine maximum row outline level
+ $maxRowOutlineLevel = 0;
+ foreach ($this->phpSheet->getRowDimensions() as $rowDimension) {
+ $maxRowOutlineLevel = max($maxRowOutlineLevel, $rowDimension->getOutlineLevel());
+ }
+
+ $col_level = 0;
+
+ // Calculate the maximum column outline level. The equivalent calculation
+ // for the row outline level is carried out in writeRow().
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $col_level = max($this->columnInfo[$i][5], $col_level);
+ }
+
+ // Set the limits for the outline levels (0 <= x <= 7).
+ $col_level = max(0, min($col_level, 7));
+
+ // The displayed level is one greater than the max outline levels
+ if ($maxRowOutlineLevel) {
+ ++$maxRowOutlineLevel;
+ }
+ if ($col_level) {
+ ++$col_level;
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $dxRwGut, $dxColGut, $maxRowOutlineLevel, $col_level);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
+ * with the SETUP record.
+ */
+ private function writeWsbool(): void
+ {
+ $record = 0x0081; // Record identifier
+ $length = 0x0002; // Bytes to follow
+ $grbit = 0x0000;
+
+ // The only option that is of interest is the flag for fit to page. So we
+ // set all the options in one go.
+ //
+ // Set the option flags
+ $grbit |= 0x0001; // Auto page breaks visible
+ if ($this->outlineStyle) {
+ $grbit |= 0x0020; // Auto outline styles
+ }
+ if ($this->phpSheet->getShowSummaryBelow()) {
+ $grbit |= 0x0040; // Outline summary below
+ }
+ if ($this->phpSheet->getShowSummaryRight()) {
+ $grbit |= 0x0080; // Outline summary right
+ }
+ if ($this->phpSheet->getPageSetup()->getFitToPage()) {
+ $grbit |= 0x0100; // Page setup fit to page
+ }
+ if ($this->outlineOn) {
+ $grbit |= 0x0400; // Outline symbols displayed
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records.
+ */
+ private function writeBreaks(): void
+ {
+ // initialize
+ $vbreaks = [];
+ $hbreaks = [];
+
+ foreach ($this->phpSheet->getBreaks() as $cell => $breakType) {
+ // Fetch coordinates
+ $coordinates = Coordinate::coordinateFromString($cell);
+
+ // Decide what to do by the type of break
+ switch ($breakType) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN:
+ // Add to list of vertical breaks
+ $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW:
+ // Add to list of horizontal breaks
+ $hbreaks[] = $coordinates[1];
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE:
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+
+ //horizontal page breaks
+ if (!empty($hbreaks)) {
+ // Sort and filter array of page breaks
+ sort($hbreaks, SORT_NUMERIC);
+ if ($hbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($hbreaks);
+ }
+
+ $record = 0x001b; // Record identifier
+ $cbrk = count($hbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($hbreaks as $hbreak) {
+ $data .= pack('vvv', $hbreak, 0x0000, 0x00ff);
+ }
+
+ $this->append($header . $data);
+ }
+
+ // vertical page breaks
+ if (!empty($vbreaks)) {
+ // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
+ // It is slightly higher in Excel 97/200, approx. 1026
+ $vbreaks = array_slice($vbreaks, 0, 1000);
+
+ // Sort and filter array of page breaks
+ sort($vbreaks, SORT_NUMERIC);
+ if ($vbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($vbreaks);
+ }
+
+ $record = 0x001a; // Record identifier
+ $cbrk = count($vbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($vbreaks as $vbreak) {
+ $data .= pack('vvv', $vbreak, 0x0000, 0xffff);
+ }
+
+ $this->append($header . $data);
+ }
+ }
+
+ /**
+ * Set the Biff PROTECT record to indicate that the worksheet is protected.
+ */
+ private function writeProtect(): void
+ {
+ // Exit unless sheet protection has been specified
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ $record = 0x0012; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fLock = 1; // Worksheet is protected
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fLock);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write SCENPROTECT.
+ */
+ private function writeScenProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if scenarios are not protected
+ if (!$this->phpSheet->getProtection()->getScenarios()) {
+ return;
+ }
+
+ $record = 0x00DD; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write OBJECTPROTECT.
+ */
+ private function writeObjectProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if objects are not protected
+ if (!$this->phpSheet->getProtection()->getObjects()) {
+ return;
+ }
+
+ $record = 0x0063; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the worksheet PASSWORD record.
+ */
+ private function writePassword(): void
+ {
+ // Exit unless sheet protection and password have been specified
+ if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) {
+ return;
+ }
+
+ $record = 0x0013; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $wPassword = hexdec($this->phpSheet->getProtection()->getPassword()); // Encoded password
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $wPassword);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Insert a 24bit bitmap image in a worksheet.
+ *
+ * @param int $row The row we are going to insert the bitmap into
+ * @param int $col The column we are going to insert the bitmap into
+ * @param mixed $bitmap The bitmap filename or GD-image resource
+ * @param int $x the horizontal position (offset) of the image inside the cell
+ * @param int $y the vertical position (offset) of the image inside the cell
+ * @param float $scale_x The horizontal scale
+ * @param float $scale_y The vertical scale
+ */
+ public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void
+ {
+ $bitmap_array = (is_resource($bitmap) ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap));
+ [$width, $height, $size, $data] = $bitmap_array;
+
+ // Scale the frame of the image.
+ $width *= $scale_x;
+ $height *= $scale_y;
+
+ // Calculate the vertices of the image and write the OBJ record
+ $this->positionImage($col, $row, $x, $y, $width, $height);
+
+ // Write the IMDATA record to store the bitmap data
+ $record = 0x007f;
+ $length = 8 + $size;
+ $cf = 0x09;
+ $env = 0x01;
+ $lcb = $size;
+
+ $header = pack('vvvvV', $record, $length, $cf, $env, $lcb);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Calculate the vertices that define the position of the image as required by
+ * the OBJ record.
+ *
+ * +------------+------------+
+ * | A | B |
+ * +-----+------------+------------+
+ * | |(x1,y1) | |
+ * | 1 |(A1)._______|______ |
+ * | | | | |
+ * | | | | |
+ * +-----+----| BITMAP |-----+
+ * | | | | |
+ * | 2 | |______________. |
+ * | | | (B2)|
+ * | | | (x2,y2)|
+ * +---- +------------+------------+
+ *
+ * Example of a bitmap that covers some of the area from cell A1 to cell B2.
+ *
+ * Based on the width and height of the bitmap we need to calculate 8 vars:
+ * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
+ * The width and height of the cells are also variable and have to be taken into
+ * account.
+ * The values of $col_start and $row_start are passed in from the calling
+ * function. The values of $col_end and $row_end are calculated by subtracting
+ * the width and height of the bitmap from the width and height of the
+ * underlying cells.
+ * The vertices are expressed as a percentage of the underlying cell width as
+ * follows (rhs values are in pixels):
+ *
+ * x1 = X / W *1024
+ * y1 = Y / H *256
+ * x2 = (X-1) / W *1024
+ * y2 = (Y-1) / H *256
+ *
+ * Where: X is distance from the left side of the underlying cell
+ * Y is distance from the top of the underlying cell
+ * W is the width of the cell
+ * H is the height of the cell
+ * The SDK incorrectly states that the height should be expressed as a
+ * percentage of 1024.
+ *
+ * @param int $col_start Col containing upper left corner of object
+ * @param int $row_start Row containing top left corner of object
+ * @param int $x1 Distance to left side of object
+ * @param int $y1 Distance to top of object
+ * @param int $width Width of image frame
+ * @param int $height Height of image frame
+ */
+ public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void
+ {
+ // Initialise end cell to the same as the start cell
+ $col_end = $col_start; // Col containing lower right corner of object
+ $row_end = $row_start; // Row containing bottom right corner of object
+
+ // Zero the specified offset if greater than the cell dimensions
+ if ($x1 >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1))) {
+ $x1 = 0;
+ }
+ if ($y1 >= Xls::sizeRow($this->phpSheet, $row_start + 1)) {
+ $y1 = 0;
+ }
+
+ $width = $width + $x1 - 1;
+ $height = $height + $y1 - 1;
+
+ // Subtract the underlying cell widths to find the end cell of the image
+ while ($width >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1))) {
+ $width -= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1));
+ ++$col_end;
+ }
+
+ // Subtract the underlying cell heights to find the end cell of the image
+ while ($height >= Xls::sizeRow($this->phpSheet, $row_end + 1)) {
+ $height -= Xls::sizeRow($this->phpSheet, $row_end + 1);
+ ++$row_end;
+ }
+
+ // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+ // with zero eight or width.
+ //
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_start + 1) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_end + 1) == 0) {
+ return;
+ }
+
+ // Convert the pixel values to the percentage value expected by Excel
+ $x1 = $x1 / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) * 1024;
+ $y1 = $y1 / Xls::sizeRow($this->phpSheet, $row_start + 1) * 256;
+ $x2 = $width / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) * 1024; // Distance to right side of object
+ $y2 = $height / Xls::sizeRow($this->phpSheet, $row_end + 1) * 256; // Distance to bottom of object
+
+ $this->writeObjPicture($col_start, $x1, $row_start, $y1, $col_end, $x2, $row_end, $y2);
+ }
+
+ /**
+ * Store the OBJ record that precedes an IMDATA record. This could be generalise
+ * to support other Excel objects.
+ *
+ * @param int $colL Column containing upper left corner of object
+ * @param int $dxL Distance from left side of cell
+ * @param int $rwT Row containing top left corner of object
+ * @param int $dyT Distance from top of cell
+ * @param int $colR Column containing lower right corner of object
+ * @param int $dxR Distance from right of cell
+ * @param int $rwB Row containing bottom right corner of object
+ * @param int $dyB Distance from bottom of cell
+ */
+ private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void
+ {
+ $record = 0x005d; // Record identifier
+ $length = 0x003c; // Bytes to follow
+
+ $cObj = 0x0001; // Count of objects in file (set to 1)
+ $OT = 0x0008; // Object type. 8 = Picture
+ $id = 0x0001; // Object ID
+ $grbit = 0x0614; // Option flags
+
+ $cbMacro = 0x0000; // Length of FMLA structure
+ $Reserved1 = 0x0000; // Reserved
+ $Reserved2 = 0x0000; // Reserved
+
+ $icvBack = 0x09; // Background colour
+ $icvFore = 0x09; // Foreground colour
+ $fls = 0x00; // Fill pattern
+ $fAuto = 0x00; // Automatic fill
+ $icv = 0x08; // Line colour
+ $lns = 0xff; // Line style
+ $lnw = 0x01; // Line weight
+ $fAutoB = 0x00; // Automatic border
+ $frs = 0x0000; // Frame style
+ $cf = 0x0009; // Image format, 9 = bitmap
+ $Reserved3 = 0x0000; // Reserved
+ $cbPictFmla = 0x0000; // Length of FMLA structure
+ $Reserved4 = 0x0000; // Reserved
+ $grbit2 = 0x0001; // Option flags
+ $Reserved5 = 0x0000; // Reserved
+
+ $header = pack('vv', $record, $length);
+ $data = pack('V', $cObj);
+ $data .= pack('v', $OT);
+ $data .= pack('v', $id);
+ $data .= pack('v', $grbit);
+ $data .= pack('v', $colL);
+ $data .= pack('v', $dxL);
+ $data .= pack('v', $rwT);
+ $data .= pack('v', $dyT);
+ $data .= pack('v', $colR);
+ $data .= pack('v', $dxR);
+ $data .= pack('v', $rwB);
+ $data .= pack('v', $dyB);
+ $data .= pack('v', $cbMacro);
+ $data .= pack('V', $Reserved1);
+ $data .= pack('v', $Reserved2);
+ $data .= pack('C', $icvBack);
+ $data .= pack('C', $icvFore);
+ $data .= pack('C', $fls);
+ $data .= pack('C', $fAuto);
+ $data .= pack('C', $icv);
+ $data .= pack('C', $lns);
+ $data .= pack('C', $lnw);
+ $data .= pack('C', $fAutoB);
+ $data .= pack('v', $frs);
+ $data .= pack('V', $cf);
+ $data .= pack('v', $Reserved3);
+ $data .= pack('v', $cbPictFmla);
+ $data .= pack('v', $Reserved4);
+ $data .= pack('v', $grbit2);
+ $data .= pack('V', $Reserved5);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Convert a GD-image into the internal format.
+ *
+ * @param resource $image The image to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmapGd($image)
+ {
+ $width = imagesx($image);
+ $height = imagesy($image);
+
+ $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ for ($j = $height; --$j;) {
+ for ($i = 0; $i < $width; ++$i) {
+ $color = imagecolorsforindex($image, imagecolorat($image, $i, $j));
+ foreach (['red', 'green', 'blue'] as $key) {
+ $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127);
+ }
+ $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']);
+ }
+ if (3 * $width % 4) {
+ $data .= str_repeat("\x00", 4 - 3 * $width % 4);
+ }
+ }
+
+ return [$width, $height, strlen($data), $data];
+ }
+
+ /**
+ * Convert a 24 bit bitmap into the modified internal format used by Windows.
+ * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
+ * MSDN library.
+ *
+ * @param string $bitmap The bitmap to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmap($bitmap)
+ {
+ // Open file.
+ $bmp_fd = @fopen($bitmap, 'rb');
+ if (!$bmp_fd) {
+ throw new WriterException("Couldn't import $bitmap");
+ }
+
+ // Slurp the file into a string.
+ $data = fread($bmp_fd, filesize($bitmap));
+
+ // Check that the file is big enough to be a bitmap.
+ if (strlen($data) <= 0x36) {
+ throw new WriterException("$bitmap doesn't contain enough data.\n");
+ }
+
+ // The first 2 bytes are used to identify the bitmap.
+ $identity = unpack('A2ident', $data);
+ if ($identity['ident'] != 'BM') {
+ throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n");
+ }
+
+ // Remove bitmap data: ID.
+ $data = substr($data, 2);
+
+ // Read and remove the bitmap size. This is more reliable than reading
+ // the data size at offset 0x22.
+ //
+ $size_array = unpack('Vsa', substr($data, 0, 4));
+ $size = $size_array['sa'];
+ $data = substr($data, 4);
+ $size -= 0x36; // Subtract size of bitmap header.
+ $size += 0x0C; // Add size of BIFF header.
+
+ // Remove bitmap data: reserved, offset, header length.
+ $data = substr($data, 12);
+
+ // Read and remove the bitmap width and height. Verify the sizes.
+ $width_and_height = unpack('V2', substr($data, 0, 8));
+ $width = $width_and_height[1];
+ $height = $width_and_height[2];
+ $data = substr($data, 8);
+ if ($width > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image width supported is 65k.\n");
+ }
+ if ($height > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image height supported is 65k.\n");
+ }
+
+ // Read and remove the bitmap planes and bpp data. Verify them.
+ $planes_and_bitcount = unpack('v2', substr($data, 0, 4));
+ $data = substr($data, 4);
+ if ($planes_and_bitcount[2] != 24) { // Bitcount
+ throw new WriterException("$bitmap isn't a 24bit true color bitmap.\n");
+ }
+ if ($planes_and_bitcount[1] != 1) {
+ throw new WriterException("$bitmap: only 1 plane supported in bitmap image.\n");
+ }
+
+ // Read and remove the bitmap compression. Verify compression.
+ $compression = unpack('Vcomp', substr($data, 0, 4));
+ $data = substr($data, 4);
+
+ if ($compression['comp'] != 0) {
+ throw new WriterException("$bitmap: compression not supported in bitmap image.\n");
+ }
+
+ // Remove bitmap data: data size, hres, vres, colours, imp. colours.
+ $data = substr($data, 20);
+
+ // Add the BITMAPCOREHEADER data
+ $header = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ $data = $header . $data;
+
+ return [$width, $height, $size, $data];
+ }
+
+ /**
+ * Store the window zoom factor. This should be a reduced fraction but for
+ * simplicity we will store all fractions with a numerator of 100.
+ */
+ private function writeZoom(): void
+ {
+ // If scale is 100 we don't need to write a record
+ if ($this->phpSheet->getSheetView()->getZoomScale() == 100) {
+ return;
+ }
+
+ $record = 0x00A0; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->phpSheet->getSheetView()->getZoomScale(), 100);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+
+ /**
+ * Write MSODRAWING record.
+ */
+ private function writeMsoDrawing(): void
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+ // write the neccesary MSODRAWING, OBJ records
+
+ // split the Escher stream
+ $spOffsets[0] = 0;
+ $nm = count($spOffsets) - 1; // number of shapes excluding first shape
+ for ($i = 1; $i <= $nm; ++$i) {
+ // MSODRAWING record
+ $record = 0x00EC; // Record identifier
+
+ // chunk of Escher stream for one shape
+ $dataChunk = substr($data, $spOffsets[$i - 1], $spOffsets[$i] - $spOffsets[$i - 1]);
+
+ $length = strlen($dataChunk);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $dataChunk);
+
+ // OBJ record
+ $record = 0x005D; // record identifier
+ $objData = '';
+
+ // ftCmo
+ if ($spTypes[$i] == 0x00C9) {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0014, // object type, 0x0014 = filter
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x2101, // option flags, 0x2001 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+
+ // Add ftSbs Scroll bar subobject
+ $objData .= pack('vv', 0x00C, 0x0014);
+ $objData .= pack('H*', '0000000000000000640001000A00000010000100');
+ // Add ftLbsData (List box data) subobject
+ $objData .= pack('vv', 0x0013, 0x1FEE);
+ $objData .= pack('H*', '00000000010001030000020008005700');
+ } else {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0008, // object type, 0x0008 = picture
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x6011, // option flags, 0x6011 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+ }
+
+ // ftEnd
+ $objData .=
+ pack(
+ 'vv',
+ 0x0000, // 0x0000 = ftEnd
+ 0x0000 // length of ftEnd data
+ );
+
+ $length = strlen($objData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $objData);
+ }
+ }
+ }
+
+ /**
+ * Store the DATAVALIDATIONS and DATAVALIDATION records.
+ */
+ private function writeDataValidity(): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $this->phpSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ // DATAVALIDATIONS record
+ $record = 0x01B2; // Record identifier
+ $length = 0x0012; // Bytes to follow
+
+ $grbit = 0x0000; // Prompt box at cell, no cached validity data at DV records
+ $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
+ $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
+ $objId = 0xFFFFFFFF; // Object identifier of drop down arrow object, or -1 if not visible
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId, count($dataValidationCollection));
+ $this->append($header . $data);
+
+ // DATAVALIDATION records
+ $record = 0x01BE; // Record identifier
+
+ foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) {
+ // initialize record data
+ $data = '';
+
+ // options
+ $options = 0x00000000;
+
+ // data type
+ $type = 0x00;
+ switch ($dataValidation->getType()) {
+ case DataValidation::TYPE_NONE:
+ $type = 0x00;
+
+ break;
+ case DataValidation::TYPE_WHOLE:
+ $type = 0x01;
+
+ break;
+ case DataValidation::TYPE_DECIMAL:
+ $type = 0x02;
+
+ break;
+ case DataValidation::TYPE_LIST:
+ $type = 0x03;
+
+ break;
+ case DataValidation::TYPE_DATE:
+ $type = 0x04;
+
+ break;
+ case DataValidation::TYPE_TIME:
+ $type = 0x05;
+
+ break;
+ case DataValidation::TYPE_TEXTLENGTH:
+ $type = 0x06;
+
+ break;
+ case DataValidation::TYPE_CUSTOM:
+ $type = 0x07;
+
+ break;
+ }
+
+ $options |= $type << 0;
+
+ // error style
+ $errorStyle = 0x00;
+ switch ($dataValidation->getErrorStyle()) {
+ case DataValidation::STYLE_STOP:
+ $errorStyle = 0x00;
+
+ break;
+ case DataValidation::STYLE_WARNING:
+ $errorStyle = 0x01;
+
+ break;
+ case DataValidation::STYLE_INFORMATION:
+ $errorStyle = 0x02;
+
+ break;
+ }
+
+ $options |= $errorStyle << 4;
+
+ // explicit formula?
+ if ($type == 0x03 && preg_match('/^\".*\"$/', $dataValidation->getFormula1())) {
+ $options |= 0x01 << 7;
+ }
+
+ // empty cells allowed
+ $options |= $dataValidation->getAllowBlank() << 8;
+
+ // show drop down
+ $options |= (!$dataValidation->getShowDropDown()) << 9;
+
+ // show input message
+ $options |= $dataValidation->getShowInputMessage() << 18;
+
+ // show error message
+ $options |= $dataValidation->getShowErrorMessage() << 19;
+
+ // condition operator
+ $operator = 0x00;
+ switch ($dataValidation->getOperator()) {
+ case DataValidation::OPERATOR_BETWEEN:
+ $operator = 0x00;
+
+ break;
+ case DataValidation::OPERATOR_NOTBETWEEN:
+ $operator = 0x01;
+
+ break;
+ case DataValidation::OPERATOR_EQUAL:
+ $operator = 0x02;
+
+ break;
+ case DataValidation::OPERATOR_NOTEQUAL:
+ $operator = 0x03;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHAN:
+ $operator = 0x04;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHAN:
+ $operator = 0x05;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHANOREQUAL:
+ $operator = 0x06;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHANOREQUAL:
+ $operator = 0x07;
+
+ break;
+ }
+
+ $options |= $operator << 20;
+
+ $data = pack('V', $options);
+
+ // prompt title
+ $promptTitle = $dataValidation->getPromptTitle() !== '' ?
+ $dataValidation->getPromptTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($promptTitle);
+
+ // error title
+ $errorTitle = $dataValidation->getErrorTitle() !== '' ?
+ $dataValidation->getErrorTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($errorTitle);
+
+ // prompt text
+ $prompt = $dataValidation->getPrompt() !== '' ?
+ $dataValidation->getPrompt() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($prompt);
+
+ // error text
+ $error = $dataValidation->getError() !== '' ?
+ $dataValidation->getError() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($error);
+
+ // formula 1
+ try {
+ $formula1 = $dataValidation->getFormula1();
+ if ($type == 0x03) { // list type
+ $formula1 = str_replace(',', chr(0), $formula1);
+ }
+ $this->parser->parse($formula1);
+ $formula1 = $this->parser->toReversePolish();
+ $sz1 = strlen($formula1);
+ } catch (PhpSpreadsheetException $e) {
+ $sz1 = 0;
+ $formula1 = '';
+ }
+ $data .= pack('vv', $sz1, 0x0000);
+ $data .= $formula1;
+
+ // formula 2
+ try {
+ $formula2 = $dataValidation->getFormula2();
+ if ($formula2 === '') {
+ throw new WriterException('No formula2');
+ }
+ $this->parser->parse($formula2);
+ $formula2 = $this->parser->toReversePolish();
+ $sz2 = strlen($formula2);
+ } catch (PhpSpreadsheetException $e) {
+ $sz2 = 0;
+ $formula2 = '';
+ }
+ $data .= pack('vv', $sz2, 0x0000);
+ $data .= $formula2;
+
+ // cell range address list
+ $data .= pack('v', 0x0001);
+ $data .= $this->writeBIFF8CellRangeAddressFixed($cellCoordinate);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+ }
+ }
+
+ /**
+ * Map Error code.
+ *
+ * @param string $errorCode
+ *
+ * @return int
+ */
+ private static function mapErrorCode($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return 0x00;
+ case '#DIV/0!':
+ return 0x07;
+ case '#VALUE!':
+ return 0x0F;
+ case '#REF!':
+ return 0x17;
+ case '#NAME?':
+ return 0x1D;
+ case '#NUM!':
+ return 0x24;
+ case '#N/A':
+ return 0x2A;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Write PLV Record.
+ */
+ private function writePageLayoutView(): void
+ {
+ $record = 0x088B; // Record identifier
+ $length = 0x0010; // Bytes to follow
+
+ $rt = 0x088B; // 2
+ $grbitFrt = 0x0000; // 2
+ $reserved = 0x0000000000000000; // 8
+ $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2
+
+ // The options flags that comprise $grbit
+ if ($this->phpSheet->getSheetView()->getView() == SheetView::SHEETVIEW_PAGE_LAYOUT) {
+ $fPageLayoutView = 1;
+ } else {
+ $fPageLayoutView = 0;
+ }
+ $fRulerVisible = 0;
+ $fWhitespaceHidden = 0;
+
+ $grbit = $fPageLayoutView; // 2
+ $grbit |= $fRulerVisible << 1;
+ $grbit |= $fWhitespaceHidden << 3;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvVVvv', $rt, $grbitFrt, 0x00000000, 0x00000000, $wScalvePLV, $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFRule Record.
+ */
+ private function writeCFRule(Conditional $conditional): void
+ {
+ $record = 0x01B1; // Record identifier
+
+ // $type : Type of the CF
+ // $operatorType : Comparison operator
+ if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
+ $type = 0x02;
+ $operatorType = 0x00;
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
+ $type = 0x01;
+
+ switch ($conditional->getOperatorType()) {
+ case Conditional::OPERATOR_NONE:
+ $operatorType = 0x00;
+
+ break;
+ case Conditional::OPERATOR_EQUAL:
+ $operatorType = 0x03;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHAN:
+ $operatorType = 0x05;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHANOREQUAL:
+ $operatorType = 0x07;
+
+ break;
+ case Conditional::OPERATOR_LESSTHAN:
+ $operatorType = 0x06;
+
+ break;
+ case Conditional::OPERATOR_LESSTHANOREQUAL:
+ $operatorType = 0x08;
+
+ break;
+ case Conditional::OPERATOR_NOTEQUAL:
+ $operatorType = 0x04;
+
+ break;
+ case Conditional::OPERATOR_BETWEEN:
+ $operatorType = 0x01;
+
+ break;
+ // not OPERATOR_NOTBETWEEN 0x02
+ }
+ }
+
+ // $szValue1 : size of the formula data for first value or formula
+ // $szValue2 : size of the formula data for second value or formula
+ $arrConditions = $conditional->getConditions();
+ $numConditions = count($arrConditions);
+ if ($numConditions == 1) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = 0x0000;
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = null;
+ } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000);
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = pack('Cv', 0x1E, $arrConditions[1]);
+ } else {
+ $szValue1 = 0x0000;
+ $szValue2 = 0x0000;
+ $operand1 = null;
+ $operand2 = null;
+ }
+
+ // $flags : Option flags
+ // Alignment
+ $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() == null ? 1 : 0);
+ $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() == null ? 1 : 0);
+ $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() == false ? 1 : 0);
+ $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() == null ? 1 : 0);
+ $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() == 0 ? 1 : 0);
+ $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() == false ? 1 : 0);
+ if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) {
+ $bFormatAlign = 1;
+ } else {
+ $bFormatAlign = 0;
+ }
+ // Protection
+ $bProtLocked = ($conditional->getStyle()->getProtection()->getLocked() == null ? 1 : 0);
+ $bProtHidden = ($conditional->getStyle()->getProtection()->getHidden() == null ? 1 : 0);
+ if ($bProtLocked == 0 || $bProtHidden == 0) {
+ $bFormatProt = 1;
+ } else {
+ $bFormatProt = 0;
+ }
+ // Border
+ $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) {
+ $bFormatBorder = 1;
+ } else {
+ $bFormatBorder = 0;
+ }
+ // Pattern
+ $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() == null ? 0 : 1);
+ $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1);
+ $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1);
+ if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) {
+ $bFormatFill = 1;
+ } else {
+ $bFormatFill = 0;
+ }
+ // Font
+ if (
+ $conditional->getStyle()->getFont()->getName() != null
+ || $conditional->getStyle()->getFont()->getSize() != null
+ || $conditional->getStyle()->getFont()->getBold() != null
+ || $conditional->getStyle()->getFont()->getItalic() != null
+ || $conditional->getStyle()->getFont()->getSuperscript() != null
+ || $conditional->getStyle()->getFont()->getSubscript() != null
+ || $conditional->getStyle()->getFont()->getUnderline() != null
+ || $conditional->getStyle()->getFont()->getStrikethrough() != null
+ || $conditional->getStyle()->getFont()->getColor()->getARGB() != null
+ ) {
+ $bFormatFont = 1;
+ } else {
+ $bFormatFont = 0;
+ }
+ // Alignment
+ $flags = 0;
+ $flags |= (1 == $bAlignHz ? 0x00000001 : 0);
+ $flags |= (1 == $bAlignVt ? 0x00000002 : 0);
+ $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0);
+ $flags |= (1 == $bTxRotation ? 0x00000008 : 0);
+ // Justify last line flag
+ $flags |= (1 == 1 ? 0x00000010 : 0);
+ $flags |= (1 == $bIndent ? 0x00000020 : 0);
+ $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0);
+ // Default
+ $flags |= (1 == 1 ? 0x00000080 : 0);
+ // Protection
+ $flags |= (1 == $bProtLocked ? 0x00000100 : 0);
+ $flags |= (1 == $bProtHidden ? 0x00000200 : 0);
+ // Border
+ $flags |= (1 == $bBorderLeft ? 0x00000400 : 0);
+ $flags |= (1 == $bBorderRight ? 0x00000800 : 0);
+ $flags |= (1 == $bBorderTop ? 0x00001000 : 0);
+ $flags |= (1 == $bBorderBottom ? 0x00002000 : 0);
+ $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border
+ $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border
+ // Pattern
+ $flags |= (1 == $bFillStyle ? 0x00010000 : 0);
+ $flags |= (1 == $bFillColor ? 0x00020000 : 0);
+ $flags |= (1 == $bFillColorBg ? 0x00040000 : 0);
+ $flags |= (1 == 1 ? 0x00380000 : 0);
+ // Font
+ $flags |= (1 == $bFormatFont ? 0x04000000 : 0);
+ // Alignment:
+ $flags |= (1 == $bFormatAlign ? 0x08000000 : 0);
+ // Border
+ $flags |= (1 == $bFormatBorder ? 0x10000000 : 0);
+ // Pattern
+ $flags |= (1 == $bFormatFill ? 0x20000000 : 0);
+ // Protection
+ $flags |= (1 == $bFormatProt ? 0x40000000 : 0);
+ // Text direction
+ $flags |= (1 == 0 ? 0x80000000 : 0);
+
+ // Data Blocks
+ if ($bFormatFont == 1) {
+ // Font Name
+ if ($conditional->getStyle()->getFont()->getName() == null) {
+ $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ } else {
+ $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName());
+ }
+ // Font Size
+ if ($conditional->getStyle()->getFont()->getSize() == null) {
+ $dataBlockFont .= pack('V', 20 * 11);
+ } else {
+ $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize());
+ }
+ // Font Options
+ $dataBlockFont .= pack('V', 0);
+ // Font weight
+ if ($conditional->getStyle()->getFont()->getBold() == true) {
+ $dataBlockFont .= pack('v', 0x02BC);
+ } else {
+ $dataBlockFont .= pack('v', 0x0190);
+ }
+ // Escapement type
+ if ($conditional->getStyle()->getFont()->getSubscript() == true) {
+ $dataBlockFont .= pack('v', 0x02);
+ $fontEscapement = 0;
+ } elseif ($conditional->getStyle()->getFont()->getSuperscript() == true) {
+ $dataBlockFont .= pack('v', 0x01);
+ $fontEscapement = 0;
+ } else {
+ $dataBlockFont .= pack('v', 0x00);
+ $fontEscapement = 1;
+ }
+ // Underline type
+ switch ($conditional->getStyle()->getFont()->getUnderline()) {
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE:
+ $dataBlockFont .= pack('C', 0x02);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x22);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE:
+ $dataBlockFont .= pack('C', 0x01);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x21);
+ $fontUnderline = 0;
+
+ break;
+ default:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 1;
+
+ break;
+ }
+ // Not used (3)
+ $dataBlockFont .= pack('vC', 0x0000, 0x00);
+ // Font color index
+ switch ($conditional->getStyle()->getFont()->getColor()->getRGB()) {
+ case '000000':
+ $colorIdx = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdx = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdx = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdx = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdx = 0x10;
+
+ break;
+ case '008000':
+ $colorIdx = 0x11;
+
+ break;
+ case '000080':
+ $colorIdx = 0x12;
+
+ break;
+ case '808000':
+ $colorIdx = 0x13;
+
+ break;
+ case '800080':
+ $colorIdx = 0x14;
+
+ break;
+ case '008080':
+ $colorIdx = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdx = 0x16;
+
+ break;
+ case '808080':
+ $colorIdx = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdx = 0x18;
+
+ break;
+ case '993366':
+ $colorIdx = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdx = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdx = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdx = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdx = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdx = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdx = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x23;
+
+ break;
+ case '800080':
+ $colorIdx = 0x24;
+
+ break;
+ case '800000':
+ $colorIdx = 0x25;
+
+ break;
+ case '008080':
+ $colorIdx = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdx = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdx = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdx = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdx = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdx = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdx = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdx = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdx = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdx = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdx = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdx = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdx = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdx = 0x35;
+
+ break;
+ case '666699':
+ $colorIdx = 0x36;
+
+ break;
+ case '969696':
+ $colorIdx = 0x37;
+
+ break;
+ case '003366':
+ $colorIdx = 0x38;
+
+ break;
+ case '339966':
+ $colorIdx = 0x39;
+
+ break;
+ case '003300':
+ $colorIdx = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdx = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdx = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdx = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdx = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdx = 0x3F;
+
+ break;
+ default:
+ $colorIdx = 0x00;
+
+ break;
+ }
+ $dataBlockFont .= pack('V', $colorIdx);
+ // Not used (4)
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Options flags for modified font attributes
+ $optionsFlags = 0;
+ $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() == null ? 1 : 0);
+ $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000008 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000010 : 0);
+ $optionsFlags |= (1 == 0 ? 0x00000020 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000080 : 0);
+ $dataBlockFont .= pack('V', $optionsFlags);
+ // Escapement type
+ $dataBlockFont .= pack('V', $fontEscapement);
+ // Underline type
+ $dataBlockFont .= pack('V', $fontUnderline);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Not used (8)
+ $dataBlockFont .= pack('VV', 0x00000000, 0x00000000);
+ // Always
+ $dataBlockFont .= pack('v', 0x0001);
+ }
+ if ($bFormatAlign == 1) {
+ $blockAlign = 0;
+ // Alignment and text break
+ switch ($conditional->getStyle()->getAlignment()->getHorizontal()) {
+ case Alignment::HORIZONTAL_GENERAL:
+ $blockAlign = 0;
+
+ break;
+ case Alignment::HORIZONTAL_LEFT:
+ $blockAlign = 1;
+
+ break;
+ case Alignment::HORIZONTAL_RIGHT:
+ $blockAlign = 3;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER:
+ $blockAlign = 2;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER_CONTINUOUS:
+ $blockAlign = 6;
+
+ break;
+ case Alignment::HORIZONTAL_JUSTIFY:
+ $blockAlign = 5;
+
+ break;
+ }
+ if ($conditional->getStyle()->getAlignment()->getWrapText() == true) {
+ $blockAlign |= 1 << 3;
+ } else {
+ $blockAlign |= 0 << 3;
+ }
+ switch ($conditional->getStyle()->getAlignment()->getVertical()) {
+ case Alignment::VERTICAL_BOTTOM:
+ $blockAlign = 2 << 4;
+
+ break;
+ case Alignment::VERTICAL_TOP:
+ $blockAlign = 0 << 4;
+
+ break;
+ case Alignment::VERTICAL_CENTER:
+ $blockAlign = 1 << 4;
+
+ break;
+ case Alignment::VERTICAL_JUSTIFY:
+ $blockAlign = 3 << 4;
+
+ break;
+ }
+ $blockAlign |= 0 << 7;
+
+ // Text rotation angle
+ $blockRotation = $conditional->getStyle()->getAlignment()->getTextRotation();
+
+ // Indentation
+ $blockIndent = $conditional->getStyle()->getAlignment()->getIndent();
+ if ($conditional->getStyle()->getAlignment()->getShrinkToFit() == true) {
+ $blockIndent |= 1 << 4;
+ } else {
+ $blockIndent |= 0 << 4;
+ }
+ $blockIndent |= 0 << 6;
+
+ // Relative indentation
+ $blockIndentRelative = 255;
+
+ $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000);
+ }
+ if ($bFormatBorder == 1) {
+ $blockLineStyle = 0;
+ switch ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 4;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 4;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 4;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 4;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 4;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 4;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 4;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 4;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 4;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 4;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 4;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 8;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 8;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 8;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 8;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 8;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 8;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 8;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 8;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 8;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 8;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 8;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 12;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 12;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 12;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 12;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 12;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 12;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 12;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 12;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 12;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 12;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 12;
+
+ break;
+ }
+
+ // TODO writeCFRule() => $blockLineStyle => Index Color for left line
+ // TODO writeCFRule() => $blockLineStyle => Index Color for right line
+ // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off
+ // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off
+ $blockColor = 0;
+ // TODO writeCFRule() => $blockColor => Index Color for top line
+ // TODO writeCFRule() => $blockColor => Index Color for bottom line
+ // TODO writeCFRule() => $blockColor => Index Color for diagonal line
+ switch ($conditional->getStyle()->getBorders()->getDiagonal()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockColor |= 0x00 << 21;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockColor |= 0x01 << 21;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockColor |= 0x02 << 21;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockColor |= 0x03 << 21;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockColor |= 0x04 << 21;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockColor |= 0x05 << 21;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockColor |= 0x06 << 21;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockColor |= 0x07 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockColor |= 0x08 << 21;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockColor |= 0x09 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockColor |= 0x0A << 21;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockColor |= 0x0B << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockColor |= 0x0C << 21;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockColor |= 0x0D << 21;
+
+ break;
+ }
+ $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor);
+ }
+ if ($bFormatFill == 1) {
+ // Fill Patern Style
+ $blockFillPatternStyle = 0;
+ switch ($conditional->getStyle()->getFill()->getFillType()) {
+ case Fill::FILL_NONE:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ case Fill::FILL_SOLID:
+ $blockFillPatternStyle = 0x01;
+
+ break;
+ case Fill::FILL_PATTERN_MEDIUMGRAY:
+ $blockFillPatternStyle = 0x02;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRAY:
+ $blockFillPatternStyle = 0x03;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRAY:
+ $blockFillPatternStyle = 0x04;
+
+ break;
+ case Fill::FILL_PATTERN_DARKHORIZONTAL:
+ $blockFillPatternStyle = 0x05;
+
+ break;
+ case Fill::FILL_PATTERN_DARKVERTICAL:
+ $blockFillPatternStyle = 0x06;
+
+ break;
+ case Fill::FILL_PATTERN_DARKDOWN:
+ $blockFillPatternStyle = 0x07;
+
+ break;
+ case Fill::FILL_PATTERN_DARKUP:
+ $blockFillPatternStyle = 0x08;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRID:
+ $blockFillPatternStyle = 0x09;
+
+ break;
+ case Fill::FILL_PATTERN_DARKTRELLIS:
+ $blockFillPatternStyle = 0x0A;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTHORIZONTAL:
+ $blockFillPatternStyle = 0x0B;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTVERTICAL:
+ $blockFillPatternStyle = 0x0C;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTDOWN:
+ $blockFillPatternStyle = 0x0D;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTUP:
+ $blockFillPatternStyle = 0x0E;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRID:
+ $blockFillPatternStyle = 0x0F;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTTRELLIS:
+ $blockFillPatternStyle = 0x10;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY125:
+ $blockFillPatternStyle = 0x11;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY0625:
+ $blockFillPatternStyle = 0x12;
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ case Fill::FILL_GRADIENT_PATH:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ default:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ }
+ // Color
+ switch ($conditional->getStyle()->getFill()->getStartColor()->getRGB()) {
+ case '000000':
+ $colorIdxBg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxBg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxBg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxBg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxBg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxBg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxBg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxBg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxBg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxBg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxBg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxBg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxBg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxBg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxBg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxBg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxBg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxBg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxBg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxBg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxBg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxBg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxBg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxBg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxBg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxBg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxBg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxBg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxBg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxBg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxBg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxBg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxBg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxBg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxBg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxBg = 0x3F;
+
+ break;
+ default:
+ $colorIdxBg = 0x41;
+
+ break;
+ }
+ // Fg Color
+ switch ($conditional->getStyle()->getFill()->getEndColor()->getRGB()) {
+ case '000000':
+ $colorIdxFg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxFg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxFg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxFg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxFg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxFg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxFg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxFg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxFg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxFg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxFg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxFg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxFg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxFg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxFg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxFg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxFg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxFg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxFg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxFg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxFg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxFg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxFg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxFg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxFg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxFg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxFg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxFg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxFg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxFg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxFg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxFg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxFg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxFg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxFg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxFg = 0x3F;
+
+ break;
+ default:
+ $colorIdxFg = 0x40;
+
+ break;
+ }
+ $dataBlockFill = pack('v', $blockFillPatternStyle);
+ $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7));
+ }
+ if ($bFormatProt == 1) {
+ $dataBlockProtection = 0;
+ if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1;
+ }
+ if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1 << 1;
+ }
+ }
+
+ $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000);
+ if ($bFormatFont == 1) { // Block Formatting : OK
+ $data .= $dataBlockFont;
+ }
+ if ($bFormatAlign == 1) {
+ $data .= $dataBlockAlign;
+ }
+ if ($bFormatBorder == 1) {
+ $data .= $dataBlockBorder;
+ }
+ if ($bFormatFill == 1) { // Block Formatting : OK
+ $data .= $dataBlockFill;
+ }
+ if ($bFormatProt == 1) {
+ $data .= $dataBlockProtection;
+ }
+ if ($operand1 !== null) {
+ $data .= $operand1;
+ }
+ if ($operand2 !== null) {
+ $data .= $operand2;
+ }
+ $header = pack('vv', $record, strlen($data));
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFHeader record.
+ */
+ private function writeCFHeader(): void
+ {
+ $record = 0x01B0; // Record identifier
+ $length = 0x0016; // Bytes to follow
+
+ $numColumnMin = null;
+ $numColumnMax = null;
+ $numRowMin = null;
+ $numRowMax = null;
+ $arrConditional = [];
+ foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ ) {
+ if (!in_array($conditional->getHashCode(), $arrConditional)) {
+ $arrConditional[] = $conditional->getHashCode();
+ }
+ // Cells
+ $arrCoord = Coordinate::coordinateFromString($cellCoordinate);
+ if (!is_numeric($arrCoord[0])) {
+ $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]);
+ }
+ if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) {
+ $numColumnMin = $arrCoord[0];
+ }
+ if ($numColumnMax === null || ($numColumnMax < $arrCoord[0])) {
+ $numColumnMax = $arrCoord[0];
+ }
+ if ($numRowMin === null || ($numRowMin > $arrCoord[1])) {
+ $numRowMin = $arrCoord[1];
+ }
+ if ($numRowMax === null || ($numRowMax < $arrCoord[1])) {
+ $numRowMax = $arrCoord[1];
+ }
+ }
+ }
+ }
+ $needRedraw = 1;
+ $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1);
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', count($arrConditional), $needRedraw);
+ $data .= $cellRange;
+ $data .= pack('v', 0x0001);
+ $data .= $cellRange;
+ $this->append($header . $data);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php new file mode 100644 index 0000000..ba584b0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php @@ -0,0 +1,548 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Xf
+{
+ /**
+ * Style XF or a cell XF ?
+ *
+ * @var bool
+ */
+ private $isStyleXf;
+
+ /**
+ * Index to the FONT record. Index 4 does not exist.
+ *
+ * @var int
+ */
+ private $fontIndex;
+
+ /**
+ * An index (2 bytes) to a FORMAT record (number format).
+ *
+ * @var int
+ */
+ private $numberFormatIndex;
+
+ /**
+ * 1 bit, apparently not used.
+ *
+ * @var int
+ */
+ private $textJustLast;
+
+ /**
+ * The cell's foreground color.
+ *
+ * @var int
+ */
+ private $foregroundColor;
+
+ /**
+ * The cell's background color.
+ *
+ * @var int
+ */
+ private $backgroundColor;
+
+ /**
+ * Color of the bottom border of the cell.
+ *
+ * @var int
+ */
+ private $bottomBorderColor;
+
+ /**
+ * Color of the top border of the cell.
+ *
+ * @var int
+ */
+ private $topBorderColor;
+
+ /**
+ * Color of the left border of the cell.
+ *
+ * @var int
+ */
+ private $leftBorderColor;
+
+ /**
+ * Color of the right border of the cell.
+ *
+ * @var int
+ */
+ private $rightBorderColor;
+
+ /**
+ * Constructor.
+ *
+ * @param Style $style The XF format
+ */
+ public function __construct(Style $style)
+ {
+ $this->isStyleXf = false;
+ $this->fontIndex = 0;
+
+ $this->numberFormatIndex = 0;
+
+ $this->textJustLast = 0;
+
+ $this->foregroundColor = 0x40;
+ $this->backgroundColor = 0x41;
+
+ $this->_diag = 0;
+
+ $this->bottomBorderColor = 0x40;
+ $this->topBorderColor = 0x40;
+ $this->leftBorderColor = 0x40;
+ $this->rightBorderColor = 0x40;
+ $this->_diag_color = 0x40;
+ $this->_style = $style;
+ }
+
+ /**
+ * Generate an Excel BIFF XF record (style or cell).
+ *
+ * @return string The XF record
+ */
+ public function writeXf()
+ {
+ // Set the type of the XF record and some of the attributes.
+ if ($this->isStyleXf) {
+ $style = 0xFFF5;
+ } else {
+ $style = self::mapLocked($this->_style->getProtection()->getLocked());
+ $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1;
+ }
+
+ // Flags to indicate if attributes have been set.
+ $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0;
+ $atr_fnt = ($this->fontIndex != 0) ? 1 : 0;
+ $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0;
+ $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0;
+ $atr_pat = (($this->foregroundColor != 0x40) ||
+ ($this->backgroundColor != 0x41) ||
+ self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0;
+ $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked())
+ | self::mapHidden($this->_style->getProtection()->getHidden());
+
+ // Zero the default border colour if the border has not been set.
+ if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) {
+ $this->bottomBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) {
+ $this->topBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) {
+ $this->rightBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) {
+ $this->leftBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) {
+ $this->_diag_color = 0;
+ }
+
+ $record = 0x00E0; // Record identifier
+ $length = 0x0014; // Number of bytes to follow
+
+ $ifnt = $this->fontIndex; // Index to FONT record
+ $ifmt = $this->numberFormatIndex; // Index to FORMAT record
+
+ $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment
+ $align |= (int) $this->_style->getAlignment()->getWrapText() << 3;
+ $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4;
+ $align |= $this->textJustLast << 7;
+
+ $used_attrib = $atr_num << 2;
+ $used_attrib |= $atr_fnt << 3;
+ $used_attrib |= $atr_alc << 4;
+ $used_attrib |= $atr_bdr << 5;
+ $used_attrib |= $atr_pat << 6;
+ $used_attrib |= $atr_prot << 7;
+
+ $icv = $this->foregroundColor; // fg and bg pattern colors
+ $icv |= $this->backgroundColor << 7;
+
+ $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12;
+ $border1 |= $this->leftBorderColor << 16;
+ $border1 |= $this->rightBorderColor << 23;
+
+ $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection();
+ $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_DOWN;
+ $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_UP;
+ $border1 |= $diag_tl_to_rb << 30;
+ $border1 |= $diag_tr_to_lb << 31;
+
+ $border2 = $this->topBorderColor; // Border color
+ $border2 |= $this->bottomBorderColor << 7;
+ $border2 |= $this->_diag_color << 14;
+ $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21;
+ $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26;
+
+ $header = pack('vv', $record, $length);
+
+ //BIFF8 options: identation, shrinkToFit and text direction
+ $biff8_options = $this->_style->getAlignment()->getIndent();
+ $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4;
+
+ $data = pack('vvvC', $ifnt, $ifmt, $style, $align);
+ $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib);
+ $data .= pack('VVv', $border1, $border2, $icv);
+
+ return $header . $data;
+ }
+
+ /**
+ * Is this a style XF ?
+ *
+ * @param bool $value
+ */
+ public function setIsStyleXf($value): void
+ {
+ $this->isStyleXf = $value;
+ }
+
+ /**
+ * Sets the cell's bottom border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBottomColor($colorIndex): void
+ {
+ $this->bottomBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's top border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setTopColor($colorIndex): void
+ {
+ $this->topBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's left border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setLeftColor($colorIndex): void
+ {
+ $this->leftBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's right border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setRightColor($colorIndex): void
+ {
+ $this->rightBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's diagonal border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setDiagColor($colorIndex): void
+ {
+ $this->_diag_color = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's foreground color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setFgColor($colorIndex): void
+ {
+ $this->foregroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's background color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBgColor($colorIndex): void
+ {
+ $this->backgroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the index to the number format record
+ * It can be date, time, currency, etc...
+ *
+ * @param int $numberFormatIndex Index to format record
+ */
+ public function setNumberFormatIndex($numberFormatIndex): void
+ {
+ $this->numberFormatIndex = $numberFormatIndex;
+ }
+
+ /**
+ * Set the font index.
+ *
+ * @param int $value Font index, note that value 4 does not exist
+ */
+ public function setFontIndex($value): void
+ {
+ $this->fontIndex = $value;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for border styles.
+ *
+ * @var array of int
+ */
+ private static $mapBorderStyles = [
+ Border::BORDER_NONE => 0x00,
+ Border::BORDER_THIN => 0x01,
+ Border::BORDER_MEDIUM => 0x02,
+ Border::BORDER_DASHED => 0x03,
+ Border::BORDER_DOTTED => 0x04,
+ Border::BORDER_THICK => 0x05,
+ Border::BORDER_DOUBLE => 0x06,
+ Border::BORDER_HAIR => 0x07,
+ Border::BORDER_MEDIUMDASHED => 0x08,
+ Border::BORDER_DASHDOT => 0x09,
+ Border::BORDER_MEDIUMDASHDOT => 0x0A,
+ Border::BORDER_DASHDOTDOT => 0x0B,
+ Border::BORDER_MEDIUMDASHDOTDOT => 0x0C,
+ Border::BORDER_SLANTDASHDOT => 0x0D,
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param string $borderStyle
+ *
+ * @return int
+ */
+ private static function mapBorderStyle($borderStyle)
+ {
+ if (isset(self::$mapBorderStyles[$borderStyle])) {
+ return self::$mapBorderStyles[$borderStyle];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for fill types.
+ *
+ * @var array of int
+ */
+ private static $mapFillTypes = [
+ Fill::FILL_NONE => 0x00,
+ Fill::FILL_SOLID => 0x01,
+ Fill::FILL_PATTERN_MEDIUMGRAY => 0x02,
+ Fill::FILL_PATTERN_DARKGRAY => 0x03,
+ Fill::FILL_PATTERN_LIGHTGRAY => 0x04,
+ Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05,
+ Fill::FILL_PATTERN_DARKVERTICAL => 0x06,
+ Fill::FILL_PATTERN_DARKDOWN => 0x07,
+ Fill::FILL_PATTERN_DARKUP => 0x08,
+ Fill::FILL_PATTERN_DARKGRID => 0x09,
+ Fill::FILL_PATTERN_DARKTRELLIS => 0x0A,
+ Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B,
+ Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C,
+ Fill::FILL_PATTERN_LIGHTDOWN => 0x0D,
+ Fill::FILL_PATTERN_LIGHTUP => 0x0E,
+ Fill::FILL_PATTERN_LIGHTGRID => 0x0F,
+ Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10,
+ Fill::FILL_PATTERN_GRAY125 => 0x11,
+ Fill::FILL_PATTERN_GRAY0625 => 0x12,
+ Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8
+ Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8
+ ];
+
+ /**
+ * Map fill type.
+ *
+ * @param string $fillType
+ *
+ * @return int
+ */
+ private static function mapFillType($fillType)
+ {
+ if (isset(self::$mapFillTypes[$fillType])) {
+ return self::$mapFillTypes[$fillType];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @var array of int
+ */
+ private static $mapHAlignments = [
+ Alignment::HORIZONTAL_GENERAL => 0,
+ Alignment::HORIZONTAL_LEFT => 1,
+ Alignment::HORIZONTAL_CENTER => 2,
+ Alignment::HORIZONTAL_RIGHT => 3,
+ Alignment::HORIZONTAL_FILL => 4,
+ Alignment::HORIZONTAL_JUSTIFY => 5,
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @param string $hAlign
+ *
+ * @return int
+ */
+ private function mapHAlign($hAlign)
+ {
+ if (isset(self::$mapHAlignments[$hAlign])) {
+ return self::$mapHAlignments[$hAlign];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @var array of int
+ */
+ private static $mapVAlignments = [
+ Alignment::VERTICAL_TOP => 0,
+ Alignment::VERTICAL_CENTER => 1,
+ Alignment::VERTICAL_BOTTOM => 2,
+ Alignment::VERTICAL_JUSTIFY => 3,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @param string $vAlign
+ *
+ * @return int
+ */
+ private static function mapVAlign($vAlign)
+ {
+ if (isset(self::$mapVAlignments[$vAlign])) {
+ return self::$mapVAlignments[$vAlign];
+ }
+
+ return 2;
+ }
+
+ /**
+ * Map to BIFF8 codes for text rotation angle.
+ *
+ * @param int $textRotation
+ *
+ * @return int
+ */
+ private static function mapTextRotation($textRotation)
+ {
+ if ($textRotation >= 0) {
+ return $textRotation;
+ } elseif ($textRotation == -165) {
+ return 255;
+ } elseif ($textRotation < 0) {
+ return 90 - $textRotation;
+ }
+ }
+
+ /**
+ * Map locked.
+ *
+ * @param string $locked
+ *
+ * @return int
+ */
+ private static function mapLocked($locked)
+ {
+ switch ($locked) {
+ case Protection::PROTECTION_INHERIT:
+ return 1;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+
+ /**
+ * Map hidden.
+ *
+ * @param string $hidden
+ *
+ * @return int
+ */
+ private static function mapHidden($hidden)
+ {
+ switch ($hidden) {
+ case Protection::PROTECTION_INHERIT:
+ return 0;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php new file mode 100644 index 0000000..f34e233 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php @@ -0,0 +1,548 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\HashTable;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
+use ZipArchive;
+use ZipStream\Exception\OverflowException;
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class Xlsx extends BaseWriter
+{
+ /**
+ * Office2003 compatibility.
+ *
+ * @var bool
+ */
+ private $office2003compatibility = false;
+
+ /**
+ * Private writer parts.
+ *
+ * @var Xlsx\WriterPart[]
+ */
+ private $writerParts = [];
+
+ /**
+ * Private Spreadsheet.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadSheet;
+
+ /**
+ * Private string table.
+ *
+ * @var string[]
+ */
+ private $stringTable = [];
+
+ /**
+ * Private unique Conditional HashTable.
+ *
+ * @var HashTable
+ */
+ private $stylesConditionalHashTable;
+
+ /**
+ * Private unique Style HashTable.
+ *
+ * @var HashTable
+ */
+ private $styleHashTable;
+
+ /**
+ * Private unique Fill HashTable.
+ *
+ * @var HashTable
+ */
+ private $fillHashTable;
+
+ /**
+ * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
+ *
+ * @var HashTable
+ */
+ private $fontHashTable;
+
+ /**
+ * Private unique Borders HashTable.
+ *
+ * @var HashTable
+ */
+ private $bordersHashTable;
+
+ /**
+ * Private unique NumberFormat HashTable.
+ *
+ * @var HashTable
+ */
+ private $numFmtHashTable;
+
+ /**
+ * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
+ *
+ * @var HashTable
+ */
+ private $drawingHashTable;
+
+ /**
+ * Private handle for zip stream.
+ *
+ * @var ZipStream
+ */
+ private $zip;
+
+ /**
+ * Create a new Xlsx Writer.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ // Assign PhpSpreadsheet
+ $this->setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'stringtable' => StringTable::class,
+ 'contenttypes' => ContentTypes::class,
+ 'docprops' => DocProps::class,
+ 'rels' => Rels::class,
+ 'theme' => Theme::class,
+ 'style' => Style::class,
+ 'workbook' => Workbook::class,
+ 'worksheet' => Worksheet::class,
+ 'drawing' => Drawing::class,
+ 'comments' => Comments::class,
+ 'chart' => Chart::class,
+ 'relsvba' => RelsVBA::class,
+ 'relsribbonobjects' => RelsRibbon::class,
+ ];
+
+ // Initialise writer parts
+ // and Assign their parent IWriters
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+
+ $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable',
+ 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable',
+ 'styleHashTable',
+ ];
+
+ // Set HashTable variables
+ foreach ($hashTablesArray as $tableName) {
+ $this->$tableName = new HashTable();
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // garbage collect
+ $this->pathNames = [];
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // Create string lookup table
+ $this->stringTable = [];
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
+ }
+
+ // Create styles dictionaries
+ $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet));
+ $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet));
+ $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet));
+ $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet));
+ $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet));
+ $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet));
+
+ // Create drawing dictionary
+ $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet));
+
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ $this->zip = new ZipStream(null, $options);
+
+ // Add [Content_Types].xml to ZIP file
+ $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts));
+
+ //if hasMacros, add the vbaProject.bin file, Certificate file(if exists)
+ if ($this->spreadSheet->hasMacros()) {
+ $macrosCode = $this->spreadSheet->getMacrosCode();
+ if ($macrosCode !== null) {
+ // we have the code ?
+ $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin
+ if ($this->spreadSheet->hasMacrosCertificate()) {
+ //signed macros ?
+ // Yes : add the certificate file and the related rels file
+ $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate());
+ $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet));
+ }
+ }
+ }
+ //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels)
+ if ($this->spreadSheet->hasRibbon()) {
+ $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target');
+ $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data'));
+ if ($this->spreadSheet->hasRibbonBinObjects()) {
+ $tmpRootPath = dirname($tmpRibbonTarget) . '/';
+ $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write
+ foreach ($ribbonBinObjects as $aPath => $aContent) {
+ $this->addZipFile($tmpRootPath . $aPath, $aContent);
+ }
+ //the rels for files
+ $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet));
+ }
+ }
+
+ // Add relationships to ZIP file
+ $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet));
+ $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet));
+
+ // Add document properties to ZIP file
+ $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet));
+ $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet));
+ $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet);
+ if ($customPropertiesPart !== null) {
+ $this->addZipFile('docProps/custom.xml', $customPropertiesPart);
+ }
+
+ // Add theme to ZIP file
+ $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet));
+
+ // Add string table to ZIP file
+ $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable));
+
+ // Add styles to ZIP file
+ $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet));
+
+ // Add workbook to ZIP file
+ $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas));
+
+ $chartCount = 0;
+ // Add worksheets
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts));
+ if ($this->includeCharts) {
+ $charts = $this->spreadSheet->getSheet($i)->getChartCollection();
+ if (count($charts) > 0) {
+ foreach ($charts as $chart) {
+ $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas));
+ ++$chartCount;
+ }
+ }
+ }
+ }
+
+ $chartRef1 = 0;
+ // Add worksheet relationships (drawings, ...)
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ // Add relationships
+ $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts));
+
+ // Add unparsedLoadedData
+ $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
+ $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
+ $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
+ $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+
+ $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ if ($this->includeCharts) {
+ $chartCount = $this->spreadSheet->getSheet($i)->getChartCount();
+ }
+
+ // Add drawing and image relationship parts
+ if (($drawingCount > 0) || ($chartCount > 0)) {
+ // Drawing relationships
+ $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts));
+
+ // Drawings
+ $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
+ // Drawings
+ $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ }
+
+ // Add unparsed drawings
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
+ $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
+ if ($drawingFile !== false) {
+ $drawingFile = ltrim($drawingFile, '.');
+ $this->addZipFile('xl' . $drawingFile, $drawingXml);
+ }
+ }
+ }
+
+ // Add comment relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
+ // VML Comments
+ $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i)));
+
+ // Comments
+ $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i)));
+ }
+
+ // Add unparsed relationship parts
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
+ $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']);
+ }
+ }
+
+ // Add header/footer relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ // VML Drawings
+ $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)));
+
+ // VML Drawing relationships
+ $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)));
+
+ // Media
+ foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath()));
+ }
+ }
+ }
+
+ // Add media
+ for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) {
+ if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) {
+ $imageContents = null;
+ $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath();
+ if (strpos($imagePath, 'zip://') !== false) {
+ $imagePath = substr($imagePath, 6);
+ $imagePathSplitted = explode('#', $imagePath);
+
+ $imageZip = new ZipArchive();
+ $imageZip->open($imagePathSplitted[0]);
+ $imageContents = $imageZip->getFromName($imagePathSplitted[1]);
+ $imageZip->close();
+ unset($imageZip);
+ } else {
+ $imageContents = file_get_contents($imagePath);
+ }
+
+ $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ ob_start();
+ call_user_func(
+ $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(),
+ $this->getDrawingHashTable()->getByIndex($i)->getImageResource()
+ );
+ $imageContents = ob_get_contents();
+ ob_end_clean();
+
+ $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ }
+ }
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ // Close file
+ try {
+ $this->zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ return $this->spreadSheet;
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+
+ /**
+ * Get string table.
+ *
+ * @return string[]
+ */
+ public function getStringTable()
+ {
+ return $this->stringTable;
+ }
+
+ /**
+ * Get Style HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStyleHashTable()
+ {
+ return $this->styleHashTable;
+ }
+
+ /**
+ * Get Conditional HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStylesConditionalHashTable()
+ {
+ return $this->stylesConditionalHashTable;
+ }
+
+ /**
+ * Get Fill HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFillHashTable()
+ {
+ return $this->fillHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFontHashTable()
+ {
+ return $this->fontHashTable;
+ }
+
+ /**
+ * Get Borders HashTable.
+ *
+ * @return HashTable
+ */
+ public function getBordersHashTable()
+ {
+ return $this->bordersHashTable;
+ }
+
+ /**
+ * Get NumberFormat HashTable.
+ *
+ * @return HashTable
+ */
+ public function getNumFmtHashTable()
+ {
+ return $this->numFmtHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
+ *
+ * @return HashTable
+ */
+ public function getDrawingHashTable()
+ {
+ return $this->drawingHashTable;
+ }
+
+ /**
+ * Get Office2003 compatibility.
+ *
+ * @return bool
+ */
+ public function getOffice2003Compatibility()
+ {
+ return $this->office2003compatibility;
+ }
+
+ /**
+ * Set Office2003 compatibility.
+ *
+ * @param bool $pValue Office2003 compatibility?
+ *
+ * @return $this
+ */
+ public function setOffice2003Compatibility($pValue)
+ {
+ $this->office2003compatibility = $pValue;
+
+ return $this;
+ }
+
+ private $pathNames = [];
+
+ private function addZipFile(string $path, string $content): void
+ {
+ if (!in_array($path, $this->pathNames)) {
+ $this->pathNames[] = $path;
+ $this->zip->addFile($path, $content);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php new file mode 100644 index 0000000..4ac780b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -0,0 +1,1516 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Chart\Axis;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
+use PhpOffice\PhpSpreadsheet\Chart\GridLines;
+use PhpOffice\PhpSpreadsheet\Chart\Layout;
+use PhpOffice\PhpSpreadsheet\Chart\Legend;
+use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
+use PhpOffice\PhpSpreadsheet\Chart\Title;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Chart extends WriterPart
+{
+ protected $calculateCellValues;
+
+ /**
+ * @var int
+ */
+ private $seriesIndex;
+
+ /**
+ * Write charts to XML format.
+ *
+ * @param mixed $calculateCellValues
+ *
+ * @return string XML Output
+ */
+ public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $calculateCellValues = true)
+ {
+ $this->calculateCellValues = $calculateCellValues;
+
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+ // Ensure that data series values are up-to-date before we save
+ if ($this->calculateCellValues) {
+ $pChart->refresh();
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // c:chartSpace
+ $objWriter->startElement('c:chartSpace');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->startElement('c:date1904');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ $objWriter->startElement('c:lang');
+ $objWriter->writeAttribute('val', 'en-GB');
+ $objWriter->endElement();
+ $objWriter->startElement('c:roundedCorners');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writeAlternateContent($objWriter);
+
+ $objWriter->startElement('c:chart');
+
+ $this->writeTitle($objWriter, $pChart->getTitle());
+
+ $objWriter->startElement('c:autoTitleDeleted');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writePlotArea($objWriter, $pChart->getWorksheet(), $pChart->getPlotArea(), $pChart->getXAxisLabel(), $pChart->getYAxisLabel(), $pChart->getChartAxisX(), $pChart->getChartAxisY(), $pChart->getMajorGridlines(), $pChart->getMinorGridlines());
+
+ $this->writeLegend($objWriter, $pChart->getLegend());
+
+ $objWriter->startElement('c:plotVisOnly');
+ $objWriter->writeAttribute('val', (int) $pChart->getPlotVisibleOnly());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:dispBlanksAs');
+ $objWriter->writeAttribute('val', $pChart->getDisplayBlanksAs());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showDLblsOverMax');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $this->writePrintSettings($objWriter);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Chart Title.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $title
+ */
+ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
+ {
+ if ($title === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+
+ $caption = $title->getCaption();
+ if ((is_array($caption)) && (count($caption) > 0)) {
+ $caption = $caption[0];
+ }
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a');
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $title->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Legend.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Legend $legend
+ */
+ private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void
+ {
+ if ($legend === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:legend');
+
+ $objWriter->startElement('c:legendPos');
+ $objWriter->writeAttribute('val', $legend->getPosition());
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $legend->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:txPr');
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->writeAttribute('rtl', 0);
+
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:endParaRPr');
+ $objWriter->writeAttribute('lang', 'en-US');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Plot Area.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param Title $yAxisLabel
+ * @param Axis $xAxis
+ * @param Axis $yAxis
+ */
+ private function writePlotArea(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet, PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null): void
+ {
+ if ($plotArea === null) {
+ return;
+ }
+
+ $id1 = $id2 = 0;
+ $this->seriesIndex = 0;
+ $objWriter->startElement('c:plotArea');
+
+ $layout = $plotArea->getLayout();
+
+ $this->writeLayout($objWriter, $layout);
+
+ $chartTypes = self::getChartType($plotArea);
+ $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
+ $plotGroupingType = '';
+ foreach ($chartTypes as $chartType) {
+ $objWriter->startElement('c:' . $chartType);
+
+ $groupCount = $plotArea->getPlotGroupCount();
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $plotGroup = $plotArea->getPlotGroupByIndex($i);
+ $groupType = $plotGroup->getPlotType();
+ if ($groupType == $chartType) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($groupType === DataSeries::TYPE_RADARCHART) {
+ $objWriter->startElement('c:radarStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) {
+ $objWriter->startElement('c:scatterStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ }
+
+ $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
+ }
+ }
+
+ $this->writeDataLabels($objWriter, $layout);
+
+ if ($chartType === DataSeries::TYPE_LINECHART) {
+ // Line only, Line3D can't be smoothed
+ $objWriter->startElement('c:smooth');
+ $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine());
+ $objWriter->endElement();
+ } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 150);
+ $objWriter->endElement();
+
+ if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
+ $objWriter->startElement('c:overlap');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+ }
+ } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showNegBubbles');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ } elseif ($chartType === DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('c:hiLowLines');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upDownBars');
+
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 300);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upBars');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:downBars');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // Generate 2 unique numbers to use for axId values
+ $id1 = '75091328';
+ $id2 = '75089408';
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:firstSliceAng');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ if ($chartType === DataSeries::TYPE_DONUTCHART) {
+ $objWriter->startElement('c:holeSize');
+ $objWriter->writeAttribute('val', 50);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ if ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ } else {
+ $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis);
+ }
+
+ $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Data Labels.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Chart\Layout $chartLayout Chart layout
+ */
+ private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void
+ {
+ $objWriter->startElement('c:dLbls');
+
+ $objWriter->startElement('c:showLegendKey');
+ $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey();
+ $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showVal');
+ $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal();
+ $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showCatName');
+ $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName();
+ $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showSerName');
+ $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName();
+ $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showPercent');
+ $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent();
+ $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showBubbleSize');
+ $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize();
+ $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showLeaderLines');
+ $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines();
+ $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Category Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void
+ {
+ $objWriter->startElement('c:catAx');
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'b');
+ $objWriter->endElement();
+
+ if ($xAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $xAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $layout = $xAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:auto');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblAlgn');
+ $objWriter->writeAttribute('val', 'ctr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblOffset');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+
+ if ($isMultiLevelSeries) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Value Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $yAxisLabel
+ * @param string $groupType Chart type
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeValueAxis($objWriter, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines): void
+ {
+ $objWriter->startElement('c:valAx');
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+
+ if ($xAxis->getAxisOptionsProperty('maximum') !== null) {
+ $objWriter->startElement('c:max');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minimum') !== null) {
+ $objWriter->startElement('c:min');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'l');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($majorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($majorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+ $objWriter->startElement('a:effectLst');
+
+ if ($majorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($majorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}");
+ if ($majorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur'));
+ }
+ if ($majorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance'));
+ }
+ if ($majorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction'));
+ }
+ if ($majorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn'));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($majorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value']));
+
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($majorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end majorGridLines
+
+ if ($minorGridlines->getObjectState()) {
+ $objWriter->startElement('c:minorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($minorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($minorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($minorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($minorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}");
+ if ($minorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur'));
+ }
+ if ($minorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance'));
+ }
+ if ($minorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction'));
+ }
+ if ($minorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn'));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($minorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($minorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end minorGridLines
+ }
+
+ if ($yAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $yAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $layout = $yAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+ }
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+
+ if ($xAxis->getFillProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getFillProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:ln');
+
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width'));
+ $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap'));
+ $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound'));
+
+ if ($xAxis->getLineProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getLineProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($xAxis->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($xAxis->getGlowProperty('size') !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size'));
+ $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}");
+
+ if ($xAxis->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur'));
+ }
+ if ($xAxis->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance'));
+ }
+ if ($xAxis->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction'));
+ }
+ if ($xAxis->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn'));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx']));
+ }
+ if ($xAxis->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape'));
+ }
+
+ $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement(); //effectList
+ $objWriter->endElement(); //end spPr
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
+ $objWriter->startElement('c:crossesAt');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value'));
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:crossBetween');
+ $objWriter->writeAttribute('val', 'midCat');
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('major_unit') !== null) {
+ $objWriter->startElement('c:majorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) {
+ $objWriter->startElement('c:minorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit'));
+ $objWriter->endElement();
+ }
+ }
+
+ if ($isMultiLevelSeries) {
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get the data series type(s) for a chart plot series.
+ *
+ * @param PlotArea $plotArea
+ *
+ * @return array|string
+ */
+ private static function getChartType($plotArea)
+ {
+ $groupCount = $plotArea->getPlotGroupCount();
+
+ if ($groupCount == 1) {
+ $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()];
+ } else {
+ $chartTypes = [];
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartType = array_unique($chartTypes);
+ if (count($chartTypes) == 0) {
+ throw new WriterException('Chart is not yet implemented');
+ }
+ }
+
+ return $chartType;
+ }
+
+ /**
+ * Method writing plot series values.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $val value for idx (default: 3)
+ * @param string $fillColor hex color (default: FF9900)
+ *
+ * @return XMLWriter XML Writer
+ */
+ private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900')
+ {
+ $objWriter->startElement('c:dPt');
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $val);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter;
+ }
+
+ /**
+ * Write Plot Group (series of related plots).
+ *
+ * @param DataSeries $plotGroup
+ * @param string $groupType Type of plot for dataseries
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool &$catIsMultiLevelSeries Is category a multi-series category
+ * @param bool &$valIsMultiLevelSeries Is value set a multi-series set
+ * @param string &$plotGroupingType Type of grouping for multi-series values
+ */
+ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
+ {
+ if ($plotGroup === null) {
+ return;
+ }
+
+ if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:barDir');
+ $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
+ $objWriter->endElement();
+ }
+
+ if ($plotGroup->getPlotGrouping() !== null) {
+ $plotGroupingType = $plotGroup->getPlotGrouping();
+ $objWriter->startElement('c:grouping');
+ $objWriter->writeAttribute('val', $plotGroupingType);
+ $objWriter->endElement();
+ }
+
+ // Get these details before the loop, because we can use the count to check for varyColors
+ $plotSeriesOrder = $plotGroup->getPlotOrder();
+ $plotSeriesCount = count($plotSeriesOrder);
+
+ if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
+ if ($groupType !== DataSeries::TYPE_LINECHART) {
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
+ $objWriter->startElement('c:ser');
+
+ $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
+ if ($plotLabel) {
+ $fillColor = $plotLabel->getFillColor();
+ if ($fillColor !== null && !is_array($fillColor)) {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:order');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef);
+ $objWriter->endElement();
+
+ // Values
+ $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ $fillColorValues = $plotSeriesValues->getFillColor();
+ if ($fillColorValues !== null && is_array($fillColorValues)) {
+ foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
+ $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900'));
+ }
+ } else {
+ $this->writePlotSeriesValuesElement($objWriter);
+ }
+ }
+
+ // Labels
+ $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef);
+ if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:strRef');
+ $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ // Formatting for the points
+ if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
+ $plotLineWidth = 12700;
+ if ($plotSeriesValues) {
+ $plotLineWidth = $plotSeriesValues->getLineWidth();
+ }
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $plotLineWidth);
+ if ($groupType == DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('a:noFill');
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($plotSeriesValues) {
+ $plotSeriesMarker = $plotSeriesValues->getPointMarker();
+ if ($plotSeriesMarker) {
+ $objWriter->startElement('c:marker');
+ $objWriter->startElement('c:symbol');
+ $objWriter->writeAttribute('val', $plotSeriesMarker);
+ $objWriter->endElement();
+
+ if ($plotSeriesMarker !== 'none') {
+ $objWriter->startElement('c:size');
+ $objWriter->writeAttribute('val', 3);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
+ $objWriter->startElement('c:invertIfNegative');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ // Category Labels
+ $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef);
+ if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
+ $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ if ($plotGroup->getPlotStyle() !== null) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($plotStyle) {
+ $objWriter->startElement('c:explosion');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:xVal');
+ } else {
+ $objWriter->startElement('c:cat');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
+ $objWriter->endElement();
+ }
+
+ // Values
+ if ($plotSeriesValues) {
+ $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:yVal');
+ } else {
+ $objWriter->startElement('c:val');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
+ $objWriter->endElement();
+ }
+
+ if ($groupType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeBubbles($plotSeriesValues, $objWriter);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $this->seriesIndex += $plotSeriesIdx + 1;
+ }
+
+ /**
+ * Write Plot Series Label.
+ *
+ * @param DataSeriesValues $plotSeriesLabel
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePlotSeriesLabel($plotSeriesLabel, $objWriter): void
+ {
+ if ($plotSeriesLabel === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesLabel->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:strCache');
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount());
+ $objWriter->endElement();
+
+ foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotLabelKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotLabelValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Plot Series Values.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $groupType Type of plot for dataseries
+ * @param string $dataType Datatype of series values
+ */
+ private function writePlotSeriesValues($plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ if ($plotSeriesValues->isMultiLevelSeries()) {
+ $levelCount = $plotSeriesValues->multiLevelCount();
+
+ $objWriter->startElement('c:multiLvlStrRef');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:multiLvlStrCache');
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ for ($level = 0; $level < $levelCount; ++$level) {
+ $objWriter->startElement('c:lvl');
+
+ foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
+ if (isset($plotSeriesValue[$level])) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue[$level]);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:' . $dataType . 'Ref');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:' . $dataType . 'Cache');
+
+ if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
+ if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData($plotSeriesValues->getFormatCode());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Bubble Chart Details.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBubbles($plotSeriesValues, $objWriter): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:bubbleSize');
+ $objWriter->startElement('c:numLit');
+
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData('General');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData(1);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Layout.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Layout $layout
+ */
+ private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void
+ {
+ $objWriter->startElement('c:layout');
+
+ if ($layout !== null) {
+ $objWriter->startElement('c:manualLayout');
+
+ $layoutTarget = $layout->getLayoutTarget();
+ if ($layoutTarget !== null) {
+ $objWriter->startElement('c:layoutTarget');
+ $objWriter->writeAttribute('val', $layoutTarget);
+ $objWriter->endElement();
+ }
+
+ $xMode = $layout->getXMode();
+ if ($xMode !== null) {
+ $objWriter->startElement('c:xMode');
+ $objWriter->writeAttribute('val', $xMode);
+ $objWriter->endElement();
+ }
+
+ $yMode = $layout->getYMode();
+ if ($yMode !== null) {
+ $objWriter->startElement('c:yMode');
+ $objWriter->writeAttribute('val', $yMode);
+ $objWriter->endElement();
+ }
+
+ $x = $layout->getXPosition();
+ if ($x !== null) {
+ $objWriter->startElement('c:x');
+ $objWriter->writeAttribute('val', $x);
+ $objWriter->endElement();
+ }
+
+ $y = $layout->getYPosition();
+ if ($y !== null) {
+ $objWriter->startElement('c:y');
+ $objWriter->writeAttribute('val', $y);
+ $objWriter->endElement();
+ }
+
+ $w = $layout->getWidth();
+ if ($w !== null) {
+ $objWriter->startElement('c:w');
+ $objWriter->writeAttribute('val', $w);
+ $objWriter->endElement();
+ }
+
+ $h = $layout->getHeight();
+ if ($h !== null) {
+ $objWriter->startElement('c:h');
+ $objWriter->writeAttribute('val', $h);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Alternate Content block.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeAlternateContent($objWriter): void
+ {
+ $objWriter->startElement('mc:AlternateContent');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+
+ $objWriter->startElement('mc:Choice');
+ $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart');
+ $objWriter->writeAttribute('Requires', 'c14');
+
+ $objWriter->startElement('c14:style');
+ $objWriter->writeAttribute('val', '102');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('mc:Fallback');
+ $objWriter->startElement('c:style');
+ $objWriter->writeAttribute('val', '2');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Printer Settings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePrintSettings($objWriter): void
+ {
+ $objWriter->startElement('c:printSettings');
+
+ $objWriter->startElement('c:headerFooter');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageMargins');
+ $objWriter->writeAttribute('footer', 0.3);
+ $objWriter->writeAttribute('header', 0.3);
+ $objWriter->writeAttribute('r', 0.7);
+ $objWriter->writeAttribute('l', 0.7);
+ $objWriter->writeAttribute('t', 0.75);
+ $objWriter->writeAttribute('b', 0.75);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageSetup');
+ $objWriter->writeAttribute('orientation', 'portrait');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php new file mode 100644 index 0000000..3d012fe --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -0,0 +1,232 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Comment;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+class Comments extends WriterPart
+{
+ /**
+ * Write comments to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // Authors cache
+ $authors = [];
+ $authorId = 0;
+ foreach ($comments as $comment) {
+ if (!isset($authors[$comment->getAuthor()])) {
+ $authors[$comment->getAuthor()] = $authorId++;
+ }
+ }
+
+ // comments
+ $objWriter->startElement('comments');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // Loop through authors
+ $objWriter->startElement('authors');
+ foreach ($authors as $author => $index) {
+ $objWriter->writeElement('author', $author);
+ }
+ $objWriter->endElement();
+
+ // Loop through comments
+ $objWriter->startElement('commentList');
+ foreach ($comments as $key => $value) {
+ $this->writeComment($objWriter, $key, $value, $authors);
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference
+ * @param Comment $pComment Comment
+ * @param array $pAuthors Array of authors
+ */
+ private function writeComment(XMLWriter $objWriter, $pCellReference, Comment $pComment, array $pAuthors): void
+ {
+ // comment
+ $objWriter->startElement('comment');
+ $objWriter->writeAttribute('ref', $pCellReference);
+ $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]);
+
+ // text
+ $objWriter->startElement('text');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write VML comments to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t202');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '202');
+ $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through comments
+ foreach ($comments as $key => $value) {
+ $this->writeVMLComment($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference, eg: 'A1'
+ * @param Comment $pComment Comment
+ */
+ private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment): void
+ {
+ // Metadata
+ [$column, $row] = Coordinate::coordinateFromString($pCellReference);
+ $column = Coordinate::columnIndexFromString($column);
+ $id = 1024 + $column + $row;
+ $id = substr($id, 0, 4);
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t202');
+ $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden'));
+ $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->writeAttribute('o:insetmode', 'auto');
+
+ // v:fill
+ $objWriter->startElement('v:fill');
+ $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->endElement();
+
+ // v:shadow
+ $objWriter->startElement('v:shadow');
+ $objWriter->writeAttribute('on', 't');
+ $objWriter->writeAttribute('color', 'black');
+ $objWriter->writeAttribute('obscured', 't');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:connecttype', 'none');
+ $objWriter->endElement();
+
+ // v:textbox
+ $objWriter->startElement('v:textbox');
+ $objWriter->writeAttribute('style', 'mso-direction-alt:auto');
+
+ // div
+ $objWriter->startElement('div');
+ $objWriter->writeAttribute('style', 'text-align:left');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // x:ClientData
+ $objWriter->startElement('x:ClientData');
+ $objWriter->writeAttribute('ObjectType', 'Note');
+
+ // x:MoveWithCells
+ $objWriter->writeElement('x:MoveWithCells', '');
+
+ // x:SizeWithCells
+ $objWriter->writeElement('x:SizeWithCells', '');
+
+ // x:AutoFill
+ $objWriter->writeElement('x:AutoFill', 'False');
+
+ // x:Row
+ $objWriter->writeElement('x:Row', ($row - 1));
+
+ // x:Column
+ $objWriter->writeElement('x:Column', ($column - 1));
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php new file mode 100644 index 0000000..2499d97 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -0,0 +1,240 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class ContentTypes extends WriterPart
+{
+ /**
+ * Write content types to XML format.
+ *
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ *
+ * @return string XML Output
+ */
+ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Types
+ $objWriter->startElement('Types');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types');
+
+ // Theme
+ $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml');
+
+ // Styles
+ $this->writeOverrideContentType($objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml');
+
+ // Rels
+ $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');
+
+ // XML
+ $this->writeDefaultContentType($objWriter, 'xml', 'application/xml');
+
+ // VML
+ $this->writeDefaultContentType($objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing');
+
+ // Workbook
+ if ($spreadsheet->hasMacros()) { //Macros in workbook ?
+ // Yes : not standard content but "macroEnabled"
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
+ //... and define a new type for the VBA project
+ // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
+ if ($spreadsheet->hasMacrosCertificate()) {
+ // signed macros ?
+ // Yes : add needed information
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature');
+ }
+ } else {
+ // no macros in workbook, so standard type
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml');
+ }
+
+ // DocProps
+ $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml');
+
+ $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml');
+ }
+
+ // Worksheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeOverrideContentType($objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml');
+ }
+
+ // Shared strings
+ $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
+
+ // Add worksheet relationship content types
+ $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
+ $chart = 1;
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
+
+ // We need a drawing relationship for the worksheet if we have either drawings or charts
+ if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
+ $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
+ }
+
+ // If we have charts, then we need a chart relationship for every individual chart
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeOverrideContentType($objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml');
+ }
+ }
+ }
+
+ // Comments
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getComments()) > 0) {
+ $this->writeOverrideContentType($objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml');
+ }
+ }
+
+ // Add media content-types
+ $aMediaContentTypes = [];
+ $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count();
+ for ($i = 0; $i < $mediaCount; ++$i) {
+ $extension = '';
+ $mimeType = '';
+
+ if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension());
+ $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath());
+ } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType();
+ }
+
+ if (!isset($aMediaContentTypes[$extension])) {
+ $aMediaContentTypes[$extension] = $mimeType;
+
+ $this->writeDefaultContentType($objWriter, $extension, $mimeType);
+ }
+ }
+ if ($spreadsheet->hasRibbonBinObjects()) {
+ // Some additional objects in the ribbon ?
+ // we need to write "Extension" but not already write for media content
+ $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types'), array_keys($aMediaContentTypes));
+ foreach ($tabRibbonTypes as $aRibbonType) {
+ $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor
+ $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType);
+ }
+ }
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) {
+ $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath());
+
+ $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]);
+ }
+ }
+ }
+ }
+
+ // unparsed defaults
+ if (isset($unparsedLoadedData['default_content_types'])) {
+ foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) {
+ $this->writeDefaultContentType($objWriter, $extName, $contentType);
+ }
+ }
+
+ // unparsed overrides
+ if (isset($unparsedLoadedData['override_content_types'])) {
+ foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) {
+ $this->writeOverrideContentType($objWriter, $partName, $overrideType);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Get image mime type.
+ *
+ * @param string $pFile Filename
+ *
+ * @return string Mime Type
+ */
+ private function getImageMimeType($pFile)
+ {
+ if (File::fileExists($pFile)) {
+ $image = getimagesize($pFile);
+
+ return image_type_to_mime_type($image[2]);
+ }
+
+ throw new WriterException("File $pFile does not exist");
+ }
+
+ /**
+ * Write Default content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeDefaultContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Default');
+ $objWriter->writeAttribute('Extension', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeOverrideContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Override');
+ $objWriter->writeAttribute('PartName', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php new file mode 100644 index 0000000..43a8d2e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -0,0 +1,223 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\DefinedName;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class DefinedNames
+{
+ private $objWriter;
+
+ private $spreadsheet;
+
+ public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet)
+ {
+ $this->objWriter = $objWriter;
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ public function write(): void
+ {
+ // Write defined names
+ $this->objWriter->startElement('definedNames');
+
+ // Named ranges
+ if (count($this->spreadsheet->getDefinedNames()) > 0) {
+ // Named ranges
+ $this->writeNamedRangesAndFormulae();
+ }
+
+ // Other defined names
+ $sheetCount = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // NamedRange for autoFilter
+ $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i);
+
+ // NamedRange for Print_Titles
+ $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i);
+
+ // NamedRange for Print_Area
+ $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i);
+ }
+
+ $this->objWriter->endElement();
+ }
+
+ /**
+ * Write defined names.
+ */
+ private function writeNamedRangesAndFormulae(): void
+ {
+ // Loop named ranges
+ $definedNames = $this->spreadsheet->getDefinedNames();
+ foreach ($definedNames as $definedName) {
+ $this->writeDefinedName($definedName);
+ }
+ }
+
+ /**
+ * Write Defined Name for named range.
+ */
+ private function writeDefinedName(DefinedName $pDefinedName): void
+ {
+ // definedName for named range
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', $pDefinedName->getName());
+ if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) {
+ $this->objWriter->writeAttribute('localSheetId', $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope()));
+ }
+
+ $definedRange = $pDefinedName->getValue();
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $definedRange,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
+ // We should have a worksheet
+ $worksheet = $pDefinedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
+ }
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+
+ $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
+ }
+
+ if (substr($definedRange, 0, 1) === '=') {
+ $definedRange = substr($definedRange, 1);
+ }
+
+ $this->objWriter->writeRawData($definedRange);
+
+ $this->objWriter->endElement();
+ }
+
+ /**
+ * Write Defined Name for autoFilter.
+ */
+ private function writeNamedRangeForAutofilter(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for autoFilter
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+ $this->objWriter->writeAttribute('hidden', '1');
+
+ // Create absolute coordinate and write as raw text
+ $range = Coordinate::splitRange($autoFilterRange);
+ $range = $range[0];
+ // Strip any worksheet ref so we can make the cell ref absolute
+ [$ws, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
+
+ $range[0] = Coordinate::absoluteCoordinate($range[0]);
+ $range[1] = Coordinate::absoluteCoordinate($range[1]);
+ $range = implode(':', $range);
+
+ $this->objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range);
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ */
+ private function writeNamedRangeForPrintTitles(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for PrintTitles
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Setting string
+ $settingString = '';
+
+ // Columns to repeat
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ // Rows to repeat
+ if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $settingString .= ',';
+ }
+
+ $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ $this->objWriter->writeRawData($settingString);
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ */
+ private function writeNamedRangeForPrintArea(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for PrintArea
+ if ($pSheet->getPageSetup()->isPrintAreaSet()) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm.Print_Area');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Print area
+ $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea());
+
+ $chunks = [];
+ foreach ($printArea as $printAreaRect) {
+ $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]);
+ $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
+ }
+
+ $this->objWriter->writeRawData(implode(',', $chunks));
+
+ $this->objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php new file mode 100644 index 0000000..7125b70 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -0,0 +1,239 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class DocProps extends WriterPart
+{
+ /**
+ * Write docProps/app.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsApp(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Properties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ // Application
+ $objWriter->writeElement('Application', 'Microsoft Excel');
+
+ // DocSecurity
+ $objWriter->writeElement('DocSecurity', '0');
+
+ // ScaleCrop
+ $objWriter->writeElement('ScaleCrop', 'false');
+
+ // HeadingPairs
+ $objWriter->startElement('HeadingPairs');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', '2');
+ $objWriter->writeAttribute('baseType', 'variant');
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:lpstr', 'Worksheets');
+ $objWriter->endElement();
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // TitlesOfParts
+ $objWriter->startElement('TitlesOfParts');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', $spreadsheet->getSheetCount());
+ $objWriter->writeAttribute('baseType', 'lpstr');
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->writeElement('vt:lpstr', $spreadsheet->getSheet($i)->getTitle());
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Company
+ $objWriter->writeElement('Company', $spreadsheet->getProperties()->getCompany());
+
+ // Company
+ $objWriter->writeElement('Manager', $spreadsheet->getProperties()->getManager());
+
+ // LinksUpToDate
+ $objWriter->writeElement('LinksUpToDate', 'false');
+
+ // SharedDoc
+ $objWriter->writeElement('SharedDoc', 'false');
+
+ // HyperlinksChanged
+ $objWriter->writeElement('HyperlinksChanged', 'false');
+
+ // AppVersion
+ $objWriter->writeElement('AppVersion', '12.0000');
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/core.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCore(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // cp:coreProperties
+ $objWriter->startElement('cp:coreProperties');
+ $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/');
+ $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+
+ // dc:creator
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+
+ // cp:lastModifiedBy
+ $objWriter->writeElement('cp:lastModifiedBy', $spreadsheet->getProperties()->getLastModifiedBy());
+
+ // dcterms:created
+ $objWriter->startElement('dcterms:created');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->endElement();
+
+ // dcterms:modified
+ $objWriter->startElement('dcterms:modified');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getModified()));
+ $objWriter->endElement();
+
+ // dc:title
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+
+ // dc:description
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+
+ // dc:subject
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+
+ // cp:keywords
+ $objWriter->writeElement('cp:keywords', $spreadsheet->getProperties()->getKeywords());
+
+ // cp:category
+ $objWriter->writeElement('cp:category', $spreadsheet->getProperties()->getCategory());
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/custom.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCustom(Spreadsheet $spreadsheet)
+ {
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (empty($customPropertyList)) {
+ return;
+ }
+
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // cp:coreProperties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ foreach ($customPropertyList as $key => $customProperty) {
+ $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty);
+ $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty);
+
+ $objWriter->startElement('property');
+ $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}');
+ $objWriter->writeAttribute('pid', $key + 2);
+ $objWriter->writeAttribute('name', $customProperty);
+
+ switch ($propertyType) {
+ case 'i':
+ $objWriter->writeElement('vt:i4', $propertyValue);
+
+ break;
+ case 'f':
+ $objWriter->writeElement('vt:r8', $propertyValue);
+
+ break;
+ case 'b':
+ $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false');
+
+ break;
+ case 'd':
+ $objWriter->startElement('vt:filetime');
+ $objWriter->writeRawData(date(DATE_W3C, $propertyValue));
+ $objWriter->endElement();
+
+ break;
+ default:
+ $objWriter->writeElement('vt:lpwstr', $propertyValue);
+
+ break;
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php new file mode 100644 index 0000000..3f989df --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -0,0 +1,505 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Drawing extends WriterPart
+{
+ /**
+ * Write drawings to XML format.
+ *
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ *
+ * @return string XML Output
+ */
+ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // xdr:wsDr
+ $objWriter->startElement('xdr:wsDr');
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+
+ // Loop through images and write drawings
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ /** @var BaseDrawing $pDrawing */
+ $pDrawing = $iterator->current();
+ $pRelationId = $i;
+ $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
+
+ $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ $chartCount = $pWorksheet->getChartCount();
+ // Loop through charts and write the chart position
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i);
+ }
+ }
+ }
+
+ // unparsed AlternateContent
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
+ $objWriter->writeRaw($drawingAlternateContent);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ */
+ public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1): void
+ {
+ $tl = $pChart->getTopLeftPosition();
+ $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']);
+ $br = $pChart->getBottomRightPosition();
+ $br['colRow'] = Coordinate::coordinateFromString($br['cell']);
+
+ $objWriter->startElement('xdr:twoCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:to');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset']));
+ $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset']));
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:graphicFrame');
+ $objWriter->writeAttribute('macro', '');
+ $objWriter->startElement('xdr:nvGraphicFramePr');
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('name', 'Chart ' . $pRelationId);
+ $objWriter->writeAttribute('id', 1025 * $pRelationId);
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:cNvGraphicFramePr');
+ $objWriter->startElement('a:graphicFrameLocks');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:xfrm');
+ $objWriter->startElement('a:off');
+ $objWriter->writeAttribute('x', '0');
+ $objWriter->writeAttribute('y', '0');
+ $objWriter->endElement();
+ $objWriter->startElement('a:ext');
+ $objWriter->writeAttribute('cx', '0');
+ $objWriter->writeAttribute('cy', '0');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:graphic');
+ $objWriter->startElement('a:graphicData');
+ $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->startElement('c:chart');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelationId);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:clientData');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ * @param null|int $hlinkClickId
+ */
+ public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null): void
+ {
+ if ($pRelationId >= 0) {
+ // xdr:oneCellAnchor
+ $objWriter->startElement('xdr:oneCellAnchor');
+ // Image location
+ $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates());
+ $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]);
+
+ // xdr:from
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX()));
+ $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY()));
+ $objWriter->endElement();
+
+ // xdr:ext
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth()));
+ $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight()));
+ $objWriter->endElement();
+
+ // xdr:pic
+ $objWriter->startElement('xdr:pic');
+
+ // xdr:nvPicPr
+ $objWriter->startElement('xdr:nvPicPr');
+
+ // xdr:cNvPr
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('id', $pRelationId);
+ $objWriter->writeAttribute('name', $pDrawing->getName());
+ $objWriter->writeAttribute('descr', $pDrawing->getDescription());
+
+ //a:hlinkClick
+ $this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
+
+ $objWriter->endElement();
+
+ // xdr:cNvPicPr
+ $objWriter->startElement('xdr:cNvPicPr');
+
+ // a:picLocks
+ $objWriter->startElement('a:picLocks');
+ $objWriter->writeAttribute('noChangeAspect', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:blipFill
+ $objWriter->startElement('xdr:blipFill');
+
+ // a:blip
+ $objWriter->startElement('a:blip');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId);
+ $objWriter->endElement();
+
+ // a:stretch
+ $objWriter->startElement('a:stretch');
+ $objWriter->writeElement('a:fillRect', null);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:spPr
+ $objWriter->startElement('xdr:spPr');
+
+ // a:xfrm
+ $objWriter->startElement('a:xfrm');
+ $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation()));
+ $objWriter->endElement();
+
+ // a:prstGeom
+ $objWriter->startElement('a:prstGeom');
+ $objWriter->writeAttribute('prst', 'rect');
+
+ // a:avLst
+ $objWriter->writeElement('a:avLst', null);
+
+ $objWriter->endElement();
+
+ if ($pDrawing->getShadow()->getVisible()) {
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius()));
+ $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance()));
+ $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection()));
+ $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment());
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB());
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:clientData
+ $objWriter->writeElement('xdr:clientData', null);
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write VML header/footer images to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Header/footer images
+ $images = $pWorksheet->getHeaderFooter()->getImages();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t75');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '75');
+ $objWriter->writeAttribute('o:preferrelative', 't');
+ $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe');
+ $objWriter->writeAttribute('filled', 'f');
+ $objWriter->writeAttribute('stroked', 'f');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:formulas
+ $objWriter->startElement('v:formulas');
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 1 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum 0 0 @1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @2 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 0 1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @6 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @8 21600 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @10 21600 0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:extrusionok', 'f');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('aspectratio', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through images
+ foreach ($images as $key => $value) {
+ $this->writeVMLHeaderFooterImage($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pReference Reference
+ * @param HeaderFooterDrawing $pImage Image
+ */
+ private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage): void
+ {
+ // Calculate object id
+ preg_match('{(\d+)}', md5($pReference), $m);
+ $id = 1500 + (substr($m[1], 0, 2) * 1);
+
+ // Calculate offset
+ $width = $pImage->getWidth();
+ $height = $pImage->getHeight();
+ $marginLeft = $pImage->getOffsetX();
+ $marginTop = $pImage->getOffsetY();
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', $pReference);
+ $objWriter->writeAttribute('o:spid', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t75');
+ $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1");
+
+ // v:imagedata
+ $objWriter->startElement('v:imagedata');
+ $objWriter->writeAttribute('o:relid', 'rId' . $pReference);
+ $objWriter->writeAttribute('o:title', $pImage->getName());
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('textRotation', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get an array of all drawings.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet
+ */
+ public function allDrawings(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all drawings
+ $aDrawings = [];
+
+ // Loop through PhpSpreadsheet
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // Loop through images and add to array
+ $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ $aDrawings[] = $iterator->current();
+
+ $iterator->next();
+ }
+ }
+
+ return $aDrawings;
+ }
+
+ /**
+ * @param null|int $hlinkClickId
+ */
+ private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void
+ {
+ if ($hlinkClickId === null) {
+ return;
+ }
+
+ $objWriter->startElement('a:hlinkClick');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php new file mode 100644 index 0000000..3d14f9a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -0,0 +1,451 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Rels extends WriterPart
+{
+ /**
+ * Write relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeRelationships(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 4,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
+ 'docProps/custom.xml'
+ );
+ }
+
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
+ 'docProps/app.xml'
+ );
+
+ // Relationship docProps/core.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
+ 'docProps/core.xml'
+ );
+
+ // Relationship xl/workbook.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
+ 'xl/workbook.xml'
+ );
+ // a custom UI in workbook ?
+ if ($spreadsheet->hasRibbon()) {
+ $this->writeRelationShip(
+ $objWriter,
+ 5,
+ 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility',
+ $spreadsheet->getRibbonXMLData('target')
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write workbook relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Relationship styles.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
+ 'styles.xml'
+ );
+
+ // Relationship theme/theme1.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',
+ 'theme/theme1.xml'
+ );
+
+ // Relationship sharedStrings.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',
+ 'sharedStrings.xml'
+ );
+
+ // Relationships with sheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeRelationship(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
+ 'worksheets/sheet' . ($i + 1) . '.xml'
+ );
+ }
+ // Relationships for vbaProject if needed
+ // id : just after the last sheet
+ if ($spreadsheet->hasMacros()) {
+ $this->writeRelationShip(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.microsoft.com/office/2006/relationships/vbaProject',
+ 'vbaProject.bin'
+ );
+ ++$i; //increment i if needed for an another relation
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write worksheet relationships to XML format.
+ *
+ * Numbering is as follows:
+ * rId1 - Drawings
+ * rId_hyperlink_x - Hyperlinks
+ *
+ * @param int $pWorksheetId
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $pWorksheetId = 1, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Write drawing relationships?
+ $drawingOriginalIds = [];
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
+ }
+
+ if ($includeCharts) {
+ $charts = $pWorksheet->getChartCollection();
+ } else {
+ $charts = [];
+ }
+
+ if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
+ $rId = 1;
+
+ // Use original $relPath to get original $rId.
+ // Take first. In future can be overwritten.
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
+ reset($drawingOriginalIds);
+ $relPath = key($drawingOriginalIds);
+ if (isset($drawingOriginalIds[$relPath])) {
+ $rId = (int) (substr($drawingOriginalIds[$relPath], 3));
+ }
+
+ // Generate new $relPath to write drawing relationship
+ $relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
+ $relPath
+ );
+ }
+
+ // Write hyperlink relationships?
+ $i = 1;
+ foreach ($pWorksheet->getHyperlinkCollection() as $hyperlink) {
+ if (!$hyperlink->isInternal()) {
+ $this->writeRelationship(
+ $objWriter,
+ '_hyperlink_' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $hyperlink->getUrl(),
+ 'External'
+ );
+
+ ++$i;
+ }
+ }
+
+ // Write comments relationship?
+ $i = 1;
+ if (count($pWorksheet->getComments()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_comments_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawing' . $pWorksheetId . '.vml'
+ );
+
+ $this->writeRelationship(
+ $objWriter,
+ '_comments' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
+ '../comments' . $pWorksheetId . '.xml'
+ );
+ }
+
+ // Write header/footer relationship?
+ $i = 1;
+ if (count($pWorksheet->getHeaderFooter()->getImages()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_headerfooter_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawingHF' . $pWorksheetId . '.vml'
+ );
+ }
+
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type): void
+ {
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) {
+ return;
+ }
+
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ $type,
+ $value['relFilePath']
+ );
+ }
+ }
+
+ /**
+ * Write drawing relationships to XML format.
+ *
+ * @param int &$chartRef Chart ID
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, &$chartRef, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ if (
+ $iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
+ || $iterator->current() instanceof MemoryDrawing
+ ) {
+ // Write relationship for image drawing
+ /** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
+ $drawing = $iterator->current();
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
+ );
+
+ $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
+ }
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ // Loop through charts and write relationships
+ $chartCount = $pWorksheet->getChartCount();
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeRelationship(
+ $objWriter,
+ $i++,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart',
+ '../charts/chart' . ++$chartRef . '.xml'
+ );
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write header/footer drawing relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ foreach ($pWorksheet->getHeaderFooter()->getImages() as $key => $value) {
+ // Write relationship for image drawing
+ $this->writeRelationship(
+ $objWriter,
+ $key,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . $value->getIndexedFilename()
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pId Relationship ID. rId will be prepended!
+ * @param string $pType Relationship type
+ * @param string $pTarget Relationship target
+ * @param string $pTargetMode Relationship target mode
+ */
+ private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget, $pTargetMode = ''): void
+ {
+ if ($pType != '' && $pTarget != '') {
+ // Write relationship
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId' . $pId);
+ $objWriter->writeAttribute('Type', $pType);
+ $objWriter->writeAttribute('Target', $pTarget);
+
+ if ($pTargetMode != '') {
+ $objWriter->writeAttribute('TargetMode', $pTargetMode);
+ }
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * @param $objWriter
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
+ * @param $i
+ *
+ * @return int
+ */
+ private function writeDrawingHyperLink($objWriter, $drawing, $i)
+ {
+ if ($drawing->getHyperlink() === null) {
+ return $i;
+ }
+
+ ++$i;
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $drawing->getHyperlink()->getUrl(),
+ $drawing->getHyperlink()->getTypeHyperlink()
+ );
+
+ return $i;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php new file mode 100644 index 0000000..66d9d77 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php @@ -0,0 +1,45 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class RelsRibbon extends WriterPart
+{
+ /**
+ * Write relationships for additional objects of custom UI (ribbon).
+ *
+ * @return string XML Output
+ */
+ public function writeRibbonRelationships(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $localRels = $spreadsheet->getRibbonBinObjects('names');
+ if (is_array($localRels)) {
+ foreach ($localRels as $aId => $aTarget) {
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', $aId);
+ $objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
+ $objWriter->writeAttribute('Target', $aTarget);
+ $objWriter->endElement();
+ }
+ }
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php new file mode 100644 index 0000000..4ffb558 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php @@ -0,0 +1,40 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class RelsVBA extends WriterPart
+{
+ /**
+ * Write relationships for a signed VBA Project.
+ *
+ * @return string XML Output
+ */
+ public function writeVBARelationships(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId1');
+ $objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature');
+ $objWriter->writeAttribute('Target', 'vbaProjectSignature.bin');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php new file mode 100644 index 0000000..310e073 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -0,0 +1,282 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class StringTable extends WriterPart
+{
+ /**
+ * Create worksheet stringtable.
+ *
+ * @param Worksheet $pSheet Worksheet
+ * @param string[] $pExistingTable Existing table to eventually merge with
+ *
+ * @return string[] String table for worksheet
+ */
+ public function createStringTable(Worksheet $pSheet, $pExistingTable = null)
+ {
+ // Create string lookup table
+ $aStringTable = [];
+ $cellCollection = null;
+ $aFlippedStringTable = null; // For faster lookup
+
+ // Is an existing table given?
+ if (($pExistingTable !== null) && is_array($pExistingTable)) {
+ $aStringTable = $pExistingTable;
+ }
+
+ // Fill index array
+ $aFlippedStringTable = $this->flipStringTable($aStringTable);
+
+ // Loop through cells
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cell = $pSheet->getCell($coordinate);
+ $cellValue = $cell->getValue();
+ if (
+ !is_object($cellValue) &&
+ ($cellValue !== null) &&
+ $cellValue !== '' &&
+ !isset($aFlippedStringTable[$cellValue]) &&
+ ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL)
+ ) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue] = true;
+ } elseif (
+ $cellValue instanceof RichText &&
+ ($cellValue !== null) &&
+ !isset($aFlippedStringTable[$cellValue->getHashCode()])
+ ) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue->getHashCode()] = true;
+ }
+ }
+
+ return $aStringTable;
+ }
+
+ /**
+ * Write string table to XML format.
+ *
+ * @param string[] $pStringTable
+ *
+ * @return string XML Output
+ */
+ public function writeStringTable(array $pStringTable)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // String table
+ $objWriter->startElement('sst');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('uniqueCount', count($pStringTable));
+
+ // Loop through string table
+ foreach ($pStringTable as $textElement) {
+ $objWriter->startElement('si');
+
+ if (!$textElement instanceof RichText) {
+ $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
+ $objWriter->startElement('t');
+ if ($textToWrite !== trim($textToWrite)) {
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ }
+ $objWriter->writeRawData($textToWrite);
+ $objWriter->endElement();
+ } elseif ($textElement instanceof RichText) {
+ $this->writeRichText($objWriter, $textElement);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText $pRichText Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichText(XMLWriter $objWriter, RichText $pRichText, $prefix = null): void
+ {
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ if ($element instanceof Run) {
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // rFont
+ $objWriter->startElement($prefix . 'rFont');
+ $objWriter->writeAttribute('val', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ // Bold
+ $objWriter->startElement($prefix . 'b');
+ $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Italic
+ $objWriter->startElement($prefix . 'i');
+ $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Superscript / subscript
+ if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
+ $objWriter->startElement($prefix . 'vertAlign');
+ if ($element->getFont()->getSuperscript()) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($element->getFont()->getSubscript()) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ $objWriter->startElement($prefix . 'strike');
+ $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Color
+ $objWriter->startElement($prefix . 'color');
+ $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
+ $objWriter->endElement();
+
+ // Size
+ $objWriter->startElement($prefix . 'sz');
+ $objWriter->writeAttribute('val', $element->getFont()->getSize());
+ $objWriter->endElement();
+
+ // Underline
+ $objWriter->startElement($prefix . 'u');
+ $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText|string $pRichText text string or Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichTextForCharts(XMLWriter $objWriter, $pRichText = null, $prefix = null): void
+ {
+ if (!$pRichText instanceof RichText) {
+ $textRun = $pRichText;
+ $pRichText = new RichText();
+ $pRichText->createTextRun($textRun);
+ }
+
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // Bold
+ $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
+ // Italic
+ $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
+ // Underline
+ $underlineType = $element->getFont()->getUnderline();
+ switch ($underlineType) {
+ case 'single':
+ $underlineType = 'sng';
+
+ break;
+ case 'double':
+ $underlineType = 'dbl';
+
+ break;
+ }
+ $objWriter->writeAttribute('u', $underlineType);
+ // Strikethrough
+ $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike'));
+
+ // rFont
+ $objWriter->startElement($prefix . 'latin');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Flip string table (for index searching).
+ *
+ * @param array $stringTable Stringtable
+ *
+ * @return array
+ */
+ public function flipStringTable(array $stringTable)
+ {
+ // Return value
+ $returnValue = [];
+
+ // Loop through stringtable and add flipped items to $returnValue
+ foreach ($stringTable as $key => $value) {
+ if (!$value instanceof RichText) {
+ $returnValue[$value] = $key;
+ } elseif ($value instanceof RichText) {
+ $returnValue[$value->getHashCode()] = $key;
+ }
+ }
+
+ return $returnValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php new file mode 100644 index 0000000..69d20a9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -0,0 +1,676 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+
+class Style extends WriterPart
+{
+ /**
+ * Write styles to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeStyles(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // styleSheet
+ $objWriter->startElement('styleSheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // numFmts
+ $objWriter->startElement('numFmts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count());
+
+ // numFmt
+ for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) {
+ $this->writeNumFmt($objWriter, $this->getParentWriter()->getNumFmtHashTable()->getByIndex($i), $i);
+ }
+
+ $objWriter->endElement();
+
+ // fonts
+ $objWriter->startElement('fonts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count());
+
+ // font
+ for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) {
+ $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // fills
+ $objWriter->startElement('fills');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count());
+
+ // fill
+ for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) {
+ $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // borders
+ $objWriter->startElement('borders');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count());
+
+ // border
+ for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) {
+ $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // cellStyleXfs
+ $objWriter->startElement('cellStyleXfs');
+ $objWriter->writeAttribute('count', 1);
+
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('numFmtId', 0);
+ $objWriter->writeAttribute('fontId', 0);
+ $objWriter->writeAttribute('fillId', 0);
+ $objWriter->writeAttribute('borderId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // cellXfs
+ $objWriter->startElement('cellXfs');
+ $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection()));
+
+ // xf
+ foreach ($spreadsheet->getCellXfCollection() as $cellXf) {
+ $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet);
+ }
+
+ $objWriter->endElement();
+
+ // cellStyles
+ $objWriter->startElement('cellStyles');
+ $objWriter->writeAttribute('count', 1);
+
+ // cellStyle
+ $objWriter->startElement('cellStyle');
+ $objWriter->writeAttribute('name', 'Normal');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('builtinId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // dxfs
+ $objWriter->startElement('dxfs');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count());
+
+ // dxf
+ for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) {
+ $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle());
+ }
+
+ $objWriter->endElement();
+
+ // tableStyles
+ $objWriter->startElement('tableStyles');
+ $objWriter->writeAttribute('defaultTableStyle', 'TableStyleMedium9');
+ $objWriter->writeAttribute('defaultPivotStyle', 'PivotTableStyle1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // Check if this is a pattern type or gradient type
+ if (
+ $pFill->getFillType() === Fill::FILL_GRADIENT_LINEAR ||
+ $pFill->getFillType() === Fill::FILL_GRADIENT_PATH
+ ) {
+ // Gradient fill
+ $this->writeGradientFill($objWriter, $pFill);
+ } elseif ($pFill->getFillType() !== null) {
+ // Pattern fill
+ $this->writePatternFill($objWriter, $pFill);
+ }
+ }
+
+ /**
+ * Write Gradient Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeGradientFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // gradientFill
+ $objWriter->startElement('gradientFill');
+ $objWriter->writeAttribute('type', $pFill->getFillType());
+ $objWriter->writeAttribute('degree', $pFill->getRotation());
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '0');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '1');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Pattern Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writePatternFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // patternFill
+ $objWriter->startElement('patternFill');
+ $objWriter->writeAttribute('patternType', $pFill->getFillType());
+
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // fgColor
+ if ($pFill->getStartColor()->getARGB()) {
+ $objWriter->startElement('fgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // bgColor
+ if ($pFill->getEndColor()->getARGB()) {
+ $objWriter->startElement('bgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Font.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Font $pFont Font style
+ */
+ private function writeFont(XMLWriter $objWriter, Font $pFont): void
+ {
+ // font
+ $objWriter->startElement('font');
+ // Weird! The order of these elements actually makes a difference when opening Xlsx
+ // files in Excel2003 with the compatibility pack. It's not documented behaviour,
+ // and makes for a real WTF!
+
+ // Bold. We explicitly write this element also when false (like MS Office Excel 2007 does
+ // for conditional formatting). Otherwise it will apparently not be picked up in conditional
+ // formatting style dialog
+ if ($pFont->getBold() !== null) {
+ $objWriter->startElement('b');
+ $objWriter->writeAttribute('val', $pFont->getBold() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Italic
+ if ($pFont->getItalic() !== null) {
+ $objWriter->startElement('i');
+ $objWriter->writeAttribute('val', $pFont->getItalic() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ if ($pFont->getStrikethrough() !== null) {
+ $objWriter->startElement('strike');
+ $objWriter->writeAttribute('val', $pFont->getStrikethrough() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Underline
+ if ($pFont->getUnderline() !== null) {
+ $objWriter->startElement('u');
+ $objWriter->writeAttribute('val', $pFont->getUnderline());
+ $objWriter->endElement();
+ }
+
+ // Superscript / subscript
+ if ($pFont->getSuperscript() === true || $pFont->getSubscript() === true) {
+ $objWriter->startElement('vertAlign');
+ if ($pFont->getSuperscript() === true) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($pFont->getSubscript() === true) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Size
+ if ($pFont->getSize() !== null) {
+ $objWriter->startElement('sz');
+ $objWriter->writeAttribute('val', StringHelper::formatNumber($pFont->getSize()));
+ $objWriter->endElement();
+ }
+
+ // Foreground color
+ if ($pFont->getColor()->getARGB() !== null) {
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFont->getColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // Name
+ if ($pFont->getName() !== null) {
+ $objWriter->startElement('name');
+ $objWriter->writeAttribute('val', $pFont->getName());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Border.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Borders $pBorders Borders style
+ */
+ private function writeBorder(XMLWriter $objWriter, Borders $pBorders): void
+ {
+ // Write border
+ $objWriter->startElement('border');
+ // Diagonal?
+ switch ($pBorders->getDiagonalDirection()) {
+ case Borders::DIAGONAL_UP:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'false');
+
+ break;
+ case Borders::DIAGONAL_DOWN:
+ $objWriter->writeAttribute('diagonalUp', 'false');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ case Borders::DIAGONAL_BOTH:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ }
+
+ // BorderPr
+ $this->writeBorderPr($objWriter, 'left', $pBorders->getLeft());
+ $this->writeBorderPr($objWriter, 'right', $pBorders->getRight());
+ $this->writeBorderPr($objWriter, 'top', $pBorders->getTop());
+ $this->writeBorderPr($objWriter, 'bottom', $pBorders->getBottom());
+ $this->writeBorderPr($objWriter, 'diagonal', $pBorders->getDiagonal());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Xf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ * @param Spreadsheet $spreadsheet Workbook
+ */
+ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle, Spreadsheet $spreadsheet): void
+ {
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($pStyle->getFont()->getHashCode()));
+ if ($pStyle->getQuotePrefix()) {
+ $objWriter->writeAttribute('quotePrefix', 1);
+ }
+
+ if ($pStyle->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($pStyle->getNumberFormat()->getHashCode()) + 164));
+ } else {
+ $objWriter->writeAttribute('numFmtId', (int) $pStyle->getNumberFormat()->getBuiltInFormatCode());
+ }
+
+ $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($pStyle->getFill()->getHashCode()));
+ $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($pStyle->getBorders()->getHashCode()));
+
+ // Apply styles?
+ $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $pStyle->getFont()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $pStyle->getNumberFormat()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $pStyle->getFill()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $pStyle->getBorders()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $pStyle->getAlignment()->getHashCode()) ? '1' : '0');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('applyProtection', 'true');
+ }
+
+ // alignment
+ $objWriter->startElement('alignment');
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+
+ $objWriter->writeAttribute('wrapText', ($pStyle->getAlignment()->getWrapText() ? 'true' : 'false'));
+ $objWriter->writeAttribute('shrinkToFit', ($pStyle->getAlignment()->getShrinkToFit() ? 'true' : 'false'));
+
+ if ($pStyle->getAlignment()->getIndent() > 0) {
+ $objWriter->writeAttribute('indent', $pStyle->getAlignment()->getIndent());
+ }
+ if ($pStyle->getAlignment()->getReadOrder() > 0) {
+ $objWriter->writeAttribute('readingOrder', $pStyle->getAlignment()->getReadOrder());
+ }
+ $objWriter->endElement();
+
+ // protection
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->startElement('protection');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if ($pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Dxf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ */
+ private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle): void
+ {
+ // dxf
+ $objWriter->startElement('dxf');
+
+ // font
+ $this->writeFont($objWriter, $pStyle->getFont());
+
+ // numFmt
+ $this->writeNumFmt($objWriter, $pStyle->getNumberFormat());
+
+ // fill
+ $this->writeFill($objWriter, $pStyle->getFill());
+
+ // alignment
+ $objWriter->startElement('alignment');
+ if ($pStyle->getAlignment()->getHorizontal() !== null) {
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ }
+ if ($pStyle->getAlignment()->getVertical() !== null) {
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+ }
+
+ if ($pStyle->getAlignment()->getTextRotation() !== null) {
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+ }
+ $objWriter->endElement();
+
+ // border
+ $this->writeBorder($objWriter, $pStyle->getBorders());
+
+ // protection
+ if (($pStyle->getProtection()->getLocked() !== null) || ($pStyle->getProtection()->getHidden() !== null)) {
+ if (
+ $pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT ||
+ $pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT
+ ) {
+ $objWriter->startElement('protection');
+ if (
+ ($pStyle->getProtection()->getLocked() !== null) &&
+ ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT)
+ ) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if (
+ ($pStyle->getProtection()->getHidden() !== null) &&
+ ($pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT)
+ ) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BorderPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pName Element name
+ * @param Border $pBorder Border style
+ */
+ private function writeBorderPr(XMLWriter $objWriter, $pName, Border $pBorder): void
+ {
+ // Write BorderPr
+ if ($pBorder->getBorderStyle() != Border::BORDER_NONE) {
+ $objWriter->startElement($pName);
+ $objWriter->writeAttribute('style', $pBorder->getBorderStyle());
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pBorder->getColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write NumberFormat.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param NumberFormat $pNumberFormat Number Format
+ * @param int $pId Number Format identifier
+ */
+ private function writeNumFmt(XMLWriter $objWriter, NumberFormat $pNumberFormat, $pId = 0): void
+ {
+ // Translate formatcode
+ $formatCode = $pNumberFormat->getFormatCode();
+
+ // numFmt
+ if ($formatCode !== null) {
+ $objWriter->startElement('numFmt');
+ $objWriter->writeAttribute('numFmtId', ($pId + 164));
+ $objWriter->writeAttribute('formatCode', $formatCode);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Get an array of all styles.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Style\Style[] All styles in PhpSpreadsheet
+ */
+ public function allStyles(Spreadsheet $spreadsheet)
+ {
+ return $spreadsheet->getCellXfCollection();
+ }
+
+ /**
+ * Get an array of all conditional styles.
+ *
+ * @return Conditional[] All conditional styles in PhpSpreadsheet
+ */
+ public function allConditionalStyles(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all styles
+ $aStyles = [];
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ foreach ($spreadsheet->getSheet($i)->getConditionalStylesCollection() as $conditionalStyles) {
+ foreach ($conditionalStyles as $conditionalStyle) {
+ $aStyles[] = $conditionalStyle;
+ }
+ }
+ }
+
+ return $aStyles;
+ }
+
+ /**
+ * Get an array of all fills.
+ *
+ * @return Fill[] All fills in PhpSpreadsheet
+ */
+ public function allFills(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fills
+ $aFills = [];
+
+ // Two first fills are predefined
+ $fill0 = new Fill();
+ $fill0->setFillType(Fill::FILL_NONE);
+ $aFills[] = $fill0;
+
+ $fill1 = new Fill();
+ $fill1->setFillType(Fill::FILL_PATTERN_GRAY125);
+ $aFills[] = $fill1;
+ // The remaining fills
+ $aStyles = $this->allStyles($spreadsheet);
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFills[$style->getFill()->getHashCode()])) {
+ $aFills[$style->getFill()->getHashCode()] = $style->getFill();
+ }
+ }
+
+ return $aFills;
+ }
+
+ /**
+ * Get an array of all fonts.
+ *
+ * @return Font[] All fonts in PhpSpreadsheet
+ */
+ public function allFonts(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fonts
+ $aFonts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFonts[$style->getFont()->getHashCode()])) {
+ $aFonts[$style->getFont()->getHashCode()] = $style->getFont();
+ }
+ }
+
+ return $aFonts;
+ }
+
+ /**
+ * Get an array of all borders.
+ *
+ * @return Borders[] All borders in PhpSpreadsheet
+ */
+ public function allBorders(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique borders
+ $aBorders = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aBorders[$style->getBorders()->getHashCode()])) {
+ $aBorders[$style->getBorders()->getHashCode()] = $style->getBorders();
+ }
+ }
+
+ return $aBorders;
+ }
+
+ /**
+ * Get an array of all number formats.
+ *
+ * @return NumberFormat[] All number formats in PhpSpreadsheet
+ */
+ public function allNumberFormats(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique number formats
+ $aNumFmts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false && !isset($aNumFmts[$style->getNumberFormat()->getHashCode()])) {
+ $aNumFmts[$style->getNumberFormat()->getHashCode()] = $style->getNumberFormat();
+ }
+ }
+
+ return $aNumFmts;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php new file mode 100644 index 0000000..b06ef41 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php @@ -0,0 +1,837 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Theme extends WriterPart
+{
+ /**
+ * Map of Major fonts to write.
+ *
+ * @var array of string
+ */
+ private static $majorFonts = [
+ 'Jpan' => 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Times New Roman',
+ 'Hebr' => 'Times New Roman',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'MoolBoran',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Times New Roman',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ /**
+ * Map of Minor fonts to write.
+ *
+ * @var array of string
+ */
+ private static $minorFonts = [
+ 'Jpan' => 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Arial',
+ 'Hebr' => 'Arial',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'DaunPenh',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Arial',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ /**
+ * Map of core colours.
+ *
+ * @var array of string
+ */
+ private static $colourScheme = [
+ 'dk2' => '1F497D',
+ 'lt2' => 'EEECE1',
+ 'accent1' => '4F81BD',
+ 'accent2' => 'C0504D',
+ 'accent3' => '9BBB59',
+ 'accent4' => '8064A2',
+ 'accent5' => '4BACC6',
+ 'accent6' => 'F79646',
+ 'hlink' => '0000FF',
+ 'folHlink' => '800080',
+ ];
+
+ /**
+ * Write theme to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeTheme(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // a:theme
+ $objWriter->startElement('a:theme');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('name', 'Office Theme');
+
+ // a:themeElements
+ $objWriter->startElement('a:themeElements');
+
+ // a:clrScheme
+ $objWriter->startElement('a:clrScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:dk1
+ $objWriter->startElement('a:dk1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'windowText');
+ $objWriter->writeAttribute('lastClr', '000000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lt1
+ $objWriter->startElement('a:lt1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'window');
+ $objWriter->writeAttribute('lastClr', 'FFFFFF');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:dk2
+ $this->writeColourScheme($objWriter);
+
+ $objWriter->endElement();
+
+ // a:fontScheme
+ $objWriter->startElement('a:fontScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:majorFont
+ $objWriter->startElement('a:majorFont');
+ $this->writeFonts($objWriter, 'Cambria', self::$majorFonts);
+ $objWriter->endElement();
+
+ // a:minorFont
+ $objWriter->startElement('a:minorFont');
+ $this->writeFonts($objWriter, 'Calibri', self::$minorFonts);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:fmtScheme
+ $objWriter->startElement('a:fmtScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:fillStyleLst
+ $objWriter->startElement('a:fillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '50000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '35000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '37000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '15000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '51000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '80000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '93000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '94000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '135000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lnStyleLst
+ $objWriter->startElement('a:lnStyleLst');
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '9525');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '95000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '105000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '25400');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '38100');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyleLst
+ $objWriter->startElement('a:effectStyleLst');
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '20000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '38000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:scene3d
+ $objWriter->startElement('a:scene3d');
+
+ // a:camera
+ $objWriter->startElement('a:camera');
+ $objWriter->writeAttribute('prst', 'orthographicFront');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lightRig
+ $objWriter->startElement('a:lightRig');
+ $objWriter->writeAttribute('rig', 'threePt');
+ $objWriter->writeAttribute('dir', 't');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '1200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:sp3d
+ $objWriter->startElement('a:sp3d');
+
+ // a:bevelT
+ $objWriter->startElement('a:bevelT');
+ $objWriter->writeAttribute('w', '63500');
+ $objWriter->writeAttribute('h', '25400');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:bgFillStyleLst
+ $objWriter->startElement('a:bgFillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '40000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '40000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '45000');
+ $objWriter->endElement();
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '99000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '20000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '255000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '-80000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '180000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '80000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '30000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '50000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '50000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:objectDefaults
+ $objWriter->writeElement('a:objectDefaults', null);
+
+ // a:extraClrSchemeLst
+ $objWriter->writeElement('a:extraClrSchemeLst', null);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write fonts to XML format.
+ *
+ * @param XMLWriter $objWriter
+ * @param string $latinFont
+ * @param array of string $fontSet
+ *
+ * @return string XML Output
+ */
+ private function writeFonts($objWriter, $latinFont, $fontSet)
+ {
+ // a:latin
+ $objWriter->startElement('a:latin');
+ $objWriter->writeAttribute('typeface', $latinFont);
+ $objWriter->endElement();
+
+ // a:ea
+ $objWriter->startElement('a:ea');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ // a:cs
+ $objWriter->startElement('a:cs');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ foreach ($fontSet as $fontScript => $typeface) {
+ $objWriter->startElement('a:font');
+ $objWriter->writeAttribute('script', $fontScript);
+ $objWriter->writeAttribute('typeface', $typeface);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write colour scheme to XML format.
+ *
+ * @param XMLWriter $objWriter
+ *
+ * @return string XML Output
+ */
+ private function writeColourScheme($objWriter)
+ {
+ foreach (self::$colourScheme as $colourName => $colourValue) {
+ $objWriter->startElement('a:' . $colourName);
+
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $colourValue);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php new file mode 100644 index 0000000..12cd159 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -0,0 +1,225 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DefinedNames as DefinedNamesWriter;
+
+class Workbook extends WriterPart
+{
+ /**
+ * Write workbook to XML format.
+ *
+ * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
+ *
+ * @return string XML Output
+ */
+ public function writeWorkbook(Spreadsheet $spreadsheet, $recalcRequired = false)
+ {
+ // Create XML writer
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // workbook
+ $objWriter->startElement('workbook');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ // fileVersion
+ $this->writeFileVersion($objWriter);
+
+ // workbookPr
+ $this->writeWorkbookPr($objWriter);
+
+ // workbookProtection
+ $this->writeWorkbookProtection($objWriter, $spreadsheet);
+
+ // bookViews
+ if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
+ $this->writeBookViews($objWriter, $spreadsheet);
+ }
+
+ // sheets
+ $this->writeSheets($objWriter, $spreadsheet);
+
+ // definedNames
+ (new DefinedNamesWriter($objWriter, $spreadsheet))->write();
+
+ // calcPr
+ $this->writeCalcPr($objWriter, $recalcRequired);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write file version.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeFileVersion(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('fileVersion');
+ $objWriter->writeAttribute('appName', 'xl');
+ $objWriter->writeAttribute('lastEdited', '4');
+ $objWriter->writeAttribute('lowestEdited', '4');
+ $objWriter->writeAttribute('rupBuild', '4505');
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookPr(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('workbookPr');
+
+ if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
+ $objWriter->writeAttribute('date1904', '1');
+ }
+
+ $objWriter->writeAttribute('codeName', 'ThisWorkbook');
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BookViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // bookViews
+ $objWriter->startElement('bookViews');
+
+ // workbookView
+ $objWriter->startElement('workbookView');
+
+ $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
+ $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false'));
+ $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex());
+ $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio());
+ $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility());
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
+ $objWriter->startElement('workbookProtection');
+ $objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));
+
+ if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
+ $objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
+ }
+
+ if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
+ $objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write calcPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
+ */
+ private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true): void
+ {
+ $objWriter->startElement('calcPr');
+
+ // Set the calcid to a higher value than Excel itself will use, otherwise Excel will always recalc
+ // If MS Excel does do a recalc, then users opening a file in MS Excel will be prompted to save on exit
+ // because the file has changed
+ $objWriter->writeAttribute('calcId', '999999');
+ $objWriter->writeAttribute('calcMode', 'auto');
+ // fullCalcOnLoad isn't needed if we've recalculating for the save
+ $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0);
+ $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1);
+ $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheets.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // Write sheets
+ $objWriter->startElement('sheets');
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // sheet
+ $this->writeSheet(
+ $objWriter,
+ $spreadsheet->getSheet($i)->getTitle(),
+ ($i + 1),
+ ($i + 1 + 3),
+ $spreadsheet->getSheet($i)->getSheetState()
+ );
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheet.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pSheetname Sheet name
+ * @param int $pSheetId Sheet id
+ * @param int $pRelId Relationship ID
+ * @param string $sheetState Sheet state (visible, hidden, veryHidden)
+ */
+ private function writeSheet(XMLWriter $objWriter, $pSheetname, $pSheetId = 1, $pRelId = 1, $sheetState = 'visible'): void
+ {
+ if ($pSheetname != '') {
+ // Write sheet
+ $objWriter->startElement('sheet');
+ $objWriter->writeAttribute('name', $pSheetname);
+ $objWriter->writeAttribute('sheetId', $pSheetId);
+ if ($sheetState !== 'visible' && $sheetState != '') {
+ $objWriter->writeAttribute('state', $sheetState);
+ }
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelId);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php new file mode 100644 index 0000000..2b89709 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -0,0 +1,1282 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
+use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
+
+class Worksheet extends WriterPart
+{
+ /**
+ * Write worksheet to XML format.
+ *
+ * @param string[] $pStringTable
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = null, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Worksheet
+ $objWriter->startElement('worksheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+ $objWriter->writeAttribute('mc:Ignorable', 'x14ac');
+ $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
+
+ // sheetPr
+ $this->writeSheetPr($objWriter, $pSheet);
+
+ // Dimension
+ $this->writeDimension($objWriter, $pSheet);
+
+ // sheetViews
+ $this->writeSheetViews($objWriter, $pSheet);
+
+ // sheetFormatPr
+ $this->writeSheetFormatPr($objWriter, $pSheet);
+
+ // cols
+ $this->writeCols($objWriter, $pSheet);
+
+ // sheetData
+ $this->writeSheetData($objWriter, $pSheet, $pStringTable);
+
+ // sheetProtection
+ $this->writeSheetProtection($objWriter, $pSheet);
+
+ // protectedRanges
+ $this->writeProtectedRanges($objWriter, $pSheet);
+
+ // autoFilter
+ $this->writeAutoFilter($objWriter, $pSheet);
+
+ // mergeCells
+ $this->writeMergeCells($objWriter, $pSheet);
+
+ // conditionalFormatting
+ $this->writeConditionalFormatting($objWriter, $pSheet);
+
+ // dataValidations
+ $this->writeDataValidations($objWriter, $pSheet);
+
+ // hyperlinks
+ $this->writeHyperlinks($objWriter, $pSheet);
+
+ // Print options
+ $this->writePrintOptions($objWriter, $pSheet);
+
+ // Page margins
+ $this->writePageMargins($objWriter, $pSheet);
+
+ // Page setup
+ $this->writePageSetup($objWriter, $pSheet);
+
+ // Header / footer
+ $this->writeHeaderFooter($objWriter, $pSheet);
+
+ // Breaks
+ $this->writeBreaks($objWriter, $pSheet);
+
+ // Drawings and/or Charts
+ $this->writeDrawings($objWriter, $pSheet, $includeCharts);
+
+ // LegacyDrawing
+ $this->writeLegacyDrawing($objWriter, $pSheet);
+
+ // LegacyDrawingHF
+ $this->writeLegacyDrawingHF($objWriter, $pSheet);
+
+ // AlternateContent
+ $this->writeAlternateContent($objWriter, $pSheet);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write SheetPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetPr
+ $objWriter->startElement('sheetPr');
+ if ($pSheet->getParent()->hasMacros()) {
+ //if the workbook have macros, we need to have codeName for the sheet
+ if (!$pSheet->hasCodeName()) {
+ $pSheet->setCodeName($pSheet->getTitle());
+ }
+ $objWriter->writeAttribute('codeName', $pSheet->getCodeName());
+ }
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $objWriter->writeAttribute('filterMode', 1);
+ $pSheet->getAutoFilter()->showHideRows();
+ }
+
+ // tabColor
+ if ($pSheet->isTabColorSet()) {
+ $objWriter->startElement('tabColor');
+ $objWriter->writeAttribute('rgb', $pSheet->getTabColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // outlinePr
+ $objWriter->startElement('outlinePr');
+ $objWriter->writeAttribute('summaryBelow', ($pSheet->getShowSummaryBelow() ? '1' : '0'));
+ $objWriter->writeAttribute('summaryRight', ($pSheet->getShowSummaryRight() ? '1' : '0'));
+ $objWriter->endElement();
+
+ // pageSetUpPr
+ if ($pSheet->getPageSetup()->getFitToPage()) {
+ $objWriter->startElement('pageSetUpPr');
+ $objWriter->writeAttribute('fitToPage', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Dimension.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // dimension
+ $objWriter->startElement('dimension');
+ $objWriter->writeAttribute('ref', $pSheet->calculateWorksheetDimension());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetViews
+ $objWriter->startElement('sheetViews');
+
+ // Sheet selected?
+ $sheetSelected = false;
+ if ($this->getParentWriter()->getSpreadsheet()->getIndex($pSheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) {
+ $sheetSelected = true;
+ }
+
+ // sheetView
+ $objWriter->startElement('sheetView');
+ $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0');
+ $objWriter->writeAttribute('workbookViewId', '0');
+
+ // Zoom scales
+ if ($pSheet->getSheetView()->getZoomScale() != 100) {
+ $objWriter->writeAttribute('zoomScale', $pSheet->getSheetView()->getZoomScale());
+ }
+ if ($pSheet->getSheetView()->getZoomScaleNormal() != 100) {
+ $objWriter->writeAttribute('zoomScaleNormal', $pSheet->getSheetView()->getZoomScaleNormal());
+ }
+
+ // Show zeros (Excel also writes this attribute only if set to false)
+ if ($pSheet->getSheetView()->getShowZeros() === false) {
+ $objWriter->writeAttribute('showZeros', 0);
+ }
+
+ // View Layout Type
+ if ($pSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) {
+ $objWriter->writeAttribute('view', $pSheet->getSheetView()->getView());
+ }
+
+ // Gridlines
+ if ($pSheet->getShowGridlines()) {
+ $objWriter->writeAttribute('showGridLines', 'true');
+ } else {
+ $objWriter->writeAttribute('showGridLines', 'false');
+ }
+
+ // Row and column headers
+ if ($pSheet->getShowRowColHeaders()) {
+ $objWriter->writeAttribute('showRowColHeaders', '1');
+ } else {
+ $objWriter->writeAttribute('showRowColHeaders', '0');
+ }
+
+ // Right-to-left
+ if ($pSheet->getRightToLeft()) {
+ $objWriter->writeAttribute('rightToLeft', 'true');
+ }
+
+ $activeCell = $pSheet->getActiveCell();
+ $sqref = $pSheet->getSelectedCells();
+
+ // Pane
+ $pane = '';
+ if ($pSheet->getFreezePane()) {
+ [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane());
+ $xSplit = Coordinate::columnIndexFromString($xSplit);
+ --$xSplit;
+ --$ySplit;
+
+ $topLeftCell = $pSheet->getTopLeftCell();
+
+ // pane
+ $pane = 'topRight';
+ $objWriter->startElement('pane');
+ if ($xSplit > 0) {
+ $objWriter->writeAttribute('xSplit', $xSplit);
+ }
+ if ($ySplit > 0) {
+ $objWriter->writeAttribute('ySplit', $ySplit);
+ $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft';
+ }
+ $objWriter->writeAttribute('topLeftCell', $topLeftCell);
+ $objWriter->writeAttribute('activePane', $pane);
+ $objWriter->writeAttribute('state', 'frozen');
+ $objWriter->endElement();
+
+ if (($xSplit > 0) && ($ySplit > 0)) {
+ // Write additional selections if more than two panes (ie both an X and a Y split)
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'topRight');
+ $objWriter->endElement();
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'bottomLeft');
+ $objWriter->endElement();
+ }
+ }
+
+ // Selection
+ // Only need to write selection element if we have a split pane
+ // We cheat a little by over-riding the active cell selection, setting it to the split cell
+ $objWriter->startElement('selection');
+ if ($pane != '') {
+ $objWriter->writeAttribute('pane', $pane);
+ }
+ $objWriter->writeAttribute('activeCell', $activeCell);
+ $objWriter->writeAttribute('sqref', $sqref);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetFormatPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetFormatPr
+ $objWriter->startElement('sheetFormatPr');
+
+ // Default row height
+ if ($pSheet->getDefaultRowDimension()->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', 'true');
+ $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($pSheet->getDefaultRowDimension()->getRowHeight()));
+ } else {
+ $objWriter->writeAttribute('defaultRowHeight', '14.4');
+ }
+
+ // Set Zero Height row
+ if (
+ (string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' ||
+ strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true'
+ ) {
+ $objWriter->writeAttribute('zeroHeight', '1');
+ }
+
+ // Default column width
+ if ($pSheet->getDefaultColumnDimension()->getWidth() >= 0) {
+ $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($pSheet->getDefaultColumnDimension()->getWidth()));
+ }
+
+ // Outline level - row
+ $outlineLevelRow = 0;
+ foreach ($pSheet->getRowDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelRow) {
+ $outlineLevelRow = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow);
+
+ // Outline level - column
+ $outlineLevelCol = 0;
+ foreach ($pSheet->getColumnDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelCol) {
+ $outlineLevelCol = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cols.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // cols
+ if (count($pSheet->getColumnDimensions()) > 0) {
+ $objWriter->startElement('cols');
+
+ $pSheet->calculateColumnWidths();
+
+ // Loop through column dimensions
+ foreach ($pSheet->getColumnDimensions() as $colDimension) {
+ // col
+ $objWriter->startElement('col');
+ $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+ $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+
+ if ($colDimension->getWidth() < 0) {
+ // No width set, apply default of 10
+ $objWriter->writeAttribute('width', '9.10');
+ } else {
+ // Width set
+ $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth()));
+ }
+
+ // Column visibility
+ if ($colDimension->getVisible() === false) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Auto size?
+ if ($colDimension->getAutoSize()) {
+ $objWriter->writeAttribute('bestFit', 'true');
+ }
+
+ // Custom width?
+ if ($colDimension->getWidth() != $pSheet->getDefaultColumnDimension()->getWidth()) {
+ $objWriter->writeAttribute('customWidth', 'true');
+ }
+
+ // Collapsed
+ if ($colDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($colDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel());
+ }
+
+ // Style
+ $objWriter->writeAttribute('style', $colDimension->getXfIndex());
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetProtection
+ $objWriter->startElement('sheetProtection');
+
+ $protection = $pSheet->getProtection();
+
+ if ($protection->getAlgorithm()) {
+ $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm());
+ $objWriter->writeAttribute('hashValue', $protection->getPassword());
+ $objWriter->writeAttribute('saltValue', $protection->getSalt());
+ $objWriter->writeAttribute('spinCount', $protection->getSpinCount());
+ } elseif ($protection->getPassword() !== '') {
+ $objWriter->writeAttribute('password', $protection->getPassword());
+ }
+
+ $objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false'));
+ $objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false'));
+ $objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false'));
+ $objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false'));
+ $objWriter->endElement();
+ }
+
+ private static function writeAttributeIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeAttribute($attr, $val);
+ }
+ }
+
+ private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeElement($attr, $val);
+ }
+ }
+
+ private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
+ || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ ) {
+ foreach ($conditional->getConditions() as $formula) {
+ // Formula
+ $objWriter->writeElement('formula', Xlfn::addXlfn($formula));
+ }
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0');
+ }
+ }
+
+ private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ $txt = $conditional->getText();
+ if ($txt !== null) {
+ $objWriter->writeAttribute('text', $txt);
+ if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) {
+ $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) {
+ $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) {
+ $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) {
+ $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
+ }
+ }
+ }
+
+ /**
+ * Write ConditionalFormatting.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Conditional id
+ $id = 1;
+
+ // Loop through styles in the current worksheet
+ foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ // WHY was this again?
+ // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
+ // continue;
+ // }
+ if ($conditional->getConditionType() != Conditional::CONDITION_NONE) {
+ // conditionalFormatting
+ $objWriter->startElement('conditionalFormatting');
+ $objWriter->writeAttribute('sqref', $cellCoordinate);
+
+ // cfRule
+ $objWriter->startElement('cfRule');
+ $objWriter->writeAttribute('type', $conditional->getConditionType());
+ $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()));
+ $objWriter->writeAttribute('priority', $id++);
+
+ self::writeAttributeif(
+ $objWriter,
+ ($conditional->getConditionType() == Conditional::CONDITION_CELLIS || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT)
+ && $conditional->getOperatorType() != Conditional::OPERATOR_NONE,
+ 'operator',
+ $conditional->getOperatorType()
+ );
+
+ self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1');
+
+ if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT) {
+ self::writeTextCondElements($objWriter, $conditional, $cellCoordinate);
+ } else {
+ self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate);
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ }
+
+ /**
+ * Write DataValidations.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $pSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection);
+ $objWriter->startElement('dataValidations');
+ $objWriter->writeAttribute('count', count($dataValidationCollection));
+
+ foreach ($dataValidationCollection as $coordinate => $dv) {
+ $objWriter->startElement('dataValidation');
+
+ if ($dv->getType() != '') {
+ $objWriter->writeAttribute('type', $dv->getType());
+ }
+
+ if ($dv->getErrorStyle() != '') {
+ $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle());
+ }
+
+ if ($dv->getOperator() != '') {
+ $objWriter->writeAttribute('operator', $dv->getOperator());
+ }
+
+ $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
+ $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
+ $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
+ $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
+
+ if ($dv->getErrorTitle() !== '') {
+ $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle());
+ }
+ if ($dv->getError() !== '') {
+ $objWriter->writeAttribute('error', $dv->getError());
+ }
+ if ($dv->getPromptTitle() !== '') {
+ $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle());
+ }
+ if ($dv->getPrompt() !== '') {
+ $objWriter->writeAttribute('prompt', $dv->getPrompt());
+ }
+
+ $objWriter->writeAttribute('sqref', $coordinate);
+
+ if ($dv->getFormula1() !== '') {
+ $objWriter->writeElement('formula1', $dv->getFormula1());
+ }
+ if ($dv->getFormula2() !== '') {
+ $objWriter->writeElement('formula2', $dv->getFormula2());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Hyperlinks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Hyperlink collection
+ $hyperlinkCollection = $pSheet->getHyperlinkCollection();
+
+ // Relation ID
+ $relationId = 1;
+
+ // Write hyperlinks?
+ if (!empty($hyperlinkCollection)) {
+ $objWriter->startElement('hyperlinks');
+
+ foreach ($hyperlinkCollection as $coordinate => $hyperlink) {
+ $objWriter->startElement('hyperlink');
+
+ $objWriter->writeAttribute('ref', $coordinate);
+ if (!$hyperlink->isInternal()) {
+ $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId);
+ ++$relationId;
+ } else {
+ $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
+ }
+
+ if ($hyperlink->getTooltip() !== '') {
+ $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
+ $objWriter->writeAttribute('display', $hyperlink->getTooltip());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write ProtectedRanges.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getProtectedCells()) > 0) {
+ // protectedRanges
+ $objWriter->startElement('protectedRanges');
+
+ // Loop protectedRanges
+ foreach ($pSheet->getProtectedCells() as $protectedCell => $passwordHash) {
+ // protectedRange
+ $objWriter->startElement('protectedRange');
+ $objWriter->writeAttribute('name', 'p' . md5($protectedCell));
+ $objWriter->writeAttribute('sqref', $protectedCell);
+ if (!empty($passwordHash)) {
+ $objWriter->writeAttribute('password', $passwordHash);
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write MergeCells.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getMergeCells()) > 0) {
+ // mergeCells
+ $objWriter->startElement('mergeCells');
+
+ // Loop mergeCells
+ foreach ($pSheet->getMergeCells() as $mergeCell) {
+ // mergeCell
+ $objWriter->startElement('mergeCell');
+ $objWriter->writeAttribute('ref', $mergeCell);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PrintOptions.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // printOptions
+ $objWriter->startElement('printOptions');
+
+ $objWriter->writeAttribute('gridLines', ($pSheet->getPrintGridlines() ? 'true' : 'false'));
+ $objWriter->writeAttribute('gridLinesSet', 'true');
+
+ if ($pSheet->getPageSetup()->getHorizontalCentered()) {
+ $objWriter->writeAttribute('horizontalCentered', 'true');
+ }
+
+ if ($pSheet->getPageSetup()->getVerticalCentered()) {
+ $objWriter->writeAttribute('verticalCentered', 'true');
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write PageMargins.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageMargins
+ $objWriter->startElement('pageMargins');
+ $objWriter->writeAttribute('left', StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()));
+ $objWriter->writeAttribute('right', StringHelper::formatNumber($pSheet->getPageMargins()->getRight()));
+ $objWriter->writeAttribute('top', StringHelper::formatNumber($pSheet->getPageMargins()->getTop()));
+ $objWriter->writeAttribute('bottom', StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()));
+ $objWriter->writeAttribute('header', StringHelper::formatNumber($pSheet->getPageMargins()->getHeader()));
+ $objWriter->writeAttribute('footer', StringHelper::formatNumber($pSheet->getPageMargins()->getFooter()));
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write AutoFilter.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ // autoFilter
+ $objWriter->startElement('autoFilter');
+
+ // Strip any worksheet reference from the filter coordinates
+ $range = Coordinate::splitRange($autoFilterRange);
+ $range = $range[0];
+ // Strip any worksheet ref
+ [$ws, $range[0]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true);
+ $range = implode(':', $range);
+
+ $objWriter->writeAttribute('ref', str_replace('$', '', $range));
+
+ $columns = $pSheet->getAutoFilter()->getColumns();
+ if (count($columns) > 0) {
+ foreach ($columns as $columnID => $column) {
+ $rules = $column->getRules();
+ if (count($rules) > 0) {
+ $objWriter->startElement('filterColumn');
+ $objWriter->writeAttribute('colId', $pSheet->getAutoFilter()->getColumnOffset($columnID));
+
+ $objWriter->startElement($column->getFilterType());
+ if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) {
+ $objWriter->writeAttribute('and', 1);
+ }
+
+ foreach ($rules as $rule) {
+ if (
+ ($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) &&
+ ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) &&
+ ($rule->getValue() === '')
+ ) {
+ // Filter rule for Blanks
+ $objWriter->writeAttribute('blank', 1);
+ } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) {
+ // Dynamic Filter Rule
+ $objWriter->writeAttribute('type', $rule->getGrouping());
+ $val = $column->getAttribute('val');
+ if ($val !== null) {
+ $objWriter->writeAttribute('val', $val);
+ }
+ $maxVal = $column->getAttribute('maxVal');
+ if ($maxVal !== null) {
+ $objWriter->writeAttribute('maxVal', $maxVal);
+ }
+ } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) {
+ // Top 10 Filter Rule
+ $objWriter->writeAttribute('val', $rule->getValue());
+ $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0'));
+ $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0'));
+ } else {
+ // Filter, DateGroupItem or CustomFilter
+ $objWriter->startElement($rule->getRuleType());
+
+ if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) {
+ $objWriter->writeAttribute('operator', $rule->getOperator());
+ }
+ if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) {
+ // Date Group filters
+ foreach ($rule->getValue() as $key => $value) {
+ if ($value > '') {
+ $objWriter->writeAttribute($key, $value);
+ }
+ }
+ $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping());
+ } else {
+ $objWriter->writeAttribute('val', $rule->getValue());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PageSetup.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageSetup
+ $objWriter->startElement('pageSetup');
+ $objWriter->writeAttribute('paperSize', $pSheet->getPageSetup()->getPaperSize());
+ $objWriter->writeAttribute('orientation', $pSheet->getPageSetup()->getOrientation());
+
+ if ($pSheet->getPageSetup()->getScale() !== null) {
+ $objWriter->writeAttribute('scale', $pSheet->getPageSetup()->getScale());
+ }
+ if ($pSheet->getPageSetup()->getFitToHeight() !== null) {
+ $objWriter->writeAttribute('fitToHeight', $pSheet->getPageSetup()->getFitToHeight());
+ } else {
+ $objWriter->writeAttribute('fitToHeight', '0');
+ }
+ if ($pSheet->getPageSetup()->getFitToWidth() !== null) {
+ $objWriter->writeAttribute('fitToWidth', $pSheet->getPageSetup()->getFitToWidth());
+ } else {
+ $objWriter->writeAttribute('fitToWidth', '0');
+ }
+ if ($pSheet->getPageSetup()->getFirstPageNumber() !== null) {
+ $objWriter->writeAttribute('firstPageNumber', $pSheet->getPageSetup()->getFirstPageNumber());
+ $objWriter->writeAttribute('useFirstPageNumber', '1');
+ }
+ $objWriter->writeAttribute('pageOrder', $pSheet->getPageSetup()->getPageOrder());
+
+ $getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
+ $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Header / Footer.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // headerFooter
+ $objWriter->startElement('headerFooter');
+ $objWriter->writeAttribute('differentOddEven', ($pSheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
+ $objWriter->writeAttribute('differentFirst', ($pSheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scaleWithDoc', ($pSheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
+ $objWriter->writeAttribute('alignWithMargins', ($pSheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
+
+ $objWriter->writeElement('oddHeader', $pSheet->getHeaderFooter()->getOddHeader());
+ $objWriter->writeElement('oddFooter', $pSheet->getHeaderFooter()->getOddFooter());
+ $objWriter->writeElement('evenHeader', $pSheet->getHeaderFooter()->getEvenHeader());
+ $objWriter->writeElement('evenFooter', $pSheet->getHeaderFooter()->getEvenFooter());
+ $objWriter->writeElement('firstHeader', $pSheet->getHeaderFooter()->getFirstHeader());
+ $objWriter->writeElement('firstFooter', $pSheet->getHeaderFooter()->getFirstFooter());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Breaks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Get row and column breaks
+ $aRowBreaks = [];
+ $aColumnBreaks = [];
+ foreach ($pSheet->getBreaks() as $cell => $breakType) {
+ if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) {
+ $aRowBreaks[] = $cell;
+ } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) {
+ $aColumnBreaks[] = $cell;
+ }
+ }
+
+ // rowBreaks
+ if (!empty($aRowBreaks)) {
+ $objWriter->startElement('rowBreaks');
+ $objWriter->writeAttribute('count', count($aRowBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks));
+
+ foreach ($aRowBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', $coords[1]);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ // Second, write column breaks
+ if (!empty($aColumnBreaks)) {
+ $objWriter->startElement('colBreaks');
+ $objWriter->writeAttribute('count', count($aColumnBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks));
+
+ foreach ($aColumnBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetData.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string[] $pStringTable String table
+ */
+ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable): void
+ {
+ // Flipped stringtable, for faster index searching
+ $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable);
+
+ // sheetData
+ $objWriter->startElement('sheetData');
+
+ // Get column count
+ $colCount = Coordinate::columnIndexFromString($pSheet->getHighestColumn());
+
+ // Highest row number
+ $highestRow = $pSheet->getHighestRow();
+
+ // Loop through cells
+ $cellsByRow = [];
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cellAddress = Coordinate::coordinateFromString($coordinate);
+ $cellsByRow[$cellAddress[1]][] = $coordinate;
+ }
+
+ $currentRow = 0;
+ while ($currentRow++ < $highestRow) {
+ // Get row dimension
+ $rowDimension = $pSheet->getRowDimension($currentRow);
+
+ // Write current row?
+ $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null;
+
+ if ($writeCurrentRow) {
+ // Start a new row
+ $objWriter->startElement('row');
+ $objWriter->writeAttribute('r', $currentRow);
+ $objWriter->writeAttribute('spans', '1:' . $colCount);
+
+ // Row dimensions
+ if ($rowDimension->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', '1');
+ $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
+ }
+
+ // Row visibility
+ if (!$rowDimension->getVisible() === true) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Collapsed
+ if ($rowDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($rowDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel());
+ }
+
+ // Style
+ if ($rowDimension->getXfIndex() !== null) {
+ $objWriter->writeAttribute('s', $rowDimension->getXfIndex());
+ $objWriter->writeAttribute('customFormat', '1');
+ }
+
+ // Write cells
+ if (isset($cellsByRow[$currentRow])) {
+ foreach ($cellsByRow[$currentRow] as $cellAddress) {
+ // Write cell
+ $this->writeCell($objWriter, $pSheet, $cellAddress, $aFlippedStringTable);
+ }
+ }
+
+ // End row
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ */
+ private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue)));
+ } elseif ($cellValue instanceof RichText) {
+ $objWriter->startElement('is');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ * @param string[] $pFlippedStringTable
+ */
+ private function writeCellString(XMLWriter $objWriter, string $mappedType, $cellValue, array $pFlippedStringTable): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ self::writeElementIf($objWriter, isset($pFlippedStringTable[$cellValue]), 'v', $pFlippedStringTable[$cellValue]);
+ } else {
+ $objWriter->writeElement('v', $pFlippedStringTable[$cellValue->getHashCode()]);
+ }
+ }
+
+ /**
+ * @param float|int $cellValue
+ */
+ private function writeCellNumeric(XMLWriter $objWriter, $cellValue): void
+ {
+ //force a decimal to be written if the type is float
+ if (is_float($cellValue)) {
+ // force point as decimal separator in case current locale uses comma
+ $cellValue = str_replace(',', '.', (string) $cellValue);
+ if (strpos($cellValue, '.') === false) {
+ $cellValue = $cellValue . '.0';
+ }
+ }
+ $objWriter->writeElement('v', $cellValue);
+ }
+
+ private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $objWriter->writeElement('v', $cellValue ? '1' : '0');
+ }
+
+ private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $cellIsFormula = substr($cellValue, 0, 1) === '=';
+ self::writeElementIf($objWriter, $cellIsFormula, 'f', Xlfn::addXlfnStripEquals($cellValue));
+ $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
+ }
+
+ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $pCell): void
+ {
+ $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $pCell->getCalculatedValue() : $cellValue;
+ if (is_string($calculatedValue)) {
+ if (\PhpOffice\PhpSpreadsheet\Calculation\Functions::isError($calculatedValue)) {
+ $this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue);
+
+ return;
+ }
+ $objWriter->writeAttribute('t', 'str');
+ } elseif (is_bool($calculatedValue)) {
+ $objWriter->writeAttribute('t', 'b');
+ }
+ // array values are not yet supported
+ //$attributes = $pCell->getFormulaAttributes();
+ //if (($attributes['t'] ?? null) === 'array') {
+ // $objWriter->startElement('f');
+ // $objWriter->writeAttribute('t', 'array');
+ // $objWriter->writeAttribute('ref', $pCellAddress);
+ // $objWriter->writeAttribute('aca', '1');
+ // $objWriter->writeAttribute('ca', '1');
+ // $objWriter->text(substr($cellValue, 1));
+ // $objWriter->endElement();
+ //} else {
+ // $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ //}
+ $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ self::writeElementIf(
+ $objWriter,
+ $this->getParentWriter()->getOffice2003Compatibility() === false,
+ 'v',
+ ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#')
+ ? StringHelper::formatNumber($calculatedValue) : '0'
+ );
+ }
+
+ /**
+ * Write Cell.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string $pCellAddress Cell Address
+ * @param string[] $pFlippedStringTable String table (flipped), for faster index searching
+ */
+ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, string $pCellAddress, array $pFlippedStringTable): void
+ {
+ // Cell
+ $pCell = $pSheet->getCell($pCellAddress);
+ $objWriter->startElement('c');
+ $objWriter->writeAttribute('r', $pCellAddress);
+
+ // Sheet styles
+ $xfi = $pCell->getXfIndex();
+ self::writeAttributeIf($objWriter, $xfi, 's', $xfi);
+
+ // If cell value is supplied, write cell value
+ $cellValue = $pCell->getValue();
+ if (is_object($cellValue) || $cellValue !== '') {
+ // Map type
+ $mappedType = $pCell->getDataType();
+
+ // Write data depending on its type
+ switch (strtolower($mappedType)) {
+ case 'inlinestr': // Inline string
+ $this->writeCellInlineStr($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 's': // String
+ $this->writeCellString($objWriter, $mappedType, $cellValue, $pFlippedStringTable);
+
+ break;
+ case 'f': // Formula
+ $this->writeCellFormula($objWriter, $cellValue, $pCell);
+
+ break;
+ case 'n': // Numeric
+ $this->writeCellNumeric($objWriter, $cellValue);
+
+ break;
+ case 'b': // Boolean
+ $this->writeCellBoolean($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 'e': // Error
+ $this->writeCellError($objWriter, $mappedType, $cellValue);
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Drawings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ */
+ private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false): void
+ {
+ $unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
+ $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
+ if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
+ return;
+ }
+
+ // If sheet contains drawings, add the relationships
+ $objWriter->startElement('drawing');
+
+ $rId = 'rId1';
+ if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
+ // take first. In future can be overriten
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships)
+ $rId = reset($drawingOriginalIds);
+ }
+
+ $objWriter->writeAttribute('r:id', $rId);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write LegacyDrawing.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains comments, add the relationships
+ if (count($pSheet->getComments()) > 0) {
+ $objWriter->startElement('legacyDrawing');
+ $objWriter->writeAttribute('r:id', 'rId_comments_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write LegacyDrawingHF.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains images, add the relationships
+ if (count($pSheet->getHeaderFooter()->getImages()) > 0) {
+ $objWriter->startElement('legacyDrawingHF');
+ $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
+ return;
+ }
+
+ foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
+ $objWriter->writeRaw($alternateContent);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php new file mode 100644 index 0000000..803662f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php @@ -0,0 +1,33 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+abstract class WriterPart
+{
+ /**
+ * Parent Xlsx object.
+ *
+ * @var Xlsx
+ */
+ private $parentWriter;
+
+ /**
+ * Get parent Xlsx object.
+ *
+ * @return Xlsx
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Set parent Xlsx object.
+ */
+ public function __construct(Xlsx $pWriter)
+ {
+ $this->parentWriter = $pWriter;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php new file mode 100644 index 0000000..9c56ff4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php @@ -0,0 +1,159 @@ +<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+class Xlfn
+{
+ const XLFNREGEXP = '/(?<!_xlfn[.])\\b('
+ // functions added with Excel 2010
+ . 'beta[.]dist'
+ . '|beta[.]inv'
+ . '|binom[.]dist'
+ . '|binom[.]inv'
+ . '|chisq[.]dist'
+ . '|chisq[.]dist[.]rt'
+ . '|chisq[.]inv'
+ . '|chisq[.]inv[.]rt'
+ . '|chisq[.]test'
+ . '|confidence[.]norm'
+ . '|confidence[.]t'
+ . '|covariance[.]p'
+ . '|covariance[.]s'
+ . '|erf[.]precise'
+ . '|erfc[.]precise'
+ . '|expon[.]dist'
+ . '|f[.]dist'
+ . '|f[.]dist[.]rt'
+ . '|f[.]inv'
+ . '|f[.]inv[.]rt'
+ . '|f[.]test'
+ . '|gamma[.]dist'
+ . '|gamma[.]inv'
+ . '|gammaln[.]precise'
+ . '|lognorm[.]dist'
+ . '|lognorm[.]inv'
+ . '|mode[.]mult'
+ . '|mode[.]sngl'
+ . '|negbinom[.]dist'
+ . '|networkdays[.]intl'
+ . '|norm[.]dist'
+ . '|norm[.]inv'
+ . '|norm[.]s[.]dist'
+ . '|norm[.]s[.]inv'
+ . '|percentile[.]exc'
+ . '|percentile[.]inc'
+ . '|percentrank[.]exc'
+ . '|percentrank[.]inc'
+ . '|poisson[.]dist'
+ . '|quartile[.]exc'
+ . '|quartile[.]inc'
+ . '|rank[.]avg'
+ . '|rank[.]eq'
+ . '|stdev[.]p'
+ . '|stdev[.]s'
+ . '|t[.]dist'
+ . '|t[.]dist[.]2t'
+ . '|t[.]dist[.]rt'
+ . '|t[.]inv'
+ . '|t[.]inv[.]2t'
+ . '|t[.]test'
+ . '|var[.]p'
+ . '|var[.]s'
+ . '|weibull[.]dist'
+ . '|z[.]test'
+ // functions added with Excel 2013
+ . '|acot'
+ . '|acoth'
+ . '|arabic'
+ . '|averageifs'
+ . '|binom[.]dist[.]range'
+ . '|bitand'
+ . '|bitlshift'
+ . '|bitor'
+ . '|bitrshift'
+ . '|bitxor'
+ . '|ceiling[.]math'
+ . '|combina'
+ . '|cot'
+ . '|coth'
+ . '|csc'
+ . '|csch'
+ . '|days'
+ . '|dbcs'
+ . '|decimal'
+ . '|encodeurl'
+ . '|filterxml'
+ . '|floor[.]math'
+ . '|formulatext'
+ . '|gamma'
+ . '|gauss'
+ . '|ifna'
+ . '|imcosh'
+ . '|imcot'
+ . '|imcsc'
+ . '|imcsch'
+ . '|imsec'
+ . '|imsech'
+ . '|imsinh'
+ . '|imtan'
+ . '|isformula'
+ . '|iso[.]ceiling'
+ . '|isoweeknum'
+ . '|munit'
+ . '|numbervalue'
+ . '|pduration'
+ . '|permutationa'
+ . '|phi'
+ . '|rri'
+ . '|sec'
+ . '|sech'
+ . '|sheet'
+ . '|sheets'
+ . '|skew[.]p'
+ . '|unichar'
+ . '|unicode'
+ . '|webservice'
+ . '|xor'
+ // functions added with Excel 2016
+ . '|forecast[.]et2'
+ . '|forecast[.]ets[.]confint'
+ . '|forecast[.]ets[.]seasonality'
+ . '|forecast[.]ets[.]stat'
+ . '|forecast[.]linear'
+ . '|switch'
+ // functions added with Excel 2019
+ . '|concat'
+ . '|countifs'
+ . '|ifs'
+ . '|maxifs'
+ . '|minifs'
+ . '|sumifs'
+ . '|textjoin'
+ // functions added with Excel 365
+ . '|filter'
+ . '|randarray'
+ . '|sequence'
+ . '|sort'
+ . '|sortby'
+ . '|unique'
+ . '|xlookup'
+ . '|xmatch'
+ . ')(?=\\s*[(])/i';
+
+ /**
+ * Prefix function name in string with _xlfn. where required.
+ */
+ public static function addXlfn(string $funcstring): string
+ {
+ return preg_replace(self::XLFNREGEXP, '_xlfn.$1', $funcstring);
+ }
+
+ /**
+ * Prefix function name in string with _xlfn. where required.
+ * Leading character, expected to be equals sign, is stripped.
+ */
+ public static function addXlfnStripEquals(string $funcstring): string
+ {
+ return self::addXlfn(substr($funcstring, 1));
+ }
+}
|