summaryrefslogtreecommitdiffstats
path: root/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell')
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php134
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php174
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php680
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php549
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php85
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php481
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php77
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php82
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php113
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php16
-rw-r--r--vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php29
11 files changed, 2420 insertions, 0 deletions
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php
new file mode 100644
index 0000000..2d8f542
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class AddressHelper
+{
+ /**
+ * Converts an R1C1 format cell address to an A1 format cell address.
+ */
+ public static function convertToA1(
+ string $address,
+ int $currentRowNumber = 1,
+ int $currentColumnNumber = 1
+ ): string {
+ $validityCheck = preg_match('/^(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))$/i', $address, $cellReference);
+
+ if ($validityCheck === 0) {
+ throw new Exception('Invalid R1C1-format Cell Reference');
+ }
+
+ $rowReference = $cellReference[2];
+ // Empty R reference is the current row
+ if ($rowReference === '') {
+ $rowReference = (string) $currentRowNumber;
+ }
+ // Bracketed R references are relative to the current row
+ if ($rowReference[0] === '[') {
+ $rowReference = $currentRowNumber + trim($rowReference, '[]');
+ }
+ $columnReference = $cellReference[4];
+ // Empty C reference is the current column
+ if ($columnReference === '') {
+ $columnReference = (string) $currentColumnNumber;
+ }
+ // Bracketed C references are relative to the current column
+ if (is_string($columnReference) && $columnReference[0] === '[') {
+ $columnReference = $currentColumnNumber + trim($columnReference, '[]');
+ }
+
+ if ($columnReference <= 0 || $rowReference <= 0) {
+ throw new Exception('Invalid R1C1-format Cell Reference, Value out of range');
+ }
+ $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
+
+ return $A1CellReference;
+ }
+
+ /**
+ * Converts a formula that uses R1C1 format cell address to an A1 format cell address.
+ */
+ public static function convertFormulaToA1(
+ string $formula,
+ int $currentRowNumber = 1,
+ int $currentColumnNumber = 1
+ ): string {
+ if (substr($formula, 0, 3) == 'of:') {
+ $formula = substr($formula, 3);
+ $temp = explode('"', $formula);
+ $key = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($key = !$key) {
+ $value = str_replace(['[.', '.', ']'], '', $value);
+ }
+ }
+ } else {
+ // Convert R1C1 style references to A1 style references (but only when not quoted)
+ $temp = explode('"', $formula);
+ $key = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($key = !$key) {
+ preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
+ // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
+ // through the formula from left to right. Reversing means that we work right to left.through
+ // the formula
+ $cellReferences = array_reverse($cellReferences);
+ // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
+ // then modify the formula to use that new reference
+ foreach ($cellReferences as $cellReference) {
+ $A1CellReference = self::convertToA1($cellReference[0][0], $currentRowNumber, $currentColumnNumber);
+ $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
+ }
+ }
+ }
+ }
+ unset($value);
+ // Then rebuild the formula string
+ $formula = implode('"', $temp);
+
+ return $formula;
+ }
+
+ /**
+ * Converts an A1 format cell address to an R1C1 format cell address.
+ * If $currentRowNumber or $currentColumnNumber are provided, then the R1C1 address will be formatted as a relative address.
+ */
+ public static function convertToR1C1(
+ string $address,
+ ?int $currentRowNumber = null,
+ ?int $currentColumnNumber = null
+ ): string {
+ $validityCheck = preg_match('/^\$?([A-Z]{1,3})\$?(\d{1,7})$/i', $address, $cellReference);
+
+ if ($validityCheck === 0) {
+ throw new Exception('Invalid A1-format Cell Reference');
+ }
+
+ $columnId = Coordinate::columnIndexFromString($cellReference[1]);
+ $rowId = (int) $cellReference[2];
+
+ if ($currentRowNumber !== null) {
+ if ($rowId === $currentRowNumber) {
+ $rowId = '';
+ } else {
+ $rowId = '[' . ($rowId - $currentRowNumber) . ']';
+ }
+ }
+
+ if ($currentColumnNumber !== null) {
+ if ($columnId === $currentColumnNumber) {
+ $columnId = '';
+ } else {
+ $columnId = '[' . ($columnId - $currentColumnNumber) . ']';
+ }
+ }
+
+ $R1C1Address = "R{$rowId}C{$columnId}";
+
+ return $R1C1Address;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
new file mode 100644
index 0000000..caa844a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+
+class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value = null)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ }
+
+ // Find out data type
+ $dataType = parent::dataTypeForValue($value);
+
+ // Style logic - strings
+ if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
+ // Test for booleans using locale-setting
+ if ($value == Calculation::getTRUE()) {
+ $cell->setValueExplicit(true, DataType::TYPE_BOOL);
+
+ return true;
+ } elseif ($value == Calculation::getFALSE()) {
+ $cell->setValueExplicit(false, DataType::TYPE_BOOL);
+
+ return true;
+ }
+
+ // Check for number in scientific format
+ if (preg_match('/^' . Calculation::CALCULATION_REGEXP_NUMBER . '$/', $value)) {
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+
+ return true;
+ }
+
+ // Check for fraction
+ if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) {
+ // Convert value to number
+ $value = $matches[2] / $matches[3];
+ if ($matches[1] == '-') {
+ $value = 0 - $value;
+ }
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode('??/??');
+
+ return true;
+ } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) {
+ // Convert value to number
+ $value = $matches[2] + ($matches[3] / $matches[4]);
+ if ($matches[1] == '-') {
+ $value = 0 - $value;
+ }
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode('# ??/??');
+
+ return true;
+ }
+
+ // Check for percentage
+ if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $value)) {
+ // Convert value to number
+ $value = (float) str_replace('%', '', $value) / 100;
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00);
+
+ return true;
+ }
+
+ // Check for currency
+ $currencyCode = StringHelper::getCurrencyCode();
+ $decimalSeparator = StringHelper::getDecimalSeparator();
+ $thousandsSeparator = StringHelper::getThousandsSeparator();
+ if (preg_match('/^' . preg_quote($currencyCode, '/') . ' *(\d{1,3}(' . preg_quote($thousandsSeparator, '/') . '\d{3})*|(\d+))(' . preg_quote($decimalSeparator, '/') . '\d{2})?$/', $value)) {
+ // Convert value to number
+ $value = (float) trim(str_replace([$currencyCode, $thousandsSeparator, $decimalSeparator], ['', '', '.'], $value));
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(
+ str_replace('$', $currencyCode, NumberFormat::FORMAT_CURRENCY_USD_SIMPLE)
+ );
+
+ return true;
+ } elseif (preg_match('/^\$ *(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/', $value)) {
+ // Convert value to number
+ $value = (float) trim(str_replace(['$', ','], '', $value));
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE);
+
+ return true;
+ }
+
+ // Check for time without seconds e.g. '9:45', '09:45'
+ if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) {
+ // Convert value to number
+ [$h, $m] = explode(':', $value);
+ $days = $h / 24 + $m / 1440;
+ $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3);
+
+ return true;
+ }
+
+ // Check for time with seconds '9:45:59', '09:45:59'
+ if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) {
+ // Convert value to number
+ [$h, $m, $s] = explode(':', $value);
+ $days = $h / 24 + $m / 1440 + $s / 86400;
+ // Convert value to number
+ $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4);
+
+ return true;
+ }
+
+ // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10'
+ if (($d = Date::stringToExcel($value)) !== false) {
+ // Convert value to number
+ $cell->setValueExplicit($d, DataType::TYPE_NUMERIC);
+ // Determine style. Either there is a time part or not. Look for ':'
+ if (strpos($value, ':') !== false) {
+ $formatCode = 'yyyy-mm-dd h:mm';
+ } else {
+ $formatCode = 'yyyy-mm-dd';
+ }
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode($formatCode);
+
+ return true;
+ }
+
+ // Check for newline character "\n"
+ if (strpos($value, "\n") !== false) {
+ $value = StringHelper::sanitizeUTF8($value);
+ $cell->setValueExplicit($value, DataType::TYPE_STRING);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getAlignment()->setWrapText(true);
+
+ return true;
+ }
+ }
+
+ // Not bound yet? Use parent...
+ return parent::bindValue($cell, $value);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
new file mode 100644
index 0000000..c3058cd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
@@ -0,0 +1,680 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Collection\Cells;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Cell
+{
+ /**
+ * Value binder to use.
+ *
+ * @var IValueBinder
+ */
+ private static $valueBinder;
+
+ /**
+ * Value of the cell.
+ *
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * Calculated value of the cell (used for caching)
+ * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
+ * create the original spreadsheet file.
+ * Note that this value is not guaranteed to reflect the actual calculated value because it is
+ * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
+ * values used by the formula have changed since it was last calculated.
+ *
+ * @var mixed
+ */
+ private $calculatedValue;
+
+ /**
+ * Type of the cell data.
+ *
+ * @var string
+ */
+ private $dataType;
+
+ /**
+ * Collection of cells.
+ *
+ * @var Cells
+ */
+ private $parent;
+
+ /**
+ * Index to cellXf.
+ *
+ * @var int
+ */
+ private $xfIndex = 0;
+
+ /**
+ * Attributes of the formula.
+ */
+ private $formulaAttributes;
+
+ /**
+ * Update the cell into the cell collection.
+ *
+ * @return $this
+ */
+ public function updateInCollection()
+ {
+ $this->parent->update($this);
+
+ return $this;
+ }
+
+ public function detach(): void
+ {
+ $this->parent = null;
+ }
+
+ public function attach(Cells $parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Create a new Cell.
+ *
+ * @param mixed $pValue
+ * @param string $pDataType
+ */
+ public function __construct($pValue, $pDataType, Worksheet $pSheet)
+ {
+ // Initialise cell value
+ $this->value = $pValue;
+
+ // Set worksheet cache
+ $this->parent = $pSheet->getCellCollection();
+
+ // Set datatype?
+ if ($pDataType !== null) {
+ if ($pDataType == DataType::TYPE_STRING2) {
+ $pDataType = DataType::TYPE_STRING;
+ }
+ $this->dataType = $pDataType;
+ } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new Exception('Value could not be bound to cell.');
+ }
+ }
+
+ /**
+ * Get cell coordinate column.
+ *
+ * @return string
+ */
+ public function getColumn()
+ {
+ return $this->parent->getCurrentColumn();
+ }
+
+ /**
+ * Get cell coordinate row.
+ *
+ * @return int
+ */
+ public function getRow()
+ {
+ return $this->parent->getCurrentRow();
+ }
+
+ /**
+ * Get cell coordinate.
+ *
+ * @return string
+ */
+ public function getCoordinate()
+ {
+ return $this->parent->getCurrentCoordinate();
+ }
+
+ /**
+ * Get cell value.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get cell value with formatting.
+ *
+ * @return string
+ */
+ public function getFormattedValue()
+ {
+ return (string) NumberFormat::toFormattedString(
+ $this->getCalculatedValue(),
+ $this->getStyle()
+ ->getNumberFormat()->getFormatCode()
+ );
+ }
+
+ /**
+ * Set cell value.
+ *
+ * Sets the value for a cell, automatically determining the datatype using the value binder
+ *
+ * @param mixed $pValue Value
+ *
+ * @return $this
+ */
+ public function setValue($pValue)
+ {
+ if (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new Exception('Value could not be bound to cell.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
+ *
+ * @param mixed $pValue Value
+ * @param string $pDataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return Cell
+ */
+ public function setValueExplicit($pValue, $pDataType)
+ {
+ // set the value according to data type
+ switch ($pDataType) {
+ case DataType::TYPE_NULL:
+ $this->value = $pValue;
+
+ break;
+ case DataType::TYPE_STRING2:
+ $pDataType = DataType::TYPE_STRING;
+ // no break
+ case DataType::TYPE_STRING:
+ // Synonym for string
+ case DataType::TYPE_INLINE:
+ // Rich text
+ $this->value = DataType::checkString($pValue);
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ if (is_string($pValue) && !is_numeric($pValue)) {
+ throw new Exception('Invalid numeric value for datatype Numeric');
+ }
+ $this->value = 0 + $pValue;
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $this->value = (string) $pValue;
+
+ break;
+ case DataType::TYPE_BOOL:
+ $this->value = (bool) $pValue;
+
+ break;
+ case DataType::TYPE_ERROR:
+ $this->value = DataType::checkErrorCode($pValue);
+
+ break;
+ default:
+ throw new Exception('Invalid datatype: ' . $pDataType);
+
+ break;
+ }
+
+ // set the datatype
+ $this->dataType = $pDataType;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get calculated cell value.
+ *
+ * @param bool $resetLog Whether the calculation engine logger should be reset or not
+ *
+ * @return mixed
+ */
+ public function getCalculatedValue($resetLog = true)
+ {
+ if ($this->dataType == DataType::TYPE_FORMULA) {
+ try {
+ $index = $this->getWorksheet()->getParent()->getActiveSheetIndex();
+ $result = Calculation::getInstance(
+ $this->getWorksheet()->getParent()
+ )->calculateCellValue($this, $resetLog);
+ $this->getWorksheet()->getParent()->setActiveSheetIndex($index);
+ // We don't yet handle array returns
+ if (is_array($result)) {
+ while (is_array($result)) {
+ $result = array_shift($result);
+ }
+ }
+ } catch (Exception $ex) {
+ if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
+ return $this->calculatedValue; // Fallback for calculations referencing external files.
+ } elseif (strpos($ex->getMessage(), 'undefined name') !== false) {
+ return \PhpOffice\PhpSpreadsheet\Calculation\Functions::NAME();
+ }
+
+ throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
+ $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
+ );
+ }
+
+ if ($result === '#Not Yet Implemented') {
+ return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
+ }
+
+ return $result;
+ } elseif ($this->value instanceof RichText) {
+ return $this->value->getPlainText();
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * Set old calculated value (cached).
+ *
+ * @param mixed $pValue Value
+ *
+ * @return Cell
+ */
+ public function setCalculatedValue($pValue)
+ {
+ if ($pValue !== null) {
+ $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
+ }
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get old calculated value (cached)
+ * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
+ * create the original spreadsheet file.
+ * Note that this value is not guaranteed to reflect the actual calculated value because it is
+ * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
+ * values used by the formula have changed since it was last calculated.
+ *
+ * @return mixed
+ */
+ public function getOldCalculatedValue()
+ {
+ return $this->calculatedValue;
+ }
+
+ /**
+ * Get cell data type.
+ *
+ * @return string
+ */
+ public function getDataType()
+ {
+ return $this->dataType;
+ }
+
+ /**
+ * Set cell data type.
+ *
+ * @param string $pDataType see DataType::TYPE_*
+ *
+ * @return Cell
+ */
+ public function setDataType($pDataType)
+ {
+ if ($pDataType == DataType::TYPE_STRING2) {
+ $pDataType = DataType::TYPE_STRING;
+ }
+ $this->dataType = $pDataType;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Identify if the cell contains a formula.
+ *
+ * @return bool
+ */
+ public function isFormula()
+ {
+ return $this->dataType == DataType::TYPE_FORMULA;
+ }
+
+ /**
+ * Does this cell contain Data validation rules?
+ *
+ * @return bool
+ */
+ public function hasDataValidation()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Data validation rules.
+ *
+ * @return DataValidation
+ */
+ public function getDataValidation()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getDataValidation($this->getCoordinate());
+ }
+
+ /**
+ * Set Data validation rules.
+ *
+ * @param DataValidation $pDataValidation
+ *
+ * @return Cell
+ */
+ public function setDataValidation(?DataValidation $pDataValidation = null)
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Does this cell contain valid value?
+ *
+ * @return bool
+ */
+ public function hasValidValue()
+ {
+ $validator = new DataValidator();
+
+ return $validator->isValid($this);
+ }
+
+ /**
+ * Does this cell contain a Hyperlink?
+ *
+ * @return bool
+ */
+ public function hasHyperlink()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Hyperlink.
+ *
+ * @return Hyperlink
+ */
+ public function getHyperlink()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getHyperlink($this->getCoordinate());
+ }
+
+ /**
+ * Set Hyperlink.
+ *
+ * @param Hyperlink $pHyperlink
+ *
+ * @return Cell
+ */
+ public function setHyperlink(?Hyperlink $pHyperlink = null)
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get cell collection.
+ *
+ * @return Cells
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Get parent worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->parent->getParent();
+ }
+
+ /**
+ * Is this cell in a merge range.
+ *
+ * @return bool
+ */
+ public function isInMergeRange()
+ {
+ return (bool) $this->getMergeRange();
+ }
+
+ /**
+ * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
+ *
+ * @return bool
+ */
+ public function isMergeRangeValueCell()
+ {
+ if ($mergeRange = $this->getMergeRange()) {
+ $mergeRange = Coordinate::splitRange($mergeRange);
+ [$startCell] = $mergeRange[0];
+ if ($this->getCoordinate() === $startCell) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * If this cell is in a merge range, then return the range.
+ *
+ * @return false|string
+ */
+ public function getMergeRange()
+ {
+ foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
+ if ($this->isInRange($mergeRange)) {
+ return $mergeRange;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get cell style.
+ *
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->getWorksheet()->getStyle($this->getCoordinate());
+ }
+
+ /**
+ * Re-bind parent.
+ *
+ * @return Cell
+ */
+ public function rebindParent(Worksheet $parent)
+ {
+ $this->parent = $parent->getCellCollection();
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Is cell in a specific range?
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return bool
+ */
+ public function isInRange($pRange)
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
+
+ // Translate properties
+ $myColumn = Coordinate::columnIndexFromString($this->getColumn());
+ $myRow = $this->getRow();
+
+ // Verify if cell is in range
+ return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
+ ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
+ }
+
+ /**
+ * Compare 2 cells.
+ *
+ * @param Cell $a Cell a
+ * @param Cell $b Cell b
+ *
+ * @return int Result of comparison (always -1 or 1, never zero!)
+ */
+ public static function compareCells(self $a, self $b)
+ {
+ if ($a->getRow() < $b->getRow()) {
+ return -1;
+ } elseif ($a->getRow() > $b->getRow()) {
+ return 1;
+ } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
+ return -1;
+ }
+
+ return 1;
+ }
+
+ /**
+ * Get value binder to use.
+ *
+ * @return IValueBinder
+ */
+ public static function getValueBinder()
+ {
+ if (self::$valueBinder === null) {
+ self::$valueBinder = new DefaultValueBinder();
+ }
+
+ return self::$valueBinder;
+ }
+
+ /**
+ * Set value binder to use.
+ */
+ public static function setValueBinder(IValueBinder $binder): void
+ {
+ self::$valueBinder = $binder;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if ((is_object($value)) && ($key != 'parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Get index to cellXf.
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->xfIndex;
+ }
+
+ /**
+ * Set index to cellXf.
+ *
+ * @param int $pValue
+ *
+ * @return Cell
+ */
+ public function setXfIndex($pValue)
+ {
+ $this->xfIndex = $pValue;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Set the formula attributes.
+ *
+ * @param mixed $pAttributes
+ *
+ * @return $this
+ */
+ public function setFormulaAttributes($pAttributes)
+ {
+ $this->formulaAttributes = $pAttributes;
+
+ return $this;
+ }
+
+ /**
+ * Get the formula attributes.
+ */
+ public function getFormulaAttributes()
+ {
+ return $this->formulaAttributes;
+ }
+
+ /**
+ * Convert to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->getValue();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php
new file mode 100644
index 0000000..0dd5cc8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php
@@ -0,0 +1,549 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+/**
+ * Helper class to manipulate cell coordinates.
+ *
+ * Columns indexes and rows are always based on 1, **not** on 0. This match the behavior
+ * that Excel users are used to, and also match the Excel functions `COLUMN()` and `ROW()`.
+ */
+abstract class Coordinate
+{
+ /**
+ * Default range variable constant.
+ *
+ * @var string
+ */
+ const DEFAULT_RANGE = 'A1:A1';
+
+ /**
+ * Coordinate from string.
+ *
+ * @param string $pCoordinateString eg: 'A1'
+ *
+ * @return string[] Array containing column and row (indexes 0 and 1)
+ */
+ public static function coordinateFromString($pCoordinateString)
+ {
+ if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) {
+ return [$matches[1], $matches[2]];
+ } elseif (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ } elseif ($pCoordinateString == '') {
+ throw new Exception('Cell coordinate can not be zero-length string');
+ }
+
+ throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
+ }
+
+ /**
+ * Checks if a coordinate represents a range of cells.
+ *
+ * @param string $coord eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2'
+ *
+ * @return bool Whether the coordinate represents a range of cells
+ */
+ public static function coordinateIsRange($coord)
+ {
+ return (strpos($coord, ':') !== false) || (strpos($coord, ',') !== false);
+ }
+
+ /**
+ * Make string row, column or cell coordinate absolute.
+ *
+ * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
+ * Note that this value can be a row or column reference as well as a cell reference
+ *
+ * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
+ */
+ public static function absoluteReference($pCoordinateString)
+ {
+ if (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ // Split out any worksheet name from the reference
+ [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
+ if ($worksheet > '') {
+ $worksheet .= '!';
+ }
+
+ // Create absolute coordinate
+ if (ctype_digit($pCoordinateString)) {
+ return $worksheet . '$' . $pCoordinateString;
+ } elseif (ctype_alpha($pCoordinateString)) {
+ return $worksheet . '$' . strtoupper($pCoordinateString);
+ }
+
+ return $worksheet . self::absoluteCoordinate($pCoordinateString);
+ }
+
+ /**
+ * Make string coordinate absolute.
+ *
+ * @param string $pCoordinateString e.g. 'A1'
+ *
+ * @return string Absolute coordinate e.g. '$A$1'
+ */
+ public static function absoluteCoordinate($pCoordinateString)
+ {
+ if (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ // Split out any worksheet name from the coordinate
+ [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
+ if ($worksheet > '') {
+ $worksheet .= '!';
+ }
+
+ // Create absolute coordinate
+ [$column, $row] = self::coordinateFromString($pCoordinateString);
+ $column = ltrim($column, '$');
+ $row = ltrim($row, '$');
+
+ return $worksheet . '$' . $column . '$' . $row;
+ }
+
+ /**
+ * Split range into coordinate strings.
+ *
+ * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
+ *
+ * @return array Array containing one or more arrays containing one or two coordinate strings
+ * e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']]
+ * or ['B4']
+ */
+ public static function splitRange($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ $exploded = explode(',', $pRange);
+ $counter = count($exploded);
+ for ($i = 0; $i < $counter; ++$i) {
+ $exploded[$i] = explode(':', $exploded[$i]);
+ }
+
+ return $exploded;
+ }
+
+ /**
+ * Build range from coordinate strings.
+ *
+ * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
+ *
+ * @return string String representation of $pRange
+ */
+ public static function buildRange(array $pRange)
+ {
+ // Verify range
+ if (empty($pRange) || !is_array($pRange[0])) {
+ throw new Exception('Range does not contain any information');
+ }
+
+ // Build range
+ $counter = count($pRange);
+ for ($i = 0; $i < $counter; ++$i) {
+ $pRange[$i] = implode(':', $pRange[$i]);
+ }
+
+ return implode(',', $pRange);
+ }
+
+ /**
+ * Calculate range boundaries.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range coordinates [Start Cell, End Cell]
+ * where Start Cell and End Cell are arrays (Column Number, Row Number)
+ */
+ public static function rangeBoundaries($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ // Calculate range outer borders
+ $rangeStart = self::coordinateFromString($rangeA);
+ $rangeEnd = self::coordinateFromString($rangeB);
+
+ // Translate column into index
+ $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
+ $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
+
+ return [$rangeStart, $rangeEnd];
+ }
+
+ /**
+ * Calculate range dimension.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range dimension (width, height)
+ */
+ public static function rangeDimension($pRange)
+ {
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = self::rangeBoundaries($pRange);
+
+ return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
+ }
+
+ /**
+ * Calculate range boundaries.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range coordinates [Start Cell, End Cell]
+ * where Start Cell and End Cell are arrays [Column ID, Row Number]
+ */
+ public static function getRangeBoundaries($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
+ }
+
+ /**
+ * Column index from string.
+ *
+ * @param string $pString eg 'A'
+ *
+ * @return int Column index (A = 1)
+ */
+ public static function columnIndexFromString($pString)
+ {
+ // Using a lookup cache adds a slight memory overhead, but boosts speed
+ // caching using a static within the method is faster than a class static,
+ // though it's additional memory overhead
+ static $indexCache = [];
+
+ if (isset($indexCache[$pString])) {
+ return $indexCache[$pString];
+ }
+ // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
+ // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
+ // memory overhead either
+ static $columnLookup = [
+ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
+ 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
+ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
+ 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26,
+ ];
+
+ // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
+ // for improved performance
+ if (isset($pString[0])) {
+ if (!isset($pString[1])) {
+ $indexCache[$pString] = $columnLookup[$pString];
+
+ return $indexCache[$pString];
+ } elseif (!isset($pString[2])) {
+ $indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]];
+
+ return $indexCache[$pString];
+ } elseif (!isset($pString[3])) {
+ $indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]];
+
+ return $indexCache[$pString];
+ }
+ }
+
+ throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty'));
+ }
+
+ /**
+ * String from column index.
+ *
+ * @param int $columnIndex Column index (A = 1)
+ *
+ * @return string
+ */
+ public static function stringFromColumnIndex($columnIndex)
+ {
+ static $indexCache = [];
+
+ if (!isset($indexCache[$columnIndex])) {
+ $indexValue = $columnIndex;
+ $base26 = null;
+ do {
+ $characterValue = ($indexValue % 26) ?: 26;
+ $indexValue = ($indexValue - $characterValue) / 26;
+ $base26 = chr($characterValue + 64) . ($base26 ?: '');
+ } while ($indexValue > 0);
+ $indexCache[$columnIndex] = $base26;
+ }
+
+ return $indexCache[$columnIndex];
+ }
+
+ /**
+ * Extract all cell references in range, which may be comprised of multiple cell ranges.
+ *
+ * @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3'
+ *
+ * @return array Array containing single cell references
+ */
+ public static function extractAllCellReferencesInRange($cellRange): array
+ {
+ [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange);
+
+ $cells = [];
+ foreach ($ranges as $range) {
+ $cells[] = self::getReferencesForCellBlock($range);
+ }
+
+ $cells = self::processRangeSetOperators($operators, $cells);
+
+ if (empty($cells)) {
+ return [];
+ }
+
+ $cellList = array_merge(...$cells);
+ $cellList = self::sortCellReferenceArray($cellList);
+
+ return $cellList;
+ }
+
+ private static function processRangeSetOperators(array $operators, array $cells): array
+ {
+ for ($offset = 0; $offset < count($operators); ++$offset) {
+ $operator = $operators[$offset];
+ if ($operator !== ' ') {
+ continue;
+ }
+
+ $cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]);
+ unset($operators[$offset], $cells[$offset + 1]);
+ $operators = array_values($operators);
+ $cells = array_values($cells);
+ --$offset;
+ }
+
+ return $cells;
+ }
+
+ private static function sortCellReferenceArray(array $cellList): array
+ {
+ // Sort the result by column and row
+ $sortKeys = [];
+ foreach ($cellList as $coord) {
+ [$column, $row] = sscanf($coord, '%[A-Z]%d');
+ $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
+ }
+ ksort($sortKeys);
+
+ return array_values($sortKeys);
+ }
+
+ /**
+ * Get all cell references for an individual cell block.
+ *
+ * @param string $cellBlock A cell range e.g. A4:B5
+ *
+ * @return array All individual cells in that range
+ */
+ private static function getReferencesForCellBlock($cellBlock)
+ {
+ $returnValue = [];
+
+ // Single cell?
+ if (!self::coordinateIsRange($cellBlock)) {
+ return (array) $cellBlock;
+ }
+
+ // Range...
+ $ranges = self::splitRange($cellBlock);
+ foreach ($ranges as $range) {
+ // Single cell?
+ if (!isset($range[1])) {
+ $returnValue[] = $range[0];
+
+ continue;
+ }
+
+ // Range...
+ [$rangeStart, $rangeEnd] = $range;
+ [$startColumn, $startRow] = self::coordinateFromString($rangeStart);
+ [$endColumn, $endRow] = self::coordinateFromString($rangeEnd);
+ $startColumnIndex = self::columnIndexFromString($startColumn);
+ $endColumnIndex = self::columnIndexFromString($endColumn);
+ ++$endColumnIndex;
+
+ // Current data
+ $currentColumnIndex = $startColumnIndex;
+ $currentRow = $startRow;
+
+ self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow);
+
+ // Loop cells
+ while ($currentColumnIndex < $endColumnIndex) {
+ while ($currentRow <= $endRow) {
+ $returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow;
+ ++$currentRow;
+ }
+ ++$currentColumnIndex;
+ $currentRow = $startRow;
+ }
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Convert an associative array of single cell coordinates to values to an associative array
+ * of cell ranges to values. Only adjacent cell coordinates with the same
+ * value will be merged. If the value is an object, it must implement the method getHashCode().
+ *
+ * For example, this function converts:
+ *
+ * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ]
+ *
+ * to:
+ *
+ * [ 'A1:A3' => 'x', 'A4' => 'y' ]
+ *
+ * @param array $pCoordCollection associative array mapping coordinates to values
+ *
+ * @return array associative array mapping coordinate ranges to valuea
+ */
+ public static function mergeRangesInCollection(array $pCoordCollection)
+ {
+ $hashedValues = [];
+ $mergedCoordCollection = [];
+
+ foreach ($pCoordCollection as $coord => $value) {
+ if (self::coordinateIsRange($coord)) {
+ $mergedCoordCollection[$coord] = $value;
+
+ continue;
+ }
+
+ [$column, $row] = self::coordinateFromString($coord);
+ $row = (int) (ltrim($row, '$'));
+ $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value);
+
+ if (!isset($hashedValues[$hashCode])) {
+ $hashedValues[$hashCode] = (object) [
+ 'value' => $value,
+ 'col' => $column,
+ 'rows' => [$row],
+ ];
+ } else {
+ $hashedValues[$hashCode]->rows[] = $row;
+ }
+ }
+
+ ksort($hashedValues);
+
+ foreach ($hashedValues as $hashedValue) {
+ sort($hashedValue->rows);
+ $rowStart = null;
+ $rowEnd = null;
+ $ranges = [];
+
+ foreach ($hashedValue->rows as $row) {
+ if ($rowStart === null) {
+ $rowStart = $row;
+ $rowEnd = $row;
+ } elseif ($rowEnd === $row - 1) {
+ $rowEnd = $row;
+ } else {
+ if ($rowStart == $rowEnd) {
+ $ranges[] = $hashedValue->col . $rowStart;
+ } else {
+ $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
+ }
+
+ $rowStart = $row;
+ $rowEnd = $row;
+ }
+ }
+
+ if ($rowStart !== null) {
+ if ($rowStart == $rowEnd) {
+ $ranges[] = $hashedValue->col . $rowStart;
+ } else {
+ $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
+ }
+ }
+
+ foreach ($ranges as $range) {
+ $mergedCoordCollection[$range] = $hashedValue->value;
+ }
+ }
+
+ return $mergedCoordCollection;
+ }
+
+ /**
+ * Get the individual cell blocks from a range string, removing any $ characters.
+ * then splitting by operators and returning an array with ranges and operators.
+ *
+ * @param string $rangeString
+ *
+ * @return array[]
+ */
+ private static function getCellBlocksFromRangeString($rangeString)
+ {
+ $rangeString = str_replace('$', '', strtoupper($rangeString));
+
+ // split range sets on intersection (space) or union (,) operators
+ $tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE);
+ // separate the range sets and the operators into arrays
+ $split = array_chunk($tokens, 2);
+ $ranges = array_column($split, 0);
+ $operators = array_column($split, 1);
+
+ return [$ranges, $operators];
+ }
+
+ /**
+ * Check that the given range is valid, i.e. that the start column and row are not greater than the end column and
+ * row.
+ *
+ * @param string $cellBlock The original range, for displaying a meaningful error message
+ * @param int $startColumnIndex
+ * @param int $endColumnIndex
+ * @param int $currentRow
+ * @param int $endRow
+ */
+ private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow): void
+ {
+ if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) {
+ throw new Exception('Invalid range: "' . $cellBlock . '"');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php
new file mode 100644
index 0000000..5e398b4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class DataType
+{
+ // Data types
+ const TYPE_STRING2 = 'str';
+ const TYPE_STRING = 's';
+ const TYPE_FORMULA = 'f';
+ const TYPE_NUMERIC = 'n';
+ const TYPE_BOOL = 'b';
+ const TYPE_NULL = 'null';
+ const TYPE_INLINE = 'inlineStr';
+ const TYPE_ERROR = 'e';
+
+ /**
+ * List of error codes.
+ *
+ * @var array
+ */
+ private static $errorCodes = [
+ '#NULL!' => 0,
+ '#DIV/0!' => 1,
+ '#VALUE!' => 2,
+ '#REF!' => 3,
+ '#NAME?' => 4,
+ '#NUM!' => 5,
+ '#N/A' => 6,
+ ];
+
+ /**
+ * Get list of error codes.
+ *
+ * @return array
+ */
+ public static function getErrorCodes()
+ {
+ return self::$errorCodes;
+ }
+
+ /**
+ * Check a string that it satisfies Excel requirements.
+ *
+ * @param null|RichText|string $pValue Value to sanitize to an Excel string
+ *
+ * @return null|RichText|string Sanitized value
+ */
+ public static function checkString($pValue)
+ {
+ if ($pValue instanceof RichText) {
+ // TODO: Sanitize Rich-Text string (max. character count is 32,767)
+ return $pValue;
+ }
+
+ // string must never be longer than 32,767 characters, truncate if necessary
+ $pValue = StringHelper::substring($pValue, 0, 32767);
+
+ // we require that newline is represented as "\n" in core, not as "\r\n" or "\r"
+ $pValue = str_replace(["\r\n", "\r"], "\n", $pValue);
+
+ return $pValue;
+ }
+
+ /**
+ * Check a value that it is a valid error code.
+ *
+ * @param mixed $pValue Value to sanitize to an Excel error code
+ *
+ * @return string Sanitized value
+ */
+ public static function checkErrorCode($pValue)
+ {
+ $pValue = (string) $pValue;
+
+ if (!isset(self::$errorCodes[$pValue])) {
+ $pValue = '#NULL!';
+ }
+
+ return $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php
new file mode 100644
index 0000000..a68147f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php
@@ -0,0 +1,481 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+class DataValidation
+{
+ // Data validation types
+ const TYPE_NONE = 'none';
+ const TYPE_CUSTOM = 'custom';
+ const TYPE_DATE = 'date';
+ const TYPE_DECIMAL = 'decimal';
+ const TYPE_LIST = 'list';
+ const TYPE_TEXTLENGTH = 'textLength';
+ const TYPE_TIME = 'time';
+ const TYPE_WHOLE = 'whole';
+
+ // Data validation error styles
+ const STYLE_STOP = 'stop';
+ const STYLE_WARNING = 'warning';
+ const STYLE_INFORMATION = 'information';
+
+ // Data validation operators
+ const OPERATOR_BETWEEN = 'between';
+ const OPERATOR_EQUAL = 'equal';
+ const OPERATOR_GREATERTHAN = 'greaterThan';
+ const OPERATOR_GREATERTHANOREQUAL = 'greaterThanOrEqual';
+ const OPERATOR_LESSTHAN = 'lessThan';
+ const OPERATOR_LESSTHANOREQUAL = 'lessThanOrEqual';
+ const OPERATOR_NOTBETWEEN = 'notBetween';
+ const OPERATOR_NOTEQUAL = 'notEqual';
+
+ /**
+ * Formula 1.
+ *
+ * @var string
+ */
+ private $formula1 = '';
+
+ /**
+ * Formula 2.
+ *
+ * @var string
+ */
+ private $formula2 = '';
+
+ /**
+ * Type.
+ *
+ * @var string
+ */
+ private $type = self::TYPE_NONE;
+
+ /**
+ * Error style.
+ *
+ * @var string
+ */
+ private $errorStyle = self::STYLE_STOP;
+
+ /**
+ * Operator.
+ *
+ * @var string
+ */
+ private $operator = self::OPERATOR_BETWEEN;
+
+ /**
+ * Allow Blank.
+ *
+ * @var bool
+ */
+ private $allowBlank = false;
+
+ /**
+ * Show DropDown.
+ *
+ * @var bool
+ */
+ private $showDropDown = false;
+
+ /**
+ * Show InputMessage.
+ *
+ * @var bool
+ */
+ private $showInputMessage = false;
+
+ /**
+ * Show ErrorMessage.
+ *
+ * @var bool
+ */
+ private $showErrorMessage = false;
+
+ /**
+ * Error title.
+ *
+ * @var string
+ */
+ private $errorTitle = '';
+
+ /**
+ * Error.
+ *
+ * @var string
+ */
+ private $error = '';
+
+ /**
+ * Prompt title.
+ *
+ * @var string
+ */
+ private $promptTitle = '';
+
+ /**
+ * Prompt.
+ *
+ * @var string
+ */
+ private $prompt = '';
+
+ /**
+ * Create a new DataValidation.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get Formula 1.
+ *
+ * @return string
+ */
+ public function getFormula1()
+ {
+ return $this->formula1;
+ }
+
+ /**
+ * Set Formula 1.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setFormula1($value)
+ {
+ $this->formula1 = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Formula 2.
+ *
+ * @return string
+ */
+ public function getFormula2()
+ {
+ return $this->formula2;
+ }
+
+ /**
+ * Set Formula 2.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setFormula2($value)
+ {
+ $this->formula2 = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set Type.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setType($value)
+ {
+ $this->type = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error style.
+ *
+ * @return string
+ */
+ public function getErrorStyle()
+ {
+ return $this->errorStyle;
+ }
+
+ /**
+ * Set Error style.
+ *
+ * @param string $value see self::STYLE_*
+ *
+ * @return $this
+ */
+ public function setErrorStyle($value)
+ {
+ $this->errorStyle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Operator.
+ *
+ * @return string
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Set Operator.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setOperator($value)
+ {
+ $this->operator = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Allow Blank.
+ *
+ * @return bool
+ */
+ public function getAllowBlank()
+ {
+ return $this->allowBlank;
+ }
+
+ /**
+ * Set Allow Blank.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setAllowBlank($value)
+ {
+ $this->allowBlank = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show DropDown.
+ *
+ * @return bool
+ */
+ public function getShowDropDown()
+ {
+ return $this->showDropDown;
+ }
+
+ /**
+ * Set Show DropDown.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowDropDown($value)
+ {
+ $this->showDropDown = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show InputMessage.
+ *
+ * @return bool
+ */
+ public function getShowInputMessage()
+ {
+ return $this->showInputMessage;
+ }
+
+ /**
+ * Set Show InputMessage.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowInputMessage($value)
+ {
+ $this->showInputMessage = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show ErrorMessage.
+ *
+ * @return bool
+ */
+ public function getShowErrorMessage()
+ {
+ return $this->showErrorMessage;
+ }
+
+ /**
+ * Set Show ErrorMessage.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowErrorMessage($value)
+ {
+ $this->showErrorMessage = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error title.
+ *
+ * @return string
+ */
+ public function getErrorTitle()
+ {
+ return $this->errorTitle;
+ }
+
+ /**
+ * Set Error title.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setErrorTitle($value)
+ {
+ $this->errorTitle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error.
+ *
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Set Error.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setError($value)
+ {
+ $this->error = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Prompt title.
+ *
+ * @return string
+ */
+ public function getPromptTitle()
+ {
+ return $this->promptTitle;
+ }
+
+ /**
+ * Set Prompt title.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setPromptTitle($value)
+ {
+ $this->promptTitle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Prompt.
+ *
+ * @return string
+ */
+ public function getPrompt()
+ {
+ return $this->prompt;
+ }
+
+ /**
+ * Set Prompt.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setPrompt($value)
+ {
+ $this->prompt = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->formula1 .
+ $this->formula2 .
+ $this->type .
+ $this->errorStyle .
+ $this->operator .
+ ($this->allowBlank ? 't' : 'f') .
+ ($this->showDropDown ? 't' : 'f') .
+ ($this->showInputMessage ? 't' : 'f') .
+ ($this->showErrorMessage ? 't' : 'f') .
+ $this->errorTitle .
+ $this->error .
+ $this->promptTitle .
+ $this->prompt .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
new file mode 100644
index 0000000..5ba9e13
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+/**
+ * Validate a cell value according to its validation rules.
+ */
+class DataValidator
+{
+ /**
+ * Does this cell contain valid value?
+ *
+ * @param Cell $cell Cell to check the value
+ *
+ * @return bool
+ */
+ public function isValid(Cell $cell)
+ {
+ if (!$cell->hasDataValidation()) {
+ return true;
+ }
+
+ $cellValue = $cell->getValue();
+ $dataValidation = $cell->getDataValidation();
+
+ if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) {
+ return false;
+ }
+
+ // TODO: write check on all cases
+ switch ($dataValidation->getType()) {
+ case DataValidation::TYPE_LIST:
+ return $this->isValueInList($cell);
+ }
+
+ return false;
+ }
+
+ /**
+ * Does this cell contain valid value, based on list?
+ *
+ * @param Cell $cell Cell to check the value
+ *
+ * @return bool
+ */
+ private function isValueInList(Cell $cell)
+ {
+ $cellValue = $cell->getValue();
+ $dataValidation = $cell->getDataValidation();
+
+ $formula1 = $dataValidation->getFormula1();
+ if (!empty($formula1)) {
+ // inline values list
+ if ($formula1[0] === '"') {
+ return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true);
+ } elseif (strpos($formula1, ':') > 0) {
+ // values list cells
+ $matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)';
+ $calculation = Calculation::getInstance($cell->getWorksheet()->getParent());
+
+ try {
+ $result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell);
+
+ return $result !== Functions::NA();
+ } catch (Exception $ex) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
new file mode 100644
index 0000000..ecf466f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use DateTimeInterface;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class DefaultValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ } elseif (is_object($value)) {
+ // Handle any objects that might be injected
+ if ($value instanceof DateTimeInterface) {
+ $value = $value->format('Y-m-d H:i:s');
+ } elseif (!($value instanceof RichText)) {
+ $value = (string) $value;
+ }
+ }
+
+ // Set value explicit
+ $cell->setValueExplicit($value, static::dataTypeForValue($value));
+
+ // Done!
+ return true;
+ }
+
+ /**
+ * DataType for value.
+ *
+ * @param mixed $pValue
+ *
+ * @return string
+ */
+ public static function dataTypeForValue($pValue)
+ {
+ // Match the value against a few data types
+ if ($pValue === null) {
+ return DataType::TYPE_NULL;
+ } elseif (is_float($pValue) || is_int($pValue)) {
+ return DataType::TYPE_NUMERIC;
+ } elseif (is_bool($pValue)) {
+ return DataType::TYPE_BOOL;
+ } elseif ($pValue === '') {
+ return DataType::TYPE_STRING;
+ } elseif ($pValue instanceof RichText) {
+ return DataType::TYPE_INLINE;
+ } elseif (is_string($pValue) && $pValue[0] === '=' && strlen($pValue) > 1) {
+ return DataType::TYPE_FORMULA;
+ } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) {
+ $tValue = ltrim($pValue, '+-');
+ if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') {
+ return DataType::TYPE_STRING;
+ } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) {
+ return DataType::TYPE_STRING;
+ } elseif (!is_numeric($pValue)) {
+ return DataType::TYPE_STRING;
+ }
+
+ return DataType::TYPE_NUMERIC;
+ } elseif (is_string($pValue)) {
+ $errorCodes = DataType::getErrorCodes();
+ if (isset($errorCodes[$pValue])) {
+ return DataType::TYPE_ERROR;
+ }
+ }
+
+ return DataType::TYPE_STRING;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php
new file mode 100644
index 0000000..03fc16d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+class Hyperlink
+{
+ /**
+ * URL to link the cell to.
+ *
+ * @var string
+ */
+ private $url;
+
+ /**
+ * Tooltip to display on the hyperlink.
+ *
+ * @var string
+ */
+ private $tooltip;
+
+ /**
+ * Create a new Hyperlink.
+ *
+ * @param string $pUrl Url to link the cell to
+ * @param string $pTooltip Tooltip to display on the hyperlink
+ */
+ public function __construct($pUrl = '', $pTooltip = '')
+ {
+ // Initialise member variables
+ $this->url = $pUrl;
+ $this->tooltip = $pTooltip;
+ }
+
+ /**
+ * Get URL.
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set URL.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setUrl($value)
+ {
+ $this->url = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get tooltip.
+ *
+ * @return string
+ */
+ public function getTooltip()
+ {
+ return $this->tooltip;
+ }
+
+ /**
+ * Set tooltip.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setTooltip($value)
+ {
+ $this->tooltip = $value;
+
+ return $this;
+ }
+
+ /**
+ * Is this hyperlink internal? (to another worksheet).
+ *
+ * @return bool
+ */
+ public function isInternal()
+ {
+ return strpos($this->url, 'sheet://') !== false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTypeHyperlink()
+ {
+ return $this->isInternal() ? '' : 'External';
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->url .
+ $this->tooltip .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
new file mode 100644
index 0000000..99e4426
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+interface IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value);
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php
new file mode 100644
index 0000000..974487e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class StringValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ }
+
+ $cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
+
+ // Done!
+ return true;
+ }
+}