mirror of
https://github.com/Karaka-Management/Resources.git
synced 2026-02-15 11:48:41 +00:00
update phpexcel
This commit is contained in:
parent
0c69612c48
commit
b652dacd5b
4777
PhpOffice/PhpSpreadsheet/Calculation/Calculation.php
Normal file
4777
PhpOffice/PhpSpreadsheet/Calculation/Calculation.php
Normal file
File diff suppressed because it is too large
Load Diff
19
PhpOffice/PhpSpreadsheet/Calculation/Category.php
Normal file
19
PhpOffice/PhpSpreadsheet/Calculation/Category.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
abstract class Category
|
||||||
|
{
|
||||||
|
// Function categories
|
||||||
|
const CATEGORY_CUBE = 'Cube';
|
||||||
|
const CATEGORY_DATABASE = 'Database';
|
||||||
|
const CATEGORY_DATE_AND_TIME = 'Date and Time';
|
||||||
|
const CATEGORY_ENGINEERING = 'Engineering';
|
||||||
|
const CATEGORY_FINANCIAL = 'Financial';
|
||||||
|
const CATEGORY_INFORMATION = 'Information';
|
||||||
|
const CATEGORY_LOGICAL = 'Logical';
|
||||||
|
const CATEGORY_LOOKUP_AND_REFERENCE = 'Lookup and Reference';
|
||||||
|
const CATEGORY_MATH_AND_TRIG = 'Math and Trig';
|
||||||
|
const CATEGORY_STATISTICAL = 'Statistical';
|
||||||
|
const CATEGORY_TEXT_AND_DATA = 'Text and Data';
|
||||||
|
}
|
||||||
632
PhpOffice/PhpSpreadsheet/Calculation/Database.php
Normal file
632
PhpOffice/PhpSpreadsheet/Calculation/Database.php
Normal file
|
|
@ -0,0 +1,632 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
class Database
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* fieldExtract.
|
||||||
|
*
|
||||||
|
* Extracts the column ID to use for the data field.
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param mixed $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
private static function fieldExtract($database, $field)
|
||||||
|
{
|
||||||
|
$field = strtoupper(Functions::flattenSingleValue($field));
|
||||||
|
$fieldNames = array_map('strtoupper', array_shift($database));
|
||||||
|
|
||||||
|
if (is_numeric($field)) {
|
||||||
|
$keys = array_keys($fieldNames);
|
||||||
|
|
||||||
|
return $keys[$field - 1];
|
||||||
|
}
|
||||||
|
$key = array_search($field, $fieldNames);
|
||||||
|
|
||||||
|
return ($key) ? $key : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* Parses the selection criteria, extracts the database rows that match those criteria, and
|
||||||
|
* returns that subset of rows.
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return array of mixed
|
||||||
|
*/
|
||||||
|
private static function filter($database, $criteria)
|
||||||
|
{
|
||||||
|
$fieldNames = array_shift($database);
|
||||||
|
$criteriaNames = array_shift($criteria);
|
||||||
|
|
||||||
|
// Convert the criteria into a set of AND/OR conditions with [:placeholders]
|
||||||
|
$testConditions = $testValues = [];
|
||||||
|
$testConditionsCount = 0;
|
||||||
|
foreach ($criteriaNames as $key => $criteriaName) {
|
||||||
|
$testCondition = [];
|
||||||
|
$testConditionCount = 0;
|
||||||
|
foreach ($criteria as $row => $criterion) {
|
||||||
|
if ($criterion[$key] > '') {
|
||||||
|
$testCondition[] = '[:' . $criteriaName . ']' . Functions::ifCondition($criterion[$key]);
|
||||||
|
++$testConditionCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($testConditionCount > 1) {
|
||||||
|
$testConditions[] = 'OR(' . implode(',', $testCondition) . ')';
|
||||||
|
++$testConditionsCount;
|
||||||
|
} elseif ($testConditionCount == 1) {
|
||||||
|
$testConditions[] = $testCondition[0];
|
||||||
|
++$testConditionsCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($testConditionsCount > 1) {
|
||||||
|
$testConditionSet = 'AND(' . implode(',', $testConditions) . ')';
|
||||||
|
} elseif ($testConditionsCount == 1) {
|
||||||
|
$testConditionSet = $testConditions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each row of the database
|
||||||
|
foreach ($database as $dataRow => $dataValues) {
|
||||||
|
// Substitute actual values from the database row for our [:placeholders]
|
||||||
|
$testConditionList = $testConditionSet;
|
||||||
|
foreach ($criteriaNames as $key => $criteriaName) {
|
||||||
|
$k = array_search($criteriaName, $fieldNames);
|
||||||
|
if (isset($dataValues[$k])) {
|
||||||
|
$dataValue = $dataValues[$k];
|
||||||
|
$dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
|
||||||
|
$testConditionList = str_replace('[:' . $criteriaName . ']', $dataValue, $testConditionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// evaluate the criteria against the row data
|
||||||
|
$result = Calculation::getInstance()->_calculateFormulaValue('=' . $testConditionList);
|
||||||
|
// If the row failed to meet the criteria, remove it from the database
|
||||||
|
if (!$result) {
|
||||||
|
unset($database[$dataRow]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getFilteredColumn($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
// reduce the database to a set of rows that match all the criteria
|
||||||
|
$database = self::filter($database, $criteria);
|
||||||
|
// extract an array of values for the requested column
|
||||||
|
$colData = [];
|
||||||
|
foreach ($database as $row) {
|
||||||
|
$colData[] = $row[$field];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $colData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DAVERAGE.
|
||||||
|
*
|
||||||
|
* Averages the values in a column of a list or database that match conditions you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DAVERAGE(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function DAVERAGE($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::AVERAGE(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCOUNT.
|
||||||
|
*
|
||||||
|
* Counts the cells that contain numbers in a column of a list or database that match conditions
|
||||||
|
* that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DCOUNT(database,[field],criteria)
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DAVERAGE(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @TODO The field argument is optional. If field is omitted, DCOUNT counts all records in the
|
||||||
|
* database that match the criteria.
|
||||||
|
*/
|
||||||
|
public static function DCOUNT($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::COUNT(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCOUNTA.
|
||||||
|
*
|
||||||
|
* Counts the nonblank cells in a column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DCOUNTA(database,[field],criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @TODO The field argument is optional. If field is omitted, DCOUNTA counts all records in the
|
||||||
|
* database that match the criteria.
|
||||||
|
*/
|
||||||
|
public static function DCOUNTA($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce the database to a set of rows that match all the criteria
|
||||||
|
$database = self::filter($database, $criteria);
|
||||||
|
// extract an array of values for the requested column
|
||||||
|
$colData = [];
|
||||||
|
foreach ($database as $row) {
|
||||||
|
$colData[] = $row[$field];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::COUNTA(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DGET.
|
||||||
|
*
|
||||||
|
* Extracts a single value from a column of a list or database that matches conditions that you
|
||||||
|
* specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DGET(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function DGET($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
$colData = self::getFilteredColumn($database, $field, $criteria);
|
||||||
|
if (count($colData) > 1) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $colData[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMAX.
|
||||||
|
*
|
||||||
|
* Returns the largest number in a column of a list or database that matches conditions you that
|
||||||
|
* specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DMAX(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DMAX($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::MAX(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMIN.
|
||||||
|
*
|
||||||
|
* Returns the smallest number in a column of a list or database that matches conditions you that
|
||||||
|
* specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DMIN(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DMIN($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::MIN(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DPRODUCT.
|
||||||
|
*
|
||||||
|
* Multiplies the values in a column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DPRODUCT(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DPRODUCT($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return MathTrig::PRODUCT(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DSTDEV.
|
||||||
|
*
|
||||||
|
* Estimates the standard deviation of a population based on a sample by using the numbers in a
|
||||||
|
* column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DSTDEV(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function DSTDEV($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::STDEV(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DSTDEVP.
|
||||||
|
*
|
||||||
|
* Calculates the standard deviation of a population based on the entire population by using the
|
||||||
|
* numbers in a column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DSTDEVP(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function DSTDEVP($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::STDEVP(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DSUM.
|
||||||
|
*
|
||||||
|
* Adds the numbers in a column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DSUM(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DSUM($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return MathTrig::SUM(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DVAR.
|
||||||
|
*
|
||||||
|
* Estimates the variance of a population based on a sample by using the numbers in a column
|
||||||
|
* of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DVAR(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DVAR($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::VARFunc(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DVARP.
|
||||||
|
*
|
||||||
|
* Calculates the variance of a population based on the entire population by using the numbers
|
||||||
|
* in a column of a list or database that match conditions that you specify.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* DVARP(database,field,criteria)
|
||||||
|
*
|
||||||
|
* @category Database Functions
|
||||||
|
*
|
||||||
|
* @param mixed[] $database The range of cells that makes up the list or database.
|
||||||
|
* A database is a list of related data in which rows of related
|
||||||
|
* information are records, and columns of data are fields. The
|
||||||
|
* first row of the list contains labels for each column.
|
||||||
|
* @param int|string $field Indicates which column is used in the function. Enter the
|
||||||
|
* column label enclosed between double quotation marks, such as
|
||||||
|
* "Age" or "Yield," or a number (without quotation marks) that
|
||||||
|
* represents the position of the column within the list: 1 for
|
||||||
|
* the first column, 2 for the second column, and so on.
|
||||||
|
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
|
||||||
|
* You can use any range for the criteria argument, as long as it
|
||||||
|
* includes at least one column label and at least one cell below
|
||||||
|
* the column label in which you specify a condition for the
|
||||||
|
* column.
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function DVARP($database, $field, $criteria)
|
||||||
|
{
|
||||||
|
$field = self::fieldExtract($database, $field);
|
||||||
|
if ($field === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return Statistical::VARP(
|
||||||
|
self::getFilteredColumn($database, $field, $criteria)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1647
PhpOffice/PhpSpreadsheet/Calculation/DateTime.php
Normal file
1647
PhpOffice/PhpSpreadsheet/Calculation/DateTime.php
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
|
||||||
|
|
||||||
|
class CyclicReferenceStack
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The call stack for calculated cells.
|
||||||
|
*
|
||||||
|
* @var mixed[]
|
||||||
|
*/
|
||||||
|
private $stack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of entries on the stack.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a new entry onto the stack.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function push($value)
|
||||||
|
{
|
||||||
|
$this->stack[$value] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop the last entry from the stack.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function pop()
|
||||||
|
{
|
||||||
|
return array_pop($this->stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to see if a specified entry exists on the stack.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function onStack($value)
|
||||||
|
{
|
||||||
|
return isset($this->stack[$value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the stack.
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->stack = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of all entries on the stack.
|
||||||
|
*
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
public function showStack()
|
||||||
|
{
|
||||||
|
return $this->stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
128
PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php
Normal file
128
PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
|
||||||
|
|
||||||
|
class Logger
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Flag to determine whether a debug log should be generated by the calculation engine
|
||||||
|
* If true, then a debug log will be generated
|
||||||
|
* If false, then a debug log will not be generated.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $writeDebugLog = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to determine whether a debug log should be echoed by the calculation engine
|
||||||
|
* If true, then a debug log will be echoed
|
||||||
|
* If false, then a debug log will not be echoed
|
||||||
|
* A debug log can only be echoed if it is generated.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $echoDebugLog = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debug log generated by the calculation engine.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $debugLog = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The calculation engine cell reference stack.
|
||||||
|
*
|
||||||
|
* @var CyclicReferenceStack
|
||||||
|
*/
|
||||||
|
private $cellStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a Calculation engine logger.
|
||||||
|
*
|
||||||
|
* @param CyclicReferenceStack $stack
|
||||||
|
*/
|
||||||
|
public function __construct(CyclicReferenceStack $stack)
|
||||||
|
{
|
||||||
|
$this->cellStack = $stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable Calculation engine logging.
|
||||||
|
*
|
||||||
|
* @param bool $pValue
|
||||||
|
*/
|
||||||
|
public function setWriteDebugLog($pValue)
|
||||||
|
{
|
||||||
|
$this->writeDebugLog = $pValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether calculation engine logging is enabled or disabled.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getWriteDebugLog()
|
||||||
|
{
|
||||||
|
return $this->writeDebugLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable echoing of debug log information.
|
||||||
|
*
|
||||||
|
* @param bool $pValue
|
||||||
|
*/
|
||||||
|
public function setEchoDebugLog($pValue)
|
||||||
|
{
|
||||||
|
$this->echoDebugLog = $pValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether echoing of debug log information is enabled or disabled.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getEchoDebugLog()
|
||||||
|
{
|
||||||
|
return $this->echoDebugLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write an entry to the calculation engine debug log.
|
||||||
|
*/
|
||||||
|
public function writeDebugLog(...$args)
|
||||||
|
{
|
||||||
|
// Only write the debug log if logging is enabled
|
||||||
|
if ($this->writeDebugLog) {
|
||||||
|
$message = implode($args);
|
||||||
|
$cellReference = implode(' -> ', $this->cellStack->showStack());
|
||||||
|
if ($this->echoDebugLog) {
|
||||||
|
echo $cellReference,
|
||||||
|
($this->cellStack->count() > 0 ? ' => ' : ''),
|
||||||
|
$message,
|
||||||
|
PHP_EOL;
|
||||||
|
}
|
||||||
|
$this->debugLog[] = $cellReference .
|
||||||
|
($this->cellStack->count() > 0 ? ' => ' : '') .
|
||||||
|
$message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the calculation engine debug log.
|
||||||
|
*/
|
||||||
|
public function clearLog()
|
||||||
|
{
|
||||||
|
$this->debugLog = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the calculation engine debug log.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getLog()
|
||||||
|
{
|
||||||
|
return $this->debugLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
2807
PhpOffice/PhpSpreadsheet/Calculation/Engineering.php
Normal file
2807
PhpOffice/PhpSpreadsheet/Calculation/Engineering.php
Normal file
File diff suppressed because it is too large
Load Diff
26
PhpOffice/PhpSpreadsheet/Calculation/Exception.php
Normal file
26
PhpOffice/PhpSpreadsheet/Calculation/Exception.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
|
|
||||||
|
class Exception extends PhpSpreadsheetException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Error handler callback.
|
||||||
|
*
|
||||||
|
* @param mixed $code
|
||||||
|
* @param mixed $string
|
||||||
|
* @param mixed $file
|
||||||
|
* @param mixed $line
|
||||||
|
* @param mixed $context
|
||||||
|
*/
|
||||||
|
public static function errorHandlerCallback($code, $string, $file, $line, $context)
|
||||||
|
{
|
||||||
|
$e = new self($string, $code);
|
||||||
|
$e->line = $line;
|
||||||
|
$e->file = $file;
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php
Normal file
22
PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
class ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register errorhandler.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
set_error_handler([Exception::class, 'errorHandlerCallback'], E_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister errorhandler.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
2405
PhpOffice/PhpSpreadsheet/Calculation/Financial.php
Normal file
2405
PhpOffice/PhpSpreadsheet/Calculation/Financial.php
Normal file
File diff suppressed because it is too large
Load Diff
623
PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php
Normal file
623
PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php
Normal file
|
|
@ -0,0 +1,623 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PARTLY BASED ON:
|
||||||
|
* Copyright (c) 2007 E. W. Bachtal, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
* portions of the Software.
|
||||||
|
*
|
||||||
|
* The software is provided "as is", without warranty of any kind, express or implied, including but not
|
||||||
|
* limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
|
||||||
|
* no event shall the authors or copyright holders be liable for any claim, damages or other liability,
|
||||||
|
* whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
|
||||||
|
* software or the use or other dealings in the software.
|
||||||
|
*
|
||||||
|
* https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
|
||||||
|
* https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
|
||||||
|
*/
|
||||||
|
class FormulaParser
|
||||||
|
{
|
||||||
|
// Character constants
|
||||||
|
const QUOTE_DOUBLE = '"';
|
||||||
|
const QUOTE_SINGLE = '\'';
|
||||||
|
const BRACKET_CLOSE = ']';
|
||||||
|
const BRACKET_OPEN = '[';
|
||||||
|
const BRACE_OPEN = '{';
|
||||||
|
const BRACE_CLOSE = '}';
|
||||||
|
const PAREN_OPEN = '(';
|
||||||
|
const PAREN_CLOSE = ')';
|
||||||
|
const SEMICOLON = ';';
|
||||||
|
const WHITESPACE = ' ';
|
||||||
|
const COMMA = ',';
|
||||||
|
const ERROR_START = '#';
|
||||||
|
|
||||||
|
const OPERATORS_SN = '+-';
|
||||||
|
const OPERATORS_INFIX = '+-*/^&=><';
|
||||||
|
const OPERATORS_POSTFIX = '%';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formula.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $formula;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens.
|
||||||
|
*
|
||||||
|
* @var FormulaToken[]
|
||||||
|
*/
|
||||||
|
private $tokens = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new FormulaParser.
|
||||||
|
*
|
||||||
|
* @param string $pFormula Formula to parse
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __construct($pFormula = '')
|
||||||
|
{
|
||||||
|
// Check parameters
|
||||||
|
if ($pFormula === null) {
|
||||||
|
throw new Exception('Invalid parameter passed: formula');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise values
|
||||||
|
$this->formula = trim($pFormula);
|
||||||
|
// Parse!
|
||||||
|
$this->parseToTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Formula.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFormula()
|
||||||
|
{
|
||||||
|
return $this->formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Token.
|
||||||
|
*
|
||||||
|
* @param int $pId Token id
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getToken($pId = 0)
|
||||||
|
{
|
||||||
|
if (isset($this->tokens[$pId])) {
|
||||||
|
return $this->tokens[$pId];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Token with id $pId does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Token count.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getTokenCount()
|
||||||
|
{
|
||||||
|
return count($this->tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Tokens.
|
||||||
|
*
|
||||||
|
* @return FormulaToken[]
|
||||||
|
*/
|
||||||
|
public function getTokens()
|
||||||
|
{
|
||||||
|
return $this->tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to tokens.
|
||||||
|
*/
|
||||||
|
private function parseToTokens()
|
||||||
|
{
|
||||||
|
// No attempt is made to verify formulas; assumes formulas are derived from Excel, where
|
||||||
|
// they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions.
|
||||||
|
|
||||||
|
// Check if the formula has a valid starting =
|
||||||
|
$formulaLength = strlen($this->formula);
|
||||||
|
if ($formulaLength < 2 || $this->formula[0] != '=') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper variables
|
||||||
|
$tokens1 = $tokens2 = $stack = [];
|
||||||
|
$inString = $inPath = $inRange = $inError = false;
|
||||||
|
$token = $previousToken = $nextToken = null;
|
||||||
|
|
||||||
|
$index = 1;
|
||||||
|
$value = '';
|
||||||
|
|
||||||
|
$ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A'];
|
||||||
|
$COMPARATORS_MULTI = ['>=', '<=', '<>'];
|
||||||
|
|
||||||
|
while ($index < $formulaLength) {
|
||||||
|
// state-dependent character evaluation (order is important)
|
||||||
|
|
||||||
|
// double-quoted strings
|
||||||
|
// embeds are doubled
|
||||||
|
// end marks token
|
||||||
|
if ($inString) {
|
||||||
|
if ($this->formula[$index] == self::QUOTE_DOUBLE) {
|
||||||
|
if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) {
|
||||||
|
$value .= self::QUOTE_DOUBLE;
|
||||||
|
++$index;
|
||||||
|
} else {
|
||||||
|
$inString = false;
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
}
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// single-quoted strings (links)
|
||||||
|
// embeds are double
|
||||||
|
// end does not mark a token
|
||||||
|
if ($inPath) {
|
||||||
|
if ($this->formula[$index] == self::QUOTE_SINGLE) {
|
||||||
|
if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) {
|
||||||
|
$value .= self::QUOTE_SINGLE;
|
||||||
|
++$index;
|
||||||
|
} else {
|
||||||
|
$inPath = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
}
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bracked strings (R1C1 range index or linked workbook name)
|
||||||
|
// no embeds (changed to "()" by Excel)
|
||||||
|
// end does not mark a token
|
||||||
|
if ($inRange) {
|
||||||
|
if ($this->formula[$index] == self::BRACKET_CLOSE) {
|
||||||
|
$inRange = false;
|
||||||
|
}
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error values
|
||||||
|
// end marks a token, determined from absolute list of values
|
||||||
|
if ($inError) {
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
++$index;
|
||||||
|
if (in_array($value, $ERRORS)) {
|
||||||
|
$inError = false;
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scientific notation check
|
||||||
|
if (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) {
|
||||||
|
if (strlen($value) > 1) {
|
||||||
|
if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) {
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// independent character evaluation (order not important)
|
||||||
|
|
||||||
|
// establish state-dependent character evaluations
|
||||||
|
if ($this->formula[$index] == self::QUOTE_DOUBLE) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
// unexpected
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$inString = true;
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->formula[$index] == self::QUOTE_SINGLE) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
// unexpected
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$inPath = true;
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->formula[$index] == self::BRACKET_OPEN) {
|
||||||
|
$inRange = true;
|
||||||
|
$value .= self::BRACKET_OPEN;
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->formula[$index] == self::ERROR_START) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
// unexpected
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$inError = true;
|
||||||
|
$value .= self::ERROR_START;
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark start and end of arrays and array rows
|
||||||
|
if ($this->formula[$index] == self::BRACE_OPEN) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
// unexpected
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
$stack[] = clone $tmp;
|
||||||
|
|
||||||
|
$tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
$stack[] = clone $tmp;
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->formula[$index] == self::SEMICOLON) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = array_pop($stack);
|
||||||
|
$tmp->setValue('');
|
||||||
|
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
|
||||||
|
$tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
|
||||||
|
$tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
$stack[] = clone $tmp;
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->formula[$index] == self::BRACE_CLOSE) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = array_pop($stack);
|
||||||
|
$tmp->setValue('');
|
||||||
|
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
|
||||||
|
$tmp = array_pop($stack);
|
||||||
|
$tmp->setValue('');
|
||||||
|
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim white-space
|
||||||
|
if ($this->formula[$index] == self::WHITESPACE) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE);
|
||||||
|
++$index;
|
||||||
|
while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) {
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// multi-character comparators
|
||||||
|
if (($index + 2) <= $formulaLength) {
|
||||||
|
if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL);
|
||||||
|
$index += 2;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// standard infix operators
|
||||||
|
if (strpos(self::OPERATORS_INFIX, $this->formula[$index]) !== false) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX);
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// standard postfix operators (only one)
|
||||||
|
if (strpos(self::OPERATORS_POSTFIX, $this->formula[$index]) !== false) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
$tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX);
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start subexpression or function
|
||||||
|
if ($this->formula[$index] == self::PAREN_OPEN) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
$stack[] = clone $tmp;
|
||||||
|
$value = '';
|
||||||
|
} else {
|
||||||
|
$tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
$stack[] = clone $tmp;
|
||||||
|
}
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// function, subexpression, or array parameters, or operand unions
|
||||||
|
if ($this->formula[$index] == self::COMMA) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = array_pop($stack);
|
||||||
|
$tmp->setValue('');
|
||||||
|
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
|
||||||
|
$stack[] = $tmp;
|
||||||
|
|
||||||
|
if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
|
||||||
|
$tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION);
|
||||||
|
} else {
|
||||||
|
$tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
|
||||||
|
}
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop subexpression
|
||||||
|
if ($this->formula[$index] == self::PAREN_CLOSE) {
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp = array_pop($stack);
|
||||||
|
$tmp->setValue('');
|
||||||
|
$tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
|
||||||
|
$tokens1[] = $tmp;
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// token accumulation
|
||||||
|
$value .= $this->formula[$index];
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump remaining accumulation
|
||||||
|
if (strlen($value) > 0) {
|
||||||
|
$tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections
|
||||||
|
$tokenCount = count($tokens1);
|
||||||
|
for ($i = 0; $i < $tokenCount; ++$i) {
|
||||||
|
$token = $tokens1[$i];
|
||||||
|
if (isset($tokens1[$i - 1])) {
|
||||||
|
$previousToken = $tokens1[$i - 1];
|
||||||
|
} else {
|
||||||
|
$previousToken = null;
|
||||||
|
}
|
||||||
|
if (isset($tokens1[$i + 1])) {
|
||||||
|
$nextToken = $tokens1[$i + 1];
|
||||||
|
} else {
|
||||||
|
$nextToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) {
|
||||||
|
$tokens2[] = $token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($previousToken === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(
|
||||||
|
(($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
(($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
|
||||||
|
)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nextToken === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(
|
||||||
|
(($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
|
||||||
|
(($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
|
||||||
|
($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
|
||||||
|
)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators
|
||||||
|
// to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names
|
||||||
|
$this->tokens = [];
|
||||||
|
|
||||||
|
$tokenCount = count($tokens2);
|
||||||
|
for ($i = 0; $i < $tokenCount; ++$i) {
|
||||||
|
$token = $tokens2[$i];
|
||||||
|
if (isset($tokens2[$i - 1])) {
|
||||||
|
$previousToken = $tokens2[$i - 1];
|
||||||
|
} else {
|
||||||
|
$previousToken = null;
|
||||||
|
}
|
||||||
|
if (isset($tokens2[$i + 1])) {
|
||||||
|
$nextToken = $tokens2[$i + 1];
|
||||||
|
} else {
|
||||||
|
$nextToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') {
|
||||||
|
if ($i == 0) {
|
||||||
|
$token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
|
||||||
|
} elseif ((($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
|
||||||
|
($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
(($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
|
||||||
|
($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
|
||||||
|
($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)) {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
|
||||||
|
} else {
|
||||||
|
$token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens[] = $token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') {
|
||||||
|
if ($i == 0) {
|
||||||
|
continue;
|
||||||
|
} elseif ((($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
|
||||||
|
($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
(($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
|
||||||
|
($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
|
||||||
|
($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
|
||||||
|
($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)) {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens[] = $token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX &&
|
||||||
|
$token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING) {
|
||||||
|
if (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
|
||||||
|
} elseif ($token->getValue() == '&') {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION);
|
||||||
|
} else {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens[] = $token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND &&
|
||||||
|
$token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING) {
|
||||||
|
if (!is_numeric($token->getValue())) {
|
||||||
|
if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
|
||||||
|
} else {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens[] = $token;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
|
||||||
|
if (strlen($token->getValue()) > 0) {
|
||||||
|
if (substr($token->getValue(), 0, 1) == '@') {
|
||||||
|
$token->setValue(substr($token->getValue(), 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens[] = $token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php
Normal file
150
PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PARTLY BASED ON:
|
||||||
|
* Copyright (c) 2007 E. W. Bachtal, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
* portions of the Software.
|
||||||
|
*
|
||||||
|
* The software is provided "as is", without warranty of any kind, express or implied, including but not
|
||||||
|
* limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
|
||||||
|
* no event shall the authors or copyright holders be liable for any claim, damages or other liability,
|
||||||
|
* whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
|
||||||
|
* software or the use or other dealings in the software.
|
||||||
|
*
|
||||||
|
* https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
|
||||||
|
* https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
|
||||||
|
*/
|
||||||
|
class FormulaToken
|
||||||
|
{
|
||||||
|
// Token types
|
||||||
|
const TOKEN_TYPE_NOOP = 'Noop';
|
||||||
|
const TOKEN_TYPE_OPERAND = 'Operand';
|
||||||
|
const TOKEN_TYPE_FUNCTION = 'Function';
|
||||||
|
const TOKEN_TYPE_SUBEXPRESSION = 'Subexpression';
|
||||||
|
const TOKEN_TYPE_ARGUMENT = 'Argument';
|
||||||
|
const TOKEN_TYPE_OPERATORPREFIX = 'OperatorPrefix';
|
||||||
|
const TOKEN_TYPE_OPERATORINFIX = 'OperatorInfix';
|
||||||
|
const TOKEN_TYPE_OPERATORPOSTFIX = 'OperatorPostfix';
|
||||||
|
const TOKEN_TYPE_WHITESPACE = 'Whitespace';
|
||||||
|
const TOKEN_TYPE_UNKNOWN = 'Unknown';
|
||||||
|
|
||||||
|
// Token subtypes
|
||||||
|
const TOKEN_SUBTYPE_NOTHING = 'Nothing';
|
||||||
|
const TOKEN_SUBTYPE_START = 'Start';
|
||||||
|
const TOKEN_SUBTYPE_STOP = 'Stop';
|
||||||
|
const TOKEN_SUBTYPE_TEXT = 'Text';
|
||||||
|
const TOKEN_SUBTYPE_NUMBER = 'Number';
|
||||||
|
const TOKEN_SUBTYPE_LOGICAL = 'Logical';
|
||||||
|
const TOKEN_SUBTYPE_ERROR = 'Error';
|
||||||
|
const TOKEN_SUBTYPE_RANGE = 'Range';
|
||||||
|
const TOKEN_SUBTYPE_MATH = 'Math';
|
||||||
|
const TOKEN_SUBTYPE_CONCATENATION = 'Concatenation';
|
||||||
|
const TOKEN_SUBTYPE_INTERSECTION = 'Intersection';
|
||||||
|
const TOKEN_SUBTYPE_UNION = 'Union';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token Type (represented by TOKEN_TYPE_*).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $tokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token SubType (represented by TOKEN_SUBTYPE_*).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $tokenSubType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new FormulaToken.
|
||||||
|
*
|
||||||
|
* @param string $pValue
|
||||||
|
* @param string $pTokenType Token type (represented by TOKEN_TYPE_*)
|
||||||
|
* @param string $pTokenSubType Token Subtype (represented by TOKEN_SUBTYPE_*)
|
||||||
|
*/
|
||||||
|
public function __construct($pValue, $pTokenType = self::TOKEN_TYPE_UNKNOWN, $pTokenSubType = self::TOKEN_SUBTYPE_NOTHING)
|
||||||
|
{
|
||||||
|
// Initialise values
|
||||||
|
$this->value = $pValue;
|
||||||
|
$this->tokenType = $pTokenType;
|
||||||
|
$this->tokenSubType = $pTokenSubType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Value.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Value.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function setValue($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Token Type (represented by TOKEN_TYPE_*).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTokenType()
|
||||||
|
{
|
||||||
|
return $this->tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Token Type (represented by TOKEN_TYPE_*).
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function setTokenType($value)
|
||||||
|
{
|
||||||
|
$this->tokenType = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Token SubType (represented by TOKEN_SUBTYPE_*).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTokenSubType()
|
||||||
|
{
|
||||||
|
return $this->tokenSubType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Token SubType (represented by TOKEN_SUBTYPE_*).
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function setTokenSubType($value)
|
||||||
|
{
|
||||||
|
$this->tokenSubType = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
678
PhpOffice/PhpSpreadsheet/Calculation/Functions.php
Normal file
678
PhpOffice/PhpSpreadsheet/Calculation/Functions.php
Normal file
|
|
@ -0,0 +1,678 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||||
|
|
||||||
|
class Functions
|
||||||
|
{
|
||||||
|
const PRECISION = 8.88E-016;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2 / PI.
|
||||||
|
*/
|
||||||
|
const M_2DIVPI = 0.63661977236758134307553505349006;
|
||||||
|
|
||||||
|
/** constants */
|
||||||
|
const COMPATIBILITY_EXCEL = 'Excel';
|
||||||
|
const COMPATIBILITY_GNUMERIC = 'Gnumeric';
|
||||||
|
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
|
||||||
|
|
||||||
|
const RETURNDATE_PHP_NUMERIC = 'P';
|
||||||
|
const RETURNDATE_UNIX_TIMESTAMP = 'P';
|
||||||
|
const RETURNDATE_PHP_OBJECT = 'O';
|
||||||
|
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
|
||||||
|
const RETURNDATE_EXCEL = 'E';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility mode to use for error checking and responses.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $compatibilityMode = self::COMPATIBILITY_EXCEL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Type to use when returning date values.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $returnDateType = self::RETURNDATE_EXCEL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of error codes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $errorCodes = [
|
||||||
|
'null' => '#NULL!',
|
||||||
|
'divisionbyzero' => '#DIV/0!',
|
||||||
|
'value' => '#VALUE!',
|
||||||
|
'reference' => '#REF!',
|
||||||
|
'name' => '#NAME?',
|
||||||
|
'num' => '#NUM!',
|
||||||
|
'na' => '#N/A',
|
||||||
|
'gettingdata' => '#GETTING_DATA',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Compatibility Mode.
|
||||||
|
*
|
||||||
|
* @category Function Configuration
|
||||||
|
*
|
||||||
|
* @param string $compatibilityMode Compatibility Mode
|
||||||
|
* Permitted values are:
|
||||||
|
* Functions::COMPATIBILITY_EXCEL 'Excel'
|
||||||
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
|
||||||
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
|
||||||
|
*
|
||||||
|
* @return bool (Success or Failure)
|
||||||
|
*/
|
||||||
|
public static function setCompatibilityMode($compatibilityMode)
|
||||||
|
{
|
||||||
|
if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
|
||||||
|
($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
|
||||||
|
($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)
|
||||||
|
) {
|
||||||
|
self::$compatibilityMode = $compatibilityMode;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current Compatibility Mode.
|
||||||
|
*
|
||||||
|
* @category Function Configuration
|
||||||
|
*
|
||||||
|
* @return string Compatibility Mode
|
||||||
|
* Possible Return values are:
|
||||||
|
* Functions::COMPATIBILITY_EXCEL 'Excel'
|
||||||
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
|
||||||
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
|
||||||
|
*/
|
||||||
|
public static function getCompatibilityMode()
|
||||||
|
{
|
||||||
|
return self::$compatibilityMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
|
||||||
|
*
|
||||||
|
* @category Function Configuration
|
||||||
|
*
|
||||||
|
* @param string $returnDateType Return Date Format
|
||||||
|
* Permitted values are:
|
||||||
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
|
||||||
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
|
||||||
|
* Functions::RETURNDATE_EXCEL 'E'
|
||||||
|
*
|
||||||
|
* @return bool Success or failure
|
||||||
|
*/
|
||||||
|
public static function setReturnDateType($returnDateType)
|
||||||
|
{
|
||||||
|
if (($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) ||
|
||||||
|
($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) ||
|
||||||
|
($returnDateType == self::RETURNDATE_EXCEL)
|
||||||
|
) {
|
||||||
|
self::$returnDateType = $returnDateType;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
|
||||||
|
*
|
||||||
|
* @category Function Configuration
|
||||||
|
*
|
||||||
|
* @return string Return Date Format
|
||||||
|
* Possible Return values are:
|
||||||
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
|
||||||
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
|
||||||
|
* Functions::RETURNDATE_EXCEL 'E'
|
||||||
|
*/
|
||||||
|
public static function getReturnDateType()
|
||||||
|
{
|
||||||
|
return self::$returnDateType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DUMMY.
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #Not Yet Implemented
|
||||||
|
*/
|
||||||
|
public static function DUMMY()
|
||||||
|
{
|
||||||
|
return '#Not Yet Implemented';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DIV0.
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #Not Yet Implemented
|
||||||
|
*/
|
||||||
|
public static function DIV0()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['divisionbyzero'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NA.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =NA()
|
||||||
|
*
|
||||||
|
* Returns the error value #N/A
|
||||||
|
* #N/A is the error value that means "no value is available."
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @return string #N/A!
|
||||||
|
*/
|
||||||
|
public static function NA()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['na'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NaN.
|
||||||
|
*
|
||||||
|
* Returns the error value #NUM!
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #NUM!
|
||||||
|
*/
|
||||||
|
public static function NAN()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['num'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NAME.
|
||||||
|
*
|
||||||
|
* Returns the error value #NAME?
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #NAME?
|
||||||
|
*/
|
||||||
|
public static function NAME()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REF.
|
||||||
|
*
|
||||||
|
* Returns the error value #REF!
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #REF!
|
||||||
|
*/
|
||||||
|
public static function REF()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['reference'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NULL.
|
||||||
|
*
|
||||||
|
* Returns the error value #NULL!
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #NULL!
|
||||||
|
*/
|
||||||
|
public static function null()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['null'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VALUE.
|
||||||
|
*
|
||||||
|
* Returns the error value #VALUE!
|
||||||
|
*
|
||||||
|
* @category Error Returns
|
||||||
|
*
|
||||||
|
* @return string #VALUE!
|
||||||
|
*/
|
||||||
|
public static function VALUE()
|
||||||
|
{
|
||||||
|
return self::$errorCodes['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isMatrixValue($idx)
|
||||||
|
{
|
||||||
|
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isValue($idx)
|
||||||
|
{
|
||||||
|
return substr_count($idx, '.') == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isCellValue($idx)
|
||||||
|
{
|
||||||
|
return substr_count($idx, '.') > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ifCondition($condition)
|
||||||
|
{
|
||||||
|
$condition = self::flattenSingleValue($condition);
|
||||||
|
if (!isset($condition[0]) && !is_numeric($condition)) {
|
||||||
|
$condition = '=""';
|
||||||
|
}
|
||||||
|
if (!in_array($condition[0], ['>', '<', '='])) {
|
||||||
|
if (!is_numeric($condition)) {
|
||||||
|
$condition = Calculation::wrapResult(strtoupper($condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '=' . $condition;
|
||||||
|
}
|
||||||
|
preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
|
||||||
|
[, $operator, $operand] = $matches;
|
||||||
|
|
||||||
|
if (is_numeric(trim($operand, '"'))) {
|
||||||
|
$operand = trim($operand, '"');
|
||||||
|
} elseif (!is_numeric($operand)) {
|
||||||
|
$operand = str_replace('"', '""', $operand);
|
||||||
|
$operand = Calculation::wrapResult(strtoupper($operand));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $operator . $operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ERROR_TYPE.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function errorType($value = '')
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach (self::$errorCodes as $errorCode) {
|
||||||
|
if ($value === $errorCode) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
++$i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_BLANK.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isBlank($value = null)
|
||||||
|
{
|
||||||
|
if ($value !== null) {
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_ERR.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isErr($value = '')
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
return self::isError($value) && (!self::isNa(($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_ERROR.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isError($value = '')
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (!is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($value, self::$errorCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_NA.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isNa($value = '')
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
return $value === self::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_EVEN.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public static function isEven($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
return self::NAME();
|
||||||
|
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
|
||||||
|
return self::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_ODD.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public static function isOdd($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
return self::NAME();
|
||||||
|
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
|
||||||
|
return self::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return abs($value) % 2 == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_NUMBER.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isNumber($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_numeric($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_LOGICAL.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isLogical($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
return is_bool($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_TEXT.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isText($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
return is_string($value) && !self::isError($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IS_NONTEXT.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isNonText($value = null)
|
||||||
|
{
|
||||||
|
return !self::isText($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* N.
|
||||||
|
*
|
||||||
|
* Returns a value converted to a number
|
||||||
|
*
|
||||||
|
* @param null|mixed $value The value you want converted
|
||||||
|
*
|
||||||
|
* @return number N converts values listed in the following table
|
||||||
|
* If value is or refers to N returns
|
||||||
|
* A number That number
|
||||||
|
* A date The serial number of that date
|
||||||
|
* TRUE 1
|
||||||
|
* FALSE 0
|
||||||
|
* An error value The error value
|
||||||
|
* Anything else 0
|
||||||
|
*/
|
||||||
|
public static function n($value = null)
|
||||||
|
{
|
||||||
|
while (is_array($value)) {
|
||||||
|
$value = array_shift($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gettype($value)) {
|
||||||
|
case 'double':
|
||||||
|
case 'float':
|
||||||
|
case 'integer':
|
||||||
|
return $value;
|
||||||
|
case 'boolean':
|
||||||
|
return (int) $value;
|
||||||
|
case 'string':
|
||||||
|
// Errors
|
||||||
|
if ((strlen($value) > 0) && ($value[0] == '#')) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TYPE.
|
||||||
|
*
|
||||||
|
* Returns a number that identifies the type of a value
|
||||||
|
*
|
||||||
|
* @param null|mixed $value The value you want tested
|
||||||
|
*
|
||||||
|
* @return number N converts values listed in the following table
|
||||||
|
* If value is or refers to N returns
|
||||||
|
* A number 1
|
||||||
|
* Text 2
|
||||||
|
* Logical Value 4
|
||||||
|
* An error value 16
|
||||||
|
* Array or Matrix 64
|
||||||
|
*/
|
||||||
|
public static function TYPE($value = null)
|
||||||
|
{
|
||||||
|
$value = self::flattenArrayIndexed($value);
|
||||||
|
if (is_array($value) && (count($value) > 1)) {
|
||||||
|
end($value);
|
||||||
|
$a = key($value);
|
||||||
|
// Range of cells is an error
|
||||||
|
if (self::isCellValue($a)) {
|
||||||
|
return 16;
|
||||||
|
// Test for Matrix
|
||||||
|
} elseif (self::isMatrixValue($a)) {
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
} elseif (empty($value)) {
|
||||||
|
// Empty Cell
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$value = self::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (($value === null) || (is_float($value)) || (is_int($value))) {
|
||||||
|
return 1;
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
return 4;
|
||||||
|
} elseif (is_array($value)) {
|
||||||
|
return 64;
|
||||||
|
} elseif (is_string($value)) {
|
||||||
|
// Errors
|
||||||
|
if ((strlen($value) > 0) && ($value[0] == '#')) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a multi-dimensional array to a simple 1-dimensional array.
|
||||||
|
*
|
||||||
|
* @param array $array Array to be flattened
|
||||||
|
*
|
||||||
|
* @return array Flattened array
|
||||||
|
*/
|
||||||
|
public static function flattenArray($array)
|
||||||
|
{
|
||||||
|
if (!is_array($array)) {
|
||||||
|
return (array) $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arrayValues = [];
|
||||||
|
foreach ($array as $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach ($value as $val) {
|
||||||
|
if (is_array($val)) {
|
||||||
|
foreach ($val as $v) {
|
||||||
|
$arrayValues[] = $v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$arrayValues[] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$arrayValues[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arrayValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
|
||||||
|
*
|
||||||
|
* @param array $array Array to be flattened
|
||||||
|
*
|
||||||
|
* @return array Flattened array
|
||||||
|
*/
|
||||||
|
public static function flattenArrayIndexed($array)
|
||||||
|
{
|
||||||
|
if (!is_array($array)) {
|
||||||
|
return (array) $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arrayValues = [];
|
||||||
|
foreach ($array as $k1 => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach ($value as $k2 => $val) {
|
||||||
|
if (is_array($val)) {
|
||||||
|
foreach ($val as $k3 => $v) {
|
||||||
|
$arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$arrayValues[$k1 . '.' . $k2] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$arrayValues[$k1] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arrayValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array to a single scalar value by extracting the first element.
|
||||||
|
*
|
||||||
|
* @param mixed $value Array or scalar value
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function flattenSingleValue($value = '')
|
||||||
|
{
|
||||||
|
while (is_array($value)) {
|
||||||
|
$value = array_pop($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISFORMULA.
|
||||||
|
*
|
||||||
|
* @param mixed $cellReference The cell to check
|
||||||
|
* @param Cell $pCell The current cell (containing this formula)
|
||||||
|
*
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public static function isFormula($cellReference = '', Cell $pCell = null)
|
||||||
|
{
|
||||||
|
if ($pCell === null) {
|
||||||
|
return self::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
|
||||||
|
|
||||||
|
$cellReference = $matches[6] . $matches[7];
|
||||||
|
$worksheetName = trim($matches[3], "'");
|
||||||
|
|
||||||
|
$worksheet = (!empty($worksheetName))
|
||||||
|
? $pCell->getWorksheet()->getParent()->getSheetByName($worksheetName)
|
||||||
|
: $pCell->getWorksheet();
|
||||||
|
|
||||||
|
return $worksheet->getCell($cellReference)->isFormula();
|
||||||
|
}
|
||||||
|
}
|
||||||
371
PhpOffice/PhpSpreadsheet/Calculation/Logical.php
Normal file
371
PhpOffice/PhpSpreadsheet/Calculation/Logical.php
Normal file
|
|
@ -0,0 +1,371 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
class Logical
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* TRUE.
|
||||||
|
*
|
||||||
|
* Returns the boolean TRUE.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =TRUE()
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @return bool True
|
||||||
|
*/
|
||||||
|
public static function true()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FALSE.
|
||||||
|
*
|
||||||
|
* Returns the boolean FALSE.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =FALSE()
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @return bool False
|
||||||
|
*/
|
||||||
|
public static function false()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function countTrueValues(array $args)
|
||||||
|
{
|
||||||
|
$returnValue = 0;
|
||||||
|
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
// Is it a boolean value?
|
||||||
|
if (is_bool($arg)) {
|
||||||
|
$returnValue += $arg;
|
||||||
|
} elseif ((is_numeric($arg)) && (!is_string($arg))) {
|
||||||
|
$returnValue += ((int) $arg != 0);
|
||||||
|
} elseif (is_string($arg)) {
|
||||||
|
$arg = strtoupper($arg);
|
||||||
|
if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) {
|
||||||
|
$arg = true;
|
||||||
|
} elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) {
|
||||||
|
$arg = false;
|
||||||
|
} else {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$returnValue += ($arg != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOGICAL_AND.
|
||||||
|
*
|
||||||
|
* Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =AND(logical1[,logical2[, ...]])
|
||||||
|
*
|
||||||
|
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
|
||||||
|
* or references that contain logical values.
|
||||||
|
*
|
||||||
|
* Boolean arguments are treated as True or False as appropriate
|
||||||
|
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
|
||||||
|
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
|
||||||
|
* the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed ...$args Data values
|
||||||
|
*
|
||||||
|
* @return bool|string the logical AND of the arguments
|
||||||
|
*/
|
||||||
|
public static function logicalAnd(...$args)
|
||||||
|
{
|
||||||
|
$args = Functions::flattenArray($args);
|
||||||
|
|
||||||
|
if (count($args) == 0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_filter($args, function ($value) {
|
||||||
|
return $value !== null || (is_string($value) && trim($value) == '');
|
||||||
|
});
|
||||||
|
$argCount = count($args);
|
||||||
|
|
||||||
|
$returnValue = self::countTrueValues($args);
|
||||||
|
if (is_string($returnValue)) {
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($returnValue > 0) && ($returnValue == $argCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOGICAL_OR.
|
||||||
|
*
|
||||||
|
* Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =OR(logical1[,logical2[, ...]])
|
||||||
|
*
|
||||||
|
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
|
||||||
|
* or references that contain logical values.
|
||||||
|
*
|
||||||
|
* Boolean arguments are treated as True or False as appropriate
|
||||||
|
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
|
||||||
|
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
|
||||||
|
* the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $args Data values
|
||||||
|
*
|
||||||
|
* @return bool|string the logical OR of the arguments
|
||||||
|
*/
|
||||||
|
public static function logicalOr(...$args)
|
||||||
|
{
|
||||||
|
$args = Functions::flattenArray($args);
|
||||||
|
|
||||||
|
if (count($args) == 0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_filter($args, function ($value) {
|
||||||
|
return $value !== null || (is_string($value) && trim($value) == '');
|
||||||
|
});
|
||||||
|
|
||||||
|
$returnValue = self::countTrueValues($args);
|
||||||
|
if (is_string($returnValue)) {
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOGICAL_XOR.
|
||||||
|
*
|
||||||
|
* Returns the Exclusive Or logical operation for one or more supplied conditions.
|
||||||
|
* i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, and FALSE otherwise.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =XOR(logical1[,logical2[, ...]])
|
||||||
|
*
|
||||||
|
* The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
|
||||||
|
* or references that contain logical values.
|
||||||
|
*
|
||||||
|
* Boolean arguments are treated as True or False as appropriate
|
||||||
|
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
|
||||||
|
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
|
||||||
|
* the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $args Data values
|
||||||
|
*
|
||||||
|
* @return bool|string the logical XOR of the arguments
|
||||||
|
*/
|
||||||
|
public static function logicalXor(...$args)
|
||||||
|
{
|
||||||
|
$args = Functions::flattenArray($args);
|
||||||
|
|
||||||
|
if (count($args) == 0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_filter($args, function ($value) {
|
||||||
|
return $value !== null || (is_string($value) && trim($value) == '');
|
||||||
|
});
|
||||||
|
|
||||||
|
$returnValue = self::countTrueValues($args);
|
||||||
|
if (is_string($returnValue)) {
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue % 2 == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT.
|
||||||
|
*
|
||||||
|
* Returns the boolean inverse of the argument.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =NOT(logical)
|
||||||
|
*
|
||||||
|
* The argument must evaluate to a logical value such as TRUE or FALSE
|
||||||
|
*
|
||||||
|
* Boolean arguments are treated as True or False as appropriate
|
||||||
|
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
|
||||||
|
* If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
|
||||||
|
* the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
|
||||||
|
*
|
||||||
|
* @return bool|string the boolean inverse of the argument
|
||||||
|
*/
|
||||||
|
public static function NOT($logical = false)
|
||||||
|
{
|
||||||
|
$logical = Functions::flattenSingleValue($logical);
|
||||||
|
|
||||||
|
if (is_string($logical)) {
|
||||||
|
$logical = strtoupper($logical);
|
||||||
|
if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) {
|
||||||
|
return false;
|
||||||
|
} elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return !$logical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STATEMENT_IF.
|
||||||
|
*
|
||||||
|
* Returns one value if a condition you specify evaluates to TRUE and another value if it evaluates to FALSE.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =IF(condition[,returnIfTrue[,returnIfFalse]])
|
||||||
|
*
|
||||||
|
* Condition is any value or expression that can be evaluated to TRUE or FALSE.
|
||||||
|
* For example, A10=100 is a logical expression; if the value in cell A10 is equal to 100,
|
||||||
|
* the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE.
|
||||||
|
* This argument can use any comparison calculation operator.
|
||||||
|
* ReturnIfTrue is the value that is returned if condition evaluates to TRUE.
|
||||||
|
* For example, if this argument is the text string "Within budget" and the condition argument evaluates to TRUE,
|
||||||
|
* then the IF function returns the text "Within budget"
|
||||||
|
* If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). To display the word TRUE, use
|
||||||
|
* the logical value TRUE for this argument.
|
||||||
|
* ReturnIfTrue can be another formula.
|
||||||
|
* ReturnIfFalse is the value that is returned if condition evaluates to FALSE.
|
||||||
|
* For example, if this argument is the text string "Over budget" and the condition argument evaluates to FALSE,
|
||||||
|
* then the IF function returns the text "Over budget".
|
||||||
|
* If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned.
|
||||||
|
* If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned.
|
||||||
|
* ReturnIfFalse can be another formula.
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $condition Condition to evaluate
|
||||||
|
* @param mixed $returnIfTrue Value to return when condition is true
|
||||||
|
* @param mixed $returnIfFalse Optional value to return when condition is false
|
||||||
|
*
|
||||||
|
* @return mixed The value of returnIfTrue or returnIfFalse determined by condition
|
||||||
|
*/
|
||||||
|
public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false)
|
||||||
|
{
|
||||||
|
$condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition);
|
||||||
|
$returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue);
|
||||||
|
$returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse);
|
||||||
|
|
||||||
|
return ($condition) ? $returnIfTrue : $returnIfFalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STATEMENT_SWITCH.
|
||||||
|
*
|
||||||
|
* Returns corresponding with first match (any data type such as a string, numeric, date, etc).
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =SWITCH (expression, value1, result1, value2, result2, ... value_n, result_n [, default])
|
||||||
|
*
|
||||||
|
* Expression
|
||||||
|
* The expression to compare to a list of values.
|
||||||
|
* value1, value2, ... value_n
|
||||||
|
* A list of values that are compared to expression. The SWITCH function is looking for the first value that matches the expression.
|
||||||
|
* result1, result2, ... result_n
|
||||||
|
* A list of results. The SWITCH function returns the corresponding result when a value matches expression.
|
||||||
|
* default
|
||||||
|
* Optional. It is the default to return if expression does not match any of the values (value1, value2, ... value_n).
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $arguments Statement arguments
|
||||||
|
*
|
||||||
|
* @return mixed The value of matched expression
|
||||||
|
*/
|
||||||
|
public static function statementSwitch(...$arguments)
|
||||||
|
{
|
||||||
|
$result = Functions::VALUE();
|
||||||
|
|
||||||
|
if (count($arguments) > 0) {
|
||||||
|
$targetValue = Functions::flattenSingleValue($arguments[0]);
|
||||||
|
$argc = count($arguments) - 1;
|
||||||
|
$switchCount = floor($argc / 2);
|
||||||
|
$switchSatisfied = false;
|
||||||
|
$hasDefaultClause = $argc % 2 !== 0;
|
||||||
|
$defaultClause = $argc % 2 === 0 ? null : $arguments[count($arguments) - 1];
|
||||||
|
|
||||||
|
if ($switchCount) {
|
||||||
|
for ($index = 0; $index < $switchCount; ++$index) {
|
||||||
|
if ($targetValue == $arguments[$index * 2 + 1]) {
|
||||||
|
$result = $arguments[$index * 2 + 2];
|
||||||
|
$switchSatisfied = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$switchSatisfied) {
|
||||||
|
$result = $hasDefaultClause ? $defaultClause : Functions::NA();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IFERROR.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =IFERROR(testValue,errorpart)
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $testValue Value to check, is also the value returned when no error
|
||||||
|
* @param mixed $errorpart Value to return when testValue is an error condition
|
||||||
|
*
|
||||||
|
* @return mixed The value of errorpart or testValue determined by error condition
|
||||||
|
*/
|
||||||
|
public static function IFERROR($testValue = '', $errorpart = '')
|
||||||
|
{
|
||||||
|
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
|
||||||
|
$errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart);
|
||||||
|
|
||||||
|
return self::statementIf(Functions::isError($testValue), $errorpart, $testValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IFNA.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =IFNA(testValue,napart)
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param mixed $testValue Value to check, is also the value returned when not an NA
|
||||||
|
* @param mixed $napart Value to return when testValue is an NA condition
|
||||||
|
*
|
||||||
|
* @return mixed The value of errorpart or testValue determined by error condition
|
||||||
|
*/
|
||||||
|
public static function IFNA($testValue = '', $napart = '')
|
||||||
|
{
|
||||||
|
$testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue);
|
||||||
|
$napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart);
|
||||||
|
|
||||||
|
return self::statementIf(Functions::isNa($testValue), $napart, $testValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
968
PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php
Normal file
968
PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php
Normal file
|
|
@ -0,0 +1,968 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class LookupRef
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* CELL_ADDRESS.
|
||||||
|
*
|
||||||
|
* Creates a cell address as text, given specified row and column numbers.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
|
||||||
|
*
|
||||||
|
* @param mixed $row Row number to use in the cell reference
|
||||||
|
* @param mixed $column Column number to use in the cell reference
|
||||||
|
* @param int $relativity Flag indicating the type of reference to return
|
||||||
|
* 1 or omitted Absolute
|
||||||
|
* 2 Absolute row; relative column
|
||||||
|
* 3 Relative row; absolute column
|
||||||
|
* 4 Relative
|
||||||
|
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
|
||||||
|
* TRUE or omitted CELL_ADDRESS returns an A1-style reference
|
||||||
|
* FALSE CELL_ADDRESS returns an R1C1-style reference
|
||||||
|
* @param string $sheetText Optional Name of worksheet to use
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
|
||||||
|
{
|
||||||
|
$row = Functions::flattenSingleValue($row);
|
||||||
|
$column = Functions::flattenSingleValue($column);
|
||||||
|
$relativity = Functions::flattenSingleValue($relativity);
|
||||||
|
$sheetText = Functions::flattenSingleValue($sheetText);
|
||||||
|
|
||||||
|
if (($row < 1) || ($column < 1)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sheetText > '') {
|
||||||
|
if (strpos($sheetText, ' ') !== false) {
|
||||||
|
$sheetText = "'" . $sheetText . "'";
|
||||||
|
}
|
||||||
|
$sheetText .= '!';
|
||||||
|
}
|
||||||
|
if ((!is_bool($referenceStyle)) || $referenceStyle) {
|
||||||
|
$rowRelative = $columnRelative = '$';
|
||||||
|
$column = Coordinate::stringFromColumnIndex($column);
|
||||||
|
if (($relativity == 2) || ($relativity == 4)) {
|
||||||
|
$columnRelative = '';
|
||||||
|
}
|
||||||
|
if (($relativity == 3) || ($relativity == 4)) {
|
||||||
|
$rowRelative = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sheetText . $columnRelative . $column . $rowRelative . $row;
|
||||||
|
}
|
||||||
|
if (($relativity == 2) || ($relativity == 4)) {
|
||||||
|
$column = '[' . $column . ']';
|
||||||
|
}
|
||||||
|
if (($relativity == 3) || ($relativity == 4)) {
|
||||||
|
$row = '[' . $row . ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sheetText . 'R' . $row . 'C' . $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COLUMN.
|
||||||
|
*
|
||||||
|
* Returns the column number of the given cell reference
|
||||||
|
* If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
|
||||||
|
* If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
|
||||||
|
* reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =COLUMN([cellAddress])
|
||||||
|
*
|
||||||
|
* @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers
|
||||||
|
*
|
||||||
|
* @return int|int[]
|
||||||
|
*/
|
||||||
|
public static function COLUMN($cellAddress = null)
|
||||||
|
{
|
||||||
|
if ($cellAddress === null || trim($cellAddress) === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($cellAddress)) {
|
||||||
|
foreach ($cellAddress as $columnKey => $value) {
|
||||||
|
$columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
|
||||||
|
|
||||||
|
return (int) Coordinate::columnIndexFromString($columnKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
if (strpos($cellAddress, ':') !== false) {
|
||||||
|
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||||
|
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
|
||||||
|
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
|
||||||
|
$returnValue = [];
|
||||||
|
do {
|
||||||
|
$returnValue[] = (int) Coordinate::columnIndexFromString($startAddress);
|
||||||
|
} while ($startAddress++ != $endAddress);
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
$cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
|
||||||
|
|
||||||
|
return (int) Coordinate::columnIndexFromString($cellAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COLUMNS.
|
||||||
|
*
|
||||||
|
* Returns the number of columns in an array or reference.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =COLUMNS(cellAddress)
|
||||||
|
*
|
||||||
|
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns
|
||||||
|
*
|
||||||
|
* @return int The number of columns in cellAddress
|
||||||
|
*/
|
||||||
|
public static function COLUMNS($cellAddress = null)
|
||||||
|
{
|
||||||
|
if ($cellAddress === null || $cellAddress === '') {
|
||||||
|
return 1;
|
||||||
|
} elseif (!is_array($cellAddress)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset($cellAddress);
|
||||||
|
$isMatrix = (is_numeric(key($cellAddress)));
|
||||||
|
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||||
|
|
||||||
|
if ($isMatrix) {
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROW.
|
||||||
|
*
|
||||||
|
* Returns the row number of the given cell reference
|
||||||
|
* If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
|
||||||
|
* If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
|
||||||
|
* reference of the cell in which the ROW function appears; otherwise this function returns 0.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =ROW([cellAddress])
|
||||||
|
*
|
||||||
|
* @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers
|
||||||
|
*
|
||||||
|
* @return int or array of integer
|
||||||
|
*/
|
||||||
|
public static function ROW($cellAddress = null)
|
||||||
|
{
|
||||||
|
if ($cellAddress === null || trim($cellAddress) === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($cellAddress)) {
|
||||||
|
foreach ($cellAddress as $columnKey => $rowValue) {
|
||||||
|
foreach ($rowValue as $rowKey => $cellValue) {
|
||||||
|
return (int) preg_replace('/\D/', '', $rowKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
if (strpos($cellAddress, ':') !== false) {
|
||||||
|
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||||
|
$startAddress = preg_replace('/\D/', '', $startAddress);
|
||||||
|
$endAddress = preg_replace('/\D/', '', $endAddress);
|
||||||
|
$returnValue = [];
|
||||||
|
do {
|
||||||
|
$returnValue[][] = (int) $startAddress;
|
||||||
|
} while ($startAddress++ != $endAddress);
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
[$cellAddress] = explode(':', $cellAddress);
|
||||||
|
|
||||||
|
return (int) preg_replace('/\D/', '', $cellAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ROWS.
|
||||||
|
*
|
||||||
|
* Returns the number of rows in an array or reference.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =ROWS(cellAddress)
|
||||||
|
*
|
||||||
|
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
|
||||||
|
*
|
||||||
|
* @return int The number of rows in cellAddress
|
||||||
|
*/
|
||||||
|
public static function ROWS($cellAddress = null)
|
||||||
|
{
|
||||||
|
if ($cellAddress === null || $cellAddress === '') {
|
||||||
|
return 1;
|
||||||
|
} elseif (!is_array($cellAddress)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset($cellAddress);
|
||||||
|
$isMatrix = (is_numeric(key($cellAddress)));
|
||||||
|
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||||
|
|
||||||
|
if ($isMatrix) {
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HYPERLINK.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =HYPERLINK(linkURL,displayName)
|
||||||
|
*
|
||||||
|
* @category Logical Functions
|
||||||
|
*
|
||||||
|
* @param string $linkURL Value to check, is also the value returned when no error
|
||||||
|
* @param string $displayName Value to return when testValue is an error condition
|
||||||
|
* @param Cell $pCell The cell to set the hyperlink in
|
||||||
|
*
|
||||||
|
* @return mixed The value of $displayName (or $linkURL if $displayName was blank)
|
||||||
|
*/
|
||||||
|
public static function HYPERLINK($linkURL = '', $displayName = null, Cell $pCell = null)
|
||||||
|
{
|
||||||
|
$linkURL = ($linkURL === null) ? '' : Functions::flattenSingleValue($linkURL);
|
||||||
|
$displayName = ($displayName === null) ? '' : Functions::flattenSingleValue($displayName);
|
||||||
|
|
||||||
|
if ((!is_object($pCell)) || (trim($linkURL) == '')) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((is_object($displayName)) || trim($displayName) == '') {
|
||||||
|
$displayName = $linkURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pCell->getHyperlink()->setUrl($linkURL);
|
||||||
|
$pCell->getHyperlink()->setTooltip($displayName);
|
||||||
|
|
||||||
|
return $displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INDIRECT.
|
||||||
|
*
|
||||||
|
* Returns the reference specified by a text string.
|
||||||
|
* References are immediately evaluated to display their contents.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =INDIRECT(cellAddress)
|
||||||
|
*
|
||||||
|
* NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
|
||||||
|
*
|
||||||
|
* @param null|array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
|
||||||
|
* @param Cell $pCell The current cell (containing this formula)
|
||||||
|
*
|
||||||
|
* @return mixed The cells referenced by cellAddress
|
||||||
|
*
|
||||||
|
* @todo Support for the optional a1 parameter introduced in Excel 2010
|
||||||
|
*/
|
||||||
|
public static function INDIRECT($cellAddress = null, Cell $pCell = null)
|
||||||
|
{
|
||||||
|
$cellAddress = Functions::flattenSingleValue($cellAddress);
|
||||||
|
if ($cellAddress === null || $cellAddress === '') {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
$cellAddress1 = $cellAddress;
|
||||||
|
$cellAddress2 = null;
|
||||||
|
if (strpos($cellAddress, ':') !== false) {
|
||||||
|
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
|
||||||
|
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))) {
|
||||||
|
if (!preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $cellAddress1, $matches)) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($cellAddress, '!') !== false) {
|
||||||
|
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
$sheetName = trim($sheetName, "'");
|
||||||
|
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||||
|
} else {
|
||||||
|
$pSheet = $pCell->getWorksheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($cellAddress, '!') !== false) {
|
||||||
|
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
$sheetName = trim($sheetName, "'");
|
||||||
|
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||||
|
} else {
|
||||||
|
$pSheet = $pCell->getWorksheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OFFSET.
|
||||||
|
*
|
||||||
|
* Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
|
||||||
|
* The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
|
||||||
|
* the number of columns to be returned.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =OFFSET(cellAddress, rows, cols, [height], [width])
|
||||||
|
*
|
||||||
|
* @param null|string $cellAddress The reference from which you want to base the offset. Reference must refer to a cell or
|
||||||
|
* range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
|
||||||
|
* @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
|
||||||
|
* Using 5 as the rows argument specifies that the upper-left cell in the reference is
|
||||||
|
* five rows below reference. Rows can be positive (which means below the starting reference)
|
||||||
|
* or negative (which means above the starting reference).
|
||||||
|
* @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell of the result
|
||||||
|
* to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
|
||||||
|
* reference is five columns to the right of reference. Cols can be positive (which means
|
||||||
|
* to the right of the starting reference) or negative (which means to the left of the
|
||||||
|
* starting reference).
|
||||||
|
* @param mixed $height The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
|
||||||
|
* @param mixed $width The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
|
||||||
|
* @param null|Cell $pCell
|
||||||
|
*
|
||||||
|
* @return string A reference to a cell or range of cells
|
||||||
|
*/
|
||||||
|
public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, Cell $pCell = null)
|
||||||
|
{
|
||||||
|
$rows = Functions::flattenSingleValue($rows);
|
||||||
|
$columns = Functions::flattenSingleValue($columns);
|
||||||
|
$height = Functions::flattenSingleValue($height);
|
||||||
|
$width = Functions::flattenSingleValue($width);
|
||||||
|
if ($cellAddress === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_object($pCell)) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheetName = null;
|
||||||
|
if (strpos($cellAddress, '!')) {
|
||||||
|
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
$sheetName = trim($sheetName, "'");
|
||||||
|
}
|
||||||
|
if (strpos($cellAddress, ':')) {
|
||||||
|
[$startCell, $endCell] = explode(':', $cellAddress);
|
||||||
|
} else {
|
||||||
|
$startCell = $endCell = $cellAddress;
|
||||||
|
}
|
||||||
|
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
|
||||||
|
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
|
||||||
|
|
||||||
|
$startCellRow += $rows;
|
||||||
|
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
|
||||||
|
$startCellColumn += $columns;
|
||||||
|
|
||||||
|
if (($startCellRow <= 0) || ($startCellColumn < 0)) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
$endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
|
||||||
|
if (($width != null) && (!is_object($width))) {
|
||||||
|
$endCellColumn = $startCellColumn + $width - 1;
|
||||||
|
} else {
|
||||||
|
$endCellColumn += $columns;
|
||||||
|
}
|
||||||
|
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
|
||||||
|
|
||||||
|
if (($height != null) && (!is_object($height))) {
|
||||||
|
$endCellRow = $startCellRow + $height - 1;
|
||||||
|
} else {
|
||||||
|
$endCellRow += $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($endCellRow <= 0) || ($endCellColumn < 0)) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
|
||||||
|
|
||||||
|
$cellAddress = $startCellColumn . $startCellRow;
|
||||||
|
if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
|
||||||
|
$cellAddress .= ':' . $endCellColumn . $endCellRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sheetName !== null) {
|
||||||
|
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||||
|
} else {
|
||||||
|
$pSheet = $pCell->getWorksheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CHOOSE.
|
||||||
|
*
|
||||||
|
* Uses lookup_value to return a value from the list of value arguments.
|
||||||
|
* Use CHOOSE to select one of up to 254 values based on the lookup_value.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =CHOOSE(index_num, value1, [value2], ...)
|
||||||
|
*
|
||||||
|
* @param mixed $index_num Specifies which value argument is selected.
|
||||||
|
* Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
|
||||||
|
* between 1 and 254.
|
||||||
|
* @param mixed $value1 ... Value1 is required, subsequent values are optional.
|
||||||
|
* Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
|
||||||
|
* index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
|
||||||
|
* text.
|
||||||
|
*
|
||||||
|
* @return mixed The selected value
|
||||||
|
*/
|
||||||
|
public static function CHOOSE(...$chooseArgs)
|
||||||
|
{
|
||||||
|
$chosenEntry = Functions::flattenArray(array_shift($chooseArgs));
|
||||||
|
$entryCount = count($chooseArgs) - 1;
|
||||||
|
|
||||||
|
if (is_array($chosenEntry)) {
|
||||||
|
$chosenEntry = array_shift($chosenEntry);
|
||||||
|
}
|
||||||
|
if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
|
||||||
|
--$chosenEntry;
|
||||||
|
} else {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$chosenEntry = floor($chosenEntry);
|
||||||
|
if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($chooseArgs[$chosenEntry])) {
|
||||||
|
return Functions::flattenArray($chooseArgs[$chosenEntry]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chooseArgs[$chosenEntry];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MATCH.
|
||||||
|
*
|
||||||
|
* The MATCH function searches for a specified item in a range of cells
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =MATCH(lookup_value, lookup_array, [match_type])
|
||||||
|
*
|
||||||
|
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||||
|
* @param mixed $lookupArray The range of cells being searched
|
||||||
|
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
|
||||||
|
* If match_type is 1 or -1, the list has to be ordered.
|
||||||
|
*
|
||||||
|
* @return int|string The relative position of the found item
|
||||||
|
*/
|
||||||
|
public static function MATCH($lookupValue, $lookupArray, $matchType = 1)
|
||||||
|
{
|
||||||
|
$lookupArray = Functions::flattenArray($lookupArray);
|
||||||
|
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||||
|
$matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType);
|
||||||
|
|
||||||
|
// MATCH is not case sensitive, so we convert lookup value to be lower cased in case it's string type.
|
||||||
|
if (is_string($lookupValue)) {
|
||||||
|
$lookupValue = StringHelper::strToLower($lookupValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup_value type has to be number, text, or logical values
|
||||||
|
if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match_type is 0, 1 or -1
|
||||||
|
if (($matchType !== 0) && ($matchType !== -1) && ($matchType !== 1)) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup_array should not be empty
|
||||||
|
$lookupArraySize = count($lookupArray);
|
||||||
|
if ($lookupArraySize <= 0) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup_array should contain only number, text, or logical values, or empty (null) cells
|
||||||
|
foreach ($lookupArray as $i => $lookupArrayValue) {
|
||||||
|
// check the type of the value
|
||||||
|
if ((!is_numeric($lookupArrayValue)) && (!is_string($lookupArrayValue)) &&
|
||||||
|
(!is_bool($lookupArrayValue)) && ($lookupArrayValue !== null)
|
||||||
|
) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
// Convert strings to lowercase for case-insensitive testing
|
||||||
|
if (is_string($lookupArrayValue)) {
|
||||||
|
$lookupArray[$i] = StringHelper::strToLower($lookupArrayValue);
|
||||||
|
}
|
||||||
|
if (($lookupArrayValue === null) && (($matchType == 1) || ($matchType == -1))) {
|
||||||
|
$lookupArray = array_slice($lookupArray, 0, $i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matchType == 1) {
|
||||||
|
// If match_type is 1 the list has to be processed from last to first
|
||||||
|
|
||||||
|
$lookupArray = array_reverse($lookupArray);
|
||||||
|
$keySet = array_reverse(array_keys($lookupArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// find the match
|
||||||
|
// **
|
||||||
|
|
||||||
|
if ($matchType === 0 || $matchType === 1) {
|
||||||
|
foreach ($lookupArray as $i => $lookupArrayValue) {
|
||||||
|
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
|
||||||
|
$exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue;
|
||||||
|
$nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue;
|
||||||
|
$exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch;
|
||||||
|
|
||||||
|
if ($matchType === 0) {
|
||||||
|
if ($typeMatch && is_string($lookupValue) && (bool) preg_match('/([\?\*])/', $lookupValue)) {
|
||||||
|
$splitString = $lookupValue;
|
||||||
|
$chars = array_map(function ($i) use ($splitString) {
|
||||||
|
return mb_substr($splitString, $i, 1);
|
||||||
|
}, range(0, mb_strlen($splitString) - 1));
|
||||||
|
|
||||||
|
$length = count($chars);
|
||||||
|
$pattern = '/^';
|
||||||
|
for ($j = 0; $j < $length; ++$j) {
|
||||||
|
if ($chars[$j] === '~') {
|
||||||
|
if (isset($chars[$j + 1])) {
|
||||||
|
if ($chars[$j + 1] === '*') {
|
||||||
|
$pattern .= preg_quote($chars[$j + 1], '/');
|
||||||
|
++$j;
|
||||||
|
} elseif ($chars[$j + 1] === '?') {
|
||||||
|
$pattern .= preg_quote($chars[$j + 1], '/');
|
||||||
|
++$j;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$pattern .= preg_quote($chars[$j], '/');
|
||||||
|
}
|
||||||
|
} elseif ($chars[$j] === '*') {
|
||||||
|
$pattern .= '.*';
|
||||||
|
} elseif ($chars[$j] === '?') {
|
||||||
|
$pattern .= '.{1}';
|
||||||
|
} else {
|
||||||
|
$pattern .= preg_quote($chars[$j], '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern .= '$/';
|
||||||
|
if ((bool) preg_match($pattern, $lookupArrayValue)) {
|
||||||
|
// exact match
|
||||||
|
return $i + 1;
|
||||||
|
}
|
||||||
|
} elseif ($exactMatch) {
|
||||||
|
// exact match
|
||||||
|
return $i + 1;
|
||||||
|
}
|
||||||
|
} elseif (($matchType === 1) && $typeMatch && ($lookupArrayValue <= $lookupValue)) {
|
||||||
|
$i = array_search($i, $keySet);
|
||||||
|
|
||||||
|
// The current value is the (first) match
|
||||||
|
return $i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$maxValueKey = null;
|
||||||
|
|
||||||
|
// The basic algorithm is:
|
||||||
|
// Iterate and keep the highest match until the next element is smaller than the searched value.
|
||||||
|
// Return immediately if perfect match is found
|
||||||
|
foreach ($lookupArray as $i => $lookupArrayValue) {
|
||||||
|
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
|
||||||
|
$exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue;
|
||||||
|
$nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue;
|
||||||
|
$exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch;
|
||||||
|
|
||||||
|
if ($exactMatch) {
|
||||||
|
// Another "special" case. If a perfect match is found,
|
||||||
|
// the algorithm gives up immediately
|
||||||
|
return $i + 1;
|
||||||
|
} elseif ($typeMatch & $lookupArrayValue >= $lookupValue) {
|
||||||
|
$maxValueKey = $i + 1;
|
||||||
|
} elseif ($typeMatch & $lookupArrayValue < $lookupValue) {
|
||||||
|
//Excel algorithm gives up immediately if the first element is smaller than the searched value
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($maxValueKey !== null) {
|
||||||
|
return $maxValueKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsuccessful in finding a match, return #N/A error value
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INDEX.
|
||||||
|
*
|
||||||
|
* Uses an index to choose a value from a reference or array
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =INDEX(range_array, row_num, [column_num])
|
||||||
|
*
|
||||||
|
* @param mixed $arrayValues A range of cells or an array constant
|
||||||
|
* @param mixed $rowNum The row in array from which to return a value. If row_num is omitted, column_num is required.
|
||||||
|
* @param mixed $columnNum The column in array from which to return a value. If column_num is omitted, row_num is required.
|
||||||
|
*
|
||||||
|
* @return mixed the value of a specified cell or array of cells
|
||||||
|
*/
|
||||||
|
public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0)
|
||||||
|
{
|
||||||
|
$rowNum = Functions::flattenSingleValue($rowNum);
|
||||||
|
$columnNum = Functions::flattenSingleValue($columnNum);
|
||||||
|
|
||||||
|
if (($rowNum < 0) || ($columnNum < 0)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($arrayValues) || ($rowNum > count($arrayValues))) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowKeys = array_keys($arrayValues);
|
||||||
|
$columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
|
||||||
|
|
||||||
|
if ($columnNum > count($columnKeys)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
} elseif ($columnNum == 0) {
|
||||||
|
if ($rowNum == 0) {
|
||||||
|
return $arrayValues;
|
||||||
|
}
|
||||||
|
$rowNum = $rowKeys[--$rowNum];
|
||||||
|
$returnArray = [];
|
||||||
|
foreach ($arrayValues as $arrayColumn) {
|
||||||
|
if (is_array($arrayColumn)) {
|
||||||
|
if (isset($arrayColumn[$rowNum])) {
|
||||||
|
$returnArray[] = $arrayColumn[$rowNum];
|
||||||
|
} else {
|
||||||
|
return [$rowNum => $arrayValues[$rowNum]];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $arrayValues[$rowNum];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnArray;
|
||||||
|
}
|
||||||
|
$columnNum = $columnKeys[--$columnNum];
|
||||||
|
if ($rowNum > count($rowKeys)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
} elseif ($rowNum == 0) {
|
||||||
|
return $arrayValues[$columnNum];
|
||||||
|
}
|
||||||
|
$rowNum = $rowKeys[--$rowNum];
|
||||||
|
|
||||||
|
return $arrayValues[$rowNum][$columnNum];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRANSPOSE.
|
||||||
|
*
|
||||||
|
* @param array $matrixData A matrix of values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix
|
||||||
|
*/
|
||||||
|
public static function TRANSPOSE($matrixData)
|
||||||
|
{
|
||||||
|
$returnMatrix = [];
|
||||||
|
if (!is_array($matrixData)) {
|
||||||
|
$matrixData = [[$matrixData]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$column = 0;
|
||||||
|
foreach ($matrixData as $matrixRow) {
|
||||||
|
$row = 0;
|
||||||
|
foreach ($matrixRow as $matrixCell) {
|
||||||
|
$returnMatrix[$row][$column] = $matrixCell;
|
||||||
|
++$row;
|
||||||
|
}
|
||||||
|
++$column;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function vlookupSort($a, $b)
|
||||||
|
{
|
||||||
|
reset($a);
|
||||||
|
$firstColumn = key($a);
|
||||||
|
$aLower = StringHelper::strToLower($a[$firstColumn]);
|
||||||
|
$bLower = StringHelper::strToLower($b[$firstColumn]);
|
||||||
|
if ($aLower == $bLower) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($aLower < $bLower) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VLOOKUP
|
||||||
|
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
|
||||||
|
*
|
||||||
|
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||||
|
* @param mixed $lookup_array The range of cells being searched
|
||||||
|
* @param mixed $index_number The column number in table_array from which the matching value must be returned. The first column is 1.
|
||||||
|
* @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value
|
||||||
|
*
|
||||||
|
* @return mixed The value of the found cell
|
||||||
|
*/
|
||||||
|
public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
|
||||||
|
{
|
||||||
|
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||||
|
$index_number = Functions::flattenSingleValue($index_number);
|
||||||
|
$not_exact_match = Functions::flattenSingleValue($not_exact_match);
|
||||||
|
|
||||||
|
// index_number must be greater than or equal to 1
|
||||||
|
if ($index_number < 1) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// index_number must be less than or equal to the number of columns in lookup_array
|
||||||
|
if ((!is_array($lookup_array)) || (empty($lookup_array))) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
$f = array_keys($lookup_array);
|
||||||
|
$firstRow = array_pop($f);
|
||||||
|
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
$columnKeys = array_keys($lookup_array[$firstRow]);
|
||||||
|
$returnColumn = $columnKeys[--$index_number];
|
||||||
|
$firstColumn = array_shift($columnKeys);
|
||||||
|
|
||||||
|
if (!$not_exact_match) {
|
||||||
|
uasort($lookup_array, ['self', 'vlookupSort']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lookupLower = StringHelper::strToLower($lookup_value);
|
||||||
|
$rowNumber = $rowValue = false;
|
||||||
|
foreach ($lookup_array as $rowKey => $rowData) {
|
||||||
|
$firstLower = StringHelper::strToLower($rowData[$firstColumn]);
|
||||||
|
|
||||||
|
// break if we have passed possible keys
|
||||||
|
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
|
||||||
|
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && ($firstLower > $lookupLower))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// remember the last key, but only if datatypes match
|
||||||
|
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
|
||||||
|
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
|
||||||
|
if ($not_exact_match) {
|
||||||
|
$rowNumber = $rowKey;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} elseif (($firstLower == $lookupLower)
|
||||||
|
// Spreadsheets software returns first exact match,
|
||||||
|
// we have sorted and we might have broken key orders
|
||||||
|
// we want the first one (by its initial index)
|
||||||
|
&& (($rowNumber == false) || ($rowKey < $rowNumber))
|
||||||
|
) {
|
||||||
|
$rowNumber = $rowKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rowNumber !== false) {
|
||||||
|
// return the appropriate value
|
||||||
|
return $lookup_array[$rowNumber][$returnColumn];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLOOKUP
|
||||||
|
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number.
|
||||||
|
*
|
||||||
|
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||||
|
* @param mixed $lookup_array The range of cells being searched
|
||||||
|
* @param mixed $index_number The row number in table_array from which the matching value must be returned. The first row is 1.
|
||||||
|
* @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value
|
||||||
|
*
|
||||||
|
* @return mixed The value of the found cell
|
||||||
|
*/
|
||||||
|
public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
|
||||||
|
{
|
||||||
|
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||||
|
$index_number = Functions::flattenSingleValue($index_number);
|
||||||
|
$not_exact_match = Functions::flattenSingleValue($not_exact_match);
|
||||||
|
|
||||||
|
// index_number must be greater than or equal to 1
|
||||||
|
if ($index_number < 1) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// index_number must be less than or equal to the number of columns in lookup_array
|
||||||
|
if ((!is_array($lookup_array)) || (empty($lookup_array))) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
$f = array_keys($lookup_array);
|
||||||
|
$firstRow = array_pop($f);
|
||||||
|
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstkey = $f[0] - 1;
|
||||||
|
$returnColumn = $firstkey + $index_number;
|
||||||
|
$firstColumn = array_shift($f);
|
||||||
|
$rowNumber = null;
|
||||||
|
foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
|
||||||
|
// break if we have passed possible keys
|
||||||
|
$bothNumeric = is_numeric($lookup_value) && is_numeric($rowData);
|
||||||
|
$bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData);
|
||||||
|
$lookupLower = StringHelper::strToLower($lookup_value);
|
||||||
|
$rowDataLower = StringHelper::strToLower($rowData);
|
||||||
|
|
||||||
|
if ($not_exact_match && (
|
||||||
|
($bothNumeric && $rowData > $lookup_value) ||
|
||||||
|
($bothNotNumeric && $rowDataLower > $lookupLower)
|
||||||
|
)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember the last key, but only if datatypes match (as in VLOOKUP)
|
||||||
|
if ($bothNumeric || $bothNotNumeric) {
|
||||||
|
if ($not_exact_match) {
|
||||||
|
$rowNumber = $rowKey;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} elseif ($rowDataLower === $lookupLower
|
||||||
|
&& ($rowNumber === null || $rowKey < $rowNumber)
|
||||||
|
) {
|
||||||
|
$rowNumber = $rowKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rowNumber !== null) {
|
||||||
|
// otherwise return the appropriate value
|
||||||
|
return $lookup_array[$returnColumn][$rowNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOOKUP
|
||||||
|
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
|
||||||
|
*
|
||||||
|
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||||
|
* @param mixed $lookup_vector The range of cells being searched
|
||||||
|
* @param null|mixed $result_vector The column from which the matching value must be returned
|
||||||
|
*
|
||||||
|
* @return mixed The value of the found cell
|
||||||
|
*/
|
||||||
|
public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null)
|
||||||
|
{
|
||||||
|
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||||
|
|
||||||
|
if (!is_array($lookup_vector)) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
$hasResultVector = isset($result_vector);
|
||||||
|
$lookupRows = count($lookup_vector);
|
||||||
|
$l = array_keys($lookup_vector);
|
||||||
|
$l = array_shift($l);
|
||||||
|
$lookupColumns = count($lookup_vector[$l]);
|
||||||
|
// we correctly orient our results
|
||||||
|
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
|
||||||
|
$lookup_vector = self::TRANSPOSE($lookup_vector);
|
||||||
|
$lookupRows = count($lookup_vector);
|
||||||
|
$l = array_keys($lookup_vector);
|
||||||
|
$lookupColumns = count($lookup_vector[array_shift($l)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result_vector === null) {
|
||||||
|
$result_vector = $lookup_vector;
|
||||||
|
}
|
||||||
|
$resultRows = count($result_vector);
|
||||||
|
$l = array_keys($result_vector);
|
||||||
|
$l = array_shift($l);
|
||||||
|
$resultColumns = count($result_vector[$l]);
|
||||||
|
// we correctly orient our results
|
||||||
|
if ($resultRows === 1 && $resultColumns > 1) {
|
||||||
|
$result_vector = self::TRANSPOSE($result_vector);
|
||||||
|
$resultRows = count($result_vector);
|
||||||
|
$r = array_keys($result_vector);
|
||||||
|
$resultColumns = count($result_vector[array_shift($r)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lookupRows === 2 && !$hasResultVector) {
|
||||||
|
$result_vector = array_pop($lookup_vector);
|
||||||
|
$lookup_vector = array_shift($lookup_vector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lookupColumns !== 2) {
|
||||||
|
foreach ($lookup_vector as &$value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$k = array_keys($value);
|
||||||
|
$key1 = $key2 = array_shift($k);
|
||||||
|
++$key2;
|
||||||
|
$dataValue1 = $value[$key1];
|
||||||
|
} else {
|
||||||
|
$key1 = 0;
|
||||||
|
$key2 = 1;
|
||||||
|
$dataValue1 = $value;
|
||||||
|
}
|
||||||
|
$dataValue2 = array_shift($result_vector);
|
||||||
|
if (is_array($dataValue2)) {
|
||||||
|
$dataValue2 = array_shift($dataValue2);
|
||||||
|
}
|
||||||
|
$value = [$key1 => $dataValue1, $key2 => $dataValue2];
|
||||||
|
}
|
||||||
|
unset($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::VLOOKUP($lookup_value, $lookup_vector, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FORMULATEXT.
|
||||||
|
*
|
||||||
|
* @param mixed $cellReference The cell to check
|
||||||
|
* @param Cell $pCell The current cell (containing this formula)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function FORMULATEXT($cellReference = '', Cell $pCell = null)
|
||||||
|
{
|
||||||
|
if ($pCell === null) {
|
||||||
|
return Functions::REF();
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
|
||||||
|
|
||||||
|
$cellReference = $matches[6] . $matches[7];
|
||||||
|
$worksheetName = trim($matches[3], "'");
|
||||||
|
$worksheet = (!empty($worksheetName))
|
||||||
|
? $pCell->getWorksheet()->getParent()->getSheetByName($worksheetName)
|
||||||
|
: $pCell->getWorksheet();
|
||||||
|
|
||||||
|
if (!$worksheet->getCell($cellReference)->isFormula()) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $worksheet->getCell($cellReference)->getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
1650
PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php
Normal file
1650
PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php
Normal file
File diff suppressed because it is too large
Load Diff
3773
PhpOffice/PhpSpreadsheet/Calculation/Statistical.php
Normal file
3773
PhpOffice/PhpSpreadsheet/Calculation/Statistical.php
Normal file
File diff suppressed because it is too large
Load Diff
672
PhpOffice/PhpSpreadsheet/Calculation/TextData.php
Normal file
672
PhpOffice/PhpSpreadsheet/Calculation/TextData.php
Normal file
|
|
@ -0,0 +1,672 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||||
|
|
||||||
|
class TextData
|
||||||
|
{
|
||||||
|
private static $invalidChars;
|
||||||
|
|
||||||
|
private static function unicodeToOrd($character)
|
||||||
|
{
|
||||||
|
return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CHARACTER.
|
||||||
|
*
|
||||||
|
* @param string $character Value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function CHARACTER($character)
|
||||||
|
{
|
||||||
|
$character = Functions::flattenSingleValue($character);
|
||||||
|
|
||||||
|
if ((!is_numeric($character)) || ($character < 0)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('iconv')) {
|
||||||
|
return iconv('UCS-4LE', 'UTF-8', pack('V', $character));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_convert_encoding('&#' . (int) $character . ';', 'UTF-8', 'HTML-ENTITIES');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRIMNONPRINTABLE.
|
||||||
|
*
|
||||||
|
* @param mixed $stringValue Value to check
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function TRIMNONPRINTABLE($stringValue = '')
|
||||||
|
{
|
||||||
|
$stringValue = Functions::flattenSingleValue($stringValue);
|
||||||
|
|
||||||
|
if (is_bool($stringValue)) {
|
||||||
|
return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$invalidChars == null) {
|
||||||
|
self::$invalidChars = range(chr(0), chr(31));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($stringValue) || is_numeric($stringValue)) {
|
||||||
|
return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRIMSPACES.
|
||||||
|
*
|
||||||
|
* @param mixed $stringValue Value to check
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function TRIMSPACES($stringValue = '')
|
||||||
|
{
|
||||||
|
$stringValue = Functions::flattenSingleValue($stringValue);
|
||||||
|
if (is_bool($stringValue)) {
|
||||||
|
return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($stringValue) || is_numeric($stringValue)) {
|
||||||
|
return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASCIICODE.
|
||||||
|
*
|
||||||
|
* @param string $characters Value
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function ASCIICODE($characters)
|
||||||
|
{
|
||||||
|
if (($characters === null) || ($characters === '')) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$characters = Functions::flattenSingleValue($characters);
|
||||||
|
if (is_bool($characters)) {
|
||||||
|
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
|
||||||
|
$characters = (int) $characters;
|
||||||
|
} else {
|
||||||
|
$characters = ($characters) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$character = $characters;
|
||||||
|
if (mb_strlen($characters, 'UTF-8') > 1) {
|
||||||
|
$character = mb_substr($characters, 0, 1, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::unicodeToOrd($character);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CONCATENATE.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function CONCATENATE(...$args)
|
||||||
|
{
|
||||||
|
$returnValue = '';
|
||||||
|
|
||||||
|
// Loop through arguments
|
||||||
|
$aArgs = Functions::flattenArray($args);
|
||||||
|
foreach ($aArgs as $arg) {
|
||||||
|
if (is_bool($arg)) {
|
||||||
|
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
|
||||||
|
$arg = (int) $arg;
|
||||||
|
} else {
|
||||||
|
$arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$returnValue .= $arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOLLAR.
|
||||||
|
*
|
||||||
|
* This function converts a number to text using currency format, with the decimals rounded to the specified place.
|
||||||
|
* The format used is $#,##0.00_);($#,##0.00)..
|
||||||
|
*
|
||||||
|
* @param float $value The value to format
|
||||||
|
* @param int $decimals The number of digits to display to the right of the decimal point.
|
||||||
|
* If decimals is negative, number is rounded to the left of the decimal point.
|
||||||
|
* If you omit decimals, it is assumed to be 2
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function DOLLAR($value = 0, $decimals = 2)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$decimals = $decimals === null ? 0 : Functions::flattenSingleValue($decimals);
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (!is_numeric($value) || !is_numeric($decimals)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
$decimals = floor($decimals);
|
||||||
|
|
||||||
|
$mask = '$#,##0';
|
||||||
|
if ($decimals > 0) {
|
||||||
|
$mask .= '.' . str_repeat('0', $decimals);
|
||||||
|
} else {
|
||||||
|
$round = pow(10, abs($decimals));
|
||||||
|
if ($value < 0) {
|
||||||
|
$round = 0 - $round;
|
||||||
|
}
|
||||||
|
$value = MathTrig::MROUND($value, $round);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NumberFormat::toFormattedString($value, $mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEARCHSENSITIVE.
|
||||||
|
*
|
||||||
|
* @param string $needle The string to look for
|
||||||
|
* @param string $haystack The string in which to look
|
||||||
|
* @param int $offset Offset within $haystack
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function SEARCHSENSITIVE($needle, $haystack, $offset = 1)
|
||||||
|
{
|
||||||
|
$needle = Functions::flattenSingleValue($needle);
|
||||||
|
$haystack = Functions::flattenSingleValue($haystack);
|
||||||
|
$offset = Functions::flattenSingleValue($offset);
|
||||||
|
|
||||||
|
if (!is_bool($needle)) {
|
||||||
|
if (is_bool($haystack)) {
|
||||||
|
$haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
|
||||||
|
if (StringHelper::countCharacters($needle) == 0) {
|
||||||
|
return $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8');
|
||||||
|
if ($pos !== false) {
|
||||||
|
return ++$pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEARCHINSENSITIVE.
|
||||||
|
*
|
||||||
|
* @param string $needle The string to look for
|
||||||
|
* @param string $haystack The string in which to look
|
||||||
|
* @param int $offset Offset within $haystack
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function SEARCHINSENSITIVE($needle, $haystack, $offset = 1)
|
||||||
|
{
|
||||||
|
$needle = Functions::flattenSingleValue($needle);
|
||||||
|
$haystack = Functions::flattenSingleValue($haystack);
|
||||||
|
$offset = Functions::flattenSingleValue($offset);
|
||||||
|
|
||||||
|
if (!is_bool($needle)) {
|
||||||
|
if (is_bool($haystack)) {
|
||||||
|
$haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
|
||||||
|
if (StringHelper::countCharacters($needle) == 0) {
|
||||||
|
return $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8');
|
||||||
|
if ($pos !== false) {
|
||||||
|
return ++$pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXEDFORMAT.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
* @param int $decimals
|
||||||
|
* @param bool $no_commas
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function FIXEDFORMAT($value, $decimals = 2, $no_commas = false)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$decimals = Functions::flattenSingleValue($decimals);
|
||||||
|
$no_commas = Functions::flattenSingleValue($no_commas);
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (!is_numeric($value) || !is_numeric($decimals)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
$decimals = floor($decimals);
|
||||||
|
|
||||||
|
$valueResult = round($value, $decimals);
|
||||||
|
if ($decimals < 0) {
|
||||||
|
$decimals = 0;
|
||||||
|
}
|
||||||
|
if (!$no_commas) {
|
||||||
|
$valueResult = number_format($valueResult, $decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $valueResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LEFT.
|
||||||
|
*
|
||||||
|
* @param string $value Value
|
||||||
|
* @param int $chars Number of characters
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function LEFT($value = '', $chars = 1)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$chars = Functions::flattenSingleValue($chars);
|
||||||
|
|
||||||
|
if ($chars < 0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_substr($value, 0, $chars, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MID.
|
||||||
|
*
|
||||||
|
* @param string $value Value
|
||||||
|
* @param int $start Start character
|
||||||
|
* @param int $chars Number of characters
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function MID($value = '', $start = 1, $chars = null)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$start = Functions::flattenSingleValue($start);
|
||||||
|
$chars = Functions::flattenSingleValue($chars);
|
||||||
|
|
||||||
|
if (($start < 1) || ($chars < 0)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($chars)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_substr($value, --$start, $chars, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RIGHT.
|
||||||
|
*
|
||||||
|
* @param string $value Value
|
||||||
|
* @param int $chars Number of characters
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function RIGHT($value = '', $chars = 1)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$chars = Functions::flattenSingleValue($chars);
|
||||||
|
|
||||||
|
if ($chars < 0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STRINGLENGTH.
|
||||||
|
*
|
||||||
|
* @param string $value Value
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function STRINGLENGTH($value = '')
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
|
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_strlen($value, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOWERCASE.
|
||||||
|
*
|
||||||
|
* Converts a string value to upper case.
|
||||||
|
*
|
||||||
|
* @param string $mixedCaseString
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function LOWERCASE($mixedCaseString)
|
||||||
|
{
|
||||||
|
$mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
|
||||||
|
|
||||||
|
if (is_bool($mixedCaseString)) {
|
||||||
|
$mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringHelper::strToLower($mixedCaseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPPERCASE.
|
||||||
|
*
|
||||||
|
* Converts a string value to upper case.
|
||||||
|
*
|
||||||
|
* @param string $mixedCaseString
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function UPPERCASE($mixedCaseString)
|
||||||
|
{
|
||||||
|
$mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
|
||||||
|
|
||||||
|
if (is_bool($mixedCaseString)) {
|
||||||
|
$mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringHelper::strToUpper($mixedCaseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PROPERCASE.
|
||||||
|
*
|
||||||
|
* Converts a string value to upper case.
|
||||||
|
*
|
||||||
|
* @param string $mixedCaseString
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function PROPERCASE($mixedCaseString)
|
||||||
|
{
|
||||||
|
$mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
|
||||||
|
|
||||||
|
if (is_bool($mixedCaseString)) {
|
||||||
|
$mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringHelper::strToTitle($mixedCaseString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REPLACE.
|
||||||
|
*
|
||||||
|
* @param string $oldText String to modify
|
||||||
|
* @param int $start Start character
|
||||||
|
* @param int $chars Number of characters
|
||||||
|
* @param string $newText String to replace in defined position
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function REPLACE($oldText, $start, $chars, $newText)
|
||||||
|
{
|
||||||
|
$oldText = Functions::flattenSingleValue($oldText);
|
||||||
|
$start = Functions::flattenSingleValue($start);
|
||||||
|
$chars = Functions::flattenSingleValue($chars);
|
||||||
|
$newText = Functions::flattenSingleValue($newText);
|
||||||
|
|
||||||
|
$left = self::LEFT($oldText, $start - 1);
|
||||||
|
$right = self::RIGHT($oldText, self::STRINGLENGTH($oldText) - ($start + $chars) + 1);
|
||||||
|
|
||||||
|
return $left . $newText . $right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SUBSTITUTE.
|
||||||
|
*
|
||||||
|
* @param string $text Value
|
||||||
|
* @param string $fromText From Value
|
||||||
|
* @param string $toText To Value
|
||||||
|
* @param int $instance Instance Number
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0)
|
||||||
|
{
|
||||||
|
$text = Functions::flattenSingleValue($text);
|
||||||
|
$fromText = Functions::flattenSingleValue($fromText);
|
||||||
|
$toText = Functions::flattenSingleValue($toText);
|
||||||
|
$instance = floor(Functions::flattenSingleValue($instance));
|
||||||
|
|
||||||
|
if ($instance == 0) {
|
||||||
|
return str_replace($fromText, $toText, $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos = -1;
|
||||||
|
while ($instance > 0) {
|
||||||
|
$pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8');
|
||||||
|
if ($pos === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
--$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pos !== false) {
|
||||||
|
return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RETURNSTRING.
|
||||||
|
*
|
||||||
|
* @param mixed $testValue Value to check
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public static function RETURNSTRING($testValue = '')
|
||||||
|
{
|
||||||
|
$testValue = Functions::flattenSingleValue($testValue);
|
||||||
|
|
||||||
|
if (is_string($testValue)) {
|
||||||
|
return $testValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TEXTFORMAT.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
* @param string $format Format mask to use
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function TEXTFORMAT($value, $format)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$format = Functions::flattenSingleValue($format);
|
||||||
|
|
||||||
|
if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) {
|
||||||
|
$value = DateTime::DATEVALUE($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) NumberFormat::toFormattedString($value, $format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VALUE.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function VALUE($value = '')
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
|
||||||
|
if (!is_numeric($value)) {
|
||||||
|
$numberValue = str_replace(
|
||||||
|
StringHelper::getThousandsSeparator(),
|
||||||
|
'',
|
||||||
|
trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
|
||||||
|
);
|
||||||
|
if (is_numeric($numberValue)) {
|
||||||
|
return (float) $numberValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateSetting = Functions::getReturnDateType();
|
||||||
|
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||||
|
|
||||||
|
if (strpos($value, ':') !== false) {
|
||||||
|
$timeValue = DateTime::TIMEVALUE($value);
|
||||||
|
if ($timeValue !== Functions::VALUE()) {
|
||||||
|
Functions::setReturnDateType($dateSetting);
|
||||||
|
|
||||||
|
return $timeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$dateValue = DateTime::DATEVALUE($value);
|
||||||
|
if ($dateValue !== Functions::VALUE()) {
|
||||||
|
Functions::setReturnDateType($dateSetting);
|
||||||
|
|
||||||
|
return $dateValue;
|
||||||
|
}
|
||||||
|
Functions::setReturnDateType($dateSetting);
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NUMBERVALUE.
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check
|
||||||
|
* @param string $decimalSeparator decimal separator, defaults to locale defined value
|
||||||
|
* @param string $groupSeparator group/thosands separator, defaults to locale defined value
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$decimalSeparator = Functions::flattenSingleValue($decimalSeparator);
|
||||||
|
$groupSeparator = Functions::flattenSingleValue($groupSeparator);
|
||||||
|
|
||||||
|
if (!is_numeric($value)) {
|
||||||
|
$decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator;
|
||||||
|
$groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator;
|
||||||
|
|
||||||
|
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
|
||||||
|
if ($decimalPositions > 1) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$decimalOffset = array_pop($matches[0])[1];
|
||||||
|
if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
|
||||||
|
|
||||||
|
// Handle the special case of trailing % signs
|
||||||
|
$percentageString = rtrim($value, '%');
|
||||||
|
if (!is_numeric($percentageString)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$percentageAdjustment = strlen($value) - strlen($percentageString);
|
||||||
|
if ($percentageAdjustment) {
|
||||||
|
$value = (float) $percentageString;
|
||||||
|
$value /= pow(10, $percentageAdjustment * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise.
|
||||||
|
* EXACT is case-sensitive but ignores formatting differences.
|
||||||
|
* Use EXACT to test text being entered into a document.
|
||||||
|
*
|
||||||
|
* @param $value1
|
||||||
|
* @param $value2
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function EXACT($value1, $value2)
|
||||||
|
{
|
||||||
|
$value1 = Functions::flattenSingleValue($value1);
|
||||||
|
$value2 = Functions::flattenSingleValue($value2);
|
||||||
|
|
||||||
|
return (string) $value2 === (string) $value1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TEXTJOIN.
|
||||||
|
*
|
||||||
|
* @param mixed $delimiter
|
||||||
|
* @param mixed $ignoreEmpty
|
||||||
|
* @param mixed $args
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args)
|
||||||
|
{
|
||||||
|
// Loop through arguments
|
||||||
|
$aArgs = Functions::flattenArray($args);
|
||||||
|
foreach ($aArgs as $key => &$arg) {
|
||||||
|
if ($ignoreEmpty && trim($arg) == '') {
|
||||||
|
unset($aArgs[$key]);
|
||||||
|
} elseif (is_bool($arg)) {
|
||||||
|
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
|
||||||
|
$arg = (int) $arg;
|
||||||
|
} else {
|
||||||
|
$arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode($delimiter, $aArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
PhpOffice/PhpSpreadsheet/Calculation/Token/Stack.php
Normal file
149
PhpOffice/PhpSpreadsheet/Calculation/Token/Stack.php
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Token;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||||
|
|
||||||
|
class Stack
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The parser stack for formulae.
|
||||||
|
*
|
||||||
|
* @var mixed[]
|
||||||
|
*/
|
||||||
|
private $stack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of entries in the parser stack.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of entries on the stack.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return $this->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a new entry onto the stack.
|
||||||
|
*
|
||||||
|
* @param mixed $type
|
||||||
|
* @param mixed $value
|
||||||
|
* @param mixed $reference
|
||||||
|
* @param null|string $storeKey will store the result under this alias
|
||||||
|
* @param null|string $onlyIf will only run computation if the matching
|
||||||
|
* store key is true
|
||||||
|
* @param null|string $onlyIfNot will only run computation if the matching
|
||||||
|
* store key is false
|
||||||
|
*/
|
||||||
|
public function push(
|
||||||
|
$type,
|
||||||
|
$value,
|
||||||
|
$reference = null,
|
||||||
|
$storeKey = null,
|
||||||
|
$onlyIf = null,
|
||||||
|
$onlyIfNot = null
|
||||||
|
) {
|
||||||
|
$stackItem = $this->getStackItem($type, $value, $reference, $storeKey, $onlyIf, $onlyIfNot);
|
||||||
|
|
||||||
|
$this->stack[$this->count++] = $stackItem;
|
||||||
|
|
||||||
|
if ($type == 'Function') {
|
||||||
|
$localeFunction = Calculation::localeFunc($value);
|
||||||
|
if ($localeFunction != $value) {
|
||||||
|
$this->stack[($this->count - 1)]['localeValue'] = $localeFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStackItem(
|
||||||
|
$type,
|
||||||
|
$value,
|
||||||
|
$reference = null,
|
||||||
|
$storeKey = null,
|
||||||
|
$onlyIf = null,
|
||||||
|
$onlyIfNot = null
|
||||||
|
) {
|
||||||
|
$stackItem = [
|
||||||
|
'type' => $type,
|
||||||
|
'value' => $value,
|
||||||
|
'reference' => $reference,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($storeKey)) {
|
||||||
|
$stackItem['storeKey'] = $storeKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($onlyIf)) {
|
||||||
|
$stackItem['onlyIf'] = $onlyIf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($onlyIfNot)) {
|
||||||
|
$stackItem['onlyIfNot'] = $onlyIfNot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stackItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop the last entry from the stack.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function pop()
|
||||||
|
{
|
||||||
|
if ($this->count > 0) {
|
||||||
|
return $this->stack[--$this->count];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an entry from the stack without removing it.
|
||||||
|
*
|
||||||
|
* @param int $n number indicating how far back in the stack we want to look
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function last($n = 1)
|
||||||
|
{
|
||||||
|
if ($this->count - $n < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stack[$this->count - $n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the stack.
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->stack = [];
|
||||||
|
$this->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
$str = 'Stack: ';
|
||||||
|
foreach ($this->stack as $index => $item) {
|
||||||
|
if ($index > $this->count - 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$value = $item['value'] ?? 'no value';
|
||||||
|
while (is_array($value)) {
|
||||||
|
$value = array_pop($value);
|
||||||
|
}
|
||||||
|
$str .= $value . ' |> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ ACCRINT
|
||||||
ACCRINTM
|
ACCRINTM
|
||||||
ACOS
|
ACOS
|
||||||
ACOSH
|
ACOSH
|
||||||
|
ACOT
|
||||||
|
ACOTH
|
||||||
ADDRESS
|
ADDRESS
|
||||||
AMORDEGRC
|
AMORDEGRC
|
||||||
AMORLINC
|
AMORLINC
|
||||||
|
|
@ -30,6 +32,11 @@ BIN2DEC
|
||||||
BIN2HEX
|
BIN2HEX
|
||||||
BIN2OCT
|
BIN2OCT
|
||||||
BINOMDIST
|
BINOMDIST
|
||||||
|
BITAND
|
||||||
|
BITLSHIFT
|
||||||
|
BITOR
|
||||||
|
BITRSHIFT
|
||||||
|
BITXOR
|
||||||
CEILING
|
CEILING
|
||||||
CELL
|
CELL
|
||||||
CHAR
|
CHAR
|
||||||
|
|
@ -43,12 +50,15 @@ COLUMN
|
||||||
COLUMNS
|
COLUMNS
|
||||||
COMBIN
|
COMBIN
|
||||||
COMPLEX
|
COMPLEX
|
||||||
|
CONCAT
|
||||||
CONCATENATE
|
CONCATENATE
|
||||||
CONFIDENCE
|
CONFIDENCE
|
||||||
CONVERT
|
CONVERT
|
||||||
CORREL
|
CORREL
|
||||||
COS
|
COS
|
||||||
COSH
|
COSH
|
||||||
|
COT
|
||||||
|
COTH
|
||||||
COUNT
|
COUNT
|
||||||
COUNTA
|
COUNTA
|
||||||
COUNTBLANK
|
COUNTBLANK
|
||||||
|
|
@ -62,6 +72,8 @@ COUPNUM
|
||||||
COUPPCD
|
COUPPCD
|
||||||
COVAR
|
COVAR
|
||||||
CRITBINOM
|
CRITBINOM
|
||||||
|
CSC
|
||||||
|
CSCH
|
||||||
CUBEKPIMEMBER
|
CUBEKPIMEMBER
|
||||||
CUBEMEMBER
|
CUBEMEMBER
|
||||||
CUBEMEMBERPROPERTY
|
CUBEMEMBERPROPERTY
|
||||||
|
|
@ -76,6 +88,7 @@ DATEDIF
|
||||||
DATEVALUE
|
DATEVALUE
|
||||||
DAVERAGE
|
DAVERAGE
|
||||||
DAY
|
DAY
|
||||||
|
DAYS
|
||||||
DAYS360
|
DAYS360
|
||||||
DB
|
DB
|
||||||
DCOUNT
|
DCOUNT
|
||||||
|
|
@ -105,7 +118,9 @@ EDATE
|
||||||
EFFECT
|
EFFECT
|
||||||
EOMONTH
|
EOMONTH
|
||||||
ERF
|
ERF
|
||||||
|
ERF.PRECISE
|
||||||
ERFC
|
ERFC
|
||||||
|
ERFC.PRECISE
|
||||||
ERROR.TYPE
|
ERROR.TYPE
|
||||||
EVEN
|
EVEN
|
||||||
EXACT
|
EXACT
|
||||||
|
|
@ -149,6 +164,10 @@ IMAGINARY
|
||||||
IMARGUMENT
|
IMARGUMENT
|
||||||
IMCONJUGATE
|
IMCONJUGATE
|
||||||
IMCOS
|
IMCOS
|
||||||
|
IMCOSH
|
||||||
|
IMCOT
|
||||||
|
IMCSC
|
||||||
|
IMCSCH
|
||||||
IMEXP
|
IMEXP
|
||||||
IMLN
|
IMLN
|
||||||
IMLOG10
|
IMLOG10
|
||||||
|
|
@ -156,10 +175,14 @@ IMLOG2
|
||||||
IMPOWER
|
IMPOWER
|
||||||
IMPRODUCT
|
IMPRODUCT
|
||||||
IMREAL
|
IMREAL
|
||||||
|
IMSEC
|
||||||
|
IMSECH
|
||||||
IMSIN
|
IMSIN
|
||||||
|
IMSINH
|
||||||
IMSQRT
|
IMSQRT
|
||||||
IMSUB
|
IMSUB
|
||||||
IMSUM
|
IMSUM
|
||||||
|
IMTAN
|
||||||
INDEX
|
INDEX
|
||||||
INDIRECT
|
INDIRECT
|
||||||
INFO
|
INFO
|
||||||
|
|
@ -177,6 +200,7 @@ ISNA
|
||||||
ISNONTEXT
|
ISNONTEXT
|
||||||
ISNUMBER
|
ISNUMBER
|
||||||
ISODD
|
ISODD
|
||||||
|
ISOWEEKNUM
|
||||||
ISPMT
|
ISPMT
|
||||||
ISREF
|
ISREF
|
||||||
ISTEXT
|
ISTEXT
|
||||||
|
|
@ -200,6 +224,7 @@ LOWER
|
||||||
MATCH
|
MATCH
|
||||||
MAX
|
MAX
|
||||||
MAXA
|
MAXA
|
||||||
|
MAXIFS
|
||||||
MDETERM
|
MDETERM
|
||||||
MDURATION
|
MDURATION
|
||||||
MEDIAN
|
MEDIAN
|
||||||
|
|
@ -207,6 +232,7 @@ MID
|
||||||
MIDB
|
MIDB
|
||||||
MIN
|
MIN
|
||||||
MINA
|
MINA
|
||||||
|
MINIFS
|
||||||
MINUTE
|
MINUTE
|
||||||
MINVERSE
|
MINVERSE
|
||||||
MIRR
|
MIRR
|
||||||
|
|
@ -229,6 +255,7 @@ NOT
|
||||||
NOW
|
NOW
|
||||||
NPER
|
NPER
|
||||||
NPV
|
NPV
|
||||||
|
NUMBERVALUE
|
||||||
OCT2BIN
|
OCT2BIN
|
||||||
OCT2DEC
|
OCT2DEC
|
||||||
OCT2HEX
|
OCT2HEX
|
||||||
|
|
@ -239,6 +266,7 @@ ODDLPRICE
|
||||||
ODDLYIELD
|
ODDLYIELD
|
||||||
OFFSET
|
OFFSET
|
||||||
OR
|
OR
|
||||||
|
PDURATION
|
||||||
PEARSON
|
PEARSON
|
||||||
PERCENTILE
|
PERCENTILE
|
||||||
PERCENTRANK
|
PERCENTRANK
|
||||||
|
|
@ -275,10 +303,13 @@ ROUNDDOWN
|
||||||
ROUNDUP
|
ROUNDUP
|
||||||
ROW
|
ROW
|
||||||
ROWS
|
ROWS
|
||||||
|
RRI
|
||||||
RSQ
|
RSQ
|
||||||
RTD
|
RTD
|
||||||
SEARCH
|
SEARCH
|
||||||
SEARCHB
|
SEARCHB
|
||||||
|
SEC
|
||||||
|
SECH
|
||||||
SECOND
|
SECOND
|
||||||
SERIESSUM
|
SERIESSUM
|
||||||
SIGN
|
SIGN
|
||||||
|
|
@ -292,6 +323,8 @@ SQRT
|
||||||
SQRTPI
|
SQRTPI
|
||||||
STANDARDIZE
|
STANDARDIZE
|
||||||
STDEV
|
STDEV
|
||||||
|
STDEV.A
|
||||||
|
STDEV.P
|
||||||
STDEVA
|
STDEVA
|
||||||
STDEVP
|
STDEVP
|
||||||
STDEVPA
|
STDEVPA
|
||||||
|
|
@ -306,6 +339,7 @@ SUMSQ
|
||||||
SUMX2MY2
|
SUMX2MY2
|
||||||
SUMX2PY2
|
SUMX2PY2
|
||||||
SUMXMY2
|
SUMXMY2
|
||||||
|
SWITCH
|
||||||
SYD
|
SYD
|
||||||
T
|
T
|
||||||
TAN
|
TAN
|
||||||
|
|
@ -315,6 +349,7 @@ TBILLPRICE
|
||||||
TBILLYIELD
|
TBILLYIELD
|
||||||
TDIST
|
TDIST
|
||||||
TEXT
|
TEXT
|
||||||
|
TEXTJOIN
|
||||||
TIME
|
TIME
|
||||||
TIMEVALUE
|
TIMEVALUE
|
||||||
TINV
|
TINV
|
||||||
|
|
@ -327,6 +362,8 @@ TRUE
|
||||||
TRUNC
|
TRUNC
|
||||||
TTEST
|
TTEST
|
||||||
TYPE
|
TYPE
|
||||||
|
UNICHAR
|
||||||
|
UNIORD
|
||||||
UPPER
|
UPPER
|
||||||
USDOLLAR
|
USDOLLAR
|
||||||
VALUE
|
VALUE
|
||||||
|
|
@ -335,7 +372,6 @@ VARA
|
||||||
VARP
|
VARP
|
||||||
VARPA
|
VARPA
|
||||||
VDB
|
VDB
|
||||||
VERSION
|
|
||||||
VLOOKUP
|
VLOOKUP
|
||||||
WEEKDAY
|
WEEKDAY
|
||||||
WEEKNUM
|
WEEKNUM
|
||||||
|
|
@ -343,6 +379,7 @@ WEIBULL
|
||||||
WORKDAY
|
WORKDAY
|
||||||
XIRR
|
XIRR
|
||||||
XNPV
|
XNPV
|
||||||
|
XOR
|
||||||
YEAR
|
YEAR
|
||||||
YEARFRAC
|
YEARFRAC
|
||||||
YIELD
|
YIELD
|
||||||
27
PhpOffice/PhpSpreadsheet/Calculation/locale/bg/config
Normal file
27
PhpOffice/PhpSpreadsheet/Calculation/locale/bg/config
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = лв
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #ПРАЗНО!
|
||||||
|
DIV0 = #ДЕЛ/0!
|
||||||
|
VALUE = #СТОЙНОСТ!
|
||||||
|
REF = #РЕФ!
|
||||||
|
NAME = #ИМЕ?
|
||||||
|
NUM = #ЧИСЛО!
|
||||||
|
NA = #Н/Д
|
||||||
|
|
@ -1,27 +1,6 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
##
|
||||||
## Data in this file derived from information provided by web-junior (http://www.web-junior.net/)
|
## Data in this file derived from information provided by web-junior (http://www.web-junior.net/)
|
||||||
##
|
##
|
||||||
23
PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config
Normal file
23
PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = Kč
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
##
|
||||||
|
NULL = #NULL!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #HODNOTA!
|
||||||
|
REF = #REF!
|
||||||
|
NAME = #NÁZEV?
|
||||||
|
NUM = #NUM!
|
||||||
|
NA = #N/A
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
25
PhpOffice/PhpSpreadsheet/Calculation/locale/da/config
Normal file
25
PhpOffice/PhpSpreadsheet/Calculation/locale/da/config
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = kr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NUL!
|
||||||
|
DIV0 = #DIVISION/0!
|
||||||
|
VALUE = #VÆRDI!
|
||||||
|
REF = #REFERENCE!
|
||||||
|
NAME = #NAVN?
|
||||||
|
NUM = #NUM!
|
||||||
|
NA = #I/T
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/de/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/de/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NULL!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #WERT!
|
||||||
|
REF = #BEZUG!
|
||||||
|
NAME = #NAME?
|
||||||
|
NUM = #ZAHL!
|
||||||
|
NA = #NV
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
8
PhpOffice/PhpSpreadsheet/Calculation/locale/en/uk/config
Normal file
8
PhpOffice/PhpSpreadsheet/Calculation/locale/en/uk/config
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = £
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/es/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/es/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = $ ## I'm surprised that the Excel Documentation suggests $ rather than €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #¡NULO!
|
||||||
|
DIV0 = #¡DIV/0!
|
||||||
|
VALUE = #¡VALOR!
|
||||||
|
REF = #¡REF!
|
||||||
|
NAME = #¿NOMBRE?
|
||||||
|
NUM = #¡NÚM!
|
||||||
|
NA = #N/A
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = $ # Symbol not known, should it be a € (Euro)?
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #TYHJÄ!
|
||||||
|
DIV0 = #JAKO/0!
|
||||||
|
VALUE = #ARVO!
|
||||||
|
REF = #VIITTAUS!
|
||||||
|
NAME = #NIMI?
|
||||||
|
NUM = #LUKU!
|
||||||
|
NA = #PUUTTUU
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NUL!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #VALEUR!
|
||||||
|
REF = #REF!
|
||||||
|
NAME = #NOM?
|
||||||
|
NUM = #NOMBRE!
|
||||||
|
NA = #N/A
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
23
PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config
Normal file
23
PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = Ft
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
##
|
||||||
|
NULL = #NULLA!
|
||||||
|
DIV0 = #ZÉRÓOSZTÓ!
|
||||||
|
VALUE = #ÉRTÉK!
|
||||||
|
REF = #HIV!
|
||||||
|
NAME = #NÉV?
|
||||||
|
NUM = #SZÁM!
|
||||||
|
NA = #HIÁNYZIK
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/it/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/it/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NULLO!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #VALORE!
|
||||||
|
REF = #RIF!
|
||||||
|
NAME = #NOME?
|
||||||
|
NUM = #NUM!
|
||||||
|
NA = #N/D
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #LEEG!
|
||||||
|
DIV0 = #DEEL/0!
|
||||||
|
VALUE = #WAARDE!
|
||||||
|
REF = #VERW!
|
||||||
|
NAME = #NAAM?
|
||||||
|
NUM = #GETAL!
|
||||||
|
NA = #N/B
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/no/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/no/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = kr
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NULL!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #VERDI!
|
||||||
|
REF = #REF!
|
||||||
|
NAME = #NAVN?
|
||||||
|
NUM = #NUM!
|
||||||
|
NA = #I/T
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = zł
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #ZERO!
|
||||||
|
DIV0 = #DZIEL/0!
|
||||||
|
VALUE = #ARG!
|
||||||
|
REF = #ADR!
|
||||||
|
NAME = #NAZWA?
|
||||||
|
NUM = #LICZBA!
|
||||||
|
NA = #N/D!
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = R$
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NULO!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #VALOR!
|
||||||
|
REF = #REF!
|
||||||
|
NAME = #NOME?
|
||||||
|
NUM = #NÚM!
|
||||||
|
NA = #N/D
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = €
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #NULO!
|
||||||
|
DIV0 = #DIV/0!
|
||||||
|
VALUE = #VALOR!
|
||||||
|
REF = #REF!
|
||||||
|
NAME = #NOME?
|
||||||
|
NUM = #NÚM!
|
||||||
|
NA = #N/D
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = р
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #ПУСТО!
|
||||||
|
DIV0 = #ДЕЛ/0!
|
||||||
|
VALUE = #ЗНАЧ!
|
||||||
|
REF = #ССЫЛ!
|
||||||
|
NAME = #ИМЯ?
|
||||||
|
NUM = #ЧИСЛО!
|
||||||
|
NA = #Н/Д
|
||||||
416
PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions
Normal file
416
PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions
Normal file
|
|
@ -0,0 +1,416 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
## Data in this file derived from information provided by web-junior (http://www.web-junior.net/)
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Add-in and Automation functions Функции надстроек и автоматизации
|
||||||
|
##
|
||||||
|
GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ ## Возвращает данные, хранящиеся в отчете сводной таблицы.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Cube functions Функции Куб
|
||||||
|
##
|
||||||
|
CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП ## Возвращает свойство ключевого индикатора производительности «(КИП)» и отображает имя «КИП» в ячейке. «КИП» представляет собой количественную величину, такую как ежемесячная валовая прибыль или ежеквартальная текучесть кадров, используемой для контроля эффективности работы организации.
|
||||||
|
CUBEMEMBER = КУБЭЛЕМЕНТ ## Возвращает элемент или кортеж из куба. Используется для проверки существования элемента или кортежа в кубе.
|
||||||
|
CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА ## Возвращает значение свойства элемента из куба. Используется для проверки существования имени элемента в кубе и возвращает указанное свойство для этого элемента.
|
||||||
|
CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ ## Возвращает n-ый или ранжированный элемент в множество. Используется для возвращения одного или нескольких элементов в множество, например, лучшего продавца или 10 лучших студентов.
|
||||||
|
CUBESET = КУБМНОЖ ## Определяет вычислительное множество элементов или кортежей, отправляя на сервер выражение, которое создает множество, а затем возвращает его в Microsoft Office Excel.
|
||||||
|
CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ ## Возвращает число элементов множества.
|
||||||
|
CUBEVALUE = КУБЗНАЧЕНИЕ ## Возвращает обобщенное значение из куба.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Database functions Функции для работы с базами данных
|
||||||
|
##
|
||||||
|
DAVERAGE = ДСРЗНАЧ ## Возвращает среднее значение выбранных записей базы данных.
|
||||||
|
DCOUNT = БСЧЁТ ## Подсчитывает количество числовых ячеек в базе данных.
|
||||||
|
DCOUNTA = БСЧЁТА ## Подсчитывает количество непустых ячеек в базе данных.
|
||||||
|
DGET = БИЗВЛЕЧЬ ## Извлекает из базы данных одну запись, удовлетворяющую заданному условию.
|
||||||
|
DMAX = ДМАКС ## Возвращает максимальное значение среди выделенных записей базы данных.
|
||||||
|
DMIN = ДМИН ## Возвращает минимальное значение среди выделенных записей базы данных.
|
||||||
|
DPRODUCT = БДПРОИЗВЕД ## Перемножает значения определенного поля в записях базы данных, удовлетворяющих условию.
|
||||||
|
DSTDEV = ДСТАНДОТКЛ ## Оценивает стандартное отклонение по выборке для выделенных записей базы данных.
|
||||||
|
DSTDEVP = ДСТАНДОТКЛП ## Вычисляет стандартное отклонение по генеральной совокупности для выделенных записей базы данных
|
||||||
|
DSUM = БДСУММ ## Суммирует числа в поле для записей базы данных, удовлетворяющих условию.
|
||||||
|
DVAR = БДДИСП ## Оценивает дисперсию по выборке из выделенных записей базы данных
|
||||||
|
DVARP = БДДИСПП ## Вычисляет дисперсию по генеральной совокупности для выделенных записей базы данных
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Date and time functions Функции даты и времени
|
||||||
|
##
|
||||||
|
DATE = ДАТА ## Возвращает заданную дату в числовом формате.
|
||||||
|
DATEVALUE = ДАТАЗНАЧ ## Преобразует дату из текстового формата в числовой формат.
|
||||||
|
DAY = ДЕНЬ ## Преобразует дату в числовом формате в день месяца.
|
||||||
|
DAYS360 = ДНЕЙ360 ## Вычисляет количество дней между двумя датами на основе 360-дневного года.
|
||||||
|
EDATE = ДАТАМЕС ## Возвращает дату в числовом формате, отстоящую на заданное число месяцев вперед или назад от начальной даты.
|
||||||
|
EOMONTH = КОНМЕСЯЦА ## Возвращает дату в числовом формате для последнего дня месяца, отстоящего вперед или назад на заданное число месяцев.
|
||||||
|
HOUR = ЧАС ## Преобразует дату в числовом формате в часы.
|
||||||
|
MINUTE = МИНУТЫ ## Преобразует дату в числовом формате в минуты.
|
||||||
|
MONTH = МЕСЯЦ ## Преобразует дату в числовом формате в месяцы.
|
||||||
|
NETWORKDAYS = ЧИСТРАБДНИ ## Возвращает количество рабочих дней между двумя датами.
|
||||||
|
NOW = ТДАТА ## Возвращает текущую дату и время в числовом формате.
|
||||||
|
SECOND = СЕКУНДЫ ## Преобразует дату в числовом формате в секунды.
|
||||||
|
TIME = ВРЕМЯ ## Возвращает заданное время в числовом формате.
|
||||||
|
TIMEVALUE = ВРЕМЗНАЧ ## Преобразует время из текстового формата в числовой формат.
|
||||||
|
TODAY = СЕГОДНЯ ## Возвращает текущую дату в числовом формате.
|
||||||
|
WEEKDAY = ДЕНЬНЕД ## Преобразует дату в числовом формате в день недели.
|
||||||
|
WEEKNUM = НОМНЕДЕЛИ ## Преобразует числовое представление в число, которое указывает, на какую неделю года приходится указанная дата.
|
||||||
|
WORKDAY = РАБДЕНЬ ## Возвращает дату в числовом формате, отстоящую вперед или назад на заданное количество рабочих дней.
|
||||||
|
YEAR = ГОД ## Преобразует дату в числовом формате в год.
|
||||||
|
YEARFRAC = ДОЛЯГОДА ## Возвращает долю года, которую составляет количество дней между начальной и конечной датами.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Engineering functions Инженерные функции
|
||||||
|
##
|
||||||
|
BESSELI = БЕССЕЛЬ.I ## Возвращает модифицированную функцию Бесселя In(x).
|
||||||
|
BESSELJ = БЕССЕЛЬ.J ## Возвращает функцию Бесселя Jn(x).
|
||||||
|
BESSELK = БЕССЕЛЬ.K ## Возвращает модифицированную функцию Бесселя Kn(x).
|
||||||
|
BESSELY = БЕССЕЛЬ.Y ## Возвращает функцию Бесселя Yn(x).
|
||||||
|
BIN2DEC = ДВ.В.ДЕС ## Преобразует двоичное число в десятичное.
|
||||||
|
BIN2HEX = ДВ.В.ШЕСТН ## Преобразует двоичное число в шестнадцатеричное.
|
||||||
|
BIN2OCT = ДВ.В.ВОСЬМ ## Преобразует двоичное число в восьмеричное.
|
||||||
|
COMPLEX = КОМПЛЕКСН ## Преобразует коэффициенты при вещественной и мнимой частях комплексного числа в комплексное число.
|
||||||
|
CONVERT = ПРЕОБР ## Преобразует число из одной системы единиц измерения в другую.
|
||||||
|
DEC2BIN = ДЕС.В.ДВ ## Преобразует десятичное число в двоичное.
|
||||||
|
DEC2HEX = ДЕС.В.ШЕСТН ## Преобразует десятичное число в шестнадцатеричное.
|
||||||
|
DEC2OCT = ДЕС.В.ВОСЬМ ## Преобразует десятичное число в восьмеричное.
|
||||||
|
DELTA = ДЕЛЬТА ## Проверяет равенство двух значений.
|
||||||
|
ERF = ФОШ ## Возвращает функцию ошибки.
|
||||||
|
ERFC = ДФОШ ## Возвращает дополнительную функцию ошибки.
|
||||||
|
GESTEP = ПОРОГ ## Проверяет, не превышает ли данное число порогового значения.
|
||||||
|
HEX2BIN = ШЕСТН.В.ДВ ## Преобразует шестнадцатеричное число в двоичное.
|
||||||
|
HEX2DEC = ШЕСТН.В.ДЕС ## Преобразует шестнадцатеричное число в десятичное.
|
||||||
|
HEX2OCT = ШЕСТН.В.ВОСЬМ ## Преобразует шестнадцатеричное число в восьмеричное.
|
||||||
|
IMABS = МНИМ.ABS ## Возвращает абсолютную величину (модуль) комплексного числа.
|
||||||
|
IMAGINARY = МНИМ.ЧАСТЬ ## Возвращает коэффициент при мнимой части комплексного числа.
|
||||||
|
IMARGUMENT = МНИМ.АРГУМЕНТ ## Возвращает значение аргумента комплексного числа (тета) — угол, выраженный в радианах.
|
||||||
|
IMCONJUGATE = МНИМ.СОПРЯЖ ## Возвращает комплексно-сопряженное комплексное число.
|
||||||
|
IMCOS = МНИМ.COS ## Возвращает косинус комплексного числа.
|
||||||
|
IMDIV = МНИМ.ДЕЛ ## Возвращает частное от деления двух комплексных чисел.
|
||||||
|
IMEXP = МНИМ.EXP ## Возвращает экспоненту комплексного числа.
|
||||||
|
IMLN = МНИМ.LN ## Возвращает натуральный логарифм комплексного числа.
|
||||||
|
IMLOG10 = МНИМ.LOG10 ## Возвращает обычный (десятичный) логарифм комплексного числа.
|
||||||
|
IMLOG2 = МНИМ.LOG2 ## Возвращает двоичный логарифм комплексного числа.
|
||||||
|
IMPOWER = МНИМ.СТЕПЕНЬ ## Возвращает комплексное число, возведенное в целую степень.
|
||||||
|
IMPRODUCT = МНИМ.ПРОИЗВЕД ## Возвращает произведение от 2 до 29 комплексных чисел.
|
||||||
|
IMREAL = МНИМ.ВЕЩ ## Возвращает коэффициент при вещественной части комплексного числа.
|
||||||
|
IMSIN = МНИМ.SIN ## Возвращает синус комплексного числа.
|
||||||
|
IMSQRT = МНИМ.КОРЕНЬ ## Возвращает значение квадратного корня из комплексного числа.
|
||||||
|
IMSUB = МНИМ.РАЗН ## Возвращает разность двух комплексных чисел.
|
||||||
|
IMSUM = МНИМ.СУММ ## Возвращает сумму комплексных чисел.
|
||||||
|
OCT2BIN = ВОСЬМ.В.ДВ ## Преобразует восьмеричное число в двоичное.
|
||||||
|
OCT2DEC = ВОСЬМ.В.ДЕС ## Преобразует восьмеричное число в десятичное.
|
||||||
|
OCT2HEX = ВОСЬМ.В.ШЕСТН ## Преобразует восьмеричное число в шестнадцатеричное.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Financial functions Финансовые функции
|
||||||
|
##
|
||||||
|
ACCRINT = НАКОПДОХОД ## Возвращает накопленный процент по ценным бумагам с периодической выплатой процентов.
|
||||||
|
ACCRINTM = НАКОПДОХОДПОГАШ ## Возвращает накопленный процент по ценным бумагам, проценты по которым выплачиваются в срок погашения.
|
||||||
|
AMORDEGRC = АМОРУМ ## Возвращает величину амортизации для каждого периода, используя коэффициент амортизации.
|
||||||
|
AMORLINC = АМОРУВ ## Возвращает величину амортизации для каждого периода.
|
||||||
|
COUPDAYBS = ДНЕЙКУПОНДО ## Возвращает количество дней от начала действия купона до даты соглашения.
|
||||||
|
COUPDAYS = ДНЕЙКУПОН ## Возвращает число дней в периоде купона, содержащем дату соглашения.
|
||||||
|
COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ ## Возвращает число дней от даты соглашения до срока следующего купона.
|
||||||
|
COUPNCD = ДАТАКУПОНПОСЛЕ ## Возвращает следующую дату купона после даты соглашения.
|
||||||
|
COUPNUM = ЧИСЛКУПОН ## Возвращает количество купонов, которые могут быть оплачены между датой соглашения и сроком вступления в силу.
|
||||||
|
COUPPCD = ДАТАКУПОНДО ## Возвращает предыдущую дату купона перед датой соглашения.
|
||||||
|
CUMIPMT = ОБЩПЛАТ ## Возвращает общую выплату, произведенную между двумя периодическими выплатами.
|
||||||
|
CUMPRINC = ОБЩДОХОД ## Возвращает общую выплату по займу между двумя периодами.
|
||||||
|
DB = ФУО ## Возвращает величину амортизации актива для заданного периода, рассчитанную методом фиксированного уменьшения остатка.
|
||||||
|
DDB = ДДОБ ## Возвращает величину амортизации актива за данный период, используя метод двойного уменьшения остатка или иной явно указанный метод.
|
||||||
|
DISC = СКИДКА ## Возвращает норму скидки для ценных бумаг.
|
||||||
|
DOLLARDE = РУБЛЬ.ДЕС ## Преобразует цену в рублях, выраженную в виде дроби, в цену в рублях, выраженную десятичным числом.
|
||||||
|
DOLLARFR = РУБЛЬ.ДРОБЬ ## Преобразует цену в рублях, выраженную десятичным числом, в цену в рублях, выраженную в виде дроби.
|
||||||
|
DURATION = ДЛИТ ## Возвращает ежегодную продолжительность действия ценных бумаг с периодическими выплатами по процентам.
|
||||||
|
EFFECT = ЭФФЕКТ ## Возвращает действующие ежегодные процентные ставки.
|
||||||
|
FV = БС ## Возвращает будущую стоимость инвестиции.
|
||||||
|
FVSCHEDULE = БЗРАСПИС ## Возвращает будущую стоимость первоначальной основной суммы после начисления ряда сложных процентов.
|
||||||
|
INTRATE = ИНОРМА ## Возвращает процентную ставку для полностью инвестированных ценных бумаг.
|
||||||
|
IPMT = ПРПЛТ ## Возвращает величину выплаты прибыли на вложения за данный период.
|
||||||
|
IRR = ВСД ## Возвращает внутреннюю ставку доходности для ряда потоков денежных средств.
|
||||||
|
ISPMT = ПРОЦПЛАТ ## Вычисляет выплаты за указанный период инвестиции.
|
||||||
|
MDURATION = МДЛИТ ## Возвращает модифицированную длительность Маколея для ценных бумаг с предполагаемой номинальной стоимостью 100 рублей.
|
||||||
|
MIRR = МВСД ## Возвращает внутреннюю ставку доходности, при которой положительные и отрицательные денежные потоки имеют разные значения ставки.
|
||||||
|
NOMINAL = НОМИНАЛ ## Возвращает номинальную годовую процентную ставку.
|
||||||
|
NPER = КПЕР ## Возвращает общее количество периодов выплаты для данного вклада.
|
||||||
|
NPV = ЧПС ## Возвращает чистую приведенную стоимость инвестиции, основанной на серии периодических денежных потоков и ставке дисконтирования.
|
||||||
|
ODDFPRICE = ЦЕНАПЕРВНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным первым периодом.
|
||||||
|
ODDFYIELD = ДОХОДПЕРВНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным первым периодом.
|
||||||
|
ODDLPRICE = ЦЕНАПОСЛНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным последним периодом.
|
||||||
|
ODDLYIELD = ДОХОДПОСЛНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным последним периодом.
|
||||||
|
PMT = ПЛТ ## Возвращает величину выплаты за один период аннуитета.
|
||||||
|
PPMT = ОСПЛТ ## Возвращает величину выплат в погашение основной суммы по инвестиции за заданный период.
|
||||||
|
PRICE = ЦЕНА ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг, по которым производится периодическая выплата процентов.
|
||||||
|
PRICEDISC = ЦЕНАСКИДКА ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, на которые сделана скидка.
|
||||||
|
PRICEMAT = ЦЕНАПОГАШ ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, проценты по которым выплачиваются в срок погашения.
|
||||||
|
PV = ПС ## Возвращает приведенную (к текущему моменту) стоимость инвестиции.
|
||||||
|
RATE = СТАВКА ## Возвращает процентную ставку по аннуитету за один период.
|
||||||
|
RECEIVED = ПОЛУЧЕНО ## Возвращает сумму, полученную к сроку погашения полностью обеспеченных ценных бумаг.
|
||||||
|
SLN = АПЛ ## Возвращает величину линейной амортизации актива за один период.
|
||||||
|
SYD = АСЧ ## Возвращает величину амортизации актива за данный период, рассчитанную методом суммы годовых чисел.
|
||||||
|
TBILLEQ = РАВНОКЧЕК ## Возвращает эквивалентный облигации доход по казначейскому чеку.
|
||||||
|
TBILLPRICE = ЦЕНАКЧЕК ## Возвращает цену за 100 рублей нарицательной стоимости для казначейского чека.
|
||||||
|
TBILLYIELD = ДОХОДКЧЕК ## Возвращает доход по казначейскому чеку.
|
||||||
|
VDB = ПУО ## Возвращает величину амортизации актива для указанного или частичного периода при использовании метода сокращающегося баланса.
|
||||||
|
XIRR = ЧИСТВНДОХ ## Возвращает внутреннюю ставку доходности для графика денежных потоков, которые не обязательно носят периодический характер.
|
||||||
|
XNPV = ЧИСТНЗ ## Возвращает чистую приведенную стоимость для денежных потоков, которые не обязательно являются периодическими.
|
||||||
|
YIELD = ДОХОД ## Возвращает доход от ценных бумаг, по которым производятся периодические выплаты процентов.
|
||||||
|
YIELDDISC = ДОХОДСКИДКА ## Возвращает годовой доход по ценным бумагам, на которые сделана скидка (пример — казначейские чеки).
|
||||||
|
YIELDMAT = ДОХОДПОГАШ ## Возвращает годовой доход от ценных бумаг, проценты по которым выплачиваются в срок погашения.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Information functions Информационные функции
|
||||||
|
##
|
||||||
|
CELL = ЯЧЕЙКА ## Возвращает информацию о формате, расположении или содержимом ячейки.
|
||||||
|
ERROR.TYPE = ТИП.ОШИБКИ ## Возвращает числовой код, соответствующий типу ошибки.
|
||||||
|
INFO = ИНФОРМ ## Возвращает информацию о текущей операционной среде.
|
||||||
|
ISBLANK = ЕПУСТО ## Возвращает значение ИСТИНА, если аргумент является ссылкой на пустую ячейку.
|
||||||
|
ISERR = ЕОШ ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки, кроме #Н/Д.
|
||||||
|
ISERROR = ЕОШИБКА ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки.
|
||||||
|
ISEVEN = ЕЧЁТН ## Возвращает значение ИСТИНА, если значение аргумента является четным числом.
|
||||||
|
ISLOGICAL = ЕЛОГИЧ ## Возвращает значение ИСТИНА, если аргумент ссылается на логическое значение.
|
||||||
|
ISNA = ЕНД ## Возвращает значение ИСТИНА, если аргумент ссылается на значение ошибки #Н/Д.
|
||||||
|
ISNONTEXT = ЕНЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента не является текстом.
|
||||||
|
ISNUMBER = ЕЧИСЛО ## Возвращает значение ИСТИНА, если аргумент ссылается на число.
|
||||||
|
ISODD = ЕНЕЧЁТ ## Возвращает значение ИСТИНА, если значение аргумента является нечетным числом.
|
||||||
|
ISREF = ЕССЫЛКА ## Возвращает значение ИСТИНА, если значение аргумента является ссылкой.
|
||||||
|
ISTEXT = ЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента является текстом.
|
||||||
|
N = Ч ## Возвращает значение, преобразованное в число.
|
||||||
|
NA = НД ## Возвращает значение ошибки #Н/Д.
|
||||||
|
TYPE = ТИП ## Возвращает число, обозначающее тип данных значения.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Logical functions Логические функции
|
||||||
|
##
|
||||||
|
AND = И ## Renvoie VRAI si tous ses arguments sont VRAI.
|
||||||
|
FALSE = ЛОЖЬ ## Возвращает логическое значение ЛОЖЬ.
|
||||||
|
IF = ЕСЛИ ## Выполняет проверку условия.
|
||||||
|
IFERROR = ЕСЛИОШИБКА ## Возвращает введённое значение, если вычисление по формуле вызывает ошибку; в противном случае функция возвращает результат вычисления.
|
||||||
|
NOT = НЕ ## Меняет логическое значение своего аргумента на противоположное.
|
||||||
|
OR = ИЛИ ## Возвращает значение ИСТИНА, если хотя бы один аргумент имеет значение ИСТИНА.
|
||||||
|
TRUE = ИСТИНА ## Возвращает логическое значение ИСТИНА.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Lookup and reference functions Функции ссылки и поиска
|
||||||
|
##
|
||||||
|
ADDRESS = АДРЕС ## Возвращает ссылку на отдельную ячейку листа в виде текста.
|
||||||
|
AREAS = ОБЛАСТИ ## Возвращает количество областей в ссылке.
|
||||||
|
CHOOSE = ВЫБОР ## Выбирает значение из списка значений по индексу.
|
||||||
|
COLUMN = СТОЛБЕЦ ## Возвращает номер столбца, на который указывает ссылка.
|
||||||
|
COLUMNS = ЧИСЛСТОЛБ ## Возвращает количество столбцов в ссылке.
|
||||||
|
HLOOKUP = ГПР ## Ищет в первой строке массива и возвращает значение отмеченной ячейки
|
||||||
|
HYPERLINK = ГИПЕРССЫЛКА ## Создает ссылку, открывающую документ, который находится на сервере сети, в интрасети или в Интернете.
|
||||||
|
INDEX = ИНДЕКС ## Использует индекс для выбора значения из ссылки или массива.
|
||||||
|
INDIRECT = ДВССЫЛ ## Возвращает ссылку, заданную текстовым значением.
|
||||||
|
LOOKUP = ПРОСМОТР ## Ищет значения в векторе или массиве.
|
||||||
|
MATCH = ПОИСКПОЗ ## Ищет значения в ссылке или массиве.
|
||||||
|
OFFSET = СМЕЩ ## Возвращает смещение ссылки относительно заданной ссылки.
|
||||||
|
ROW = СТРОКА ## Возвращает номер строки, определяемой ссылкой.
|
||||||
|
ROWS = ЧСТРОК ## Возвращает количество строк в ссылке.
|
||||||
|
RTD = ДРВ ## Извлекает данные реального времени из программ, поддерживающих автоматизацию COM (Программирование объектов. Стандартное средство для работы с объектами некоторого приложения из другого приложения или средства разработки. Программирование объектов (ранее называемое программированием OLE) является функцией модели COM (Component Object Model, модель компонентных объектов).).
|
||||||
|
TRANSPOSE = ТРАНСП ## Возвращает транспонированный массив.
|
||||||
|
VLOOKUP = ВПР ## Ищет значение в первом столбце массива и возвращает значение из ячейки в найденной строке и указанном столбце.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Math and trigonometry functions Математические и тригонометрические функции
|
||||||
|
##
|
||||||
|
ABS = ABS ## Возвращает модуль (абсолютную величину) числа.
|
||||||
|
ACOS = ACOS ## Возвращает арккосинус числа.
|
||||||
|
ACOSH = ACOSH ## Возвращает гиперболический арккосинус числа.
|
||||||
|
ASIN = ASIN ## Возвращает арксинус числа.
|
||||||
|
ASINH = ASINH ## Возвращает гиперболический арксинус числа.
|
||||||
|
ATAN = ATAN ## Возвращает арктангенс числа.
|
||||||
|
ATAN2 = ATAN2 ## Возвращает арктангенс для заданных координат x и y.
|
||||||
|
ATANH = ATANH ## Возвращает гиперболический арктангенс числа.
|
||||||
|
CEILING = ОКРВВЕРХ ## Округляет число до ближайшего целого или до ближайшего кратного указанному значению.
|
||||||
|
COMBIN = ЧИСЛКОМБ ## Возвращает количество комбинаций для заданного числа объектов.
|
||||||
|
COS = COS ## Возвращает косинус числа.
|
||||||
|
COSH = COSH ## Возвращает гиперболический косинус числа.
|
||||||
|
DEGREES = ГРАДУСЫ ## Преобразует радианы в градусы.
|
||||||
|
EVEN = ЧЁТН ## Округляет число до ближайшего четного целого.
|
||||||
|
EXP = EXP ## Возвращает число e, возведенное в указанную степень.
|
||||||
|
FACT = ФАКТР ## Возвращает факториал числа.
|
||||||
|
FACTDOUBLE = ДВФАКТР ## Возвращает двойной факториал числа.
|
||||||
|
FLOOR = ОКРВНИЗ ## Округляет число до ближайшего меньшего по модулю значения.
|
||||||
|
GCD = НОД ## Возвращает наибольший общий делитель.
|
||||||
|
INT = ЦЕЛОЕ ## Округляет число до ближайшего меньшего целого.
|
||||||
|
LCM = НОК ## Возвращает наименьшее общее кратное.
|
||||||
|
LN = LN ## Возвращает натуральный логарифм числа.
|
||||||
|
LOG = LOG ## Возвращает логарифм числа по заданному основанию.
|
||||||
|
LOG10 = LOG10 ## Возвращает десятичный логарифм числа.
|
||||||
|
MDETERM = МОПРЕД ## Возвращает определитель матрицы массива.
|
||||||
|
MINVERSE = МОБР ## Возвращает обратную матрицу массива.
|
||||||
|
MMULT = МУМНОЖ ## Возвращает произведение матриц двух массивов.
|
||||||
|
MOD = ОСТАТ ## Возвращает остаток от деления.
|
||||||
|
MROUND = ОКРУГЛТ ## Возвращает число, округленное с требуемой точностью.
|
||||||
|
MULTINOMIAL = МУЛЬТИНОМ ## Возвращает мультиномиальный коэффициент множества чисел.
|
||||||
|
ODD = НЕЧЁТ ## Округляет число до ближайшего нечетного целого.
|
||||||
|
PI = ПИ ## Возвращает число пи.
|
||||||
|
POWER = СТЕПЕНЬ ## Возвращает результат возведения числа в степень.
|
||||||
|
PRODUCT = ПРОИЗВЕД ## Возвращает произведение аргументов.
|
||||||
|
QUOTIENT = ЧАСТНОЕ ## Возвращает целую часть частного при делении.
|
||||||
|
RADIANS = РАДИАНЫ ## Преобразует градусы в радианы.
|
||||||
|
RAND = СЛЧИС ## Возвращает случайное число в интервале от 0 до 1.
|
||||||
|
RANDBETWEEN = СЛУЧМЕЖДУ ## Возвращает случайное число в интервале между двумя заданными числами.
|
||||||
|
ROMAN = РИМСКОЕ ## Преобразует арабские цифры в римские в виде текста.
|
||||||
|
ROUND = ОКРУГЛ ## Округляет число до указанного количества десятичных разрядов.
|
||||||
|
ROUNDDOWN = ОКРУГЛВНИЗ ## Округляет число до ближайшего меньшего по модулю значения.
|
||||||
|
ROUNDUP = ОКРУГЛВВЕРХ ## Округляет число до ближайшего большего по модулю значения.
|
||||||
|
SERIESSUM = РЯД.СУММ ## Возвращает сумму степенного ряда, вычисленную по формуле.
|
||||||
|
SIGN = ЗНАК ## Возвращает знак числа.
|
||||||
|
SIN = SIN ## Возвращает синус заданного угла.
|
||||||
|
SINH = SINH ## Возвращает гиперболический синус числа.
|
||||||
|
SQRT = КОРЕНЬ ## Возвращает положительное значение квадратного корня.
|
||||||
|
SQRTPI = КОРЕНЬПИ ## Возвращает квадратный корень из значения выражения (число * ПИ).
|
||||||
|
SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ ## Возвращает промежуточный итог в списке или базе данных.
|
||||||
|
SUM = СУММ ## Суммирует аргументы.
|
||||||
|
SUMIF = СУММЕСЛИ ## Суммирует ячейки, удовлетворяющие заданному условию.
|
||||||
|
SUMIFS = СУММЕСЛИМН ## Суммирует диапазон ячеек, удовлетворяющих нескольким условиям.
|
||||||
|
SUMPRODUCT = СУММПРОИЗВ ## Возвращает сумму произведений соответствующих элементов массивов.
|
||||||
|
SUMSQ = СУММКВ ## Возвращает сумму квадратов аргументов.
|
||||||
|
SUMX2MY2 = СУММРАЗНКВ ## Возвращает сумму разностей квадратов соответствующих значений в двух массивах.
|
||||||
|
SUMX2PY2 = СУММСУММКВ ## Возвращает сумму сумм квадратов соответствующих элементов двух массивов.
|
||||||
|
SUMXMY2 = СУММКВРАЗН ## Возвращает сумму квадратов разностей соответствующих значений в двух массивах.
|
||||||
|
TAN = TAN ## Возвращает тангенс числа.
|
||||||
|
TANH = TANH ## Возвращает гиперболический тангенс числа.
|
||||||
|
TRUNC = ОТБР ## Отбрасывает дробную часть числа.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Statistical functions Статистические функции
|
||||||
|
##
|
||||||
|
AVEDEV = СРОТКЛ ## Возвращает среднее арифметическое абсолютных значений отклонений точек данных от среднего.
|
||||||
|
AVERAGE = СРЗНАЧ ## Возвращает среднее арифметическое аргументов.
|
||||||
|
AVERAGEA = СРЗНАЧА ## Возвращает среднее арифметическое аргументов, включая числа, текст и логические значения.
|
||||||
|
AVERAGEIF = СРЗНАЧЕСЛИ ## Возвращает среднее значение (среднее арифметическое) всех ячеек в диапазоне, которые удовлетворяют данному условию.
|
||||||
|
AVERAGEIFS = СРЗНАЧЕСЛИМН ## Возвращает среднее значение (среднее арифметическое) всех ячеек, которые удовлетворяют нескольким условиям.
|
||||||
|
BETADIST = БЕТАРАСП ## Возвращает интегральную функцию бета-распределения.
|
||||||
|
BETAINV = БЕТАОБР ## Возвращает обратную интегральную функцию указанного бета-распределения.
|
||||||
|
BINOMDIST = БИНОМРАСП ## Возвращает отдельное значение биномиального распределения.
|
||||||
|
CHIDIST = ХИ2РАСП ## Возвращает одностороннюю вероятность распределения хи-квадрат.
|
||||||
|
CHIINV = ХИ2ОБР ## Возвращает обратное значение односторонней вероятности распределения хи-квадрат.
|
||||||
|
CHITEST = ХИ2ТЕСТ ## Возвращает тест на независимость.
|
||||||
|
CONFIDENCE = ДОВЕРИТ ## Возвращает доверительный интервал для среднего значения по генеральной совокупности.
|
||||||
|
CORREL = КОРРЕЛ ## Возвращает коэффициент корреляции между двумя множествами данных.
|
||||||
|
COUNT = СЧЁТ ## Подсчитывает количество чисел в списке аргументов.
|
||||||
|
COUNTA = СЧЁТЗ ## Подсчитывает количество значений в списке аргументов.
|
||||||
|
COUNTBLANK = СЧИТАТЬПУСТОТЫ ## Подсчитывает количество пустых ячеек в диапазоне
|
||||||
|
COUNTIF = СЧЁТЕСЛИ ## Подсчитывает количество ячеек в диапазоне, удовлетворяющих заданному условию
|
||||||
|
COUNTIFS = СЧЁТЕСЛИМН ## Подсчитывает количество ячеек внутри диапазона, удовлетворяющих нескольким условиям.
|
||||||
|
COVAR = КОВАР ## Возвращает ковариацию, среднее произведений парных отклонений
|
||||||
|
CRITBINOM = КРИТБИНОМ ## Возвращает наименьшее значение, для которого интегральное биномиальное распределение меньше или равно заданному критерию.
|
||||||
|
DEVSQ = КВАДРОТКЛ ## Возвращает сумму квадратов отклонений.
|
||||||
|
EXPONDIST = ЭКСПРАСП ## Возвращает экспоненциальное распределение.
|
||||||
|
FDIST = FРАСП ## Возвращает F-распределение вероятности.
|
||||||
|
FINV = FРАСПОБР ## Возвращает обратное значение для F-распределения вероятности.
|
||||||
|
FISHER = ФИШЕР ## Возвращает преобразование Фишера.
|
||||||
|
FISHERINV = ФИШЕРОБР ## Возвращает обратное преобразование Фишера.
|
||||||
|
FORECAST = ПРЕДСКАЗ ## Возвращает значение линейного тренда.
|
||||||
|
FREQUENCY = ЧАСТОТА ## Возвращает распределение частот в виде вертикального массива.
|
||||||
|
FTEST = ФТЕСТ ## Возвращает результат F-теста.
|
||||||
|
GAMMADIST = ГАММАРАСП ## Возвращает гамма-распределение.
|
||||||
|
GAMMAINV = ГАММАОБР ## Возвращает обратное гамма-распределение.
|
||||||
|
GAMMALN = ГАММАНЛОГ ## Возвращает натуральный логарифм гамма функции, Γ(x).
|
||||||
|
GEOMEAN = СРГЕОМ ## Возвращает среднее геометрическое.
|
||||||
|
GROWTH = РОСТ ## Возвращает значения в соответствии с экспоненциальным трендом.
|
||||||
|
HARMEAN = СРГАРМ ## Возвращает среднее гармоническое.
|
||||||
|
HYPGEOMDIST = ГИПЕРГЕОМЕТ ## Возвращает гипергеометрическое распределение.
|
||||||
|
INTERCEPT = ОТРЕЗОК ## Возвращает отрезок, отсекаемый на оси линией линейной регрессии.
|
||||||
|
KURT = ЭКСЦЕСС ## Возвращает эксцесс множества данных.
|
||||||
|
LARGE = НАИБОЛЬШИЙ ## Возвращает k-ое наибольшее значение в множестве данных.
|
||||||
|
LINEST = ЛИНЕЙН ## Возвращает параметры линейного тренда.
|
||||||
|
LOGEST = ЛГРФПРИБЛ ## Возвращает параметры экспоненциального тренда.
|
||||||
|
LOGINV = ЛОГНОРМОБР ## Возвращает обратное логарифмическое нормальное распределение.
|
||||||
|
LOGNORMDIST = ЛОГНОРМРАСП ## Возвращает интегральное логарифмическое нормальное распределение.
|
||||||
|
MAX = МАКС ## Возвращает наибольшее значение в списке аргументов.
|
||||||
|
MAXA = МАКСА ## Возвращает наибольшее значение в списке аргументов, включая числа, текст и логические значения.
|
||||||
|
MEDIAN = МЕДИАНА ## Возвращает медиану заданных чисел.
|
||||||
|
MIN = МИН ## Возвращает наименьшее значение в списке аргументов.
|
||||||
|
MINA = МИНА ## Возвращает наименьшее значение в списке аргументов, включая числа, текст и логические значения.
|
||||||
|
MODE = МОДА ## Возвращает значение моды множества данных.
|
||||||
|
NEGBINOMDIST = ОТРБИНОМРАСП ## Возвращает отрицательное биномиальное распределение.
|
||||||
|
NORMDIST = НОРМРАСП ## Возвращает нормальную функцию распределения.
|
||||||
|
NORMINV = НОРМОБР ## Возвращает обратное нормальное распределение.
|
||||||
|
NORMSDIST = НОРМСТРАСП ## Возвращает стандартное нормальное интегральное распределение.
|
||||||
|
NORMSINV = НОРМСТОБР ## Возвращает обратное значение стандартного нормального распределения.
|
||||||
|
PEARSON = ПИРСОН ## Возвращает коэффициент корреляции Пирсона.
|
||||||
|
PERCENTILE = ПЕРСЕНТИЛЬ ## Возвращает k-ую персентиль для значений диапазона.
|
||||||
|
PERCENTRANK = ПРОЦЕНТРАНГ ## Возвращает процентную норму значения в множестве данных.
|
||||||
|
PERMUT = ПЕРЕСТ ## Возвращает количество перестановок для заданного числа объектов.
|
||||||
|
POISSON = ПУАССОН ## Возвращает распределение Пуассона.
|
||||||
|
PROB = ВЕРОЯТНОСТЬ ## Возвращает вероятность того, что значение из диапазона находится внутри заданных пределов.
|
||||||
|
QUARTILE = КВАРТИЛЬ ## Возвращает квартиль множества данных.
|
||||||
|
RANK = РАНГ ## Возвращает ранг числа в списке чисел.
|
||||||
|
RSQ = КВПИРСОН ## Возвращает квадрат коэффициента корреляции Пирсона.
|
||||||
|
SKEW = СКОС ## Возвращает асимметрию распределения.
|
||||||
|
SLOPE = НАКЛОН ## Возвращает наклон линии линейной регрессии.
|
||||||
|
SMALL = НАИМЕНЬШИЙ ## Возвращает k-ое наименьшее значение в множестве данных.
|
||||||
|
STANDARDIZE = НОРМАЛИЗАЦИЯ ## Возвращает нормализованное значение.
|
||||||
|
STDEV = СТАНДОТКЛОН ## Оценивает стандартное отклонение по выборке.
|
||||||
|
STDEVA = СТАНДОТКЛОНА ## Оценивает стандартное отклонение по выборке, включая числа, текст и логические значения.
|
||||||
|
STDEVP = СТАНДОТКЛОНП ## Вычисляет стандартное отклонение по генеральной совокупности.
|
||||||
|
STDEVPA = СТАНДОТКЛОНПА ## Вычисляет стандартное отклонение по генеральной совокупности, включая числа, текст и логические значения.
|
||||||
|
STEYX = СТОШYX ## Возвращает стандартную ошибку предсказанных значений y для каждого значения x в регрессии.
|
||||||
|
TDIST = СТЬЮДРАСП ## Возвращает t-распределение Стьюдента.
|
||||||
|
TINV = СТЬЮДРАСПОБР ## Возвращает обратное t-распределение Стьюдента.
|
||||||
|
TREND = ТЕНДЕНЦИЯ ## Возвращает значения в соответствии с линейным трендом.
|
||||||
|
TRIMMEAN = УРЕЗСРЕДНЕЕ ## Возвращает среднее внутренности множества данных.
|
||||||
|
TTEST = ТТЕСТ ## Возвращает вероятность, соответствующую критерию Стьюдента.
|
||||||
|
VAR = ДИСП ## Оценивает дисперсию по выборке.
|
||||||
|
VARA = ДИСПА ## Оценивает дисперсию по выборке, включая числа, текст и логические значения.
|
||||||
|
VARP = ДИСПР ## Вычисляет дисперсию для генеральной совокупности.
|
||||||
|
VARPA = ДИСПРА ## Вычисляет дисперсию для генеральной совокупности, включая числа, текст и логические значения.
|
||||||
|
WEIBULL = ВЕЙБУЛЛ ## Возвращает распределение Вейбулла.
|
||||||
|
ZTEST = ZТЕСТ ## Возвращает двустороннее P-значение z-теста.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Text functions Текстовые функции
|
||||||
|
##
|
||||||
|
ASC = ASC ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полноширинные (двухбайтовые) знаки в полуширинные (однобайтовые).
|
||||||
|
BAHTTEXT = БАТТЕКСТ ## Преобразует число в текст, используя денежный формат ß (БАТ).
|
||||||
|
CHAR = СИМВОЛ ## Возвращает знак с заданным кодом.
|
||||||
|
CLEAN = ПЕЧСИМВ ## Удаляет все непечатаемые знаки из текста.
|
||||||
|
CODE = КОДСИМВ ## Возвращает числовой код первого знака в текстовой строке.
|
||||||
|
CONCATENATE = СЦЕПИТЬ ## Объединяет несколько текстовых элементов в один.
|
||||||
|
DOLLAR = РУБЛЬ ## Преобразует число в текст, используя денежный формат.
|
||||||
|
EXACT = СОВПАД ## Проверяет идентичность двух текстовых значений.
|
||||||
|
FIND = НАЙТИ ## Ищет вхождения одного текстового значения в другом (с учетом регистра).
|
||||||
|
FINDB = НАЙТИБ ## Ищет вхождения одного текстового значения в другом (с учетом регистра).
|
||||||
|
FIXED = ФИКСИРОВАННЫЙ ## Форматирует число и преобразует его в текст с заданным числом десятичных знаков.
|
||||||
|
JIS = JIS ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полуширинные (однобайтовые) знаки в текстовой строке в полноширинные (двухбайтовые).
|
||||||
|
LEFT = ЛЕВСИМВ ## Возвращает крайние слева знаки текстового значения.
|
||||||
|
LEFTB = ЛЕВБ ## Возвращает крайние слева знаки текстового значения.
|
||||||
|
LEN = ДЛСТР ## Возвращает количество знаков в текстовой строке.
|
||||||
|
LENB = ДЛИНБ ## Возвращает количество знаков в текстовой строке.
|
||||||
|
LOWER = СТРОЧН ## Преобразует все буквы текста в строчные.
|
||||||
|
MID = ПСТР ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции.
|
||||||
|
MIDB = ПСТРБ ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции.
|
||||||
|
PHONETIC = PHONETIC ## Извлекает фонетические (фуригана) знаки из текстовой строки.
|
||||||
|
PROPER = ПРОПНАЧ ## Преобразует первую букву в каждом слове текста в прописную.
|
||||||
|
REPLACE = ЗАМЕНИТЬ ## Заменяет знаки в тексте.
|
||||||
|
REPLACEB = ЗАМЕНИТЬБ ## Заменяет знаки в тексте.
|
||||||
|
REPT = ПОВТОР ## Повторяет текст заданное число раз.
|
||||||
|
RIGHT = ПРАВСИМВ ## Возвращает крайние справа знаки текстовой строки.
|
||||||
|
RIGHTB = ПРАВБ ## Возвращает крайние справа знаки текстовой строки.
|
||||||
|
SEARCH = ПОИСК ## Ищет вхождения одного текстового значения в другом (без учета регистра).
|
||||||
|
SEARCHB = ПОИСКБ ## Ищет вхождения одного текстового значения в другом (без учета регистра).
|
||||||
|
SUBSTITUTE = ПОДСТАВИТЬ ## Заменяет в текстовой строке старый текст новым.
|
||||||
|
T = Т ## Преобразует аргументы в текст.
|
||||||
|
TEXT = ТЕКСТ ## Форматирует число и преобразует его в текст.
|
||||||
|
TRIM = СЖПРОБЕЛЫ ## Удаляет из текста пробелы.
|
||||||
|
UPPER = ПРОПИСН ## Преобразует все буквы текста в прописные.
|
||||||
|
VALUE = ЗНАЧЕН ## Преобразует текстовый аргумент в число.
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = kr
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #Skärning!
|
||||||
|
DIV0 = #Division/0!
|
||||||
|
VALUE = #Värdefel!
|
||||||
|
REF = #Referens!
|
||||||
|
NAME = #Namn?
|
||||||
|
NUM = #Ogiltigt!
|
||||||
|
NA = #Saknas!
|
||||||
24
PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config
Normal file
24
PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
##
|
||||||
|
## PhpSpreadsheet
|
||||||
|
##
|
||||||
|
|
||||||
|
ArgumentSeparator = ;
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## (For future use)
|
||||||
|
##
|
||||||
|
currencySymbol = YTL
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Excel Error Codes (For future use)
|
||||||
|
|
||||||
|
##
|
||||||
|
NULL = #BOŞ!
|
||||||
|
DIV0 = #SAYI/0!
|
||||||
|
VALUE = #DEĞER!
|
||||||
|
REF = #BAŞV!
|
||||||
|
NAME = #AD?
|
||||||
|
NUM = #SAYI!
|
||||||
|
NA = #YOK
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
##
|
##
|
||||||
## PHPExcel
|
## PhpSpreadsheet
|
||||||
##
|
##
|
||||||
## Copyright (c) 2006 - 2013 PHPExcel
|
## Data in this file derived from https://www.excel-function-translation.com/
|
||||||
##
|
|
||||||
## This library is free software; you can redistribute it and/or
|
|
||||||
## modify it under the terms of the GNU Lesser General Public
|
|
||||||
## License as published by the Free Software Foundation; either
|
|
||||||
## version 2.1 of the License, or (at your option) any later version.
|
|
||||||
##
|
|
||||||
## This library is distributed in the hope that it will be useful,
|
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
## Lesser General Public License for more details.
|
|
||||||
##
|
|
||||||
## You should have received a copy of the GNU Lesser General Public
|
|
||||||
## License along with this library; if not, write to the Free Software
|
|
||||||
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
##
|
|
||||||
## @category PHPExcel
|
|
||||||
## @package PHPExcel_Calculation
|
|
||||||
## @copyright Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
|
|
||||||
## @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
||||||
## @version ##VERSION##, ##DATE##
|
|
||||||
##
|
|
||||||
## Data in this file derived from http://www.piuha.fi/excel-function-name-translation/
|
|
||||||
##
|
##
|
||||||
##
|
##
|
||||||
|
|
||||||
176
PhpOffice/PhpSpreadsheet/Cell/AdvancedValueBinder.php
Normal file
176
PhpOffice/PhpSpreadsheet/Cell/AdvancedValueBinder.php
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
<?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
|
||||||
|
*
|
||||||
|
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
698
PhpOffice/PhpSpreadsheet/Cell/Cell.php
Normal file
698
PhpOffice/PhpSpreadsheet/Cell/Cell.php
Normal file
|
|
@ -0,0 +1,698 @@
|
||||||
|
<?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 self
|
||||||
|
*/
|
||||||
|
public function updateInCollection()
|
||||||
|
{
|
||||||
|
$this->parent->update($this);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
$this->parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attach(Cells $parent)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Cell.
|
||||||
|
*
|
||||||
|
* @param mixed $pValue
|
||||||
|
* @param string $pDataType
|
||||||
|
* @param Worksheet $pSheet
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Cell
|
||||||
|
*/
|
||||||
|
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_*
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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:
|
||||||
|
$this->value = (float) $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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getCalculatedValue($resetLog = true)
|
||||||
|
{
|
||||||
|
if ($this->dataType == DataType::TYPE_FORMULA) {
|
||||||
|
try {
|
||||||
|
$result = Calculation::getInstance(
|
||||||
|
$this->getWorksheet()->getParent()
|
||||||
|
)->calculateCellValue($this, $resetLog);
|
||||||
|
// We don't yet handle array returns
|
||||||
|
if (is_array($result)) {
|
||||||
|
while (is_array($result)) {
|
||||||
|
$result = array_pop($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
|
||||||
|
return $this->calculatedValue; // Fallback for calculations referencing external files.
|
||||||
|
}
|
||||||
|
|
||||||
|
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?
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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?
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @param Worksheet $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.
|
||||||
|
*
|
||||||
|
* @param IValueBinder $binder
|
||||||
|
*/
|
||||||
|
public static function setValueBinder(IValueBinder $binder)
|
||||||
|
{
|
||||||
|
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 Cell
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
520
PhpOffice/PhpSpreadsheet/Cell/Coordinate.php
Normal file
520
PhpOffice/PhpSpreadsheet/Cell/Coordinate.php
Normal file
|
|
@ -0,0 +1,520 @@
|
||||||
|
<?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'
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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'
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @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 $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
|
||||||
|
*
|
||||||
|
* @return array Array containing single cell references
|
||||||
|
*/
|
||||||
|
public static function extractAllCellReferencesInRange($pRange)
|
||||||
|
{
|
||||||
|
$returnValue = [];
|
||||||
|
|
||||||
|
// Explode spaces
|
||||||
|
$cellBlocks = self::getCellBlocksFromRangeString($pRange);
|
||||||
|
foreach ($cellBlocks as $cellBlock) {
|
||||||
|
$returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the result by column and row
|
||||||
|
$sortKeys = [];
|
||||||
|
foreach (array_unique($returnValue) as $coord) {
|
||||||
|
$column = '';
|
||||||
|
$row = 0;
|
||||||
|
|
||||||
|
sscanf($coord, '%[A-Z]%d', $column, $row);
|
||||||
|
$sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
|
||||||
|
}
|
||||||
|
ksort($sortKeys);
|
||||||
|
|
||||||
|
// Return value
|
||||||
|
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, splitting by space and removing any $ characters.
|
||||||
|
*
|
||||||
|
* @param string $pRange
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private static function getCellBlocksFromRangeString($pRange)
|
||||||
|
{
|
||||||
|
return explode(' ', str_replace('$', '', strtoupper($pRange)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) {
|
||||||
|
throw new Exception('Invalid range: "' . $cellBlock . '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
PhpOffice/PhpSpreadsheet/Cell/DataType.php
Normal file
85
PhpOffice/PhpSpreadsheet/Cell/DataType.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
481
PhpOffice/PhpSpreadsheet/Cell/DataValidation.php
Normal file
481
PhpOffice/PhpSpreadsheet/Cell/DataValidation.php
Normal 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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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 DataValidation
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
PhpOffice/PhpSpreadsheet/Cell/DataValidator.php
Normal file
77
PhpOffice/PhpSpreadsheet/Cell/DataValidator.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php
Normal file
82
PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php
Normal 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
|
||||||
|
*
|
||||||
|
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||||
|
*
|
||||||
|
* @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 ($pValue === '') {
|
||||||
|
return DataType::TYPE_STRING;
|
||||||
|
} elseif ($pValue instanceof RichText) {
|
||||||
|
return DataType::TYPE_INLINE;
|
||||||
|
} elseif (strpos($pValue, "=") === 0 && strlen($pValue) > 1) {
|
||||||
|
return DataType::TYPE_FORMULA;
|
||||||
|
} elseif (is_bool($pValue)) {
|
||||||
|
return DataType::TYPE_BOOL;
|
||||||
|
} elseif (is_float($pValue) || is_int($pValue)) {
|
||||||
|
return DataType::TYPE_NUMERIC;
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DataType::TYPE_NUMERIC;
|
||||||
|
} elseif (is_string($pValue)) {
|
||||||
|
$errorCodes = DataType::getErrorCodes();
|
||||||
|
if (isset($errorCodes[$pValue])) {
|
||||||
|
return DataType::TYPE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DataType::TYPE_STRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php
Normal file
113
PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php
Normal 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 Hyperlink
|
||||||
|
*/
|
||||||
|
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 Hyperlink
|
||||||
|
*/
|
||||||
|
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__
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
PhpOffice/PhpSpreadsheet/Cell/IValueBinder.php
Normal file
16
PhpOffice/PhpSpreadsheet/Cell/IValueBinder.php
Normal 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);
|
||||||
|
}
|
||||||
31
PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php
Normal file
31
PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?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
|
||||||
|
*
|
||||||
|
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
557
PhpOffice/PhpSpreadsheet/Chart/Axis.php
Normal file
557
PhpOffice/PhpSpreadsheet/Chart/Axis.php
Normal file
|
|
@ -0,0 +1,557 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: Wiktor Trzonkowski
|
||||||
|
* Date: 6/17/14
|
||||||
|
* Time: 12:11 PM.
|
||||||
|
*/
|
||||||
|
class Axis extends Properties
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Axis Number.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $axisNumber = [
|
||||||
|
'format' => self::FORMAT_CODE_GENERAL,
|
||||||
|
'source_linked' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Axis Options.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $axisOptions = [
|
||||||
|
'minimum' => null,
|
||||||
|
'maximum' => null,
|
||||||
|
'major_unit' => null,
|
||||||
|
'minor_unit' => null,
|
||||||
|
'orientation' => self::ORIENTATION_NORMAL,
|
||||||
|
'minor_tick_mark' => self::TICK_MARK_NONE,
|
||||||
|
'major_tick_mark' => self::TICK_MARK_NONE,
|
||||||
|
'axis_labels' => self::AXIS_LABELS_NEXT_TO,
|
||||||
|
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
|
||||||
|
'horizontal_crosses_value' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $fillProperties = [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_ARGB,
|
||||||
|
'value' => null,
|
||||||
|
'alpha' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $lineProperties = [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_ARGB,
|
||||||
|
'value' => null,
|
||||||
|
'alpha' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line Style Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $lineStyleProperties = [
|
||||||
|
'width' => '9525',
|
||||||
|
'compound' => self::LINE_STYLE_COMPOUND_SIMPLE,
|
||||||
|
'dash' => self::LINE_STYLE_DASH_SOLID,
|
||||||
|
'cap' => self::LINE_STYLE_CAP_FLAT,
|
||||||
|
'join' => self::LINE_STYLE_JOIN_BEVEL,
|
||||||
|
'arrow' => [
|
||||||
|
'head' => [
|
||||||
|
'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
|
||||||
|
'size' => self::LINE_STYLE_ARROW_SIZE_5,
|
||||||
|
],
|
||||||
|
'end' => [
|
||||||
|
'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
|
||||||
|
'size' => self::LINE_STYLE_ARROW_SIZE_8,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shadow Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $shadowProperties = [
|
||||||
|
'presets' => self::SHADOW_PRESETS_NOSHADOW,
|
||||||
|
'effect' => null,
|
||||||
|
'color' => [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_STANDARD,
|
||||||
|
'value' => 'black',
|
||||||
|
'alpha' => 40,
|
||||||
|
],
|
||||||
|
'size' => [
|
||||||
|
'sx' => null,
|
||||||
|
'sy' => null,
|
||||||
|
'kx' => null,
|
||||||
|
],
|
||||||
|
'blur' => null,
|
||||||
|
'direction' => null,
|
||||||
|
'distance' => null,
|
||||||
|
'algn' => null,
|
||||||
|
'rotWithShape' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glow Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $glowProperties = [
|
||||||
|
'size' => null,
|
||||||
|
'color' => [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_STANDARD,
|
||||||
|
'value' => 'black',
|
||||||
|
'alpha' => 40,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft Edge Properties.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $softEdges = [
|
||||||
|
'size' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Data Type.
|
||||||
|
*
|
||||||
|
* @param mixed $format_code
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function setAxisNumberProperties($format_code)
|
||||||
|
{
|
||||||
|
$this->axisNumber['format'] = (string) $format_code;
|
||||||
|
$this->axisNumber['source_linked'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Axis Number Format Data Type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAxisNumberFormat()
|
||||||
|
{
|
||||||
|
return $this->axisNumber['format'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Axis Number Source Linked.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAxisNumberSourceLinked()
|
||||||
|
{
|
||||||
|
return (string) $this->axisNumber['source_linked'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Axis Options Properties.
|
||||||
|
*
|
||||||
|
* @param string $axis_labels
|
||||||
|
* @param string $horizontal_crosses_value
|
||||||
|
* @param string $horizontal_crosses
|
||||||
|
* @param string $axis_orientation
|
||||||
|
* @param string $major_tmt
|
||||||
|
* @param string $minor_tmt
|
||||||
|
* @param string $minimum
|
||||||
|
* @param string $maximum
|
||||||
|
* @param string $major_unit
|
||||||
|
* @param string $minor_unit
|
||||||
|
*/
|
||||||
|
public function setAxisOptionsProperties($axis_labels, $horizontal_crosses_value = null, $horizontal_crosses = null, $axis_orientation = null, $major_tmt = null, $minor_tmt = null, $minimum = null, $maximum = null, $major_unit = null, $minor_unit = null)
|
||||||
|
{
|
||||||
|
$this->axisOptions['axis_labels'] = (string) $axis_labels;
|
||||||
|
($horizontal_crosses_value !== null) ? $this->axisOptions['horizontal_crosses_value'] = (string) $horizontal_crosses_value : null;
|
||||||
|
($horizontal_crosses !== null) ? $this->axisOptions['horizontal_crosses'] = (string) $horizontal_crosses : null;
|
||||||
|
($axis_orientation !== null) ? $this->axisOptions['orientation'] = (string) $axis_orientation : null;
|
||||||
|
($major_tmt !== null) ? $this->axisOptions['major_tick_mark'] = (string) $major_tmt : null;
|
||||||
|
($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null;
|
||||||
|
($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null;
|
||||||
|
($minimum !== null) ? $this->axisOptions['minimum'] = (string) $minimum : null;
|
||||||
|
($maximum !== null) ? $this->axisOptions['maximum'] = (string) $maximum : null;
|
||||||
|
($major_unit !== null) ? $this->axisOptions['major_unit'] = (string) $major_unit : null;
|
||||||
|
($minor_unit !== null) ? $this->axisOptions['minor_unit'] = (string) $minor_unit : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Axis Options Property.
|
||||||
|
*
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAxisOptionsProperty($property)
|
||||||
|
{
|
||||||
|
return $this->axisOptions[$property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Axis Orientation Property.
|
||||||
|
*
|
||||||
|
* @param string $orientation
|
||||||
|
*/
|
||||||
|
public function setAxisOrientation($orientation)
|
||||||
|
{
|
||||||
|
$this->axisOptions['orientation'] = (string) $orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Fill Property.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*/
|
||||||
|
public function setFillParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB)
|
||||||
|
{
|
||||||
|
$this->fillProperties = $this->setColorProperties($color, $alpha, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Line Property.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*/
|
||||||
|
public function setLineParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB)
|
||||||
|
{
|
||||||
|
$this->lineProperties = $this->setColorProperties($color, $alpha, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Fill Property.
|
||||||
|
*
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFillProperty($property)
|
||||||
|
{
|
||||||
|
return $this->fillProperties[$property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Property.
|
||||||
|
*
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineProperty($property)
|
||||||
|
{
|
||||||
|
return $this->lineProperties[$property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Line Style Properties.
|
||||||
|
*
|
||||||
|
* @param float $line_width
|
||||||
|
* @param string $compound_type
|
||||||
|
* @param string $dash_type
|
||||||
|
* @param string $cap_type
|
||||||
|
* @param string $join_type
|
||||||
|
* @param string $head_arrow_type
|
||||||
|
* @param string $head_arrow_size
|
||||||
|
* @param string $end_arrow_type
|
||||||
|
* @param string $end_arrow_size
|
||||||
|
*/
|
||||||
|
public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null)
|
||||||
|
{
|
||||||
|
($line_width !== null) ? $this->lineStyleProperties['width'] = $this->getExcelPointsWidth((float) $line_width) : null;
|
||||||
|
($compound_type !== null) ? $this->lineStyleProperties['compound'] = (string) $compound_type : null;
|
||||||
|
($dash_type !== null) ? $this->lineStyleProperties['dash'] = (string) $dash_type : null;
|
||||||
|
($cap_type !== null) ? $this->lineStyleProperties['cap'] = (string) $cap_type : null;
|
||||||
|
($join_type !== null) ? $this->lineStyleProperties['join'] = (string) $join_type : null;
|
||||||
|
($head_arrow_type !== null) ? $this->lineStyleProperties['arrow']['head']['type'] = (string) $head_arrow_type : null;
|
||||||
|
($head_arrow_size !== null) ? $this->lineStyleProperties['arrow']['head']['size'] = (string) $head_arrow_size : null;
|
||||||
|
($end_arrow_type !== null) ? $this->lineStyleProperties['arrow']['end']['type'] = (string) $end_arrow_type : null;
|
||||||
|
($end_arrow_size !== null) ? $this->lineStyleProperties['arrow']['end']['size'] = (string) $end_arrow_size : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Style Property.
|
||||||
|
*
|
||||||
|
* @param array|string $elements
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineStyleProperty($elements)
|
||||||
|
{
|
||||||
|
return $this->getArrayElementsValue($this->lineStyleProperties, $elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Style Arrow Excel Width.
|
||||||
|
*
|
||||||
|
* @param string $arrow
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineStyleArrowWidth($arrow)
|
||||||
|
{
|
||||||
|
return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'w');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Style Arrow Excel Length.
|
||||||
|
*
|
||||||
|
* @param string $arrow
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineStyleArrowLength($arrow)
|
||||||
|
{
|
||||||
|
return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'len');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Properties.
|
||||||
|
*
|
||||||
|
* @param int $sh_presets
|
||||||
|
* @param string $sh_color_value
|
||||||
|
* @param string $sh_color_type
|
||||||
|
* @param string $sh_color_alpha
|
||||||
|
* @param float $sh_blur
|
||||||
|
* @param int $sh_angle
|
||||||
|
* @param float $sh_distance
|
||||||
|
*/
|
||||||
|
public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null)
|
||||||
|
{
|
||||||
|
$this->setShadowPresetsProperties((int) $sh_presets)
|
||||||
|
->setShadowColor(
|
||||||
|
$sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value,
|
||||||
|
$sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $sh_color_alpha,
|
||||||
|
$sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type
|
||||||
|
)
|
||||||
|
->setShadowBlur($sh_blur)
|
||||||
|
->setShadowAngle($sh_angle)
|
||||||
|
->setShadowDistance($sh_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Color.
|
||||||
|
*
|
||||||
|
* @param int $shadow_presets
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowPresetsProperties($shadow_presets)
|
||||||
|
{
|
||||||
|
$this->shadowProperties['presets'] = $shadow_presets;
|
||||||
|
$this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Properties from Mapped Values.
|
||||||
|
*
|
||||||
|
* @param array $properties_map
|
||||||
|
* @param mixed &$reference
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
|
||||||
|
{
|
||||||
|
$base_reference = $reference;
|
||||||
|
foreach ($properties_map as $property_key => $property_val) {
|
||||||
|
if (is_array($property_val)) {
|
||||||
|
if ($reference === null) {
|
||||||
|
$reference = &$this->shadowProperties[$property_key];
|
||||||
|
} else {
|
||||||
|
$reference = &$reference[$property_key];
|
||||||
|
}
|
||||||
|
$this->setShadowProperiesMapValues($property_val, $reference);
|
||||||
|
} else {
|
||||||
|
if ($base_reference === null) {
|
||||||
|
$this->shadowProperties[$property_key] = $property_val;
|
||||||
|
} else {
|
||||||
|
$reference[$property_key] = $property_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Color.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowColor($color, $alpha, $type)
|
||||||
|
{
|
||||||
|
$this->shadowProperties['color'] = $this->setColorProperties($color, $alpha, $type);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Blur.
|
||||||
|
*
|
||||||
|
* @param float $blur
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowBlur($blur)
|
||||||
|
{
|
||||||
|
if ($blur !== null) {
|
||||||
|
$this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Angle.
|
||||||
|
*
|
||||||
|
* @param int $angle
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowAngle($angle)
|
||||||
|
{
|
||||||
|
if ($angle !== null) {
|
||||||
|
$this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Distance.
|
||||||
|
*
|
||||||
|
* @param float $distance
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setShadowDistance($distance)
|
||||||
|
{
|
||||||
|
if ($distance !== null) {
|
||||||
|
$this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Shadow Property.
|
||||||
|
*
|
||||||
|
* @param string|string[] $elements
|
||||||
|
*
|
||||||
|
* @return null|array|int|string
|
||||||
|
*/
|
||||||
|
public function getShadowProperty($elements)
|
||||||
|
{
|
||||||
|
return $this->getArrayElementsValue($this->shadowProperties, $elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Properties.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
* @param string $color_value
|
||||||
|
* @param int $color_alpha
|
||||||
|
* @param string $color_type
|
||||||
|
*/
|
||||||
|
public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null)
|
||||||
|
{
|
||||||
|
$this->setGlowSize($size)
|
||||||
|
->setGlowColor(
|
||||||
|
$color_value === null ? $this->glowProperties['color']['value'] : $color_value,
|
||||||
|
$color_alpha === null ? (int) $this->glowProperties['color']['alpha'] : $color_alpha,
|
||||||
|
$color_type === null ? $this->glowProperties['color']['type'] : $color_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Glow Property.
|
||||||
|
*
|
||||||
|
* @param array|string $property
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGlowProperty($property)
|
||||||
|
{
|
||||||
|
return $this->getArrayElementsValue($this->glowProperties, $property);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Color.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setGlowSize($size)
|
||||||
|
{
|
||||||
|
if ($size !== null) {
|
||||||
|
$this->glowProperties['size'] = $this->getExcelPointsWidth($size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Color.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
private function setGlowColor($color, $alpha, $type)
|
||||||
|
{
|
||||||
|
$this->glowProperties['color'] = $this->setColorProperties($color, $alpha, $type);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Soft Edges Size.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
*/
|
||||||
|
public function setSoftEdges($size)
|
||||||
|
{
|
||||||
|
if ($size !== null) {
|
||||||
|
$softEdges['size'] = (string) $this->getExcelPointsWidth($size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Soft Edges Size.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSoftEdgesSize()
|
||||||
|
{
|
||||||
|
return $this->softEdges['size'];
|
||||||
|
}
|
||||||
|
}
|
||||||
680
PhpOffice/PhpSpreadsheet/Chart/Chart.php
Normal file
680
PhpOffice/PhpSpreadsheet/Chart/Chart.php
Normal file
|
|
@ -0,0 +1,680 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Settings;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class Chart
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Chart Name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worksheet.
|
||||||
|
*
|
||||||
|
* @var Worksheet
|
||||||
|
*/
|
||||||
|
private $worksheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Title.
|
||||||
|
*
|
||||||
|
* @var Title
|
||||||
|
*/
|
||||||
|
private $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Legend.
|
||||||
|
*
|
||||||
|
* @var Legend
|
||||||
|
*/
|
||||||
|
private $legend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* X-Axis Label.
|
||||||
|
*
|
||||||
|
* @var Title
|
||||||
|
*/
|
||||||
|
private $xAxisLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y-Axis Label.
|
||||||
|
*
|
||||||
|
* @var Title
|
||||||
|
*/
|
||||||
|
private $yAxisLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Plot Area.
|
||||||
|
*
|
||||||
|
* @var PlotArea
|
||||||
|
*/
|
||||||
|
private $plotArea;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Visible Only.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $plotVisibleOnly = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display Blanks as.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $displayBlanksAs = '0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Asix Y as.
|
||||||
|
*
|
||||||
|
* @var Axis
|
||||||
|
*/
|
||||||
|
private $yAxis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Asix X as.
|
||||||
|
*
|
||||||
|
* @var Axis
|
||||||
|
*/
|
||||||
|
private $xAxis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Major Gridlines as.
|
||||||
|
*
|
||||||
|
* @var GridLines
|
||||||
|
*/
|
||||||
|
private $majorGridlines;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart Minor Gridlines as.
|
||||||
|
*
|
||||||
|
* @var GridLines
|
||||||
|
*/
|
||||||
|
private $minorGridlines;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-Left Cell Position.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $topLeftCellRef = 'A1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-Left X-Offset.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $topLeftXOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-Left Y-Offset.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $topLeftYOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom-Right Cell Position.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $bottomRightCellRef = 'A1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom-Right X-Offset.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $bottomRightXOffset = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom-Right Y-Offset.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $bottomRightYOffset = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Chart.
|
||||||
|
*
|
||||||
|
* @param mixed $name
|
||||||
|
* @param null|Title $title
|
||||||
|
* @param null|Legend $legend
|
||||||
|
* @param null|PlotArea $plotArea
|
||||||
|
* @param mixed $plotVisibleOnly
|
||||||
|
* @param mixed $displayBlanksAs
|
||||||
|
* @param null|Title $xAxisLabel
|
||||||
|
* @param null|Title $yAxisLabel
|
||||||
|
* @param null|Axis $xAxis
|
||||||
|
* @param null|Axis $yAxis
|
||||||
|
* @param null|GridLines $majorGridlines
|
||||||
|
* @param null|GridLines $minorGridlines
|
||||||
|
*/
|
||||||
|
public function __construct($name, Title $title = null, Legend $legend = null, PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = '0', Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->legend = $legend;
|
||||||
|
$this->xAxisLabel = $xAxisLabel;
|
||||||
|
$this->yAxisLabel = $yAxisLabel;
|
||||||
|
$this->plotArea = $plotArea;
|
||||||
|
$this->plotVisibleOnly = $plotVisibleOnly;
|
||||||
|
$this->displayBlanksAs = $displayBlanksAs;
|
||||||
|
$this->xAxis = $xAxis;
|
||||||
|
$this->yAxis = $yAxis;
|
||||||
|
$this->majorGridlines = $majorGridlines;
|
||||||
|
$this->minorGridlines = $minorGridlines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Worksheet.
|
||||||
|
*
|
||||||
|
* @return Worksheet
|
||||||
|
*/
|
||||||
|
public function getWorksheet()
|
||||||
|
{
|
||||||
|
return $this->worksheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Worksheet.
|
||||||
|
*
|
||||||
|
* @param Worksheet $pValue
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setWorksheet(Worksheet $pValue = null)
|
||||||
|
{
|
||||||
|
$this->worksheet = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Title.
|
||||||
|
*
|
||||||
|
* @return Title
|
||||||
|
*/
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Title.
|
||||||
|
*
|
||||||
|
* @param Title $title
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setTitle(Title $title)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Legend.
|
||||||
|
*
|
||||||
|
* @return Legend
|
||||||
|
*/
|
||||||
|
public function getLegend()
|
||||||
|
{
|
||||||
|
return $this->legend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Legend.
|
||||||
|
*
|
||||||
|
* @param Legend $legend
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setLegend(Legend $legend)
|
||||||
|
{
|
||||||
|
$this->legend = $legend;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get X-Axis Label.
|
||||||
|
*
|
||||||
|
* @return Title
|
||||||
|
*/
|
||||||
|
public function getXAxisLabel()
|
||||||
|
{
|
||||||
|
return $this->xAxisLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set X-Axis Label.
|
||||||
|
*
|
||||||
|
* @param Title $label
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setXAxisLabel(Title $label)
|
||||||
|
{
|
||||||
|
$this->xAxisLabel = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Y-Axis Label.
|
||||||
|
*
|
||||||
|
* @return Title
|
||||||
|
*/
|
||||||
|
public function getYAxisLabel()
|
||||||
|
{
|
||||||
|
return $this->yAxisLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Y-Axis Label.
|
||||||
|
*
|
||||||
|
* @param Title $label
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setYAxisLabel(Title $label)
|
||||||
|
{
|
||||||
|
$this->yAxisLabel = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Area.
|
||||||
|
*
|
||||||
|
* @return PlotArea
|
||||||
|
*/
|
||||||
|
public function getPlotArea()
|
||||||
|
{
|
||||||
|
return $this->plotArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Visible Only.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getPlotVisibleOnly()
|
||||||
|
{
|
||||||
|
return $this->plotVisibleOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Visible Only.
|
||||||
|
*
|
||||||
|
* @param bool $plotVisibleOnly
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setPlotVisibleOnly($plotVisibleOnly)
|
||||||
|
{
|
||||||
|
$this->plotVisibleOnly = $plotVisibleOnly;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Display Blanks as.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDisplayBlanksAs()
|
||||||
|
{
|
||||||
|
return $this->displayBlanksAs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Display Blanks as.
|
||||||
|
*
|
||||||
|
* @param string $displayBlanksAs
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setDisplayBlanksAs($displayBlanksAs)
|
||||||
|
{
|
||||||
|
$this->displayBlanksAs = $displayBlanksAs;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get yAxis.
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
public function getChartAxisY()
|
||||||
|
{
|
||||||
|
if ($this->yAxis !== null) {
|
||||||
|
return $this->yAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get xAxis.
|
||||||
|
*
|
||||||
|
* @return Axis
|
||||||
|
*/
|
||||||
|
public function getChartAxisX()
|
||||||
|
{
|
||||||
|
if ($this->xAxis !== null) {
|
||||||
|
return $this->xAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Major Gridlines.
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
public function getMajorGridlines()
|
||||||
|
{
|
||||||
|
if ($this->majorGridlines !== null) {
|
||||||
|
return $this->majorGridlines;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GridLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Minor Gridlines.
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
public function getMinorGridlines()
|
||||||
|
{
|
||||||
|
if ($this->minorGridlines !== null) {
|
||||||
|
return $this->minorGridlines;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GridLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Top Left position for the chart.
|
||||||
|
*
|
||||||
|
* @param string $cell
|
||||||
|
* @param int $xOffset
|
||||||
|
* @param int $yOffset
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null)
|
||||||
|
{
|
||||||
|
$this->topLeftCellRef = $cell;
|
||||||
|
if ($xOffset !== null) {
|
||||||
|
$this->setTopLeftXOffset($xOffset);
|
||||||
|
}
|
||||||
|
if ($yOffset !== null) {
|
||||||
|
$this->setTopLeftYOffset($yOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the top left position of the chart.
|
||||||
|
*
|
||||||
|
* @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
|
||||||
|
*/
|
||||||
|
public function getTopLeftPosition()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cell' => $this->topLeftCellRef,
|
||||||
|
'xOffset' => $this->topLeftXOffset,
|
||||||
|
'yOffset' => $this->topLeftYOffset,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cell address where the top left of the chart is fixed.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTopLeftCell()
|
||||||
|
{
|
||||||
|
return $this->topLeftCellRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Top Left cell position for the chart.
|
||||||
|
*
|
||||||
|
* @param string $cell
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setTopLeftCell($cell)
|
||||||
|
{
|
||||||
|
$this->topLeftCellRef = $cell;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offset position within the Top Left cell for the chart.
|
||||||
|
*
|
||||||
|
* @param int $xOffset
|
||||||
|
* @param int $yOffset
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setTopLeftOffset($xOffset, $yOffset)
|
||||||
|
{
|
||||||
|
if ($xOffset !== null) {
|
||||||
|
$this->setTopLeftXOffset($xOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($yOffset !== null) {
|
||||||
|
$this->setTopLeftYOffset($yOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offset position within the Top Left cell for the chart.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getTopLeftOffset()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'X' => $this->topLeftXOffset,
|
||||||
|
'Y' => $this->topLeftYOffset,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTopLeftXOffset($xOffset)
|
||||||
|
{
|
||||||
|
$this->topLeftXOffset = $xOffset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTopLeftXOffset()
|
||||||
|
{
|
||||||
|
return $this->topLeftXOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTopLeftYOffset($yOffset)
|
||||||
|
{
|
||||||
|
$this->topLeftYOffset = $yOffset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTopLeftYOffset()
|
||||||
|
{
|
||||||
|
return $this->topLeftYOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Bottom Right position of the chart.
|
||||||
|
*
|
||||||
|
* @param string $cell
|
||||||
|
* @param int $xOffset
|
||||||
|
* @param int $yOffset
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null)
|
||||||
|
{
|
||||||
|
$this->bottomRightCellRef = $cell;
|
||||||
|
if ($xOffset !== null) {
|
||||||
|
$this->setBottomRightXOffset($xOffset);
|
||||||
|
}
|
||||||
|
if ($yOffset !== null) {
|
||||||
|
$this->setBottomRightYOffset($yOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bottom right position of the chart.
|
||||||
|
*
|
||||||
|
* @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
|
||||||
|
*/
|
||||||
|
public function getBottomRightPosition()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cell' => $this->bottomRightCellRef,
|
||||||
|
'xOffset' => $this->bottomRightXOffset,
|
||||||
|
'yOffset' => $this->bottomRightYOffset,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBottomRightCell($cell)
|
||||||
|
{
|
||||||
|
$this->bottomRightCellRef = $cell;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cell address where the bottom right of the chart is fixed.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBottomRightCell()
|
||||||
|
{
|
||||||
|
return $this->bottomRightCellRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offset position within the Bottom Right cell for the chart.
|
||||||
|
*
|
||||||
|
* @param int $xOffset
|
||||||
|
* @param int $yOffset
|
||||||
|
*
|
||||||
|
* @return Chart
|
||||||
|
*/
|
||||||
|
public function setBottomRightOffset($xOffset, $yOffset)
|
||||||
|
{
|
||||||
|
if ($xOffset !== null) {
|
||||||
|
$this->setBottomRightXOffset($xOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($yOffset !== null) {
|
||||||
|
$this->setBottomRightYOffset($yOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offset position within the Bottom Right cell for the chart.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getBottomRightOffset()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'X' => $this->bottomRightXOffset,
|
||||||
|
'Y' => $this->bottomRightYOffset,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBottomRightXOffset($xOffset)
|
||||||
|
{
|
||||||
|
$this->bottomRightXOffset = $xOffset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBottomRightXOffset()
|
||||||
|
{
|
||||||
|
return $this->bottomRightXOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBottomRightYOffset($yOffset)
|
||||||
|
{
|
||||||
|
$this->bottomRightYOffset = $yOffset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBottomRightYOffset()
|
||||||
|
{
|
||||||
|
return $this->bottomRightYOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh()
|
||||||
|
{
|
||||||
|
if ($this->worksheet !== null) {
|
||||||
|
$this->plotArea->refresh($this->worksheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the chart to given file (or stream).
|
||||||
|
*
|
||||||
|
* @param string $outputDestination Name of the file render to
|
||||||
|
*
|
||||||
|
* @return bool true on success
|
||||||
|
*/
|
||||||
|
public function render($outputDestination = null)
|
||||||
|
{
|
||||||
|
if ($outputDestination == 'php://output') {
|
||||||
|
$outputDestination = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$libraryName = Settings::getChartRenderer();
|
||||||
|
if ($libraryName === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that data series values are up-to-date before we render
|
||||||
|
$this->refresh();
|
||||||
|
|
||||||
|
$renderer = new $libraryName($this);
|
||||||
|
|
||||||
|
return $renderer->render($outputDestination);
|
||||||
|
}
|
||||||
|
}
|
||||||
390
PhpOffice/PhpSpreadsheet/Chart/DataSeries.php
Normal file
390
PhpOffice/PhpSpreadsheet/Chart/DataSeries.php
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class DataSeries
|
||||||
|
{
|
||||||
|
const TYPE_BARCHART = 'barChart';
|
||||||
|
const TYPE_BARCHART_3D = 'bar3DChart';
|
||||||
|
const TYPE_LINECHART = 'lineChart';
|
||||||
|
const TYPE_LINECHART_3D = 'line3DChart';
|
||||||
|
const TYPE_AREACHART = 'areaChart';
|
||||||
|
const TYPE_AREACHART_3D = 'area3DChart';
|
||||||
|
const TYPE_PIECHART = 'pieChart';
|
||||||
|
const TYPE_PIECHART_3D = 'pie3DChart';
|
||||||
|
const TYPE_DOUGHNUTCHART = 'doughnutChart';
|
||||||
|
const TYPE_DONUTCHART = self::TYPE_DOUGHNUTCHART; // Synonym
|
||||||
|
const TYPE_SCATTERCHART = 'scatterChart';
|
||||||
|
const TYPE_SURFACECHART = 'surfaceChart';
|
||||||
|
const TYPE_SURFACECHART_3D = 'surface3DChart';
|
||||||
|
const TYPE_RADARCHART = 'radarChart';
|
||||||
|
const TYPE_BUBBLECHART = 'bubbleChart';
|
||||||
|
const TYPE_STOCKCHART = 'stockChart';
|
||||||
|
const TYPE_CANDLECHART = self::TYPE_STOCKCHART; // Synonym
|
||||||
|
|
||||||
|
const GROUPING_CLUSTERED = 'clustered';
|
||||||
|
const GROUPING_STACKED = 'stacked';
|
||||||
|
const GROUPING_PERCENT_STACKED = 'percentStacked';
|
||||||
|
const GROUPING_STANDARD = 'standard';
|
||||||
|
|
||||||
|
const DIRECTION_BAR = 'bar';
|
||||||
|
const DIRECTION_HORIZONTAL = self::DIRECTION_BAR;
|
||||||
|
const DIRECTION_COL = 'col';
|
||||||
|
const DIRECTION_COLUMN = self::DIRECTION_COL;
|
||||||
|
const DIRECTION_VERTICAL = self::DIRECTION_COL;
|
||||||
|
|
||||||
|
const STYLE_LINEMARKER = 'lineMarker';
|
||||||
|
const STYLE_SMOOTHMARKER = 'smoothMarker';
|
||||||
|
const STYLE_MARKER = 'marker';
|
||||||
|
const STYLE_FILLED = 'filled';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series Plot Type.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $plotType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Grouping Type.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $plotGrouping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Direction.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $plotDirection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Style.
|
||||||
|
*
|
||||||
|
* @var null|string
|
||||||
|
*/
|
||||||
|
private $plotStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order of plots in Series.
|
||||||
|
*
|
||||||
|
* @var array of integer
|
||||||
|
*/
|
||||||
|
private $plotOrder = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Label.
|
||||||
|
*
|
||||||
|
* @var array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
private $plotLabel = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Category.
|
||||||
|
*
|
||||||
|
* @var array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
private $plotCategory = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smooth Line.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $smoothLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Values.
|
||||||
|
*
|
||||||
|
* @var array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
private $plotValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DataSeries.
|
||||||
|
*
|
||||||
|
* @param null|mixed $plotType
|
||||||
|
* @param null|mixed $plotGrouping
|
||||||
|
* @param int[] $plotOrder
|
||||||
|
* @param DataSeriesValues[] $plotLabel
|
||||||
|
* @param DataSeriesValues[] $plotCategory
|
||||||
|
* @param DataSeriesValues[] $plotValues
|
||||||
|
* @param null|string $plotDirection
|
||||||
|
* @param bool $smoothLine
|
||||||
|
* @param null|string $plotStyle
|
||||||
|
*/
|
||||||
|
public function __construct($plotType = null, $plotGrouping = null, array $plotOrder = [], array $plotLabel = [], array $plotCategory = [], array $plotValues = [], $plotDirection = null, $smoothLine = false, $plotStyle = null)
|
||||||
|
{
|
||||||
|
$this->plotType = $plotType;
|
||||||
|
$this->plotGrouping = $plotGrouping;
|
||||||
|
$this->plotOrder = $plotOrder;
|
||||||
|
$keys = array_keys($plotValues);
|
||||||
|
$this->plotValues = $plotValues;
|
||||||
|
if ((count($plotLabel) == 0) || ($plotLabel[$keys[0]] === null)) {
|
||||||
|
$plotLabel[$keys[0]] = new DataSeriesValues();
|
||||||
|
}
|
||||||
|
$this->plotLabel = $plotLabel;
|
||||||
|
|
||||||
|
if ((count($plotCategory) == 0) || ($plotCategory[$keys[0]] === null)) {
|
||||||
|
$plotCategory[$keys[0]] = new DataSeriesValues();
|
||||||
|
}
|
||||||
|
$this->plotCategory = $plotCategory;
|
||||||
|
|
||||||
|
$this->smoothLine = $smoothLine;
|
||||||
|
$this->plotStyle = $plotStyle;
|
||||||
|
|
||||||
|
if ($plotDirection === null) {
|
||||||
|
$plotDirection = self::DIRECTION_COL;
|
||||||
|
}
|
||||||
|
$this->plotDirection = $plotDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPlotType()
|
||||||
|
{
|
||||||
|
return $this->plotType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Type.
|
||||||
|
*
|
||||||
|
* @param string $plotType
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function setPlotType($plotType)
|
||||||
|
{
|
||||||
|
$this->plotType = $plotType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Grouping Type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPlotGrouping()
|
||||||
|
{
|
||||||
|
return $this->plotGrouping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Grouping Type.
|
||||||
|
*
|
||||||
|
* @param string $groupingType
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function setPlotGrouping($groupingType)
|
||||||
|
{
|
||||||
|
$this->plotGrouping = $groupingType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Direction.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPlotDirection()
|
||||||
|
{
|
||||||
|
return $this->plotDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Direction.
|
||||||
|
*
|
||||||
|
* @param string $plotDirection
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function setPlotDirection($plotDirection)
|
||||||
|
{
|
||||||
|
$this->plotDirection = $plotDirection;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Order.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getPlotOrder()
|
||||||
|
{
|
||||||
|
return $this->plotOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Labels.
|
||||||
|
*
|
||||||
|
* @return array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotLabels()
|
||||||
|
{
|
||||||
|
return $this->plotLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Label by Index.
|
||||||
|
*
|
||||||
|
* @param mixed $index
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotLabelByIndex($index)
|
||||||
|
{
|
||||||
|
$keys = array_keys($this->plotLabel);
|
||||||
|
if (in_array($index, $keys)) {
|
||||||
|
return $this->plotLabel[$index];
|
||||||
|
} elseif (isset($keys[$index])) {
|
||||||
|
return $this->plotLabel[$keys[$index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Categories.
|
||||||
|
*
|
||||||
|
* @return array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotCategories()
|
||||||
|
{
|
||||||
|
return $this->plotCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Category by Index.
|
||||||
|
*
|
||||||
|
* @param mixed $index
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotCategoryByIndex($index)
|
||||||
|
{
|
||||||
|
$keys = array_keys($this->plotCategory);
|
||||||
|
if (in_array($index, $keys)) {
|
||||||
|
return $this->plotCategory[$index];
|
||||||
|
} elseif (isset($keys[$index])) {
|
||||||
|
return $this->plotCategory[$keys[$index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Style.
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getPlotStyle()
|
||||||
|
{
|
||||||
|
return $this->plotStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Style.
|
||||||
|
*
|
||||||
|
* @param null|string $plotStyle
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function setPlotStyle($plotStyle)
|
||||||
|
{
|
||||||
|
$this->plotStyle = $plotStyle;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Values.
|
||||||
|
*
|
||||||
|
* @return array of DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotValues()
|
||||||
|
{
|
||||||
|
return $this->plotValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Values by Index.
|
||||||
|
*
|
||||||
|
* @param mixed $index
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function getPlotValuesByIndex($index)
|
||||||
|
{
|
||||||
|
$keys = array_keys($this->plotValues);
|
||||||
|
if (in_array($index, $keys)) {
|
||||||
|
return $this->plotValues[$index];
|
||||||
|
} elseif (isset($keys[$index])) {
|
||||||
|
return $this->plotValues[$keys[$index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Number of Plot Series.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPlotSeriesCount()
|
||||||
|
{
|
||||||
|
return count($this->plotValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Smooth Line.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getSmoothLine()
|
||||||
|
{
|
||||||
|
return $this->smoothLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Smooth Line.
|
||||||
|
*
|
||||||
|
* @param bool $smoothLine
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function setSmoothLine($smoothLine)
|
||||||
|
{
|
||||||
|
$this->smoothLine = $smoothLine;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(Worksheet $worksheet)
|
||||||
|
{
|
||||||
|
foreach ($this->plotValues as $plotValues) {
|
||||||
|
if ($plotValues !== null) {
|
||||||
|
$plotValues->refresh($worksheet, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($this->plotLabel as $plotValues) {
|
||||||
|
if ($plotValues !== null) {
|
||||||
|
$plotValues->refresh($worksheet, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($this->plotCategory as $plotValues) {
|
||||||
|
if ($plotValues !== null) {
|
||||||
|
$plotValues->refresh($worksheet, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
401
PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php
Normal file
401
PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php
Normal file
|
|
@ -0,0 +1,401 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class DataSeriesValues
|
||||||
|
{
|
||||||
|
const DATASERIES_TYPE_STRING = 'String';
|
||||||
|
const DATASERIES_TYPE_NUMBER = 'Number';
|
||||||
|
|
||||||
|
private static $dataTypeValues = [
|
||||||
|
self::DATASERIES_TYPE_STRING,
|
||||||
|
self::DATASERIES_TYPE_NUMBER,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series Data Type.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $dataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series Data Source.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $dataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format Code.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $formatCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Series Point Marker.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $pointMarker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point Count (The number of datapoints in the dataseries).
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $pointCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Values.
|
||||||
|
*
|
||||||
|
* @var array of mixed
|
||||||
|
*/
|
||||||
|
private $dataValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill color (can be array with colors if dataseries have custom colors).
|
||||||
|
*
|
||||||
|
* @var string|string[]
|
||||||
|
*/
|
||||||
|
private $fillColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line Width.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $lineWidth = 12700;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DataSeriesValues object.
|
||||||
|
*
|
||||||
|
* @param string $dataType
|
||||||
|
* @param string $dataSource
|
||||||
|
* @param null|mixed $formatCode
|
||||||
|
* @param int $pointCount
|
||||||
|
* @param mixed $dataValues
|
||||||
|
* @param null|mixed $marker
|
||||||
|
* @param null|string|string[] $fillColor
|
||||||
|
*/
|
||||||
|
public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null)
|
||||||
|
{
|
||||||
|
$this->setDataType($dataType);
|
||||||
|
$this->dataSource = $dataSource;
|
||||||
|
$this->formatCode = $formatCode;
|
||||||
|
$this->pointCount = $pointCount;
|
||||||
|
$this->dataValues = $dataValues;
|
||||||
|
$this->pointMarker = $marker;
|
||||||
|
$this->fillColor = $fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Data Type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDataType()
|
||||||
|
{
|
||||||
|
return $this->dataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Series Data Type.
|
||||||
|
*
|
||||||
|
* @param string $dataType Datatype of this data series
|
||||||
|
* Typical values are:
|
||||||
|
* DataSeriesValues::DATASERIES_TYPE_STRING
|
||||||
|
* Normally used for axis point values
|
||||||
|
* DataSeriesValues::DATASERIES_TYPE_NUMBER
|
||||||
|
* Normally used for chart data values
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setDataType($dataType)
|
||||||
|
{
|
||||||
|
if (!in_array($dataType, self::$dataTypeValues)) {
|
||||||
|
throw new Exception('Invalid datatype for chart data series values');
|
||||||
|
}
|
||||||
|
$this->dataType = $dataType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Data Source (formula).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDataSource()
|
||||||
|
{
|
||||||
|
return $this->dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Series Data Source (formula).
|
||||||
|
*
|
||||||
|
* @param string $dataSource
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setDataSource($dataSource)
|
||||||
|
{
|
||||||
|
$this->dataSource = $dataSource;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Point Marker.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPointMarker()
|
||||||
|
{
|
||||||
|
return $this->pointMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Point Marker.
|
||||||
|
*
|
||||||
|
* @param string $marker
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setPointMarker($marker)
|
||||||
|
{
|
||||||
|
$this->pointMarker = $marker;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Format Code.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFormatCode()
|
||||||
|
{
|
||||||
|
return $this->formatCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Series Format Code.
|
||||||
|
*
|
||||||
|
* @param string $formatCode
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setFormatCode($formatCode)
|
||||||
|
{
|
||||||
|
$this->formatCode = $formatCode;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Point Count.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPointCount()
|
||||||
|
{
|
||||||
|
return $this->pointCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fill color.
|
||||||
|
*
|
||||||
|
* @return string|string[] HEX color or array with HEX colors
|
||||||
|
*/
|
||||||
|
public function getFillColor()
|
||||||
|
{
|
||||||
|
return $this->fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set fill color for series.
|
||||||
|
*
|
||||||
|
* @param string|string[] $color HEX color or array with HEX colors
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setFillColor($color)
|
||||||
|
{
|
||||||
|
if (is_array($color)) {
|
||||||
|
foreach ($color as $colorValue) {
|
||||||
|
$this->validateColor($colorValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->validateColor($color);
|
||||||
|
}
|
||||||
|
$this->fillColor = $color;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for validating hex color.
|
||||||
|
*
|
||||||
|
* @param string $color value for color
|
||||||
|
*
|
||||||
|
* @throws \Exception thrown if color is invalid
|
||||||
|
*
|
||||||
|
* @return bool true if validation was successful
|
||||||
|
*/
|
||||||
|
private function validateColor($color)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
|
||||||
|
throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get line width for series.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLineWidth()
|
||||||
|
{
|
||||||
|
return $this->lineWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set line width for the series.
|
||||||
|
*
|
||||||
|
* @param int $width
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setLineWidth($width)
|
||||||
|
{
|
||||||
|
$minWidth = 12700;
|
||||||
|
$this->lineWidth = max($minWidth, $width);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify if the Data Series is a multi-level or a simple series.
|
||||||
|
*
|
||||||
|
* @return null|bool
|
||||||
|
*/
|
||||||
|
public function isMultiLevelSeries()
|
||||||
|
{
|
||||||
|
if (count($this->dataValues) > 0) {
|
||||||
|
return is_array(array_values($this->dataValues)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the level count of a multi-level Data Series.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function multiLevelCount()
|
||||||
|
{
|
||||||
|
$levelCount = 0;
|
||||||
|
foreach ($this->dataValues as $dataValueSet) {
|
||||||
|
$levelCount = max($levelCount, count($dataValueSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $levelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Series Data Values.
|
||||||
|
*
|
||||||
|
* @return array of mixed
|
||||||
|
*/
|
||||||
|
public function getDataValues()
|
||||||
|
{
|
||||||
|
return $this->dataValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first Series Data value.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getDataValue()
|
||||||
|
{
|
||||||
|
$count = count($this->dataValues);
|
||||||
|
if ($count == 0) {
|
||||||
|
return null;
|
||||||
|
} elseif ($count == 1) {
|
||||||
|
return $this->dataValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->dataValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Series Data Values.
|
||||||
|
*
|
||||||
|
* @param array $dataValues
|
||||||
|
*
|
||||||
|
* @return DataSeriesValues
|
||||||
|
*/
|
||||||
|
public function setDataValues($dataValues)
|
||||||
|
{
|
||||||
|
$this->dataValues = Functions::flattenArray($dataValues);
|
||||||
|
$this->pointCount = count($dataValues);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(Worksheet $worksheet, $flatten = true)
|
||||||
|
{
|
||||||
|
if ($this->dataSource !== null) {
|
||||||
|
$calcEngine = Calculation::getInstance($worksheet->getParent());
|
||||||
|
$newDataValues = Calculation::unwrapResult(
|
||||||
|
$calcEngine->_calculateFormulaValue(
|
||||||
|
'=' . $this->dataSource,
|
||||||
|
null,
|
||||||
|
$worksheet->getCell('A1')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($flatten) {
|
||||||
|
$this->dataValues = Functions::flattenArray($newDataValues);
|
||||||
|
foreach ($this->dataValues as &$dataValue) {
|
||||||
|
if ((!empty($dataValue)) && ($dataValue[0] == '#')) {
|
||||||
|
$dataValue = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($dataValue);
|
||||||
|
} else {
|
||||||
|
[$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
|
||||||
|
$dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange));
|
||||||
|
if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
|
||||||
|
$this->dataValues = Functions::flattenArray($newDataValues);
|
||||||
|
} else {
|
||||||
|
$newArray = array_values(array_shift($newDataValues));
|
||||||
|
foreach ($newArray as $i => $newDataSet) {
|
||||||
|
$newArray[$i] = [$newDataSet];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($newDataValues as $newDataSet) {
|
||||||
|
$i = 0;
|
||||||
|
foreach ($newDataSet as $newDataVal) {
|
||||||
|
array_unshift($newArray[$i++], $newDataVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->dataValues = $newArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->pointCount = count($this->dataValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
PhpOffice/PhpSpreadsheet/Chart/Exception.php
Normal file
9
PhpOffice/PhpSpreadsheet/Chart/Exception.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
|
|
||||||
|
class Exception extends PhpSpreadsheetException
|
||||||
|
{
|
||||||
|
}
|
||||||
455
PhpOffice/PhpSpreadsheet/Chart/GridLines.php
Normal file
455
PhpOffice/PhpSpreadsheet/Chart/GridLines.php
Normal file
|
|
@ -0,0 +1,455 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: Wiktor Trzonkowski
|
||||||
|
* Date: 7/2/14
|
||||||
|
* Time: 2:36 PM.
|
||||||
|
*/
|
||||||
|
class GridLines extends Properties
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Properties of Class:
|
||||||
|
* Object State (State for Minor Tick Mark) @var bool
|
||||||
|
* Line Properties @var array of mixed
|
||||||
|
* Shadow Properties @var array of mixed
|
||||||
|
* Glow Properties @var array of mixed
|
||||||
|
* Soft Properties @var array of mixed.
|
||||||
|
*/
|
||||||
|
private $objectState = false;
|
||||||
|
|
||||||
|
private $lineProperties = [
|
||||||
|
'color' => [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_STANDARD,
|
||||||
|
'value' => null,
|
||||||
|
'alpha' => 0,
|
||||||
|
],
|
||||||
|
'style' => [
|
||||||
|
'width' => '9525',
|
||||||
|
'compound' => self::LINE_STYLE_COMPOUND_SIMPLE,
|
||||||
|
'dash' => self::LINE_STYLE_DASH_SOLID,
|
||||||
|
'cap' => self::LINE_STYLE_CAP_FLAT,
|
||||||
|
'join' => self::LINE_STYLE_JOIN_BEVEL,
|
||||||
|
'arrow' => [
|
||||||
|
'head' => [
|
||||||
|
'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
|
||||||
|
'size' => self::LINE_STYLE_ARROW_SIZE_5,
|
||||||
|
],
|
||||||
|
'end' => [
|
||||||
|
'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
|
||||||
|
'size' => self::LINE_STYLE_ARROW_SIZE_8,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
private $shadowProperties = [
|
||||||
|
'presets' => self::SHADOW_PRESETS_NOSHADOW,
|
||||||
|
'effect' => null,
|
||||||
|
'color' => [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_STANDARD,
|
||||||
|
'value' => 'black',
|
||||||
|
'alpha' => 85,
|
||||||
|
],
|
||||||
|
'size' => [
|
||||||
|
'sx' => null,
|
||||||
|
'sy' => null,
|
||||||
|
'kx' => null,
|
||||||
|
],
|
||||||
|
'blur' => null,
|
||||||
|
'direction' => null,
|
||||||
|
'distance' => null,
|
||||||
|
'algn' => null,
|
||||||
|
'rotWithShape' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
private $glowProperties = [
|
||||||
|
'size' => null,
|
||||||
|
'color' => [
|
||||||
|
'type' => self::EXCEL_COLOR_TYPE_STANDARD,
|
||||||
|
'value' => 'black',
|
||||||
|
'alpha' => 40,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
private $softEdges = [
|
||||||
|
'size' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Object State.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getObjectState()
|
||||||
|
{
|
||||||
|
return $this->objectState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change Object State to True.
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function activateObject()
|
||||||
|
{
|
||||||
|
$this->objectState = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Line Color Properties.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*/
|
||||||
|
public function setLineColorProperties($value, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_STANDARD)
|
||||||
|
{
|
||||||
|
$this->activateObject()
|
||||||
|
->lineProperties['color'] = $this->setColorProperties(
|
||||||
|
$value,
|
||||||
|
$alpha,
|
||||||
|
$type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Line Color Properties.
|
||||||
|
*
|
||||||
|
* @param float $line_width
|
||||||
|
* @param string $compound_type
|
||||||
|
* @param string $dash_type
|
||||||
|
* @param string $cap_type
|
||||||
|
* @param string $join_type
|
||||||
|
* @param string $head_arrow_type
|
||||||
|
* @param string $head_arrow_size
|
||||||
|
* @param string $end_arrow_type
|
||||||
|
* @param string $end_arrow_size
|
||||||
|
*/
|
||||||
|
public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null)
|
||||||
|
{
|
||||||
|
$this->activateObject();
|
||||||
|
($line_width !== null)
|
||||||
|
? $this->lineProperties['style']['width'] = $this->getExcelPointsWidth((float) $line_width)
|
||||||
|
: null;
|
||||||
|
($compound_type !== null)
|
||||||
|
? $this->lineProperties['style']['compound'] = (string) $compound_type
|
||||||
|
: null;
|
||||||
|
($dash_type !== null)
|
||||||
|
? $this->lineProperties['style']['dash'] = (string) $dash_type
|
||||||
|
: null;
|
||||||
|
($cap_type !== null)
|
||||||
|
? $this->lineProperties['style']['cap'] = (string) $cap_type
|
||||||
|
: null;
|
||||||
|
($join_type !== null)
|
||||||
|
? $this->lineProperties['style']['join'] = (string) $join_type
|
||||||
|
: null;
|
||||||
|
($head_arrow_type !== null)
|
||||||
|
? $this->lineProperties['style']['arrow']['head']['type'] = (string) $head_arrow_type
|
||||||
|
: null;
|
||||||
|
($head_arrow_size !== null)
|
||||||
|
? $this->lineProperties['style']['arrow']['head']['size'] = (string) $head_arrow_size
|
||||||
|
: null;
|
||||||
|
($end_arrow_type !== null)
|
||||||
|
? $this->lineProperties['style']['arrow']['end']['type'] = (string) $end_arrow_type
|
||||||
|
: null;
|
||||||
|
($end_arrow_size !== null)
|
||||||
|
? $this->lineProperties['style']['arrow']['end']['size'] = (string) $end_arrow_size
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Color Property.
|
||||||
|
*
|
||||||
|
* @param string $parameter
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineColorProperty($parameter)
|
||||||
|
{
|
||||||
|
return $this->lineProperties['color'][$parameter];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Style Property.
|
||||||
|
*
|
||||||
|
* @param array|string $elements
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineStyleProperty($elements)
|
||||||
|
{
|
||||||
|
return $this->getArrayElementsValue($this->lineProperties['style'], $elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Properties.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
* @param string $color_value
|
||||||
|
* @param int $color_alpha
|
||||||
|
* @param string $color_type
|
||||||
|
*/
|
||||||
|
public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null)
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->activateObject()
|
||||||
|
->setGlowSize($size)
|
||||||
|
->setGlowColor($color_value, $color_alpha, $color_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Glow Color Property.
|
||||||
|
*
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGlowColor($property)
|
||||||
|
{
|
||||||
|
return $this->glowProperties['color'][$property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Glow Size.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGlowSize()
|
||||||
|
{
|
||||||
|
return $this->glowProperties['size'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Size.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setGlowSize($size)
|
||||||
|
{
|
||||||
|
$this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Glow Color.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setGlowColor($color, $alpha, $type)
|
||||||
|
{
|
||||||
|
if ($color !== null) {
|
||||||
|
$this->glowProperties['color']['value'] = (string) $color;
|
||||||
|
}
|
||||||
|
if ($alpha !== null) {
|
||||||
|
$this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
|
||||||
|
}
|
||||||
|
if ($type !== null) {
|
||||||
|
$this->glowProperties['color']['type'] = (string) $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Line Style Arrow Parameters.
|
||||||
|
*
|
||||||
|
* @param string $arrow_selector
|
||||||
|
* @param string $property_selector
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLineStyleArrowParameters($arrow_selector, $property_selector)
|
||||||
|
{
|
||||||
|
return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrow_selector]['size'], $property_selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Properties.
|
||||||
|
*
|
||||||
|
* @param int $sh_presets
|
||||||
|
* @param string $sh_color_value
|
||||||
|
* @param string $sh_color_type
|
||||||
|
* @param int $sh_color_alpha
|
||||||
|
* @param string $sh_blur
|
||||||
|
* @param int $sh_angle
|
||||||
|
* @param float $sh_distance
|
||||||
|
*/
|
||||||
|
public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null)
|
||||||
|
{
|
||||||
|
$this->activateObject()
|
||||||
|
->setShadowPresetsProperties((int) $sh_presets)
|
||||||
|
->setShadowColor(
|
||||||
|
$sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value,
|
||||||
|
$sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($sh_color_alpha),
|
||||||
|
$sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type
|
||||||
|
)
|
||||||
|
->setShadowBlur($sh_blur)
|
||||||
|
->setShadowAngle($sh_angle)
|
||||||
|
->setShadowDistance($sh_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Presets Properties.
|
||||||
|
*
|
||||||
|
* @param int $shadow_presets
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowPresetsProperties($shadow_presets)
|
||||||
|
{
|
||||||
|
$this->shadowProperties['presets'] = $shadow_presets;
|
||||||
|
$this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Properties Values.
|
||||||
|
*
|
||||||
|
* @param array $properties_map
|
||||||
|
* @param mixed &$reference
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
|
||||||
|
{
|
||||||
|
$base_reference = $reference;
|
||||||
|
foreach ($properties_map as $property_key => $property_val) {
|
||||||
|
if (is_array($property_val)) {
|
||||||
|
if ($reference === null) {
|
||||||
|
$reference = &$this->shadowProperties[$property_key];
|
||||||
|
} else {
|
||||||
|
$reference = &$reference[$property_key];
|
||||||
|
}
|
||||||
|
$this->setShadowProperiesMapValues($property_val, $reference);
|
||||||
|
} else {
|
||||||
|
if ($base_reference === null) {
|
||||||
|
$this->shadowProperties[$property_key] = $property_val;
|
||||||
|
} else {
|
||||||
|
$reference[$property_key] = $property_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Color.
|
||||||
|
*
|
||||||
|
* @param string $color
|
||||||
|
* @param int $alpha
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowColor($color, $alpha, $type)
|
||||||
|
{
|
||||||
|
if ($color !== null) {
|
||||||
|
$this->shadowProperties['color']['value'] = (string) $color;
|
||||||
|
}
|
||||||
|
if ($alpha !== null) {
|
||||||
|
$this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
|
||||||
|
}
|
||||||
|
if ($type !== null) {
|
||||||
|
$this->shadowProperties['color']['type'] = (string) $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Blur.
|
||||||
|
*
|
||||||
|
* @param float $blur
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowBlur($blur)
|
||||||
|
{
|
||||||
|
if ($blur !== null) {
|
||||||
|
$this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Angle.
|
||||||
|
*
|
||||||
|
* @param int $angle
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowAngle($angle)
|
||||||
|
{
|
||||||
|
if ($angle !== null) {
|
||||||
|
$this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Shadow Distance.
|
||||||
|
*
|
||||||
|
* @param float $distance
|
||||||
|
*
|
||||||
|
* @return GridLines
|
||||||
|
*/
|
||||||
|
private function setShadowDistance($distance)
|
||||||
|
{
|
||||||
|
if ($distance !== null) {
|
||||||
|
$this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Shadow Property.
|
||||||
|
*
|
||||||
|
* @param string|string[] $elements
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getShadowProperty($elements)
|
||||||
|
{
|
||||||
|
return $this->getArrayElementsValue($this->shadowProperties, $elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Soft Edges Size.
|
||||||
|
*
|
||||||
|
* @param float $size
|
||||||
|
*/
|
||||||
|
public function setSoftEdgesSize($size)
|
||||||
|
{
|
||||||
|
if ($size !== null) {
|
||||||
|
$this->activateObject();
|
||||||
|
$this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Soft Edges Size.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSoftEdgesSize()
|
||||||
|
{
|
||||||
|
return $this->softEdges['size'];
|
||||||
|
}
|
||||||
|
}
|
||||||
483
PhpOffice/PhpSpreadsheet/Chart/Layout.php
Normal file
483
PhpOffice/PhpSpreadsheet/Chart/Layout.php
Normal file
|
|
@ -0,0 +1,483 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
class Layout
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* layoutTarget.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $layoutTarget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* X Mode.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $xMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y Mode.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $yMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* X-Position.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $xPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y-Position.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $yPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* width.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* height.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show legend key
|
||||||
|
* Specifies that legend keys should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showLegendKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show value
|
||||||
|
* Specifies that the value should be shown in a data label.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showVal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show category name
|
||||||
|
* Specifies that the category name should be shown in the data label.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showCatName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show data series name
|
||||||
|
* Specifies that the series name should be shown in the data label.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showSerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show percentage
|
||||||
|
* Specifies that the percentage should be shown in the data label.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showPercent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show bubble size.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showBubbleSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show leader lines
|
||||||
|
* Specifies that leader lines should be shown for the data label.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $showLeaderLines;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Layout.
|
||||||
|
*
|
||||||
|
* @param array $layout
|
||||||
|
*/
|
||||||
|
public function __construct(array $layout = [])
|
||||||
|
{
|
||||||
|
if (isset($layout['layoutTarget'])) {
|
||||||
|
$this->layoutTarget = $layout['layoutTarget'];
|
||||||
|
}
|
||||||
|
if (isset($layout['xMode'])) {
|
||||||
|
$this->xMode = $layout['xMode'];
|
||||||
|
}
|
||||||
|
if (isset($layout['yMode'])) {
|
||||||
|
$this->yMode = $layout['yMode'];
|
||||||
|
}
|
||||||
|
if (isset($layout['x'])) {
|
||||||
|
$this->xPos = (float) $layout['x'];
|
||||||
|
}
|
||||||
|
if (isset($layout['y'])) {
|
||||||
|
$this->yPos = (float) $layout['y'];
|
||||||
|
}
|
||||||
|
if (isset($layout['w'])) {
|
||||||
|
$this->width = (float) $layout['w'];
|
||||||
|
}
|
||||||
|
if (isset($layout['h'])) {
|
||||||
|
$this->height = (float) $layout['h'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Layout Target.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLayoutTarget()
|
||||||
|
{
|
||||||
|
return $this->layoutTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Layout Target.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setLayoutTarget($value)
|
||||||
|
{
|
||||||
|
$this->layoutTarget = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get X-Mode.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getXMode()
|
||||||
|
{
|
||||||
|
return $this->xMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set X-Mode.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setXMode($value)
|
||||||
|
{
|
||||||
|
$this->xMode = (string) $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Y-Mode.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getYMode()
|
||||||
|
{
|
||||||
|
return $this->yMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Y-Mode.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setYMode($value)
|
||||||
|
{
|
||||||
|
$this->yMode = (string) $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get X-Position.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
public function getXPosition()
|
||||||
|
{
|
||||||
|
return $this->xPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set X-Position.
|
||||||
|
*
|
||||||
|
* @param float $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setXPosition($value)
|
||||||
|
{
|
||||||
|
$this->xPos = (float) $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Y-Position.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
public function getYPosition()
|
||||||
|
{
|
||||||
|
return $this->yPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Y-Position.
|
||||||
|
*
|
||||||
|
* @param float $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setYPosition($value)
|
||||||
|
{
|
||||||
|
$this->yPos = (float) $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Width.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
public function getWidth()
|
||||||
|
{
|
||||||
|
return $this->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Width.
|
||||||
|
*
|
||||||
|
* @param float $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setWidth($value)
|
||||||
|
{
|
||||||
|
$this->width = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Height.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
public function getHeight()
|
||||||
|
{
|
||||||
|
return $this->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Height.
|
||||||
|
*
|
||||||
|
* @param float $value
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setHeight($value)
|
||||||
|
{
|
||||||
|
$this->height = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show legend key.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowLegendKey()
|
||||||
|
{
|
||||||
|
return $this->showLegendKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show legend key
|
||||||
|
* Specifies that legend keys should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show legend key
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowLegendKey($value)
|
||||||
|
{
|
||||||
|
$this->showLegendKey = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show value.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowVal()
|
||||||
|
{
|
||||||
|
return $this->showVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show val
|
||||||
|
* Specifies that the value should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show val
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowVal($value)
|
||||||
|
{
|
||||||
|
$this->showVal = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show category name.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowCatName()
|
||||||
|
{
|
||||||
|
return $this->showCatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show cat name
|
||||||
|
* Specifies that the category name should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show cat name
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowCatName($value)
|
||||||
|
{
|
||||||
|
$this->showCatName = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show data series name.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowSerName()
|
||||||
|
{
|
||||||
|
return $this->showSerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show ser name
|
||||||
|
* Specifies that the series name should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show series name
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowSerName($value)
|
||||||
|
{
|
||||||
|
$this->showSerName = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show percentage.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowPercent()
|
||||||
|
{
|
||||||
|
return $this->showPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show percentage
|
||||||
|
* Specifies that the percentage should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show percentage
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowPercent($value)
|
||||||
|
{
|
||||||
|
$this->showPercent = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show bubble size.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowBubbleSize()
|
||||||
|
{
|
||||||
|
return $this->showBubbleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show bubble size
|
||||||
|
* Specifies that the bubble size should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show bubble size
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowBubbleSize($value)
|
||||||
|
{
|
||||||
|
$this->showBubbleSize = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get show leader lines.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getShowLeaderLines()
|
||||||
|
{
|
||||||
|
return $this->showLeaderLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set show leader lines
|
||||||
|
* Specifies that leader lines should be shown in data labels.
|
||||||
|
*
|
||||||
|
* @param bool $value Show leader lines
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function setShowLeaderLines($value)
|
||||||
|
{
|
||||||
|
$this->showLeaderLines = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
158
PhpOffice/PhpSpreadsheet/Chart/Legend.php
Normal file
158
PhpOffice/PhpSpreadsheet/Chart/Legend.php
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
class Legend
|
||||||
|
{
|
||||||
|
/** Legend positions */
|
||||||
|
const XL_LEGEND_POSITION_BOTTOM = -4107; // Below the chart.
|
||||||
|
const XL_LEGEND_POSITION_CORNER = 2; // In the upper right-hand corner of the chart border.
|
||||||
|
const XL_LEGEND_POSITION_CUSTOM = -4161; // A custom position.
|
||||||
|
const XL_LEGEND_POSITION_LEFT = -4131; // Left of the chart.
|
||||||
|
const XL_LEGEND_POSITION_RIGHT = -4152; // Right of the chart.
|
||||||
|
const XL_LEGEND_POSITION_TOP = -4160; // Above the chart.
|
||||||
|
|
||||||
|
const POSITION_RIGHT = 'r';
|
||||||
|
const POSITION_LEFT = 'l';
|
||||||
|
const POSITION_BOTTOM = 'b';
|
||||||
|
const POSITION_TOP = 't';
|
||||||
|
const POSITION_TOPRIGHT = 'tr';
|
||||||
|
|
||||||
|
private static $positionXLref = [
|
||||||
|
self::XL_LEGEND_POSITION_BOTTOM => self::POSITION_BOTTOM,
|
||||||
|
self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT,
|
||||||
|
self::XL_LEGEND_POSITION_CUSTOM => '??',
|
||||||
|
self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT,
|
||||||
|
self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT,
|
||||||
|
self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legend position.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $position = self::POSITION_RIGHT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow overlay of other elements?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $overlay = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legend Layout.
|
||||||
|
*
|
||||||
|
* @var Layout
|
||||||
|
*/
|
||||||
|
private $layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Legend.
|
||||||
|
*
|
||||||
|
* @param string $position
|
||||||
|
* @param null|Layout $layout
|
||||||
|
* @param bool $overlay
|
||||||
|
*/
|
||||||
|
public function __construct($position = self::POSITION_RIGHT, Layout $layout = null, $overlay = false)
|
||||||
|
{
|
||||||
|
$this->setPosition($position);
|
||||||
|
$this->layout = $layout;
|
||||||
|
$this->setOverlay($overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get legend position as an excel string value.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPosition()
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get legend position using an excel string value.
|
||||||
|
*
|
||||||
|
* @param string $position see self::POSITION_*
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setPosition($position)
|
||||||
|
{
|
||||||
|
if (!in_array($position, self::$positionXLref)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position = $position;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get legend position as an Excel internal numeric value.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPositionXL()
|
||||||
|
{
|
||||||
|
return array_search($this->position, self::$positionXLref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set legend position using an Excel internal numeric value.
|
||||||
|
*
|
||||||
|
* @param int $positionXL see self::XL_LEGEND_POSITION_*
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setPositionXL($positionXL)
|
||||||
|
{
|
||||||
|
if (!isset(self::$positionXLref[$positionXL])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position = self::$positionXLref[$positionXL];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get allow overlay of other elements?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getOverlay()
|
||||||
|
{
|
||||||
|
return $this->overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set allow overlay of other elements?
|
||||||
|
*
|
||||||
|
* @param bool $overlay
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setOverlay($overlay)
|
||||||
|
{
|
||||||
|
if (!is_bool($overlay)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->overlay = $overlay;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Layout.
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function getLayout()
|
||||||
|
{
|
||||||
|
return $this->layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
112
PhpOffice/PhpSpreadsheet/Chart/PlotArea.php
Normal file
112
PhpOffice/PhpSpreadsheet/Chart/PlotArea.php
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class PlotArea
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* PlotArea Layout.
|
||||||
|
*
|
||||||
|
* @var Layout
|
||||||
|
*/
|
||||||
|
private $layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot Series.
|
||||||
|
*
|
||||||
|
* @var DataSeries[]
|
||||||
|
*/
|
||||||
|
private $plotSeries = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new PlotArea.
|
||||||
|
*
|
||||||
|
* @param null|Layout $layout
|
||||||
|
* @param DataSeries[] $plotSeries
|
||||||
|
*/
|
||||||
|
public function __construct(Layout $layout = null, array $plotSeries = [])
|
||||||
|
{
|
||||||
|
$this->layout = $layout;
|
||||||
|
$this->plotSeries = $plotSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Layout.
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function getLayout()
|
||||||
|
{
|
||||||
|
return $this->layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Number of Plot Groups.
|
||||||
|
*
|
||||||
|
* @return array of DataSeries
|
||||||
|
*/
|
||||||
|
public function getPlotGroupCount()
|
||||||
|
{
|
||||||
|
return count($this->plotSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Number of Plot Series.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getPlotSeriesCount()
|
||||||
|
{
|
||||||
|
$seriesCount = 0;
|
||||||
|
foreach ($this->plotSeries as $plot) {
|
||||||
|
$seriesCount += $plot->getPlotSeriesCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $seriesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Series.
|
||||||
|
*
|
||||||
|
* @return array of DataSeries
|
||||||
|
*/
|
||||||
|
public function getPlotGroup()
|
||||||
|
{
|
||||||
|
return $this->plotSeries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Plot Series by Index.
|
||||||
|
*
|
||||||
|
* @param mixed $index
|
||||||
|
*
|
||||||
|
* @return DataSeries
|
||||||
|
*/
|
||||||
|
public function getPlotGroupByIndex($index)
|
||||||
|
{
|
||||||
|
return $this->plotSeries[$index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Plot Series.
|
||||||
|
*
|
||||||
|
* @param DataSeries[] $plotSeries
|
||||||
|
*
|
||||||
|
* @return PlotArea
|
||||||
|
*/
|
||||||
|
public function setPlotSeries(array $plotSeries)
|
||||||
|
{
|
||||||
|
$this->plotSeries = $plotSeries;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(Worksheet $worksheet)
|
||||||
|
{
|
||||||
|
foreach ($this->plotSeries as $plotSeries) {
|
||||||
|
$plotSeries->refresh($worksheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
369
PhpOffice/PhpSpreadsheet/Chart/Properties.php
Normal file
369
PhpOffice/PhpSpreadsheet/Chart/Properties.php
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: nhw2h8s
|
||||||
|
* Date: 7/2/14
|
||||||
|
* Time: 5:45 PM.
|
||||||
|
*/
|
||||||
|
abstract class Properties
|
||||||
|
{
|
||||||
|
const
|
||||||
|
EXCEL_COLOR_TYPE_STANDARD = 'prstClr';
|
||||||
|
const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr';
|
||||||
|
const EXCEL_COLOR_TYPE_ARGB = 'srgbClr';
|
||||||
|
|
||||||
|
const
|
||||||
|
AXIS_LABELS_LOW = 'low';
|
||||||
|
const AXIS_LABELS_HIGH = 'high';
|
||||||
|
const AXIS_LABELS_NEXT_TO = 'nextTo';
|
||||||
|
const AXIS_LABELS_NONE = 'none';
|
||||||
|
|
||||||
|
const
|
||||||
|
TICK_MARK_NONE = 'none';
|
||||||
|
const TICK_MARK_INSIDE = 'in';
|
||||||
|
const TICK_MARK_OUTSIDE = 'out';
|
||||||
|
const TICK_MARK_CROSS = 'cross';
|
||||||
|
|
||||||
|
const
|
||||||
|
HORIZONTAL_CROSSES_AUTOZERO = 'autoZero';
|
||||||
|
const HORIZONTAL_CROSSES_MAXIMUM = 'max';
|
||||||
|
|
||||||
|
const
|
||||||
|
FORMAT_CODE_GENERAL = 'General';
|
||||||
|
const FORMAT_CODE_NUMBER = '#,##0.00';
|
||||||
|
const FORMAT_CODE_CURRENCY = '$#,##0.00';
|
||||||
|
const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)';
|
||||||
|
const FORMAT_CODE_DATE = 'm/d/yyyy';
|
||||||
|
const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM';
|
||||||
|
const FORMAT_CODE_PERCENTAGE = '0.00%';
|
||||||
|
const FORMAT_CODE_FRACTION = '# ?/?';
|
||||||
|
const FORMAT_CODE_SCIENTIFIC = '0.00E+00';
|
||||||
|
const FORMAT_CODE_TEXT = '@';
|
||||||
|
const FORMAT_CODE_SPECIAL = '00000';
|
||||||
|
|
||||||
|
const
|
||||||
|
ORIENTATION_NORMAL = 'minMax';
|
||||||
|
const ORIENTATION_REVERSED = 'maxMin';
|
||||||
|
|
||||||
|
const
|
||||||
|
LINE_STYLE_COMPOUND_SIMPLE = 'sng';
|
||||||
|
const LINE_STYLE_COMPOUND_DOUBLE = 'dbl';
|
||||||
|
const LINE_STYLE_COMPOUND_THICKTHIN = 'thickThin';
|
||||||
|
const LINE_STYLE_COMPOUND_THINTHICK = 'thinThick';
|
||||||
|
const LINE_STYLE_COMPOUND_TRIPLE = 'tri';
|
||||||
|
const LINE_STYLE_DASH_SOLID = 'solid';
|
||||||
|
const LINE_STYLE_DASH_ROUND_DOT = 'sysDot';
|
||||||
|
const LINE_STYLE_DASH_SQUERE_DOT = 'sysDash';
|
||||||
|
const LINE_STYPE_DASH_DASH = 'dash';
|
||||||
|
const LINE_STYLE_DASH_DASH_DOT = 'dashDot';
|
||||||
|
const LINE_STYLE_DASH_LONG_DASH = 'lgDash';
|
||||||
|
const LINE_STYLE_DASH_LONG_DASH_DOT = 'lgDashDot';
|
||||||
|
const LINE_STYLE_DASH_LONG_DASH_DOT_DOT = 'lgDashDotDot';
|
||||||
|
const LINE_STYLE_CAP_SQUARE = 'sq';
|
||||||
|
const LINE_STYLE_CAP_ROUND = 'rnd';
|
||||||
|
const LINE_STYLE_CAP_FLAT = 'flat';
|
||||||
|
const LINE_STYLE_JOIN_ROUND = 'bevel';
|
||||||
|
const LINE_STYLE_JOIN_MITER = 'miter';
|
||||||
|
const LINE_STYLE_JOIN_BEVEL = 'bevel';
|
||||||
|
const LINE_STYLE_ARROW_TYPE_NOARROW = null;
|
||||||
|
const LINE_STYLE_ARROW_TYPE_ARROW = 'triangle';
|
||||||
|
const LINE_STYLE_ARROW_TYPE_OPEN = 'arrow';
|
||||||
|
const LINE_STYLE_ARROW_TYPE_STEALTH = 'stealth';
|
||||||
|
const LINE_STYLE_ARROW_TYPE_DIAMOND = 'diamond';
|
||||||
|
const LINE_STYLE_ARROW_TYPE_OVAL = 'oval';
|
||||||
|
const LINE_STYLE_ARROW_SIZE_1 = 1;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_2 = 2;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_3 = 3;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_4 = 4;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_5 = 5;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_6 = 6;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_7 = 7;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_8 = 8;
|
||||||
|
const LINE_STYLE_ARROW_SIZE_9 = 9;
|
||||||
|
|
||||||
|
const
|
||||||
|
SHADOW_PRESETS_NOSHADOW = null;
|
||||||
|
const SHADOW_PRESETS_OUTER_BOTTTOM_RIGHT = 1;
|
||||||
|
const SHADOW_PRESETS_OUTER_BOTTOM = 2;
|
||||||
|
const SHADOW_PRESETS_OUTER_BOTTOM_LEFT = 3;
|
||||||
|
const SHADOW_PRESETS_OUTER_RIGHT = 4;
|
||||||
|
const SHADOW_PRESETS_OUTER_CENTER = 5;
|
||||||
|
const SHADOW_PRESETS_OUTER_LEFT = 6;
|
||||||
|
const SHADOW_PRESETS_OUTER_TOP_RIGHT = 7;
|
||||||
|
const SHADOW_PRESETS_OUTER_TOP = 8;
|
||||||
|
const SHADOW_PRESETS_OUTER_TOP_LEFT = 9;
|
||||||
|
const SHADOW_PRESETS_INNER_BOTTTOM_RIGHT = 10;
|
||||||
|
const SHADOW_PRESETS_INNER_BOTTOM = 11;
|
||||||
|
const SHADOW_PRESETS_INNER_BOTTOM_LEFT = 12;
|
||||||
|
const SHADOW_PRESETS_INNER_RIGHT = 13;
|
||||||
|
const SHADOW_PRESETS_INNER_CENTER = 14;
|
||||||
|
const SHADOW_PRESETS_INNER_LEFT = 15;
|
||||||
|
const SHADOW_PRESETS_INNER_TOP_RIGHT = 16;
|
||||||
|
const SHADOW_PRESETS_INNER_TOP = 17;
|
||||||
|
const SHADOW_PRESETS_INNER_TOP_LEFT = 18;
|
||||||
|
const SHADOW_PRESETS_PERSPECTIVE_BELOW = 19;
|
||||||
|
const SHADOW_PRESETS_PERSPECTIVE_UPPER_RIGHT = 20;
|
||||||
|
const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
|
||||||
|
const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
|
||||||
|
const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param float $width
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
protected function getExcelPointsWidth($width)
|
||||||
|
{
|
||||||
|
return $width * 12700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param float $angle
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
protected function getExcelPointsAngle($angle)
|
||||||
|
{
|
||||||
|
return $angle * 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTrueAlpha($alpha)
|
||||||
|
{
|
||||||
|
return (string) 100 - $alpha . '000';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setColorProperties($color, $alpha, $type)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => (string) $type,
|
||||||
|
'value' => (string) $color,
|
||||||
|
'alpha' => (string) $this->getTrueAlpha($alpha),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLineStyleArrowSize($array_selector, $array_kay_selector)
|
||||||
|
{
|
||||||
|
$sizes = [
|
||||||
|
1 => ['w' => 'sm', 'len' => 'sm'],
|
||||||
|
2 => ['w' => 'sm', 'len' => 'med'],
|
||||||
|
3 => ['w' => 'sm', 'len' => 'lg'],
|
||||||
|
4 => ['w' => 'med', 'len' => 'sm'],
|
||||||
|
5 => ['w' => 'med', 'len' => 'med'],
|
||||||
|
6 => ['w' => 'med', 'len' => 'lg'],
|
||||||
|
7 => ['w' => 'lg', 'len' => 'sm'],
|
||||||
|
8 => ['w' => 'lg', 'len' => 'med'],
|
||||||
|
9 => ['w' => 'lg', 'len' => 'lg'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $sizes[$array_selector][$array_kay_selector];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getShadowPresetsMap($shadow_presets_option)
|
||||||
|
{
|
||||||
|
$presets_options = [
|
||||||
|
//OUTER
|
||||||
|
1 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '2700000',
|
||||||
|
'algn' => 'tl',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '5400000',
|
||||||
|
'algn' => 't',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
3 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '8100000',
|
||||||
|
'algn' => 'tr',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
4 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'algn' => 'l',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
5 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'size' => [
|
||||||
|
'sx' => '102000',
|
||||||
|
'sy' => '102000',
|
||||||
|
],
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '38100',
|
||||||
|
'algn' => 'ctr',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
6 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '10800000',
|
||||||
|
'algn' => 'r',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
7 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '18900000',
|
||||||
|
'algn' => 'bl',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
8 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '16200000',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
9 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '50800',
|
||||||
|
'distance' => '38100',
|
||||||
|
'direction' => '13500000',
|
||||||
|
'algn' => 'br',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
//INNER
|
||||||
|
10 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '2700000',
|
||||||
|
],
|
||||||
|
11 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '5400000',
|
||||||
|
],
|
||||||
|
12 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '8100000',
|
||||||
|
],
|
||||||
|
13 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
],
|
||||||
|
14 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '114300',
|
||||||
|
],
|
||||||
|
15 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '10800000',
|
||||||
|
],
|
||||||
|
16 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '18900000',
|
||||||
|
],
|
||||||
|
17 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '16200000',
|
||||||
|
],
|
||||||
|
18 => [
|
||||||
|
'effect' => 'innerShdw',
|
||||||
|
'blur' => '63500',
|
||||||
|
'distance' => '50800',
|
||||||
|
'direction' => '13500000',
|
||||||
|
],
|
||||||
|
//perspective
|
||||||
|
19 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '152400',
|
||||||
|
'distance' => '317500',
|
||||||
|
'size' => [
|
||||||
|
'sx' => '90000',
|
||||||
|
'sy' => '-19000',
|
||||||
|
],
|
||||||
|
'direction' => '5400000',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
20 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '76200',
|
||||||
|
'direction' => '18900000',
|
||||||
|
'size' => [
|
||||||
|
'sy' => '23000',
|
||||||
|
'kx' => '-1200000',
|
||||||
|
],
|
||||||
|
'algn' => 'bl',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
21 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '76200',
|
||||||
|
'direction' => '13500000',
|
||||||
|
'size' => [
|
||||||
|
'sy' => '23000',
|
||||||
|
'kx' => '1200000',
|
||||||
|
],
|
||||||
|
'algn' => 'br',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
22 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '76200',
|
||||||
|
'distance' => '12700',
|
||||||
|
'direction' => '2700000',
|
||||||
|
'size' => [
|
||||||
|
'sy' => '-23000',
|
||||||
|
'kx' => '-800400',
|
||||||
|
],
|
||||||
|
'algn' => 'bl',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
23 => [
|
||||||
|
'effect' => 'outerShdw',
|
||||||
|
'blur' => '76200',
|
||||||
|
'distance' => '12700',
|
||||||
|
'direction' => '8100000',
|
||||||
|
'size' => [
|
||||||
|
'sy' => '-23000',
|
||||||
|
'kx' => '800400',
|
||||||
|
],
|
||||||
|
'algn' => 'br',
|
||||||
|
'rotWithShape' => '0',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $presets_options[$shadow_presets_option];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getArrayElementsValue($properties, $elements)
|
||||||
|
{
|
||||||
|
$reference = &$properties;
|
||||||
|
if (!is_array($elements)) {
|
||||||
|
return $reference[$elements];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($elements as $keys) {
|
||||||
|
$reference = &$reference[$keys];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $reference;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php
Normal file
24
PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||||
|
|
||||||
|
interface IRenderer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* IRenderer constructor.
|
||||||
|
*
|
||||||
|
* @param \PhpOffice\PhpSpreadsheet\Chart\Chart $chart
|
||||||
|
*/
|
||||||
|
public function __construct(Chart $chart);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the chart to given file (or stream).
|
||||||
|
*
|
||||||
|
* @param string $filename Name of the file render to
|
||||||
|
*
|
||||||
|
* @return bool true on success
|
||||||
|
*/
|
||||||
|
public function render($filename);
|
||||||
|
}
|
||||||
856
PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php
Normal file
856
PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php
Normal file
|
|
@ -0,0 +1,856 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/Polyfill.php';
|
||||||
|
|
||||||
|
class JpGraph implements IRenderer
|
||||||
|
{
|
||||||
|
private static $width = 640;
|
||||||
|
|
||||||
|
private static $height = 480;
|
||||||
|
|
||||||
|
private static $colourSet = [
|
||||||
|
'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
|
||||||
|
'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
|
||||||
|
'mediumblue', 'magenta', 'sandybrown', 'cyan',
|
||||||
|
'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
|
||||||
|
'goldenrod2',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $markSet;
|
||||||
|
|
||||||
|
private $chart;
|
||||||
|
|
||||||
|
private $graph;
|
||||||
|
|
||||||
|
private static $plotColour = 0;
|
||||||
|
|
||||||
|
private static $plotMark = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new jpgraph.
|
||||||
|
*
|
||||||
|
* @param Chart $chart
|
||||||
|
*/
|
||||||
|
public function __construct(Chart $chart)
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
$this->graph = null;
|
||||||
|
$this->chart = $chart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function init()
|
||||||
|
{
|
||||||
|
static $loaded = false;
|
||||||
|
if ($loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
\JpGraph\JpGraph::load();
|
||||||
|
\JpGraph\JpGraph::module('bar');
|
||||||
|
\JpGraph\JpGraph::module('contour');
|
||||||
|
\JpGraph\JpGraph::module('line');
|
||||||
|
\JpGraph\JpGraph::module('pie');
|
||||||
|
\JpGraph\JpGraph::module('pie3d');
|
||||||
|
\JpGraph\JpGraph::module('radar');
|
||||||
|
\JpGraph\JpGraph::module('regstat');
|
||||||
|
\JpGraph\JpGraph::module('scatter');
|
||||||
|
\JpGraph\JpGraph::module('stock');
|
||||||
|
|
||||||
|
self::$markSet = [
|
||||||
|
'diamond' => MARK_DIAMOND,
|
||||||
|
'square' => MARK_SQUARE,
|
||||||
|
'triangle' => MARK_UTRIANGLE,
|
||||||
|
'x' => MARK_X,
|
||||||
|
'star' => MARK_STAR,
|
||||||
|
'dot' => MARK_FILLEDCIRCLE,
|
||||||
|
'dash' => MARK_DTRIANGLE,
|
||||||
|
'circle' => MARK_CIRCLE,
|
||||||
|
'plus' => MARK_CROSS,
|
||||||
|
];
|
||||||
|
|
||||||
|
$loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatPointMarker($seriesPlot, $markerID)
|
||||||
|
{
|
||||||
|
$plotMarkKeys = array_keys(self::$markSet);
|
||||||
|
if ($markerID === null) {
|
||||||
|
// Use default plot marker (next marker in the series)
|
||||||
|
self::$plotMark %= count(self::$markSet);
|
||||||
|
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
|
||||||
|
} elseif ($markerID !== 'none') {
|
||||||
|
// Use specified plot marker (if it exists)
|
||||||
|
if (isset(self::$markSet[$markerID])) {
|
||||||
|
$seriesPlot->mark->SetType(self::$markSet[$markerID]);
|
||||||
|
} else {
|
||||||
|
// If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
|
||||||
|
self::$plotMark %= count(self::$markSet);
|
||||||
|
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hide plot marker
|
||||||
|
$seriesPlot->mark->Hide();
|
||||||
|
}
|
||||||
|
$seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
|
||||||
|
$seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
|
||||||
|
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
|
||||||
|
|
||||||
|
return $seriesPlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '')
|
||||||
|
{
|
||||||
|
$datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode();
|
||||||
|
if ($datasetLabelFormatCode !== null) {
|
||||||
|
// Retrieve any label formatting code
|
||||||
|
$datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$testCurrentIndex = 0;
|
||||||
|
foreach ($datasetLabels as $i => $datasetLabel) {
|
||||||
|
if (is_array($datasetLabel)) {
|
||||||
|
if ($rotation == 'bar') {
|
||||||
|
$datasetLabels[$i] = implode(' ', $datasetLabel);
|
||||||
|
} else {
|
||||||
|
$datasetLabel = array_reverse($datasetLabel);
|
||||||
|
$datasetLabels[$i] = implode("\n", $datasetLabel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Format labels according to any formatting code
|
||||||
|
if ($datasetLabelFormatCode !== null) {
|
||||||
|
$datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $datasetLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function percentageSumCalculation($groupID, $seriesCount)
|
||||||
|
{
|
||||||
|
$sumValues = [];
|
||||||
|
// Adjust our values to a percentage value across all series in the group
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
if ($i == 0) {
|
||||||
|
$sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
} else {
|
||||||
|
$nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
foreach ($nextValues as $k => $value) {
|
||||||
|
if (isset($sumValues[$k])) {
|
||||||
|
$sumValues[$k] += $value;
|
||||||
|
} else {
|
||||||
|
$sumValues[$k] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sumValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function percentageAdjustValues($dataValues, $sumValues)
|
||||||
|
{
|
||||||
|
foreach ($dataValues as $k => $dataValue) {
|
||||||
|
$dataValues[$k] = $dataValue / $sumValues[$k] * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dataValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCaption($captionElement)
|
||||||
|
{
|
||||||
|
// Read any caption
|
||||||
|
$caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
|
||||||
|
// Test if we have a title caption to display
|
||||||
|
if ($caption !== null) {
|
||||||
|
// If we do, it could be a plain string or an array
|
||||||
|
if (is_array($caption)) {
|
||||||
|
// Implode an array to a plain string
|
||||||
|
$caption = implode('', $caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderTitle()
|
||||||
|
{
|
||||||
|
$title = $this->getCaption($this->chart->getTitle());
|
||||||
|
if ($title !== null) {
|
||||||
|
$this->graph->title->Set($title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderLegend()
|
||||||
|
{
|
||||||
|
$legend = $this->chart->getLegend();
|
||||||
|
if ($legend !== null) {
|
||||||
|
$legendPosition = $legend->getPosition();
|
||||||
|
switch ($legendPosition) {
|
||||||
|
case 'r':
|
||||||
|
$this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right
|
||||||
|
$this->graph->legend->SetColumns(1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
$this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left
|
||||||
|
$this->graph->legend->SetColumns(1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
$this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
$this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right
|
||||||
|
$this->graph->legend->SetColumns(1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->graph->legend->Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderCartesianPlotArea($type = 'textlin')
|
||||||
|
{
|
||||||
|
$this->graph = new \Graph(self::$width, self::$height);
|
||||||
|
$this->graph->SetScale($type);
|
||||||
|
|
||||||
|
$this->renderTitle();
|
||||||
|
|
||||||
|
// Rotate for bar rather than column chart
|
||||||
|
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
|
||||||
|
$reverse = $rotation == 'bar';
|
||||||
|
|
||||||
|
$xAxisLabel = $this->chart->getXAxisLabel();
|
||||||
|
if ($xAxisLabel !== null) {
|
||||||
|
$title = $this->getCaption($xAxisLabel);
|
||||||
|
if ($title !== null) {
|
||||||
|
$this->graph->xaxis->SetTitle($title, 'center');
|
||||||
|
$this->graph->xaxis->title->SetMargin(35);
|
||||||
|
if ($reverse) {
|
||||||
|
$this->graph->xaxis->title->SetAngle(90);
|
||||||
|
$this->graph->xaxis->title->SetMargin(90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$yAxisLabel = $this->chart->getYAxisLabel();
|
||||||
|
if ($yAxisLabel !== null) {
|
||||||
|
$title = $this->getCaption($yAxisLabel);
|
||||||
|
if ($title !== null) {
|
||||||
|
$this->graph->yaxis->SetTitle($title, 'center');
|
||||||
|
if ($reverse) {
|
||||||
|
$this->graph->yaxis->title->SetAngle(0);
|
||||||
|
$this->graph->yaxis->title->SetMargin(-55);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPiePlotArea()
|
||||||
|
{
|
||||||
|
$this->graph = new \PieGraph(self::$width, self::$height);
|
||||||
|
|
||||||
|
$this->renderTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderRadarPlotArea()
|
||||||
|
{
|
||||||
|
$this->graph = new \RadarGraph(self::$width, self::$height);
|
||||||
|
$this->graph->SetScale('lin');
|
||||||
|
|
||||||
|
$this->renderTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d')
|
||||||
|
{
|
||||||
|
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
|
||||||
|
|
||||||
|
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
|
||||||
|
if ($labelCount > 0) {
|
||||||
|
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
|
||||||
|
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
|
||||||
|
$this->graph->xaxis->SetTickLabels($datasetLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
if ($grouping == 'percentStacked') {
|
||||||
|
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
|
||||||
|
|
||||||
|
if ($grouping == 'percentStacked') {
|
||||||
|
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in any missing values in the $dataValues array
|
||||||
|
$testCurrentIndex = 0;
|
||||||
|
foreach ($dataValues as $k => $dataValue) {
|
||||||
|
while ($k != $testCurrentIndex) {
|
||||||
|
$dataValues[$testCurrentIndex] = null;
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesPlot = new \LinePlot($dataValues);
|
||||||
|
if ($combination) {
|
||||||
|
$seriesPlot->SetBarCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filled) {
|
||||||
|
$seriesPlot->SetFilled(true);
|
||||||
|
$seriesPlot->SetColor('black');
|
||||||
|
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
|
||||||
|
} else {
|
||||||
|
// Set the appropriate plot marker
|
||||||
|
$this->formatPointMarker($seriesPlot, $marker);
|
||||||
|
}
|
||||||
|
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
|
||||||
|
$seriesPlot->SetLegend($dataLabel);
|
||||||
|
|
||||||
|
$seriesPlots[] = $seriesPlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grouping == 'standard') {
|
||||||
|
$groupPlot = $seriesPlots;
|
||||||
|
} else {
|
||||||
|
$groupPlot = new \AccLinePlot($seriesPlots);
|
||||||
|
}
|
||||||
|
$this->graph->Add($groupPlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotBar($groupID, $dimensions = '2d')
|
||||||
|
{
|
||||||
|
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
|
||||||
|
// Rotate for bar rather than column chart
|
||||||
|
if (($groupID == 0) && ($rotation == 'bar')) {
|
||||||
|
$this->graph->Set90AndMargin();
|
||||||
|
}
|
||||||
|
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
|
||||||
|
|
||||||
|
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
|
||||||
|
if ($labelCount > 0) {
|
||||||
|
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
|
||||||
|
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation);
|
||||||
|
// Rotate for bar rather than column chart
|
||||||
|
if ($rotation == 'bar') {
|
||||||
|
$datasetLabels = array_reverse($datasetLabels);
|
||||||
|
$this->graph->yaxis->SetPos('max');
|
||||||
|
$this->graph->yaxis->SetLabelAlign('center', 'top');
|
||||||
|
$this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
|
||||||
|
}
|
||||||
|
$this->graph->xaxis->SetTickLabels($datasetLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
if ($grouping == 'percentStacked') {
|
||||||
|
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($j = 0; $j < $seriesCount; ++$j) {
|
||||||
|
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
|
||||||
|
if ($grouping == 'percentStacked') {
|
||||||
|
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in any missing values in the $dataValues array
|
||||||
|
$testCurrentIndex = 0;
|
||||||
|
foreach ($dataValues as $k => $dataValue) {
|
||||||
|
while ($k != $testCurrentIndex) {
|
||||||
|
$dataValues[$testCurrentIndex] = null;
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the $dataValues order for bar rather than column chart
|
||||||
|
if ($rotation == 'bar') {
|
||||||
|
$dataValues = array_reverse($dataValues);
|
||||||
|
}
|
||||||
|
$seriesPlot = new \BarPlot($dataValues);
|
||||||
|
$seriesPlot->SetColor('black');
|
||||||
|
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
|
||||||
|
if ($dimensions == '3d') {
|
||||||
|
$seriesPlot->SetShadow();
|
||||||
|
}
|
||||||
|
if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
|
||||||
|
$dataLabel = '';
|
||||||
|
} else {
|
||||||
|
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
|
||||||
|
}
|
||||||
|
$seriesPlot->SetLegend($dataLabel);
|
||||||
|
|
||||||
|
$seriesPlots[] = $seriesPlot;
|
||||||
|
}
|
||||||
|
// Reverse the plot order for bar rather than column chart
|
||||||
|
if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
|
||||||
|
$seriesPlots = array_reverse($seriesPlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grouping == 'clustered') {
|
||||||
|
$groupPlot = new \GroupBarPlot($seriesPlots);
|
||||||
|
} elseif ($grouping == 'standard') {
|
||||||
|
$groupPlot = new \GroupBarPlot($seriesPlots);
|
||||||
|
} else {
|
||||||
|
$groupPlot = new \AccBarPlot($seriesPlots);
|
||||||
|
if ($dimensions == '3d') {
|
||||||
|
$groupPlot->SetShadow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->graph->Add($groupPlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotScatter($groupID, $bubble)
|
||||||
|
{
|
||||||
|
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
|
||||||
|
$scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
|
||||||
|
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
|
||||||
|
foreach ($dataValuesY as $k => $dataValueY) {
|
||||||
|
$dataValuesY[$k] = $k;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesPlot = new \ScatterPlot($dataValuesX, $dataValuesY);
|
||||||
|
if ($scatterStyle == 'lineMarker') {
|
||||||
|
$seriesPlot->SetLinkPoints();
|
||||||
|
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
|
||||||
|
} elseif ($scatterStyle == 'smoothMarker') {
|
||||||
|
$spline = new \Spline($dataValuesY, $dataValuesX);
|
||||||
|
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
|
||||||
|
$lplot = new \LinePlot($splineDataX, $splineDataY);
|
||||||
|
$lplot->SetColor(self::$colourSet[self::$plotColour]);
|
||||||
|
|
||||||
|
$this->graph->Add($lplot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bubble) {
|
||||||
|
$this->formatPointMarker($seriesPlot, 'dot');
|
||||||
|
$seriesPlot->mark->SetColor('black');
|
||||||
|
$seriesPlot->mark->SetSize($bubbleSize);
|
||||||
|
} else {
|
||||||
|
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
|
||||||
|
$this->formatPointMarker($seriesPlot, $marker);
|
||||||
|
}
|
||||||
|
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
|
||||||
|
$seriesPlot->SetLegend($dataLabel);
|
||||||
|
|
||||||
|
$this->graph->Add($seriesPlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotRadar($groupID)
|
||||||
|
{
|
||||||
|
$radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
|
||||||
|
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
|
||||||
|
|
||||||
|
$dataValues = [];
|
||||||
|
foreach ($dataValuesY as $k => $dataValueY) {
|
||||||
|
$dataValues[$k] = implode(' ', array_reverse($dataValueY));
|
||||||
|
}
|
||||||
|
$tmp = array_shift($dataValues);
|
||||||
|
$dataValues[] = $tmp;
|
||||||
|
$tmp = array_shift($dataValuesX);
|
||||||
|
$dataValuesX[] = $tmp;
|
||||||
|
|
||||||
|
$this->graph->SetTitles(array_reverse($dataValues));
|
||||||
|
|
||||||
|
$seriesPlot = new \RadarPlot(array_reverse($dataValuesX));
|
||||||
|
|
||||||
|
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
|
||||||
|
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
|
||||||
|
if ($radarStyle == 'filled') {
|
||||||
|
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
|
||||||
|
}
|
||||||
|
$this->formatPointMarker($seriesPlot, $marker);
|
||||||
|
$seriesPlot->SetLegend($dataLabel);
|
||||||
|
|
||||||
|
$this->graph->Add($seriesPlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotContour($groupID)
|
||||||
|
{
|
||||||
|
$contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
|
||||||
|
$dataValues = [];
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
|
||||||
|
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
|
||||||
|
|
||||||
|
$dataValues[$i] = $dataValuesX;
|
||||||
|
}
|
||||||
|
$seriesPlot = new \ContourPlot($dataValues);
|
||||||
|
|
||||||
|
$this->graph->Add($seriesPlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlotStock($groupID)
|
||||||
|
{
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
|
||||||
|
|
||||||
|
$dataValues = [];
|
||||||
|
// Loop through each data series in turn and build the plot arrays
|
||||||
|
foreach ($plotOrder as $i => $v) {
|
||||||
|
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
|
||||||
|
foreach ($dataValuesX as $j => $dataValueX) {
|
||||||
|
$dataValues[$plotOrder[$i]][$j] = $dataValueX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($dataValues)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dataValuesPlot = [];
|
||||||
|
// Flatten the plot arrays to a single dimensional array to work with jpgraph
|
||||||
|
$jMax = count($dataValues[0]);
|
||||||
|
for ($j = 0; $j < $jMax; ++$j) {
|
||||||
|
for ($i = 0; $i < $seriesCount; ++$i) {
|
||||||
|
$dataValuesPlot[] = $dataValues[$i][$j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the x-axis labels
|
||||||
|
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
|
||||||
|
if ($labelCount > 0) {
|
||||||
|
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
|
||||||
|
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
|
||||||
|
$this->graph->xaxis->SetTickLabels($datasetLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesPlot = new \StockPlot($dataValuesPlot);
|
||||||
|
$seriesPlot->SetWidth(20);
|
||||||
|
|
||||||
|
$this->graph->Add($seriesPlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderAreaChart($groupCount, $dimensions = '2d')
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotLine($i, true, false, $dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderLineChart($groupCount, $dimensions = '2d')
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotLine($i, false, false, $dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderBarChart($groupCount, $dimensions = '2d')
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotBar($i, $dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderScatterChart($groupCount)
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea('linlin');
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotScatter($i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderBubbleChart($groupCount)
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea('linlin');
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotScatter($i, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false)
|
||||||
|
{
|
||||||
|
$this->renderPiePlotArea();
|
||||||
|
|
||||||
|
$iLimit = ($multiplePlots) ? $groupCount : 1;
|
||||||
|
for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
|
||||||
|
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
|
||||||
|
$exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
|
||||||
|
$datasetLabels = [];
|
||||||
|
if ($groupID == 0) {
|
||||||
|
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
|
||||||
|
if ($labelCount > 0) {
|
||||||
|
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
|
||||||
|
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
|
||||||
|
$seriesPlots = [];
|
||||||
|
// For pie charts, we only display the first series: doughnut charts generally display all series
|
||||||
|
$jLimit = ($multiplePlots) ? $seriesCount : 1;
|
||||||
|
// Loop through each data series in turn
|
||||||
|
for ($j = 0; $j < $jLimit; ++$j) {
|
||||||
|
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
|
||||||
|
|
||||||
|
// Fill in any missing values in the $dataValues array
|
||||||
|
$testCurrentIndex = 0;
|
||||||
|
foreach ($dataValues as $k => $dataValue) {
|
||||||
|
while ($k != $testCurrentIndex) {
|
||||||
|
$dataValues[$testCurrentIndex] = null;
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
++$testCurrentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dimensions == '3d') {
|
||||||
|
$seriesPlot = new \PiePlot3D($dataValues);
|
||||||
|
} else {
|
||||||
|
if ($doughnut) {
|
||||||
|
$seriesPlot = new \PiePlotC($dataValues);
|
||||||
|
} else {
|
||||||
|
$seriesPlot = new \PiePlot($dataValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($multiplePlots) {
|
||||||
|
$seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($doughnut) {
|
||||||
|
$seriesPlot->SetMidColor('white');
|
||||||
|
}
|
||||||
|
|
||||||
|
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
|
||||||
|
if (count($datasetLabels) > 0) {
|
||||||
|
$seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
|
||||||
|
}
|
||||||
|
if ($dimensions != '3d') {
|
||||||
|
$seriesPlot->SetGuideLines(false);
|
||||||
|
}
|
||||||
|
if ($j == 0) {
|
||||||
|
if ($exploded) {
|
||||||
|
$seriesPlot->ExplodeAll();
|
||||||
|
}
|
||||||
|
$seriesPlot->SetLegends($datasetLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->graph->Add($seriesPlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderRadarChart($groupCount)
|
||||||
|
{
|
||||||
|
$this->renderRadarPlotArea();
|
||||||
|
|
||||||
|
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
|
||||||
|
$this->renderPlotRadar($groupID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderStockChart($groupCount)
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea('intint');
|
||||||
|
|
||||||
|
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
|
||||||
|
$this->renderPlotStock($groupID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderContourChart($groupCount, $dimensions)
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea('intint');
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$this->renderPlotContour($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderCombinationChart($groupCount, $dimensions, $outputDestination)
|
||||||
|
{
|
||||||
|
$this->renderCartesianPlotArea();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$dimensions = null;
|
||||||
|
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
|
||||||
|
switch ($chartType) {
|
||||||
|
case 'area3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'areaChart':
|
||||||
|
$this->renderPlotLine($i, true, true, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bar3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'barChart':
|
||||||
|
$this->renderPlotBar($i, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'line3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'lineChart':
|
||||||
|
$this->renderPlotLine($i, false, true, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'scatterChart':
|
||||||
|
$this->renderPlotScatter($i, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bubbleChart':
|
||||||
|
$this->renderPlotScatter($i, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->graph = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderLegend();
|
||||||
|
|
||||||
|
$this->graph->Stroke($outputDestination);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render($outputDestination)
|
||||||
|
{
|
||||||
|
self::$plotColour = 0;
|
||||||
|
|
||||||
|
$groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
|
||||||
|
|
||||||
|
$dimensions = null;
|
||||||
|
if ($groupCount == 1) {
|
||||||
|
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
|
||||||
|
} else {
|
||||||
|
$chartTypes = [];
|
||||||
|
for ($i = 0; $i < $groupCount; ++$i) {
|
||||||
|
$chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
|
||||||
|
}
|
||||||
|
$chartTypes = array_unique($chartTypes);
|
||||||
|
if (count($chartTypes) == 1) {
|
||||||
|
$chartType = array_pop($chartTypes);
|
||||||
|
} elseif (count($chartTypes) == 0) {
|
||||||
|
echo 'Chart is not yet implemented<br />';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($chartType) {
|
||||||
|
case 'area3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'areaChart':
|
||||||
|
$this->renderAreaChart($groupCount, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bar3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'barChart':
|
||||||
|
$this->renderBarChart($groupCount, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'line3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'lineChart':
|
||||||
|
$this->renderLineChart($groupCount, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'pie3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'pieChart':
|
||||||
|
$this->renderPieChart($groupCount, $dimensions, false, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'doughnut3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'doughnutChart':
|
||||||
|
$this->renderPieChart($groupCount, $dimensions, true, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'scatterChart':
|
||||||
|
$this->renderScatterChart($groupCount);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bubbleChart':
|
||||||
|
$this->renderBubbleChart($groupCount);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'radarChart':
|
||||||
|
$this->renderRadarChart($groupCount);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'surface3DChart':
|
||||||
|
$dimensions = '3d';
|
||||||
|
// no break
|
||||||
|
case 'surfaceChart':
|
||||||
|
$this->renderContourChart($groupCount, $dimensions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'stockChart':
|
||||||
|
$this->renderStockChart($groupCount);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo $chartType . ' is not yet implemented<br />';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->renderLegend();
|
||||||
|
|
||||||
|
$this->graph->Stroke($outputDestination);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
ChartDirector
|
||||||
|
https://www.advsofteng.com/cdphp.html
|
||||||
|
|
||||||
|
GraPHPite
|
||||||
|
http://graphpite.sourceforge.net/
|
||||||
|
|
||||||
|
JpGraph
|
||||||
|
http://www.aditus.nu/jpgraph/
|
||||||
|
|
||||||
|
LibChart
|
||||||
|
https://naku.dohcrew.com/libchart/pages/introduction/
|
||||||
|
|
||||||
|
pChart
|
||||||
|
http://pchart.sourceforge.net/
|
||||||
|
|
||||||
|
TeeChart
|
||||||
|
https://www.steema.com/
|
||||||
|
|
||||||
|
PHPGraphLib
|
||||||
|
http://www.ebrueggeman.com/phpgraphlib
|
||||||
9
PhpOffice/PhpSpreadsheet/Chart/Renderer/Polyfill.php
Normal file
9
PhpOffice/PhpSpreadsheet/Chart/Renderer/Polyfill.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// This is a dirty workaround to output JpGraph charts even when antialiasing is not available
|
||||||
|
if (!function_exists('imageantialias')) {
|
||||||
|
function imageantialias(...$args)
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
66
PhpOffice/PhpSpreadsheet/Chart/Title.php
Normal file
66
PhpOffice/PhpSpreadsheet/Chart/Title.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Chart;
|
||||||
|
|
||||||
|
class Title
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Title Caption.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $caption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title Layout.
|
||||||
|
*
|
||||||
|
* @var Layout
|
||||||
|
*/
|
||||||
|
private $layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Title.
|
||||||
|
*
|
||||||
|
* @param null|mixed $caption
|
||||||
|
* @param null|Layout $layout
|
||||||
|
*/
|
||||||
|
public function __construct($caption = null, Layout $layout = null)
|
||||||
|
{
|
||||||
|
$this->caption = $caption;
|
||||||
|
$this->layout = $layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get caption.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCaption()
|
||||||
|
{
|
||||||
|
return $this->caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set caption.
|
||||||
|
*
|
||||||
|
* @param string $caption
|
||||||
|
*
|
||||||
|
* @return Title
|
||||||
|
*/
|
||||||
|
public function setCaption($caption)
|
||||||
|
{
|
||||||
|
$this->caption = $caption;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Layout.
|
||||||
|
*
|
||||||
|
* @return Layout
|
||||||
|
*/
|
||||||
|
public function getLayout()
|
||||||
|
{
|
||||||
|
return $this->layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
504
PhpOffice/PhpSpreadsheet/Collection/Cells.php
Normal file
504
PhpOffice/PhpSpreadsheet/Collection/Cells.php
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Collection;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class Cells
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent worksheet.
|
||||||
|
*
|
||||||
|
* @var Worksheet
|
||||||
|
*/
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active Cell.
|
||||||
|
*
|
||||||
|
* @var Cell
|
||||||
|
*/
|
||||||
|
private $currentCell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinate of the currently active Cell.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $currentCoordinate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating whether the currently active Cell requires saving.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $currentCellIsDirty = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An index of existing cells. Booleans indexed by their coordinate.
|
||||||
|
*
|
||||||
|
* @var bool[]
|
||||||
|
*/
|
||||||
|
private $index = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix used to uniquely identify cache data for this worksheet.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $cachePrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise this new cell collection.
|
||||||
|
*
|
||||||
|
* @param Worksheet $parent The worksheet for this cell collection
|
||||||
|
* @param CacheInterface $cache
|
||||||
|
*/
|
||||||
|
public function __construct(Worksheet $parent, $cache)
|
||||||
|
{
|
||||||
|
// Set our parent worksheet.
|
||||||
|
// This is maintained here to facilitate re-attaching it to Cell objects when
|
||||||
|
// they are woken from a serialized state
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->cachePrefix = $this->getUniqueID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the parent worksheet for this cell collection.
|
||||||
|
*
|
||||||
|
* @return Worksheet
|
||||||
|
*/
|
||||||
|
public function getParent()
|
||||||
|
{
|
||||||
|
return $this->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the collection holds a cell for the given coordinate.
|
||||||
|
*
|
||||||
|
* @param string $pCoord Coordinate of the cell to check
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has($pCoord)
|
||||||
|
{
|
||||||
|
if ($pCoord === $this->currentCoordinate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the requested entry exists in the index
|
||||||
|
return isset($this->index[$pCoord]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or update a cell in the collection.
|
||||||
|
*
|
||||||
|
* @param Cell $cell Cell to update
|
||||||
|
*
|
||||||
|
* @throws PhpSpreadsheetException
|
||||||
|
*
|
||||||
|
* @return Cell
|
||||||
|
*/
|
||||||
|
public function update(Cell $cell)
|
||||||
|
{
|
||||||
|
return $this->add($cell->getCoordinate(), $cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a cell in cache identified by coordinate.
|
||||||
|
*
|
||||||
|
* @param string $pCoord Coordinate of the cell to delete
|
||||||
|
*/
|
||||||
|
public function delete($pCoord)
|
||||||
|
{
|
||||||
|
if ($pCoord === $this->currentCoordinate && $this->currentCell !== null) {
|
||||||
|
$this->currentCell->detach();
|
||||||
|
$this->currentCoordinate = null;
|
||||||
|
$this->currentCell = null;
|
||||||
|
$this->currentCellIsDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->index[$pCoord]);
|
||||||
|
|
||||||
|
// Delete the entry from cache
|
||||||
|
$this->cache->delete($this->cachePrefix . $pCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of all cell coordinates currently held in the collection.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getCoordinates()
|
||||||
|
{
|
||||||
|
return array_keys($this->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a sorted list of all cell coordinates currently held in the collection by row and column.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getSortedCoordinates()
|
||||||
|
{
|
||||||
|
$sortKeys = [];
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$column = '';
|
||||||
|
$row = 0;
|
||||||
|
sscanf($coord, '%[A-Z]%d', $column, $row);
|
||||||
|
$sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
|
||||||
|
}
|
||||||
|
ksort($sortKeys);
|
||||||
|
|
||||||
|
return array_values($sortKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get highest worksheet column and highest row that have cell records.
|
||||||
|
*
|
||||||
|
* @return array Highest column name and highest row number
|
||||||
|
*/
|
||||||
|
public function getHighestRowAndColumn()
|
||||||
|
{
|
||||||
|
// Lookup highest column and highest row
|
||||||
|
$col = ['A' => '1A'];
|
||||||
|
$row = [1];
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$c = '';
|
||||||
|
$r = 0;
|
||||||
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
|
$row[$r] = $r;
|
||||||
|
$col[$c] = strlen($c) . $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine highest column and row
|
||||||
|
$highestRow = max($row);
|
||||||
|
$highestColumn = substr(max($col), 1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'row' => $highestRow,
|
||||||
|
'column' => $highestColumn,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cell coordinate of the currently active cell object.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCurrentCoordinate()
|
||||||
|
{
|
||||||
|
return $this->currentCoordinate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the column coordinate of the currently active cell object.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCurrentColumn()
|
||||||
|
{
|
||||||
|
$column = '';
|
||||||
|
$row = 0;
|
||||||
|
|
||||||
|
sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
|
||||||
|
|
||||||
|
return $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the row coordinate of the currently active cell object.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getCurrentRow()
|
||||||
|
{
|
||||||
|
$column = '';
|
||||||
|
$row = 0;
|
||||||
|
|
||||||
|
sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
|
||||||
|
|
||||||
|
return (int) $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get highest worksheet column.
|
||||||
|
*
|
||||||
|
* @param string $row Return the highest column for the specified row,
|
||||||
|
* or the highest column of any row if no row number is passed
|
||||||
|
*
|
||||||
|
* @return string Highest column name
|
||||||
|
*/
|
||||||
|
public function getHighestColumn($row = null)
|
||||||
|
{
|
||||||
|
if ($row === null) {
|
||||||
|
$colRow = $this->getHighestRowAndColumn();
|
||||||
|
|
||||||
|
return $colRow['column'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnList = [1];
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$c = '';
|
||||||
|
$r = 0;
|
||||||
|
|
||||||
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
|
if ($r != $row) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$columnList[] = Coordinate::columnIndexFromString($c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Coordinate::stringFromColumnIndex(max($columnList));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get highest worksheet row.
|
||||||
|
*
|
||||||
|
* @param string $column Return the highest row for the specified column,
|
||||||
|
* or the highest row of any column if no column letter is passed
|
||||||
|
*
|
||||||
|
* @return int Highest row number
|
||||||
|
*/
|
||||||
|
public function getHighestRow($column = null)
|
||||||
|
{
|
||||||
|
if ($column === null) {
|
||||||
|
$colRow = $this->getHighestRowAndColumn();
|
||||||
|
|
||||||
|
return $colRow['row'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowList = [0];
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$c = '';
|
||||||
|
$r = 0;
|
||||||
|
|
||||||
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
|
if ($c != $column) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rowList[] = $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max($rowList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique ID for cache referencing.
|
||||||
|
*
|
||||||
|
* @return string Unique Reference
|
||||||
|
*/
|
||||||
|
private function getUniqueID()
|
||||||
|
{
|
||||||
|
return uniqid('phpspreadsheet.', true) . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the cell collection.
|
||||||
|
*
|
||||||
|
* @param Worksheet $parent The new worksheet that we're copying to
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function cloneCellCollection(Worksheet $parent)
|
||||||
|
{
|
||||||
|
$this->storeCurrentCell();
|
||||||
|
$newCollection = clone $this;
|
||||||
|
|
||||||
|
$newCollection->parent = $parent;
|
||||||
|
if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) {
|
||||||
|
$newCollection->currentCell->attach($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get old values
|
||||||
|
$oldKeys = $newCollection->getAllCacheKeys();
|
||||||
|
$oldValues = $newCollection->cache->getMultiple($oldKeys);
|
||||||
|
$newValues = [];
|
||||||
|
$oldCachePrefix = $newCollection->cachePrefix;
|
||||||
|
|
||||||
|
// Change prefix
|
||||||
|
$newCollection->cachePrefix = $newCollection->getUniqueID();
|
||||||
|
foreach ($oldValues as $oldKey => $value) {
|
||||||
|
$newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store new values
|
||||||
|
$stored = $newCollection->cache->setMultiple($newValues);
|
||||||
|
if (!$stored) {
|
||||||
|
$newCollection->__destruct();
|
||||||
|
|
||||||
|
throw new PhpSpreadsheetException('Failed to copy cells in cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a row, deleting all cells in that row.
|
||||||
|
*
|
||||||
|
* @param string $row Row number to remove
|
||||||
|
*/
|
||||||
|
public function removeRow($row)
|
||||||
|
{
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$c = '';
|
||||||
|
$r = 0;
|
||||||
|
|
||||||
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
|
if ($r == $row) {
|
||||||
|
$this->delete($coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a column, deleting all cells in that column.
|
||||||
|
*
|
||||||
|
* @param string $column Column ID to remove
|
||||||
|
*/
|
||||||
|
public function removeColumn($column)
|
||||||
|
{
|
||||||
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
|
$c = '';
|
||||||
|
$r = 0;
|
||||||
|
|
||||||
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
|
if ($c == $column) {
|
||||||
|
$this->delete($coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store cell data in cache for the current cell object if it's "dirty",
|
||||||
|
* and the 'nullify' the current cell object.
|
||||||
|
*
|
||||||
|
* @throws PhpSpreadsheetException
|
||||||
|
*/
|
||||||
|
private function storeCurrentCell()
|
||||||
|
{
|
||||||
|
if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) {
|
||||||
|
$this->currentCell->detach();
|
||||||
|
|
||||||
|
$stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell);
|
||||||
|
if (!$stored) {
|
||||||
|
$this->__destruct();
|
||||||
|
|
||||||
|
throw new PhpSpreadsheetException("Failed to store cell {$this->currentCoordinate} in cache");
|
||||||
|
}
|
||||||
|
$this->currentCellIsDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentCoordinate = null;
|
||||||
|
$this->currentCell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or update a cell identified by its coordinate into the collection.
|
||||||
|
*
|
||||||
|
* @param string $pCoord Coordinate of the cell to update
|
||||||
|
* @param Cell $cell Cell to update
|
||||||
|
*
|
||||||
|
* @throws PhpSpreadsheetException
|
||||||
|
*
|
||||||
|
* @return \PhpOffice\PhpSpreadsheet\Cell\Cell
|
||||||
|
*/
|
||||||
|
public function add($pCoord, Cell $cell)
|
||||||
|
{
|
||||||
|
if ($pCoord !== $this->currentCoordinate) {
|
||||||
|
$this->storeCurrentCell();
|
||||||
|
}
|
||||||
|
$this->index[$pCoord] = true;
|
||||||
|
|
||||||
|
$this->currentCoordinate = $pCoord;
|
||||||
|
$this->currentCell = $cell;
|
||||||
|
$this->currentCellIsDirty = true;
|
||||||
|
|
||||||
|
return $cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cell at a specific coordinate.
|
||||||
|
*
|
||||||
|
* @param string $pCoord Coordinate of the cell
|
||||||
|
*
|
||||||
|
* @throws PhpSpreadsheetException
|
||||||
|
*
|
||||||
|
* @return \PhpOffice\PhpSpreadsheet\Cell\Cell Cell that was found, or null if not found
|
||||||
|
*/
|
||||||
|
public function get($pCoord)
|
||||||
|
{
|
||||||
|
if ($pCoord === $this->currentCoordinate) {
|
||||||
|
return $this->currentCell;
|
||||||
|
}
|
||||||
|
$this->storeCurrentCell();
|
||||||
|
|
||||||
|
// Return null if requested entry doesn't exist in collection
|
||||||
|
if (!$this->has($pCoord)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the entry that has been requested actually exists
|
||||||
|
$cell = $this->cache->get($this->cachePrefix . $pCoord);
|
||||||
|
if ($cell === null) {
|
||||||
|
throw new PhpSpreadsheetException("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current entry to the requested entry
|
||||||
|
$this->currentCoordinate = $pCoord;
|
||||||
|
$this->currentCell = $cell;
|
||||||
|
// Re-attach this as the cell's parent
|
||||||
|
$this->currentCell->attach($this);
|
||||||
|
|
||||||
|
// Return requested entry
|
||||||
|
return $this->currentCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cell collection and disconnect from our parent.
|
||||||
|
*/
|
||||||
|
public function unsetWorksheetCells()
|
||||||
|
{
|
||||||
|
if ($this->currentCell !== null) {
|
||||||
|
$this->currentCell->detach();
|
||||||
|
$this->currentCell = null;
|
||||||
|
$this->currentCoordinate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the cache
|
||||||
|
$this->__destruct();
|
||||||
|
|
||||||
|
$this->index = [];
|
||||||
|
|
||||||
|
// detach ourself from the worksheet, so that it can then delete this object successfully
|
||||||
|
$this->parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this cell collection.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->cache->deleteMultiple($this->getAllCacheKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all known cache keys.
|
||||||
|
*
|
||||||
|
* @return \Generator|string[]
|
||||||
|
*/
|
||||||
|
private function getAllCacheKeys()
|
||||||
|
{
|
||||||
|
foreach ($this->getCoordinates() as $coordinate) {
|
||||||
|
yield $this->cachePrefix . $coordinate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php
Normal file
23
PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Collection;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Settings;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
abstract class CellsFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Initialise the cache storage.
|
||||||
|
*
|
||||||
|
* @param Worksheet $parent Enable cell caching for this worksheet
|
||||||
|
*
|
||||||
|
* @return Cells
|
||||||
|
* */
|
||||||
|
public static function getInstance(Worksheet $parent)
|
||||||
|
{
|
||||||
|
$instance = new Cells($parent, Settings::getCache());
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
77
PhpOffice/PhpSpreadsheet/Collection/Memory.php
Normal file
77
PhpOffice/PhpSpreadsheet/Collection/Memory.php
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the default implementation for in-memory cell collection.
|
||||||
|
*
|
||||||
|
* Alternatives implementation should leverage off-memory, non-volatile storage
|
||||||
|
* to reduce overall memory usage.
|
||||||
|
*/
|
||||||
|
class Memory
|
||||||
|
{
|
||||||
|
private $cache = [];
|
||||||
|
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->cache = [];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($key)
|
||||||
|
{
|
||||||
|
unset($this->cache[$key]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteMultiple($keys)
|
||||||
|
{
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$this->delete($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($key, $default = null)
|
||||||
|
{
|
||||||
|
if ($this->has($key)) {
|
||||||
|
return $this->cache[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMultiple($keys, $default = null)
|
||||||
|
{
|
||||||
|
$results = [];
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$results[$key] = $this->get($key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has($key)
|
||||||
|
{
|
||||||
|
return array_key_exists($key, $this->cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$this->cache[$key] = $value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMultiple($values, $ttl = null)
|
||||||
|
{
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
$this->set($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
331
PhpOffice/PhpSpreadsheet/Comment.php
Normal file
331
PhpOffice/PhpSpreadsheet/Comment.php
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||||
|
|
||||||
|
class Comment implements IComparable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Author.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rich text comment.
|
||||||
|
*
|
||||||
|
* @var RichText
|
||||||
|
*/
|
||||||
|
private $text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment width (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $width = '96pt';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $marginLeft = '59.25pt';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $marginTop = '1.5pt';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visible.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $visible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment height (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $height = '55.5pt';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment fill color.
|
||||||
|
*
|
||||||
|
* @var Style\Color
|
||||||
|
*/
|
||||||
|
private $fillColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alignment.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $alignment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Comment.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Initialise variables
|
||||||
|
$this->author = 'Author';
|
||||||
|
$this->text = new RichText();
|
||||||
|
$this->fillColor = new Style\Color('FFFFFFE1');
|
||||||
|
$this->alignment = Style\Alignment::HORIZONTAL_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Author.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAuthor()
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Author.
|
||||||
|
*
|
||||||
|
* @param string $author
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setAuthor($author)
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Rich text comment.
|
||||||
|
*
|
||||||
|
* @return RichText
|
||||||
|
*/
|
||||||
|
public function getText()
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Rich text comment.
|
||||||
|
*
|
||||||
|
* @param RichText $pValue
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setText(RichText $pValue)
|
||||||
|
{
|
||||||
|
$this->text = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get comment width (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWidth()
|
||||||
|
{
|
||||||
|
return $this->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set comment width (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @param string $width
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setWidth($width)
|
||||||
|
{
|
||||||
|
$this->width = $width;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get comment height (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHeight()
|
||||||
|
{
|
||||||
|
return $this->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set comment height (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setHeight($value)
|
||||||
|
{
|
||||||
|
$this->height = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get left margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMarginLeft()
|
||||||
|
{
|
||||||
|
return $this->marginLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set left margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setMarginLeft($value)
|
||||||
|
{
|
||||||
|
$this->marginLeft = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get top margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMarginTop()
|
||||||
|
{
|
||||||
|
return $this->marginTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set top margin (CSS style, i.e. XXpx or YYpt).
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setMarginTop($value)
|
||||||
|
{
|
||||||
|
$this->marginTop = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the comment visible by default?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getVisible()
|
||||||
|
{
|
||||||
|
return $this->visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set comment default visibility.
|
||||||
|
*
|
||||||
|
* @param bool $value
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setVisible($value)
|
||||||
|
{
|
||||||
|
$this->visible = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fill color.
|
||||||
|
*
|
||||||
|
* @return Style\Color
|
||||||
|
*/
|
||||||
|
public function getFillColor()
|
||||||
|
{
|
||||||
|
return $this->fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Alignment.
|
||||||
|
*
|
||||||
|
* @param string $alignment see Style\Alignment::HORIZONTAL_*
|
||||||
|
*
|
||||||
|
* @return Comment
|
||||||
|
*/
|
||||||
|
public function setAlignment($alignment)
|
||||||
|
{
|
||||||
|
$this->alignment = $alignment;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Alignment.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAlignment()
|
||||||
|
{
|
||||||
|
return $this->alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hash code.
|
||||||
|
*
|
||||||
|
* @return string Hash code
|
||||||
|
*/
|
||||||
|
public function getHashCode()
|
||||||
|
{
|
||||||
|
return md5(
|
||||||
|
$this->author .
|
||||||
|
$this->text->getHashCode() .
|
||||||
|
$this->width .
|
||||||
|
$this->height .
|
||||||
|
$this->marginLeft .
|
||||||
|
$this->marginTop .
|
||||||
|
($this->visible ? 1 : 0) .
|
||||||
|
$this->fillColor->getHashCode() .
|
||||||
|
$this->alignment .
|
||||||
|
__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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->text->getPlainText();
|
||||||
|
}
|
||||||
|
}
|
||||||
629
PhpOffice/PhpSpreadsheet/Document/Properties.php
Normal file
629
PhpOffice/PhpSpreadsheet/Document/Properties.php
Normal file
|
|
@ -0,0 +1,629 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Document;
|
||||||
|
|
||||||
|
class Properties
|
||||||
|
{
|
||||||
|
/** constants */
|
||||||
|
const PROPERTY_TYPE_BOOLEAN = 'b';
|
||||||
|
const PROPERTY_TYPE_INTEGER = 'i';
|
||||||
|
const PROPERTY_TYPE_FLOAT = 'f';
|
||||||
|
const PROPERTY_TYPE_DATE = 'd';
|
||||||
|
const PROPERTY_TYPE_STRING = 's';
|
||||||
|
const PROPERTY_TYPE_UNKNOWN = 'u';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creator.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $creator = 'Unknown Creator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LastModifiedBy.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $lastModifiedBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $modified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $title = 'Untitled Spreadsheet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subject.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $subject = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keywords.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $keywords = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $category = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $manager = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Company.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $company = 'Microsoft Corporation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Properties.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $customProperties = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Document Properties instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Initialise values
|
||||||
|
$this->lastModifiedBy = $this->creator;
|
||||||
|
$this->created = time();
|
||||||
|
$this->modified = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Creator.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCreator()
|
||||||
|
{
|
||||||
|
return $this->creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Creator.
|
||||||
|
*
|
||||||
|
* @param string $creator
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setCreator($creator)
|
||||||
|
{
|
||||||
|
$this->creator = $creator;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Last Modified By.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLastModifiedBy()
|
||||||
|
{
|
||||||
|
return $this->lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Last Modified By.
|
||||||
|
*
|
||||||
|
* @param string $pValue
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setLastModifiedBy($pValue)
|
||||||
|
{
|
||||||
|
$this->lastModifiedBy = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Created.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getCreated()
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Created.
|
||||||
|
*
|
||||||
|
* @param int|string $time
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setCreated($time)
|
||||||
|
{
|
||||||
|
if ($time === null) {
|
||||||
|
$time = time();
|
||||||
|
} elseif (is_string($time)) {
|
||||||
|
if (is_numeric($time)) {
|
||||||
|
$time = (int) $time;
|
||||||
|
} else {
|
||||||
|
$time = strtotime($time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->created = $time;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Modified.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getModified()
|
||||||
|
{
|
||||||
|
return $this->modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Modified.
|
||||||
|
*
|
||||||
|
* @param int|string $time
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setModified($time)
|
||||||
|
{
|
||||||
|
if ($time === null) {
|
||||||
|
$time = time();
|
||||||
|
} elseif (is_string($time)) {
|
||||||
|
if (is_numeric($time)) {
|
||||||
|
$time = (int) $time;
|
||||||
|
} else {
|
||||||
|
$time = strtotime($time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->modified = $time;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Title.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Title.
|
||||||
|
*
|
||||||
|
* @param string $title
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setTitle($title)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Description.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDescription()
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Description.
|
||||||
|
*
|
||||||
|
* @param string $description
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setDescription($description)
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Subject.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSubject()
|
||||||
|
{
|
||||||
|
return $this->subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Subject.
|
||||||
|
*
|
||||||
|
* @param string $subject
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setSubject($subject)
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Keywords.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Keywords.
|
||||||
|
*
|
||||||
|
* @param string $keywords
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setKeywords($keywords)
|
||||||
|
{
|
||||||
|
$this->keywords = $keywords;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Category.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCategory()
|
||||||
|
{
|
||||||
|
return $this->category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Category.
|
||||||
|
*
|
||||||
|
* @param string $category
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setCategory($category)
|
||||||
|
{
|
||||||
|
$this->category = $category;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Company.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCompany()
|
||||||
|
{
|
||||||
|
return $this->company;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Company.
|
||||||
|
*
|
||||||
|
* @param string $company
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setCompany($company)
|
||||||
|
{
|
||||||
|
$this->company = $company;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Manager.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getManager()
|
||||||
|
{
|
||||||
|
return $this->manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Manager.
|
||||||
|
*
|
||||||
|
* @param string $manager
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setManager($manager)
|
||||||
|
{
|
||||||
|
$this->manager = $manager;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a List of Custom Property Names.
|
||||||
|
*
|
||||||
|
* @return array of string
|
||||||
|
*/
|
||||||
|
public function getCustomProperties()
|
||||||
|
{
|
||||||
|
return array_keys($this->customProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a Custom Property is defined.
|
||||||
|
*
|
||||||
|
* @param string $propertyName
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isCustomPropertySet($propertyName)
|
||||||
|
{
|
||||||
|
return isset($this->customProperties[$propertyName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Custom Property Value.
|
||||||
|
*
|
||||||
|
* @param string $propertyName
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getCustomPropertyValue($propertyName)
|
||||||
|
{
|
||||||
|
if (isset($this->customProperties[$propertyName])) {
|
||||||
|
return $this->customProperties[$propertyName]['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Custom Property Type.
|
||||||
|
*
|
||||||
|
* @param string $propertyName
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCustomPropertyType($propertyName)
|
||||||
|
{
|
||||||
|
if (isset($this->customProperties[$propertyName])) {
|
||||||
|
return $this->customProperties[$propertyName]['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Custom Property.
|
||||||
|
*
|
||||||
|
* @param string $propertyName
|
||||||
|
* @param mixed $propertyValue
|
||||||
|
* @param string $propertyType
|
||||||
|
* 'i' : Integer
|
||||||
|
* 'f' : Floating Point
|
||||||
|
* 's' : String
|
||||||
|
* 'd' : Date/Time
|
||||||
|
* 'b' : Boolean
|
||||||
|
*
|
||||||
|
* @return Properties
|
||||||
|
*/
|
||||||
|
public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null)
|
||||||
|
{
|
||||||
|
if (($propertyType === null) || (!in_array($propertyType, [self::PROPERTY_TYPE_INTEGER,
|
||||||
|
self::PROPERTY_TYPE_FLOAT,
|
||||||
|
self::PROPERTY_TYPE_STRING,
|
||||||
|
self::PROPERTY_TYPE_DATE,
|
||||||
|
self::PROPERTY_TYPE_BOOLEAN, ]))) {
|
||||||
|
if ($propertyValue === null) {
|
||||||
|
$propertyType = self::PROPERTY_TYPE_STRING;
|
||||||
|
} elseif (is_float($propertyValue)) {
|
||||||
|
$propertyType = self::PROPERTY_TYPE_FLOAT;
|
||||||
|
} elseif (is_int($propertyValue)) {
|
||||||
|
$propertyType = self::PROPERTY_TYPE_INTEGER;
|
||||||
|
} elseif (is_bool($propertyValue)) {
|
||||||
|
$propertyType = self::PROPERTY_TYPE_BOOLEAN;
|
||||||
|
} else {
|
||||||
|
$propertyType = self::PROPERTY_TYPE_STRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->customProperties[$propertyName] = [
|
||||||
|
'value' => $propertyValue,
|
||||||
|
'type' => $propertyType,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function convertProperty($propertyValue, $propertyType)
|
||||||
|
{
|
||||||
|
switch ($propertyType) {
|
||||||
|
case 'empty': // Empty
|
||||||
|
return '';
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'null': // Null
|
||||||
|
return null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'i1': // 1-Byte Signed Integer
|
||||||
|
case 'i2': // 2-Byte Signed Integer
|
||||||
|
case 'i4': // 4-Byte Signed Integer
|
||||||
|
case 'i8': // 8-Byte Signed Integer
|
||||||
|
case 'int': // Integer
|
||||||
|
return (int) $propertyValue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'ui1': // 1-Byte Unsigned Integer
|
||||||
|
case 'ui2': // 2-Byte Unsigned Integer
|
||||||
|
case 'ui4': // 4-Byte Unsigned Integer
|
||||||
|
case 'ui8': // 8-Byte Unsigned Integer
|
||||||
|
case 'uint': // Unsigned Integer
|
||||||
|
return abs((int) $propertyValue);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'r4': // 4-Byte Real Number
|
||||||
|
case 'r8': // 8-Byte Real Number
|
||||||
|
case 'decimal': // Decimal
|
||||||
|
return (float) $propertyValue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'lpstr': // LPSTR
|
||||||
|
case 'lpwstr': // LPWSTR
|
||||||
|
case 'bstr': // Basic String
|
||||||
|
return $propertyValue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'date': // Date and Time
|
||||||
|
case 'filetime': // File Time
|
||||||
|
return strtotime($propertyValue);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bool': // Boolean
|
||||||
|
return $propertyValue == 'true';
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'cy': // Currency
|
||||||
|
case 'error': // Error Status Code
|
||||||
|
case 'vector': // Vector
|
||||||
|
case 'array': // Array
|
||||||
|
case 'blob': // Binary Blob
|
||||||
|
case 'oblob': // Binary Blob Object
|
||||||
|
case 'stream': // Binary Stream
|
||||||
|
case 'ostream': // Binary Stream Object
|
||||||
|
case 'storage': // Binary Storage
|
||||||
|
case 'ostorage': // Binary Storage Object
|
||||||
|
case 'vstream': // Binary Versioned Stream
|
||||||
|
case 'clsid': // Class ID
|
||||||
|
case 'cf': // Clipboard Data
|
||||||
|
return $propertyValue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $propertyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function convertPropertyType($propertyType)
|
||||||
|
{
|
||||||
|
switch ($propertyType) {
|
||||||
|
case 'i1': // 1-Byte Signed Integer
|
||||||
|
case 'i2': // 2-Byte Signed Integer
|
||||||
|
case 'i4': // 4-Byte Signed Integer
|
||||||
|
case 'i8': // 8-Byte Signed Integer
|
||||||
|
case 'int': // Integer
|
||||||
|
case 'ui1': // 1-Byte Unsigned Integer
|
||||||
|
case 'ui2': // 2-Byte Unsigned Integer
|
||||||
|
case 'ui4': // 4-Byte Unsigned Integer
|
||||||
|
case 'ui8': // 8-Byte Unsigned Integer
|
||||||
|
case 'uint': // Unsigned Integer
|
||||||
|
return self::PROPERTY_TYPE_INTEGER;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'r4': // 4-Byte Real Number
|
||||||
|
case 'r8': // 8-Byte Real Number
|
||||||
|
case 'decimal': // Decimal
|
||||||
|
return self::PROPERTY_TYPE_FLOAT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'empty': // Empty
|
||||||
|
case 'null': // Null
|
||||||
|
case 'lpstr': // LPSTR
|
||||||
|
case 'lpwstr': // LPWSTR
|
||||||
|
case 'bstr': // Basic String
|
||||||
|
return self::PROPERTY_TYPE_STRING;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'date': // Date and Time
|
||||||
|
case 'filetime': // File Time
|
||||||
|
return self::PROPERTY_TYPE_DATE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bool': // Boolean
|
||||||
|
return self::PROPERTY_TYPE_BOOLEAN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'cy': // Currency
|
||||||
|
case 'error': // Error Status Code
|
||||||
|
case 'vector': // Vector
|
||||||
|
case 'array': // Array
|
||||||
|
case 'blob': // Binary Blob
|
||||||
|
case 'oblob': // Binary Blob Object
|
||||||
|
case 'stream': // Binary Stream
|
||||||
|
case 'ostream': // Binary Stream Object
|
||||||
|
case 'storage': // Binary Storage
|
||||||
|
case 'ostorage': // Binary Storage Object
|
||||||
|
case 'vstream': // Binary Versioned Stream
|
||||||
|
case 'clsid': // Class ID
|
||||||
|
case 'cf': // Clipboard Data
|
||||||
|
return self::PROPERTY_TYPE_UNKNOWN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::PROPERTY_TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
205
PhpOffice/PhpSpreadsheet/Document/Security.php
Normal file
205
PhpOffice/PhpSpreadsheet/Document/Security.php
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Document;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
|
||||||
|
|
||||||
|
class Security
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* LockRevision.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $lockRevision = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LockStructure.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $lockStructure = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LockWindows.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $lockWindows = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RevisionsPassword.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $revisionsPassword = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WorkbookPassword.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $workbookPassword = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Document Security instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is some sort of document security enabled?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSecurityEnabled()
|
||||||
|
{
|
||||||
|
return $this->lockRevision ||
|
||||||
|
$this->lockStructure ||
|
||||||
|
$this->lockWindows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LockRevision.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getLockRevision()
|
||||||
|
{
|
||||||
|
return $this->lockRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set LockRevision.
|
||||||
|
*
|
||||||
|
* @param bool $pValue
|
||||||
|
*
|
||||||
|
* @return Security
|
||||||
|
*/
|
||||||
|
public function setLockRevision($pValue)
|
||||||
|
{
|
||||||
|
$this->lockRevision = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LockStructure.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getLockStructure()
|
||||||
|
{
|
||||||
|
return $this->lockStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set LockStructure.
|
||||||
|
*
|
||||||
|
* @param bool $pValue
|
||||||
|
*
|
||||||
|
* @return Security
|
||||||
|
*/
|
||||||
|
public function setLockStructure($pValue)
|
||||||
|
{
|
||||||
|
$this->lockStructure = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LockWindows.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getLockWindows()
|
||||||
|
{
|
||||||
|
return $this->lockWindows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set LockWindows.
|
||||||
|
*
|
||||||
|
* @param bool $pValue
|
||||||
|
*
|
||||||
|
* @return Security
|
||||||
|
*/
|
||||||
|
public function setLockWindows($pValue)
|
||||||
|
{
|
||||||
|
$this->lockWindows = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get RevisionsPassword (hashed).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRevisionsPassword()
|
||||||
|
{
|
||||||
|
return $this->revisionsPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set RevisionsPassword.
|
||||||
|
*
|
||||||
|
* @param string $pValue
|
||||||
|
* @param bool $pAlreadyHashed If the password has already been hashed, set this to true
|
||||||
|
*
|
||||||
|
* @return Security
|
||||||
|
*/
|
||||||
|
public function setRevisionsPassword($pValue, $pAlreadyHashed = false)
|
||||||
|
{
|
||||||
|
if (!$pAlreadyHashed) {
|
||||||
|
$pValue = PasswordHasher::hashPassword($pValue);
|
||||||
|
}
|
||||||
|
$this->revisionsPassword = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get WorkbookPassword (hashed).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWorkbookPassword()
|
||||||
|
{
|
||||||
|
return $this->workbookPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set WorkbookPassword.
|
||||||
|
*
|
||||||
|
* @param string $pValue
|
||||||
|
* @param bool $pAlreadyHashed If the password has already been hashed, set this to true
|
||||||
|
*
|
||||||
|
* @return Security
|
||||||
|
*/
|
||||||
|
public function setWorkbookPassword($pValue, $pAlreadyHashed = false)
|
||||||
|
{
|
||||||
|
if (!$pAlreadyHashed) {
|
||||||
|
$pValue = PasswordHasher::hashPassword($pValue);
|
||||||
|
}
|
||||||
|
$this->workbookPassword = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
PhpOffice/PhpSpreadsheet/Exception.php
Normal file
7
PhpOffice/PhpSpreadsheet/Exception.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
class Exception extends \Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
179
PhpOffice/PhpSpreadsheet/HashTable.php
Normal file
179
PhpOffice/PhpSpreadsheet/HashTable.php
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
class HashTable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* HashTable elements.
|
||||||
|
*
|
||||||
|
* @var IComparable[]
|
||||||
|
*/
|
||||||
|
protected $items = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashTable key map.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $keyMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new \PhpOffice\PhpSpreadsheet\HashTable.
|
||||||
|
*
|
||||||
|
* @param IComparable[] $pSource Optional source array to create HashTable from
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __construct($pSource = null)
|
||||||
|
{
|
||||||
|
if ($pSource !== null) {
|
||||||
|
// Create HashTable
|
||||||
|
$this->addFromSource($pSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add HashTable items from source.
|
||||||
|
*
|
||||||
|
* @param IComparable[] $pSource Source array to create HashTable from
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function addFromSource(array $pSource = null)
|
||||||
|
{
|
||||||
|
// Check if an array was passed
|
||||||
|
if ($pSource == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pSource as $item) {
|
||||||
|
$this->add($item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add HashTable item.
|
||||||
|
*
|
||||||
|
* @param IComparable $pSource Item to add
|
||||||
|
*/
|
||||||
|
public function add(IComparable $pSource)
|
||||||
|
{
|
||||||
|
$hash = $pSource->getHashCode();
|
||||||
|
if (!isset($this->items[$hash])) {
|
||||||
|
$this->items[$hash] = $pSource;
|
||||||
|
$this->keyMap[count($this->items) - 1] = $hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove HashTable item.
|
||||||
|
*
|
||||||
|
* @param IComparable $pSource Item to remove
|
||||||
|
*/
|
||||||
|
public function remove(IComparable $pSource)
|
||||||
|
{
|
||||||
|
$hash = $pSource->getHashCode();
|
||||||
|
if (isset($this->items[$hash])) {
|
||||||
|
unset($this->items[$hash]);
|
||||||
|
|
||||||
|
$deleteKey = -1;
|
||||||
|
foreach ($this->keyMap as $key => $value) {
|
||||||
|
if ($deleteKey >= 0) {
|
||||||
|
$this->keyMap[$key - 1] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value == $hash) {
|
||||||
|
$deleteKey = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($this->keyMap[count($this->keyMap) - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear HashTable.
|
||||||
|
*/
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->items = [];
|
||||||
|
$this->keyMap = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get index for hash code.
|
||||||
|
*
|
||||||
|
* @param string $pHashCode
|
||||||
|
*
|
||||||
|
* @return int Index
|
||||||
|
*/
|
||||||
|
public function getIndexForHashCode($pHashCode)
|
||||||
|
{
|
||||||
|
return array_search($pHashCode, $this->keyMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get by index.
|
||||||
|
*
|
||||||
|
* @param int $pIndex
|
||||||
|
*
|
||||||
|
* @return IComparable
|
||||||
|
*/
|
||||||
|
public function getByIndex($pIndex)
|
||||||
|
{
|
||||||
|
if (isset($this->keyMap[$pIndex])) {
|
||||||
|
return $this->getByHashCode($this->keyMap[$pIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get by hashcode.
|
||||||
|
*
|
||||||
|
* @param string $pHashCode
|
||||||
|
*
|
||||||
|
* @return IComparable
|
||||||
|
*/
|
||||||
|
public function getByHashCode($pHashCode)
|
||||||
|
{
|
||||||
|
if (isset($this->items[$pHashCode])) {
|
||||||
|
return $this->items[$pHashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashTable to array.
|
||||||
|
*
|
||||||
|
* @return IComparable[]
|
||||||
|
*/
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PHPExcel_Helper_HTML
|
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||||
|
|
||||||
|
use DOMDocument;
|
||||||
|
use DOMElement;
|
||||||
|
use DOMNode;
|
||||||
|
use DOMText;
|
||||||
|
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Color;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||||
|
|
||||||
|
class Html
|
||||||
{
|
{
|
||||||
protected static $colourMap = array(
|
protected static $colourMap = [
|
||||||
'aliceblue' => 'f0f8ff',
|
'aliceblue' => 'f0f8ff',
|
||||||
'antiquewhite' => 'faebd7',
|
'antiquewhite' => 'faebd7',
|
||||||
'antiquewhite1' => 'ffefdb',
|
'antiquewhite1' => 'ffefdb',
|
||||||
|
|
@ -520,20 +530,27 @@ class PHPExcel_Helper_HTML
|
||||||
'yellow3' => 'cdcd00',
|
'yellow3' => 'cdcd00',
|
||||||
'yellow4' => '8b8b00',
|
'yellow4' => '8b8b00',
|
||||||
'yellowgreen' => '9acd32',
|
'yellowgreen' => '9acd32',
|
||||||
);
|
];
|
||||||
|
|
||||||
protected $face;
|
protected $face;
|
||||||
|
|
||||||
protected $size;
|
protected $size;
|
||||||
|
|
||||||
protected $color;
|
protected $color;
|
||||||
|
|
||||||
protected $bold = false;
|
protected $bold = false;
|
||||||
protected $italic = false;
|
|
||||||
protected $underline = false;
|
|
||||||
protected $superscript = false;
|
|
||||||
protected $subscript = false;
|
|
||||||
protected $strikethrough = false;
|
|
||||||
|
|
||||||
protected $startTagCallbacks = array(
|
protected $italic = false;
|
||||||
|
|
||||||
|
protected $underline = false;
|
||||||
|
|
||||||
|
protected $superscript = false;
|
||||||
|
|
||||||
|
protected $subscript = false;
|
||||||
|
|
||||||
|
protected $strikethrough = false;
|
||||||
|
|
||||||
|
protected $startTagCallbacks = [
|
||||||
'font' => 'startFontTag',
|
'font' => 'startFontTag',
|
||||||
'b' => 'startBoldTag',
|
'b' => 'startBoldTag',
|
||||||
'strong' => 'startBoldTag',
|
'strong' => 'startBoldTag',
|
||||||
|
|
@ -544,9 +561,9 @@ class PHPExcel_Helper_HTML
|
||||||
'del' => 'startStrikethruTag',
|
'del' => 'startStrikethruTag',
|
||||||
'sup' => 'startSuperscriptTag',
|
'sup' => 'startSuperscriptTag',
|
||||||
'sub' => 'startSubscriptTag',
|
'sub' => 'startSubscriptTag',
|
||||||
);
|
];
|
||||||
|
|
||||||
protected $endTagCallbacks = array(
|
protected $endTagCallbacks = [
|
||||||
'font' => 'endFontTag',
|
'font' => 'endFontTag',
|
||||||
'b' => 'endBoldTag',
|
'b' => 'endBoldTag',
|
||||||
'strong' => 'endBoldTag',
|
'strong' => 'endBoldTag',
|
||||||
|
|
@ -565,44 +582,76 @@ class PHPExcel_Helper_HTML
|
||||||
'h4' => 'breakTag',
|
'h4' => 'breakTag',
|
||||||
'h5' => 'breakTag',
|
'h5' => 'breakTag',
|
||||||
'h6' => 'breakTag',
|
'h6' => 'breakTag',
|
||||||
);
|
];
|
||||||
|
|
||||||
protected $stack = array();
|
protected $stack = [];
|
||||||
|
|
||||||
protected $stringData = '';
|
protected $stringData = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RichText
|
||||||
|
*/
|
||||||
protected $richTextObject;
|
protected $richTextObject;
|
||||||
|
|
||||||
protected function initialise() {
|
protected function initialise()
|
||||||
|
{
|
||||||
$this->face = $this->size = $this->color = null;
|
$this->face = $this->size = $this->color = null;
|
||||||
$this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false;
|
$this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false;
|
||||||
|
|
||||||
$this->stack = array();
|
$this->stack = [];
|
||||||
|
|
||||||
$this->stringData = '';
|
$this->stringData = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toRichTextObject($html) {
|
/**
|
||||||
|
* Parse HTML formatting and return the resulting RichText.
|
||||||
|
*
|
||||||
|
* @param string $html
|
||||||
|
*
|
||||||
|
* @return RichText
|
||||||
|
*/
|
||||||
|
public function toRichTextObject($html)
|
||||||
|
{
|
||||||
$this->initialise();
|
$this->initialise();
|
||||||
|
|
||||||
// Create a new DOM object
|
// Create a new DOM object
|
||||||
$dom = new domDocument;
|
$dom = new DOMDocument();
|
||||||
// Load the HTML file into the DOM object
|
// Load the HTML file into the DOM object
|
||||||
// Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
|
// Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
|
||||||
$loaded = @$dom->loadHTML($html);
|
$prefix = '<?xml encoding="UTF-8">';
|
||||||
|
@$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||||
// Discard excess white space
|
// Discard excess white space
|
||||||
$dom->preserveWhiteSpace = false;
|
$dom->preserveWhiteSpace = false;
|
||||||
|
|
||||||
$this->richTextObject = new PHPExcel_RichText();;
|
$this->richTextObject = new RichText();
|
||||||
$this->parseElements($dom);
|
$this->parseElements($dom);
|
||||||
|
|
||||||
|
// Clean any further spurious whitespace
|
||||||
|
$this->cleanWhitespace();
|
||||||
|
|
||||||
return $this->richTextObject;
|
return $this->richTextObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildTextRun() {
|
protected function cleanWhitespace()
|
||||||
|
{
|
||||||
|
foreach ($this->richTextObject->getRichTextElements() as $key => $element) {
|
||||||
|
$text = $element->getText();
|
||||||
|
// Trim any leading spaces on the first run
|
||||||
|
if ($key == 0) {
|
||||||
|
$text = ltrim($text);
|
||||||
|
}
|
||||||
|
// Trim any spaces immediately after a line break
|
||||||
|
$text = preg_replace('/\n */mu', "\n", $text);
|
||||||
|
$element->setText($text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildTextRun()
|
||||||
|
{
|
||||||
$text = $this->stringData;
|
$text = $this->stringData;
|
||||||
if (trim($text) === '')
|
if (trim($text) === '') {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$richtextRun = $this->richTextObject->createTextRun($this->stringData);
|
$richtextRun = $this->richTextObject->createTextRun($this->stringData);
|
||||||
if ($this->face) {
|
if ($this->face) {
|
||||||
|
|
@ -612,7 +661,7 @@ class PHPExcel_Helper_HTML
|
||||||
$richtextRun->getFont()->setSize($this->size);
|
$richtextRun->getFont()->setSize($this->size);
|
||||||
}
|
}
|
||||||
if ($this->color) {
|
if ($this->color) {
|
||||||
$richtextRun->getFont()->setColor( new PHPExcel_Style_Color( 'ff' . $this->color ) );
|
$richtextRun->getFont()->setColor(new Color('ff' . $this->color));
|
||||||
}
|
}
|
||||||
if ($this->bold) {
|
if ($this->bold) {
|
||||||
$richtextRun->getFont()->setBold(true);
|
$richtextRun->getFont()->setBold(true);
|
||||||
|
|
@ -621,13 +670,13 @@ class PHPExcel_Helper_HTML
|
||||||
$richtextRun->getFont()->setItalic(true);
|
$richtextRun->getFont()->setItalic(true);
|
||||||
}
|
}
|
||||||
if ($this->underline) {
|
if ($this->underline) {
|
||||||
$richtextRun->getFont()->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE);
|
$richtextRun->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
|
||||||
}
|
}
|
||||||
if ($this->superscript) {
|
if ($this->superscript) {
|
||||||
$richtextRun->getFont()->setSuperScript(true);
|
$richtextRun->getFont()->setSuperscript(true);
|
||||||
}
|
}
|
||||||
if ($this->subscript) {
|
if ($this->subscript) {
|
||||||
$richtextRun->getFont()->setSubScript(true);
|
$richtextRun->getFont()->setSubscript(true);
|
||||||
}
|
}
|
||||||
if ($this->strikethrough) {
|
if ($this->strikethrough) {
|
||||||
$richtextRun->getFont()->setStrikethrough(true);
|
$richtextRun->getFont()->setStrikethrough(true);
|
||||||
|
|
@ -635,19 +684,23 @@ class PHPExcel_Helper_HTML
|
||||||
$this->stringData = '';
|
$this->stringData = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function rgbToColour($rgb) {
|
protected function rgbToColour($rgb)
|
||||||
|
{
|
||||||
preg_match_all('/\d+/', $rgb, $values);
|
preg_match_all('/\d+/', $rgb, $values);
|
||||||
foreach($values[0] as &$value) {
|
foreach ($values[0] as &$value) {
|
||||||
$value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT);
|
$value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode($values[0]);
|
return implode($values[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function colourNameLookup($rgb) {
|
protected function colourNameLookup($rgb)
|
||||||
|
{
|
||||||
return self::$colourMap[$rgb];
|
return self::$colourMap[$rgb];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startFontTag($tag) {
|
protected function startFontTag($tag)
|
||||||
|
{
|
||||||
foreach ($tag->attributes as $attribute) {
|
foreach ($tag->attributes as $attribute) {
|
||||||
$attributeName = strtolower($attribute->name);
|
$attributeName = strtolower($attribute->name);
|
||||||
$attributeValue = $attribute->value;
|
$attributeValue = $attribute->value;
|
||||||
|
|
@ -655,7 +708,7 @@ class PHPExcel_Helper_HTML
|
||||||
if ($attributeName == 'color') {
|
if ($attributeName == 'color') {
|
||||||
if (preg_match('/rgb\s*\(/', $attributeValue)) {
|
if (preg_match('/rgb\s*\(/', $attributeValue)) {
|
||||||
$this->$attributeName = $this->rgbToColour($attributeValue);
|
$this->$attributeName = $this->rgbToColour($attributeValue);
|
||||||
} elseif(strpos(trim($attributeValue), '#') === 0) {
|
} elseif (strpos(trim($attributeValue), '#') === 0) {
|
||||||
$this->$attributeName = ltrim($attributeValue, '#');
|
$this->$attributeName = ltrim($attributeValue, '#');
|
||||||
} else {
|
} else {
|
||||||
$this->$attributeName = $this->colourNameLookup($attributeValue);
|
$this->$attributeName = $this->colourNameLookup($attributeValue);
|
||||||
|
|
@ -666,91 +719,117 @@ class PHPExcel_Helper_HTML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endFontTag() {
|
protected function endFontTag()
|
||||||
|
{
|
||||||
$this->face = $this->size = $this->color = null;
|
$this->face = $this->size = $this->color = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startBoldTag() {
|
protected function startBoldTag()
|
||||||
|
{
|
||||||
$this->bold = true;
|
$this->bold = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endBoldTag() {
|
protected function endBoldTag()
|
||||||
|
{
|
||||||
$this->bold = false;
|
$this->bold = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startItalicTag() {
|
protected function startItalicTag()
|
||||||
|
{
|
||||||
$this->italic = true;
|
$this->italic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endItalicTag() {
|
protected function endItalicTag()
|
||||||
|
{
|
||||||
$this->italic = false;
|
$this->italic = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startUnderlineTag() {
|
protected function startUnderlineTag()
|
||||||
|
{
|
||||||
$this->underline = true;
|
$this->underline = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endUnderlineTag() {
|
protected function endUnderlineTag()
|
||||||
|
{
|
||||||
$this->underline = false;
|
$this->underline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startSubscriptTag() {
|
protected function startSubscriptTag()
|
||||||
|
{
|
||||||
$this->subscript = true;
|
$this->subscript = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endSubscriptTag() {
|
protected function endSubscriptTag()
|
||||||
|
{
|
||||||
$this->subscript = false;
|
$this->subscript = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startSuperscriptTag() {
|
protected function startSuperscriptTag()
|
||||||
|
{
|
||||||
$this->superscript = true;
|
$this->superscript = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endSuperscriptTag() {
|
protected function endSuperscriptTag()
|
||||||
|
{
|
||||||
$this->superscript = false;
|
$this->superscript = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startStrikethruTag() {
|
protected function startStrikethruTag()
|
||||||
|
{
|
||||||
$this->strikethrough = true;
|
$this->strikethrough = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function endStrikethruTag() {
|
protected function endStrikethruTag()
|
||||||
|
{
|
||||||
$this->strikethrough = false;
|
$this->strikethrough = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function breakTag() {
|
protected function breakTag()
|
||||||
$this->stringData .= PHP_EOL;
|
{
|
||||||
|
$this->stringData .= "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function parseTextNode(DOMText $textNode) {
|
protected function parseTextNode(DOMText $textNode)
|
||||||
$domText = preg_replace('/\s+/u', ' ', ltrim($textNode->nodeValue));
|
{
|
||||||
|
$domText = preg_replace(
|
||||||
|
'/\s+/u',
|
||||||
|
' ',
|
||||||
|
str_replace(["\r", "\n"], ' ', $textNode->nodeValue)
|
||||||
|
);
|
||||||
$this->stringData .= $domText;
|
$this->stringData .= $domText;
|
||||||
$this->buildTextRun();
|
$this->buildTextRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleCallback($element, $callbackTag, $callbacks) {
|
/**
|
||||||
|
* @param DOMElement $element
|
||||||
|
* @param string $callbackTag
|
||||||
|
* @param array $callbacks
|
||||||
|
*/
|
||||||
|
protected function handleCallback(DOMElement $element, $callbackTag, array $callbacks)
|
||||||
|
{
|
||||||
if (isset($callbacks[$callbackTag])) {
|
if (isset($callbacks[$callbackTag])) {
|
||||||
$elementHandler = $callbacks[$callbackTag];
|
$elementHandler = $callbacks[$callbackTag];
|
||||||
if (method_exists($this, $elementHandler)) {
|
if (method_exists($this, $elementHandler)) {
|
||||||
call_user_func(array($this, $elementHandler), $element);
|
call_user_func([$this, $elementHandler], $element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function parseElementNode(DOMElement $element) {
|
protected function parseElementNode(DOMElement $element)
|
||||||
|
{
|
||||||
$callbackTag = strtolower($element->nodeName);
|
$callbackTag = strtolower($element->nodeName);
|
||||||
$this->stack[] = $callbackTag;
|
$this->stack[] = $callbackTag;
|
||||||
|
|
||||||
$this->handleCallback($element, $callbackTag, $this->startTagCallbacks);
|
$this->handleCallback($element, $callbackTag, $this->startTagCallbacks);
|
||||||
|
|
||||||
$this->parseElements($element);
|
$this->parseElements($element);
|
||||||
$this->stringData .= ' ';
|
|
||||||
array_pop($this->stack);
|
array_pop($this->stack);
|
||||||
|
|
||||||
$this->handleCallback($element, $callbackTag, $this->endTagCallbacks);
|
$this->handleCallback($element, $callbackTag, $this->endTagCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function parseElements(DOMNode $element) {
|
protected function parseElements(DOMNode $element)
|
||||||
|
{
|
||||||
foreach ($element->childNodes as $child) {
|
foreach ($element->childNodes as $child) {
|
||||||
if ($child instanceof DOMText) {
|
if ($child instanceof DOMText) {
|
||||||
$this->parseTextNode($child);
|
$this->parseTextNode($child);
|
||||||
333
PhpOffice/PhpSpreadsheet/Helper/Migrator.php
Normal file
333
PhpOffice/PhpSpreadsheet/Helper/Migrator.php
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||||
|
|
||||||
|
class Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $from;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $to;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->from = array_keys($this->getMapping());
|
||||||
|
$this->to = array_values($this->getMapping());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ordered mapping from old PHPExcel class names to new PhpSpreadsheet one.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getMapping()
|
||||||
|
{
|
||||||
|
// Order matters here, we should have the deepest namespaces first (the most "unique" strings)
|
||||||
|
$classes = [
|
||||||
|
'PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip::class,
|
||||||
|
'PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer::class,
|
||||||
|
'PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::class,
|
||||||
|
'PHPExcel_Shared_Escher_DgContainer_SpgrContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer::class,
|
||||||
|
'PHPExcel_Shared_Escher_DggContainer_BstoreContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer::class,
|
||||||
|
'PHPExcel_Shared_OLE_PPS_File' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::class,
|
||||||
|
'PHPExcel_Shared_OLE_PPS_Root' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root::class,
|
||||||
|
'PHPExcel_Worksheet_AutoFilter_Column_Rule' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Cell_Comment' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment::class,
|
||||||
|
'PHPExcel_Calculation_Token_Stack' => \PhpOffice\PhpSpreadsheet\Calculation\Token\Stack::class,
|
||||||
|
'PHPExcel_Chart_Renderer_jpgraph' => \PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class,
|
||||||
|
'PHPExcel_Reader_Excel5_Escher' => \PhpOffice\PhpSpreadsheet\Reader\Xls\Escher::class,
|
||||||
|
'PHPExcel_Reader_Excel5_MD5' => \PhpOffice\PhpSpreadsheet\Reader\Xls\MD5::class,
|
||||||
|
'PHPExcel_Reader_Excel5_RC4' => \PhpOffice\PhpSpreadsheet\Reader\Xls\RC4::class,
|
||||||
|
'PHPExcel_Reader_Excel2007_Chart' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart::class,
|
||||||
|
'PHPExcel_Reader_Excel2007_Theme' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme::class,
|
||||||
|
'PHPExcel_Shared_Escher_DgContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer::class,
|
||||||
|
'PHPExcel_Shared_Escher_DggContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer::class,
|
||||||
|
'CholeskyDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\CholeskyDecomposition::class,
|
||||||
|
'EigenvalueDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\EigenvalueDecomposition::class,
|
||||||
|
'PHPExcel_Shared_JAMA_LUDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\LUDecomposition::class,
|
||||||
|
'PHPExcel_Shared_JAMA_Matrix' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix::class,
|
||||||
|
'QRDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\QRDecomposition::class,
|
||||||
|
'PHPExcel_Shared_JAMA_QRDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\QRDecomposition::class,
|
||||||
|
'SingularValueDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\SingularValueDecomposition::class,
|
||||||
|
'PHPExcel_Shared_OLE_ChainedBlockStream' => \PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream::class,
|
||||||
|
'PHPExcel_Shared_OLE_PPS' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS::class,
|
||||||
|
'PHPExcel_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\BestFit::class,
|
||||||
|
'PHPExcel_Exponential_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\ExponentialBestFit::class,
|
||||||
|
'PHPExcel_Linear_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\LinearBestFit::class,
|
||||||
|
'PHPExcel_Logarithmic_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\LogarithmicBestFit::class,
|
||||||
|
'polynomialBestFit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PolynomialBestFit::class,
|
||||||
|
'PHPExcel_Polynomial_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PolynomialBestFit::class,
|
||||||
|
'powerBestFit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PowerBestFit::class,
|
||||||
|
'PHPExcel_Power_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PowerBestFit::class,
|
||||||
|
'trendClass' => \PhpOffice\PhpSpreadsheet\Shared\Trend\Trend::class,
|
||||||
|
'PHPExcel_Worksheet_AutoFilter_Column' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::class,
|
||||||
|
'PHPExcel_Worksheet_Drawing_Shadow' => \PhpOffice\PhpSpreadsheet\Worksheet\Drawing\Shadow::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Content' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Content::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Meta' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Meta::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_MetaInf' => \PhpOffice\PhpSpreadsheet\Writer\Ods\MetaInf::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Mimetype' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Mimetype::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Settings' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Settings::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Styles' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Styles::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_Thumbnails' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument_WriterPart' => \PhpOffice\PhpSpreadsheet\Writer\Ods\WriterPart::class,
|
||||||
|
'PHPExcel_Writer_PDF_Core' => \PhpOffice\PhpSpreadsheet\Writer\Pdf::class,
|
||||||
|
'PHPExcel_Writer_PDF_DomPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class,
|
||||||
|
'PHPExcel_Writer_PDF_mPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class,
|
||||||
|
'PHPExcel_Writer_PDF_tcPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf::class,
|
||||||
|
'PHPExcel_Writer_Excel5_BIFFwriter' => \PhpOffice\PhpSpreadsheet\Writer\Xls\BIFFwriter::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Escher' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Escher::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Font' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Font::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Parser' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Workbook' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Worksheet' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet::class,
|
||||||
|
'PHPExcel_Writer_Excel5_Xf' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Chart' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Comments' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_ContentTypes' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_DocProps' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Drawing' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Rels' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_RelsRibbon' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_RelsVBA' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_StringTable' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Style' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Theme' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Workbook' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_Worksheet' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::class,
|
||||||
|
'PHPExcel_Writer_Excel2007_WriterPart' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart::class,
|
||||||
|
'PHPExcel_CachedObjectStorage_CacheBase' => \PhpOffice\PhpSpreadsheet\Collection\Cells::class,
|
||||||
|
'PHPExcel_CalcEngine_CyclicReferenceStack' => \PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack::class,
|
||||||
|
'PHPExcel_CalcEngine_Logger' => \PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger::class,
|
||||||
|
'PHPExcel_Calculation_Functions' => \PhpOffice\PhpSpreadsheet\Calculation\Functions::class,
|
||||||
|
'PHPExcel_Calculation_Function' => \PhpOffice\PhpSpreadsheet\Calculation\Category::class,
|
||||||
|
'PHPExcel_Calculation_Database' => \PhpOffice\PhpSpreadsheet\Calculation\Database::class,
|
||||||
|
'PHPExcel_Calculation_DateTime' => \PhpOffice\PhpSpreadsheet\Calculation\DateTime::class,
|
||||||
|
'PHPExcel_Calculation_Engineering' => \PhpOffice\PhpSpreadsheet\Calculation\Engineering::class,
|
||||||
|
'PHPExcel_Calculation_Exception' => \PhpOffice\PhpSpreadsheet\Calculation\Exception::class,
|
||||||
|
'PHPExcel_Calculation_ExceptionHandler' => \PhpOffice\PhpSpreadsheet\Calculation\ExceptionHandler::class,
|
||||||
|
'PHPExcel_Calculation_Financial' => \PhpOffice\PhpSpreadsheet\Calculation\Financial::class,
|
||||||
|
'PHPExcel_Calculation_FormulaParser' => \PhpOffice\PhpSpreadsheet\Calculation\FormulaParser::class,
|
||||||
|
'PHPExcel_Calculation_FormulaToken' => \PhpOffice\PhpSpreadsheet\Calculation\FormulaToken::class,
|
||||||
|
'PHPExcel_Calculation_Logical' => \PhpOffice\PhpSpreadsheet\Calculation\Logical::class,
|
||||||
|
'PHPExcel_Calculation_LookupRef' => \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::class,
|
||||||
|
'PHPExcel_Calculation_MathTrig' => \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::class,
|
||||||
|
'PHPExcel_Calculation_Statistical' => \PhpOffice\PhpSpreadsheet\Calculation\Statistical::class,
|
||||||
|
'PHPExcel_Calculation_TextData' => \PhpOffice\PhpSpreadsheet\Calculation\TextData::class,
|
||||||
|
'PHPExcel_Cell_AdvancedValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class,
|
||||||
|
'PHPExcel_Cell_DataType' => \PhpOffice\PhpSpreadsheet\Cell\DataType::class,
|
||||||
|
'PHPExcel_Cell_DataValidation' => \PhpOffice\PhpSpreadsheet\Cell\DataValidation::class,
|
||||||
|
'PHPExcel_Cell_DefaultValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder::class,
|
||||||
|
'PHPExcel_Cell_Hyperlink' => \PhpOffice\PhpSpreadsheet\Cell\Hyperlink::class,
|
||||||
|
'PHPExcel_Cell_IValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\IValueBinder::class,
|
||||||
|
'PHPExcel_Chart_Axis' => \PhpOffice\PhpSpreadsheet\Chart\Axis::class,
|
||||||
|
'PHPExcel_Chart_DataSeries' => \PhpOffice\PhpSpreadsheet\Chart\DataSeries::class,
|
||||||
|
'PHPExcel_Chart_DataSeriesValues' => \PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues::class,
|
||||||
|
'PHPExcel_Chart_Exception' => \PhpOffice\PhpSpreadsheet\Chart\Exception::class,
|
||||||
|
'PHPExcel_Chart_GridLines' => \PhpOffice\PhpSpreadsheet\Chart\GridLines::class,
|
||||||
|
'PHPExcel_Chart_Layout' => \PhpOffice\PhpSpreadsheet\Chart\Layout::class,
|
||||||
|
'PHPExcel_Chart_Legend' => \PhpOffice\PhpSpreadsheet\Chart\Legend::class,
|
||||||
|
'PHPExcel_Chart_PlotArea' => \PhpOffice\PhpSpreadsheet\Chart\PlotArea::class,
|
||||||
|
'PHPExcel_Properties' => \PhpOffice\PhpSpreadsheet\Chart\Properties::class,
|
||||||
|
'PHPExcel_Chart_Title' => \PhpOffice\PhpSpreadsheet\Chart\Title::class,
|
||||||
|
'PHPExcel_DocumentProperties' => \PhpOffice\PhpSpreadsheet\Document\Properties::class,
|
||||||
|
'PHPExcel_DocumentSecurity' => \PhpOffice\PhpSpreadsheet\Document\Security::class,
|
||||||
|
'PHPExcel_Helper_HTML' => \PhpOffice\PhpSpreadsheet\Helper\Html::class,
|
||||||
|
'PHPExcel_Reader_Abstract' => \PhpOffice\PhpSpreadsheet\Reader\BaseReader::class,
|
||||||
|
'PHPExcel_Reader_CSV' => \PhpOffice\PhpSpreadsheet\Reader\Csv::class,
|
||||||
|
'PHPExcel_Reader_DefaultReadFilter' => \PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter::class,
|
||||||
|
'PHPExcel_Reader_Excel2003XML' => \PhpOffice\PhpSpreadsheet\Reader\Xml::class,
|
||||||
|
'PHPExcel_Reader_Exception' => \PhpOffice\PhpSpreadsheet\Reader\Exception::class,
|
||||||
|
'PHPExcel_Reader_Gnumeric' => \PhpOffice\PhpSpreadsheet\Reader\Gnumeric::class,
|
||||||
|
'PHPExcel_Reader_HTML' => \PhpOffice\PhpSpreadsheet\Reader\Html::class,
|
||||||
|
'PHPExcel_Reader_IReadFilter' => \PhpOffice\PhpSpreadsheet\Reader\IReadFilter::class,
|
||||||
|
'PHPExcel_Reader_IReader' => \PhpOffice\PhpSpreadsheet\Reader\IReader::class,
|
||||||
|
'PHPExcel_Reader_OOCalc' => \PhpOffice\PhpSpreadsheet\Reader\Ods::class,
|
||||||
|
'PHPExcel_Reader_SYLK' => \PhpOffice\PhpSpreadsheet\Reader\Slk::class,
|
||||||
|
'PHPExcel_Reader_Excel5' => \PhpOffice\PhpSpreadsheet\Reader\Xls::class,
|
||||||
|
'PHPExcel_Reader_Excel2007' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx::class,
|
||||||
|
'PHPExcel_RichText_ITextElement' => \PhpOffice\PhpSpreadsheet\RichText\ITextElement::class,
|
||||||
|
'PHPExcel_RichText_Run' => \PhpOffice\PhpSpreadsheet\RichText\Run::class,
|
||||||
|
'PHPExcel_RichText_TextElement' => \PhpOffice\PhpSpreadsheet\RichText\TextElement::class,
|
||||||
|
'PHPExcel_Shared_CodePage' => \PhpOffice\PhpSpreadsheet\Shared\CodePage::class,
|
||||||
|
'PHPExcel_Shared_Date' => \PhpOffice\PhpSpreadsheet\Shared\Date::class,
|
||||||
|
'PHPExcel_Shared_Drawing' => \PhpOffice\PhpSpreadsheet\Shared\Drawing::class,
|
||||||
|
'PHPExcel_Shared_Escher' => \PhpOffice\PhpSpreadsheet\Shared\Escher::class,
|
||||||
|
'PHPExcel_Shared_File' => \PhpOffice\PhpSpreadsheet\Shared\File::class,
|
||||||
|
'PHPExcel_Shared_Font' => \PhpOffice\PhpSpreadsheet\Shared\Font::class,
|
||||||
|
'PHPExcel_Shared_OLE' => \PhpOffice\PhpSpreadsheet\Shared\OLE::class,
|
||||||
|
'PHPExcel_Shared_OLERead' => \PhpOffice\PhpSpreadsheet\Shared\OLERead::class,
|
||||||
|
'PHPExcel_Shared_PasswordHasher' => \PhpOffice\PhpSpreadsheet\Shared\PasswordHasher::class,
|
||||||
|
'PHPExcel_Shared_String' => \PhpOffice\PhpSpreadsheet\Shared\StringHelper::class,
|
||||||
|
'PHPExcel_Shared_TimeZone' => \PhpOffice\PhpSpreadsheet\Shared\TimeZone::class,
|
||||||
|
'PHPExcel_Shared_XMLWriter' => \PhpOffice\PhpSpreadsheet\Shared\XMLWriter::class,
|
||||||
|
'PHPExcel_Shared_Excel5' => \PhpOffice\PhpSpreadsheet\Shared\Xls::class,
|
||||||
|
'PHPExcel_Style_Alignment' => \PhpOffice\PhpSpreadsheet\Style\Alignment::class,
|
||||||
|
'PHPExcel_Style_Border' => \PhpOffice\PhpSpreadsheet\Style\Border::class,
|
||||||
|
'PHPExcel_Style_Borders' => \PhpOffice\PhpSpreadsheet\Style\Borders::class,
|
||||||
|
'PHPExcel_Style_Color' => \PhpOffice\PhpSpreadsheet\Style\Color::class,
|
||||||
|
'PHPExcel_Style_Conditional' => \PhpOffice\PhpSpreadsheet\Style\Conditional::class,
|
||||||
|
'PHPExcel_Style_Fill' => \PhpOffice\PhpSpreadsheet\Style\Fill::class,
|
||||||
|
'PHPExcel_Style_Font' => \PhpOffice\PhpSpreadsheet\Style\Font::class,
|
||||||
|
'PHPExcel_Style_NumberFormat' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::class,
|
||||||
|
'PHPExcel_Style_Protection' => \PhpOffice\PhpSpreadsheet\Style\Protection::class,
|
||||||
|
'PHPExcel_Style_Supervisor' => \PhpOffice\PhpSpreadsheet\Style\Supervisor::class,
|
||||||
|
'PHPExcel_Worksheet_AutoFilter' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter::class,
|
||||||
|
'PHPExcel_Worksheet_BaseDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing::class,
|
||||||
|
'PHPExcel_Worksheet_CellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\CellIterator::class,
|
||||||
|
'PHPExcel_Worksheet_Column' => \PhpOffice\PhpSpreadsheet\Worksheet\Column::class,
|
||||||
|
'PHPExcel_Worksheet_ColumnCellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnCellIterator::class,
|
||||||
|
'PHPExcel_Worksheet_ColumnDimension' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension::class,
|
||||||
|
'PHPExcel_Worksheet_ColumnIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnIterator::class,
|
||||||
|
'PHPExcel_Worksheet_Drawing' => \PhpOffice\PhpSpreadsheet\Worksheet\Drawing::class,
|
||||||
|
'PHPExcel_Worksheet_HeaderFooter' => \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooter::class,
|
||||||
|
'PHPExcel_Worksheet_HeaderFooterDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing::class,
|
||||||
|
'PHPExcel_WorksheetIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\Iterator::class,
|
||||||
|
'PHPExcel_Worksheet_MemoryDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::class,
|
||||||
|
'PHPExcel_Worksheet_PageMargins' => \PhpOffice\PhpSpreadsheet\Worksheet\PageMargins::class,
|
||||||
|
'PHPExcel_Worksheet_PageSetup' => \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::class,
|
||||||
|
'PHPExcel_Worksheet_Protection' => \PhpOffice\PhpSpreadsheet\Worksheet\Protection::class,
|
||||||
|
'PHPExcel_Worksheet_Row' => \PhpOffice\PhpSpreadsheet\Worksheet\Row::class,
|
||||||
|
'PHPExcel_Worksheet_RowCellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator::class,
|
||||||
|
'PHPExcel_Worksheet_RowDimension' => \PhpOffice\PhpSpreadsheet\Worksheet\RowDimension::class,
|
||||||
|
'PHPExcel_Worksheet_RowIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\RowIterator::class,
|
||||||
|
'PHPExcel_Worksheet_SheetView' => \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::class,
|
||||||
|
'PHPExcel_Writer_Abstract' => \PhpOffice\PhpSpreadsheet\Writer\BaseWriter::class,
|
||||||
|
'PHPExcel_Writer_CSV' => \PhpOffice\PhpSpreadsheet\Writer\Csv::class,
|
||||||
|
'PHPExcel_Writer_Exception' => \PhpOffice\PhpSpreadsheet\Writer\Exception::class,
|
||||||
|
'PHPExcel_Writer_HTML' => \PhpOffice\PhpSpreadsheet\Writer\Html::class,
|
||||||
|
'PHPExcel_Writer_IWriter' => \PhpOffice\PhpSpreadsheet\Writer\IWriter::class,
|
||||||
|
'PHPExcel_Writer_OpenDocument' => \PhpOffice\PhpSpreadsheet\Writer\Ods::class,
|
||||||
|
'PHPExcel_Writer_PDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf::class,
|
||||||
|
'PHPExcel_Writer_Excel5' => \PhpOffice\PhpSpreadsheet\Writer\Xls::class,
|
||||||
|
'PHPExcel_Writer_Excel2007' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx::class,
|
||||||
|
'PHPExcel_CachedObjectStorageFactory' => \PhpOffice\PhpSpreadsheet\Collection\CellsFactory::class,
|
||||||
|
'PHPExcel_Calculation' => \PhpOffice\PhpSpreadsheet\Calculation\Calculation::class,
|
||||||
|
'PHPExcel_Cell' => \PhpOffice\PhpSpreadsheet\Cell\Cell::class,
|
||||||
|
'PHPExcel_Chart' => \PhpOffice\PhpSpreadsheet\Chart\Chart::class,
|
||||||
|
'PHPExcel_Comment' => \PhpOffice\PhpSpreadsheet\Comment::class,
|
||||||
|
'PHPExcel_Exception' => \PhpOffice\PhpSpreadsheet\Exception::class,
|
||||||
|
'PHPExcel_HashTable' => \PhpOffice\PhpSpreadsheet\HashTable::class,
|
||||||
|
'PHPExcel_IComparable' => \PhpOffice\PhpSpreadsheet\IComparable::class,
|
||||||
|
'PHPExcel_IOFactory' => \PhpOffice\PhpSpreadsheet\IOFactory::class,
|
||||||
|
'PHPExcel_NamedRange' => \PhpOffice\PhpSpreadsheet\NamedRange::class,
|
||||||
|
'PHPExcel_ReferenceHelper' => \PhpOffice\PhpSpreadsheet\ReferenceHelper::class,
|
||||||
|
'PHPExcel_RichText' => \PhpOffice\PhpSpreadsheet\RichText\RichText::class,
|
||||||
|
'PHPExcel_Settings' => \PhpOffice\PhpSpreadsheet\Settings::class,
|
||||||
|
'PHPExcel_Style' => \PhpOffice\PhpSpreadsheet\Style\Style::class,
|
||||||
|
'PHPExcel_Worksheet' => \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
$methods = [
|
||||||
|
'MINUTEOFHOUR' => 'MINUTE',
|
||||||
|
'SECONDOFMINUTE' => 'SECOND',
|
||||||
|
'DAYOFWEEK' => 'WEEKDAY',
|
||||||
|
'WEEKOFYEAR' => 'WEEKNUM',
|
||||||
|
'ExcelToPHPObject' => 'excelToDateTimeObject',
|
||||||
|
'ExcelToPHP' => 'excelToTimestamp',
|
||||||
|
'FormattedPHPToExcel' => 'formattedPHPToExcel',
|
||||||
|
'Cell::absoluteCoordinate' => 'Coordinate::absoluteCoordinate',
|
||||||
|
'Cell::absoluteReference' => 'Coordinate::absoluteReference',
|
||||||
|
'Cell::buildRange' => 'Coordinate::buildRange',
|
||||||
|
'Cell::columnIndexFromString' => 'Coordinate::columnIndexFromString',
|
||||||
|
'Cell::coordinateFromString' => 'Coordinate::coordinateFromString',
|
||||||
|
'Cell::extractAllCellReferencesInRange' => 'Coordinate::extractAllCellReferencesInRange',
|
||||||
|
'Cell::getRangeBoundaries' => 'Coordinate::getRangeBoundaries',
|
||||||
|
'Cell::mergeRangesInCollection' => 'Coordinate::mergeRangesInCollection',
|
||||||
|
'Cell::rangeBoundaries' => 'Coordinate::rangeBoundaries',
|
||||||
|
'Cell::rangeDimension' => 'Coordinate::rangeDimension',
|
||||||
|
'Cell::splitRange' => 'Coordinate::splitRange',
|
||||||
|
'Cell::stringFromColumnIndex' => 'Coordinate::stringFromColumnIndex',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Keep '\' prefix for class names
|
||||||
|
$prefixedClasses = [];
|
||||||
|
foreach ($classes as $key => &$value) {
|
||||||
|
$value = str_replace('PhpOffice\\', '\\PhpOffice\\', $value);
|
||||||
|
$prefixedClasses['\\' . $key] = $value;
|
||||||
|
}
|
||||||
|
$mapping = $prefixedClasses + $classes + $methods;
|
||||||
|
|
||||||
|
return $mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in all files in given directory.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
*/
|
||||||
|
private function recursiveReplace($path)
|
||||||
|
{
|
||||||
|
$patterns = [
|
||||||
|
'/*.md',
|
||||||
|
'/*.txt',
|
||||||
|
'/*.TXT',
|
||||||
|
'/*.php',
|
||||||
|
'/*.phpt',
|
||||||
|
'/*.php3',
|
||||||
|
'/*.php4',
|
||||||
|
'/*.php5',
|
||||||
|
'/*.phtml',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
foreach (glob($path . $pattern) as $file) {
|
||||||
|
if (strpos($path, '/vendor/') !== false) {
|
||||||
|
echo $file . " skipped\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$original = file_get_contents($file);
|
||||||
|
$converted = $this->replace($original);
|
||||||
|
|
||||||
|
if ($original !== $converted) {
|
||||||
|
echo $file . " converted\n";
|
||||||
|
file_put_contents($file, $converted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the recursion in subdirectory
|
||||||
|
foreach (glob($path . '/*', GLOB_ONLYDIR) as $subpath) {
|
||||||
|
if (strpos($subpath, $path . '/') === 0) {
|
||||||
|
$this->recursiveReplace($subpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function migrate()
|
||||||
|
{
|
||||||
|
$path = realpath(getcwd());
|
||||||
|
echo 'This will search and replace recursively in ' . $path . PHP_EOL;
|
||||||
|
echo 'You MUST backup your files first, or you risk losing data.' . PHP_EOL;
|
||||||
|
echo 'Are you sure ? (y/n)';
|
||||||
|
|
||||||
|
$confirm = fread(STDIN, 1);
|
||||||
|
if ($confirm === 'y') {
|
||||||
|
$this->recursiveReplace($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate the given code from PHPExcel to PhpSpreadsheet.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function replace($original)
|
||||||
|
{
|
||||||
|
$converted = str_replace($this->from, $this->to, $original);
|
||||||
|
|
||||||
|
// The string "PHPExcel" gets special treatment because of how common it might be.
|
||||||
|
// This regex requires a word boundary around the string, and it can't be
|
||||||
|
// preceded by $ or -> (goal is to filter out cases where a variable is named $PHPExcel or similar)
|
||||||
|
$converted = preg_replace('~(?<!\$|->)(\b|\\\\)PHPExcel\b~', '\\' . \PhpOffice\PhpSpreadsheet\Spreadsheet::class, $converted);
|
||||||
|
|
||||||
|
return $converted;
|
||||||
|
}
|
||||||
|
}
|
||||||
230
PhpOffice/PhpSpreadsheet/Helper/Sample.php
Normal file
230
PhpOffice/PhpSpreadsheet/Helper/Sample.php
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Helper;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Pdf;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use RecursiveRegexIterator;
|
||||||
|
use ReflectionClass;
|
||||||
|
use RegexIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to be used in sample code.
|
||||||
|
*/
|
||||||
|
class Sample
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns whether we run on CLI or browser.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isCli()
|
||||||
|
{
|
||||||
|
return PHP_SAPI === 'cli';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the filename currently being executed.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getScriptFilename()
|
||||||
|
{
|
||||||
|
return basename($_SERVER['SCRIPT_FILENAME'], '.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we are executing the index page.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isIndex()
|
||||||
|
{
|
||||||
|
return $this->getScriptFilename() === 'index';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the page title.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPageTitle()
|
||||||
|
{
|
||||||
|
return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the page heading.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPageHeading()
|
||||||
|
{
|
||||||
|
return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all known samples.
|
||||||
|
*
|
||||||
|
* @return string[] [$name => $path]
|
||||||
|
*/
|
||||||
|
public function getSamples()
|
||||||
|
{
|
||||||
|
// Populate samples
|
||||||
|
$baseDir = realpath(__DIR__ . '/../../../samples');
|
||||||
|
$directory = new RecursiveDirectoryIterator($baseDir);
|
||||||
|
$iterator = new RecursiveIteratorIterator($directory);
|
||||||
|
$regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
foreach ($regex as $file) {
|
||||||
|
$file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
|
||||||
|
$info = pathinfo($file);
|
||||||
|
$category = str_replace('_', ' ', $info['dirname']);
|
||||||
|
$name = str_replace('_', ' ', preg_replace('/(|\.php)/', '', $info['filename']));
|
||||||
|
if (!in_array($category, ['.', 'boostrap', 'templates'])) {
|
||||||
|
if (!isset($files[$category])) {
|
||||||
|
$files[$category] = [];
|
||||||
|
}
|
||||||
|
$files[$category][$name] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort everything
|
||||||
|
ksort($files);
|
||||||
|
foreach ($files as &$f) {
|
||||||
|
asort($f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write documents.
|
||||||
|
*
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
* @param string $filename
|
||||||
|
* @param string[] $writers
|
||||||
|
*/
|
||||||
|
public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'])
|
||||||
|
{
|
||||||
|
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
|
||||||
|
$spreadsheet->setActiveSheetIndex(0);
|
||||||
|
|
||||||
|
// Write documents
|
||||||
|
foreach ($writers as $writerType) {
|
||||||
|
$path = $this->getFilename($filename, mb_strtolower($writerType));
|
||||||
|
$writer = IOFactory::createWriter($spreadsheet, $writerType);
|
||||||
|
if ($writer instanceof Pdf) {
|
||||||
|
// PDF writer needs temporary directory
|
||||||
|
$tempDir = $this->getTemporaryFolder();
|
||||||
|
$writer->setTempDir($tempDir);
|
||||||
|
}
|
||||||
|
$callStartTime = microtime(true);
|
||||||
|
$writer->save($path);
|
||||||
|
$this->logWrite($writer, $path, $callStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logEndingNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the temporary directory and make sure it exists.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getTemporaryFolder()
|
||||||
|
{
|
||||||
|
$tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
|
||||||
|
if (!is_dir($tempFolder)) {
|
||||||
|
if (!mkdir($tempFolder) && !is_dir($tempFolder)) {
|
||||||
|
throw new \RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tempFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the filename that should be used for sample output.
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $extension
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFilename($filename, $extension = 'xlsx')
|
||||||
|
{
|
||||||
|
$originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random temporary file name.
|
||||||
|
*
|
||||||
|
* @param string $extension
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTemporaryFilename($extension = 'xlsx')
|
||||||
|
{
|
||||||
|
$temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
|
||||||
|
unlink($temporaryFilename);
|
||||||
|
|
||||||
|
return $temporaryFilename . '.' . $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function log($message)
|
||||||
|
{
|
||||||
|
$eol = $this->isCli() ? PHP_EOL : '<br />';
|
||||||
|
echo date('H:i:s ') . $message . $eol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log ending notes.
|
||||||
|
*/
|
||||||
|
public function logEndingNotes()
|
||||||
|
{
|
||||||
|
// Do not show execution time for index
|
||||||
|
$this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a line about the write operation.
|
||||||
|
*
|
||||||
|
* @param IWriter $writer
|
||||||
|
* @param string $path
|
||||||
|
* @param float $callStartTime
|
||||||
|
*/
|
||||||
|
public function logWrite(IWriter $writer, $path, $callStartTime)
|
||||||
|
{
|
||||||
|
$callEndTime = microtime(true);
|
||||||
|
$callTime = $callEndTime - $callStartTime;
|
||||||
|
$reflection = new ReflectionClass($writer);
|
||||||
|
$format = $reflection->getShortName();
|
||||||
|
$message = "Write {$format} format to <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
|
||||||
|
|
||||||
|
$this->log($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a line about the read operation.
|
||||||
|
*
|
||||||
|
* @param string $format
|
||||||
|
* @param string $path
|
||||||
|
* @param float $callStartTime
|
||||||
|
*/
|
||||||
|
public function logRead($format, $path, $callStartTime)
|
||||||
|
{
|
||||||
|
$callEndTime = microtime(true);
|
||||||
|
$callTime = $callEndTime - $callStartTime;
|
||||||
|
$message = "Read {$format} format from <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
|
||||||
|
|
||||||
|
$this->log($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
PhpOffice/PhpSpreadsheet/IComparable.php
Normal file
13
PhpOffice/PhpSpreadsheet/IComparable.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
interface IComparable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get hash code.
|
||||||
|
*
|
||||||
|
* @return string Hash code
|
||||||
|
*/
|
||||||
|
public function getHashCode();
|
||||||
|
}
|
||||||
230
PhpOffice/PhpSpreadsheet/IOFactory.php
Normal file
230
PhpOffice/PhpSpreadsheet/IOFactory.php
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create readers and writers easily.
|
||||||
|
*
|
||||||
|
* It is not required to use this class, but it should make it easier to read and write files.
|
||||||
|
* Especially for reading files with an unknown format.
|
||||||
|
*/
|
||||||
|
abstract class IOFactory
|
||||||
|
{
|
||||||
|
private static $readers = [
|
||||||
|
'Xlsx' => Reader\Xlsx::class,
|
||||||
|
'Xls' => Reader\Xls::class,
|
||||||
|
'Xml' => Reader\Xml::class,
|
||||||
|
'Ods' => Reader\Ods::class,
|
||||||
|
'Slk' => Reader\Slk::class,
|
||||||
|
'Gnumeric' => Reader\Gnumeric::class,
|
||||||
|
'Html' => Reader\Html::class,
|
||||||
|
'Csv' => Reader\Csv::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $writers = [
|
||||||
|
'Xls' => Writer\Xls::class,
|
||||||
|
'Xlsx' => Writer\Xlsx::class,
|
||||||
|
'Ods' => Writer\Ods::class,
|
||||||
|
'Csv' => Writer\Csv::class,
|
||||||
|
'Html' => Writer\Html::class,
|
||||||
|
'Tcpdf' => Writer\Pdf\Tcpdf::class,
|
||||||
|
'Dompdf' => Writer\Pdf\Dompdf::class,
|
||||||
|
'Mpdf' => Writer\Pdf\Mpdf::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Writer\IWriter.
|
||||||
|
*
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
* @param string $writerType Example: Xlsx
|
||||||
|
*
|
||||||
|
* @throws Writer\Exception
|
||||||
|
*
|
||||||
|
* @return Writer\IWriter
|
||||||
|
*/
|
||||||
|
public static function createWriter(Spreadsheet $spreadsheet, $writerType)
|
||||||
|
{
|
||||||
|
if (!isset(self::$writers[$writerType])) {
|
||||||
|
throw new Writer\Exception("No writer found for type $writerType");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate writer
|
||||||
|
$className = self::$writers[$writerType];
|
||||||
|
$writer = new $className($spreadsheet);
|
||||||
|
|
||||||
|
return $writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Reader\IReader.
|
||||||
|
*
|
||||||
|
* @param string $readerType Example: Xlsx
|
||||||
|
*
|
||||||
|
* @throws Reader\Exception
|
||||||
|
*
|
||||||
|
* @return Reader\IReader
|
||||||
|
*/
|
||||||
|
public static function createReader($readerType)
|
||||||
|
{
|
||||||
|
if (!isset(self::$readers[$readerType])) {
|
||||||
|
throw new Reader\Exception("No reader found for type $readerType");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate reader
|
||||||
|
$className = self::$readers[$readerType];
|
||||||
|
$reader = new $className();
|
||||||
|
|
||||||
|
return $reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Spreadsheet from file using automatic Reader\IReader resolution.
|
||||||
|
*
|
||||||
|
* @param string $pFilename The name of the spreadsheet file
|
||||||
|
*
|
||||||
|
* @throws Reader\Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public static function load($pFilename)
|
||||||
|
{
|
||||||
|
$reader = self::createReaderForFile($pFilename);
|
||||||
|
|
||||||
|
return $reader->load($pFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify file type using automatic Reader\IReader resolution.
|
||||||
|
*
|
||||||
|
* @param string $pFilename The name of the spreadsheet file to identify
|
||||||
|
*
|
||||||
|
* @throws Reader\Exception
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function identify($pFilename)
|
||||||
|
{
|
||||||
|
$reader = self::createReaderForFile($pFilename);
|
||||||
|
$className = get_class($reader);
|
||||||
|
$classType = explode('\\', $className);
|
||||||
|
unset($reader);
|
||||||
|
|
||||||
|
return array_pop($classType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Reader\IReader for file using automatic Reader\IReader resolution.
|
||||||
|
*
|
||||||
|
* @param string $filename The name of the spreadsheet file
|
||||||
|
*
|
||||||
|
* @throws Reader\Exception
|
||||||
|
*
|
||||||
|
* @return Reader\IReader
|
||||||
|
*/
|
||||||
|
public static function createReaderForFile($filename)
|
||||||
|
{
|
||||||
|
File::assertFile($filename);
|
||||||
|
|
||||||
|
// First, lucky guess by inspecting file extension
|
||||||
|
$guessedReader = self::getReaderTypeFromExtension($filename);
|
||||||
|
if ($guessedReader !== null) {
|
||||||
|
$reader = self::createReader($guessedReader);
|
||||||
|
|
||||||
|
// Let's see if we are lucky
|
||||||
|
if (isset($reader) && $reader->canRead($filename)) {
|
||||||
|
return $reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here then "lucky guess" didn't give any result
|
||||||
|
// Try walking through all the options in self::$autoResolveClasses
|
||||||
|
foreach (self::$readers as $type => $class) {
|
||||||
|
// Ignore our original guess, we know that won't work
|
||||||
|
if ($type !== $guessedReader) {
|
||||||
|
$reader = self::createReader($type);
|
||||||
|
if ($reader->canRead($filename)) {
|
||||||
|
return $reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Reader\Exception('Unable to identify a reader for this file');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess a reader type from the file extension, if any.
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
private static function getReaderTypeFromExtension($filename)
|
||||||
|
{
|
||||||
|
$pathinfo = pathinfo($filename);
|
||||||
|
if (!isset($pathinfo['extension'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (strtolower($pathinfo['extension'])) {
|
||||||
|
case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
|
||||||
|
case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
|
||||||
|
case 'xltx': // Excel (OfficeOpenXML) Template
|
||||||
|
case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
|
||||||
|
return 'Xlsx';
|
||||||
|
case 'xls': // Excel (BIFF) Spreadsheet
|
||||||
|
case 'xlt': // Excel (BIFF) Template
|
||||||
|
return 'Xls';
|
||||||
|
case 'ods': // Open/Libre Offic Calc
|
||||||
|
case 'ots': // Open/Libre Offic Calc Template
|
||||||
|
return 'Ods';
|
||||||
|
case 'slk':
|
||||||
|
return 'Slk';
|
||||||
|
case 'xml': // Excel 2003 SpreadSheetML
|
||||||
|
return 'Xml';
|
||||||
|
case 'gnumeric':
|
||||||
|
return 'Gnumeric';
|
||||||
|
case 'htm':
|
||||||
|
case 'html':
|
||||||
|
return 'Html';
|
||||||
|
case 'csv':
|
||||||
|
// Do nothing
|
||||||
|
// We must not try to use CSV reader since it loads
|
||||||
|
// all files including Excel files etc.
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a writer with its type and class name.
|
||||||
|
*
|
||||||
|
* @param string $writerType
|
||||||
|
* @param string $writerClass
|
||||||
|
*/
|
||||||
|
public static function registerWriter($writerType, $writerClass)
|
||||||
|
{
|
||||||
|
if (!is_a($writerClass, Writer\IWriter::class, true)) {
|
||||||
|
throw new Writer\Exception('Registered writers must implement ' . Writer\IWriter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$writers[$writerType] = $writerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a reader with its type and class name.
|
||||||
|
*
|
||||||
|
* @param string $readerType
|
||||||
|
* @param string $readerClass
|
||||||
|
*/
|
||||||
|
public static function registerReader($readerType, $readerClass)
|
||||||
|
{
|
||||||
|
if (!is_a($readerClass, Reader\IReader::class, true)) {
|
||||||
|
throw new Reader\Exception('Registered readers must implement ' . Reader\IReader::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$readers[$readerType] = $readerClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
240
PhpOffice/PhpSpreadsheet/NamedRange.php
Normal file
240
PhpOffice/PhpSpreadsheet/NamedRange.php
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
class NamedRange
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Range name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worksheet on which the named range can be resolved.
|
||||||
|
*
|
||||||
|
* @var Worksheet
|
||||||
|
*/
|
||||||
|
private $worksheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range of the referenced cells.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the named range local? (i.e. can only be used on $this->worksheet).
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $localOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope.
|
||||||
|
*
|
||||||
|
* @var Worksheet
|
||||||
|
*/
|
||||||
|
private $scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new NamedRange.
|
||||||
|
*
|
||||||
|
* @param string $pName
|
||||||
|
* @param Worksheet $pWorksheet
|
||||||
|
* @param string $pRange
|
||||||
|
* @param bool $pLocalOnly
|
||||||
|
* @param null|Worksheet $pScope Scope. Only applies when $pLocalOnly = true. Null for global scope.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __construct($pName, Worksheet $pWorksheet, $pRange = 'A1', $pLocalOnly = false, $pScope = null)
|
||||||
|
{
|
||||||
|
// Validate data
|
||||||
|
if (($pName === null) || ($pWorksheet === null) || ($pRange === null)) {
|
||||||
|
throw new Exception('Parameters can not be null.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set local members
|
||||||
|
$this->name = $pName;
|
||||||
|
$this->worksheet = $pWorksheet;
|
||||||
|
$this->range = $pRange;
|
||||||
|
$this->localOnly = $pLocalOnly;
|
||||||
|
$this->scope = ($pLocalOnly == true) ? (($pScope == null) ? $pWorksheet : $pScope) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set name.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public function setName($value)
|
||||||
|
{
|
||||||
|
if ($value !== null) {
|
||||||
|
// Old title
|
||||||
|
$oldTitle = $this->name;
|
||||||
|
|
||||||
|
// Re-attach
|
||||||
|
if ($this->worksheet !== null) {
|
||||||
|
$this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet);
|
||||||
|
}
|
||||||
|
$this->name = $value;
|
||||||
|
|
||||||
|
if ($this->worksheet !== null) {
|
||||||
|
$this->worksheet->getParent()->addNamedRange($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New title
|
||||||
|
$newTitle = $this->name;
|
||||||
|
ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get worksheet.
|
||||||
|
*
|
||||||
|
* @return Worksheet
|
||||||
|
*/
|
||||||
|
public function getWorksheet()
|
||||||
|
{
|
||||||
|
return $this->worksheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set worksheet.
|
||||||
|
*
|
||||||
|
* @param Worksheet $value
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public function setWorksheet(Worksheet $value = null)
|
||||||
|
{
|
||||||
|
if ($value !== null) {
|
||||||
|
$this->worksheet = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get range.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getRange()
|
||||||
|
{
|
||||||
|
return $this->range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set range.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public function setRange($value)
|
||||||
|
{
|
||||||
|
if ($value !== null) {
|
||||||
|
$this->range = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get localOnly.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getLocalOnly()
|
||||||
|
{
|
||||||
|
return $this->localOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set localOnly.
|
||||||
|
*
|
||||||
|
* @param bool $value
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public function setLocalOnly($value)
|
||||||
|
{
|
||||||
|
$this->localOnly = $value;
|
||||||
|
$this->scope = $value ? $this->worksheet : null;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get scope.
|
||||||
|
*
|
||||||
|
* @return null|Worksheet
|
||||||
|
*/
|
||||||
|
public function getScope()
|
||||||
|
{
|
||||||
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set scope.
|
||||||
|
*
|
||||||
|
* @param null|Worksheet $value
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public function setScope(Worksheet $value = null)
|
||||||
|
{
|
||||||
|
$this->scope = $value;
|
||||||
|
$this->localOnly = $value != null;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a named range to a regular cell range.
|
||||||
|
*
|
||||||
|
* @param string $pNamedRange Named range
|
||||||
|
* @param null|Worksheet $pSheet Scope. Use null for global scope
|
||||||
|
*
|
||||||
|
* @return NamedRange
|
||||||
|
*/
|
||||||
|
public static function resolveRange($pNamedRange, Worksheet $pSheet)
|
||||||
|
{
|
||||||
|
return $pSheet->getParent()->getNamedRange($pNamedRange, $pSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
PhpOffice/PhpSpreadsheet/Reader/BaseReader.php
Normal file
160
PhpOffice/PhpSpreadsheet/Reader/BaseReader.php
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||||
|
|
||||||
|
abstract class BaseReader implements IReader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Read data only?
|
||||||
|
* Identifies whether the Reader should only read data values for cells, and ignore any formatting information;
|
||||||
|
* or whether it should read both data and formatting.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $readDataOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read empty cells?
|
||||||
|
* Identifies whether the Reader should read data values for cells all cells, or should ignore cells containing
|
||||||
|
* null value or empty string.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $readEmptyCells = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read charts that are defined in the workbook?
|
||||||
|
* Identifies whether the Reader should read the definitions for any charts that exist in the workbook;.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $includeCharts = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restrict which sheets should be loaded?
|
||||||
|
* This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded.
|
||||||
|
*
|
||||||
|
* @var array of string
|
||||||
|
*/
|
||||||
|
protected $loadSheetsOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IReadFilter instance.
|
||||||
|
*
|
||||||
|
* @var IReadFilter
|
||||||
|
*/
|
||||||
|
protected $readFilter;
|
||||||
|
|
||||||
|
protected $fileHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var XmlScanner
|
||||||
|
*/
|
||||||
|
protected $securityScanner;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->readFilter = new DefaultReadFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReadDataOnly()
|
||||||
|
{
|
||||||
|
return $this->readDataOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReadDataOnly($pValue)
|
||||||
|
{
|
||||||
|
$this->readDataOnly = (bool) $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReadEmptyCells()
|
||||||
|
{
|
||||||
|
return $this->readEmptyCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReadEmptyCells($pValue)
|
||||||
|
{
|
||||||
|
$this->readEmptyCells = (bool) $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIncludeCharts()
|
||||||
|
{
|
||||||
|
return $this->includeCharts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIncludeCharts($pValue)
|
||||||
|
{
|
||||||
|
$this->includeCharts = (bool) $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLoadSheetsOnly()
|
||||||
|
{
|
||||||
|
return $this->loadSheetsOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLoadSheetsOnly($value)
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return $this->setLoadAllSheets();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadSheetsOnly = is_array($value) ? $value : [$value];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLoadAllSheets()
|
||||||
|
{
|
||||||
|
$this->loadSheetsOnly = null;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReadFilter()
|
||||||
|
{
|
||||||
|
return $this->readFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReadFilter(IReadFilter $pValue)
|
||||||
|
{
|
||||||
|
$this->readFilter = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSecurityScanner()
|
||||||
|
{
|
||||||
|
if (property_exists($this, 'securityScanner')) {
|
||||||
|
return $this->securityScanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open file for reading.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function openFile($pFilename)
|
||||||
|
{
|
||||||
|
File::assertFile($pFilename);
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
$this->fileHandle = fopen($pFilename, 'r');
|
||||||
|
if ($this->fileHandle === false) {
|
||||||
|
throw new Exception('Could not open file ' . $pFilename . ' for reading.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
563
PhpOffice/PhpSpreadsheet/Reader/Csv.php
Normal file
563
PhpOffice/PhpSpreadsheet/Reader/Csv.php
Normal file
|
|
@ -0,0 +1,563 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
|
||||||
|
class Csv extends BaseReader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Input encoding.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $inputEncoding = 'UTF-8';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delimiter.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $delimiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enclosure.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $enclosure = '"';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sheet index to read.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $sheetIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load rows contiguously.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $contiguous = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row counter for loading rows contiguously.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $contiguousRow = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character that can escape the enclosure.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $escapeCharacter = '\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new CSV Reader instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set input encoding.
|
||||||
|
*
|
||||||
|
* @param string $pValue Input encoding, eg: 'UTF-8'
|
||||||
|
*
|
||||||
|
* @return Csv
|
||||||
|
*/
|
||||||
|
public function setInputEncoding($pValue)
|
||||||
|
{
|
||||||
|
$this->inputEncoding = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get input encoding.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputEncoding()
|
||||||
|
{
|
||||||
|
return $this->inputEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move filepointer past any BOM marker.
|
||||||
|
*/
|
||||||
|
protected function skipBOM()
|
||||||
|
{
|
||||||
|
rewind($this->fileHandle);
|
||||||
|
|
||||||
|
switch ($this->inputEncoding) {
|
||||||
|
case 'UTF-8':
|
||||||
|
fgets($this->fileHandle, 4) == "\xEF\xBB\xBF" ?
|
||||||
|
fseek($this->fileHandle, 3) : fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'UTF-16LE':
|
||||||
|
fgets($this->fileHandle, 3) == "\xFF\xFE" ?
|
||||||
|
fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'UTF-16BE':
|
||||||
|
fgets($this->fileHandle, 3) == "\xFE\xFF" ?
|
||||||
|
fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'UTF-32LE':
|
||||||
|
fgets($this->fileHandle, 5) == "\xFF\xFE\x00\x00" ?
|
||||||
|
fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'UTF-32BE':
|
||||||
|
fgets($this->fileHandle, 5) == "\x00\x00\xFE\xFF" ?
|
||||||
|
fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify any separator that is explicitly set in the file.
|
||||||
|
*/
|
||||||
|
protected function checkSeparator()
|
||||||
|
{
|
||||||
|
$line = fgets($this->fileHandle);
|
||||||
|
if ($line === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
|
||||||
|
$this->delimiter = substr($line, 4, 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->skipBOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer the separator if it isn't explicitly set in the file or specified by the user.
|
||||||
|
*/
|
||||||
|
protected function inferSeparator()
|
||||||
|
{
|
||||||
|
if ($this->delimiter !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~'];
|
||||||
|
$counts = [];
|
||||||
|
foreach ($potentialDelimiters as $delimiter) {
|
||||||
|
$counts[$delimiter] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count how many times each of the potential delimiters appears in each line
|
||||||
|
$numberLines = 0;
|
||||||
|
while (($line = $this->getNextLine()) !== false && (++$numberLines < 1000)) {
|
||||||
|
$countLine = [];
|
||||||
|
for ($i = strlen($line) - 1; $i >= 0; --$i) {
|
||||||
|
$char = $line[$i];
|
||||||
|
if (isset($counts[$char])) {
|
||||||
|
if (!isset($countLine[$char])) {
|
||||||
|
$countLine[$char] = 0;
|
||||||
|
}
|
||||||
|
++$countLine[$char];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($potentialDelimiters as $delimiter) {
|
||||||
|
$counts[$delimiter][] = $countLine[$delimiter]
|
||||||
|
?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If number of lines is 0, nothing to infer : fall back to the default
|
||||||
|
if ($numberLines === 0) {
|
||||||
|
$this->delimiter = reset($potentialDelimiters);
|
||||||
|
$this->skipBOM();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently)
|
||||||
|
$meanSquareDeviations = [];
|
||||||
|
$middleIdx = floor(($numberLines - 1) / 2);
|
||||||
|
|
||||||
|
foreach ($potentialDelimiters as $delimiter) {
|
||||||
|
$series = $counts[$delimiter];
|
||||||
|
sort($series);
|
||||||
|
|
||||||
|
$median = ($numberLines % 2)
|
||||||
|
? $series[$middleIdx]
|
||||||
|
: ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
|
||||||
|
|
||||||
|
if ($median === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$meanSquareDeviations[$delimiter] = array_reduce(
|
||||||
|
$series,
|
||||||
|
function ($sum, $value) use ($median) {
|
||||||
|
return $sum + pow($value - $median, 2);
|
||||||
|
}
|
||||||
|
) / count($series);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and pick the delimiter with the smallest mean square deviation (in case of ties, the order in potentialDelimiters is respected)
|
||||||
|
$min = INF;
|
||||||
|
foreach ($potentialDelimiters as $delimiter) {
|
||||||
|
if (!isset($meanSquareDeviations[$delimiter])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($meanSquareDeviations[$delimiter] < $min) {
|
||||||
|
$min = $meanSquareDeviations[$delimiter];
|
||||||
|
$this->delimiter = $delimiter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no delimiter could be detected, fall back to the default
|
||||||
|
if ($this->delimiter === null) {
|
||||||
|
$this->delimiter = reset($potentialDelimiters);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->skipBOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next full line from the file.
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
*
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
private function getNextLine($line = '')
|
||||||
|
{
|
||||||
|
// Get the next line in the file
|
||||||
|
$newLine = fgets($this->fileHandle);
|
||||||
|
|
||||||
|
// Return false if there is no next line
|
||||||
|
if ($newLine === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new line to the line passed in
|
||||||
|
$line = $line . $newLine;
|
||||||
|
|
||||||
|
// Drop everything that is enclosed to avoid counting false positives in enclosures
|
||||||
|
$enclosure = '(?<!' . preg_quote($this->escapeCharacter, '/') . ')'
|
||||||
|
. preg_quote($this->enclosure, '/');
|
||||||
|
$line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
|
||||||
|
|
||||||
|
// See if we have any enclosures left in the line
|
||||||
|
// if we still have an enclosure then we need to read the next line as well
|
||||||
|
if (preg_match('/(' . $enclosure . ')/', $line) > 0) {
|
||||||
|
$line = $this->getNextLine($line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function listWorksheetInfo($pFilename)
|
||||||
|
{
|
||||||
|
// Open file
|
||||||
|
if (!$this->canRead($pFilename)) {
|
||||||
|
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||||
|
}
|
||||||
|
$this->openFile($pFilename);
|
||||||
|
$fileHandle = $this->fileHandle;
|
||||||
|
|
||||||
|
// Skip BOM, if any
|
||||||
|
$this->skipBOM();
|
||||||
|
$this->checkSeparator();
|
||||||
|
$this->inferSeparator();
|
||||||
|
|
||||||
|
$worksheetInfo = [];
|
||||||
|
$worksheetInfo[0]['worksheetName'] = 'Worksheet';
|
||||||
|
$worksheetInfo[0]['lastColumnLetter'] = 'A';
|
||||||
|
$worksheetInfo[0]['lastColumnIndex'] = 0;
|
||||||
|
$worksheetInfo[0]['totalRows'] = 0;
|
||||||
|
$worksheetInfo[0]['totalColumns'] = 0;
|
||||||
|
|
||||||
|
// Loop through each line of the file in turn
|
||||||
|
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
|
||||||
|
++$worksheetInfo[0]['totalRows'];
|
||||||
|
$worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
|
||||||
|
$worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
|
||||||
|
|
||||||
|
// Close file
|
||||||
|
fclose($fileHandle);
|
||||||
|
|
||||||
|
return $worksheetInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Spreadsheet from file.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function load($pFilename)
|
||||||
|
{
|
||||||
|
// Create new Spreadsheet
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
|
||||||
|
// Load into this instance
|
||||||
|
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||||
|
{
|
||||||
|
$lineEnding = ini_get('auto_detect_line_endings');
|
||||||
|
ini_set('auto_detect_line_endings', true);
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
if (!$this->canRead($pFilename)) {
|
||||||
|
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||||
|
}
|
||||||
|
$this->openFile($pFilename);
|
||||||
|
$fileHandle = $this->fileHandle;
|
||||||
|
|
||||||
|
// Skip BOM, if any
|
||||||
|
$this->skipBOM();
|
||||||
|
$this->checkSeparator();
|
||||||
|
$this->inferSeparator();
|
||||||
|
|
||||||
|
// Create new PhpSpreadsheet object
|
||||||
|
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
}
|
||||||
|
$sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
|
||||||
|
|
||||||
|
// Set our starting row based on whether we're in contiguous mode or not
|
||||||
|
$currentRow = 1;
|
||||||
|
if ($this->contiguous) {
|
||||||
|
$currentRow = ($this->contiguousRow == -1) ? $sheet->getHighestRow() : $this->contiguousRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each line of the file in turn
|
||||||
|
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
|
||||||
|
$columnLetter = 'A';
|
||||||
|
foreach ($rowData as $rowDatum) {
|
||||||
|
if ($rowDatum != '' && $this->readFilter->readCell($columnLetter, $currentRow)) {
|
||||||
|
// Convert encoding if necessary
|
||||||
|
if ($this->inputEncoding !== 'UTF-8') {
|
||||||
|
$rowDatum = StringHelper::convertEncoding($rowDatum, 'UTF-8', $this->inputEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cell value
|
||||||
|
$sheet->getCell($columnLetter . $currentRow)->setValue($rowDatum);
|
||||||
|
}
|
||||||
|
++$columnLetter;
|
||||||
|
}
|
||||||
|
++$currentRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file
|
||||||
|
fclose($fileHandle);
|
||||||
|
|
||||||
|
if ($this->contiguous) {
|
||||||
|
$this->contiguousRow = $currentRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('auto_detect_line_endings', $lineEnding);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $spreadsheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get delimiter.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDelimiter()
|
||||||
|
{
|
||||||
|
return $this->delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set delimiter.
|
||||||
|
*
|
||||||
|
* @param string $delimiter Delimiter, eg: ','
|
||||||
|
*
|
||||||
|
* @return CSV
|
||||||
|
*/
|
||||||
|
public function setDelimiter($delimiter)
|
||||||
|
{
|
||||||
|
$this->delimiter = $delimiter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enclosure.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEnclosure()
|
||||||
|
{
|
||||||
|
return $this->enclosure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set enclosure.
|
||||||
|
*
|
||||||
|
* @param string $enclosure Enclosure, defaults to "
|
||||||
|
*
|
||||||
|
* @return CSV
|
||||||
|
*/
|
||||||
|
public function setEnclosure($enclosure)
|
||||||
|
{
|
||||||
|
if ($enclosure == '') {
|
||||||
|
$enclosure = '"';
|
||||||
|
}
|
||||||
|
$this->enclosure = $enclosure;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sheet index.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getSheetIndex()
|
||||||
|
{
|
||||||
|
return $this->sheetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sheet index.
|
||||||
|
*
|
||||||
|
* @param int $pValue Sheet index
|
||||||
|
*
|
||||||
|
* @return CSV
|
||||||
|
*/
|
||||||
|
public function setSheetIndex($pValue)
|
||||||
|
{
|
||||||
|
$this->sheetIndex = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Contiguous.
|
||||||
|
*
|
||||||
|
* @param bool $contiguous
|
||||||
|
*
|
||||||
|
* @return Csv
|
||||||
|
*/
|
||||||
|
public function setContiguous($contiguous)
|
||||||
|
{
|
||||||
|
$this->contiguous = (bool) $contiguous;
|
||||||
|
if (!$contiguous) {
|
||||||
|
$this->contiguousRow = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Contiguous.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getContiguous()
|
||||||
|
{
|
||||||
|
return $this->contiguous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set escape backslashes.
|
||||||
|
*
|
||||||
|
* @param string $escapeCharacter
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setEscapeCharacter($escapeCharacter)
|
||||||
|
{
|
||||||
|
$this->escapeCharacter = $escapeCharacter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get escape backslashes.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getEscapeCharacter()
|
||||||
|
{
|
||||||
|
return $this->escapeCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can the current IReader read the file?
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canRead($pFilename)
|
||||||
|
{
|
||||||
|
// Check if file exists
|
||||||
|
try {
|
||||||
|
$this->openFile($pFilename);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($this->fileHandle);
|
||||||
|
|
||||||
|
// Trust file extension if any
|
||||||
|
$extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION));
|
||||||
|
if (in_array($extension, ['csv', 'tsv'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to guess mimetype
|
||||||
|
$type = mime_content_type($pFilename);
|
||||||
|
$supportedTypes = [
|
||||||
|
'text/csv',
|
||||||
|
'text/plain',
|
||||||
|
'inode/x-empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
return in_array($type, $supportedTypes, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php
Normal file
20
PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
class DefaultReadFilter implements IReadFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Should this cell be read?
|
||||||
|
*
|
||||||
|
* @param string $column Column address (as a string value like "A", or "IV")
|
||||||
|
* @param int $row Row number
|
||||||
|
* @param string $worksheetName Optional worksheet name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function readCell($column, $row, $worksheetName = '')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
PhpOffice/PhpSpreadsheet/Reader/Exception.php
Normal file
9
PhpOffice/PhpSpreadsheet/Reader/Exception.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
|
|
||||||
|
class Exception extends PhpSpreadsheetException
|
||||||
|
{
|
||||||
|
}
|
||||||
889
PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php
Normal file
889
PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php
Normal file
|
|
@ -0,0 +1,889 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||||
|
use PhpOffice\PhpSpreadsheet\NamedRange;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||||
|
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
|
||||||
|
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Settings;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use XMLReader;
|
||||||
|
|
||||||
|
class Gnumeric extends BaseReader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Shared Expressions.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $expressions = [];
|
||||||
|
|
||||||
|
private $referenceHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Gnumeric.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->referenceHelper = ReferenceHelper::getInstance();
|
||||||
|
$this->securityScanner = XmlScanner::getInstance($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can the current IReader read the file?
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canRead($pFilename)
|
||||||
|
{
|
||||||
|
File::assertFile($pFilename);
|
||||||
|
|
||||||
|
// Check if gzlib functions are available
|
||||||
|
if (!function_exists('gzread')) {
|
||||||
|
throw new Exception('gzlib library is not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read signature data (first 3 bytes)
|
||||||
|
$fh = fopen($pFilename, 'r');
|
||||||
|
$data = fread($fh, 2);
|
||||||
|
fclose($fh);
|
||||||
|
|
||||||
|
return $data == chr(0x1F) . chr(0x8B);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function listWorksheetNames($pFilename)
|
||||||
|
{
|
||||||
|
File::assertFile($pFilename);
|
||||||
|
|
||||||
|
$xml = new XMLReader();
|
||||||
|
$xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
|
||||||
|
$xml->setParserProperty(2, true);
|
||||||
|
|
||||||
|
$worksheetNames = [];
|
||||||
|
while ($xml->read()) {
|
||||||
|
if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||||
|
$xml->read(); // Move onto the value node
|
||||||
|
$worksheetNames[] = (string) $xml->value;
|
||||||
|
} elseif ($xml->name == 'gnm:Sheets') {
|
||||||
|
// break out of the loop once we've got our sheet names rather than parse the entire file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $worksheetNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function listWorksheetInfo($pFilename)
|
||||||
|
{
|
||||||
|
File::assertFile($pFilename);
|
||||||
|
|
||||||
|
$xml = new XMLReader();
|
||||||
|
$xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
|
||||||
|
$xml->setParserProperty(2, true);
|
||||||
|
|
||||||
|
$worksheetInfo = [];
|
||||||
|
while ($xml->read()) {
|
||||||
|
if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||||
|
$tmpInfo = [
|
||||||
|
'worksheetName' => '',
|
||||||
|
'lastColumnLetter' => 'A',
|
||||||
|
'lastColumnIndex' => 0,
|
||||||
|
'totalRows' => 0,
|
||||||
|
'totalColumns' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
while ($xml->read()) {
|
||||||
|
if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||||
|
$xml->read(); // Move onto the value node
|
||||||
|
$tmpInfo['worksheetName'] = (string) $xml->value;
|
||||||
|
} elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||||
|
$xml->read(); // Move onto the value node
|
||||||
|
$tmpInfo['lastColumnIndex'] = (int) $xml->value;
|
||||||
|
$tmpInfo['totalColumns'] = (int) $xml->value + 1;
|
||||||
|
} elseif ($xml->name == 'gnm:MaxRow' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||||
|
$xml->read(); // Move onto the value node
|
||||||
|
$tmpInfo['totalRows'] = (int) $xml->value + 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
|
||||||
|
$worksheetInfo[] = $tmpInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $worksheetInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function gzfileGetContents($filename)
|
||||||
|
{
|
||||||
|
$file = @gzopen($filename, 'rb');
|
||||||
|
$data = '';
|
||||||
|
if ($file !== false) {
|
||||||
|
while (!gzeof($file)) {
|
||||||
|
$data .= gzread($file, 1024);
|
||||||
|
}
|
||||||
|
gzclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Spreadsheet from file.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function load($pFilename)
|
||||||
|
{
|
||||||
|
// Create new Spreadsheet
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
|
||||||
|
// Load into this instance
|
||||||
|
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads from file into Spreadsheet instance.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||||
|
{
|
||||||
|
File::assertFile($pFilename);
|
||||||
|
|
||||||
|
$gFileData = $this->gzfileGetContents($pFilename);
|
||||||
|
|
||||||
|
$xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
|
||||||
|
$namespacesMeta = $xml->getNamespaces(true);
|
||||||
|
|
||||||
|
$gnmXML = $xml->children($namespacesMeta['gnm']);
|
||||||
|
|
||||||
|
$docProps = $spreadsheet->getProperties();
|
||||||
|
// Document Properties are held differently, depending on the version of Gnumeric
|
||||||
|
if (isset($namespacesMeta['office'])) {
|
||||||
|
$officeXML = $xml->children($namespacesMeta['office']);
|
||||||
|
$officeDocXML = $officeXML->{'document-meta'};
|
||||||
|
$officeDocMetaXML = $officeDocXML->meta;
|
||||||
|
|
||||||
|
foreach ($officeDocMetaXML as $officePropertyData) {
|
||||||
|
$officePropertyDC = [];
|
||||||
|
if (isset($namespacesMeta['dc'])) {
|
||||||
|
$officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
|
||||||
|
}
|
||||||
|
foreach ($officePropertyDC as $propertyName => $propertyValue) {
|
||||||
|
$propertyValue = (string) $propertyValue;
|
||||||
|
switch ($propertyName) {
|
||||||
|
case 'title':
|
||||||
|
$docProps->setTitle(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'subject':
|
||||||
|
$docProps->setSubject(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'creator':
|
||||||
|
$docProps->setCreator(trim($propertyValue));
|
||||||
|
$docProps->setLastModifiedBy(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
$creationDate = strtotime(trim($propertyValue));
|
||||||
|
$docProps->setCreated($creationDate);
|
||||||
|
$docProps->setModified($creationDate);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'description':
|
||||||
|
$docProps->setDescription(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$officePropertyMeta = [];
|
||||||
|
if (isset($namespacesMeta['meta'])) {
|
||||||
|
$officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
|
||||||
|
}
|
||||||
|
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
|
||||||
|
$attributes = $propertyValue->attributes($namespacesMeta['meta']);
|
||||||
|
$propertyValue = (string) $propertyValue;
|
||||||
|
switch ($propertyName) {
|
||||||
|
case 'keyword':
|
||||||
|
$docProps->setKeywords(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'initial-creator':
|
||||||
|
$docProps->setCreator(trim($propertyValue));
|
||||||
|
$docProps->setLastModifiedBy(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'creation-date':
|
||||||
|
$creationDate = strtotime(trim($propertyValue));
|
||||||
|
$docProps->setCreated($creationDate);
|
||||||
|
$docProps->setModified($creationDate);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'user-defined':
|
||||||
|
[, $attrName] = explode(':', $attributes['name']);
|
||||||
|
switch ($attrName) {
|
||||||
|
case 'publisher':
|
||||||
|
$docProps->setCompany(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
$docProps->setCategory(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'manager':
|
||||||
|
$docProps->setManager(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (isset($gnmXML->Summary)) {
|
||||||
|
foreach ($gnmXML->Summary->Item as $summaryItem) {
|
||||||
|
$propertyName = $summaryItem->name;
|
||||||
|
$propertyValue = $summaryItem->{'val-string'};
|
||||||
|
switch ($propertyName) {
|
||||||
|
case 'title':
|
||||||
|
$docProps->setTitle(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'comments':
|
||||||
|
$docProps->setDescription(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'keywords':
|
||||||
|
$docProps->setKeywords(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
$docProps->setCategory(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'manager':
|
||||||
|
$docProps->setManager(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'author':
|
||||||
|
$docProps->setCreator(trim($propertyValue));
|
||||||
|
$docProps->setLastModifiedBy(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'company':
|
||||||
|
$docProps->setCompany(trim($propertyValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$worksheetID = 0;
|
||||||
|
foreach ($gnmXML->Sheets->Sheet as $sheet) {
|
||||||
|
$worksheetName = (string) $sheet->Name;
|
||||||
|
if ((isset($this->loadSheetsOnly)) && (!in_array($worksheetName, $this->loadSheetsOnly))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxRow = $maxCol = 0;
|
||||||
|
|
||||||
|
// Create new Worksheet
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
$spreadsheet->setActiveSheetIndex($worksheetID);
|
||||||
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
|
||||||
|
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
|
||||||
|
// name in line with the formula, not the reverse
|
||||||
|
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
|
||||||
|
|
||||||
|
if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) {
|
||||||
|
if (isset($sheet->PrintInformation->Margins)) {
|
||||||
|
foreach ($sheet->PrintInformation->Margins->children('gnm', true) as $key => $margin) {
|
||||||
|
$marginAttributes = $margin->attributes();
|
||||||
|
$marginSize = 72 / 100; // Default
|
||||||
|
switch ($marginAttributes['PrefUnit']) {
|
||||||
|
case 'mm':
|
||||||
|
$marginSize = (int) ($marginAttributes['Points']) / 100;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch ($key) {
|
||||||
|
case 'top':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'header':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'footer':
|
||||||
|
$spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($sheet->Cells->Cell as $cell) {
|
||||||
|
$cellAttributes = $cell->attributes();
|
||||||
|
$row = (int) $cellAttributes->Row + 1;
|
||||||
|
$column = (int) $cellAttributes->Col;
|
||||||
|
|
||||||
|
if ($row > $maxRow) {
|
||||||
|
$maxRow = $row;
|
||||||
|
}
|
||||||
|
if ($column > $maxCol) {
|
||||||
|
$maxCol = $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
$column = Coordinate::stringFromColumnIndex($column + 1);
|
||||||
|
|
||||||
|
// Read cell?
|
||||||
|
if ($this->getReadFilter() !== null) {
|
||||||
|
if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ValueType = $cellAttributes->ValueType;
|
||||||
|
$ExprID = (string) $cellAttributes->ExprID;
|
||||||
|
$type = DataType::TYPE_FORMULA;
|
||||||
|
if ($ExprID > '') {
|
||||||
|
if (((string) $cell) > '') {
|
||||||
|
$this->expressions[$ExprID] = [
|
||||||
|
'column' => $cellAttributes->Col,
|
||||||
|
'row' => $cellAttributes->Row,
|
||||||
|
'formula' => (string) $cell,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$expression = $this->expressions[$ExprID];
|
||||||
|
|
||||||
|
$cell = $this->referenceHelper->updateFormulaReferences(
|
||||||
|
$expression['formula'],
|
||||||
|
'A1',
|
||||||
|
$cellAttributes->Col - $expression['column'],
|
||||||
|
$cellAttributes->Row - $expression['row'],
|
||||||
|
$worksheetName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$type = DataType::TYPE_FORMULA;
|
||||||
|
} else {
|
||||||
|
switch ($ValueType) {
|
||||||
|
case '10': // NULL
|
||||||
|
$type = DataType::TYPE_NULL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '20': // Boolean
|
||||||
|
$type = DataType::TYPE_BOOL;
|
||||||
|
$cell = $cell == 'TRUE';
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '30': // Integer
|
||||||
|
$cell = (int) $cell;
|
||||||
|
// Excel 2007+ doesn't differentiate between integer and float, so set the value and dropthru to the next (numeric) case
|
||||||
|
// no break
|
||||||
|
case '40': // Float
|
||||||
|
$type = DataType::TYPE_NUMERIC;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '50': // Error
|
||||||
|
$type = DataType::TYPE_ERROR;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '60': // String
|
||||||
|
$type = DataType::TYPE_STRING;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '70': // Cell Range
|
||||||
|
case '80': // Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
|
||||||
|
foreach ($sheet->Objects->children('gnm', true) as $key => $comment) {
|
||||||
|
$commentAttributes = $comment->attributes();
|
||||||
|
// Only comment objects are handled at the moment
|
||||||
|
if ($commentAttributes->Text) {
|
||||||
|
$spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($sheet->Styles->StyleRegion as $styleRegion) {
|
||||||
|
$styleAttributes = $styleRegion->attributes();
|
||||||
|
if (($styleAttributes['startRow'] <= $maxRow) &&
|
||||||
|
($styleAttributes['startCol'] <= $maxCol)) {
|
||||||
|
$startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
|
||||||
|
$startRow = $styleAttributes['startRow'] + 1;
|
||||||
|
|
||||||
|
$endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
|
||||||
|
$endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
|
||||||
|
$endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow'];
|
||||||
|
$endRow += 1;
|
||||||
|
$cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
|
||||||
|
|
||||||
|
$styleAttributes = $styleRegion->Style->attributes();
|
||||||
|
|
||||||
|
// We still set the number format mask for date/time values, even if readDataOnly is true
|
||||||
|
if ((!$this->readDataOnly) ||
|
||||||
|
(Date::isDateTimeFormatCode((string) $styleAttributes['Format']))) {
|
||||||
|
$styleArray = [];
|
||||||
|
$styleArray['numberFormat']['formatCode'] = (string) $styleAttributes['Format'];
|
||||||
|
// If readDataOnly is false, we set all formatting information
|
||||||
|
if (!$this->readDataOnly) {
|
||||||
|
switch ($styleAttributes['HAlign']) {
|
||||||
|
case '1':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '8':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '16':
|
||||||
|
case '64':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '32':
|
||||||
|
$styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($styleAttributes['VAlign']) {
|
||||||
|
case '1':
|
||||||
|
$styleArray['alignment']['vertical'] = Alignment::VERTICAL_TOP;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$styleArray['alignment']['vertical'] = Alignment::VERTICAL_BOTTOM;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
$styleArray['alignment']['vertical'] = Alignment::VERTICAL_CENTER;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '8':
|
||||||
|
$styleArray['alignment']['vertical'] = Alignment::VERTICAL_JUSTIFY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
|
||||||
|
$styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
|
||||||
|
$styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
|
||||||
|
|
||||||
|
$RGB = self::parseGnumericColour($styleAttributes['Fore']);
|
||||||
|
$styleArray['font']['color']['rgb'] = $RGB;
|
||||||
|
$RGB = self::parseGnumericColour($styleAttributes['Back']);
|
||||||
|
$shade = $styleAttributes['Shade'];
|
||||||
|
if (($RGB != '000000') || ($shade != '0')) {
|
||||||
|
$styleArray['fill']['color']['rgb'] = $styleArray['fill']['startColor']['rgb'] = $RGB;
|
||||||
|
$RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
|
||||||
|
$styleArray['fill']['endColor']['rgb'] = $RGB2;
|
||||||
|
switch ($shade) {
|
||||||
|
case '1':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_SOLID;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_LINEAR;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_PATH;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKDOWN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRAY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '6':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRID;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKHORIZONTAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '8':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKTRELLIS;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '9':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKUP;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '10':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKVERTICAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '11':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY0625;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '12':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY125;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '13':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTDOWN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '14':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRAY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '15':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRID;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '16':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTHORIZONTAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '17':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTTRELLIS;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '18':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTUP;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '19':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTVERTICAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '20':
|
||||||
|
$styleArray['fill']['fillType'] = Fill::FILL_PATTERN_MEDIUMGRAY;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fontAttributes = $styleRegion->Style->Font->attributes();
|
||||||
|
$styleArray['font']['name'] = (string) $styleRegion->Style->Font;
|
||||||
|
$styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
|
||||||
|
$styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
|
||||||
|
$styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
|
||||||
|
$styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
|
||||||
|
switch ($fontAttributes['Underline']) {
|
||||||
|
case '1':
|
||||||
|
$styleArray['font']['underline'] = Font::UNDERLINE_SINGLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$styleArray['font']['underline'] = Font::UNDERLINE_DOUBLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
$styleArray['font']['underline'] = Font::UNDERLINE_SINGLEACCOUNTING;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
$styleArray['font']['underline'] = Font::UNDERLINE_DOUBLEACCOUNTING;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$styleArray['font']['underline'] = Font::UNDERLINE_NONE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch ($fontAttributes['Script']) {
|
||||||
|
case '1':
|
||||||
|
$styleArray['font']['superscript'] = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '-1':
|
||||||
|
$styleArray['font']['subscript'] = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($styleRegion->Style->StyleBorder)) {
|
||||||
|
if (isset($styleRegion->Style->StyleBorder->Top)) {
|
||||||
|
$styleArray['borders']['top'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Top->attributes());
|
||||||
|
}
|
||||||
|
if (isset($styleRegion->Style->StyleBorder->Bottom)) {
|
||||||
|
$styleArray['borders']['bottom'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Bottom->attributes());
|
||||||
|
}
|
||||||
|
if (isset($styleRegion->Style->StyleBorder->Left)) {
|
||||||
|
$styleArray['borders']['left'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Left->attributes());
|
||||||
|
}
|
||||||
|
if (isset($styleRegion->Style->StyleBorder->Right)) {
|
||||||
|
$styleArray['borders']['right'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Right->attributes());
|
||||||
|
}
|
||||||
|
if ((isset($styleRegion->Style->StyleBorder->Diagonal)) && (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}))) {
|
||||||
|
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
|
||||||
|
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
|
||||||
|
} elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) {
|
||||||
|
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
|
||||||
|
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
|
||||||
|
} elseif (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'})) {
|
||||||
|
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}->attributes());
|
||||||
|
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($styleRegion->Style->HyperLink)) {
|
||||||
|
// TO DO
|
||||||
|
$hyperlink = $styleRegion->Style->HyperLink->attributes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$this->readDataOnly) && (isset($sheet->Cols))) {
|
||||||
|
// Column Widths
|
||||||
|
$columnAttributes = $sheet->Cols->attributes();
|
||||||
|
$defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
|
||||||
|
$c = 0;
|
||||||
|
foreach ($sheet->Cols->ColInfo as $columnOverride) {
|
||||||
|
$columnAttributes = $columnOverride->attributes();
|
||||||
|
$column = $columnAttributes['No'];
|
||||||
|
$columnWidth = $columnAttributes['Unit'] / 5.4;
|
||||||
|
$hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1');
|
||||||
|
$columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1;
|
||||||
|
while ($c < $column) {
|
||||||
|
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
|
||||||
|
++$c;
|
||||||
|
}
|
||||||
|
while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
|
||||||
|
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
|
||||||
|
if ($hidden) {
|
||||||
|
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
|
||||||
|
}
|
||||||
|
++$c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ($c <= $maxCol) {
|
||||||
|
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
|
||||||
|
++$c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$this->readDataOnly) && (isset($sheet->Rows))) {
|
||||||
|
// Row Heights
|
||||||
|
$rowAttributes = $sheet->Rows->attributes();
|
||||||
|
$defaultHeight = $rowAttributes['DefaultSizePts'];
|
||||||
|
$r = 0;
|
||||||
|
|
||||||
|
foreach ($sheet->Rows->RowInfo as $rowOverride) {
|
||||||
|
$rowAttributes = $rowOverride->attributes();
|
||||||
|
$row = $rowAttributes['No'];
|
||||||
|
$rowHeight = $rowAttributes['Unit'];
|
||||||
|
$hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1');
|
||||||
|
$rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1;
|
||||||
|
while ($r < $row) {
|
||||||
|
++$r;
|
||||||
|
$spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
|
||||||
|
}
|
||||||
|
while (($r < ($row + $rowCount)) && ($r < $maxRow)) {
|
||||||
|
++$r;
|
||||||
|
$spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
|
||||||
|
if ($hidden) {
|
||||||
|
$spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ($r < $maxRow) {
|
||||||
|
++$r;
|
||||||
|
$spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Merged Cells in this worksheet
|
||||||
|
if (isset($sheet->MergedRegions)) {
|
||||||
|
foreach ($sheet->MergedRegions->Merge as $mergeCells) {
|
||||||
|
if (strpos($mergeCells, ':') !== false) {
|
||||||
|
$spreadsheet->getActiveSheet()->mergeCells($mergeCells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++$worksheetID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through definedNames (global named ranges)
|
||||||
|
if (isset($gnmXML->Names)) {
|
||||||
|
foreach ($gnmXML->Names->Name as $namedRange) {
|
||||||
|
$name = (string) $namedRange->name;
|
||||||
|
$range = (string) $namedRange->value;
|
||||||
|
if (stripos($range, '#REF!') !== false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = Worksheet::extractSheetTitle($range, true);
|
||||||
|
$range[0] = trim($range[0], "'");
|
||||||
|
if ($worksheet = $spreadsheet->getSheetByName($range[0])) {
|
||||||
|
$extractedRange = str_replace('$', '', $range[1]);
|
||||||
|
$spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $spreadsheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function parseBorderAttributes($borderAttributes)
|
||||||
|
{
|
||||||
|
$styleArray = [];
|
||||||
|
if (isset($borderAttributes['Color'])) {
|
||||||
|
$styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($borderAttributes['Style']) {
|
||||||
|
case '0':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_NONE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '1':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_THIN;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_MEDIUM;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_SLANTDASHDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_DASHED;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_THICK;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '6':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_DOUBLE;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_DOTTED;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '8':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHED;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '9':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_DASHDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '10':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '11':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_DASHDOTDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '12':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '13':
|
||||||
|
$styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $styleArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseRichText($is)
|
||||||
|
{
|
||||||
|
$value = new RichText();
|
||||||
|
$value->createText($is);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function parseGnumericColour($gnmColour)
|
||||||
|
{
|
||||||
|
[$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
|
||||||
|
$gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||||
|
$gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||||
|
$gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||||
|
|
||||||
|
return $gnmR . $gnmG . $gnmB;
|
||||||
|
}
|
||||||
|
}
|
||||||
970
PhpOffice/PhpSpreadsheet/Reader/Html.php
Normal file
970
PhpOffice/PhpSpreadsheet/Reader/Html.php
Normal file
|
|
@ -0,0 +1,970 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||||
|
|
||||||
|
use DOMDocument;
|
||||||
|
use DOMElement;
|
||||||
|
use DOMNode;
|
||||||
|
use DOMText;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Color;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
|
/** PhpSpreadsheet root directory */
|
||||||
|
class Html extends BaseReader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sample size to read to determine if it's HTML or not.
|
||||||
|
*/
|
||||||
|
const TEST_SAMPLE_SIZE = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input encoding.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $inputEncoding = 'ANSI';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sheet index to read.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $sheetIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $formats = [
|
||||||
|
'h1' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 24,
|
||||||
|
],
|
||||||
|
], // Bold, 24pt
|
||||||
|
'h2' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 18,
|
||||||
|
],
|
||||||
|
], // Bold, 18pt
|
||||||
|
'h3' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 13.5,
|
||||||
|
],
|
||||||
|
], // Bold, 13.5pt
|
||||||
|
'h4' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 12,
|
||||||
|
],
|
||||||
|
], // Bold, 12pt
|
||||||
|
'h5' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 10,
|
||||||
|
],
|
||||||
|
], // Bold, 10pt
|
||||||
|
'h6' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
'size' => 7.5,
|
||||||
|
],
|
||||||
|
], // Bold, 7.5pt
|
||||||
|
'a' => [
|
||||||
|
'font' => [
|
||||||
|
'underline' => true,
|
||||||
|
'color' => [
|
||||||
|
'argb' => Color::COLOR_BLUE,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], // Blue underlined
|
||||||
|
'hr' => [
|
||||||
|
'borders' => [
|
||||||
|
'bottom' => [
|
||||||
|
'borderStyle' => Border::BORDER_THIN,
|
||||||
|
'color' => [
|
||||||
|
Color::COLOR_BLACK,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], // Bottom border
|
||||||
|
'strong' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
],
|
||||||
|
], // Bold
|
||||||
|
'b' => [
|
||||||
|
'font' => [
|
||||||
|
'bold' => true,
|
||||||
|
],
|
||||||
|
], // Bold
|
||||||
|
'i' => [
|
||||||
|
'font' => [
|
||||||
|
'italic' => true,
|
||||||
|
],
|
||||||
|
], // Italic
|
||||||
|
'em' => [
|
||||||
|
'font' => [
|
||||||
|
'italic' => true,
|
||||||
|
],
|
||||||
|
], // Italic
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $rowspan = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new HTML Reader instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->securityScanner = XmlScanner::getInstance($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the current file is an HTML file.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canRead($pFilename)
|
||||||
|
{
|
||||||
|
// Check if file exists
|
||||||
|
try {
|
||||||
|
$this->openFile($pFilename);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$beginning = $this->readBeginning();
|
||||||
|
$startWithTag = self::startsWithTag($beginning);
|
||||||
|
$containsTags = self::containsTags($beginning);
|
||||||
|
$endsWithTag = self::endsWithTag($this->readEnding());
|
||||||
|
|
||||||
|
fclose($this->fileHandle);
|
||||||
|
|
||||||
|
return $startWithTag && $containsTags && $endsWithTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readBeginning()
|
||||||
|
{
|
||||||
|
fseek($this->fileHandle, 0);
|
||||||
|
|
||||||
|
return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readEnding()
|
||||||
|
{
|
||||||
|
$meta = stream_get_meta_data($this->fileHandle);
|
||||||
|
$filename = $meta['uri'];
|
||||||
|
|
||||||
|
$size = filesize($filename);
|
||||||
|
if ($size === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockSize = self::TEST_SAMPLE_SIZE;
|
||||||
|
if ($size < $blockSize) {
|
||||||
|
$blockSize = $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->fileHandle, $size - $blockSize);
|
||||||
|
|
||||||
|
return fread($this->fileHandle, $blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function startsWithTag($data)
|
||||||
|
{
|
||||||
|
return '<' === substr(trim($data), 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function endsWithTag($data)
|
||||||
|
{
|
||||||
|
return '>' === substr(trim($data), -1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function containsTags($data)
|
||||||
|
{
|
||||||
|
return strlen($data) !== strlen(strip_tags($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Spreadsheet from file.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function load($pFilename)
|
||||||
|
{
|
||||||
|
// Create new Spreadsheet
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
|
||||||
|
// Load into this instance
|
||||||
|
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set input encoding.
|
||||||
|
*
|
||||||
|
* @param string $pValue Input encoding, eg: 'ANSI'
|
||||||
|
*
|
||||||
|
* @return Html
|
||||||
|
*/
|
||||||
|
public function setInputEncoding($pValue)
|
||||||
|
{
|
||||||
|
$this->inputEncoding = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get input encoding.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getInputEncoding()
|
||||||
|
{
|
||||||
|
return $this->inputEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Array used for testing only, should write to Spreadsheet object on completion of tests
|
||||||
|
protected $dataArray = [];
|
||||||
|
|
||||||
|
protected $tableLevel = 0;
|
||||||
|
|
||||||
|
protected $nestedColumn = ['A'];
|
||||||
|
|
||||||
|
protected function setTableStartColumn($column)
|
||||||
|
{
|
||||||
|
if ($this->tableLevel == 0) {
|
||||||
|
$column = 'A';
|
||||||
|
}
|
||||||
|
++$this->tableLevel;
|
||||||
|
$this->nestedColumn[$this->tableLevel] = $column;
|
||||||
|
|
||||||
|
return $this->nestedColumn[$this->tableLevel];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTableStartColumn()
|
||||||
|
{
|
||||||
|
return $this->nestedColumn[$this->tableLevel];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function releaseTableStartColumn()
|
||||||
|
{
|
||||||
|
--$this->tableLevel;
|
||||||
|
|
||||||
|
return array_pop($this->nestedColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent)
|
||||||
|
{
|
||||||
|
if (is_string($cellContent)) {
|
||||||
|
// Simple String content
|
||||||
|
if (trim($cellContent) > '') {
|
||||||
|
// Only actually write it if there's content in the string
|
||||||
|
// Write to worksheet to be done here...
|
||||||
|
// ... we return the cell so we can mess about with styles more easily
|
||||||
|
$sheet->setCellValue($column . $row, $cellContent);
|
||||||
|
$this->dataArray[$row][$column] = $cellContent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We have a Rich Text run
|
||||||
|
// TODO
|
||||||
|
$this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent;
|
||||||
|
}
|
||||||
|
$cellContent = (string) '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DOMNode $element
|
||||||
|
* @param Worksheet $sheet
|
||||||
|
* @param int $row
|
||||||
|
* @param string $column
|
||||||
|
* @param string $cellContent
|
||||||
|
*/
|
||||||
|
protected function processDomElement(DOMNode $element, Worksheet $sheet, &$row, &$column, &$cellContent)
|
||||||
|
{
|
||||||
|
foreach ($element->childNodes as $child) {
|
||||||
|
if ($child instanceof DOMText) {
|
||||||
|
$domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
|
||||||
|
if (is_string($cellContent)) {
|
||||||
|
// simply append the text if the cell content is a plain text string
|
||||||
|
$cellContent .= $domText;
|
||||||
|
}
|
||||||
|
// but if we have a rich text run instead, we need to append it correctly
|
||||||
|
// TODO
|
||||||
|
} elseif ($child instanceof DOMElement) {
|
||||||
|
$attributeArray = [];
|
||||||
|
foreach ($child->attributes as $attribute) {
|
||||||
|
$attributeArray[$attribute->name] = $attribute->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($child->nodeName) {
|
||||||
|
case 'meta':
|
||||||
|
foreach ($attributeArray as $attributeName => $attributeValue) {
|
||||||
|
// Extract character set, so we can convert to UTF-8 if required
|
||||||
|
if ($attributeName === 'charset') {
|
||||||
|
$this->setInputEncoding($attributeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'title':
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
$sheet->setTitle($cellContent, true, false);
|
||||||
|
$cellContent = '';
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'span':
|
||||||
|
case 'div':
|
||||||
|
case 'font':
|
||||||
|
case 'i':
|
||||||
|
case 'em':
|
||||||
|
case 'strong':
|
||||||
|
case 'b':
|
||||||
|
if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
|
||||||
|
$sheet->getComment($column . $row)
|
||||||
|
->getText()
|
||||||
|
->createTextRun($child->textContent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cellContent > '') {
|
||||||
|
$cellContent .= ' ';
|
||||||
|
}
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
if ($cellContent > '') {
|
||||||
|
$cellContent .= ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->formats[$child->nodeName])) {
|
||||||
|
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'hr':
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
++$row;
|
||||||
|
if (isset($this->formats[$child->nodeName])) {
|
||||||
|
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||||
|
} else {
|
||||||
|
$cellContent = '----------';
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
}
|
||||||
|
++$row;
|
||||||
|
// Add a break after a horizontal rule, simply by allowing the code to dropthru
|
||||||
|
// no break
|
||||||
|
case 'br':
|
||||||
|
if ($this->tableLevel > 0) {
|
||||||
|
// If we're inside a table, replace with a \n and set the cell to wrap
|
||||||
|
$cellContent .= "\n";
|
||||||
|
$sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
|
||||||
|
} else {
|
||||||
|
// Otherwise flush our existing content and move the row cursor on
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
++$row;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
foreach ($attributeArray as $attributeName => $attributeValue) {
|
||||||
|
switch ($attributeName) {
|
||||||
|
case 'href':
|
||||||
|
$sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue);
|
||||||
|
if (isset($this->formats[$child->nodeName])) {
|
||||||
|
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'class':
|
||||||
|
if ($attributeValue === 'comment-indicator') {
|
||||||
|
break; // Ignore - it's just a red square.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cellContent .= ' ';
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
|
case 'h6':
|
||||||
|
case 'ol':
|
||||||
|
case 'ul':
|
||||||
|
case 'p':
|
||||||
|
if ($this->tableLevel > 0) {
|
||||||
|
// If we're inside a table, replace with a \n
|
||||||
|
$cellContent .= "\n";
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
} else {
|
||||||
|
if ($cellContent > '') {
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
++$row;
|
||||||
|
}
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
|
||||||
|
if (isset($this->formats[$child->nodeName])) {
|
||||||
|
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
++$row;
|
||||||
|
$column = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'li':
|
||||||
|
if ($this->tableLevel > 0) {
|
||||||
|
// If we're inside a table, replace with a \n
|
||||||
|
$cellContent .= "\n";
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
} else {
|
||||||
|
if ($cellContent > '') {
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
}
|
||||||
|
++$row;
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
$column = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'img':
|
||||||
|
$this->insertImage($sheet, $column, $row, $attributeArray);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'table':
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
$column = $this->setTableStartColumn($column);
|
||||||
|
if ($this->tableLevel > 1) {
|
||||||
|
--$row;
|
||||||
|
}
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
$column = $this->releaseTableStartColumn();
|
||||||
|
if ($this->tableLevel > 1) {
|
||||||
|
++$column;
|
||||||
|
} else {
|
||||||
|
++$row;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'thead':
|
||||||
|
case 'tbody':
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'tr':
|
||||||
|
$column = $this->getTableStartColumn();
|
||||||
|
$cellContent = '';
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
if (isset($attributeArray['height'])) {
|
||||||
|
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
|
||||||
|
}
|
||||||
|
|
||||||
|
++$row;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'th':
|
||||||
|
case 'td':
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
// apply inline style
|
||||||
|
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
|
||||||
|
|
||||||
|
while (isset($this->rowspan[$column . $row])) {
|
||||||
|
++$column;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||||
|
|
||||||
|
if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
|
||||||
|
//create merging rowspan and colspan
|
||||||
|
$columnTo = $column;
|
||||||
|
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
|
||||||
|
++$columnTo;
|
||||||
|
}
|
||||||
|
$range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
|
||||||
|
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
|
||||||
|
$this->rowspan[$value] = true;
|
||||||
|
}
|
||||||
|
$sheet->mergeCells($range);
|
||||||
|
$column = $columnTo;
|
||||||
|
} elseif (isset($attributeArray['rowspan'])) {
|
||||||
|
//create merging rowspan
|
||||||
|
$range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
|
||||||
|
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
|
||||||
|
$this->rowspan[$value] = true;
|
||||||
|
}
|
||||||
|
$sheet->mergeCells($range);
|
||||||
|
} elseif (isset($attributeArray['colspan'])) {
|
||||||
|
//create merging colspan
|
||||||
|
$columnTo = $column;
|
||||||
|
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
|
||||||
|
++$columnTo;
|
||||||
|
}
|
||||||
|
$sheet->mergeCells($column . $row . ':' . $columnTo . $row);
|
||||||
|
$column = $columnTo;
|
||||||
|
} elseif (isset($attributeArray['bgcolor'])) {
|
||||||
|
$sheet->getStyle($column . $row)->applyFromArray(
|
||||||
|
[
|
||||||
|
'fill' => [
|
||||||
|
'fillType' => Fill::FILL_SOLID,
|
||||||
|
'color' => ['rgb' => $attributeArray['bgcolor']],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attributeArray['width'])) {
|
||||||
|
$sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attributeArray['height'])) {
|
||||||
|
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attributeArray['align'])) {
|
||||||
|
$sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attributeArray['valign'])) {
|
||||||
|
$sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($attributeArray['data-format'])) {
|
||||||
|
$sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
|
||||||
|
}
|
||||||
|
|
||||||
|
++$column;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'body':
|
||||||
|
$row = 1;
|
||||||
|
$column = 'A';
|
||||||
|
$cellContent = '';
|
||||||
|
$this->tableLevel = 0;
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||||
|
*
|
||||||
|
* @param string $pFilename
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
if (!$this->canRead($pFilename)) {
|
||||||
|
throw new Exception($pFilename . ' is an Invalid HTML file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new DOM object
|
||||||
|
$dom = new DOMDocument();
|
||||||
|
// Reload the HTML file into the DOM object
|
||||||
|
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
|
||||||
|
if ($loaded === false) {
|
||||||
|
throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->loadDocument($dom, $spreadsheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spreadsheet from content.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
public function loadFromString($content): Spreadsheet
|
||||||
|
{
|
||||||
|
// Create a new DOM object
|
||||||
|
$dom = new DOMDocument();
|
||||||
|
// Reload the HTML file into the DOM object
|
||||||
|
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
|
||||||
|
if ($loaded === false) {
|
||||||
|
throw new Exception('Failed to load content as a DOM Document');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->loadDocument($dom, new Spreadsheet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
|
||||||
|
*
|
||||||
|
* @param DOMDocument $document
|
||||||
|
* @param Spreadsheet $spreadsheet
|
||||||
|
*
|
||||||
|
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||||
|
*
|
||||||
|
* @return Spreadsheet
|
||||||
|
*/
|
||||||
|
private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
|
||||||
|
{
|
||||||
|
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
}
|
||||||
|
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
|
||||||
|
|
||||||
|
// Discard white space
|
||||||
|
$document->preserveWhiteSpace = false;
|
||||||
|
|
||||||
|
$row = 0;
|
||||||
|
$column = 'A';
|
||||||
|
$content = '';
|
||||||
|
$this->rowspan = [];
|
||||||
|
$this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return $spreadsheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sheet index.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getSheetIndex()
|
||||||
|
{
|
||||||
|
return $this->sheetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sheet index.
|
||||||
|
*
|
||||||
|
* @param int $pValue Sheet index
|
||||||
|
*
|
||||||
|
* @return HTML
|
||||||
|
*/
|
||||||
|
public function setSheetIndex($pValue)
|
||||||
|
{
|
||||||
|
$this->sheetIndex = $pValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply inline css inline style.
|
||||||
|
*
|
||||||
|
* NOTES :
|
||||||
|
* Currently only intended for td & th element,
|
||||||
|
* and only takes 'background-color' and 'color'; property with HEX color
|
||||||
|
*
|
||||||
|
* TODO :
|
||||||
|
* - Implement to other propertie, such as border
|
||||||
|
*
|
||||||
|
* @param Worksheet $sheet
|
||||||
|
* @param int $row
|
||||||
|
* @param string $column
|
||||||
|
* @param array $attributeArray
|
||||||
|
*/
|
||||||
|
private function applyInlineStyle(&$sheet, $row, $column, $attributeArray)
|
||||||
|
{
|
||||||
|
if (!isset($attributeArray['style'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cellStyle = $sheet->getStyle($column . $row);
|
||||||
|
|
||||||
|
// add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
|
||||||
|
$styles = explode(';', $attributeArray['style']);
|
||||||
|
foreach ($styles as $st) {
|
||||||
|
$value = explode(':', $st);
|
||||||
|
$styleName = isset($value[0]) ? trim($value[0]) : null;
|
||||||
|
$styleValue = isset($value[1]) ? trim($value[1]) : null;
|
||||||
|
|
||||||
|
if (!$styleName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($styleName) {
|
||||||
|
case 'background':
|
||||||
|
case 'background-color':
|
||||||
|
$styleColor = $this->getStyleColor($styleValue);
|
||||||
|
|
||||||
|
if (!$styleColor) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'color':
|
||||||
|
$styleColor = $this->getStyleColor($styleValue);
|
||||||
|
|
||||||
|
if (!$styleColor) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'border':
|
||||||
|
$this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'border-top':
|
||||||
|
$this->setBorderStyle($cellStyle, $styleValue, 'top');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'border-bottom':
|
||||||
|
$this->setBorderStyle($cellStyle, $styleValue, 'bottom');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'border-left':
|
||||||
|
$this->setBorderStyle($cellStyle, $styleValue, 'left');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'border-right':
|
||||||
|
$this->setBorderStyle($cellStyle, $styleValue, 'right');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'font-size':
|
||||||
|
$cellStyle->getFont()->setSize(
|
||||||
|
(float) $styleValue
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'font-weight':
|
||||||
|
if ($styleValue === 'bold' || $styleValue >= 500) {
|
||||||
|
$cellStyle->getFont()->setBold(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'font-style':
|
||||||
|
if ($styleValue === 'italic') {
|
||||||
|
$cellStyle->getFont()->setItalic(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'font-family':
|
||||||
|
$cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'text-decoration':
|
||||||
|
switch ($styleValue) {
|
||||||
|
case 'underline':
|
||||||
|
$cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'line-through':
|
||||||
|
$cellStyle->getFont()->setStrikethrough(true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'text-align':
|
||||||
|
$cellStyle->getAlignment()->setHorizontal($styleValue);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'vertical-align':
|
||||||
|
$cellStyle->getAlignment()->setVertical($styleValue);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'width':
|
||||||
|
$sheet->getColumnDimension($column)->setWidth(
|
||||||
|
str_replace('px', '', $styleValue)
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'height':
|
||||||
|
$sheet->getRowDimension($row)->setRowHeight(
|
||||||
|
str_replace('px', '', $styleValue)
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'word-wrap':
|
||||||
|
$cellStyle->getAlignment()->setWrapText(
|
||||||
|
$styleValue === 'break-word'
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'text-indent':
|
||||||
|
$cellStyle->getAlignment()->setIndent(
|
||||||
|
(int) str_replace(['px'], '', $styleValue)
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if has #, so we can get clean hex.
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getStyleColor($value)
|
||||||
|
{
|
||||||
|
if (strpos($value, '#') === 0) {
|
||||||
|
return substr($value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Worksheet $sheet
|
||||||
|
* @param string $column
|
||||||
|
* @param int $row
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||||
|
*/
|
||||||
|
private function insertImage(Worksheet $sheet, $column, $row, array $attributes)
|
||||||
|
{
|
||||||
|
if (!isset($attributes['src'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$src = urldecode($attributes['src']);
|
||||||
|
$width = isset($attributes['width']) ? (float) $attributes['width'] : null;
|
||||||
|
$height = isset($attributes['height']) ? (float) $attributes['height'] : null;
|
||||||
|
$name = isset($attributes['alt']) ? (float) $attributes['alt'] : null;
|
||||||
|
|
||||||
|
$drawing = new Drawing();
|
||||||
|
$drawing->setPath($src);
|
||||||
|
$drawing->setWorksheet($sheet);
|
||||||
|
$drawing->setCoordinates($column . $row);
|
||||||
|
$drawing->setOffsetX(0);
|
||||||
|
$drawing->setOffsetY(10);
|
||||||
|
$drawing->setResizeProportional(true);
|
||||||
|
|
||||||
|
if ($name) {
|
||||||
|
$drawing->setName($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($width) {
|
||||||
|
$drawing->setWidth((int) $width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($height) {
|
||||||
|
$drawing->setHeight((int) $height);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->getColumnDimension($column)->setWidth(
|
||||||
|
$drawing->getWidth() / 6
|
||||||
|
);
|
||||||
|
|
||||||
|
$sheet->getRowDimension($row)->setRowHeight(
|
||||||
|
$drawing->getHeight() * 0.9
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map html border style to PhpSpreadsheet border style.
|
||||||
|
*
|
||||||
|
* @param string $style
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getBorderStyle($style)
|
||||||
|
{
|
||||||
|
switch ($style) {
|
||||||
|
case 'solid':
|
||||||
|
return Border::BORDER_THIN;
|
||||||
|
case 'dashed':
|
||||||
|
return Border::BORDER_DASHED;
|
||||||
|
case 'dotted':
|
||||||
|
return Border::BORDER_DOTTED;
|
||||||
|
case 'medium':
|
||||||
|
return Border::BORDER_MEDIUM;
|
||||||
|
case 'thick':
|
||||||
|
return Border::BORDER_THICK;
|
||||||
|
case 'none':
|
||||||
|
return Border::BORDER_NONE;
|
||||||
|
case 'dash-dot':
|
||||||
|
return Border::BORDER_DASHDOT;
|
||||||
|
case 'dash-dot-dot':
|
||||||
|
return Border::BORDER_DASHDOTDOT;
|
||||||
|
case 'double':
|
||||||
|
return Border::BORDER_DOUBLE;
|
||||||
|
case 'hair':
|
||||||
|
return Border::BORDER_HAIR;
|
||||||
|
case 'medium-dash-dot':
|
||||||
|
return Border::BORDER_MEDIUMDASHDOT;
|
||||||
|
case 'medium-dash-dot-dot':
|
||||||
|
return Border::BORDER_MEDIUMDASHDOTDOT;
|
||||||
|
case 'medium-dashed':
|
||||||
|
return Border::BORDER_MEDIUMDASHED;
|
||||||
|
case 'slant-dash-dot':
|
||||||
|
return Border::BORDER_SLANTDASHDOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Style $cellStyle
|
||||||
|
* @param string $styleValue
|
||||||
|
* @param string $type
|
||||||
|
*/
|
||||||
|
private function setBorderStyle(Style $cellStyle, $styleValue, $type)
|
||||||
|
{
|
||||||
|
[, $borderStyle, $color] = explode(' ', $styleValue);
|
||||||
|
|
||||||
|
$cellStyle->applyFromArray([
|
||||||
|
'borders' => [
|
||||||
|
$type => [
|
||||||
|
'borderStyle' => $this->getBorderStyle($borderStyle),
|
||||||
|
'color' => ['rgb' => $this->getStyleColor($color)],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user