Updates to 3rd party libraries
Add Dockerfile and specific docker-php.ini
This commit is contained in:
@ -224,6 +224,16 @@ class Calculation
|
||||
'functionCall' => 'acosh',
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ACOT' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'ACOT'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ACOTH' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'ACOTH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ADDRESS' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'cellAddress'],
|
||||
@ -359,6 +369,31 @@ class Calculation
|
||||
'functionCall' => [Statistical::class, 'BINOMDIST'],
|
||||
'argumentCount' => '4',
|
||||
],
|
||||
'BITAND' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'BITAND'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'BITOR' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'BITOR'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'BITXOR' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'BITOR'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'BITLSHIFT' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'BITLSHIFT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'BITRSHIFT' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'BITRSHIFT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'CEILING' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'CEILING'],
|
||||
@ -425,6 +460,11 @@ class Calculation
|
||||
'functionCall' => [Engineering::class, 'COMPLEX'],
|
||||
'argumentCount' => '2,3',
|
||||
],
|
||||
'CONCAT' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'CONCATENATE'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'CONCATENATE' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'CONCATENATE'],
|
||||
@ -455,6 +495,16 @@ class Calculation
|
||||
'functionCall' => 'cosh',
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'COT' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'COT'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'COTH' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'COTH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'COUNT' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'COUNT'],
|
||||
@ -520,6 +570,16 @@ class Calculation
|
||||
'functionCall' => [Statistical::class, 'CRITBINOM'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'CSC' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'CSC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'CSCH' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'CSCH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'CUBEKPIMEMBER' => [
|
||||
'category' => Category::CATEGORY_CUBE,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
@ -735,11 +795,21 @@ class Calculation
|
||||
'functionCall' => [Engineering::class, 'ERF'],
|
||||
'argumentCount' => '1,2',
|
||||
],
|
||||
'ERF.PRECISE' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'ERFPRECISE'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ERFC' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'ERFC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ERFC.PRECISE' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'ERFC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ERROR.TYPE' => [
|
||||
'category' => Category::CATEGORY_INFORMATION,
|
||||
'functionCall' => [Functions::class, 'errorType'],
|
||||
@ -752,7 +822,7 @@ class Calculation
|
||||
],
|
||||
'EXACT' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [TextData::class, 'EXACT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'EXP' => [
|
||||
@ -825,6 +895,13 @@ class Calculation
|
||||
'functionCall' => [Statistical::class, 'FORECAST'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'FORMULATEXT' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'FORMULATEXT'],
|
||||
'argumentCount' => '1',
|
||||
'passCellReference' => true,
|
||||
'passByReference' => [true],
|
||||
],
|
||||
'FREQUENCY' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
@ -961,6 +1038,26 @@ class Calculation
|
||||
'functionCall' => [Engineering::class, 'IMCOS'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMCOSH' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMCOSH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMCOT' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMCOT'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMCSC' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMCSC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMCSCH' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMCSCH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMDIV' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMDIV'],
|
||||
@ -1001,11 +1098,26 @@ class Calculation
|
||||
'functionCall' => [Engineering::class, 'IMREAL'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMSEC' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMSEC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMSECH' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMSECH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMSIN' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMSIN'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMSINH' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMSINH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'IMSQRT' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMSQRT'],
|
||||
@ -1021,6 +1133,11 @@ class Calculation
|
||||
'functionCall' => [Engineering::class, 'IMSUM'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'IMTAN' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'IMTAN'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'INDEX' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'INDEX'],
|
||||
@ -1114,6 +1231,11 @@ class Calculation
|
||||
'functionCall' => [Functions::class, 'isOdd'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ISOWEEKNUM' => [
|
||||
'category' => Category::CATEGORY_DATE_AND_TIME,
|
||||
'functionCall' => [DateTime::class, 'ISOWEEKNUM'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'ISPMT' => [
|
||||
'category' => Category::CATEGORY_FINANCIAL,
|
||||
'functionCall' => [Financial::class, 'ISPMT'],
|
||||
@ -1394,6 +1516,11 @@ class Calculation
|
||||
'functionCall' => [Financial::class, 'NPV'],
|
||||
'argumentCount' => '2+',
|
||||
],
|
||||
'NUMBERVALUE' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'NUMBERVALUE'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'OCT2BIN' => [
|
||||
'category' => Category::CATEGORY_ENGINEERING,
|
||||
'functionCall' => [Engineering::class, 'OCTTOBIN'],
|
||||
@ -1446,6 +1573,11 @@ class Calculation
|
||||
'functionCall' => [Logical::class, 'logicalOr'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'PDURATION' => [
|
||||
'category' => Category::CATEGORY_FINANCIAL,
|
||||
'functionCall' => [Financial::class, 'PDURATION'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'PEARSON' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'CORREL'],
|
||||
@ -1627,6 +1759,11 @@ class Calculation
|
||||
'functionCall' => [LookupRef::class, 'ROWS'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'RRI' => [
|
||||
'category' => Category::CATEGORY_FINANCIAL,
|
||||
'functionCall' => [Financial::class, 'RRI'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'RSQ' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'RSQ'],
|
||||
@ -1647,6 +1784,16 @@ class Calculation
|
||||
'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
|
||||
'argumentCount' => '2,3',
|
||||
],
|
||||
'SEC' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'SEC'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'SECH' => [
|
||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||
'functionCall' => [MathTrig::class, 'SECH'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'SECOND' => [
|
||||
'category' => Category::CATEGORY_DATE_AND_TIME,
|
||||
'functionCall' => [DateTime::class, 'SECOND'],
|
||||
@ -1838,6 +1985,11 @@ class Calculation
|
||||
'functionCall' => [TextData::class, 'TEXTFORMAT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'TEXTJOIN' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'TEXTJOIN'],
|
||||
'argumentCount' => '3+',
|
||||
],
|
||||
'TIME' => [
|
||||
'category' => Category::CATEGORY_DATE_AND_TIME,
|
||||
'functionCall' => [DateTime::class, 'TIME'],
|
||||
@ -1898,6 +2050,16 @@ class Calculation
|
||||
'functionCall' => [Functions::class, 'TYPE'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'UNICHAR' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'CHARACTER'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'UNICODE' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'ASCIICODE'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'UPPER' => [
|
||||
'category' => Category::CATEGORY_TEXT_AND_DATA,
|
||||
'functionCall' => [TextData::class, 'UPPERCASE'],
|
||||
@ -1918,6 +2080,16 @@ class Calculation
|
||||
'functionCall' => [Statistical::class, 'VARFunc'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'VAR.P' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'VARP'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'VAR.S' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'VARFunc'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'VARA' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'VARA'],
|
||||
@ -1973,6 +2145,11 @@ class Calculation
|
||||
'functionCall' => [Financial::class, 'XNPV'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'XOR' => [
|
||||
'category' => Category::CATEGORY_LOGICAL,
|
||||
'functionCall' => [Logical::class, 'logicalXor'],
|
||||
'argumentCount' => '1+',
|
||||
],
|
||||
'YEAR' => [
|
||||
'category' => Category::CATEGORY_DATE_AND_TIME,
|
||||
'functionCall' => [DateTime::class, 'YEAR'],
|
||||
@ -2395,10 +2572,10 @@ class Calculation
|
||||
|
||||
if (self::$functionReplaceToLocale === null) {
|
||||
self::$functionReplaceToLocale = [];
|
||||
foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
|
||||
foreach (self::$localeFunctions as $localeFunctionName) {
|
||||
self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
|
||||
}
|
||||
foreach (array_values(self::$localeBoolean) as $localeBoolean) {
|
||||
foreach (self::$localeBoolean as $localeBoolean) {
|
||||
self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
|
||||
}
|
||||
}
|
||||
@ -2414,10 +2591,10 @@ class Calculation
|
||||
{
|
||||
if (self::$functionReplaceFromLocale === null) {
|
||||
self::$functionReplaceFromLocale = [];
|
||||
foreach (array_values(self::$localeFunctions) as $localeFunctionName) {
|
||||
foreach (self::$localeFunctions as $localeFunctionName) {
|
||||
self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui';
|
||||
}
|
||||
foreach (array_values(self::$localeBoolean) as $excelBoolean) {
|
||||
foreach (self::$localeBoolean as $excelBoolean) {
|
||||
self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class DateTime
|
||||
/**
|
||||
* Identify if a year is a leap year or not.
|
||||
*
|
||||
* @param int $year The year to test
|
||||
* @param int|string $year The year to test
|
||||
*
|
||||
* @return bool TRUE if the year is a leap year, otherwise FALSE
|
||||
*/
|
||||
@ -70,7 +70,7 @@ class DateTime
|
||||
(Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
if ((is_object($dateValue)) && ($dateValue instanceof \DateTime)) {
|
||||
if ((is_object($dateValue)) && ($dateValue instanceof \DateTimeImmutable)) {
|
||||
$dateValue = Date::PHPToExcel($dateValue);
|
||||
} else {
|
||||
$saveReturnDateType = Functions::getReturnDateType();
|
||||
@ -650,7 +650,7 @@ class DateTime
|
||||
* or a standard date string
|
||||
* @param string $unit
|
||||
*
|
||||
* @return int Interval between the dates
|
||||
* @return int|string Interval between the dates
|
||||
*/
|
||||
public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D')
|
||||
{
|
||||
@ -792,7 +792,7 @@ class DateTime
|
||||
* occur on the 31st of a month become equal to the 30th of the
|
||||
* same month.
|
||||
*
|
||||
* @return int Number of days between start date and end date
|
||||
* @return int|string Number of days between start date and end date
|
||||
*/
|
||||
public static function DAYS360($startDate = 0, $endDate = 0, $method = false)
|
||||
{
|
||||
@ -942,7 +942,7 @@ class DateTime
|
||||
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int Interval between the dates
|
||||
* @return int|string Interval between the dates
|
||||
*/
|
||||
public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs)
|
||||
{
|
||||
@ -1127,7 +1127,7 @@ class DateTime
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int Day of the month
|
||||
* @return int|string Day of the month
|
||||
*/
|
||||
public static function DAYOFMONTH($dateValue = 1)
|
||||
{
|
||||
@ -1169,7 +1169,7 @@ class DateTime
|
||||
* 2 Numbers 1 (Monday) through 7 (Sunday).
|
||||
* 3 Numbers 0 (Monday) through 6 (Sunday).
|
||||
*
|
||||
* @return int Day of the week value
|
||||
* @return int|string Day of the week value
|
||||
*/
|
||||
public static function WEEKDAY($dateValue = 1, $style = 1)
|
||||
{
|
||||
@ -1248,7 +1248,7 @@ class DateTime
|
||||
* 1 or omitted Week begins on Sunday.
|
||||
* 2 Week begins on Monday.
|
||||
*
|
||||
* @return int Week Number
|
||||
* @return int|string Week Number
|
||||
*/
|
||||
public static function WEEKNUM($dateValue = 1, $method = 1)
|
||||
{
|
||||
@ -1286,6 +1286,37 @@ class DateTime
|
||||
return (int) $weekOfYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* ISOWEEKNUM.
|
||||
*
|
||||
* Returns the ISO 8601 week number of the year for a specified date.
|
||||
*
|
||||
* Excel Function:
|
||||
* ISOWEEKNUM(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Week Number
|
||||
*/
|
||||
public static function ISOWEEKNUM($dateValue = 1)
|
||||
{
|
||||
$dateValue = Functions::flattenSingleValue($dateValue);
|
||||
|
||||
if ($dateValue === null) {
|
||||
$dateValue = 1;
|
||||
} elseif (is_string($dateValue = self::getDateValue($dateValue))) {
|
||||
return Functions::VALUE();
|
||||
} elseif ($dateValue < 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
|
||||
return (int) $PHPDateObject->format('W');
|
||||
}
|
||||
|
||||
/**
|
||||
* MONTHOFYEAR.
|
||||
*
|
||||
@ -1298,7 +1329,7 @@ class DateTime
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int Month of the year
|
||||
* @return int|string Month of the year
|
||||
*/
|
||||
public static function MONTHOFYEAR($dateValue = 1)
|
||||
{
|
||||
@ -1331,7 +1362,7 @@ class DateTime
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int Year
|
||||
* @return int|string Year
|
||||
*/
|
||||
public static function YEAR($dateValue = 1)
|
||||
{
|
||||
@ -1363,7 +1394,7 @@ class DateTime
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int Hour
|
||||
* @return int|string Hour
|
||||
*/
|
||||
public static function HOUROFDAY($timeValue = 0)
|
||||
{
|
||||
@ -1404,7 +1435,7 @@ class DateTime
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int Minute
|
||||
* @return int|string Minute
|
||||
*/
|
||||
public static function MINUTE($timeValue = 0)
|
||||
{
|
||||
@ -1445,7 +1476,7 @@ class DateTime
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int Second
|
||||
* @return int|string Second
|
||||
*/
|
||||
public static function SECOND($timeValue = 0)
|
||||
{
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||
|
||||
use Complex\Complex;
|
||||
use Complex\Exception as ComplexException;
|
||||
|
||||
class Engineering
|
||||
{
|
||||
/**
|
||||
@ -718,83 +721,23 @@ class Engineering
|
||||
*
|
||||
* Parses a complex number into its real and imaginary parts, and an I or J suffix
|
||||
*
|
||||
* @deprecated 2.0.0 No longer used by internal code. Please use the Complex\Complex class instead
|
||||
*
|
||||
* @param string $complexNumber The complex number
|
||||
*
|
||||
* @return string[] Indexed on "real", "imaginary" and "suffix"
|
||||
* @return mixed[] Indexed on "real", "imaginary" and "suffix"
|
||||
*/
|
||||
public static function parseComplex($complexNumber)
|
||||
{
|
||||
$workString = (string) $complexNumber;
|
||||
|
||||
$realNumber = $imaginary = 0;
|
||||
// Extract the suffix, if there is one
|
||||
$suffix = substr($workString, -1);
|
||||
if (!is_numeric($suffix)) {
|
||||
$workString = substr($workString, 0, -1);
|
||||
} else {
|
||||
$suffix = '';
|
||||
}
|
||||
|
||||
// Split the input into its Real and Imaginary components
|
||||
$leadingSign = 0;
|
||||
if (strlen($workString) > 0) {
|
||||
$leadingSign = (($workString[0] == '+') || ($workString[0] == '-')) ? 1 : 0;
|
||||
}
|
||||
$power = '';
|
||||
$realNumber = strtok($workString, '+-');
|
||||
if (strtoupper(substr($realNumber, -1)) == 'E') {
|
||||
$power = strtok('+-');
|
||||
++$leadingSign;
|
||||
}
|
||||
|
||||
$realNumber = substr($workString, 0, strlen($realNumber) + strlen($power) + $leadingSign);
|
||||
|
||||
if ($suffix != '') {
|
||||
$imaginary = substr($workString, strlen($realNumber));
|
||||
|
||||
if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) {
|
||||
$imaginary = $realNumber . '1';
|
||||
$realNumber = '0';
|
||||
} elseif ($imaginary == '') {
|
||||
$imaginary = $realNumber;
|
||||
$realNumber = '0';
|
||||
} elseif (($imaginary == '+') || ($imaginary == '-')) {
|
||||
$imaginary .= '1';
|
||||
}
|
||||
}
|
||||
$complex = new Complex($complexNumber);
|
||||
|
||||
return [
|
||||
'real' => $realNumber,
|
||||
'imaginary' => $imaginary,
|
||||
'suffix' => $suffix,
|
||||
'real' => $complex->getReal(),
|
||||
'imaginary' => $complex->getImaginary(),
|
||||
'suffix' => $complex->getSuffix(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the leading characters in a complex number string.
|
||||
*
|
||||
* @param string $complexNumber The complex number to clean
|
||||
*
|
||||
* @return string The "cleaned" complex number
|
||||
*/
|
||||
private static function cleanComplex($complexNumber)
|
||||
{
|
||||
if ($complexNumber[0] == '+') {
|
||||
$complexNumber = substr($complexNumber, 1);
|
||||
}
|
||||
if ($complexNumber[0] == '0') {
|
||||
$complexNumber = substr($complexNumber, 1);
|
||||
}
|
||||
if ($complexNumber[0] == '.') {
|
||||
$complexNumber = '0' . $complexNumber;
|
||||
}
|
||||
if ($complexNumber[0] == '+') {
|
||||
$complexNumber = substr($complexNumber, 1);
|
||||
}
|
||||
|
||||
return $complexNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number base string value with leading zeroes.
|
||||
*
|
||||
@ -1745,10 +1688,10 @@ class Engineering
|
||||
/**
|
||||
* COMPLEX.
|
||||
*
|
||||
* Converts real and imaginary coefficients into a complex number of the form x + yi or x + yj.
|
||||
* Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
|
||||
*
|
||||
* Excel Function:
|
||||
* COMPLEX(realNumber,imaginary[,places])
|
||||
* COMPLEX(realNumber,imaginary[,suffix])
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
@ -1768,34 +1711,9 @@ class Engineering
|
||||
if (((is_numeric($realNumber)) && (is_numeric($imaginary))) &&
|
||||
(($suffix == 'i') || ($suffix == 'j') || ($suffix == ''))
|
||||
) {
|
||||
$realNumber = (float) $realNumber;
|
||||
$imaginary = (float) $imaginary;
|
||||
$complex = new Complex($realNumber, $imaginary, $suffix);
|
||||
|
||||
if ($suffix == '') {
|
||||
$suffix = 'i';
|
||||
}
|
||||
if ($realNumber == 0.0) {
|
||||
if ($imaginary == 0.0) {
|
||||
return (string) '0';
|
||||
} elseif ($imaginary == 1.0) {
|
||||
return (string) $suffix;
|
||||
} elseif ($imaginary == -1.0) {
|
||||
return (string) '-' . $suffix;
|
||||
}
|
||||
|
||||
return (string) $imaginary . $suffix;
|
||||
} elseif ($imaginary == 0.0) {
|
||||
return (string) $realNumber;
|
||||
} elseif ($imaginary == 1.0) {
|
||||
return (string) $realNumber . '+' . $suffix;
|
||||
} elseif ($imaginary == -1.0) {
|
||||
return (string) $realNumber . '-' . $suffix;
|
||||
}
|
||||
if ($imaginary > 0) {
|
||||
$imaginary = (string) '+' . $imaginary;
|
||||
}
|
||||
|
||||
return (string) $realNumber . $imaginary . $suffix;
|
||||
return (string) $complex;
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
@ -1820,9 +1738,7 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
return $parsedComplex['imaginary'];
|
||||
return (new Complex($complexNumber))->getImaginary();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1843,9 +1759,7 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
return $parsedComplex['real'];
|
||||
return (new Complex($complexNumber))->getReal();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1864,12 +1778,7 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
return sqrt(
|
||||
($parsedComplex['real'] * $parsedComplex['real']) +
|
||||
($parsedComplex['imaginary'] * $parsedComplex['imaginary'])
|
||||
);
|
||||
return (new Complex($complexNumber))->abs();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1883,27 +1792,18 @@ class Engineering
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the argument theta
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMARGUMENT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
if ($parsedComplex['real'] == 0.0) {
|
||||
if ($parsedComplex['imaginary'] == 0.0) {
|
||||
return Functions::DIV0();
|
||||
} elseif ($parsedComplex['imaginary'] < 0.0) {
|
||||
return M_PI / -2;
|
||||
}
|
||||
|
||||
return M_PI / 2;
|
||||
} elseif ($parsedComplex['real'] > 0.0) {
|
||||
return atan($parsedComplex['imaginary'] / $parsedComplex['real']);
|
||||
} elseif ($parsedComplex['imaginary'] < 0.0) {
|
||||
return 0 - (M_PI - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real'])));
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
return M_PI - atan($parsedComplex['imaginary'] / abs($parsedComplex['real']));
|
||||
return $complex->argument();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1922,19 +1822,7 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
if ($parsedComplex['imaginary'] == 0.0) {
|
||||
return $parsedComplex['real'];
|
||||
}
|
||||
|
||||
return self::cleanComplex(
|
||||
self::COMPLEX(
|
||||
$parsedComplex['real'],
|
||||
0 - $parsedComplex['imaginary'],
|
||||
$parsedComplex['suffix']
|
||||
)
|
||||
);
|
||||
return (string) (new Complex($complexNumber))->conjugate();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1953,19 +1841,83 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
return (string) (new Complex($complexNumber))->cos();
|
||||
}
|
||||
|
||||
if ($parsedComplex['imaginary'] == 0.0) {
|
||||
return cos($parsedComplex['real']);
|
||||
}
|
||||
/**
|
||||
* IMCOSH.
|
||||
*
|
||||
* Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCOSH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOSH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return self::IMCONJUGATE(
|
||||
self::COMPLEX(
|
||||
cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']),
|
||||
sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']),
|
||||
$parsedComplex['suffix']
|
||||
)
|
||||
);
|
||||
return (string) (new Complex($complexNumber))->cosh();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCOT.
|
||||
*
|
||||
* Returns the cotangent of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCOT(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cotangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->cot();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCSC.
|
||||
*
|
||||
* Returns the cosecant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCSC(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->csc();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCSCH.
|
||||
*
|
||||
* Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCSCH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSCH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->csch();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1984,17 +1936,83 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
return (string) (new Complex($complexNumber))->sin();
|
||||
}
|
||||
|
||||
if ($parsedComplex['imaginary'] == 0.0) {
|
||||
return sin($parsedComplex['real']);
|
||||
}
|
||||
/**
|
||||
* IMSINH.
|
||||
*
|
||||
* Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSINH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic sine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSINH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return self::COMPLEX(
|
||||
sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']),
|
||||
cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']),
|
||||
$parsedComplex['suffix']
|
||||
);
|
||||
return (string) (new Complex($complexNumber))->sinh();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSEC.
|
||||
*
|
||||
* Returns the secant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSEC(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSEC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sec();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSECH.
|
||||
*
|
||||
* Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSECH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSECH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sech();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMTAN.
|
||||
*
|
||||
* Returns the tangent of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMTAN(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the tangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMTAN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->tan();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2013,22 +2031,12 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
$theta = self::IMARGUMENT($complexNumber);
|
||||
if ($theta === Functions::DIV0()) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$d1 = cos($theta / 2);
|
||||
$d2 = sin($theta / 2);
|
||||
$r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
|
||||
|
||||
if ($parsedComplex['suffix'] == '') {
|
||||
return self::COMPLEX($d1 * $r, $d2 * $r);
|
||||
}
|
||||
|
||||
return self::COMPLEX($d1 * $r, $d2 * $r, $parsedComplex['suffix']);
|
||||
return (string) (new Complex($complexNumber))->sqrt();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2047,20 +2055,12 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
|
||||
$t = self::IMARGUMENT($complexNumber);
|
||||
|
||||
if ($parsedComplex['suffix'] == '') {
|
||||
return self::COMPLEX($logR, $t);
|
||||
}
|
||||
|
||||
return self::COMPLEX($logR, $t, $parsedComplex['suffix']);
|
||||
return (string) (new Complex($complexNumber))->ln();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2079,15 +2079,12 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
} elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
return log10($parsedComplex['real']);
|
||||
}
|
||||
|
||||
return self::IMPRODUCT(log10(self::EULER), self::IMLN($complexNumber));
|
||||
return (string) (new Complex($complexNumber))->log10();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2106,15 +2103,12 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
} elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
return log($parsedComplex['real'], 2);
|
||||
}
|
||||
|
||||
return self::IMPRODUCT(log(self::EULER, 2), self::IMLN($complexNumber));
|
||||
return (string) (new Complex($complexNumber))->log2();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2133,21 +2127,7 @@ class Engineering
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
|
||||
return '1';
|
||||
}
|
||||
|
||||
$e = exp($parsedComplex['real']);
|
||||
$eX = $e * cos($parsedComplex['imaginary']);
|
||||
$eY = $e * sin($parsedComplex['imaginary']);
|
||||
|
||||
if ($parsedComplex['suffix'] == '') {
|
||||
return self::COMPLEX($eX, $eY);
|
||||
}
|
||||
|
||||
return self::COMPLEX($eX, $eY, $parsedComplex['suffix']);
|
||||
return (string) (new Complex($complexNumber))->exp();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2172,18 +2152,7 @@ class Engineering
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$parsedComplex = self::parseComplex($complexNumber);
|
||||
|
||||
$r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
|
||||
$rPower = pow($r, $realNumber);
|
||||
$theta = self::IMARGUMENT($complexNumber) * $realNumber;
|
||||
if ($theta == 0) {
|
||||
return 1;
|
||||
} elseif ($parsedComplex['imaginary'] == 0.0) {
|
||||
return self::COMPLEX($rPower * cos($theta), $rPower * sin($theta), $parsedComplex['suffix']);
|
||||
}
|
||||
|
||||
return self::COMPLEX($rPower * cos($theta), $rPower * sin($theta), $parsedComplex['suffix']);
|
||||
return (string) (new Complex($complexNumber))->pow($realNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2204,32 +2173,11 @@ class Engineering
|
||||
$complexDividend = Functions::flattenSingleValue($complexDividend);
|
||||
$complexDivisor = Functions::flattenSingleValue($complexDivisor);
|
||||
|
||||
$parsedComplexDividend = self::parseComplex($complexDividend);
|
||||
$parsedComplexDivisor = self::parseComplex($complexDivisor);
|
||||
|
||||
if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] != '') &&
|
||||
($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix'])
|
||||
) {
|
||||
try {
|
||||
return (string) (new Complex($complexDividend))->divideby(new Complex($complexDivisor));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] == '')) {
|
||||
$parsedComplexDivisor['suffix'] = $parsedComplexDividend['suffix'];
|
||||
}
|
||||
|
||||
$d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']);
|
||||
$d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']);
|
||||
$d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']);
|
||||
|
||||
$r = $d1 / $d3;
|
||||
$i = $d2 / $d3;
|
||||
|
||||
if ($i > 0.0) {
|
||||
return self::cleanComplex($r . '+' . $i . $parsedComplexDivisor['suffix']);
|
||||
} elseif ($i < 0.0) {
|
||||
return self::cleanComplex($r . $i . $parsedComplexDivisor['suffix']);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2250,21 +2198,11 @@ class Engineering
|
||||
$complexNumber1 = Functions::flattenSingleValue($complexNumber1);
|
||||
$complexNumber2 = Functions::flattenSingleValue($complexNumber2);
|
||||
|
||||
$parsedComplex1 = self::parseComplex($complexNumber1);
|
||||
$parsedComplex2 = self::parseComplex($complexNumber2);
|
||||
|
||||
if ((($parsedComplex1['suffix'] != '') && ($parsedComplex2['suffix'] != '')) &&
|
||||
($parsedComplex1['suffix'] != $parsedComplex2['suffix'])
|
||||
) {
|
||||
try {
|
||||
return (string) (new Complex($complexNumber1))->subtract(new Complex($complexNumber2));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
} elseif (($parsedComplex1['suffix'] == '') && ($parsedComplex2['suffix'] != '')) {
|
||||
$parsedComplex1['suffix'] = $parsedComplex2['suffix'];
|
||||
}
|
||||
|
||||
$d1 = $parsedComplex1['real'] - $parsedComplex2['real'];
|
||||
$d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary'];
|
||||
|
||||
return self::COMPLEX($d1, $d2, $parsedComplex1['suffix']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2282,29 +2220,19 @@ class Engineering
|
||||
public static function IMSUM(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = self::parseComplex('0');
|
||||
$activeSuffix = '';
|
||||
|
||||
// Loop through the arguments
|
||||
$returnValue = new Complex(0.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
foreach ($aArgs as $arg) {
|
||||
$parsedComplex = self::parseComplex($arg);
|
||||
|
||||
if ($activeSuffix == '') {
|
||||
$activeSuffix = $parsedComplex['suffix'];
|
||||
} elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
|
||||
return Functions::NAN();
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->add(new Complex($complex));
|
||||
}
|
||||
|
||||
$returnValue['real'] += $parsedComplex['real'];
|
||||
$returnValue['imaginary'] += $parsedComplex['imaginary'];
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($returnValue['imaginary'] == 0.0) {
|
||||
$activeSuffix = '';
|
||||
}
|
||||
|
||||
return self::COMPLEX($returnValue['real'], $returnValue['imaginary'], $activeSuffix);
|
||||
return (string) $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2322,29 +2250,19 @@ class Engineering
|
||||
public static function IMPRODUCT(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = self::parseComplex('1');
|
||||
$activeSuffix = '';
|
||||
|
||||
// Loop through the arguments
|
||||
$returnValue = new Complex(1.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
foreach ($aArgs as $arg) {
|
||||
$parsedComplex = self::parseComplex($arg);
|
||||
|
||||
$workValue = $returnValue;
|
||||
if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) {
|
||||
$activeSuffix = $parsedComplex['suffix'];
|
||||
} elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
|
||||
return Functions::NAN();
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->multiply(new Complex($complex));
|
||||
}
|
||||
$returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']);
|
||||
$returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($returnValue['imaginary'] == 0.0) {
|
||||
$activeSuffix = '';
|
||||
}
|
||||
|
||||
return self::COMPLEX($returnValue['real'], $returnValue['imaginary'], $activeSuffix);
|
||||
return (string) $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2423,6 +2341,179 @@ class Engineering
|
||||
return self::$twoSqrtPi * $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate arguments passed to the bitwise functions.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function validateBitwiseArgument($value)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
|
||||
if (is_int($value)) {
|
||||
return $value;
|
||||
} elseif (is_numeric($value)) {
|
||||
if ($value == (int) ($value)) {
|
||||
$value = (int) ($value);
|
||||
if (($value > pow(2, 48) - 1) || ($value < 0)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
* BITAND.
|
||||
*
|
||||
* Returns the bitwise AND of two integer values.
|
||||
*
|
||||
* Excel Function:
|
||||
* BITAND(number1, number2)
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
* @param int $number1
|
||||
* @param int $number2
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public static function BITAND($number1, $number2)
|
||||
{
|
||||
try {
|
||||
$number1 = self::validateBitwiseArgument($number1);
|
||||
$number2 = self::validateBitwiseArgument($number2);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $number1 & $number2;
|
||||
}
|
||||
|
||||
/**
|
||||
* BITOR.
|
||||
*
|
||||
* Returns the bitwise OR of two integer values.
|
||||
*
|
||||
* Excel Function:
|
||||
* BITOR(number1, number2)
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
* @param int $number1
|
||||
* @param int $number2
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public static function BITOR($number1, $number2)
|
||||
{
|
||||
try {
|
||||
$number1 = self::validateBitwiseArgument($number1);
|
||||
$number2 = self::validateBitwiseArgument($number2);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $number1 | $number2;
|
||||
}
|
||||
|
||||
/**
|
||||
* BITXOR.
|
||||
*
|
||||
* Returns the bitwise XOR of two integer values.
|
||||
*
|
||||
* Excel Function:
|
||||
* BITXOR(number1, number2)
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
* @param int $number1
|
||||
* @param int $number2
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public static function BITXOR($number1, $number2)
|
||||
{
|
||||
try {
|
||||
$number1 = self::validateBitwiseArgument($number1);
|
||||
$number2 = self::validateBitwiseArgument($number2);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $number1 ^ $number2;
|
||||
}
|
||||
|
||||
/**
|
||||
* BITLSHIFT.
|
||||
*
|
||||
* Returns the number value shifted left by shift_amount bits.
|
||||
*
|
||||
* Excel Function:
|
||||
* BITLSHIFT(number, shift_amount)
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
* @param int $number
|
||||
* @param int $shiftAmount
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public static function BITLSHIFT($number, $shiftAmount)
|
||||
{
|
||||
try {
|
||||
$number = self::validateBitwiseArgument($number);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$shiftAmount = Functions::flattenSingleValue($shiftAmount);
|
||||
|
||||
$result = $number << $shiftAmount;
|
||||
if ($result > pow(2, 48) - 1) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* BITRSHIFT.
|
||||
*
|
||||
* Returns the number value shifted right by shift_amount bits.
|
||||
*
|
||||
* Excel Function:
|
||||
* BITRSHIFT(number, shift_amount)
|
||||
*
|
||||
* @category Engineering Functions
|
||||
*
|
||||
* @param int $number
|
||||
* @param int $shiftAmount
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public static function BITRSHIFT($number, $shiftAmount)
|
||||
{
|
||||
try {
|
||||
$number = self::validateBitwiseArgument($number);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$shiftAmount = Functions::flattenSingleValue($shiftAmount);
|
||||
|
||||
return $number >> $shiftAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* ERF.
|
||||
*
|
||||
@ -2431,7 +2522,7 @@ class Engineering
|
||||
* Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
|
||||
* the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
|
||||
* improved, so that it can now calculate the function for both positive and negative ranges.
|
||||
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
|
||||
* PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
|
||||
*
|
||||
* Excel Function:
|
||||
* ERF(lower[,upper])
|
||||
@ -2440,7 +2531,7 @@ class Engineering
|
||||
* @param float $upper upper bound for integrating ERF.
|
||||
* If omitted, ERF integrates between zero and lower_limit
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function ERF($lower, $upper = null)
|
||||
{
|
||||
@ -2459,6 +2550,25 @@ class Engineering
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* ERFPRECISE.
|
||||
*
|
||||
* Returns the error function integrated between the lower and upper bound arguments.
|
||||
*
|
||||
* Excel Function:
|
||||
* ERF.PRECISE(limit)
|
||||
*
|
||||
* @param float $limit bound for integrating ERF
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function ERFPRECISE($limit)
|
||||
{
|
||||
$limit = Functions::flattenSingleValue($limit);
|
||||
|
||||
return self::ERF($limit);
|
||||
}
|
||||
|
||||
//
|
||||
// Private method to calculate the erfc value
|
||||
//
|
||||
@ -2507,7 +2617,7 @@ class Engineering
|
||||
*
|
||||
* @param float $x The lower bound for integrating ERFC
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function ERFC($x)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Financial
|
||||
{
|
||||
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||
const FINANCIAL_MAX_ITERATIONS = 32;
|
||||
|
||||
const FINANCIAL_PRECISION = 1.0e-08;
|
||||
|
||||
@ -63,8 +63,8 @@ class Financial
|
||||
*
|
||||
* Returns the number of days in a specified year, as defined by the "basis" value
|
||||
*
|
||||
* @param int $year The year against which we're testing
|
||||
* @param int $basis The type of day count:
|
||||
* @param int|string $year The year against which we're testing
|
||||
* @param int|string $basis The type of day count:
|
||||
* 0 or omitted US (NASD) 360
|
||||
* 1 Actual (365 or 366 in a leap year)
|
||||
* 2 360
|
||||
@ -144,7 +144,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0)
|
||||
{
|
||||
@ -197,7 +197,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
|
||||
{
|
||||
@ -401,7 +401,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0)
|
||||
{
|
||||
@ -460,7 +460,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
|
||||
{
|
||||
@ -489,7 +489,7 @@ class Financial
|
||||
case 1:
|
||||
// Actual/actual
|
||||
if ($frequency == 1) {
|
||||
$daysPerYear = self::daysPerYear(DateTime::YEAR($maturity), $basis);
|
||||
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
|
||||
|
||||
return $daysPerYear / $frequency;
|
||||
}
|
||||
@ -534,7 +534,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0)
|
||||
{
|
||||
@ -651,7 +651,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return int
|
||||
* @return int|string
|
||||
*/
|
||||
public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0)
|
||||
{
|
||||
@ -769,7 +769,7 @@ class Financial
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0)
|
||||
{
|
||||
@ -817,7 +817,7 @@ class Financial
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0)
|
||||
{
|
||||
@ -870,7 +870,7 @@ class Financial
|
||||
* @param int $month Number of months in the first year. If month is omitted,
|
||||
* it defaults to 12.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DB($cost, $salvage, $life, $period, $month = 12)
|
||||
{
|
||||
@ -940,7 +940,7 @@ class Financial
|
||||
* If factor is omitted, it is assumed to be 2 (the
|
||||
* double-declining balance method).
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
|
||||
{
|
||||
@ -1004,7 +1004,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0)
|
||||
{
|
||||
@ -1049,7 +1049,7 @@ class Financial
|
||||
* @param float $fractional_dollar Fractional Dollar
|
||||
* @param int $fraction Fraction
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
|
||||
{
|
||||
@ -1087,7 +1087,7 @@ class Financial
|
||||
* @param float $decimal_dollar Decimal Dollar
|
||||
* @param int $fraction Fraction
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
|
||||
{
|
||||
@ -1124,7 +1124,7 @@ class Financial
|
||||
* @param float $nominal_rate Nominal interest rate
|
||||
* @param int $npery Number of compounding payments per year
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function EFFECT($nominal_rate = 0, $npery = 0)
|
||||
{
|
||||
@ -1160,7 +1160,7 @@ class Financial
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0)
|
||||
{
|
||||
@ -1230,7 +1230,7 @@ class Financial
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0)
|
||||
{
|
||||
@ -1275,7 +1275,7 @@ class Financial
|
||||
* @param float $fv Future Value
|
||||
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
|
||||
{
|
||||
@ -1318,7 +1318,7 @@ class Financial
|
||||
* calculate the internal rate of return.
|
||||
* @param float $guess A number that you guess is close to the result of IRR
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IRR($values, $guess = 0.1)
|
||||
{
|
||||
@ -1428,7 +1428,7 @@ class Financial
|
||||
* @param float $finance_rate The interest rate you pay on the money used in the cash flows
|
||||
* @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function MIRR($values, $finance_rate, $reinvestment_rate)
|
||||
{
|
||||
@ -1470,7 +1470,7 @@ class Financial
|
||||
* @param float $effect_rate Effective interest rate
|
||||
* @param int $npery Number of compounding payments per year
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function NOMINAL($effect_rate = 0, $npery = 0)
|
||||
{
|
||||
@ -1497,7 +1497,7 @@ class Financial
|
||||
* @param float $fv Future Value
|
||||
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0)
|
||||
{
|
||||
@ -1556,6 +1556,33 @@ class Financial
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* PDURATION.
|
||||
*
|
||||
* Calculates the number of periods required for an investment to reach a specified value.
|
||||
*
|
||||
* @param float $rate Interest rate per period
|
||||
* @param float $pv Present Value
|
||||
* @param float $fv Future Value
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$pv = Functions::flattenSingleValue($pv);
|
||||
$fv = Functions::flattenSingleValue($fv);
|
||||
|
||||
// Validate parameters
|
||||
if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) {
|
||||
return Functions::VALUE();
|
||||
} elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (log($fv) - log($pv)) / log(1 + $rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* PMT.
|
||||
*
|
||||
@ -1933,6 +1960,33 @@ class Financial
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* RRI.
|
||||
*
|
||||
* Calculates the interest rate required for an investment to grow to a specified future value .
|
||||
*
|
||||
* @param float $nper The number of periods over which the investment is made
|
||||
* @param float $pv Present Value
|
||||
* @param float $fv Future Value
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function RRI($nper = 0, $pv = 0, $fv = 0)
|
||||
{
|
||||
$nper = Functions::flattenSingleValue($nper);
|
||||
$pv = Functions::flattenSingleValue($pv);
|
||||
$fv = Functions::flattenSingleValue($fv);
|
||||
|
||||
// Validate parameters
|
||||
if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) {
|
||||
return Functions::VALUE();
|
||||
} elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return pow($fv / $pv, 1 / $nper) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* SLN.
|
||||
*
|
||||
@ -1942,7 +1996,7 @@ class Financial
|
||||
* @param mixed $salvage Value at the end of the depreciation
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function SLN($cost, $salvage, $life)
|
||||
{
|
||||
@ -1972,7 +2026,7 @@ class Financial
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
* @param mixed $period Period
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function SYD($cost, $salvage, $life, $period)
|
||||
{
|
||||
|
@ -277,7 +277,7 @@ class Functions
|
||||
|
||||
return '=' . $condition;
|
||||
}
|
||||
preg_match('/([<>=]+)(.*)/', $condition, $matches);
|
||||
preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
|
||||
list(, $operator, $operand) = $matches;
|
||||
|
||||
if (!is_numeric($operand)) {
|
||||
@ -355,7 +355,7 @@ class Functions
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($value, array_values(self::$errorCodes));
|
||||
return in_array($value, self::$errorCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -648,17 +648,26 @@ class Functions
|
||||
/**
|
||||
* ISFORMULA.
|
||||
*
|
||||
* @param mixed $value The cell to check
|
||||
* @param mixed $cellReference The cell to check
|
||||
* @param Cell $pCell The current cell (containing this formula)
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function isFormula($value = '', Cell $pCell = null)
|
||||
public static function isFormula($cellReference = '', Cell $pCell = null)
|
||||
{
|
||||
if ($pCell === null) {
|
||||
return self::REF();
|
||||
}
|
||||
|
||||
return substr($pCell->getWorksheet()->getCell($value)->getValue(), 0, 1) === '=';
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,32 @@ class Logical
|
||||
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.
|
||||
*
|
||||
@ -62,37 +88,23 @@ class Logical
|
||||
*/
|
||||
public static function logicalAnd(...$args)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = true;
|
||||
$args = Functions::flattenArray($args);
|
||||
|
||||
// Loop through the arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$argCount = -1;
|
||||
foreach ($aArgs as $argCount => $arg) {
|
||||
// Is it a boolean value?
|
||||
if (is_bool($arg)) {
|
||||
$returnValue = $returnValue && $arg;
|
||||
} elseif ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
$returnValue = $returnValue && ($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 = $returnValue && ($arg != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
if ($argCount < 0) {
|
||||
if (count($args) == 0) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,37 +131,65 @@ class Logical
|
||||
*/
|
||||
public static function logicalOr(...$args)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = false;
|
||||
$args = Functions::flattenArray($args);
|
||||
|
||||
// Loop through the arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$argCount = -1;
|
||||
foreach ($aArgs as $argCount => $arg) {
|
||||
// Is it a boolean value?
|
||||
if (is_bool($arg)) {
|
||||
$returnValue = $returnValue || $arg;
|
||||
} elseif ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
$returnValue = $returnValue || ($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 = $returnValue || ($arg != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
if ($argCount < 0) {
|
||||
if (count($args) == 0) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,6 +216,7 @@ class Logical
|
||||
public static function NOT($logical = false)
|
||||
{
|
||||
$logical = Functions::flattenSingleValue($logical);
|
||||
|
||||
if (is_string($logical)) {
|
||||
$logical = strtoupper($logical);
|
||||
if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) {
|
||||
|
@ -866,4 +866,33 @@ class LookupRef
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1081,30 +1081,55 @@ class MathTrig
|
||||
);
|
||||
}
|
||||
|
||||
protected static function filterFormulaArgs($cellReference, $args)
|
||||
{
|
||||
return array_filter(
|
||||
$args,
|
||||
function ($index) use ($cellReference) {
|
||||
list(, $row, $column) = explode('.', $index);
|
||||
if ($cellReference->getWorksheet()->cellExists($column . $row)) {
|
||||
//take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula
|
||||
$isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula();
|
||||
$cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue());
|
||||
|
||||
return !$isFormula || $cellFormula;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SUBTOTAL.
|
||||
*
|
||||
* Returns a subtotal in a list or database.
|
||||
*
|
||||
* @param int the number 1 to 11 that specifies which function to
|
||||
* use in calculating subtotals within a list
|
||||
* use in calculating subtotals within a range
|
||||
* list
|
||||
* Numbers 101 to 111 shadow the functions of 1 to 11
|
||||
* but ignore any values in the range that are
|
||||
* in hidden rows or columns
|
||||
* @param array of mixed Data Series
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function SUBTOTAL(...$args)
|
||||
{
|
||||
$cellReference = array_pop($args);
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
$cellReference = array_pop($aArgs);
|
||||
$subtotal = array_shift($aArgs);
|
||||
|
||||
// Calculate
|
||||
if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
|
||||
if ($subtotal > 100) {
|
||||
$aArgs = self::filterHiddenArgs($cellReference, $aArgs);
|
||||
$subtotal = $subtotal - 100;
|
||||
$subtotal -= 100;
|
||||
}
|
||||
|
||||
$aArgs = self::filterFormulaArgs($cellReference, $aArgs);
|
||||
switch ($subtotal) {
|
||||
case 1:
|
||||
return Statistical::AVERAGE($aArgs);
|
||||
@ -1433,4 +1458,178 @@ class MathTrig
|
||||
|
||||
return ((int) ($value * $adjust)) / $adjust;
|
||||
}
|
||||
|
||||
/**
|
||||
* SEC.
|
||||
*
|
||||
* Returns the secant of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The secant of the angle
|
||||
*/
|
||||
public static function SEC($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = cos($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* SECH.
|
||||
*
|
||||
* Returns the hyperbolic secant of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The hyperbolic secant of the angle
|
||||
*/
|
||||
public static function SECH($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = cosh($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSC.
|
||||
*
|
||||
* Returns the cosecant of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The cosecant of the angle
|
||||
*/
|
||||
public static function CSC($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = sin($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSCH.
|
||||
*
|
||||
* Returns the hyperbolic cosecant of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The hyperbolic cosecant of the angle
|
||||
*/
|
||||
public static function CSCH($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = sinh($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* COT.
|
||||
*
|
||||
* Returns the cotangent of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The cotangent of the angle
|
||||
*/
|
||||
public static function COT($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = tan($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* COTH.
|
||||
*
|
||||
* Returns the hyperbolic cotangent of an angle.
|
||||
*
|
||||
* @param float $angle Number
|
||||
*
|
||||
* @return float|string The hyperbolic cotangent of the angle
|
||||
*/
|
||||
public static function COTH($angle)
|
||||
{
|
||||
$angle = Functions::flattenSingleValue($angle);
|
||||
|
||||
if (!is_numeric($angle)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = tanh($angle);
|
||||
|
||||
return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACOT.
|
||||
*
|
||||
* Returns the arccotangent of a number.
|
||||
*
|
||||
* @param float $number Number
|
||||
*
|
||||
* @return float|string The arccotangent of the number
|
||||
*/
|
||||
public static function ACOT($number)
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
|
||||
if (!is_numeric($number)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return (M_PI / 2) - atan($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* ACOTH.
|
||||
*
|
||||
* Returns the hyperbolic arccotangent of a number.
|
||||
*
|
||||
* @param float $number Number
|
||||
*
|
||||
* @return float|string The hyperbolic arccotangent of the number
|
||||
*/
|
||||
public static function ACOTH($number)
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
|
||||
if (!is_numeric($number)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$result = log(($number + 1) / ($number - 1)) / 2;
|
||||
|
||||
return is_nan($result) ? Functions::NAN() : $result;
|
||||
}
|
||||
}
|
||||
|
@ -577,4 +577,96 @@ class TextData
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ ACCRINT
|
||||
ACCRINTM
|
||||
ACOS
|
||||
ACOSH
|
||||
ACOT
|
||||
ACOTH
|
||||
ADDRESS
|
||||
AMORDEGRC
|
||||
AMORLINC
|
||||
@ -30,6 +32,11 @@ BIN2DEC
|
||||
BIN2HEX
|
||||
BIN2OCT
|
||||
BINOMDIST
|
||||
BITAND
|
||||
BITLSHIFT
|
||||
BITOR
|
||||
BITRSHIFT
|
||||
BITXOR
|
||||
CEILING
|
||||
CELL
|
||||
CHAR
|
||||
@ -43,12 +50,15 @@ COLUMN
|
||||
COLUMNS
|
||||
COMBIN
|
||||
COMPLEX
|
||||
CONCAT
|
||||
CONCATENATE
|
||||
CONFIDENCE
|
||||
CONVERT
|
||||
CORREL
|
||||
COS
|
||||
COSH
|
||||
COT
|
||||
COTH
|
||||
COUNT
|
||||
COUNTA
|
||||
COUNTBLANK
|
||||
@ -62,6 +72,8 @@ COUPNUM
|
||||
COUPPCD
|
||||
COVAR
|
||||
CRITBINOM
|
||||
CSC
|
||||
CSCH
|
||||
CUBEKPIMEMBER
|
||||
CUBEMEMBER
|
||||
CUBEMEMBERPROPERTY
|
||||
@ -105,7 +117,9 @@ EDATE
|
||||
EFFECT
|
||||
EOMONTH
|
||||
ERF
|
||||
ERF.PRECISE
|
||||
ERFC
|
||||
ERFC.PRECISE
|
||||
ERROR.TYPE
|
||||
EVEN
|
||||
EXACT
|
||||
@ -149,6 +163,10 @@ IMAGINARY
|
||||
IMARGUMENT
|
||||
IMCONJUGATE
|
||||
IMCOS
|
||||
IMCOSH
|
||||
IMCOT
|
||||
IMCSC
|
||||
IMCSCH
|
||||
IMEXP
|
||||
IMLN
|
||||
IMLOG10
|
||||
@ -156,10 +174,14 @@ IMLOG2
|
||||
IMPOWER
|
||||
IMPRODUCT
|
||||
IMREAL
|
||||
IMSEC
|
||||
IMSECH
|
||||
IMSIN
|
||||
IMSINH
|
||||
IMSQRT
|
||||
IMSUB
|
||||
IMSUM
|
||||
IMTAN
|
||||
INDEX
|
||||
INDIRECT
|
||||
INFO
|
||||
@ -177,6 +199,7 @@ ISNA
|
||||
ISNONTEXT
|
||||
ISNUMBER
|
||||
ISODD
|
||||
ISOWEEKNUM
|
||||
ISPMT
|
||||
ISREF
|
||||
ISTEXT
|
||||
@ -229,6 +252,7 @@ NOT
|
||||
NOW
|
||||
NPER
|
||||
NPV
|
||||
NUMBERVALUE
|
||||
OCT2BIN
|
||||
OCT2DEC
|
||||
OCT2HEX
|
||||
@ -239,6 +263,7 @@ ODDLPRICE
|
||||
ODDLYIELD
|
||||
OFFSET
|
||||
OR
|
||||
PDURATION
|
||||
PEARSON
|
||||
PERCENTILE
|
||||
PERCENTRANK
|
||||
@ -275,10 +300,13 @@ ROUNDDOWN
|
||||
ROUNDUP
|
||||
ROW
|
||||
ROWS
|
||||
RRI
|
||||
RSQ
|
||||
RTD
|
||||
SEARCH
|
||||
SEARCHB
|
||||
SEC
|
||||
SECH
|
||||
SECOND
|
||||
SERIESSUM
|
||||
SIGN
|
||||
@ -292,6 +320,8 @@ SQRT
|
||||
SQRTPI
|
||||
STANDARDIZE
|
||||
STDEV
|
||||
STDEV.A
|
||||
STDEV.P
|
||||
STDEVA
|
||||
STDEVP
|
||||
STDEVPA
|
||||
@ -315,6 +345,7 @@ TBILLPRICE
|
||||
TBILLYIELD
|
||||
TDIST
|
||||
TEXT
|
||||
TEXTJOIN
|
||||
TIME
|
||||
TIMEVALUE
|
||||
TINV
|
||||
@ -327,6 +358,8 @@ TRUE
|
||||
TRUNC
|
||||
TTEST
|
||||
TYPE
|
||||
UNICHAR
|
||||
UNIORD
|
||||
UPPER
|
||||
USDOLLAR
|
||||
VALUE
|
||||
@ -342,6 +375,7 @@ WEIBULL
|
||||
WORKDAY
|
||||
XIRR
|
||||
XNPV
|
||||
XOR
|
||||
YEAR
|
||||
YEARFRAC
|
||||
YIELD
|
||||
|
@ -327,7 +327,7 @@ abstract class Coordinate
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all cell references in range.
|
||||
* 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)
|
||||
*
|
||||
@ -335,49 +335,12 @@ abstract class Coordinate
|
||||
*/
|
||||
public static function extractAllCellReferencesInRange($pRange)
|
||||
{
|
||||
// Returnvalue
|
||||
$returnValue = [];
|
||||
|
||||
// Explode spaces
|
||||
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
|
||||
$cellBlocks = self::getCellBlocksFromRangeString($pRange);
|
||||
foreach ($cellBlocks as $cellBlock) {
|
||||
// Single cell?
|
||||
if (!self::coordinateIsRange($cellBlock)) {
|
||||
$returnValue[] = $cellBlock;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Range...
|
||||
$ranges = self::splitRange($cellBlock);
|
||||
foreach ($ranges as $range) {
|
||||
// Single cell?
|
||||
if (!isset($range[1])) {
|
||||
$returnValue[] = $range[0];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Range...
|
||||
list($rangeStart, $rangeEnd) = $range;
|
||||
sscanf($rangeStart, '%[A-Z]%d', $startCol, $startRow);
|
||||
sscanf($rangeEnd, '%[A-Z]%d', $endCol, $endRow);
|
||||
++$endCol;
|
||||
|
||||
// Current data
|
||||
$currentCol = $startCol;
|
||||
$currentRow = $startRow;
|
||||
|
||||
// Loop cells
|
||||
while ($currentCol != $endCol) {
|
||||
while ($currentRow <= $endRow) {
|
||||
$returnValue[] = $currentCol . $currentRow;
|
||||
++$currentRow;
|
||||
}
|
||||
++$currentCol;
|
||||
$currentRow = $startRow;
|
||||
}
|
||||
}
|
||||
$returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock));
|
||||
}
|
||||
|
||||
// Sort the result by column and row
|
||||
@ -392,6 +355,60 @@ abstract class Coordinate
|
||||
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...
|
||||
list($rangeStart, $rangeEnd) = $range;
|
||||
list($startColumn, $startRow) = self::coordinateFromString($rangeStart);
|
||||
list($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
|
||||
@ -477,4 +494,33 @@ abstract class Coordinate
|
||||
|
||||
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 . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,14 @@ class Hyperlink
|
||||
return strpos($this->url, 'sheet://') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTypeHyperlink()
|
||||
{
|
||||
return $this->isInternal() ? '' : 'External';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hash code.
|
||||
*
|
||||
|
@ -217,7 +217,7 @@ class DataSeries
|
||||
/**
|
||||
* Get Plot Order.
|
||||
*
|
||||
* @return string
|
||||
* @return int[]
|
||||
*/
|
||||
public function getPlotOrder()
|
||||
{
|
||||
|
@ -603,6 +603,13 @@ class Html
|
||||
$this->stringData = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HTML formatting and return the resulting RichText.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return RichText
|
||||
*/
|
||||
public function toRichTextObject($html)
|
||||
{
|
||||
$this->initialise();
|
||||
@ -611,8 +618,8 @@ class Html
|
||||
$dom = new DOMDocument();
|
||||
// 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
|
||||
@$dom->loadHTML($html);
|
||||
|
||||
$prefix = '<?xml encoding="UTF-8">';
|
||||
@$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||
// Discard excess white space
|
||||
$dom->preserveWhiteSpace = false;
|
||||
|
||||
|
@ -4,6 +4,22 @@ 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.
|
||||
*
|
||||
@ -204,7 +220,6 @@ class Migrator
|
||||
'PHPExcel_Settings' => \PhpOffice\PhpSpreadsheet\Settings::class,
|
||||
'PHPExcel_Style' => \PhpOffice\PhpSpreadsheet\Style\Style::class,
|
||||
'PHPExcel_Worksheet' => \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::class,
|
||||
'PHPExcel' => \PhpOffice\PhpSpreadsheet\Spreadsheet::class,
|
||||
];
|
||||
|
||||
$methods = [
|
||||
@ -249,19 +264,25 @@ class Migrator
|
||||
{
|
||||
$patterns = [
|
||||
'/*.md',
|
||||
'/*.php',
|
||||
'/*.phtml',
|
||||
'/*.txt',
|
||||
'/*.TXT',
|
||||
'/*.php',
|
||||
'/*.phpt',
|
||||
'/*.php3',
|
||||
'/*.php4',
|
||||
'/*.php5',
|
||||
'/*.phtml',
|
||||
];
|
||||
|
||||
$from = array_keys($this->getMapping());
|
||||
$to = array_values($this->getMapping());
|
||||
|
||||
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 = str_replace($from, $to, $original);
|
||||
$converted = $this->replace($original);
|
||||
|
||||
if ($original !== $converted) {
|
||||
echo $file . " converted\n";
|
||||
@ -290,4 +311,23 @@ class Migrator
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,13 @@ class Csv extends BaseReader
|
||||
*/
|
||||
private $contiguousRow = -1;
|
||||
|
||||
/**
|
||||
* The character that can escape the enclosure.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $escapeCharacter = '\\';
|
||||
|
||||
/**
|
||||
* Create a new CSV Reader instance.
|
||||
*/
|
||||
@ -254,7 +261,7 @@ class Csv extends BaseReader
|
||||
$worksheetInfo[0]['totalColumns'] = 0;
|
||||
|
||||
// Loop through each line of the file in turn
|
||||
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure)) !== false) {
|
||||
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);
|
||||
}
|
||||
@ -326,7 +333,7 @@ class Csv extends BaseReader
|
||||
}
|
||||
|
||||
// Loop through each line of the file in turn
|
||||
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure)) !== false) {
|
||||
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)) {
|
||||
@ -458,6 +465,30 @@ class Csv extends BaseReader
|
||||
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?
|
||||
*
|
||||
@ -476,6 +507,12 @@ class Csv extends BaseReader
|
||||
|
||||
fclose($this->fileHandle);
|
||||
|
||||
// Trust file extension if any
|
||||
if (strtolower(pathinfo($pFilename, PATHINFO_EXTENSION)) === 'csv') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to guess mimetype
|
||||
$type = mime_content_type($pFilename);
|
||||
$supportedTypes = [
|
||||
'text/csv',
|
||||
|
@ -554,6 +554,7 @@ class Html extends BaseReader
|
||||
$row = 0;
|
||||
$column = 'A';
|
||||
$content = '';
|
||||
$this->rowspan = [];
|
||||
$this->processDomElement($dom, $spreadsheet->getActiveSheet(), $row, $column, $content);
|
||||
|
||||
// Return
|
||||
|
@ -7,9 +7,9 @@ interface IReadFilter
|
||||
/**
|
||||
* Should this cell be read?
|
||||
*
|
||||
* @param $column string Column address (as a string value like "A", or "IV")
|
||||
* @param $row int Row number
|
||||
* @param $worksheetName string Optional worksheet name
|
||||
* @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
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\NamedRange;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
|
||||
@ -25,6 +26,7 @@ use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
use XMLReader;
|
||||
use ZipArchive;
|
||||
|
||||
@ -115,15 +117,17 @@ class Xlsx extends BaseReader
|
||||
$zip->open($pFilename);
|
||||
|
||||
// The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
$rels = simplexml_load_string(
|
||||
$this->securityScan($this->getFromZipArchive($zip, '_rels/.rels'))
|
||||
); //~ http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
);
|
||||
foreach ($rels->Relationship as $rel) {
|
||||
switch ($rel['Type']) {
|
||||
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlWorkbook = simplexml_load_string(
|
||||
$this->securityScan($this->getFromZipArchive($zip, "{$rel['Target']}"))
|
||||
); //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
);
|
||||
|
||||
if ($xmlWorkbook->sheets) {
|
||||
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
|
||||
@ -157,8 +161,8 @@ class Xlsx extends BaseReader
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($pFilename);
|
||||
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$rels = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan($this->getFromZipArchive($zip, '_rels/.rels')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -166,8 +170,9 @@ class Xlsx extends BaseReader
|
||||
foreach ($rels->Relationship as $rel) {
|
||||
if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') {
|
||||
$dir = dirname($rel['Target']);
|
||||
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorkbook = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')
|
||||
),
|
||||
@ -183,8 +188,8 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlWorkbook = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, "{$rel['Target']}")
|
||||
),
|
||||
@ -193,7 +198,7 @@ class Xlsx extends BaseReader
|
||||
);
|
||||
if ($xmlWorkbook->sheets) {
|
||||
$dir = dirname($rel['Target']);
|
||||
/** @var \SimpleXMLElement $eleSheet */
|
||||
/** @var SimpleXMLElement $eleSheet */
|
||||
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
|
||||
$tmpInfo = [
|
||||
'worksheetName' => (string) $eleSheet['name'],
|
||||
@ -316,6 +321,60 @@ class Xlsx extends BaseReader
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Worksheet column attributes by attributes array passed.
|
||||
*
|
||||
* @param Worksheet $docSheet
|
||||
* @param string $column A, B, ... DX, ...
|
||||
* @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
|
||||
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
|
||||
*/
|
||||
private function setColumnAttributes(Worksheet $docSheet, $column, array $columnAttributes)
|
||||
{
|
||||
if (isset($columnAttributes['xfIndex'])) {
|
||||
$docSheet->getColumnDimension($column)->setXfIndex($columnAttributes['xfIndex']);
|
||||
}
|
||||
if (isset($columnAttributes['visible'])) {
|
||||
$docSheet->getColumnDimension($column)->setVisible($columnAttributes['visible']);
|
||||
}
|
||||
if (isset($columnAttributes['collapsed'])) {
|
||||
$docSheet->getColumnDimension($column)->setCollapsed($columnAttributes['collapsed']);
|
||||
}
|
||||
if (isset($columnAttributes['outlineLevel'])) {
|
||||
$docSheet->getColumnDimension($column)->setOutlineLevel($columnAttributes['outlineLevel']);
|
||||
}
|
||||
if (isset($columnAttributes['width'])) {
|
||||
$docSheet->getColumnDimension($column)->setWidth($columnAttributes['width']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Worksheet row attributes by attributes array passed.
|
||||
*
|
||||
* @param Worksheet $docSheet
|
||||
* @param int $row 1, 2, 3, ... 99, ...
|
||||
* @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
|
||||
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
|
||||
*/
|
||||
private function setRowAttributes(Worksheet $docSheet, $row, array $rowAttributes)
|
||||
{
|
||||
if (isset($rowAttributes['xfIndex'])) {
|
||||
$docSheet->getRowDimension($row)->setXfIndex($rowAttributes['xfIndex']);
|
||||
}
|
||||
if (isset($rowAttributes['visible'])) {
|
||||
$docSheet->getRowDimension($row)->setVisible($rowAttributes['visible']);
|
||||
}
|
||||
if (isset($rowAttributes['collapsed'])) {
|
||||
$docSheet->getRowDimension($row)->setCollapsed($rowAttributes['collapsed']);
|
||||
}
|
||||
if (isset($rowAttributes['outlineLevel'])) {
|
||||
$docSheet->getRowDimension($row)->setOutlineLevel($rowAttributes['outlineLevel']);
|
||||
}
|
||||
if (isset($rowAttributes['rowHeight'])) {
|
||||
$docSheet->getRowDimension($row)->setRowHeight($rowAttributes['rowHeight']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*
|
||||
@ -336,13 +395,14 @@ class Xlsx extends BaseReader
|
||||
$excel->removeCellStyleXfByIndex(0); // remove the default style
|
||||
$excel->removeCellXfByIndex(0); // remove the default style
|
||||
}
|
||||
$unparsedLoadedData = [];
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($pFilename);
|
||||
|
||||
// Read the theme first, because we need the colour scheme when reading the styles
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$wbRels = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan($this->getFromZipArchive($zip, 'xl/_rels/workbook.xml.rels')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -388,8 +448,8 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$rels = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan($this->getFromZipArchive($zip, '_rels/.rels')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -444,7 +504,7 @@ class Xlsx extends BaseReader
|
||||
);
|
||||
if (is_object($xmlCore)) {
|
||||
$docProps = $excel->getProperties();
|
||||
/** @var \SimpleXMLElement $xmlProperty */
|
||||
/** @var SimpleXMLElement $xmlProperty */
|
||||
foreach ($xmlCore as $xmlProperty) {
|
||||
$cellDataOfficeAttributes = $xmlProperty->attributes();
|
||||
if (isset($cellDataOfficeAttributes['name'])) {
|
||||
@ -470,8 +530,8 @@ class Xlsx extends BaseReader
|
||||
break;
|
||||
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
|
||||
$dir = dirname($rel['Target']);
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorkbook = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -480,8 +540,8 @@ class Xlsx extends BaseReader
|
||||
|
||||
$sharedStrings = [];
|
||||
$xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlStrings = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$this->securityScan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -527,8 +587,8 @@ class Xlsx extends BaseReader
|
||||
$styles = [];
|
||||
$cellStyles = [];
|
||||
$xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlStyles = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$this->securityScan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -638,8 +698,8 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlWorkbook = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$this->securityScan($this->getFromZipArchive($zip, "{$rel['Target']}")),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -655,6 +715,9 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
// Set protection
|
||||
$this->readProtection($excel, $xmlWorkbook);
|
||||
|
||||
$sheetId = 0; // keep track of new sheet id in final workbook
|
||||
$oldSheetId = -1; // keep track of old sheet id in final workbook
|
||||
$countSkippedSheets = 0; // keep track of number of skipped sheets
|
||||
@ -663,7 +726,7 @@ class Xlsx extends BaseReader
|
||||
$charts = $chartDetails = [];
|
||||
|
||||
if ($xmlWorkbook->sheets) {
|
||||
/** @var \SimpleXMLElement $eleSheet */
|
||||
/** @var SimpleXMLElement $eleSheet */
|
||||
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
|
||||
++$oldSheetId;
|
||||
|
||||
@ -687,8 +750,8 @@ class Xlsx extends BaseReader
|
||||
// reverse
|
||||
$docSheet->setTitle((string) $eleSheet['name'], false, false);
|
||||
$fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$xmlSheet = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||
$this->securityScan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
@ -811,30 +874,6 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($xmlSheet->cols) && !$this->readDataOnly) {
|
||||
foreach ($xmlSheet->cols->col as $col) {
|
||||
for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) {
|
||||
if ($col['style'] && !$this->readDataOnly) {
|
||||
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setXfIndex((int) ($col['style']));
|
||||
}
|
||||
if (self::boolean($col['hidden'])) {
|
||||
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setVisible(false);
|
||||
}
|
||||
if (self::boolean($col['collapsed'])) {
|
||||
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setCollapsed(true);
|
||||
}
|
||||
if ($col['outlineLevel'] > 0) {
|
||||
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setOutlineLevel((int) ($col['outlineLevel']));
|
||||
}
|
||||
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setWidth((float) ($col['width']));
|
||||
|
||||
if ((int) ($col['max']) == 16384) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($xmlSheet->printOptions) && !$this->readDataOnly) {
|
||||
if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) {
|
||||
$docSheet->setShowGridlines(true);
|
||||
@ -850,25 +889,11 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
$this->readColumnsAndRowsAttributes($xmlSheet, $docSheet);
|
||||
|
||||
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
|
||||
$cIndex = 1; // Cell Start from 1
|
||||
foreach ($xmlSheet->sheetData->row as $row) {
|
||||
if ($row['ht'] && !$this->readDataOnly) {
|
||||
$docSheet->getRowDimension((int) ($row['r']))->setRowHeight((float) ($row['ht']));
|
||||
}
|
||||
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
|
||||
$docSheet->getRowDimension((int) ($row['r']))->setVisible(false);
|
||||
}
|
||||
if (self::boolean($row['collapsed'])) {
|
||||
$docSheet->getRowDimension((int) ($row['r']))->setCollapsed(true);
|
||||
}
|
||||
if ($row['outlineLevel'] > 0) {
|
||||
$docSheet->getRowDimension((int) ($row['r']))->setOutlineLevel((int) ($row['outlineLevel']));
|
||||
}
|
||||
if ($row['s'] && !$this->readDataOnly) {
|
||||
$docSheet->getRowDimension((int) ($row['r']))->setXfIndex((int) ($row['s']));
|
||||
}
|
||||
|
||||
$rowIndex = 1;
|
||||
foreach ($row->c as $c) {
|
||||
$r = (string) $c['r'];
|
||||
@ -883,7 +908,7 @@ class Xlsx extends BaseReader
|
||||
if ($this->getReadFilter() !== null) {
|
||||
$coordinates = Coordinate::coordinateFromString($r);
|
||||
|
||||
if (!$this->getReadFilter()->readCell($coordinates[0], $coordinates[1], $docSheet->getTitle())) {
|
||||
if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -1068,8 +1093,8 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
// Or Date Group elements
|
||||
foreach ($filters->dateGroupItem as $dateGroupItem) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
null,
|
||||
[
|
||||
'year' => (string) $dateGroupItem['year'],
|
||||
@ -1106,8 +1131,8 @@ class Xlsx extends BaseReader
|
||||
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
|
||||
// We should only ever have one dynamic filter
|
||||
foreach ($filterColumn->dynamicFilter as $filterRule) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
null,
|
||||
(string) $filterRule['val'],
|
||||
(string) $filterRule['type']
|
||||
@ -1185,6 +1210,11 @@ class Xlsx extends BaseReader
|
||||
self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) {
|
||||
$docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
|
||||
}
|
||||
|
||||
$relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
if (isset($relAttributes['id'])) {
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($xmlSheet && $xmlSheet->headerFooter && !$this->readDataOnly) {
|
||||
@ -1268,13 +1298,23 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed sheet AlternateContent
|
||||
if ($xmlSheet && !$this->readDataOnly) {
|
||||
$mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
|
||||
if ($mc->AlternateContent) {
|
||||
foreach ($mc->AlternateContent as $alternateContent) {
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add hyperlinks
|
||||
$hyperlinks = [];
|
||||
if (!$this->readDataOnly) {
|
||||
// Locate hyperlink relations
|
||||
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
@ -1290,7 +1330,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
// Loop through hyperlinks
|
||||
if ($xmlSheet && $xmlSheet->hyperlinks) {
|
||||
/** @var \SimpleXMLElement $hyperlink */
|
||||
/** @var SimpleXMLElement $hyperlink */
|
||||
foreach ($xmlSheet->hyperlinks->hyperlink as $hyperlink) {
|
||||
// Link url
|
||||
$linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
@ -1322,8 +1362,8 @@ class Xlsx extends BaseReader
|
||||
if (!$this->readDataOnly) {
|
||||
// Locate comment relations
|
||||
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
@ -1367,6 +1407,9 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
// later we will remove from it real vmlComments
|
||||
$unparsedVmlDrawings = $vmlComments;
|
||||
|
||||
// Loop through VML comments
|
||||
foreach ($vmlComments as $relName => $relPath) {
|
||||
// Load VML comments file
|
||||
@ -1407,7 +1450,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
if (($column !== null) && ($row !== null)) {
|
||||
// Set comment properties
|
||||
$comment = $docSheet->getCommentByColumnAndRow((string) $column, $row + 1);
|
||||
$comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
|
||||
$comment->getFillColor()->setRGB($fillColor);
|
||||
|
||||
// Parse style
|
||||
@ -1431,16 +1474,31 @@ class Xlsx extends BaseReader
|
||||
$comment->setVisible($stylePair[1] == 'visible');
|
||||
}
|
||||
}
|
||||
|
||||
unset($unparsedVmlDrawings[$relName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed vmlDrawing
|
||||
if ($unparsedVmlDrawings) {
|
||||
foreach ($unparsedVmlDrawings as $rId => $relPath) {
|
||||
$rId = substr($rId, 3); // rIdXXX
|
||||
$unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
|
||||
$unparsedVmlDrawing[$rId] = [];
|
||||
$unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
|
||||
$unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
|
||||
$unparsedVmlDrawing[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
|
||||
unset($unparsedVmlDrawing);
|
||||
}
|
||||
}
|
||||
|
||||
// Header/footer images
|
||||
if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {
|
||||
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
@ -1457,8 +1515,8 @@ class Xlsx extends BaseReader
|
||||
|
||||
if ($vmlRelationship != '') {
|
||||
// Fetch linked images
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsVML = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels')
|
||||
),
|
||||
@ -1520,8 +1578,8 @@ class Xlsx extends BaseReader
|
||||
|
||||
// TODO: Autoshapes from twoCellAnchors!
|
||||
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
@ -1537,8 +1595,8 @@ class Xlsx extends BaseReader
|
||||
if ($xmlSheet->drawing && !$this->readDataOnly) {
|
||||
foreach ($xmlSheet->drawing as $drawing) {
|
||||
$fileDrawing = $drawings[(string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsDrawing = simplexml_load_string(
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels')
|
||||
),
|
||||
@ -1546,9 +1604,12 @@ class Xlsx extends BaseReader
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
$images = [];
|
||||
|
||||
$hyperlinks = [];
|
||||
if ($relsDrawing && $relsDrawing->Relationship) {
|
||||
foreach ($relsDrawing->Relationship as $ele) {
|
||||
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
|
||||
$hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
|
||||
}
|
||||
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
|
||||
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
|
||||
} elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
|
||||
@ -1570,12 +1631,15 @@ class Xlsx extends BaseReader
|
||||
if ($xmlDrawing->oneCellAnchor) {
|
||||
foreach ($xmlDrawing->oneCellAnchor as $oneCellAnchor) {
|
||||
if ($oneCellAnchor->pic->blipFill) {
|
||||
/** @var \SimpleXMLElement $blip */
|
||||
/** @var SimpleXMLElement $blip */
|
||||
$blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
|
||||
/** @var \SimpleXMLElement $xfrm */
|
||||
/** @var SimpleXMLElement $xfrm */
|
||||
$xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
|
||||
/** @var \SimpleXMLElement $outerShdw */
|
||||
/** @var SimpleXMLElement $outerShdw */
|
||||
$outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
|
||||
/** @var \SimpleXMLElement $hlinkClick */
|
||||
$hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
|
||||
|
||||
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
|
||||
$objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
|
||||
$objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
|
||||
@ -1606,6 +1670,9 @@ class Xlsx extends BaseReader
|
||||
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val'));
|
||||
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000);
|
||||
}
|
||||
|
||||
$this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
|
||||
|
||||
$objDrawing->setWorksheet($docSheet);
|
||||
} else {
|
||||
// ? Can charts be positioned with a oneCellAnchor ?
|
||||
@ -1623,6 +1690,7 @@ class Xlsx extends BaseReader
|
||||
$blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
|
||||
$xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
|
||||
$outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
|
||||
$hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
|
||||
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
|
||||
$objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
|
||||
$objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
|
||||
@ -1654,6 +1722,9 @@ class Xlsx extends BaseReader
|
||||
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val'));
|
||||
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000);
|
||||
}
|
||||
|
||||
$this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
|
||||
|
||||
$objDrawing->setWorksheet($docSheet);
|
||||
} elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
|
||||
$fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
|
||||
@ -1663,7 +1734,7 @@ class Xlsx extends BaseReader
|
||||
$toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
|
||||
$toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
|
||||
$graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
|
||||
/** @var \SimpleXMLElement $chartRef */
|
||||
/** @var SimpleXMLElement $chartRef */
|
||||
$chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
|
||||
$thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
|
||||
@ -1680,9 +1751,33 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store original rId of drawing files
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
|
||||
foreach ($relsWorksheet->Relationship as $ele) {
|
||||
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = (string) $ele['Id'];
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed drawing AlternateContent
|
||||
$xmlAltDrawing = simplexml_load_string(
|
||||
$this->securityScan($this->getFromZipArchive($zip, $fileDrawing)),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
)->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
|
||||
|
||||
if ($xmlAltDrawing->AlternateContent) {
|
||||
foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
|
||||
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
|
||||
$this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
|
||||
|
||||
// Loop through definedNames
|
||||
if ($xmlWorkbook->definedNames) {
|
||||
foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
|
||||
@ -1826,8 +1921,10 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
|
||||
if ((!$this->readDataOnly) || (!empty($this->loadSheetsOnly))) {
|
||||
$workbookView = $xmlWorkbook->bookViews->workbookView;
|
||||
|
||||
// active sheet index
|
||||
$activeTab = (int) ($xmlWorkbook->bookViews->workbookView['activeTab']); // refers to old sheet index
|
||||
$activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index
|
||||
|
||||
// keep active sheet index if sheet is still loaded, else first sheet is set as the active
|
||||
if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
|
||||
@ -1838,6 +1935,46 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
$excel->setActiveSheetIndex(0);
|
||||
}
|
||||
|
||||
if (isset($workbookView['showHorizontalScroll'])) {
|
||||
$showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
|
||||
$excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
|
||||
}
|
||||
|
||||
if (isset($workbookView['showVerticalScroll'])) {
|
||||
$showVerticalScroll = (string) $workbookView['showVerticalScroll'];
|
||||
$excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
|
||||
}
|
||||
|
||||
if (isset($workbookView['showSheetTabs'])) {
|
||||
$showSheetTabs = (string) $workbookView['showSheetTabs'];
|
||||
$excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
|
||||
}
|
||||
|
||||
if (isset($workbookView['minimized'])) {
|
||||
$minimized = (string) $workbookView['minimized'];
|
||||
$excel->setMinimized($this->castXsdBooleanToBool($minimized));
|
||||
}
|
||||
|
||||
if (isset($workbookView['autoFilterDateGrouping'])) {
|
||||
$autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
|
||||
$excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
|
||||
}
|
||||
|
||||
if (isset($workbookView['firstSheet'])) {
|
||||
$firstSheet = (string) $workbookView['firstSheet'];
|
||||
$excel->setFirstSheetIndex((int) $firstSheet);
|
||||
}
|
||||
|
||||
if (isset($workbookView['visibility'])) {
|
||||
$visibility = (string) $workbookView['visibility'];
|
||||
$excel->setVisibility($visibility);
|
||||
}
|
||||
|
||||
if (isset($workbookView['tabRatio'])) {
|
||||
$tabRatio = (string) $workbookView['tabRatio'];
|
||||
$excel->setTabRatio((int) $tabRatio);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@ -1852,6 +1989,18 @@ class Xlsx extends BaseReader
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
|
||||
// Default content types
|
||||
foreach ($contentTypes->Default as $contentType) {
|
||||
switch ($contentType['ContentType']) {
|
||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
|
||||
$unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Override content types
|
||||
foreach ($contentTypes->Override as $contentType) {
|
||||
switch ($contentType['ContentType']) {
|
||||
case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
|
||||
@ -1876,10 +2025,20 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// unparsed
|
||||
case 'application/vnd.ms-excel.controlproperties+xml':
|
||||
$unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$excel->setUnparsedLoadedData($unparsedLoadedData);
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $excel;
|
||||
@ -1912,7 +2071,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
/**
|
||||
* @param Style $docStyle
|
||||
* @param \SimpleXMLElement|\stdClass $style
|
||||
* @param SimpleXMLElement|\stdClass $style
|
||||
*/
|
||||
private static function readStyle(Style $docStyle, $style)
|
||||
{
|
||||
@ -1953,7 +2112,7 @@ class Xlsx extends BaseReader
|
||||
// fill
|
||||
if (isset($style->fill)) {
|
||||
if ($style->fill->gradientFill) {
|
||||
/** @var \SimpleXMLElement $gradientFill */
|
||||
/** @var SimpleXMLElement $gradientFill */
|
||||
$gradientFill = $style->fill->gradientFill[0];
|
||||
if (!empty($gradientFill['type'])) {
|
||||
$docStyle->getFill()->setFillType((string) $gradientFill['type']);
|
||||
@ -2042,7 +2201,7 @@ class Xlsx extends BaseReader
|
||||
|
||||
/**
|
||||
* @param Border $docBorder
|
||||
* @param \SimpleXMLElement $eleBorder
|
||||
* @param SimpleXMLElement $eleBorder
|
||||
*/
|
||||
private static function readBorder(Border $docBorder, $eleBorder)
|
||||
{
|
||||
@ -2055,7 +2214,7 @@ class Xlsx extends BaseReader
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SimpleXMLElement | null $is
|
||||
* @param SimpleXMLElement | null $is
|
||||
*
|
||||
* @return RichText
|
||||
*/
|
||||
@ -2215,4 +2374,227 @@ class Xlsx extends BaseReader
|
||||
|
||||
return $value === 'true' || $value === 'TRUE';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing
|
||||
* @param \SimpleXMLElement $cellAnchor
|
||||
* @param array $hyperlinks
|
||||
*/
|
||||
private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks)
|
||||
{
|
||||
$hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
|
||||
|
||||
if ($hlinkClick->count() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id'];
|
||||
$hyperlink = new Hyperlink(
|
||||
$hyperlinks[$hlinkId],
|
||||
(string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')
|
||||
);
|
||||
$objDrawing->setHyperlink($hyperlink);
|
||||
}
|
||||
|
||||
private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook)
|
||||
{
|
||||
if (!$xmlWorkbook->workbookProtection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['lockRevision']) {
|
||||
$excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']);
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['lockStructure']) {
|
||||
$excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']);
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['lockWindows']) {
|
||||
$excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']);
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
|
||||
$excel->getSecurity()->setRevisionsPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true);
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['workbookPassword']) {
|
||||
$excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true);
|
||||
}
|
||||
}
|
||||
|
||||
private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData)
|
||||
{
|
||||
if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
$ctrlProps = [];
|
||||
foreach ($relsWorksheet->Relationship as $ele) {
|
||||
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') {
|
||||
$ctrlProps[(string) $ele['Id']] = $ele;
|
||||
}
|
||||
}
|
||||
|
||||
$unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps'];
|
||||
foreach ($ctrlProps as $rId => $ctrlProp) {
|
||||
$rId = substr($rId, 3); // rIdXXX
|
||||
$unparsedCtrlProps[$rId] = [];
|
||||
$unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']);
|
||||
$unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target'];
|
||||
$unparsedCtrlProps[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath']));
|
||||
}
|
||||
unset($unparsedCtrlProps);
|
||||
}
|
||||
|
||||
private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData)
|
||||
{
|
||||
if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//~ http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
$relsWorksheet = simplexml_load_string(
|
||||
$this->securityScan(
|
||||
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
|
||||
),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
$sheetPrinterSettings = [];
|
||||
foreach ($relsWorksheet->Relationship as $ele) {
|
||||
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') {
|
||||
$sheetPrinterSettings[(string) $ele['Id']] = $ele;
|
||||
}
|
||||
}
|
||||
|
||||
$unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
|
||||
foreach ($sheetPrinterSettings as $rId => $printerSettings) {
|
||||
$rId = substr($rId, 3); // rIdXXX
|
||||
$unparsedPrinterSettings[$rId] = [];
|
||||
$unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
|
||||
$unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
|
||||
$unparsedPrinterSettings[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath']));
|
||||
}
|
||||
unset($unparsedPrinterSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an 'xsd:boolean' XML value to a PHP boolean value.
|
||||
* A valid 'xsd:boolean' XML value can be one of the following
|
||||
* four values: 'true', 'false', '1', '0'. It is case sensitive.
|
||||
*
|
||||
* Note that just doing '(bool) $xsdBoolean' is not safe,
|
||||
* since '(bool) "false"' returns true.
|
||||
*
|
||||
* @see https://www.w3.org/TR/xmlschema11-2/#boolean
|
||||
*
|
||||
* @param string $xsdBoolean An XML string value of type 'xsd:boolean'
|
||||
*
|
||||
* @return bool Boolean value
|
||||
*/
|
||||
private function castXsdBooleanToBool($xsdBoolean)
|
||||
{
|
||||
if ($xsdBoolean === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $xsdBoolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read columns and rows attributes from XML and set them on the worksheet.
|
||||
*
|
||||
* @param SimpleXMLElement $xmlSheet
|
||||
* @param Worksheet $docSheet
|
||||
*/
|
||||
private function readColumnsAndRowsAttributes(SimpleXMLElement $xmlSheet, Worksheet $docSheet)
|
||||
{
|
||||
$columnsAttributes = [];
|
||||
$rowsAttributes = [];
|
||||
if (isset($xmlSheet->cols) && !$this->readDataOnly) {
|
||||
foreach ($xmlSheet->cols->col as $col) {
|
||||
for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) {
|
||||
if ($col['style'] && !$this->readDataOnly) {
|
||||
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['xfIndex'] = (int) $col['style'];
|
||||
}
|
||||
if (self::boolean($col['hidden'])) {
|
||||
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['visible'] = false;
|
||||
}
|
||||
if (self::boolean($col['collapsed'])) {
|
||||
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['collapsed'] = true;
|
||||
}
|
||||
if ($col['outlineLevel'] > 0) {
|
||||
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['outlineLevel'] = (int) $col['outlineLevel'];
|
||||
}
|
||||
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['width'] = (float) $col['width'];
|
||||
|
||||
if ((int) ($col['max']) == 16384) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
|
||||
foreach ($xmlSheet->sheetData->row as $row) {
|
||||
if ($row['ht'] && !$this->readDataOnly) {
|
||||
$rowsAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
|
||||
$rowsAttributes[(int) $row['r']]['visible'] = false;
|
||||
}
|
||||
if (self::boolean($row['collapsed'])) {
|
||||
$rowsAttributes[(int) $row['r']]['collapsed'] = true;
|
||||
}
|
||||
if ($row['outlineLevel'] > 0) {
|
||||
$rowsAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if ($row['s'] && !$this->readDataOnly) {
|
||||
$rowsAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set columns/rows attributes
|
||||
$columnsAttributesSet = [];
|
||||
$rowsAttributesSet = [];
|
||||
foreach ($columnsAttributes as $coordColumn => $columnAttributes) {
|
||||
foreach ($rowsAttributes as $coordRow => $rowAttributes) {
|
||||
if ($this->getReadFilter() !== null) {
|
||||
if (!$this->getReadFilter()->readCell($coordColumn, $coordRow, $docSheet->getTitle())) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($columnsAttributesSet[$coordColumn])) {
|
||||
$this->setColumnAttributes($docSheet, $coordColumn, $columnAttributes);
|
||||
$columnsAttributesSet[$coordColumn] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rowsAttributes as $coordRow => $rowAttributes) {
|
||||
foreach ($columnsAttributes as $coordColumn => $columnAttributes) {
|
||||
if ($this->getReadFilter() !== null) {
|
||||
if (!$this->getReadFilter()->readCell($coordColumn, $coordRow, $docSheet->getTitle())) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($rowsAttributesSet[$coordRow])) {
|
||||
$this->setRowAttributes($docSheet, $coordRow, $rowAttributes);
|
||||
$rowsAttributesSet[$coordRow] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class Xml extends BaseReader
|
||||
// Read sample data (first 2 KB will do)
|
||||
$data = fread($fileHandle, 2048);
|
||||
fclose($fileHandle);
|
||||
$data = strtr($data, "'", '"'); // fix headers with single quote
|
||||
$data = str_replace("'", '"', $data); // fix headers with single quote
|
||||
|
||||
$valid = true;
|
||||
foreach ($signature as $match) {
|
||||
|
@ -256,7 +256,7 @@ class Date
|
||||
/**
|
||||
* Convert a Unix timestamp to an MS Excel serialized date/time value.
|
||||
*
|
||||
* @param DateTimeInterface $dateValue Unix Timestamp
|
||||
* @param int $dateValue Unix Timestamp
|
||||
*
|
||||
* @return float MS Excel serialized date/time value
|
||||
*/
|
||||
|
@ -1000,7 +1000,7 @@ class Matrix
|
||||
*/
|
||||
public function times(...$args)
|
||||
{
|
||||
if (count() > 0) {
|
||||
if (count($args) > 0) {
|
||||
$match = implode(',', array_map('gettype', $args));
|
||||
|
||||
switch ($match) {
|
||||
@ -1094,7 +1094,7 @@ class Matrix
|
||||
*/
|
||||
public function power(...$args)
|
||||
{
|
||||
if (count() > 0) {
|
||||
if (count($args) > 0) {
|
||||
$match = implode(',', array_map('gettype', $args));
|
||||
|
||||
switch ($match) {
|
||||
|
@ -142,7 +142,7 @@ class BestFit
|
||||
*
|
||||
* @param int $dp Number of places of decimal precision to display
|
||||
*
|
||||
* @return string
|
||||
* @return float
|
||||
*/
|
||||
public function getSlope($dp = 0)
|
||||
{
|
||||
@ -158,7 +158,7 @@ class BestFit
|
||||
*
|
||||
* @param int $dp Number of places of decimal precision to display
|
||||
*
|
||||
* @return string
|
||||
* @return float
|
||||
*/
|
||||
public function getSlopeSE($dp = 0)
|
||||
{
|
||||
@ -174,7 +174,7 @@ class BestFit
|
||||
*
|
||||
* @param int $dp Number of places of decimal precision to display
|
||||
*
|
||||
* @return string
|
||||
* @return float
|
||||
*/
|
||||
public function getIntersect($dp = 0)
|
||||
{
|
||||
@ -190,7 +190,7 @@ class BestFit
|
||||
*
|
||||
* @param int $dp Number of places of decimal precision to display
|
||||
*
|
||||
* @return string
|
||||
* @return float
|
||||
*/
|
||||
public function getIntersectSE($dp = 0)
|
||||
{
|
||||
@ -217,6 +217,13 @@ class BestFit
|
||||
return $this->goodnessOfFit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the goodness of fit for this regression.
|
||||
*
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getGoodnessOfFitPercent($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -242,6 +249,11 @@ class BestFit
|
||||
return $this->stdevOfResiduals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSSRegression($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -251,6 +263,11 @@ class BestFit
|
||||
return $this->SSRegression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSSResiduals($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -260,6 +277,11 @@ class BestFit
|
||||
return $this->SSResiduals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getDFResiduals($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -269,6 +291,11 @@ class BestFit
|
||||
return $this->DFResiduals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getF($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -278,6 +305,11 @@ class BestFit
|
||||
return $this->f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getCovariance($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -287,6 +319,11 @@ class BestFit
|
||||
return $this->covariance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dp Number of places of decimal precision to return
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getCorrelation($dp = 0)
|
||||
{
|
||||
if ($dp != 0) {
|
||||
@ -296,6 +333,9 @@ class BestFit
|
||||
return $this->correlation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float[]
|
||||
*/
|
||||
public function getYBestFitValues()
|
||||
{
|
||||
return $this->yBestFitValues;
|
||||
|
@ -164,7 +164,7 @@ class PolynomialBestFit extends BestFit
|
||||
$this->intersect = array_shift($coefficients);
|
||||
$this->slope = $coefficients;
|
||||
|
||||
$this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum);
|
||||
$this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
|
||||
foreach ($this->xValues as $xKey => $xValue) {
|
||||
$this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
|
||||
}
|
||||
|
@ -9,6 +9,17 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class Spreadsheet
|
||||
{
|
||||
// Allowable values for workbook window visilbity
|
||||
const VISIBILITY_VISIBLE = 'visible';
|
||||
const VISIBILITY_HIDDEN = 'hidden';
|
||||
const VISIBILITY_VERY_HIDDEN = 'veryHidden';
|
||||
|
||||
private static $workbookViewVisibilityValues = [
|
||||
self::VISIBILITY_VISIBLE,
|
||||
self::VISIBILITY_HIDDEN,
|
||||
self::VISIBILITY_VERY_HIDDEN,
|
||||
];
|
||||
|
||||
/**
|
||||
* Unique ID.
|
||||
*
|
||||
@ -115,6 +126,75 @@ class Spreadsheet
|
||||
*/
|
||||
private $ribbonBinObjects;
|
||||
|
||||
/**
|
||||
* List of unparsed loaded data for export to same format with better compatibility.
|
||||
* It has to be minimized when the library start to support currently unparsed data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unparsedLoadedData = [];
|
||||
|
||||
/**
|
||||
* Controls visibility of the horizonal scroll bar in the application.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showHorizontalScroll = true;
|
||||
|
||||
/**
|
||||
* Controls visibility of the horizonal scroll bar in the application.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showVerticalScroll = true;
|
||||
|
||||
/**
|
||||
* Controls visibility of the sheet tabs in the application.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $showSheetTabs = true;
|
||||
|
||||
/**
|
||||
* Specifies a boolean value that indicates whether the workbook window
|
||||
* is minimized.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $minimized = false;
|
||||
|
||||
/**
|
||||
* Specifies a boolean value that indicates whether to group dates
|
||||
* when presenting the user with filtering optiomd in the user
|
||||
* interface.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $autoFilterDateGrouping = true;
|
||||
|
||||
/**
|
||||
* Specifies the index to the first sheet in the book view.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $firstSheetIndex = 0;
|
||||
|
||||
/**
|
||||
* Specifies the visible status of the workbook.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $visibility = self::VISIBILITY_VISIBLE;
|
||||
|
||||
/**
|
||||
* Specifies the ratio between the workbook tabs bar and the horizontal
|
||||
* scroll bar. TabRatio is assumed to be out of 1000 of the horizontal
|
||||
* window width.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $tabRatio = 600;
|
||||
|
||||
/**
|
||||
* The workbook has macros ?
|
||||
*
|
||||
@ -256,6 +336,32 @@ class Spreadsheet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of unparsed loaded data for export to same format with better compatibility.
|
||||
* It has to be minimized when the library start to support currently unparsed data.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUnparsedLoadedData()
|
||||
{
|
||||
return $this->unparsedLoadedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of unparsed loaded data for export to same format with better compatibility.
|
||||
* It has to be minimized when the library start to support currently unparsed data.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array $unparsedLoadedData
|
||||
*/
|
||||
public function setUnparsedLoadedData(array $unparsedLoadedData)
|
||||
{
|
||||
$this->unparsedLoadedData = $unparsedLoadedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
|
||||
*
|
||||
@ -1182,4 +1288,203 @@ class Spreadsheet
|
||||
{
|
||||
return $this->uniqueID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visibility of the horizonal scroll bar in the application.
|
||||
*
|
||||
* @return bool True if horizonal scroll bar is visible
|
||||
*/
|
||||
public function getShowHorizontalScroll()
|
||||
{
|
||||
return $this->showHorizontalScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility of the horizonal scroll bar in the application.
|
||||
*
|
||||
* @param bool $showHorizontalScroll True if horizonal scroll bar is visible
|
||||
*/
|
||||
public function setShowHorizontalScroll($showHorizontalScroll)
|
||||
{
|
||||
$this->showHorizontalScroll = (bool) $showHorizontalScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visibility of the vertical scroll bar in the application.
|
||||
*
|
||||
* @return bool True if vertical scroll bar is visible
|
||||
*/
|
||||
public function getShowVerticalScroll()
|
||||
{
|
||||
return $this->showVerticalScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility of the vertical scroll bar in the application.
|
||||
*
|
||||
* @param bool $showVerticalScroll True if vertical scroll bar is visible
|
||||
*/
|
||||
public function setShowVerticalScroll($showVerticalScroll)
|
||||
{
|
||||
$this->showVerticalScroll = (bool) $showVerticalScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visibility of the sheet tabs in the application.
|
||||
*
|
||||
* @return bool True if the sheet tabs are visible
|
||||
*/
|
||||
public function getShowSheetTabs()
|
||||
{
|
||||
return $this->showSheetTabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility of the sheet tabs in the application.
|
||||
*
|
||||
* @param bool $showSheetTabs True if sheet tabs are visible
|
||||
*/
|
||||
public function setShowSheetTabs($showSheetTabs)
|
||||
{
|
||||
$this->showSheetTabs = (bool) $showSheetTabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the workbook window is minimized.
|
||||
*
|
||||
* @return bool true if workbook window is minimized
|
||||
*/
|
||||
public function getMinimized()
|
||||
{
|
||||
return $this->minimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the workbook window is minimized.
|
||||
*
|
||||
* @param bool $minimized true if workbook window is minimized
|
||||
*/
|
||||
public function setMinimized($minimized)
|
||||
{
|
||||
$this->minimized = (bool) $minimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to group dates when presenting the user with
|
||||
* filtering optiomd in the user interface.
|
||||
*
|
||||
* @return bool true if workbook window is minimized
|
||||
*/
|
||||
public function getAutoFilterDateGrouping()
|
||||
{
|
||||
return $this->autoFilterDateGrouping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to group dates when presenting the user with
|
||||
* filtering optiomd in the user interface.
|
||||
*
|
||||
* @param bool $autoFilterDateGrouping true if workbook window is minimized
|
||||
*/
|
||||
public function setAutoFilterDateGrouping($autoFilterDateGrouping)
|
||||
{
|
||||
$this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first sheet in the book view.
|
||||
*
|
||||
* @return int First sheet in book view
|
||||
*/
|
||||
public function getFirstSheetIndex()
|
||||
{
|
||||
return $this->firstSheetIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the first sheet in the book view.
|
||||
*
|
||||
* @param int $firstSheetIndex First sheet in book view
|
||||
*
|
||||
* @throws Exception if the given value is invalid
|
||||
*/
|
||||
public function setFirstSheetIndex($firstSheetIndex)
|
||||
{
|
||||
if ($firstSheetIndex >= 0) {
|
||||
$this->firstSheetIndex = (int) $firstSheetIndex;
|
||||
} else {
|
||||
throw new Exception('First sheet index must be a positive integer.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the visibility status of the workbook.
|
||||
*
|
||||
* This may be one of the following three values:
|
||||
* - visibile
|
||||
*
|
||||
* @return string Visible status
|
||||
*/
|
||||
public function getVisibility()
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visibility status of the workbook.
|
||||
*
|
||||
* Valid values are:
|
||||
* - 'visible' (self::VISIBILITY_VISIBLE):
|
||||
* Workbook window is visible
|
||||
* - 'hidden' (self::VISIBILITY_HIDDEN):
|
||||
* Workbook window is hidden, but can be shown by the user
|
||||
* via the user interface
|
||||
* - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
|
||||
* Workbook window is hidden and cannot be shown in the
|
||||
* user interface.
|
||||
*
|
||||
* @param string $visibility visibility status of the workbook
|
||||
*
|
||||
* @throws Exception if the given value is invalid
|
||||
*/
|
||||
public function setVisibility($visibility)
|
||||
{
|
||||
if ($visibility === null) {
|
||||
$visibility = self::VISIBILITY_VISIBLE;
|
||||
}
|
||||
|
||||
if (in_array($visibility, self::$workbookViewVisibilityValues)) {
|
||||
$this->visibility = $visibility;
|
||||
} else {
|
||||
throw new Exception('Invalid visibility value.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ratio between the workbook tabs bar and the horizontal scroll bar.
|
||||
* TabRatio is assumed to be out of 1000 of the horizontal window width.
|
||||
*
|
||||
* @return int Ratio between the workbook tabs bar and the horizontal scroll bar
|
||||
*/
|
||||
public function getTabRatio()
|
||||
{
|
||||
return $this->tabRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ratio between the workbook tabs bar and the horizontal scroll bar
|
||||
* TabRatio is assumed to be out of 1000 of the horizontal window width.
|
||||
*
|
||||
* @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
|
||||
*
|
||||
* @throws Exception if the given value is invalid
|
||||
*/
|
||||
public function setTabRatio($tabRatio)
|
||||
{
|
||||
if ($tabRatio >= 0 || $tabRatio <= 1000) {
|
||||
$this->tabRatio = (int) $tabRatio;
|
||||
} else {
|
||||
throw new Exception('Tab ratio must be between 0 and 1000.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +589,7 @@ class NumberFormat extends Supervisor
|
||||
}
|
||||
|
||||
// Convert any other escaped characters to quoted strings, e.g. (\T to "T")
|
||||
$format = preg_replace('/(\\\(.))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
|
||||
$format = preg_replace('/(\\\([^ ]))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
|
||||
|
||||
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
|
||||
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
|
||||
|
@ -333,6 +333,9 @@ class Style extends Supervisor
|
||||
}
|
||||
}
|
||||
|
||||
// restore initial cell selection range
|
||||
$this->getActiveSheet()->getStyle($pRange);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -841,7 +841,7 @@ class AutoFilter
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
if ($key == 'workSheet') {
|
||||
if ($key === 'workSheet') {
|
||||
// Detach from worksheet
|
||||
$this->{$key} = null;
|
||||
} else {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Worksheet;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\IComparable;
|
||||
|
||||
@ -98,6 +99,13 @@ class BaseDrawing implements IComparable
|
||||
*/
|
||||
protected $shadow;
|
||||
|
||||
/**
|
||||
* Image hyperlink.
|
||||
*
|
||||
* @var null|Hyperlink
|
||||
*/
|
||||
private $hyperlink;
|
||||
|
||||
/**
|
||||
* Create a new BaseDrawing.
|
||||
*/
|
||||
@ -501,11 +509,29 @@ class BaseDrawing implements IComparable
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
if ($key == 'worksheet') {
|
||||
$this->worksheet = null;
|
||||
} elseif (is_object($value)) {
|
||||
$this->$key = clone $value;
|
||||
} else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|Hyperlink $pHyperlink
|
||||
*/
|
||||
public function setHyperlink(Hyperlink $pHyperlink = null)
|
||||
{
|
||||
$this->hyperlink = $pHyperlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|Hyperlink
|
||||
*/
|
||||
public function getHyperlink()
|
||||
{
|
||||
return $this->hyperlink;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,13 @@ class Worksheet implements IComparable
|
||||
const SHEETSTATE_HIDDEN = 'hidden';
|
||||
const SHEETSTATE_VERYHIDDEN = 'veryHidden';
|
||||
|
||||
/**
|
||||
* Maximum 31 characters allowed for sheet title.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SHEET_TITLE_MAXIMUM_LENGTH = 31;
|
||||
|
||||
/**
|
||||
* Invalid characters in sheet title.
|
||||
*
|
||||
@ -434,9 +441,9 @@ class Worksheet implements IComparable
|
||||
throw new Exception('Invalid character found in sheet code name');
|
||||
}
|
||||
|
||||
// Maximum 31 characters allowed for sheet title
|
||||
if ($CharCount > 31) {
|
||||
throw new Exception('Maximum 31 characters allowed in sheet code name.');
|
||||
// Enforce maximum characters allowed for sheet title
|
||||
if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) {
|
||||
throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.');
|
||||
}
|
||||
|
||||
return $pValue;
|
||||
@ -458,9 +465,9 @@ class Worksheet implements IComparable
|
||||
throw new Exception('Invalid character found in sheet title');
|
||||
}
|
||||
|
||||
// Maximum 31 characters allowed for sheet title
|
||||
if (Shared\StringHelper::countCharacters($pValue) > 31) {
|
||||
throw new Exception('Maximum 31 characters allowed in sheet title.');
|
||||
// Enforce maximum characters allowed for sheet title
|
||||
if (Shared\StringHelper::countCharacters($pValue) > self::SHEET_TITLE_MAXIMUM_LENGTH) {
|
||||
throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.');
|
||||
}
|
||||
|
||||
return $pValue;
|
||||
@ -2956,13 +2963,14 @@ class Worksheet implements IComparable
|
||||
$newCollection = $this->cellCollection->cloneCellCollection($this);
|
||||
$this->cellCollection = $newCollection;
|
||||
} elseif ($key == 'drawingCollection') {
|
||||
$newCollection = new ArrayObject();
|
||||
foreach ($this->drawingCollection as $id => $item) {
|
||||
$currentCollection = $this->drawingCollection;
|
||||
$this->drawingCollection = new ArrayObject();
|
||||
foreach ($currentCollection as $item) {
|
||||
if (is_object($item)) {
|
||||
$newCollection[$id] = clone $this->drawingCollection[$id];
|
||||
$newDrawing = clone $item;
|
||||
$newDrawing->setWorksheet($this);
|
||||
}
|
||||
}
|
||||
$this->drawingCollection = $newCollection;
|
||||
} elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
|
||||
$newAutoFilter = clone $this->autoFilter;
|
||||
$this->autoFilter = $newAutoFilter;
|
||||
|
@ -827,7 +827,7 @@ class Worksheet extends BIFFwriter
|
||||
$formula = substr($formula, 1);
|
||||
} else {
|
||||
// Error handling
|
||||
$this->writeString($row, $col, 'Unrecognised character for formula');
|
||||
$this->writeString($row, $col, 'Unrecognised character for formula', 0);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -137,9 +137,9 @@ class Xlsx extends BaseWriter
|
||||
}
|
||||
|
||||
$hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable',
|
||||
'bordersHashTable', 'numFmtHashTable', 'drawingHashTable',
|
||||
'styleHashTable',
|
||||
];
|
||||
'bordersHashTable', 'numFmtHashTable', 'drawingHashTable',
|
||||
'styleHashTable',
|
||||
];
|
||||
|
||||
// Set HashTable variables
|
||||
foreach ($hashTablesArray as $tableName) {
|
||||
@ -290,12 +290,26 @@ class Xlsx extends BaseWriter
|
||||
}
|
||||
}
|
||||
|
||||
$chartRef1 = $chartRef2 = 0;
|
||||
$chartRef1 = 0;
|
||||
// Add worksheet relationships (drawings, ...)
|
||||
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
|
||||
// Add relationships
|
||||
$zip->addFromString('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts));
|
||||
|
||||
// Add unparsedLoadedData
|
||||
$sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
|
||||
$unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData();
|
||||
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) {
|
||||
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
|
||||
$zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']);
|
||||
}
|
||||
}
|
||||
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) {
|
||||
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
|
||||
$zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']);
|
||||
}
|
||||
}
|
||||
|
||||
$drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
|
||||
$drawingCount = count($drawings);
|
||||
if ($this->includeCharts) {
|
||||
@ -307,6 +321,9 @@ class Xlsx extends BaseWriter
|
||||
// Drawing relationships
|
||||
$zip->addFromString('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts));
|
||||
|
||||
// Drawings
|
||||
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
|
||||
} elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
|
||||
// Drawings
|
||||
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
|
||||
}
|
||||
@ -320,6 +337,13 @@ class Xlsx extends BaseWriter
|
||||
$zip->addFromString('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i)));
|
||||
}
|
||||
|
||||
// Add unparsed relationship parts
|
||||
if (isset($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) {
|
||||
foreach ($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) {
|
||||
$zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']);
|
||||
}
|
||||
}
|
||||
|
||||
// Add header/footer relationship parts
|
||||
if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
|
||||
// VML Drawings
|
||||
|
@ -57,7 +57,8 @@ class ContentTypes extends WriterPart
|
||||
// Yes : not standard content but "macroEnabled"
|
||||
$this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
|
||||
//... and define a new type for the VBA project
|
||||
$this->writeDefaultContentType($objWriter, 'bin', 'application/vnd.ms-office.vbaProject');
|
||||
// Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
|
||||
$this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
|
||||
if ($spreadsheet->hasMacrosCertificate()) {
|
||||
// signed macros ?
|
||||
// Yes : add needed information
|
||||
@ -88,14 +89,16 @@ class ContentTypes extends WriterPart
|
||||
$this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
|
||||
|
||||
// Add worksheet relationship content types
|
||||
$unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
|
||||
$chart = 1;
|
||||
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||
$drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
|
||||
$drawingCount = count($drawings);
|
||||
$chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
|
||||
$hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
|
||||
|
||||
// We need a drawing relationship for the worksheet if we have either drawings or charts
|
||||
if (($drawingCount > 0) || ($chartCount > 0)) {
|
||||
if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
|
||||
$this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
|
||||
}
|
||||
|
||||
@ -160,6 +163,20 @@ class ContentTypes extends WriterPart
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed defaults
|
||||
if (isset($unparsedLoadedData['default_content_types'])) {
|
||||
foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) {
|
||||
$this->writeDefaultContentType($objWriter, $extName, $contentType);
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed overrides
|
||||
if (isset($unparsedLoadedData['override_content_types'])) {
|
||||
foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) {
|
||||
$this->writeOverrideContentType($objWriter, $partName, $overrideType);
|
||||
}
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
// Return
|
||||
|
@ -43,7 +43,12 @@ class Drawing extends WriterPart
|
||||
$i = 1;
|
||||
$iterator = $pWorksheet->getDrawingCollection()->getIterator();
|
||||
while ($iterator->valid()) {
|
||||
$this->writeDrawing($objWriter, $iterator->current(), $i);
|
||||
/** @var BaseDrawing $pDrawing */
|
||||
$pDrawing = $iterator->current();
|
||||
$pRelationId = $i;
|
||||
$hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
|
||||
|
||||
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
|
||||
|
||||
$iterator->next();
|
||||
++$i;
|
||||
@ -59,6 +64,14 @@ class Drawing extends WriterPart
|
||||
}
|
||||
}
|
||||
|
||||
// unparsed AlternateContent
|
||||
$unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
|
||||
if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
|
||||
foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
|
||||
$objWriter->writeRaw($drawingAlternateContent);
|
||||
}
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
// Return
|
||||
@ -142,10 +155,11 @@ class Drawing extends WriterPart
|
||||
* @param XMLWriter $objWriter XML Writer
|
||||
* @param BaseDrawing $pDrawing
|
||||
* @param int $pRelationId
|
||||
* @param null|int $hlinkClickId
|
||||
*
|
||||
* @throws WriterException
|
||||
*/
|
||||
public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1)
|
||||
public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null)
|
||||
{
|
||||
if ($pRelationId >= 0) {
|
||||
// xdr:oneCellAnchor
|
||||
@ -179,6 +193,10 @@ class Drawing extends WriterPart
|
||||
$objWriter->writeAttribute('id', $pRelationId);
|
||||
$objWriter->writeAttribute('name', $pDrawing->getName());
|
||||
$objWriter->writeAttribute('descr', $pDrawing->getDescription());
|
||||
|
||||
//a:hlinkClick
|
||||
$this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
// xdr:cNvPicPr
|
||||
@ -482,4 +500,20 @@ class Drawing extends WriterPart
|
||||
|
||||
return $aDrawings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param XMLWriter $objWriter
|
||||
* @param null|int $hlinkClickId
|
||||
*/
|
||||
private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId)
|
||||
{
|
||||
if ($hlinkClickId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objWriter->startElement('a:hlinkClick');
|
||||
$objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
$objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
|
||||
$objWriter->endElement();
|
||||
}
|
||||
}
|
||||
|
@ -195,18 +195,31 @@ class Rels extends WriterPart
|
||||
|
||||
// Write drawing relationships?
|
||||
$d = 0;
|
||||
$drawingOriginalIds = [];
|
||||
$unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
|
||||
if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
|
||||
$drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
|
||||
}
|
||||
|
||||
if ($includeCharts) {
|
||||
$charts = $pWorksheet->getChartCollection();
|
||||
} else {
|
||||
$charts = [];
|
||||
}
|
||||
if (($pWorksheet->getDrawingCollection()->count() > 0) ||
|
||||
(count($charts) > 0)) {
|
||||
|
||||
if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
|
||||
$relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
|
||||
$rId = ++$d;
|
||||
|
||||
if (isset($drawingOriginalIds[$relPath])) {
|
||||
$rId = (int) (substr($drawingOriginalIds[$relPath], 3));
|
||||
}
|
||||
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
++$d,
|
||||
$rId,
|
||||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
|
||||
'../drawings/drawing' . $pWorksheetId . '.xml'
|
||||
$relPath
|
||||
);
|
||||
}
|
||||
|
||||
@ -255,11 +268,32 @@ class Rels extends WriterPart
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
|
||||
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
|
||||
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
return $objWriter->getData();
|
||||
}
|
||||
|
||||
private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type)
|
||||
{
|
||||
$unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
|
||||
if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
$rId,
|
||||
$type,
|
||||
$value['relFilePath']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write drawing relationships to XML format.
|
||||
*
|
||||
@ -295,12 +329,16 @@ class Rels extends WriterPart
|
||||
if ($iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
|
||||
|| $iterator->current() instanceof MemoryDrawing) {
|
||||
// Write relationship for image drawing
|
||||
/** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
|
||||
$drawing = $iterator->current();
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
$i,
|
||||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
|
||||
'../media/' . str_replace(' ', '', $iterator->current()->getIndexedFilename())
|
||||
'../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
|
||||
);
|
||||
|
||||
$i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
@ -398,4 +436,31 @@ class Rels extends WriterPart
|
||||
throw new WriterException('Invalid parameters passed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $objWriter
|
||||
* @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
|
||||
* @param $i
|
||||
*
|
||||
* @throws WriterException
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function writeDrawingHyperLink($objWriter, $drawing, $i)
|
||||
{
|
||||
if ($drawing->getHyperlink() === null) {
|
||||
return $i;
|
||||
}
|
||||
|
||||
++$i;
|
||||
$this->writeRelationship(
|
||||
$objWriter,
|
||||
$i,
|
||||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
|
||||
$drawing->getHyperlink()->getUrl(),
|
||||
$drawing->getHyperlink()->getTypeHyperlink()
|
||||
);
|
||||
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
@ -117,14 +117,14 @@ class Workbook extends WriterPart
|
||||
$objWriter->startElement('workbookView');
|
||||
|
||||
$objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
|
||||
$objWriter->writeAttribute('autoFilterDateGrouping', '1');
|
||||
$objWriter->writeAttribute('firstSheet', '0');
|
||||
$objWriter->writeAttribute('minimized', '0');
|
||||
$objWriter->writeAttribute('showHorizontalScroll', '1');
|
||||
$objWriter->writeAttribute('showSheetTabs', '1');
|
||||
$objWriter->writeAttribute('showVerticalScroll', '1');
|
||||
$objWriter->writeAttribute('tabRatio', '600');
|
||||
$objWriter->writeAttribute('visibility', 'visible');
|
||||
$objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex());
|
||||
$objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio());
|
||||
$objWriter->writeAttribute('visibility', $spreadsheet->getVisibility());
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
@ -175,6 +175,7 @@ class Workbook extends WriterPart
|
||||
// fullCalcOnLoad isn't needed if we've recalculating for the save
|
||||
$objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0);
|
||||
$objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1);
|
||||
$objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1);
|
||||
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
@ -51,6 +51,12 @@ class Worksheet extends WriterPart
|
||||
$objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
$objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
|
||||
$objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
|
||||
$objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
|
||||
$objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
|
||||
$objWriter->writeAttribute('mc:Ignorable', 'x14ac');
|
||||
$objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
|
||||
|
||||
// sheetPr
|
||||
$this->writeSheetPr($objWriter, $pSheet);
|
||||
|
||||
@ -114,6 +120,9 @@ class Worksheet extends WriterPart
|
||||
// LegacyDrawingHF
|
||||
$this->writeLegacyDrawingHF($objWriter, $pSheet);
|
||||
|
||||
// AlternateContent
|
||||
$this->writeAlternateContent($objWriter, $pSheet);
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
// Return
|
||||
@ -237,6 +246,7 @@ class Worksheet extends WriterPart
|
||||
}
|
||||
|
||||
$activeCell = $pSheet->getActiveCell();
|
||||
$sqref = $pSheet->getSelectedCells();
|
||||
|
||||
// Pane
|
||||
$pane = '';
|
||||
@ -248,6 +258,7 @@ class Worksheet extends WriterPart
|
||||
|
||||
$topLeftCell = $pSheet->getTopLeftCell();
|
||||
$activeCell = $topLeftCell;
|
||||
$sqref = $topLeftCell;
|
||||
|
||||
// pane
|
||||
$pane = 'topRight';
|
||||
@ -283,7 +294,7 @@ class Worksheet extends WriterPart
|
||||
$objWriter->writeAttribute('pane', $pane);
|
||||
}
|
||||
$objWriter->writeAttribute('activeCell', $activeCell);
|
||||
$objWriter->writeAttribute('sqref', $activeCell);
|
||||
$objWriter->writeAttribute('sqref', $sqref);
|
||||
$objWriter->endElement();
|
||||
|
||||
$objWriter->endElement();
|
||||
@ -843,6 +854,11 @@ class Worksheet extends WriterPart
|
||||
$objWriter->writeAttribute('useFirstPageNumber', '1');
|
||||
}
|
||||
|
||||
$getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
|
||||
if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
|
||||
$objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
@ -1142,16 +1158,27 @@ class Worksheet extends WriterPart
|
||||
* @param PhpspreadsheetWorksheet $pSheet Worksheet
|
||||
* @param bool $includeCharts Flag indicating if we should include drawing details for charts
|
||||
*/
|
||||
private function writeDrawings(XMLWriter $objWriter = null, PhpspreadsheetWorksheet $pSheet = null, $includeCharts = false)
|
||||
private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false)
|
||||
{
|
||||
$unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
|
||||
$hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
|
||||
$chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
|
||||
// If sheet contains drawings, add the relationships
|
||||
if (($pSheet->getDrawingCollection()->count() > 0) ||
|
||||
($chartCount > 0)) {
|
||||
$objWriter->startElement('drawing');
|
||||
$objWriter->writeAttribute('r:id', 'rId1');
|
||||
$objWriter->endElement();
|
||||
if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If sheet contains drawings, add the relationships
|
||||
$objWriter->startElement('drawing');
|
||||
|
||||
$rId = 'rId1';
|
||||
if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
|
||||
$drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
|
||||
// take first. In future can be overriten
|
||||
$rId = reset($drawingOriginalIds);
|
||||
}
|
||||
|
||||
$objWriter->writeAttribute('r:id', $rId);
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1185,4 +1212,15 @@ class Worksheet extends WriterPart
|
||||
$objWriter->endElement();
|
||||
}
|
||||
}
|
||||
|
||||
private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
|
||||
{
|
||||
if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
|
||||
$objWriter->writeRaw($alternateContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user