Updates to 3rd party libraries

Add Dockerfile and specific docker-php.ini
This commit is contained in:
2018-08-28 21:27:13 -04:00
parent 9edd6c1c35
commit d52454d1bb
511 changed files with 45960 additions and 2739 deletions

View File

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

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

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

View File

@ -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())) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -89,6 +89,14 @@ class Hyperlink
return strpos($this->url, 'sheet://') !== false;
}
/**
* @return string
*/
public function getTypeHyperlink()
{
return $this->isInternal() ? '' : 'External';
}
/**
* Get hash code.
*

View File

@ -217,7 +217,7 @@ class DataSeries
/**
* Get Plot Order.
*
* @return string
* @return int[]
*/
public function getPlotOrder()
{

View File

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

View File

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

View File

@ -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',

View File

@ -554,6 +554,7 @@ class Html extends BaseReader
$row = 0;
$column = 'A';
$content = '';
$this->rowspan = [];
$this->processDomElement($dom, $spreadsheet->getActiveSheet(), $row, $column, $content);
// Return

View File

@ -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
*/

View File

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

View File

@ -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) {

View File

@ -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
*/

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -333,6 +333,9 @@ class Style extends Supervisor
}
}
// restore initial cell selection range
$this->getActiveSheet()->getStyle($pRange);
return $this;
}

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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