updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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 . '"');
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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__
);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}