package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -8,6 +8,7 @@ class CodePage
{
public const DEFAULT_CODE_PAGE = 'CP1252';
/** @var array */
private static $pageArray = [
0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program
367 => 'ASCII', // ASCII
@@ -56,7 +57,7 @@ class CodePage
10010 => 'MACROMANIA', // Macintosh Romania
10017 => 'MACUKRAINE', // Macintosh Ukraine
10021 => 'MACTHAI', // Macintosh Thai
10029 => 'MACCENTRALEUROPE', // Macintosh Central Europe
10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe
10079 => 'MACICELAND', // Macintosh Icelandic
10081 => 'MACTURKISH', // Macintosh Turkish
10082 => 'MACCROATIAN', // Macintosh Croatian
@@ -65,6 +66,7 @@ class CodePage
//32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3)
65000 => 'UTF-7', // Unicode (UTF-7)
65001 => 'UTF-8', // Unicode (UTF-8)
99999 => ['unsupported'], // Unicode (UTF-8)
];
public static function validate(string $codePage): bool
@@ -83,7 +85,20 @@ class CodePage
public static function numberToName(int $codePage): string
{
if (array_key_exists($codePage, self::$pageArray)) {
return self::$pageArray[$codePage];
$value = self::$pageArray[$codePage];
if (is_array($value)) {
foreach ($value as $encoding) {
if (@iconv('UTF-8', $encoding, ' ') !== false) {
self::$pageArray[$codePage] = $encoding;
return $encoding;
}
}
throw new PhpSpreadsheetException("Code page $codePage not implemented on this system.");
} else {
return $value;
}
}
if ($codePage == 720 || $codePage == 32769) {
throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic

View File

@@ -2,12 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Date
@@ -65,17 +68,17 @@ class Date
/**
* Set the Excel calendar (Windows 1900 or Mac 1904).
*
* @param int $baseDate Excel base date (1900 or 1904)
* @param int $baseYear Excel base date (1900 or 1904)
*
* @return bool Success or failure
*/
public static function setExcelCalendar($baseDate)
public static function setExcelCalendar($baseYear)
{
if (
($baseDate == self::CALENDAR_WINDOWS_1900) ||
($baseDate == self::CALENDAR_MAC_1904)
($baseYear == self::CALENDAR_WINDOWS_1900) ||
($baseYear == self::CALENDAR_MAC_1904)
) {
self::$excelCalendar = $baseDate;
self::$excelCalendar = $baseYear;
return true;
}
@@ -96,7 +99,7 @@ class Date
/**
* Set the Default timezone to use for dates.
*
* @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
* @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
*
* @return bool Success or failure
*/
@@ -114,29 +117,39 @@ class Date
}
/**
* Return the Default timezone being used for dates.
*
* @return DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object
* Return the Default timezone, or UTC if default not set.
*/
public static function getDefaultTimezone()
public static function getDefaultTimezone(): DateTimeZone
{
if (self::$defaultTimeZone === null) {
self::$defaultTimeZone = new DateTimeZone('UTC');
}
return self::$defaultTimeZone ?? new DateTimeZone('UTC');
}
/**
* Return the Default timezone, or local timezone if default is not set.
*/
public static function getDefaultOrLocalTimezone(): DateTimeZone
{
return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get());
}
/**
* Return the Default timezone even if null.
*/
public static function getDefaultTimezoneOrNull(): ?DateTimeZone
{
return self::$defaultTimeZone;
}
/**
* Validate a timezone.
*
* @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
* @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
*
* @return DateTimeZone The timezone as a timezone object
* @return ?DateTimeZone The timezone as a timezone object
*/
private static function validateTimeZone($timeZone)
{
if ($timeZone instanceof DateTimeZone) {
if ($timeZone instanceof DateTimeZone || $timeZone === null) {
return $timeZone;
}
if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
@@ -146,34 +159,64 @@ class Date
throw new PhpSpreadsheetException('Invalid timezone');
}
/**
* @param mixed $value
*
* @return float|int
*/
public static function convertIsoDate($value)
{
if (!is_string($value)) {
throw new Exception('Non-string value supplied for Iso Date conversion');
}
$date = new DateTime($value);
$dateErrors = DateTime::getLastErrors();
if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
$newValue = SharedDate::PHPToExcel($date);
if ($newValue === false) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) {
$newValue = fmod($newValue, 1.0);
}
return $newValue;
}
/**
* Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UST) unless you absolutely need a conversion
* Use the default (UTC) unless you absolutely need a conversion
*
* @return \DateTime PHP date/time object
* @return DateTime PHP date/time object
*/
public static function excelToDateTimeObject($excelTimestamp, $timeZone = null)
{
$timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
if ($excelTimestamp < 1.0) {
if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) {
// Unix timestamp base date
$baseDate = new \DateTime('1970-01-01', $timeZone);
$baseDate = new DateTime('1970-01-01', $timeZone);
} else {
// MS Excel calendar base dates
if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
// Allow adjustment for 1900 Leap Year in MS Excel
$baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone);
$baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone);
} else {
$baseDate = new \DateTime('1904-01-01', $timeZone);
$baseDate = new DateTime('1904-01-01', $timeZone);
}
}
} else {
$baseDate = new \DateTime('1899-12-30', $timeZone);
$baseDate = new DateTime('1899-12-30', $timeZone);
}
$days = floor($excelTimestamp);
@@ -195,11 +238,13 @@ class Date
/**
* Convert a MS serialized datetime value from Excel to a unix timestamp.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UST) unless you absolutely need a conversion
* Use the default (UTC) unless you absolutely need a conversion
*
* @return int Unix timetamp for this date/time
*/
@@ -212,9 +257,10 @@ class Date
/**
* Convert a date from PHP to an MS Excel serialized date/time value.
*
* @param mixed $dateValue Unix Timestamp or PHP DateTime object or a string
* @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged;
* not Y2038-safe on a 32-bit system, and no timezone info
*
* @return bool|float Excel date/time value
* @return false|float Excel date/time value
* or boolean FALSE on failure
*/
public static function PHPToExcel($dateValue)
@@ -251,18 +297,20 @@ class Date
/**
* Convert a Unix timestamp to an MS Excel serialized date/time value.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param int $dateValue Unix Timestamp
* @param float|int|string $unixTimestamp Unix Timestamp
*
* @return float MS Excel serialized date/time value
* @return false|float MS Excel serialized date/time value
*/
public static function timestampToExcel($dateValue)
public static function timestampToExcel($unixTimestamp)
{
if (!is_numeric($dateValue)) {
if (!is_numeric($unixTimestamp)) {
return false;
}
return self::dateTimeToExcel(new \DateTime('@' . $dateValue));
return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp));
}
/**
@@ -303,8 +351,8 @@ class Date
}
// Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
$century = substr($year, 0, 2);
$decade = substr($year, 2, 2);
$century = (int) substr((string) $year, 0, 2);
$decade = (int) substr((string) $year, 2, 2);
$excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
$excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
@@ -315,16 +363,30 @@ class Date
/**
* Is a given cell a date/time?
*
* @param mixed $value
*
* @return bool
*/
public static function isDateTime(Cell $pCell)
public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true)
{
return is_numeric($pCell->getCalculatedValue()) &&
self::isDateTimeFormat(
$pCell->getWorksheet()->getStyle(
$pCell->getCoordinate()
)->getNumberFormat()
);
$result = false;
$worksheet = $cell->getWorksheetOrNull();
$spreadsheet = ($worksheet === null) ? null : $worksheet->getParent();
if ($worksheet !== null && $spreadsheet !== null) {
$index = $spreadsheet->getActiveSheetIndex();
$selected = $worksheet->getSelectedCells();
$result = is_numeric($value ?? $cell->getCalculatedValue()) &&
self::isDateTimeFormat(
$worksheet->getStyle(
$cell->getCoordinate()
)->getNumberFormat(),
$dateWithoutTimeOkay
);
$worksheet->setSelectedCells($selected);
$spreadsheet->setActiveSheetIndex($index);
}
return $result;
}
/**
@@ -332,79 +394,59 @@ class Date
*
* @return bool
*/
public static function isDateTimeFormat(NumberFormat $pFormat)
public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true)
{
return self::isDateTimeFormatCode($pFormat->getFormatCode());
return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay);
}
private static $possibleDateFormatCharacters = 'eymdHs';
private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs';
private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity
/**
* Is a given number format code a date/time?
*
* @param string $pFormatCode
* @param string $excelFormatCode
*
* @return bool
*/
public static function isDateTimeFormatCode($pFormatCode)
public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true)
{
if (strtolower($pFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
// "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
return false;
}
if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) {
if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) {
// Scientific format
return false;
}
// Switch on formatcode
switch ($pFormatCode) {
// Explicitly defined date formats
case NumberFormat::FORMAT_DATE_YYYYMMDD:
case NumberFormat::FORMAT_DATE_YYYYMMDD2:
case NumberFormat::FORMAT_DATE_DDMMYYYY:
case NumberFormat::FORMAT_DATE_DMYSLASH:
case NumberFormat::FORMAT_DATE_DMYMINUS:
case NumberFormat::FORMAT_DATE_DMMINUS:
case NumberFormat::FORMAT_DATE_MYMINUS:
case NumberFormat::FORMAT_DATE_DATETIME:
case NumberFormat::FORMAT_DATE_TIME1:
case NumberFormat::FORMAT_DATE_TIME2:
case NumberFormat::FORMAT_DATE_TIME3:
case NumberFormat::FORMAT_DATE_TIME4:
case NumberFormat::FORMAT_DATE_TIME5:
case NumberFormat::FORMAT_DATE_TIME6:
case NumberFormat::FORMAT_DATE_TIME7:
case NumberFormat::FORMAT_DATE_TIME8:
case NumberFormat::FORMAT_DATE_YYYYMMDDSLASH:
case NumberFormat::FORMAT_DATE_XLSX14:
case NumberFormat::FORMAT_DATE_XLSX15:
case NumberFormat::FORMAT_DATE_XLSX16:
case NumberFormat::FORMAT_DATE_XLSX17:
case NumberFormat::FORMAT_DATE_XLSX22:
return true;
if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) {
return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY);
}
// Typically number, currency or accounting (or occasionally fraction) formats
if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) {
if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) {
return false;
}
// Some "special formats" provided in German Excel versions were detected as date time value,
// so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany).
if (\strpos($pFormatCode, '-00000') !== false) {
if (\strpos($excelFormatCode, '-00000') !== false) {
return false;
}
$possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS;
// Try checking for any of the date formatting characters that don't appear within square braces
if (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $pFormatCode)) {
if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) {
// We might also have a format mask containing quoted strings...
// we don't want to test for any of our characters within the quoted blocks
if (strpos($pFormatCode, '"') !== false) {
if (strpos($excelFormatCode, '"') !== false) {
$segMatcher = false;
foreach (explode('"', $pFormatCode) as $subVal) {
foreach (explode('"', $excelFormatCode) as $subVal) {
// Only test in alternate array entries (the non-quoted blocks)
$segMatcher = $segMatcher === false;
if (
($segMatcher = !$segMatcher) &&
(preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $subVal))
$segMatcher &&
(preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal))
) {
return true;
}
@@ -436,15 +478,15 @@ class Date
return false;
}
$dateValueNew = DateTime::DATEVALUE($dateValue);
$dateValueNew = DateTimeExcel\DateValue::fromString($dateValue);
if ($dateValueNew === Functions::VALUE()) {
if (!is_float($dateValueNew)) {
return false;
}
if (strpos($dateValue, ':') !== false) {
$timeValue = DateTime::TIMEVALUE($dateValue);
if ($timeValue === Functions::VALUE()) {
$timeValue = DateTimeExcel\TimeValue::fromString($dateValue);
if (!is_float($timeValue)) {
return false;
}
$dateValueNew += $timeValue;
@@ -456,21 +498,21 @@ class Date
/**
* Converts a month name (either a long or a short name) to a month number.
*
* @param string $month Month name or abbreviation
* @param string $monthName Month name or abbreviation
*
* @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
*/
public static function monthStringToNumber($month)
public static function monthStringToNumber($monthName)
{
$monthIndex = 1;
foreach (self::$monthNames as $shortMonthName => $longMonthName) {
if (($month === $longMonthName) || ($month === $shortMonthName)) {
if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) {
return $monthIndex;
}
++$monthIndex;
}
return $month;
return $monthName;
}
/**
@@ -489,4 +531,19 @@ class Date
return $day;
}
public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime
{
$dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime();
$dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone());
return $dtobj;
}
public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string
{
$dtobj = self::dateTimeFromTimestamp($date, $timeZone);
return $dtobj->format($format);
}
}

View File

@@ -3,32 +3,35 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use GdImage;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use SimpleXMLElement;
class Drawing
{
/**
* Convert pixels to EMU.
*
* @param int $pValue Value in pixels
* @param int $pixelValue Value in pixels
*
* @return int Value in EMU
*/
public static function pixelsToEMU($pValue)
public static function pixelsToEMU($pixelValue)
{
return round($pValue * 9525);
return $pixelValue * 9525;
}
/**
* Convert EMU to pixels.
*
* @param int $pValue Value in EMU
* @param int|SimpleXMLElement $emuValue Value in EMU
*
* @return int Value in pixels
*/
public static function EMUToPixels($pValue)
public static function EMUToPixels($emuValue)
{
if ($pValue != 0) {
return round($pValue / 9525);
$emuValue = (int) $emuValue;
if ($emuValue != 0) {
return (int) round($emuValue / 9525);
}
return 0;
@@ -39,50 +42,51 @@ class Drawing
* By inspection of a real Excel file using Calibri 11, one finds 1000px ~ 142.85546875
* This gives a conversion factor of 7. Also, we assume that pixels and font size are proportional.
*
* @param int $pValue Value in pixels
* @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook
* @param int $pixelValue Value in pixels
*
* @return int Value in cell dimension
* @return float|int Value in cell dimension
*/
public static function pixelsToCellDimension($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont)
public static function pixelsToCellDimension($pixelValue, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont)
{
// Font name and size
$name = $pDefaultFont->getName();
$size = $pDefaultFont->getSize();
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::$defaultColumnWidths[$name][$size])) {
// Exact width can be determined
$colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['width'] / Font::$defaultColumnWidths[$name][$size]['px'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$colWidth = $pValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size;
return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width']
/ Font::$defaultColumnWidths[$name][$size]['px'];
}
return $colWidth;
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width']
/ Font::$defaultColumnWidths['Calibri'][11]['px'] / $size;
}
/**
* Convert column width from (intrinsic) Excel units to pixels.
*
* @param float $pValue Value in cell dimension
* @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook
* @param float $cellWidth Value in cell dimension
* @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook
*
* @return int Value in pixels
*/
public static function cellDimensionToPixels($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont)
public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont)
{
// Font name and size
$name = $pDefaultFont->getName();
$size = $pDefaultFont->getSize();
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::$defaultColumnWidths[$name][$size])) {
// Exact width can be determined
$colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['px'] / Font::$defaultColumnWidths[$name][$size]['width'];
$colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px']
/ Font::$defaultColumnWidths[$name][$size]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$colWidth = $pValue * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11;
$colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px']
/ Font::$defaultColumnWidths['Calibri'][11]['width'] / 11;
}
// Round pixels to closest integer
@@ -94,26 +98,26 @@ class Drawing
/**
* Convert pixels to points.
*
* @param int $pValue Value in pixels
* @param int $pixelValue Value in pixels
*
* @return float Value in points
*/
public static function pixelsToPoints($pValue)
public static function pixelsToPoints($pixelValue)
{
return $pValue * 0.75;
return $pixelValue * 0.75;
}
/**
* Convert points to pixels.
*
* @param int $pValue Value in points
* @param int $pointValue Value in points
*
* @return int Value in pixels
*/
public static function pointsToPixels($pValue)
public static function pointsToPixels($pointValue)
{
if ($pValue != 0) {
return (int) ceil($pValue / 0.75);
if ($pointValue != 0) {
return (int) ceil($pointValue / 0.75);
}
return 0;
@@ -122,26 +126,27 @@ class Drawing
/**
* Convert degrees to angle.
*
* @param int $pValue Degrees
* @param int $degrees Degrees
*
* @return int Angle
*/
public static function degreesToAngle($pValue)
public static function degreesToAngle($degrees)
{
return (int) round($pValue * 60000);
return (int) round($degrees * 60000);
}
/**
* Convert angle to degrees.
*
* @param int $pValue Angle
* @param int|SimpleXMLElement $angle Angle
*
* @return int Degrees
*/
public static function angleToDegrees($pValue)
public static function angleToDegrees($angle)
{
if ($pValue != 0) {
return round($pValue / 60000);
$angle = (int) $angle;
if ($angle != 0) {
return (int) round($angle / 60000);
}
return 0;
@@ -152,100 +157,21 @@ class Drawing
*
* @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
*
* @param string $p_sFile Path to Windows DIB (BMP) image
* @param string $bmpFilename Path to Windows DIB (BMP) image
*
* @return GdImage|resource
*
* @deprecated 1.26 use Php function imagecreatefrombmp instead
*
* @codeCoverageIgnore
*/
public static function imagecreatefrombmp($p_sFile)
public static function imagecreatefrombmp($bmpFilename)
{
// Load the image into a string
$file = fopen($p_sFile, 'rb');
$read = fread($file, 10);
while (!feof($file) && ($read != '')) {
$read .= fread($file, 1024);
$retVal = @imagecreatefrombmp($bmpFilename);
if ($retVal === false) {
throw new ReaderException("Unable to create image from $bmpFilename");
}
$temp = unpack('H*', $read);
$hex = $temp[1];
$header = substr($hex, 0, 108);
// Process the header
// Structure: http://www.fastgraph.com/help/bmp_header_format.html
if (substr($header, 0, 4) == '424d') {
// Cut it in parts of 2 bytes
$header_parts = str_split($header, 2);
// Get the width 4 bytes
$width = hexdec($header_parts[19] . $header_parts[18]);
// Get the height 4 bytes
$height = hexdec($header_parts[23] . $header_parts[22]);
// Unset the header params
unset($header_parts);
}
// Define starting X and Y
$x = 0;
$y = 1;
// Create newimage
$image = imagecreatetruecolor($width, $height);
// Grab the body from the image
$body = substr($hex, 108);
// Calculate if padding at the end-line is needed
// Divided by two to keep overview.
// 1 byte = 2 HEX-chars
$body_size = (strlen($body) / 2);
$header_size = ($width * $height);
// Use end-line padding? Only when needed
$usePadding = ($body_size > ($header_size * 3) + 4);
// Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
// Calculate the next DWORD-position in the body
for ($i = 0; $i < $body_size; $i += 3) {
// Calculate line-ending and padding
if ($x >= $width) {
// If padding needed, ignore image-padding
// Shift i to the ending of the current 32-bit-block
if ($usePadding) {
$i += $width % 4;
}
// Reset horizontal position
$x = 0;
// Raise the height-position (bottom-up)
++$y;
// Reached the image-height? Break the for-loop
if ($y > $height) {
break;
}
}
// Calculation of the RGB-pixel (defined as BGR in image-data)
// Define $i_pos as absolute position in the body
$i_pos = $i * 2;
$r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
$g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
$b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
// Calculate and draw the pixel
$color = imagecolorallocate($image, $r, $g, $b);
imagesetpixel($image, $x, $height - $y, $color);
// Raise the horizontal position
++$x;
}
// Unset the body / free the memory
unset($body);
// Return image-object
return $image;
return $retVal;
}
}

View File

@@ -7,7 +7,7 @@ class SpgrContainer
/**
* Parent Shape Group Container.
*
* @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
* @var null|SpgrContainer
*/
private $parent;
@@ -20,20 +20,16 @@ class SpgrContainer
/**
* Set parent Shape Group Container.
*
* @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer $parent
*/
public function setParent($parent): void
public function setParent(?self $parent): void
{
$this->parent = $parent;
}
/**
* Get the parent Shape Group Container if any.
*
* @return null|\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
*/
public function getParent()
public function getParent(): ?self
{
return $this->parent;
}

View File

@@ -166,10 +166,10 @@ class DggContainer
/**
* Set identifier clusters. [<drawingId> => <max shape id>, ...].
*
* @param array $pValue
* @param array $IDCLs
*/
public function setIDCLs($pValue): void
public function setIDCLs($IDCLs): void
{
$this->IDCLs = $pValue;
$this->IDCLs = $IDCLs;
}
}

View File

@@ -7,16 +7,14 @@ class BstoreContainer
/**
* BLIP Store Entries. Each of them holds one BLIP (Big Large Image or Picture).
*
* @var array
* @var BstoreContainer\BSE[]
*/
private $BSECollection = [];
/**
* Add a BLIP Store Entry.
*
* @param BstoreContainer\BSE $BSE
*/
public function addBSE($BSE): void
public function addBSE(BstoreContainer\BSE $BSE): void
{
$this->BSECollection[] = $BSE;
$BSE->setParent($this);

View File

@@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
class BSE
{
const BLIPTYPE_ERROR = 0x00;
@@ -18,7 +20,7 @@ class BSE
/**
* The parent BLIP Store Entry Container.
*
* @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer
* @var BstoreContainer
*/
private $parent;
@@ -38,10 +40,8 @@ class BSE
/**
* Set parent BLIP Store Entry Container.
*
* @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer $parent
*/
public function setParent($parent): void
public function setParent(BstoreContainer $parent): void
{
$this->parent = $parent;
}
@@ -58,10 +58,8 @@ class BSE
/**
* Set the BLIP.
*
* @param BSE\Blip $blip
*/
public function setBlip($blip): void
public function setBlip(BSE\Blip $blip): void
{
$this->blip = $blip;
$blip->setParent($this);

View File

@@ -2,12 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
class Blip
{
/**
* The parent BSE.
*
* @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE
* @var BSE
*/
private $parent;
@@ -40,20 +42,16 @@ class Blip
/**
* Set parent BSE.
*
* @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
*/
public function setParent($parent): void
public function setParent(BSE $parent): void
{
$this->parent = $parent;
}
/**
* Get parent BSE.
*
* @return \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
*/
public function getParent()
public function getParent(): BSE
{
return $this->parent;
}

View File

@@ -2,7 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use InvalidArgumentException;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use ZipArchive;
class File
@@ -16,75 +17,81 @@ class File
/**
* Set the flag indicating whether the File Upload Temp directory should be used for temporary files.
*
* @param bool $useUploadTempDir Use File Upload Temporary directory (true or false)
*/
public static function setUseUploadTempDirectory($useUploadTempDir): void
public static function setUseUploadTempDirectory(bool $useUploadTempDir): void
{
self::$useUploadTempDirectory = (bool) $useUploadTempDir;
}
/**
* Get the flag indicating whether the File Upload Temp directory should be used for temporary files.
*
* @return bool Use File Upload Temporary directory (true or false)
*/
public static function getUseUploadTempDirectory()
public static function getUseUploadTempDirectory(): bool
{
return self::$useUploadTempDirectory;
}
// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
// Section 4.3.7
// Looks like there might be endian-ness considerations
private const ZIP_FIRST_4 = [
"\x50\x4b\x03\x04", // what it looks like on my system
"\x04\x03\x4b\x50", // what it says in documentation
];
private static function validateZipFirst4(string $zipFile): bool
{
$contents = @file_get_contents($zipFile, false, null, 0, 4);
return in_array($contents, self::ZIP_FIRST_4, true);
}
/**
* Verify if a file exists.
*
* @param string $pFilename Filename
*
* @return bool
*/
public static function fileExists($pFilename)
public static function fileExists(string $filename): bool
{
// Sick construction, but it seems that
// file_exists returns strange values when
// doing the original file_exists on ZIP archives...
if (strtolower(substr($pFilename, 0, 3)) == 'zip') {
if (strtolower(substr($filename, 0, 6)) == 'zip://') {
// Open ZIP file and verify if the file exists
$zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6);
$archiveFile = substr($pFilename, strpos($pFilename, '#') + 1);
$zipFile = substr($filename, 6, strrpos($filename, '#') - 6);
$archiveFile = substr($filename, strrpos($filename, '#') + 1);
$zip = new ZipArchive();
if ($zip->open($zipFile) === true) {
$returnValue = ($zip->getFromName($archiveFile) !== false);
$zip->close();
if (self::validateZipFirst4($zipFile)) {
$zip = new ZipArchive();
$res = $zip->open($zipFile);
if ($res === true) {
$returnValue = ($zip->getFromName($archiveFile) !== false);
$zip->close();
return $returnValue;
return $returnValue;
}
}
return false;
}
return file_exists($pFilename);
return file_exists($filename);
}
/**
* Returns canonicalized absolute pathname, also for ZIP archives.
*
* @param string $pFilename
*
* @return string
*/
public static function realpath($pFilename)
public static function realpath(string $filename): string
{
// Returnvalue
$returnValue = '';
// Try using realpath()
if (file_exists($pFilename)) {
$returnValue = realpath($pFilename);
if (file_exists($filename)) {
$returnValue = realpath($filename) ?: '';
}
// Found something?
if ($returnValue == '' || ($returnValue === null)) {
$pathArray = explode('/', $pFilename);
if ($returnValue === '') {
$pathArray = explode('/', $filename);
while (in_array('..', $pathArray) && $pathArray[0] != '..') {
$iMax = count($pathArray);
for ($i = 0; $i < $iMax; ++$i) {
@@ -104,39 +111,75 @@ class File
/**
* Get the systems temporary directory.
*
* @return string
*/
public static function sysGetTempDir()
public static function sysGetTempDir(): string
{
$path = sys_get_temp_dir();
if (self::$useUploadTempDirectory) {
// use upload-directory when defined to allow running on environments having very restricted
// open_basedir configs
if (ini_get('upload_tmp_dir') !== false) {
if ($temp = ini_get('upload_tmp_dir')) {
if (file_exists($temp)) {
return realpath($temp);
$path = $temp;
}
}
}
}
return realpath(sys_get_temp_dir());
return realpath($path) ?: '';
}
public static function temporaryFilename(): string
{
$filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet');
if ($filename === false) {
throw new Exception('Could not create temporary file');
}
return $filename;
}
/**
* Assert that given path is an existing file and is readable, otherwise throw exception.
*
* @param string $filename
*/
public static function assertFile($filename): void
public static function assertFile(string $filename, string $zipMember = ''): void
{
if (!is_file($filename)) {
throw new InvalidArgumentException('File "' . $filename . '" does not exist.');
throw new ReaderException('File "' . $filename . '" does not exist.');
}
if (!is_readable($filename)) {
throw new InvalidArgumentException('Could not open "' . $filename . '" for reading.');
throw new ReaderException('Could not open "' . $filename . '" for reading.');
}
if ($zipMember !== '') {
$zipfile = "zip://$filename#$zipMember";
if (!self::fileExists($zipfile)) {
throw new ReaderException("Could not find zip member $zipfile");
}
}
}
/**
* Same as assertFile, except return true/false and don't throw Exception.
*/
public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
{
if (!is_file($filename)) {
return false;
}
if (!is_readable($filename)) {
return false;
}
if ($zipMember === null) {
return true;
}
// validate zip, but don't check specific member
if ($zipMember === '') {
return self::validateZipFirst4($filename);
}
return self::fileExists("zip://$filename#$zipMember");
}
}

View File

@@ -4,6 +4,8 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Font as FontStyle;
class Font
{
@@ -11,7 +13,7 @@ class Font
const AUTOSIZE_METHOD_APPROX = 'approx';
const AUTOSIZE_METHOD_EXACT = 'exact';
private static $autoSizeMethods = [
private const AUTOSIZE_METHODS = [
self::AUTOSIZE_METHOD_APPROX,
self::AUTOSIZE_METHOD_EXACT,
];
@@ -44,10 +46,10 @@ class Font
const ARIAL_ITALIC = 'ariali.ttf';
const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
const CALIBRI = 'CALIBRI.TTF';
const CALIBRI_BOLD = 'CALIBRIB.TTF';
const CALIBRI_ITALIC = 'CALIBRII.TTF';
const CALIBRI_BOLD_ITALIC = 'CALIBRIZ.TTF';
const CALIBRI = 'calibri.ttf';
const CALIBRI_BOLD = 'calibrib.ttf';
const CALIBRI_ITALIC = 'calibrii.ttf';
const CALIBRI_BOLD_ITALIC = 'calibriz.ttf';
const COMIC_SANS_MS = 'comic.ttf';
const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
@@ -99,6 +101,105 @@ class Font
const VERDANA_ITALIC = 'verdanai.ttf';
const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
const FONT_FILE_NAMES = [
'Arial' => [
'x' => self::ARIAL,
'xb' => self::ARIAL_BOLD,
'xi' => self::ARIAL_ITALIC,
'xbi' => self::ARIAL_BOLD_ITALIC,
],
'Calibri' => [
'x' => self::CALIBRI,
'xb' => self::CALIBRI_BOLD,
'xi' => self::CALIBRI_ITALIC,
'xbi' => self::CALIBRI_BOLD_ITALIC,
],
'Comic Sans MS' => [
'x' => self::COMIC_SANS_MS,
'xb' => self::COMIC_SANS_MS_BOLD,
'xi' => self::COMIC_SANS_MS,
'xbi' => self::COMIC_SANS_MS_BOLD,
],
'Courier New' => [
'x' => self::COURIER_NEW,
'xb' => self::COURIER_NEW_BOLD,
'xi' => self::COURIER_NEW_ITALIC,
'xbi' => self::COURIER_NEW_BOLD_ITALIC,
],
'Georgia' => [
'x' => self::GEORGIA,
'xb' => self::GEORGIA_BOLD,
'xi' => self::GEORGIA_ITALIC,
'xbi' => self::GEORGIA_BOLD_ITALIC,
],
'Impact' => [
'x' => self::IMPACT,
'xb' => self::IMPACT,
'xi' => self::IMPACT,
'xbi' => self::IMPACT,
],
'Liberation Sans' => [
'x' => self::LIBERATION_SANS,
'xb' => self::LIBERATION_SANS_BOLD,
'xi' => self::LIBERATION_SANS_ITALIC,
'xbi' => self::LIBERATION_SANS_BOLD_ITALIC,
],
'Lucida Console' => [
'x' => self::LUCIDA_CONSOLE,
'xb' => self::LUCIDA_CONSOLE,
'xi' => self::LUCIDA_CONSOLE,
'xbi' => self::LUCIDA_CONSOLE,
],
'Lucida Sans Unicode' => [
'x' => self::LUCIDA_SANS_UNICODE,
'xb' => self::LUCIDA_SANS_UNICODE,
'xi' => self::LUCIDA_SANS_UNICODE,
'xbi' => self::LUCIDA_SANS_UNICODE,
],
'Microsoft Sans Serif' => [
'x' => self::MICROSOFT_SANS_SERIF,
'xb' => self::MICROSOFT_SANS_SERIF,
'xi' => self::MICROSOFT_SANS_SERIF,
'xbi' => self::MICROSOFT_SANS_SERIF,
],
'Palatino Linotype' => [
'x' => self::PALATINO_LINOTYPE,
'xb' => self::PALATINO_LINOTYPE_BOLD,
'xi' => self::PALATINO_LINOTYPE_ITALIC,
'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC,
],
'Symbol' => [
'x' => self::SYMBOL,
'xb' => self::SYMBOL,
'xi' => self::SYMBOL,
'xbi' => self::SYMBOL,
],
'Tahoma' => [
'x' => self::TAHOMA,
'xb' => self::TAHOMA_BOLD,
'xi' => self::TAHOMA,
'xbi' => self::TAHOMA_BOLD,
],
'Times New Roman' => [
'x' => self::TIMES_NEW_ROMAN,
'xb' => self::TIMES_NEW_ROMAN_BOLD,
'xi' => self::TIMES_NEW_ROMAN_ITALIC,
'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC,
],
'Trebuchet MS' => [
'x' => self::TREBUCHET_MS,
'xb' => self::TREBUCHET_MS_BOLD,
'xi' => self::TREBUCHET_MS_ITALIC,
'xbi' => self::TREBUCHET_MS_BOLD_ITALIC,
],
'Verdana' => [
'x' => self::VERDANA,
'xb' => self::VERDANA_BOLD,
'xi' => self::VERDANA_ITALIC,
'xbi' => self::VERDANA_BOLD_ITALIC,
],
];
/**
* AutoSize method.
*
@@ -111,68 +212,79 @@ class Font
*
* @var string
*/
private static $trueTypeFontPath = null;
private static $trueTypeFontPath = '';
/**
* How wide is a default column for a given default font and size?
* Empirical data found by inspecting real Excel files and reading off the pixel width
* in Microsoft Office Excel 2007.
*
* @var array
* Added height in points.
*/
public static $defaultColumnWidths = [
public const DEFAULT_COLUMN_WIDTHS = [
'Arial' => [
1 => ['px' => 24, 'width' => 12.00000000],
2 => ['px' => 24, 'width' => 12.00000000],
3 => ['px' => 32, 'width' => 10.66406250],
4 => ['px' => 32, 'width' => 10.66406250],
5 => ['px' => 40, 'width' => 10.00000000],
6 => ['px' => 48, 'width' => 9.59765625],
7 => ['px' => 48, 'width' => 9.59765625],
8 => ['px' => 56, 'width' => 9.33203125],
9 => ['px' => 64, 'width' => 9.14062500],
10 => ['px' => 64, 'width' => 9.14062500],
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
],
'Calibri' => [
1 => ['px' => 24, 'width' => 12.00000000],
2 => ['px' => 24, 'width' => 12.00000000],
3 => ['px' => 32, 'width' => 10.66406250],
4 => ['px' => 32, 'width' => 10.66406250],
5 => ['px' => 40, 'width' => 10.00000000],
6 => ['px' => 48, 'width' => 9.59765625],
7 => ['px' => 48, 'width' => 9.59765625],
8 => ['px' => 56, 'width' => 9.33203125],
9 => ['px' => 56, 'width' => 9.33203125],
10 => ['px' => 64, 'width' => 9.14062500],
11 => ['px' => 64, 'width' => 9.14062500],
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0],
],
'Verdana' => [
1 => ['px' => 24, 'width' => 12.00000000],
2 => ['px' => 24, 'width' => 12.00000000],
3 => ['px' => 32, 'width' => 10.66406250],
4 => ['px' => 32, 'width' => 10.66406250],
5 => ['px' => 40, 'width' => 10.00000000],
6 => ['px' => 48, 'width' => 9.59765625],
7 => ['px' => 48, 'width' => 9.59765625],
8 => ['px' => 64, 'width' => 9.14062500],
9 => ['px' => 72, 'width' => 9.00000000],
10 => ['px' => 72, 'width' => 9.00000000],
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5],
9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25],
10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75],
],
];
/**
* List of column widths. Replaced by constant;
* previously it was public and updateable, allowing
* user to make inappropriate alterations.
*
* @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead.
*
* @var array
*/
public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS;
/**
* Set autoSize method.
*
* @param string $pValue see self::AUTOSIZE_METHOD_*
* @param string $method see self::AUTOSIZE_METHOD_*
*
* @return bool Success or failure
*/
public static function setAutoSizeMethod($pValue)
public static function setAutoSizeMethod($method)
{
if (!in_array($pValue, self::$autoSizeMethods)) {
if (!in_array($method, self::AUTOSIZE_METHODS)) {
return false;
}
self::$autoSizeMethod = $pValue;
self::$autoSizeMethod = $method;
return true;
}
@@ -196,11 +308,11 @@ class Font
* <li>~/.fonts/</li>
* </ul>.
*
* @param string $pValue
* @param string $folderPath
*/
public static function setTrueTypeFontPath($pValue): void
public static function setTrueTypeFontPath($folderPath): void
{
self::$trueTypeFontPath = $pValue;
self::$trueTypeFontPath = $folderPath;
}
/**
@@ -216,35 +328,48 @@ class Font
/**
* Calculate an (approximate) OpenXML column width, based on font size and text contained.
*
* @param \PhpOffice\PhpSpreadsheet\Style\Font $font Font object
* @param RichText|string $cellText Text to calculate width
* @param FontStyle $font Font object
* @param null|RichText|string $cellText Text to calculate width
* @param int $rotation Rotation angle
* @param null|\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Font object
*
* @return int Column width
* @param null|FontStyle $defaultFont Font object
* @param bool $filterAdjustment Add space for Autofilter or Table dropdown
*/
public static function calculateColumnWidth(\PhpOffice\PhpSpreadsheet\Style\Font $font, $cellText = '', $rotation = 0, ?\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont = null)
{
public static function calculateColumnWidth(
FontStyle $font,
$cellText = '',
$rotation = 0,
?FontStyle $defaultFont = null,
bool $filterAdjustment = false,
int $indentAdjustment = 0
): int {
// If it is rich text, use plain text
if ($cellText instanceof RichText) {
$cellText = $cellText->getPlainText();
}
// Special case if there are one or more newline characters ("\n")
$cellText = (string) $cellText;
if (strpos($cellText, "\n") !== false) {
$lineTexts = explode("\n", $cellText);
$lineWidths = [];
foreach ($lineTexts as $lineText) {
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont);
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
}
return max($lineWidths); // width of longest line in cell
}
// Try to get the exact text width in pixels
$approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
$columnWidth = 0;
if (!$approximate) {
$columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07);
$columnWidthAdjust = ceil(
self::getTextWidthPixelsExact(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
) * 1.07
);
try {
// Width of text in pixels excl. padding
@@ -256,29 +381,27 @@ class Font
}
if ($approximate) {
$columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0);
$columnWidthAdjust = self::getTextWidthPixelsApprox(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
);
// Width of text in pixels excl. padding, approximation
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
}
// Convert from pixel width to column width
$columnWidth = Drawing::pixelsToCellDimension($columnWidth, $defaultFont);
$columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
// Return
return round($columnWidth, 6);
return (int) round($columnWidth, 6);
}
/**
* Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
*
* @param string $text
* @param \PhpOffice\PhpSpreadsheet\Style\Font
* @param int $rotation
*
* @return int
*/
public static function getTextWidthPixelsExact($text, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0)
public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): int
{
if (!function_exists('imagettfbbox')) {
throw new PhpSpreadsheetException('GD library needs to be enabled');
@@ -287,7 +410,12 @@ class Font
// font size should really be supplied in pixels in GD2,
// but since GD2 seems to assume 72dpi, pixels and points are the same
$fontFile = self::getTrueTypeFontFileFromFont($font);
$textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text);
$textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
if ($textBox === false) {
// @codeCoverageIgnoreStart
throw new PhpSpreadsheetException('imagettfbbox failed');
// @codeCoverageIgnoreEnd
}
// Get corners positions
$lowerLeftCornerX = $textBox[0];
@@ -307,7 +435,7 @@ class Font
*
* @return int Text width in pixels (no padding added)
*/
public static function getTextWidthPixelsApprox($columnText, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0)
public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
{
$fontName = $font->getName();
$fontSize = $font->getSize();
@@ -342,7 +470,7 @@ class Font
// Calculate approximate rotated column width
if ($rotation !== 0) {
if ($rotation == -165) {
if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
// stacked text
$columnWidth = 4; // approximation
} else {
@@ -395,183 +523,88 @@ class Font
/**
* Returns the font path given the font.
*
* @param \PhpOffice\PhpSpreadsheet\Style\Font $font
*
* @return string Path to TrueType font file
*/
public static function getTrueTypeFontFileFromFont($font)
public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true)
{
if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) {
if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
}
$name = $font->getName();
if (!isset(self::FONT_FILE_NAMES[$name])) {
throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
}
$bold = $font->getBold();
$italic = $font->getItalic();
// Check if we can map font to true type font file
switch ($name) {
case 'Arial':
$fontFile = (
$bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD)
: ($italic ? self::ARIAL_ITALIC : self::ARIAL)
);
break;
case 'Calibri':
$fontFile = (
$bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD)
: ($italic ? self::CALIBRI_ITALIC : self::CALIBRI)
);
break;
case 'Courier New':
$fontFile = (
$bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD)
: ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW)
);
break;
case 'Comic Sans MS':
$fontFile = (
$bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS
);
break;
case 'Georgia':
$fontFile = (
$bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD)
: ($italic ? self::GEORGIA_ITALIC : self::GEORGIA)
);
break;
case 'Impact':
$fontFile = self::IMPACT;
break;
case 'Liberation Sans':
$fontFile = (
$bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD)
: ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS)
);
break;
case 'Lucida Console':
$fontFile = self::LUCIDA_CONSOLE;
break;
case 'Lucida Sans Unicode':
$fontFile = self::LUCIDA_SANS_UNICODE;
break;
case 'Microsoft Sans Serif':
$fontFile = self::MICROSOFT_SANS_SERIF;
break;
case 'Palatino Linotype':
$fontFile = (
$bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD)
: ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE)
);
break;
case 'Symbol':
$fontFile = self::SYMBOL;
break;
case 'Tahoma':
$fontFile = (
$bold ? self::TAHOMA_BOLD : self::TAHOMA
);
break;
case 'Times New Roman':
$fontFile = (
$bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD)
: ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN)
);
break;
case 'Trebuchet MS':
$fontFile = (
$bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD)
: ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS)
);
break;
case 'Verdana':
$fontFile = (
$bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD)
: ($italic ? self::VERDANA_ITALIC : self::VERDANA)
);
break;
default:
throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
break;
$index = 'x';
if ($bold) {
$index .= 'b';
}
if ($italic) {
$index .= 'i';
}
$fontFile = self::FONT_FILE_NAMES[$name][$index];
$fontFile = self::$trueTypeFontPath . $fontFile;
$separator = '';
if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
$separator = DIRECTORY_SEPARATOR;
}
$fontFile = self::$trueTypeFontPath . $separator . $fontFile;
// Check if file actually exists
if (!file_exists($fontFile)) {
if ($checkPath && !file_exists($fontFile)) {
throw new PhpSpreadsheetException('TrueType Font file not found');
}
return $fontFile;
}
public const CHARSET_FROM_FONT_NAME = [
'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
'Wingdings' => self::CHARSET_SYMBOL,
'Wingdings 2' => self::CHARSET_SYMBOL,
'Wingdings 3' => self::CHARSET_SYMBOL,
];
/**
* Returns the associated charset for the font name.
*
* @param string $name Font name
* @param string $fontName Font name
*
* @return int Character set code
*/
public static function getCharsetFromFontName($name)
public static function getCharsetFromFontName($fontName)
{
switch ($name) {
// Add more cases. Check FONT records in real Excel files.
case 'EucrosiaUPC':
return self::CHARSET_ANSI_THAI;
case 'Wingdings':
return self::CHARSET_SYMBOL;
case 'Wingdings 2':
return self::CHARSET_SYMBOL;
case 'Wingdings 3':
return self::CHARSET_SYMBOL;
default:
return self::CHARSET_ANSI_LATIN;
}
return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
}
/**
* Get the effective column width for columns without a column dimension or column with width -1
* For example, for Calibri 11 this is 9.140625 (64 px).
*
* @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
* @param bool $pPixels true = return column width in pixels, false = return in OOXML units
* @param FontStyle $font The workbooks default font
* @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
*
* @return mixed Column width
*/
public static function getDefaultColumnWidthByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font, $pPixels = false)
public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
{
if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) {
if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
// Exact width can be determined
$columnWidth = $pPixels ?
self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px']
: self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width'];
$columnWidth = $returnAsPixels ?
self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
: self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$columnWidth = $pPixels ?
self::$defaultColumnWidths['Calibri'][11]['px']
: self::$defaultColumnWidths['Calibri'][11]['width'];
$columnWidth = $returnAsPixels ?
self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
: self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
$columnWidth = $columnWidth * $font->getSize() / 11;
// Round pixels to closest integer
if ($pPixels) {
if ($returnAsPixels) {
$columnWidth = (int) round($columnWidth);
}
}
@@ -583,179 +616,20 @@ class Font
* Get the effective row height for rows without a row dimension or rows with height -1
* For example, for Calibri 11 this is 15 points.
*
* @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
* @param FontStyle $font The workbooks default font
*
* @return float Row height in points
*/
public static function getDefaultRowHeightByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
public static function getDefaultRowHeightByFont(FontStyle $font)
{
switch ($font->getName()) {
case 'Arial':
switch ($font->getSize()) {
case 10:
// inspection of Arial 10 workbook says 12.75pt ~17px
$rowHeight = 12.75;
break;
case 9:
// inspection of Arial 9 workbook says 12.00pt ~16px
$rowHeight = 12;
break;
case 8:
// inspection of Arial 8 workbook says 11.25pt ~15px
$rowHeight = 11.25;
break;
case 7:
// inspection of Arial 7 workbook says 9.00pt ~12px
$rowHeight = 9;
break;
case 6:
case 5:
// inspection of Arial 5,6 workbook says 8.25pt ~11px
$rowHeight = 8.25;
break;
case 4:
// inspection of Arial 4 workbook says 6.75pt ~9px
$rowHeight = 6.75;
break;
case 3:
// inspection of Arial 3 workbook says 6.00pt ~8px
$rowHeight = 6;
break;
case 2:
case 1:
// inspection of Arial 1,2 workbook says 5.25pt ~7px
$rowHeight = 5.25;
break;
default:
// use Arial 10 workbook as an approximation, extrapolation
$rowHeight = 12.75 * $font->getSize() / 10;
break;
}
break;
case 'Calibri':
switch ($font->getSize()) {
case 11:
// inspection of Calibri 11 workbook says 15.00pt ~20px
$rowHeight = 15;
break;
case 10:
// inspection of Calibri 10 workbook says 12.75pt ~17px
$rowHeight = 12.75;
break;
case 9:
// inspection of Calibri 9 workbook says 12.00pt ~16px
$rowHeight = 12;
break;
case 8:
// inspection of Calibri 8 workbook says 11.25pt ~15px
$rowHeight = 11.25;
break;
case 7:
// inspection of Calibri 7 workbook says 9.00pt ~12px
$rowHeight = 9;
break;
case 6:
case 5:
// inspection of Calibri 5,6 workbook says 8.25pt ~11px
$rowHeight = 8.25;
break;
case 4:
// inspection of Calibri 4 workbook says 6.75pt ~9px
$rowHeight = 6.75;
break;
case 3:
// inspection of Calibri 3 workbook says 6.00pt ~8px
$rowHeight = 6.00;
break;
case 2:
case 1:
// inspection of Calibri 1,2 workbook says 5.25pt ~7px
$rowHeight = 5.25;
break;
default:
// use Calibri 11 workbook as an approximation, extrapolation
$rowHeight = 15 * $font->getSize() / 11;
break;
}
break;
case 'Verdana':
switch ($font->getSize()) {
case 10:
// inspection of Verdana 10 workbook says 12.75pt ~17px
$rowHeight = 12.75;
break;
case 9:
// inspection of Verdana 9 workbook says 11.25pt ~15px
$rowHeight = 11.25;
break;
case 8:
// inspection of Verdana 8 workbook says 10.50pt ~14px
$rowHeight = 10.50;
break;
case 7:
// inspection of Verdana 7 workbook says 9.00pt ~12px
$rowHeight = 9.00;
break;
case 6:
case 5:
// inspection of Verdana 5,6 workbook says 8.25pt ~11px
$rowHeight = 8.25;
break;
case 4:
// inspection of Verdana 4 workbook says 6.75pt ~9px
$rowHeight = 6.75;
break;
case 3:
// inspection of Verdana 3 workbook says 6.00pt ~8px
$rowHeight = 6;
break;
case 2:
case 1:
// inspection of Verdana 1,2 workbook says 5.25pt ~7px
$rowHeight = 5.25;
break;
default:
// use Verdana 10 workbook as an approximation, extrapolation
$rowHeight = 12.75 * $font->getSize() / 10;
break;
}
break;
default:
// just use Calibri as an approximation
$rowHeight = 15 * $font->getSize() / 11;
break;
$name = $font->getName();
$size = $font->getSize();
if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
} elseif ($name === 'Arial' || $name === 'Verdana') {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
} else {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
}
return $rowHeight;

View File

@@ -0,0 +1,21 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class IntOrFloat
{
/**
* Help some functions with large results operate correctly on 32-bit,
* by returning result as int when possible, float otherwise.
*
* @param float|int $value
*
* @return float|int
*/
public static function evaluate($value)
{
$iValue = (int) $value;
return ($value == $iValue) ? $iValue : $value;
}
}

View File

@@ -1,147 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
/**
* Cholesky decomposition class.
*
* For a symmetric, positive definite matrix A, the Cholesky decomposition
* is an lower triangular matrix L so that A = L*L'.
*
* If the matrix is not symmetric or positive definite, the constructor
* returns a partial decomposition and sets an internal flag that may
* be queried by the isSPD() method.
*
* @author Paul Meagher
* @author Michael Bommarito
*
* @version 1.2
*/
class CholeskyDecomposition
{
/**
* Decomposition storage.
*
* @var array
*/
private $L = [];
/**
* Matrix row and column dimension.
*
* @var int
*/
private $m;
/**
* Symmetric positive definite flag.
*
* @var bool
*/
private $isspd = true;
/**
* CholeskyDecomposition.
*
* Class constructor - decomposes symmetric positive definite matrix
*
* @param Matrix $A Matrix square symmetric positive definite matrix
*/
public function __construct(Matrix $A)
{
$this->L = $A->getArray();
$this->m = $A->getRowDimension();
for ($i = 0; $i < $this->m; ++$i) {
for ($j = $i; $j < $this->m; ++$j) {
for ($sum = $this->L[$i][$j], $k = $i - 1; $k >= 0; --$k) {
$sum -= $this->L[$i][$k] * $this->L[$j][$k];
}
if ($i == $j) {
if ($sum >= 0) {
$this->L[$i][$i] = sqrt($sum);
} else {
$this->isspd = false;
}
} else {
if ($this->L[$i][$i] != 0) {
$this->L[$j][$i] = $sum / $this->L[$i][$i];
}
}
}
for ($k = $i + 1; $k < $this->m; ++$k) {
$this->L[$i][$k] = 0.0;
}
}
}
/**
* Is the matrix symmetric and positive definite?
*
* @return bool
*/
public function isSPD()
{
return $this->isspd;
}
/**
* getL.
*
* Return triangular factor.
*
* @return Matrix Lower triangular matrix
*/
public function getL()
{
return new Matrix($this->L);
}
/**
* Solve A*X = B.
*
* @param $B Row-equal matrix
*
* @return Matrix L * L' * X = B
*/
public function solve(Matrix $B)
{
if ($B->getRowDimension() == $this->m) {
if ($this->isspd) {
$X = $B->getArrayCopy();
$nx = $B->getColumnDimension();
for ($k = 0; $k < $this->m; ++$k) {
for ($i = $k + 1; $i < $this->m; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X[$i][$j] -= $X[$k][$j] * $this->L[$i][$k];
}
}
for ($j = 0; $j < $nx; ++$j) {
$X[$k][$j] /= $this->L[$k][$k];
}
}
for ($k = $this->m - 1; $k >= 0; --$k) {
for ($j = 0; $j < $nx; ++$j) {
$X[$k][$j] /= $this->L[$k][$k];
}
for ($i = 0; $i < $k; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X[$i][$j] -= $X[$k][$j] * $this->L[$k][$i];
}
}
}
return new Matrix($X, $this->m, $nx);
}
throw new CalculationException(Matrix::MATRIX_SPD_EXCEPTION);
}
throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
}
}

View File

@@ -1,863 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
/**
* Class to obtain eigenvalues and eigenvectors of a real matrix.
*
* If A is symmetric, then A = V*D*V' where the eigenvalue matrix D
* is diagonal and the eigenvector matrix V is orthogonal (i.e.
* A = V.times(D.times(V.transpose())) and V.times(V.transpose())
* equals the identity matrix).
*
* If A is not symmetric, then the eigenvalue matrix D is block diagonal
* with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
* lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The
* columns of V represent the eigenvectors in the sense that A*V = V*D,
* i.e. A.times(V) equals V.times(D). The matrix V may be badly
* conditioned, or even singular, so the validity of the equation
* A = V*D*inverse(V) depends upon V.cond().
*
* @author Paul Meagher
*
* @version 1.1
*/
class EigenvalueDecomposition
{
/**
* Row and column dimension (square matrix).
*
* @var int
*/
private $n;
/**
* Arrays for internal storage of eigenvalues.
*
* @var array
*/
private $d = [];
private $e = [];
/**
* Array for internal storage of eigenvectors.
*
* @var array
*/
private $V = [];
/**
* Array for internal storage of nonsymmetric Hessenberg form.
*
* @var array
*/
private $H = [];
/**
* Working storage for nonsymmetric algorithm.
*
* @var array
*/
private $ort;
/**
* Used for complex scalar division.
*
* @var float
*/
private $cdivr;
private $cdivi;
/**
* Symmetric Householder reduction to tridiagonal form.
*/
private function tred2(): void
{
// This is derived from the Algol procedures tred2 by
// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutine in EISPACK.
$this->d = $this->V[$this->n - 1];
// Householder reduction to tridiagonal form.
for ($i = $this->n - 1; $i > 0; --$i) {
$i_ = $i - 1;
// Scale to avoid under/overflow.
$h = $scale = 0.0;
$scale += array_sum(array_map('abs', $this->d));
if ($scale == 0.0) {
$this->e[$i] = $this->d[$i_];
$this->d = array_slice($this->V[$i_], 0, $i_);
for ($j = 0; $j < $i; ++$j) {
$this->V[$j][$i] = $this->V[$i][$j] = 0.0;
}
} else {
// Generate Householder vector.
for ($k = 0; $k < $i; ++$k) {
$this->d[$k] /= $scale;
$h += $this->d[$k] ** 2;
}
$f = $this->d[$i_];
$g = sqrt($h);
if ($f > 0) {
$g = -$g;
}
$this->e[$i] = $scale * $g;
$h = $h - $f * $g;
$this->d[$i_] = $f - $g;
for ($j = 0; $j < $i; ++$j) {
$this->e[$j] = 0.0;
}
// Apply similarity transformation to remaining columns.
for ($j = 0; $j < $i; ++$j) {
$f = $this->d[$j];
$this->V[$j][$i] = $f;
$g = $this->e[$j] + $this->V[$j][$j] * $f;
for ($k = $j + 1; $k <= $i_; ++$k) {
$g += $this->V[$k][$j] * $this->d[$k];
$this->e[$k] += $this->V[$k][$j] * $f;
}
$this->e[$j] = $g;
}
$f = 0.0;
for ($j = 0; $j < $i; ++$j) {
$this->e[$j] /= $h;
$f += $this->e[$j] * $this->d[$j];
}
$hh = $f / (2 * $h);
for ($j = 0; $j < $i; ++$j) {
$this->e[$j] -= $hh * $this->d[$j];
}
for ($j = 0; $j < $i; ++$j) {
$f = $this->d[$j];
$g = $this->e[$j];
for ($k = $j; $k <= $i_; ++$k) {
$this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
}
$this->d[$j] = $this->V[$i - 1][$j];
$this->V[$i][$j] = 0.0;
}
}
$this->d[$i] = $h;
}
// Accumulate transformations.
for ($i = 0; $i < $this->n - 1; ++$i) {
$this->V[$this->n - 1][$i] = $this->V[$i][$i];
$this->V[$i][$i] = 1.0;
$h = $this->d[$i + 1];
if ($h != 0.0) {
for ($k = 0; $k <= $i; ++$k) {
$this->d[$k] = $this->V[$k][$i + 1] / $h;
}
for ($j = 0; $j <= $i; ++$j) {
$g = 0.0;
for ($k = 0; $k <= $i; ++$k) {
$g += $this->V[$k][$i + 1] * $this->V[$k][$j];
}
for ($k = 0; $k <= $i; ++$k) {
$this->V[$k][$j] -= $g * $this->d[$k];
}
}
}
for ($k = 0; $k <= $i; ++$k) {
$this->V[$k][$i + 1] = 0.0;
}
}
$this->d = $this->V[$this->n - 1];
$this->V[$this->n - 1] = array_fill(0, $j, 0.0);
$this->V[$this->n - 1][$this->n - 1] = 1.0;
$this->e[0] = 0.0;
}
/**
* Symmetric tridiagonal QL algorithm.
*
* This is derived from the Algol procedures tql2, by
* Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
* Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutine in EISPACK.
*/
private function tql2(): void
{
for ($i = 1; $i < $this->n; ++$i) {
$this->e[$i - 1] = $this->e[$i];
}
$this->e[$this->n - 1] = 0.0;
$f = 0.0;
$tst1 = 0.0;
$eps = 2.0 ** (-52.0);
for ($l = 0; $l < $this->n; ++$l) {
// Find small subdiagonal element
$tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l]));
$m = $l;
while ($m < $this->n) {
if (abs($this->e[$m]) <= $eps * $tst1) {
break;
}
++$m;
}
// If m == l, $this->d[l] is an eigenvalue,
// otherwise, iterate.
if ($m > $l) {
$iter = 0;
do {
// Could check iteration count here.
++$iter;
// Compute implicit shift
$g = $this->d[$l];
$p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]);
$r = hypo($p, 1.0);
if ($p < 0) {
$r *= -1;
}
$this->d[$l] = $this->e[$l] / ($p + $r);
$this->d[$l + 1] = $this->e[$l] * ($p + $r);
$dl1 = $this->d[$l + 1];
$h = $g - $this->d[$l];
for ($i = $l + 2; $i < $this->n; ++$i) {
$this->d[$i] -= $h;
}
$f += $h;
// Implicit QL transformation.
$p = $this->d[$m];
$c = 1.0;
$c2 = $c3 = $c;
$el1 = $this->e[$l + 1];
$s = $s2 = 0.0;
for ($i = $m - 1; $i >= $l; --$i) {
$c3 = $c2;
$c2 = $c;
$s2 = $s;
$g = $c * $this->e[$i];
$h = $c * $p;
$r = hypo($p, $this->e[$i]);
$this->e[$i + 1] = $s * $r;
$s = $this->e[$i] / $r;
$c = $p / $r;
$p = $c * $this->d[$i] - $s * $g;
$this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]);
// Accumulate transformation.
for ($k = 0; $k < $this->n; ++$k) {
$h = $this->V[$k][$i + 1];
$this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h;
$this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
}
}
$p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
$this->e[$l] = $s * $p;
$this->d[$l] = $c * $p;
// Check for convergence.
} while (abs($this->e[$l]) > $eps * $tst1);
}
$this->d[$l] = $this->d[$l] + $f;
$this->e[$l] = 0.0;
}
// Sort eigenvalues and corresponding vectors.
for ($i = 0; $i < $this->n - 1; ++$i) {
$k = $i;
$p = $this->d[$i];
for ($j = $i + 1; $j < $this->n; ++$j) {
if ($this->d[$j] < $p) {
$k = $j;
$p = $this->d[$j];
}
}
if ($k != $i) {
$this->d[$k] = $this->d[$i];
$this->d[$i] = $p;
for ($j = 0; $j < $this->n; ++$j) {
$p = $this->V[$j][$i];
$this->V[$j][$i] = $this->V[$j][$k];
$this->V[$j][$k] = $p;
}
}
}
}
/**
* Nonsymmetric reduction to Hessenberg form.
*
* This is derived from the Algol procedures orthes and ortran,
* by Martin and Wilkinson, Handbook for Auto. Comp.,
* Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutines in EISPACK.
*/
private function orthes(): void
{
$low = 0;
$high = $this->n - 1;
for ($m = $low + 1; $m <= $high - 1; ++$m) {
// Scale column.
$scale = 0.0;
for ($i = $m; $i <= $high; ++$i) {
$scale = $scale + abs($this->H[$i][$m - 1]);
}
if ($scale != 0.0) {
// Compute Householder transformation.
$h = 0.0;
for ($i = $high; $i >= $m; --$i) {
$this->ort[$i] = $this->H[$i][$m - 1] / $scale;
$h += $this->ort[$i] * $this->ort[$i];
}
$g = sqrt($h);
if ($this->ort[$m] > 0) {
$g *= -1;
}
$h -= $this->ort[$m] * $g;
$this->ort[$m] -= $g;
// Apply Householder similarity transformation
// H = (I -u * u' / h) * H * (I -u * u') / h)
for ($j = $m; $j < $this->n; ++$j) {
$f = 0.0;
for ($i = $high; $i >= $m; --$i) {
$f += $this->ort[$i] * $this->H[$i][$j];
}
$f /= $h;
for ($i = $m; $i <= $high; ++$i) {
$this->H[$i][$j] -= $f * $this->ort[$i];
}
}
for ($i = 0; $i <= $high; ++$i) {
$f = 0.0;
for ($j = $high; $j >= $m; --$j) {
$f += $this->ort[$j] * $this->H[$i][$j];
}
$f = $f / $h;
for ($j = $m; $j <= $high; ++$j) {
$this->H[$i][$j] -= $f * $this->ort[$j];
}
}
$this->ort[$m] = $scale * $this->ort[$m];
$this->H[$m][$m - 1] = $scale * $g;
}
}
// Accumulate transformations (Algol's ortran).
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
$this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
}
}
for ($m = $high - 1; $m >= $low + 1; --$m) {
if ($this->H[$m][$m - 1] != 0.0) {
for ($i = $m + 1; $i <= $high; ++$i) {
$this->ort[$i] = $this->H[$i][$m - 1];
}
for ($j = $m; $j <= $high; ++$j) {
$g = 0.0;
for ($i = $m; $i <= $high; ++$i) {
$g += $this->ort[$i] * $this->V[$i][$j];
}
// Double division avoids possible underflow
$g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
for ($i = $m; $i <= $high; ++$i) {
$this->V[$i][$j] += $g * $this->ort[$i];
}
}
}
}
}
/**
* Performs complex division.
*
* @param mixed $xr
* @param mixed $xi
* @param mixed $yr
* @param mixed $yi
*/
private function cdiv($xr, $xi, $yr, $yi): void
{
if (abs($yr) > abs($yi)) {
$r = $yi / $yr;
$d = $yr + $r * $yi;
$this->cdivr = ($xr + $r * $xi) / $d;
$this->cdivi = ($xi - $r * $xr) / $d;
} else {
$r = $yr / $yi;
$d = $yi + $r * $yr;
$this->cdivr = ($r * $xr + $xi) / $d;
$this->cdivi = ($r * $xi - $xr) / $d;
}
}
/**
* Nonsymmetric reduction from Hessenberg to real Schur form.
*
* Code is derived from the Algol procedure hqr2,
* by Martin and Wilkinson, Handbook for Auto. Comp.,
* Vol.ii-Linear Algebra, and the corresponding
* Fortran subroutine in EISPACK.
*/
private function hqr2(): void
{
// Initialize
$nn = $this->n;
$n = $nn - 1;
$low = 0;
$high = $nn - 1;
$eps = 2.0 ** (-52.0);
$exshift = 0.0;
$p = $q = $r = $s = $z = 0;
// Store roots isolated by balanc and compute matrix norm
$norm = 0.0;
for ($i = 0; $i < $nn; ++$i) {
if (($i < $low) || ($i > $high)) {
$this->d[$i] = $this->H[$i][$i];
$this->e[$i] = 0.0;
}
for ($j = max($i - 1, 0); $j < $nn; ++$j) {
$norm = $norm + abs($this->H[$i][$j]);
}
}
// Outer loop over eigenvalue index
$iter = 0;
while ($n >= $low) {
// Look for single small sub-diagonal element
$l = $n;
while ($l > $low) {
$s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]);
if ($s == 0.0) {
$s = $norm;
}
if (abs($this->H[$l][$l - 1]) < $eps * $s) {
break;
}
--$l;
}
// Check for convergence
// One root found
if ($l == $n) {
$this->H[$n][$n] = $this->H[$n][$n] + $exshift;
$this->d[$n] = $this->H[$n][$n];
$this->e[$n] = 0.0;
--$n;
$iter = 0;
// Two roots found
} elseif ($l == $n - 1) {
$w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
$p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0;
$q = $p * $p + $w;
$z = sqrt(abs($q));
$this->H[$n][$n] = $this->H[$n][$n] + $exshift;
$this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift;
$x = $this->H[$n][$n];
// Real pair
if ($q >= 0) {
if ($p >= 0) {
$z = $p + $z;
} else {
$z = $p - $z;
}
$this->d[$n - 1] = $x + $z;
$this->d[$n] = $this->d[$n - 1];
if ($z != 0.0) {
$this->d[$n] = $x - $w / $z;
}
$this->e[$n - 1] = 0.0;
$this->e[$n] = 0.0;
$x = $this->H[$n][$n - 1];
$s = abs($x) + abs($z);
$p = $x / $s;
$q = $z / $s;
$r = sqrt($p * $p + $q * $q);
$p = $p / $r;
$q = $q / $r;
// Row modification
for ($j = $n - 1; $j < $nn; ++$j) {
$z = $this->H[$n - 1][$j];
$this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
$this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
}
// Column modification
for ($i = 0; $i <= $n; ++$i) {
$z = $this->H[$i][$n - 1];
$this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
$this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
}
// Accumulate transformations
for ($i = $low; $i <= $high; ++$i) {
$z = $this->V[$i][$n - 1];
$this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
$this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
}
// Complex pair
} else {
$this->d[$n - 1] = $x + $p;
$this->d[$n] = $x + $p;
$this->e[$n - 1] = $z;
$this->e[$n] = -$z;
}
$n = $n - 2;
$iter = 0;
// No convergence yet
} else {
// Form shift
$x = $this->H[$n][$n];
$y = 0.0;
$w = 0.0;
if ($l < $n) {
$y = $this->H[$n - 1][$n - 1];
$w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
}
// Wilkinson's original ad hoc shift
if ($iter == 10) {
$exshift += $x;
for ($i = $low; $i <= $n; ++$i) {
$this->H[$i][$i] -= $x;
}
$s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
$x = $y = 0.75 * $s;
$w = -0.4375 * $s * $s;
}
// MATLAB's new ad hoc shift
if ($iter == 30) {
$s = ($y - $x) / 2.0;
$s = $s * $s + $w;
if ($s > 0) {
$s = sqrt($s);
if ($y < $x) {
$s = -$s;
}
$s = $x - $w / (($y - $x) / 2.0 + $s);
for ($i = $low; $i <= $n; ++$i) {
$this->H[$i][$i] -= $s;
}
$exshift += $s;
$x = $y = $w = 0.964;
}
}
// Could check iteration count here.
$iter = $iter + 1;
// Look for two consecutive small sub-diagonal elements
$m = $n - 2;
while ($m >= $l) {
$z = $this->H[$m][$m];
$r = $x - $z;
$s = $y - $z;
$p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1];
$q = $this->H[$m + 1][$m + 1] - $z - $r - $s;
$r = $this->H[$m + 2][$m + 1];
$s = abs($p) + abs($q) + abs($r);
$p = $p / $s;
$q = $q / $s;
$r = $r / $s;
if ($m == $l) {
break;
}
if (
abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
$eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))
) {
break;
}
--$m;
}
for ($i = $m + 2; $i <= $n; ++$i) {
$this->H[$i][$i - 2] = 0.0;
if ($i > $m + 2) {
$this->H[$i][$i - 3] = 0.0;
}
}
// Double QR step involving rows l:n and columns m:n
for ($k = $m; $k <= $n - 1; ++$k) {
$notlast = ($k != $n - 1);
if ($k != $m) {
$p = $this->H[$k][$k - 1];
$q = $this->H[$k + 1][$k - 1];
$r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0);
$x = abs($p) + abs($q) + abs($r);
if ($x != 0.0) {
$p = $p / $x;
$q = $q / $x;
$r = $r / $x;
}
}
if ($x == 0.0) {
break;
}
$s = sqrt($p * $p + $q * $q + $r * $r);
if ($p < 0) {
$s = -$s;
}
if ($s != 0) {
if ($k != $m) {
$this->H[$k][$k - 1] = -$s * $x;
} elseif ($l != $m) {
$this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
}
$p = $p + $s;
$x = $p / $s;
$y = $q / $s;
$z = $r / $s;
$q = $q / $p;
$r = $r / $p;
// Row modification
for ($j = $k; $j < $nn; ++$j) {
$p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j];
if ($notlast) {
$p = $p + $r * $this->H[$k + 2][$j];
$this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
}
$this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
$this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
}
// Column modification
$iMax = min($n, $k + 3);
for ($i = 0; $i <= $iMax; ++$i) {
$p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
if ($notlast) {
$p = $p + $z * $this->H[$i][$k + 2];
$this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
}
$this->H[$i][$k] = $this->H[$i][$k] - $p;
$this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
}
// Accumulate transformations
for ($i = $low; $i <= $high; ++$i) {
$p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
if ($notlast) {
$p = $p + $z * $this->V[$i][$k + 2];
$this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
}
$this->V[$i][$k] = $this->V[$i][$k] - $p;
$this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
}
} // ($s != 0)
} // k loop
} // check convergence
} // while ($n >= $low)
// Backsubstitute to find vectors of upper triangular form
if ($norm == 0.0) {
return;
}
for ($n = $nn - 1; $n >= 0; --$n) {
$p = $this->d[$n];
$q = $this->e[$n];
// Real vector
if ($q == 0) {
$l = $n;
$this->H[$n][$n] = 1.0;
for ($i = $n - 1; $i >= 0; --$i) {
$w = $this->H[$i][$i] - $p;
$r = 0.0;
for ($j = $l; $j <= $n; ++$j) {
$r = $r + $this->H[$i][$j] * $this->H[$j][$n];
}
if ($this->e[$i] < 0.0) {
$z = $w;
$s = $r;
} else {
$l = $i;
if ($this->e[$i] == 0.0) {
if ($w != 0.0) {
$this->H[$i][$n] = -$r / $w;
} else {
$this->H[$i][$n] = -$r / ($eps * $norm);
}
// Solve real equations
} else {
$x = $this->H[$i][$i + 1];
$y = $this->H[$i + 1][$i];
$q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i];
$t = ($x * $s - $z * $r) / $q;
$this->H[$i][$n] = $t;
if (abs($x) > abs($z)) {
$this->H[$i + 1][$n] = (-$r - $w * $t) / $x;
} else {
$this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
}
}
// Overflow control
$t = abs($this->H[$i][$n]);
if (($eps * $t) * $t > 1) {
for ($j = $i; $j <= $n; ++$j) {
$this->H[$j][$n] = $this->H[$j][$n] / $t;
}
}
}
}
// Complex vector
} elseif ($q < 0) {
$l = $n - 1;
// Last vector component imaginary so matrix is triangular
if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) {
$this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1];
$this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1];
} else {
$this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q);
$this->H[$n - 1][$n - 1] = $this->cdivr;
$this->H[$n - 1][$n] = $this->cdivi;
}
$this->H[$n][$n - 1] = 0.0;
$this->H[$n][$n] = 1.0;
for ($i = $n - 2; $i >= 0; --$i) {
// double ra,sa,vr,vi;
$ra = 0.0;
$sa = 0.0;
for ($j = $l; $j <= $n; ++$j) {
$ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
$sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
}
$w = $this->H[$i][$i] - $p;
if ($this->e[$i] < 0.0) {
$z = $w;
$r = $ra;
$s = $sa;
} else {
$l = $i;
if ($this->e[$i] == 0) {
$this->cdiv(-$ra, -$sa, $w, $q);
$this->H[$i][$n - 1] = $this->cdivr;
$this->H[$i][$n] = $this->cdivi;
} else {
// Solve complex equations
$x = $this->H[$i][$i + 1];
$y = $this->H[$i + 1][$i];
$vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q;
$vi = ($this->d[$i] - $p) * 2.0 * $q;
if ($vr == 0.0 & $vi == 0.0) {
$vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
}
$this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
$this->H[$i][$n - 1] = $this->cdivr;
$this->H[$i][$n] = $this->cdivi;
if (abs($x) > (abs($z) + abs($q))) {
$this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x;
$this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x;
} else {
$this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q);
$this->H[$i + 1][$n - 1] = $this->cdivr;
$this->H[$i + 1][$n] = $this->cdivi;
}
}
// Overflow control
$t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
if (($eps * $t) * $t > 1) {
for ($j = $i; $j <= $n; ++$j) {
$this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t;
$this->H[$j][$n] = $this->H[$j][$n] / $t;
}
}
} // end else
} // end for
} // end else for complex case
} // end for
// Vectors of isolated roots
for ($i = 0; $i < $nn; ++$i) {
if ($i < $low | $i > $high) {
for ($j = $i; $j < $nn; ++$j) {
$this->V[$i][$j] = $this->H[$i][$j];
}
}
}
// Back transformation to get eigenvectors of original matrix
for ($j = $nn - 1; $j >= $low; --$j) {
for ($i = $low; $i <= $high; ++$i) {
$z = 0.0;
$kMax = min($j, $high);
for ($k = $low; $k <= $kMax; ++$k) {
$z = $z + $this->V[$i][$k] * $this->H[$k][$j];
}
$this->V[$i][$j] = $z;
}
}
}
// end hqr2
/**
* Constructor: Check for symmetry, then construct the eigenvalue decomposition.
*
* @param mixed $Arg A Square matrix
*/
public function __construct($Arg)
{
$this->A = $Arg->getArray();
$this->n = $Arg->getColumnDimension();
$issymmetric = true;
for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) {
for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) {
$issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
}
}
if ($issymmetric) {
$this->V = $this->A;
// Tridiagonalize.
$this->tred2();
// Diagonalize.
$this->tql2();
} else {
$this->H = $this->A;
$this->ort = [];
// Reduce to Hessenberg form.
$this->orthes();
// Reduce Hessenberg to real Schur form.
$this->hqr2();
}
}
/**
* Return the eigenvector matrix.
*
* @return Matrix V
*/
public function getV()
{
return new Matrix($this->V, $this->n, $this->n);
}
/**
* Return the real parts of the eigenvalues.
*
* @return array real(diag(D))
*/
public function getRealEigenvalues()
{
return $this->d;
}
/**
* Return the imaginary parts of the eigenvalues.
*
* @return array imag(diag(D))
*/
public function getImagEigenvalues()
{
return $this->e;
}
/**
* Return the block diagonal eigenvalue matrix.
*
* @return Matrix D
*/
public function getD()
{
for ($i = 0; $i < $this->n; ++$i) {
$D[$i] = array_fill(0, $this->n, 0.0);
$D[$i][$i] = $this->d[$i];
if ($this->e[$i] == 0) {
continue;
}
$o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
$D[$i][$o] = $this->e[$i];
}
return new Matrix($D);
}
}

View File

@@ -64,7 +64,7 @@ class LUDecomposition
/**
* LU Decomposition constructor.
*
* @param Matrix $A Rectangular matrix
* @param ?Matrix $A Rectangular matrix
*/
public function __construct($A)
{
@@ -77,7 +77,7 @@ class LUDecomposition
$this->piv[$i] = $i;
}
$this->pivsign = 1;
$LUrowi = $LUcolj = [];
$LUcolj = [];
// Outer loop.
for ($j = 0; $j < $this->n; ++$j) {
@@ -135,6 +135,7 @@ class LUDecomposition
*/
public function getL()
{
$L = [];
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i > $j) {
@@ -159,6 +160,7 @@ class LUDecomposition
*/
public function getU()
{
$U = [];
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i <= $j) {
@@ -189,7 +191,9 @@ class LUDecomposition
/**
* Alias for getPivot.
*
* @see getPivot
* @see getPivot
*
* @return array Pivot vector
*/
public function getDoublePivot()
{
@@ -219,7 +223,7 @@ class LUDecomposition
/**
* Count determinants.
*
* @return array d matrix deterninat
* @return float
*/
public function det()
{
@@ -240,11 +244,11 @@ class LUDecomposition
/**
* Solve A*X = B.
*
* @param mixed $B a Matrix with as many rows as A and any number of columns
* @param Matrix $B a Matrix with as many rows as A and any number of columns
*
* @return Matrix X so that L*U*X = B(piv,:)
*/
public function solve($B)
public function solve(Matrix $B)
{
if ($B->getRowDimension() == $this->m) {
if ($this->isNonsingular()) {

View File

@@ -2,9 +2,10 @@
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
/**
* Matrix class.
@@ -66,21 +67,21 @@ class Matrix
$this->A = $args[0];
break;
//Square matrix - n x n
//Square matrix - n x n
case 'integer':
$this->m = $args[0];
$this->n = $args[0];
$this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
break;
//Rectangular matrix - m x n
//Rectangular matrix - m x n
case 'integer,integer':
$this->m = $args[0];
$this->n = $args[1];
$this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
break;
//Rectangular matrix - m x n initialized from packed array
//Rectangular matrix - m x n initialized from packed array
case 'array,integer':
$this->m = $args[1];
if ($this->m != 0) {
@@ -147,7 +148,7 @@ class Matrix
* @param int $i Row position
* @param int $j Column position
*
* @return mixed Element (int/float/double)
* @return float|int
*/
public function get($i = null, $j = null)
{
@@ -188,9 +189,7 @@ class Matrix
}
return $R;
break;
//A($i0...$iF; $j0...$jF)
//A($i0...$iF; $j0...$jF)
case 'integer,integer,integer,integer':
[$i0, $iF, $j0, $jF] = $args;
if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
@@ -211,9 +210,7 @@ class Matrix
}
return $R;
break;
//$R = array of row indices; $C = array of column indices
//$R = array of row indices; $C = array of column indices
case 'array,array':
[$RL, $CL] = $args;
if (count($RL) > 0) {
@@ -234,9 +231,7 @@ class Matrix
}
return $R;
break;
//A($i0...$iF); $CL = array of column indices
//A($i0...$iF); $CL = array of column indices
case 'integer,integer,array':
[$i0, $iF, $CL] = $args;
if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
@@ -257,9 +252,7 @@ class Matrix
}
return $R;
break;
//$RL = array of row indices
//$RL = array of row indices
case 'array,integer,integer':
[$RL, $j0, $jF] = $args;
if (count($RL) > 0) {
@@ -280,8 +273,6 @@ class Matrix
}
return $R;
break;
default:
throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
@@ -323,11 +314,9 @@ class Matrix
*
* @param int $i Row position
* @param int $j Column position
* @param mixed $c Int/float/double value
*
* @return mixed Element (int/float/double)
* @param float|int $c value
*/
public function set($i = null, $j = null, $c = null)
public function set($i = null, $j = null, $c = null): void
{
// Optimized set version just has this
$this->A[$i][$j] = $c;
@@ -456,17 +445,6 @@ class Matrix
return $s;
}
/**
* uminus.
*
* Unary minus matrix -A
*
* @return Matrix Unary minus matrix
*/
public function uminus()
{
}
/**
* plus.
*
@@ -545,18 +523,12 @@ class Matrix
for ($j = 0; $j < $this->n; ++$j) {
$validValues = true;
$value = $M->get($i, $j);
if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
$this->A[$i][$j] = trim($this->A[$i][$j], '"');
$validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= StringHelper::convertToNumberIfFraction($value);
}
[$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
[$value, $validValues] = $this->validateExtractedValue($value, /** @scrutinizer ignore-type */ $validValues);
if ($validValues) {
$this->A[$i][$j] += $value;
} else {
$this->A[$i][$j] = Functions::NAN();
$this->A[$i][$j] = ExcelError::NAN();
}
}
}
@@ -645,18 +617,12 @@ class Matrix
for ($j = 0; $j < $this->n; ++$j) {
$validValues = true;
$value = $M->get($i, $j);
if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
$this->A[$i][$j] = trim($this->A[$i][$j], '"');
$validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= StringHelper::convertToNumberIfFraction($value);
}
[$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
[$value, $validValues] = $this->validateExtractedValue($value, /** @scrutinizer ignore-type */ $validValues);
if ($validValues) {
$this->A[$i][$j] -= $value;
} else {
$this->A[$i][$j] = Functions::NAN();
$this->A[$i][$j] = ExcelError::NAN();
}
}
}
@@ -747,18 +713,12 @@ class Matrix
for ($j = 0; $j < $this->n; ++$j) {
$validValues = true;
$value = $M->get($i, $j);
if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
$this->A[$i][$j] = trim($this->A[$i][$j], '"');
$validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= StringHelper::convertToNumberIfFraction($value);
}
[$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
[$value, $validValues] = $this->validateExtractedValue($value, /** @scrutinizer ignore-type */ $validValues);
if ($validValues) {
$this->A[$i][$j] *= $value;
} else {
$this->A[$i][$j] = Functions::NAN();
$this->A[$i][$j] = ExcelError::NAN();
}
}
}
@@ -805,23 +765,17 @@ class Matrix
for ($j = 0; $j < $this->n; ++$j) {
$validValues = true;
$value = $M->get($i, $j);
if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
$this->A[$i][$j] = trim($this->A[$i][$j], '"');
$validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= StringHelper::convertToNumberIfFraction($value);
}
[$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
[$value, $validValues] = $this->validateExtractedValue($value, /** @scrutinizer ignore-type */ $validValues);
if ($validValues) {
if ($value == 0) {
// Trap for Divide by Zero error
$M->set($i, $j, '#DIV/0!');
$M->set($i, $j, /** @scrutinizer ignore-type */ '#DIV/0!');
} else {
$M->set($i, $j, $this->A[$i][$j] / $value);
}
} else {
$M->set($i, $j, Functions::NAN());
$M->set($i, $j, /** @scrutinizer ignore-type */ ExcelError::NAN());
}
}
}
@@ -1092,18 +1046,12 @@ class Matrix
for ($j = 0; $j < $this->n; ++$j) {
$validValues = true;
$value = $M->get($i, $j);
if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
$this->A[$i][$j] = trim($this->A[$i][$j], '"');
$validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= StringHelper::convertToNumberIfFraction($value);
}
[$this->A[$i][$j], $validValues] = $this->validateExtractedValue($this->A[$i][$j], $validValues);
[$value, $validValues] = $this->validateExtractedValue($value, /** @scrutinizer ignore-type */ $validValues);
if ($validValues) {
$this->A[$i][$j] = $this->A[$i][$j] ** $value;
} else {
$this->A[$i][$j] = Functions::NAN();
$this->A[$i][$j] = ExcelError::NAN();
}
}
}
@@ -1147,6 +1095,7 @@ class Matrix
$this->checkMatrixDimensions($M);
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
// @phpstan-ignore-next-line
$this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
}
}
@@ -1164,7 +1113,7 @@ class Matrix
*
* @return Matrix ... Solution if A is square, least squares solution otherwise
*/
public function solve($B)
public function solve(self $B)
{
if ($this->m == $this->n) {
$LU = new LUDecomposition($this);
@@ -1199,4 +1148,20 @@ class Matrix
return $L->det();
}
/**
* @param mixed $value
*/
private function validateExtractedValue($value, bool $validValues): array
{
if (!is_numeric($value) && is_array($value)) {
$value = Functions::flattenArray($value)[0];
}
if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
$value = trim($value, '"');
$validValues &= FormattedNumber::convertToNumberIfFormatted($value);
}
return [$value, $validValues];
}
}

View File

@@ -15,9 +15,9 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
* of simultaneous linear equations. This will fail if isFullRank()
* returns false.
*
* @author Paul Meagher
* @author Paul Meagher
*
* @version 1.1
* @version 1.1
*/
class QRDecomposition
{
@@ -54,47 +54,43 @@ class QRDecomposition
/**
* QR Decomposition computed by Householder reflections.
*
* @param matrix $A Rectangular matrix
* @param Matrix $A Rectangular matrix
*/
public function __construct($A)
public function __construct(Matrix $A)
{
if ($A instanceof Matrix) {
// Initialize.
$this->QR = $A->getArray();
$this->m = $A->getRowDimension();
$this->n = $A->getColumnDimension();
// Main loop.
for ($k = 0; $k < $this->n; ++$k) {
// Compute 2-norm of k-th column without under/overflow.
$nrm = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$nrm = hypo($nrm, $this->QR[$i][$k]);
}
if ($nrm != 0.0) {
// Form k-th Householder vector.
if ($this->QR[$k][$k] < 0) {
$nrm = -$nrm;
}
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$k] /= $nrm;
}
$this->QR[$k][$k] += 1.0;
// Apply transformation to remaining columns.
for ($j = $k + 1; $j < $this->n; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $this->QR[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
$this->Rdiag[$k] = -$nrm;
// Initialize.
$this->QR = $A->getArray();
$this->m = $A->getRowDimension();
$this->n = $A->getColumnDimension();
// Main loop.
for ($k = 0; $k < $this->n; ++$k) {
// Compute 2-norm of k-th column without under/overflow.
$nrm = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$nrm = hypo($nrm, $this->QR[$i][$k]);
}
} else {
throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
if ($nrm != 0.0) {
// Form k-th Householder vector.
if ($this->QR[$k][$k] < 0) {
$nrm = -$nrm;
}
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$k] /= $nrm;
}
$this->QR[$k][$k] += 1.0;
// Apply transformation to remaining columns.
for ($j = $k + 1; $j < $this->n; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $this->QR[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
$this->Rdiag[$k] = -$nrm;
}
}
@@ -205,13 +201,13 @@ class QRDecomposition
*
* @return Matrix matrix that minimizes the two norm of Q*R*X-B
*/
public function solve($B)
public function solve(Matrix $B)
{
if ($B->getRowDimension() == $this->m) {
if ($this->isFullRank()) {
// Copy right hand side
$nx = $B->getColumnDimension();
$X = $B->getArrayCopy();
$X = $B->getArray();
// Compute Y = transpose(Q)*B
for ($k = 0; $k < $this->n; ++$k) {
for ($j = 0; $j < $nx; ++$j) {

View File

@@ -1,528 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
/**
* For an m-by-n matrix A with m >= n, the singular value decomposition is
* an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
* an n-by-n orthogonal matrix V so that A = U*S*V'.
*
* The singular values, sigma[$k] = S[$k][$k], are ordered so that
* sigma[0] >= sigma[1] >= ... >= sigma[n-1].
*
* The singular value decompostion always exists, so the constructor will
* never fail. The matrix condition number and the effective numerical
* rank can be computed from this decomposition.
*
* @author Paul Meagher
*
* @version 1.1
*/
class SingularValueDecomposition
{
/**
* Internal storage of U.
*
* @var array
*/
private $U = [];
/**
* Internal storage of V.
*
* @var array
*/
private $V = [];
/**
* Internal storage of singular values.
*
* @var array
*/
private $s = [];
/**
* Row dimension.
*
* @var int
*/
private $m;
/**
* Column dimension.
*
* @var int
*/
private $n;
/**
* Construct the singular value decomposition.
*
* Derived from LINPACK code.
*
* @param mixed $Arg Rectangular matrix
*/
public function __construct($Arg)
{
// Initialize.
$A = $Arg->getArrayCopy();
$this->m = $Arg->getRowDimension();
$this->n = $Arg->getColumnDimension();
$nu = min($this->m, $this->n);
$e = [];
$work = [];
$wantu = true;
$wantv = true;
$nct = min($this->m - 1, $this->n);
$nrt = max(0, min($this->n - 2, $this->m));
// Reduce A to bidiagonal form, storing the diagonal elements
// in s and the super-diagonal elements in e.
$kMax = max($nct, $nrt);
for ($k = 0; $k < $kMax; ++$k) {
if ($k < $nct) {
// Compute the transformation for the k-th column and
// place the k-th diagonal in s[$k].
// Compute 2-norm of k-th column without under/overflow.
$this->s[$k] = 0;
for ($i = $k; $i < $this->m; ++$i) {
$this->s[$k] = hypo($this->s[$k], $A[$i][$k]);
}
if ($this->s[$k] != 0.0) {
if ($A[$k][$k] < 0.0) {
$this->s[$k] = -$this->s[$k];
}
for ($i = $k; $i < $this->m; ++$i) {
$A[$i][$k] /= $this->s[$k];
}
$A[$k][$k] += 1.0;
}
$this->s[$k] = -$this->s[$k];
}
for ($j = $k + 1; $j < $this->n; ++$j) {
if (($k < $nct) & ($this->s[$k] != 0.0)) {
// Apply the transformation.
$t = 0;
for ($i = $k; $i < $this->m; ++$i) {
$t += $A[$i][$k] * $A[$i][$j];
}
$t = -$t / $A[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$A[$i][$j] += $t * $A[$i][$k];
}
// Place the k-th row of A into e for the
// subsequent calculation of the row transformation.
$e[$j] = $A[$k][$j];
}
}
if ($wantu && ($k < $nct)) {
// Place the transformation in U for subsequent back
// multiplication.
for ($i = $k; $i < $this->m; ++$i) {
$this->U[$i][$k] = $A[$i][$k];
}
}
if ($k < $nrt) {
// Compute the k-th row transformation and place the
// k-th super-diagonal in e[$k].
// Compute 2-norm without under/overflow.
$e[$k] = 0;
for ($i = $k + 1; $i < $this->n; ++$i) {
$e[$k] = hypo($e[$k], $e[$i]);
}
if ($e[$k] != 0.0) {
if ($e[$k + 1] < 0.0) {
$e[$k] = -$e[$k];
}
for ($i = $k + 1; $i < $this->n; ++$i) {
$e[$i] /= $e[$k];
}
$e[$k + 1] += 1.0;
}
$e[$k] = -$e[$k];
if (($k + 1 < $this->m) && ($e[$k] != 0.0)) {
// Apply the transformation.
for ($i = $k + 1; $i < $this->m; ++$i) {
$work[$i] = 0.0;
}
for ($j = $k + 1; $j < $this->n; ++$j) {
for ($i = $k + 1; $i < $this->m; ++$i) {
$work[$i] += $e[$j] * $A[$i][$j];
}
}
for ($j = $k + 1; $j < $this->n; ++$j) {
$t = -$e[$j] / $e[$k + 1];
for ($i = $k + 1; $i < $this->m; ++$i) {
$A[$i][$j] += $t * $work[$i];
}
}
}
if ($wantv) {
// Place the transformation in V for subsequent
// back multiplication.
for ($i = $k + 1; $i < $this->n; ++$i) {
$this->V[$i][$k] = $e[$i];
}
}
}
}
// Set up the final bidiagonal matrix or order p.
$p = min($this->n, $this->m + 1);
if ($nct < $this->n) {
$this->s[$nct] = $A[$nct][$nct];
}
if ($this->m < $p) {
$this->s[$p - 1] = 0.0;
}
if ($nrt + 1 < $p) {
$e[$nrt] = $A[$nrt][$p - 1];
}
$e[$p - 1] = 0.0;
// If required, generate U.
if ($wantu) {
for ($j = $nct; $j < $nu; ++$j) {
for ($i = 0; $i < $this->m; ++$i) {
$this->U[$i][$j] = 0.0;
}
$this->U[$j][$j] = 1.0;
}
for ($k = $nct - 1; $k >= 0; --$k) {
if ($this->s[$k] != 0.0) {
for ($j = $k + 1; $j < $nu; ++$j) {
$t = 0;
for ($i = $k; $i < $this->m; ++$i) {
$t += $this->U[$i][$k] * $this->U[$i][$j];
}
$t = -$t / $this->U[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$this->U[$i][$j] += $t * $this->U[$i][$k];
}
}
for ($i = $k; $i < $this->m; ++$i) {
$this->U[$i][$k] = -$this->U[$i][$k];
}
$this->U[$k][$k] = 1.0 + $this->U[$k][$k];
for ($i = 0; $i < $k - 1; ++$i) {
$this->U[$i][$k] = 0.0;
}
} else {
for ($i = 0; $i < $this->m; ++$i) {
$this->U[$i][$k] = 0.0;
}
$this->U[$k][$k] = 1.0;
}
}
}
// If required, generate V.
if ($wantv) {
for ($k = $this->n - 1; $k >= 0; --$k) {
if (($k < $nrt) && ($e[$k] != 0.0)) {
for ($j = $k + 1; $j < $nu; ++$j) {
$t = 0;
for ($i = $k + 1; $i < $this->n; ++$i) {
$t += $this->V[$i][$k] * $this->V[$i][$j];
}
$t = -$t / $this->V[$k + 1][$k];
for ($i = $k + 1; $i < $this->n; ++$i) {
$this->V[$i][$j] += $t * $this->V[$i][$k];
}
}
}
for ($i = 0; $i < $this->n; ++$i) {
$this->V[$i][$k] = 0.0;
}
$this->V[$k][$k] = 1.0;
}
}
// Main iteration loop for the singular values.
$pp = $p - 1;
$iter = 0;
$eps = 2.0 ** (-52.0);
while ($p > 0) {
// Here is where a test for too many iterations would go.
// This section of the program inspects for negligible
// elements in the s and e arrays. On completion the
// variables kase and k are set as follows:
// kase = 1 if s(p) and e[k-1] are negligible and k<p
// kase = 2 if s(k) is negligible and k<p
// kase = 3 if e[k-1] is negligible, k<p, and
// s(k), ..., s(p) are not negligible (qr step).
// kase = 4 if e(p-1) is negligible (convergence).
for ($k = $p - 2; $k >= -1; --$k) {
if ($k == -1) {
break;
}
if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) {
$e[$k] = 0.0;
break;
}
}
if ($k == $p - 2) {
$kase = 4;
} else {
for ($ks = $p - 1; $ks >= $k; --$ks) {
if ($ks == $k) {
break;
}
$t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.);
if (abs($this->s[$ks]) <= $eps * $t) {
$this->s[$ks] = 0.0;
break;
}
}
if ($ks == $k) {
$kase = 3;
} elseif ($ks == $p - 1) {
$kase = 1;
} else {
$kase = 2;
$k = $ks;
}
}
++$k;
// Perform the task indicated by kase.
switch ($kase) {
// Deflate negligible s(p).
case 1:
$f = $e[$p - 2];
$e[$p - 2] = 0.0;
for ($j = $p - 2; $j >= $k; --$j) {
$t = hypo($this->s[$j], $f);
$cs = $this->s[$j] / $t;
$sn = $f / $t;
$this->s[$j] = $t;
if ($j != $k) {
$f = -$sn * $e[$j - 1];
$e[$j - 1] = $cs * $e[$j - 1];
}
if ($wantv) {
for ($i = 0; $i < $this->n; ++$i) {
$t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1];
$this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1];
$this->V[$i][$j] = $t;
}
}
}
break;
// Split at negligible s(k).
case 2:
$f = $e[$k - 1];
$e[$k - 1] = 0.0;
for ($j = $k; $j < $p; ++$j) {
$t = hypo($this->s[$j], $f);
$cs = $this->s[$j] / $t;
$sn = $f / $t;
$this->s[$j] = $t;
$f = -$sn * $e[$j];
$e[$j] = $cs * $e[$j];
if ($wantu) {
for ($i = 0; $i < $this->m; ++$i) {
$t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1];
$this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1];
$this->U[$i][$j] = $t;
}
}
}
break;
// Perform one qr step.
case 3:
// Calculate the shift.
$scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k]));
$sp = $this->s[$p - 1] / $scale;
$spm1 = $this->s[$p - 2] / $scale;
$epm1 = $e[$p - 2] / $scale;
$sk = $this->s[$k] / $scale;
$ek = $e[$k] / $scale;
$b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;
$c = ($sp * $epm1) * ($sp * $epm1);
$shift = 0.0;
if (($b != 0.0) || ($c != 0.0)) {
$shift = sqrt($b * $b + $c);
if ($b < 0.0) {
$shift = -$shift;
}
$shift = $c / ($b + $shift);
}
$f = ($sk + $sp) * ($sk - $sp) + $shift;
$g = $sk * $ek;
// Chase zeros.
for ($j = $k; $j < $p - 1; ++$j) {
$t = hypo($f, $g);
$cs = $f / $t;
$sn = $g / $t;
if ($j != $k) {
$e[$j - 1] = $t;
}
$f = $cs * $this->s[$j] + $sn * $e[$j];
$e[$j] = $cs * $e[$j] - $sn * $this->s[$j];
$g = $sn * $this->s[$j + 1];
$this->s[$j + 1] = $cs * $this->s[$j + 1];
if ($wantv) {
for ($i = 0; $i < $this->n; ++$i) {
$t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1];
$this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1];
$this->V[$i][$j] = $t;
}
}
$t = hypo($f, $g);
$cs = $f / $t;
$sn = $g / $t;
$this->s[$j] = $t;
$f = $cs * $e[$j] + $sn * $this->s[$j + 1];
$this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1];
$g = $sn * $e[$j + 1];
$e[$j + 1] = $cs * $e[$j + 1];
if ($wantu && ($j < $this->m - 1)) {
for ($i = 0; $i < $this->m; ++$i) {
$t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1];
$this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1];
$this->U[$i][$j] = $t;
}
}
}
$e[$p - 2] = $f;
$iter = $iter + 1;
break;
// Convergence.
case 4:
// Make the singular values positive.
if ($this->s[$k] <= 0.0) {
$this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);
if ($wantv) {
for ($i = 0; $i <= $pp; ++$i) {
$this->V[$i][$k] = -$this->V[$i][$k];
}
}
}
// Order the singular values.
while ($k < $pp) {
if ($this->s[$k] >= $this->s[$k + 1]) {
break;
}
$t = $this->s[$k];
$this->s[$k] = $this->s[$k + 1];
$this->s[$k + 1] = $t;
if ($wantv && ($k < $this->n - 1)) {
for ($i = 0; $i < $this->n; ++$i) {
$t = $this->V[$i][$k + 1];
$this->V[$i][$k + 1] = $this->V[$i][$k];
$this->V[$i][$k] = $t;
}
}
if ($wantu && ($k < $this->m - 1)) {
for ($i = 0; $i < $this->m; ++$i) {
$t = $this->U[$i][$k + 1];
$this->U[$i][$k + 1] = $this->U[$i][$k];
$this->U[$i][$k] = $t;
}
}
++$k;
}
$iter = 0;
--$p;
break;
} // end switch
} // end while
}
/**
* Return the left singular vectors.
*
* @return Matrix U
*/
public function getU()
{
return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));
}
/**
* Return the right singular vectors.
*
* @return Matrix V
*/
public function getV()
{
return new Matrix($this->V);
}
/**
* Return the one-dimensional array of singular values.
*
* @return array diagonal of S
*/
public function getSingularValues()
{
return $this->s;
}
/**
* Return the diagonal matrix of singular values.
*
* @return Matrix S
*/
public function getS()
{
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
$S[$i][$j] = 0.0;
}
$S[$i][$i] = $this->s[$i];
}
return new Matrix($S);
}
/**
* Two norm.
*
* @return float max(S)
*/
public function norm2()
{
return $this->s[0];
}
/**
* Two norm condition number.
*
* @return float max(S)/min(S)
*/
public function cond()
{
return $this->s[0] / $this->s[min($this->m, $this->n) - 1];
}
/**
* Effective numerical matrix rank.
*
* @return int Number of nonnegligible singular values
*/
public function rank()
{
$eps = 2.0 ** (-52.0);
$tol = max($this->m, $this->n) * $this->s[0] * $eps;
$r = 0;
$iMax = count($this->s);
for ($i = 0; $i < $iMax; ++$i) {
if ($this->s[$i] > $tol) {
++$r;
}
}
return $r;
}
}

View File

@@ -21,6 +21,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
@@ -109,15 +110,15 @@ class OLE
*
* @acces public
*
* @param string $file
* @param string $filename
*
* @return bool true on success, PEAR_Error on failure
*/
public function read($file)
public function read($filename)
{
$fh = fopen($file, 'rb');
if (!$fh) {
throw new ReaderException("Can't open file $file");
$fh = fopen($filename, 'rb');
if ($fh === false) {
throw new ReaderException("Can't open file $filename");
}
$this->_file_handle = $fh;
@@ -227,7 +228,8 @@ class OLE
// in OLE_ChainedBlockStream::stream_open().
// Object is removed from self::$instances in OLE_Stream::close().
$GLOBALS['_OLE_INSTANCES'][] = $this;
$instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
$keys = array_keys($GLOBALS['_OLE_INSTANCES']);
$instanceId = end($keys);
$path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
if ($blockIdOrPps instanceof OLE\PPS) {
@@ -243,13 +245,14 @@ class OLE
/**
* Reads a signed char.
*
* @param resource $fh file handle
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt1($fh)
private static function readInt1($fileHandle)
{
[, $tmp] = unpack('c', fread($fh, 1));
// @phpstan-ignore-next-line
[, $tmp] = unpack('c', fread($fileHandle, 1));
return $tmp;
}
@@ -257,13 +260,14 @@ class OLE
/**
* Reads an unsigned short (2 octets).
*
* @param resource $fh file handle
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt2($fh)
private static function readInt2($fileHandle)
{
[, $tmp] = unpack('v', fread($fh, 2));
// @phpstan-ignore-next-line
[, $tmp] = unpack('v', fread($fileHandle, 2));
return $tmp;
}
@@ -271,13 +275,14 @@ class OLE
/**
* Reads an unsigned long (4 octets).
*
* @param resource $fh file handle
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt4($fh)
private static function readInt4($fileHandle)
{
[, $tmp] = unpack('V', fread($fh, 4));
// @phpstan-ignore-next-line
[, $tmp] = unpack('V', fread($fileHandle, 4));
return $tmp;
}
@@ -316,7 +321,7 @@ class OLE
break;
default:
break;
throw new Exception('Unsupported PPS type');
}
fseek($fh, 1, SEEK_CUR);
$pps->Type = $type;
@@ -344,7 +349,7 @@ class OLE
if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
$nos = [$pps->DirPps];
$pps->children = [];
while ($nos) {
while (!empty($nos)) {
$no = array_pop($nos);
if ($no != -1) {
$childPps = $this->_list[$no];
@@ -489,42 +494,33 @@ class OLE
* Utility function
* Returns a string for the OLE container with the date given.
*
* @param int $date A timestamp
* @param float|int $date A timestamp
*
* @return string The string for the OLE container
*/
public static function localDateToOLE($date)
{
if (!isset($date)) {
if (!$date) {
return "\x00\x00\x00\x00\x00\x00\x00\x00";
}
// factor used for separating numbers into 4 bytes parts
$factor = 2 ** 32;
$dateTime = Date::dateTimeFromTimestamp("$date");
// days from 1-1-1601 until the beggining of UNIX era
$days = 134774;
// calculate seconds
$big_date = $days * 24 * 3600 + mktime((int) date('H', $date), (int) date('i', $date), (int) date('s', $date), (int) date('m', $date), (int) date('d', $date), (int) date('Y', $date));
$big_date = $days * 24 * 3600 + (float) $dateTime->format('U');
// multiply just to make MS happy
$big_date *= 10000000;
$high_part = floor($big_date / $factor);
// lower 4 bytes
$low_part = floor((($big_date / $factor) - $high_part) * $factor);
// Make HEX string
$res = '';
for ($i = 0; $i < 4; ++$i) {
$hex = $low_part % 0x100;
$res .= pack('c', $hex);
$low_part /= 0x100;
}
for ($i = 0; $i < 4; ++$i) {
$hex = $high_part % 0x100;
$res .= pack('c', $hex);
$high_part /= 0x100;
$factor = 2 ** 56;
while ($factor >= 1) {
$hex = (int) floor($big_date / $factor);
$res = pack('c', $hex) . $res;
$big_date = fmod($big_date, $factor);
$factor /= 256;
}
return $res;
@@ -535,7 +531,7 @@ class OLE
*
* @param string $oleTimestamp A binary string with the encoded date
*
* @return int The Unix timestamp corresponding to the string
* @return float|int The Unix timestamp corresponding to the string
*/
public static function OLE2LocalDate($oleTimestamp)
{
@@ -558,9 +554,6 @@ class OLE
// translate to seconds since 1970:
$unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
$iTimestamp = (int) $unixTimestamp;
// Overflow conditions can't happen on 64-bit system
return ($iTimestamp == $unixTimestamp) ? $iTimestamp : ($unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN);
return IntOrFloat::evaluate($unixTimestamp);
}
}

View File

@@ -9,7 +9,7 @@ class ChainedBlockStream
/**
* The OLE container of the file that is being read.
*
* @var OLE
* @var null|OLE
*/
public $ole;
@@ -42,7 +42,7 @@ class ChainedBlockStream
* ole-chainedblockstream://oleInstanceId=1
* @param string $mode only "r" is supported
* @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH
* @param string &$openedPath absolute path of the opened stream (out parameter)
* @param string $openedPath absolute path of the opened stream (out parameter)
*
* @return bool true on success
*/
@@ -112,7 +112,7 @@ class ChainedBlockStream
*
* @param int $count maximum number of bytes to read
*
* @return string
* @return false|string
*/
public function stream_read($count) // @codingStandardsIgnoreLine
{
@@ -160,7 +160,8 @@ class ChainedBlockStream
$this->pos = $offset;
} elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
$this->pos += $offset;
} elseif ($whence == SEEK_END && -$offset <= count($this->data)) {
// @phpstan-ignore-next-line
} elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) {
$this->pos = strlen($this->data) + $offset;
} else {
return false;

View File

@@ -74,14 +74,14 @@ class PPS
/**
* A timestamp.
*
* @var int
* @var float|int
*/
public $Time1st;
/**
* A timestamp.
*
* @var int
* @var float|int
*/
public $Time2nd;
@@ -129,8 +129,8 @@ class PPS
* @param int $prev The index of the previous PPS
* @param int $next The index of the next PPS
* @param int $dir The index of it's first child if this is a Dir or Root PPS
* @param int $time_1st A timestamp
* @param int $time_2nd A timestamp
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param string $data The (usually binary) source data of the PPS
* @param array $children Array containing children PPS for this PPS
*/
@@ -142,8 +142,8 @@ class PPS
$this->PrevPps = $prev;
$this->NextPps = $next;
$this->DirPps = $dir;
$this->Time1st = $time_1st;
$this->Time2nd = $time_2nd;
$this->Time1st = $time_1st ?? 0;
$this->Time2nd = $time_2nd ?? 0;
$this->_data = $data;
$this->children = $children;
if ($data != '') {
@@ -189,7 +189,7 @@ class PPS
. "\x00\x00\x00\x00" // 100
. OLE::localDateToOLE($this->Time1st) // 108
. OLE::localDateToOLE($this->Time2nd) // 116
. pack('V', isset($this->startBlock) ? $this->startBlock : 0) // 120
. pack('V', $this->startBlock ?? 0) // 120
. pack('V', $this->Size) // 124
. pack('V', 0); // 128
@@ -200,7 +200,7 @@ class PPS
* Updates index and pointers to previous, next and children PPS's for this
* PPS. I don't think it'll work with Dir PPS's.
*
* @param array &$raList Reference to the array of PPS's for the whole OLE
* @param array $raList Reference to the array of PPS's for the whole OLE
* container
* @param mixed $to_save
* @param mixed $depth
@@ -220,7 +220,7 @@ class PPS
$raList[$cnt]->NextPps = 0xFFFFFFFF;
$raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
} else {
$iPos = floor(count($to_save) / 2);
$iPos = (int) floor(count($to_save) / 2);
$aPrev = array_slice($to_save, 0, $iPos);
$aNext = array_slice($to_save, $iPos + 1);
$cnt = count($raList);

View File

@@ -46,8 +46,8 @@ class Root extends PPS
private $bigBlockSize;
/**
* @param int $time_1st A timestamp
* @param int $time_2nd A timestamp
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param File[] $raChild
*/
public function __construct($time_1st, $time_2nd, $raChild)
@@ -71,12 +71,12 @@ class Root extends PPS
$this->fileHandle = $fileHandle;
// Initial Setting for saving
$this->bigBlockSize = 2 ** (
$this->bigBlockSize = (int) (2 ** (
(isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
);
$this->smallBlockSize = 2 ** (
));
$this->smallBlockSize = (int) (2 ** (
(isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
);
));
// Make an array of PPS's (for Save)
$aList = [];
@@ -84,17 +84,17 @@ class Root extends PPS
// calculate values for header
[$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo);
// Save Header
$this->saveHeader($iSBDcnt, $iBBcnt, $iPPScnt);
$this->saveHeader((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
// Make Small Data string (write SBD)
$this->_data = $this->makeSmallData($aList);
// Write BB
$this->saveBigData($iSBDcnt, $aList);
$this->saveBigData((int) $iSBDcnt, $aList);
// Write PPS
$this->savePps($aList);
// Write Big Block Depot and BDList and Adding Header informations
$this->saveBbd($iSBDcnt, $iBBcnt, $iPPScnt);
$this->saveBbd((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
return true;
}
@@ -110,7 +110,6 @@ class Root extends PPS
{
// Calculate Basic Setting
[$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
$iSmallLen = 0;
$iSBcnt = 0;
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
@@ -237,7 +236,7 @@ class Root extends PPS
* Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param int $iStBlk
* @param array &$raList Reference to array of PPS's
* @param array $raList Reference to array of PPS's
*/
private function saveBigData($iStBlk, &$raList): void
{
@@ -267,7 +266,7 @@ class Root extends PPS
/**
* get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param array &$raList Reference to array of PPS's
* @param array $raList Reference to array of PPS's
*
* @return string
*/

View File

@@ -92,25 +92,23 @@ class OLERead
/**
* Read the file.
*
* @param $pFilename string Filename
*/
public function read($pFilename): void
public function read(string $filename): void
{
File::assertFile($pFilename);
File::assertFile($filename);
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->data = file_get_contents($pFilename, false, null, 0, 8);
$this->data = file_get_contents($filename, false, null, 0, 8);
// Check OLE identifier
$identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
if ($this->data != $identifierOle) {
throw new ReaderException('The filename ' . $pFilename . ' is not recognised as an OLE file');
throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = file_get_contents($pFilename);
$this->data = file_get_contents($filename);
// Total number of sectors used for the SAT
$this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
@@ -166,7 +164,6 @@ class OLERead
$pos += 4 * $bbs;
}
$pos = 0;
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
@@ -188,9 +185,9 @@ class OLERead
/**
* Extract binary stream data.
*
* @param int $stream
* @param ?int $stream
*
* @return string
* @return null|string
*/
public function getStream($stream)
{
@@ -237,13 +234,12 @@ class OLERead
/**
* Read a standard stream (by joining sectors using information from SAT).
*
* @param int $bl Sector ID where the stream starts
* @param int $block Sector ID where the stream starts
*
* @return string Data for standard stream
*/
private function readData($bl)
private function readData($block)
{
$block = $bl;
$data = '';
while ($block != -2) {

View File

@@ -2,11 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as SpException;
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
class PasswordHasher
{
const MAX_PASSWORD_LENGTH = 255;
/**
* Get algorithm name for PHP.
*/
@@ -34,36 +36,40 @@ class PasswordHasher
return $mapping[$algorithmName];
}
throw new Exception('Unsupported password algorithm: ' . $algorithmName);
throw new SpException('Unsupported password algorithm: ' . $algorithmName);
}
/**
* Create a password hash from a given string.
*
* This method is based on the algorithm provided by
* This method is based on the spec at:
* https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf
* 2.3.7.1 Binary Document Password Verifier Derivation Method 1
*
* It replaces a method based on the algorithm provided by
* Daniel Rentz of OpenOffice and the PEAR package
* Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
*
* @param string $pPassword Password to hash
* Scrutinizer will squawk at the use of bitwise operations here,
* but it should ultimately pass.
*
* @param string $password Password to hash
*/
private static function defaultHashPassword(string $pPassword): string
private static function defaultHashPassword(string $password): string
{
$password = 0x0000;
$charPos = 1; // char position
// split the plain text password in its component characters
$chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY);
foreach ($chars as $char) {
$value = ord($char) << $charPos++; // shifted ASCII value
$rotated_bits = $value >> 15; // rotated bits beyond bit 15
$value &= 0x7fff; // first 15 bits
$password ^= ($value | $rotated_bits);
$verifier = 0;
$pwlen = strlen($password);
$passwordArray = pack('c', $pwlen) . $password;
for ($i = $pwlen; $i >= 0; --$i) {
$intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1;
$intermediate2 = 2 * $verifier;
$intermediate2 = $intermediate2 & 0x7fff;
$intermediate3 = $intermediate1 | $intermediate2;
$verifier = $intermediate3 ^ ord($passwordArray[$i]);
}
$verifier ^= 0xCE4B;
$password ^= strlen($pPassword);
$password ^= 0xCE4B;
return strtoupper(dechex($password));
return strtoupper(dechex($verifier));
}
/**
@@ -82,6 +88,9 @@ class PasswordHasher
*/
public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
{
if (strlen($password) > self::MAX_PASSWORD_LENGTH) {
throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters');
}
$phpAlgorithm = self::getAlgorithm($algorithm);
if (!$phpAlgorithm) {
return self::defaultHashPassword($password);
@@ -90,7 +99,7 @@ class PasswordHasher
$saltValue = base64_decode($salt);
$encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
$hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
$hashValue = hash($phpAlgorithm, $saltValue . /** @scrutinizer ignore-type */ $encodedPassword, true);
for ($i = 0; $i < $spinCount; ++$i) {
$hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
}

View File

@@ -2,15 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class StringHelper
{
/** Constants */
/** Regular Expressions */
// Fraction
const STRING_REGEXP_FRACTION = '(-?)(\d+)\s+(\d+\/\d+)';
/**
* Control characters array.
*
@@ -28,14 +21,14 @@ class StringHelper
/**
* Decimal separator.
*
* @var string
* @var ?string
*/
private static $decimalSeparator;
/**
* Thousands separator.
*
* @var string
* @var ?string
*/
private static $thousandsSeparator;
@@ -49,7 +42,7 @@ class StringHelper
/**
* Is iconv extension avalable?
*
* @var bool
* @var ?bool
*/
private static $isIconvEnabled;
@@ -294,15 +287,15 @@ class StringHelper
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $value Value to unescape
* @param string $textValue Value to unescape
*
* @return string
*/
public static function controlCharacterOOXML2PHP($value)
public static function controlCharacterOOXML2PHP($textValue)
{
self::buildCharacterSets();
return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value);
return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue);
}
/**
@@ -316,64 +309,63 @@ class StringHelper
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $value Value to escape
* @param string $textValue Value to escape
*
* @return string
*/
public static function controlCharacterPHP2OOXML($value)
public static function controlCharacterPHP2OOXML($textValue)
{
self::buildCharacterSets();
return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value);
return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue);
}
/**
* Try to sanitize UTF8, stripping invalid byte sequences. Not perfect. Does not surrogate characters.
*
* @param string $value
*
* @return string
* Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters.
*/
public static function sanitizeUTF8($value)
public static function sanitizeUTF8(string $textValue): string
{
if (self::getIsIconvEnabled()) {
$value = @iconv('UTF-8', 'UTF-8', $value);
$textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue);
$subst = mb_substitute_character(); // default is question mark
mb_substitute_character(65533); // Unicode substitution character
// Phpstan does not think this can return false.
$returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8');
mb_substitute_character(/** @scrutinizer ignore-type */ $subst);
return $value;
}
return self::returnString($returnValue);
}
$value = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
return $value;
/**
* Strictly to satisfy Scrutinizer.
*
* @param mixed $value
*/
private static function returnString($value): string
{
return is_string($value) ? $value : '';
}
/**
* Check if a string contains UTF8 data.
*
* @param string $value
*
* @return bool
*/
public static function isUTF8($value)
public static function isUTF8(string $textValue): bool
{
return $value === '' || preg_match('/^./su', $value) === 1;
return $textValue === self::sanitizeUTF8($textValue);
}
/**
* Formats a numeric value as a string for output in various output writers forcing
* point as decimal separator in case locale is other than English.
*
* @param mixed $value
*
* @return string
* @param float|int|string $numericValue
*/
public static function formatNumber($value)
public static function formatNumber($numericValue): string
{
if (is_float($value)) {
return str_replace(',', '.', $value);
if (is_float($numericValue)) {
return str_replace(',', '.', (string) $numericValue);
}
return (string) $value;
return (string) $numericValue;
}
/**
@@ -383,25 +375,23 @@ class StringHelper
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $value UTF-8 encoded string
* @param string $textValue UTF-8 encoded string
* @param mixed[] $arrcRuns Details of rich text runs in $value
*
* @return string
*/
public static function UTF8toBIFF8UnicodeShort($value, $arrcRuns = [])
public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string
{
// character count
$ln = self::countCharacters($value, 'UTF-8');
$ln = self::countCharacters($textValue, 'UTF-8');
// option flags
if (empty($arrcRuns)) {
$data = pack('CC', $ln, 0x0001);
// characters
$data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
} else {
$data = pack('vC', $ln, 0x09);
$data .= pack('v', count($arrcRuns));
// characters
$data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
foreach ($arrcRuns as $cRun) {
$data .= pack('v', $cRun['strlen']);
$data .= pack('v', $cRun['fontidx']);
@@ -418,17 +408,15 @@ class StringHelper
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $value UTF-8 encoded string
*
* @return string
* @param string $textValue UTF-8 encoded string
*/
public static function UTF8toBIFF8UnicodeLong($value)
public static function UTF8toBIFF8UnicodeLong(string $textValue): string
{
// character count
$ln = self::countCharacters($value, 'UTF-8');
$ln = self::countCharacters($textValue, 'UTF-8');
// characters
$chars = self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
$chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
return pack('vC', $ln, 0x0001) . $chars;
}
@@ -436,111 +424,102 @@ class StringHelper
/**
* Convert string from one encoding to another.
*
* @param string $value
* @param string $to Encoding to convert to, e.g. 'UTF-8'
* @param string $from Encoding to convert from, e.g. 'UTF-16LE'
*
* @return string
*/
public static function convertEncoding($value, $to, $from)
public static function convertEncoding(string $textValue, string $to, string $from): string
{
if (self::getIsIconvEnabled()) {
$result = iconv($from, $to . self::$iconvOptions, $value);
$result = iconv($from, $to . self::$iconvOptions, $textValue);
if (false !== $result) {
return $result;
}
}
return mb_convert_encoding($value, $to, $from);
return self::returnString(mb_convert_encoding($textValue, $to, $from));
}
/**
* Get character count.
*
* @param string $value
* @param string $enc Encoding
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharacters($value, $enc = 'UTF-8')
public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strlen($value, $enc);
return mb_strlen($textValue, $encoding);
}
/**
* Get a substring of a UTF-8 encoded string.
*
* @param string $pValue UTF-8 encoded string
* @param int $pStart Start offset
* @param int $pLength Maximum number of characters in substring
*
* @return string
* @param string $textValue UTF-8 encoded string
* @param int $offset Start offset
* @param ?int $length Maximum number of characters in substring
*/
public static function substring($pValue, $pStart, $pLength = 0)
public static function substring(string $textValue, int $offset, ?int $length = 0): string
{
return mb_substr($pValue, $pStart, $pLength, 'UTF-8');
return mb_substr($textValue, $offset, $length, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to upper case.
*
* @param string $pValue UTF-8 encoded string
*
* @return string
* @param string $textValue UTF-8 encoded string
*/
public static function strToUpper($pValue)
public static function strToUpper(string $textValue): string
{
return mb_convert_case($pValue, MB_CASE_UPPER, 'UTF-8');
return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to lower case.
*
* @param string $pValue UTF-8 encoded string
*
* @return string
* @param string $textValue UTF-8 encoded string
*/
public static function strToLower($pValue)
public static function strToLower(string $textValue): string
{
return mb_convert_case($pValue, MB_CASE_LOWER, 'UTF-8');
return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to title/proper case
* (uppercase every first character in each word, lower case all other characters).
*
* @param string $pValue UTF-8 encoded string
*
* @return string
* @param string $textValue UTF-8 encoded string
*/
public static function strToTitle($pValue)
public static function strToTitle(string $textValue): string
{
return mb_convert_case($pValue, MB_CASE_TITLE, 'UTF-8');
return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8');
}
public static function mbIsUpper($char)
public static function mbIsUpper(string $character): bool
{
return mb_strtolower($char, 'UTF-8') != $char;
return mb_strtolower($character, 'UTF-8') !== $character;
}
public static function mbStrSplit($string)
/**
* Splits a UTF-8 string into an array of individual characters.
*/
public static function mbStrSplit(string $string): array
{
// Split at all position not after the start: ^
// and not before the end: $
return preg_split('/(?<!^)(?!$)/u', $string);
$split = preg_split('/(?<!^)(?!$)/u', $string);
return ($split === false) ? [] : $split;
}
/**
* Reverse the case of a string, so that all uppercase characters become lowercase
* and all lowercase characters become uppercase.
*
* @param string $pValue UTF-8 encoded string
*
* @return string
* @param string $textValue UTF-8 encoded string
*/
public static function strCaseReverse($pValue)
public static function strCaseReverse(string $textValue): string
{
$characters = self::mbStrSplit($pValue);
$characters = self::mbStrSplit($textValue);
foreach ($characters as &$character) {
if (self::mbIsUpper($character)) {
$character = mb_strtolower($character, 'UTF-8');
@@ -552,36 +531,11 @@ class StringHelper
return implode('', $characters);
}
/**
* Identify whether a string contains a fractional numeric value,
* and convert it to a numeric if it is.
*
* @param string &$operand string value to test
*
* @return bool
*/
public static function convertToNumberIfFraction(&$operand)
{
if (preg_match('/^' . self::STRING_REGEXP_FRACTION . '$/i', $operand, $match)) {
$sign = ($match[1] == '-') ? '-' : '+';
$fractionFormula = '=' . $sign . $match[2] . $sign . $match[3];
$operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
return true;
}
return false;
}
// function convertToNumberIfFraction()
/**
* Get the decimal separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*
* @return string
*/
public static function getDecimalSeparator()
public static function getDecimalSeparator(): string
{
if (!isset(self::$decimalSeparator)) {
$localeconv = localeconv();
@@ -601,20 +555,18 @@ class StringHelper
* Set the decimal separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $pValue Character for decimal separator
* @param string $separator Character for decimal separator
*/
public static function setDecimalSeparator($pValue): void
public static function setDecimalSeparator(string $separator): void
{
self::$decimalSeparator = $pValue;
self::$decimalSeparator = $separator;
}
/**
* Get the thousands separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*
* @return string
*/
public static function getThousandsSeparator()
public static function getThousandsSeparator(): string
{
if (!isset(self::$thousandsSeparator)) {
$localeconv = localeconv();
@@ -634,20 +586,18 @@ class StringHelper
* Set the thousands separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $pValue Character for thousands separator
* @param string $separator Character for thousands separator
*/
public static function setThousandsSeparator($pValue): void
public static function setThousandsSeparator(string $separator): void
{
self::$thousandsSeparator = $pValue;
self::$thousandsSeparator = $separator;
}
/**
* Get the currency code. If it has not yet been set explicitly, try to obtain the
* symbol information from locale.
*
* @return string
*/
public static function getCurrencyCode()
public static function getCurrencyCode(): string
{
if (!empty(self::$currencyCode)) {
return self::$currencyCode;
@@ -672,51 +622,51 @@ class StringHelper
* Set the currency code. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $pValue Character for currency code
* @param string $currencyCode Character for currency code
*/
public static function setCurrencyCode($pValue): void
public static function setCurrencyCode(string $currencyCode): void
{
self::$currencyCode = $pValue;
self::$currencyCode = $currencyCode;
}
/**
* Convert SYLK encoded string to UTF-8.
*
* @param string $pValue
* @param string $textValue SYLK encoded string
*
* @return string UTF-8 encoded string
*/
public static function SYLKtoUTF8($pValue)
public static function SYLKtoUTF8(string $textValue): string
{
self::buildCharacterSets();
// If there is no escape character in the string there is nothing to do
if (strpos($pValue, '') === false) {
return $pValue;
if (strpos($textValue, '') === false) {
return $textValue;
}
foreach (self::$SYLKCharacters as $k => $v) {
$pValue = str_replace($k, $v, $pValue);
$textValue = str_replace($k, $v, $textValue);
}
return $pValue;
return $textValue;
}
/**
* Retrieve any leading numeric part of a string, or return the full string if no leading numeric
* (handles basic integer or float, but not exponent or non decimal).
*
* @param string $value
* @param string $textValue
*
* @return mixed string or only the leading numeric part of the string
*/
public static function testStringAsNumeric($value)
public static function testStringAsNumeric($textValue)
{
if (is_numeric($value)) {
return $value;
if (is_numeric($textValue)) {
return $textValue;
}
$v = (float) $value;
$v = (float) $textValue;
return (is_numeric(substr($value, 0, strlen($v)))) ? $v : $value;
return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue;
}
}

View File

@@ -17,26 +17,26 @@ class TimeZone
/**
* Validate a Timezone name.
*
* @param string $timezone Time zone (e.g. 'Europe/London')
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
private static function validateTimeZone($timezone)
private static function validateTimeZone(string $timezoneName): bool
{
return in_array($timezone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC));
return in_array($timezoneName, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC), true);
}
/**
* Set the Default Timezone used for date/time conversions.
*
* @param string $timezone Time zone (e.g. 'Europe/London')
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
public static function setTimeZone($timezone)
public static function setTimeZone(string $timezoneName): bool
{
if (self::validateTimezone($timezone)) {
self::$timezone = $timezone;
if (self::validateTimeZone($timezoneName)) {
self::$timezone = $timezoneName;
return true;
}
@@ -49,7 +49,7 @@ class TimeZone
*
* @return string Timezone (e.g. 'Europe/London')
*/
public static function getTimeZone()
public static function getTimeZone(): string
{
return self::$timezone;
}
@@ -58,24 +58,20 @@ class TimeZone
* Return the Timezone offset used for date/time conversions to/from UST
* This requires both the timezone and the calculated date/time to allow for local DST.
*
* @param string $timezone The timezone for finding the adjustment to UST
* @param int $timestamp PHP date/time value
* @param ?string $timezoneName The timezone for finding the adjustment to UST
* @param float|int $timestamp PHP date/time value
*
* @return int Number of seconds for timezone adjustment
*/
public static function getTimeZoneAdjustment($timezone, $timestamp)
public static function getTimeZoneAdjustment(?string $timezoneName, $timestamp): int
{
if ($timezone !== null) {
if (!self::validateTimezone($timezone)) {
throw new PhpSpreadsheetException('Invalid timezone ' . $timezone);
}
} else {
$timezone = self::$timezone;
$timezoneName = $timezoneName ?? self::$timezone;
$dtobj = Date::dateTimeFromTimestamp("$timestamp");
if (!self::validateTimeZone($timezoneName)) {
throw new PhpSpreadsheetException("Invalid timezone $timezoneName");
}
$dtobj->setTimeZone(new DateTimeZone($timezoneName));
$objTimezone = new DateTimeZone($timezone);
$transitions = $objTimezone->getTransitions($timestamp, $timestamp);
return (count($transitions) > 0) ? $transitions[0]['offset'] : 0;
return $dtobj->getOffset();
}
}

View File

@@ -2,7 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class BestFit
abstract class BestFit
{
/**
* Indicator flag for a calculation error.
@@ -96,24 +96,18 @@ class BestFit
*
* @param float $xValue X-Value
*
* @return bool Y-Value
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
return false;
}
abstract public function getValueOfYForX($xValue);
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return bool X-Value
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return false;
}
abstract public function getValueOfXForY($yValue);
/**
* Return the original set of X-Values.
@@ -130,12 +124,9 @@ class BestFit
*
* @param int $dp Number of places of decimal precision to display
*
* @return bool
* @return string
*/
public function getEquation($dp = 0)
{
return false;
}
abstract public function getEquation($dp = 0);
/**
* Return the Slope of the line.
@@ -341,20 +332,32 @@ class BestFit
return $this->yBestFitValues;
}
/** @var mixed */
private static $scrutinizerZeroPointZero = 0.0;
/**
* @param mixed $x
* @param mixed $y
*/
private static function scrutinizerLooseCompare($x, $y): bool
{
return $x == $y;
}
protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
{
$SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
$SSres = $SScov = $SStot = $SSsex = 0.0;
foreach ($this->xValues as $xKey => $xValue) {
$bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
$SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
if ($const) {
if ($const === true) {
$SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
} else {
$SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
}
$SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
if ($const) {
if ($const === true) {
$SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
} else {
$SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
@@ -362,14 +365,15 @@ class BestFit
}
$this->SSResiduals = $SSres;
$this->DFResiduals = $this->valueCount - 1 - $const;
$this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
if ($this->DFResiduals == 0.0) {
$this->stdevOfResiduals = 0.0;
} else {
$this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
}
if (($SStot == 0.0) || ($SSres == $SStot)) {
// Scrutinizer thinks $SSres == $SStot is always true. It is wrong.
if ($SStot == self::$scrutinizerZeroPointZero || self::scrutinizerLooseCompare($SSres, $SStot)) {
$this->goodnessOfFit = 1;
} else {
$this->goodnessOfFit = 1 - ($SSres / $SStot);
@@ -395,27 +399,39 @@ class BestFit
}
}
private function sumSquares(array $values)
{
return array_sum(
array_map(
function ($value) {
return $value ** 2;
},
$values
)
);
}
/**
* @param float[] $yValues
* @param float[] $xValues
* @param bool $const
*/
protected function leastSquareFit(array $yValues, array $xValues, $const): void
protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
{
// calculate sums
$x_sum = array_sum($xValues);
$y_sum = array_sum($yValues);
$meanX = $x_sum / $this->valueCount;
$meanY = $y_sum / $this->valueCount;
$mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0;
$sumValuesX = array_sum($xValues);
$sumValuesY = array_sum($yValues);
$meanValueX = $sumValuesX / $this->valueCount;
$meanValueY = $sumValuesY / $this->valueCount;
$sumSquaresX = $this->sumSquares($xValues);
$sumSquaresY = $this->sumSquares($yValues);
$mBase = $mDivisor = 0.0;
$xy_sum = 0.0;
for ($i = 0; $i < $this->valueCount; ++$i) {
$xy_sum += $xValues[$i] * $yValues[$i];
$xx_sum += $xValues[$i] * $xValues[$i];
$yy_sum += $yValues[$i] * $yValues[$i];
if ($const) {
$mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY);
$mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX);
if ($const === true) {
$mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
$mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
} else {
$mBase += $xValues[$i] * $yValues[$i];
$mDivisor += $xValues[$i] * $xValues[$i];
@@ -426,13 +442,9 @@ class BestFit
$this->slope = $mBase / $mDivisor;
// calculate intersect
if ($const) {
$this->intersect = $meanY - ($this->slope * $meanX);
} else {
$this->intersect = 0;
}
$this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
$this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const);
$this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
}
/**
@@ -440,23 +452,22 @@ class BestFit
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($yValues, $xValues = [], $const = true)
public function __construct($yValues, $xValues = [])
{
// Calculate number of points
$nY = count($yValues);
$nX = count($xValues);
$yValueCount = count($yValues);
$xValueCount = count($xValues);
// Define X Values if necessary
if ($nX == 0) {
$xValues = range(1, $nY);
} elseif ($nY != $nX) {
if ($xValueCount === 0) {
$xValues = range(1, $yValueCount);
} elseif ($yValueCount !== $xValueCount) {
// Ensure both arrays of points are the same size
$this->error = true;
}
$this->valueCount = $nY;
$this->valueCount = $yValueCount;
$this->xValues = $xValues;
$this->yValues = $yValues;
}

View File

@@ -88,20 +88,17 @@ class ExponentialBestFit extends BestFit
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
private function exponentialRegression($yValues, $xValues, $const): void
private function exponentialRegression(array $yValues, array $xValues, bool $const): void
{
foreach ($yValues as &$value) {
if ($value < 0.0) {
$value = 0 - log(abs($value));
} elseif ($value > 0.0) {
$value = log($value);
}
}
unset($value);
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$this->leastSquareFit($yValues, $xValues, $const);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
@@ -116,7 +113,7 @@ class ExponentialBestFit extends BestFit
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->exponentialRegression($yValues, $xValues, $const);
$this->exponentialRegression($yValues, $xValues, (bool) $const);
}
}
}

View File

@@ -56,9 +56,8 @@ class LinearBestFit extends BestFit
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
private function linearRegression($yValues, $xValues, $const): void
private function linearRegression(array $yValues, array $xValues, bool $const): void
{
$this->leastSquareFit($yValues, $xValues, $const);
}
@@ -75,7 +74,7 @@ class LinearBestFit extends BestFit
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->linearRegression($yValues, $xValues, $const);
$this->linearRegression($yValues, $xValues, (bool) $const);
}
}
}

View File

@@ -48,7 +48,7 @@ class LogarithmicBestFit extends BestFit
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' + ' . $slope . ' * log(X)';
return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)';
}
/**
@@ -56,20 +56,17 @@ class LogarithmicBestFit extends BestFit
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
private function logarithmicRegression($yValues, $xValues, $const): void
private function logarithmicRegression(array $yValues, array $xValues, bool $const): void
{
foreach ($xValues as &$value) {
if ($value < 0.0) {
$value = 0 - log(abs($value));
} elseif ($value > 0.0) {
$value = log($value);
}
}
unset($value);
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$this->leastSquareFit($yValues, $xValues, $const);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
@@ -84,7 +81,7 @@ class LogarithmicBestFit extends BestFit
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->logarithmicRegression($yValues, $xValues, $const);
$this->logarithmicRegression($yValues, $xValues, (bool) $const);
}
}
}

View File

@@ -4,6 +4,10 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
// Phpstan and Scrutinizer seem to have legitimate complaints.
// $this->slope is specified where an array is expected in several places.
// But it seems that it should always be float.
// This code is probably not exercised at all in unit tests.
class PolynomialBestFit extends BestFit
{
/**
@@ -42,6 +46,8 @@ class PolynomialBestFit extends BestFit
{
$retVal = $this->getIntersect();
$slope = $this->getSlope();
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$retVal += $value * $xValue ** ($key + 1);
@@ -76,6 +82,8 @@ class PolynomialBestFit extends BestFit
$intersect = $this->getIntersect($dp);
$equation = 'Y = ' . $intersect;
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$equation .= ' + ' . $value . ' * X';
@@ -93,16 +101,18 @@ class PolynomialBestFit extends BestFit
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
* @return float
*/
public function getSlope($dp = 0)
{
if ($dp != 0) {
$coefficients = [];
// Scrutinizer is correct - $this->slope is float, not array.
foreach ($this->slope as $coefficient) {
$coefficients[] = round($coefficient, $dp);
}
// @phpstan-ignore-next-line
return $coefficients;
}
@@ -111,6 +121,8 @@ class PolynomialBestFit extends BestFit
public function getCoefficients($dp = 0)
{
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
}
@@ -178,9 +190,8 @@ class PolynomialBestFit extends BestFit
* @param int $order Order of Polynomial for this regression
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($order, $yValues, $xValues = [], $const = true)
public function __construct($order, $yValues, $xValues = [])
{
parent::__construct($yValues, $xValues);

View File

@@ -72,28 +72,23 @@ class PowerBestFit extends BestFit
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
private function powerRegression($yValues, $xValues, $const): void
private function powerRegression(array $yValues, array $xValues, bool $const): void
{
foreach ($xValues as &$value) {
if ($value < 0.0) {
$value = 0 - log(abs($value));
} elseif ($value > 0.0) {
$value = log($value);
}
}
unset($value);
foreach ($yValues as &$value) {
if ($value < 0.0) {
$value = 0 - log(abs($value));
} elseif ($value > 0.0) {
$value = log($value);
}
}
unset($value);
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$adjustedXValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$xValues
);
$this->leastSquareFit($yValues, $xValues, $const);
$this->leastSquareFit($adjustedYValues, $adjustedXValues, $const);
}
/**
@@ -108,7 +103,7 @@ class PowerBestFit extends BestFit
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->powerRegression($yValues, $xValues, $const);
$this->powerRegression($yValues, $xValues, (bool) $const);
}
}
}

View File

@@ -44,7 +44,7 @@ class Trend
/**
* Cached results for each method when trying to identify which provides the best fit.
*
* @var bestFit[]
* @var BestFit[]
*/
private static $trendCache = [];
@@ -55,10 +55,9 @@ class Trend
$nX = count($xValues);
// Define X Values if necessary
if ($nX == 0) {
if ($nX === 0) {
$xValues = range(1, $nY);
$nX = $nY;
} elseif ($nY != $nX) {
} elseif ($nY !== $nX) {
// Ensure both arrays of points are the same size
trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR);
}
@@ -73,6 +72,7 @@ class Trend
case self::TREND_POWER:
if (!isset(self::$trendCache[$key])) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
// @phpstan-ignore-next-line
self::$trendCache[$key] = new $className($yValues, $xValues, $const);
}
@@ -83,8 +83,8 @@ class Trend
case self::TREND_POLYNOMIAL_5:
case self::TREND_POLYNOMIAL_6:
if (!isset(self::$trendCache[$key])) {
$order = substr($trendType, -1);
self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues, $const);
$order = (int) substr($trendType, -1);
self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues);
}
return self::$trendCache[$key];
@@ -92,6 +92,8 @@ class Trend
case self::TREND_BEST_FIT_NO_POLY:
// If the request is to determine the best fit regression, then we test each Trend line in turn
// Start by generating an instance of each available Trend method
$bestFit = [];
$bestFitValue = [];
foreach (self::$trendTypes as $trendMethod) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
@@ -99,8 +101,8 @@ class Trend
}
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
foreach (self::$trendTypePolynomialOrders as $trendMethod) {
$order = substr($trendMethod, -1);
$bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues, $const);
$order = (int) substr($trendMethod, -1);
$bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues);
if ($bestFit[$trendMethod]->getError()) {
unset($bestFit[$trendMethod]);
} else {

View File

@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
class XMLWriter extends \XMLWriter
{
/** @var bool */
public static $debugEnabled = false;
/** Temporary storage method */
@@ -20,23 +21,23 @@ class XMLWriter extends \XMLWriter
/**
* Create a new XMLWriter instance.
*
* @param int $pTemporaryStorage Temporary storage location
* @param string $pTemporaryStorageFolder Temporary storage folder
* @param int $temporaryStorage Temporary storage location
* @param string $temporaryStorageFolder Temporary storage folder
*/
public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageFolder = null)
public function __construct($temporaryStorage = self::STORAGE_MEMORY, $temporaryStorageFolder = null)
{
// Open temporary storage
if ($pTemporaryStorage == self::STORAGE_MEMORY) {
if ($temporaryStorage == self::STORAGE_MEMORY) {
$this->openMemory();
} else {
// Create temporary filename
if ($pTemporaryStorageFolder === null) {
$pTemporaryStorageFolder = File::sysGetTempDir();
if ($temporaryStorageFolder === null) {
$temporaryStorageFolder = File::sysGetTempDir();
}
$this->tempFileName = @tempnam($pTemporaryStorageFolder, 'xml');
$this->tempFileName = (string) @tempnam($temporaryStorageFolder, 'xml');
// Open storage
if ($this->openUri($this->tempFileName) === false) {
if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) {
// Fallback to memory...
$this->openMemory();
}
@@ -54,7 +55,9 @@ class XMLWriter extends \XMLWriter
public function __destruct()
{
// Unlink temporary files
// There is nothing reasonable to do if unlink fails.
if ($this->tempFileName != '') {
/** @scrutinizer ignore-unhandled */
@unlink($this->tempFileName);
}
}
@@ -71,22 +74,22 @@ class XMLWriter extends \XMLWriter
}
$this->flush();
return file_get_contents($this->tempFileName);
return file_get_contents($this->tempFileName) ?: '';
}
/**
* Wrapper method for writeRaw.
*
* @param string|string[] $text
* @param null|string|string[] $rawTextData
*
* @return bool
*/
public function writeRawData($text)
public function writeRawData($rawTextData)
{
if (is_array($text)) {
$text = implode("\n", $text);
if (is_array($rawTextData)) {
$rawTextData = implode("\n", $rawTextData);
}
return $this->writeRaw(htmlspecialchars($text));
return $this->writeRaw(htmlspecialchars($rawTextData ?? ''));
}
}

View File

@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Xls
@@ -12,17 +13,17 @@ class Xls
* x is the width in intrinsic Excel units (measuring width in number of normal characters)
* This holds for Arial 10.
*
* @param Worksheet $sheet The sheet
* @param Worksheet $worksheet The sheet
* @param string $col The column
*
* @return int The width in pixels
*/
public static function sizeCol($sheet, $col = 'A')
public static function sizeCol(Worksheet $worksheet, $col = 'A')
{
// default font of the workbook
$font = $sheet->getParent()->getDefaultStyle()->getFont();
$font = $worksheet->getParent()->getDefaultStyle()->getFont();
$columnDimensions = $sheet->getColumnDimensions();
$columnDimensions = $worksheet->getColumnDimensions();
// first find the true column width in pixels (uncollapsed and unhidden)
if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) {
@@ -30,9 +31,9 @@ class Xls
$columnDimension = $columnDimensions[$col];
$width = $columnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} elseif ($sheet->getDefaultColumnDimension()->getWidth() != -1) {
} elseif ($worksheet->getDefaultColumnDimension()->getWidth() != -1) {
// then we have default column dimension with explicit width
$defaultColumnDimension = $sheet->getDefaultColumnDimension();
$defaultColumnDimension = $worksheet->getDefaultColumnDimension();
$width = $defaultColumnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} else {
@@ -55,17 +56,17 @@ class Xls
* the relationship is: y = 4/3x. If the height hasn't been set by the user we
* use the default value. If the row is hidden we use a value of zero.
*
* @param Worksheet $sheet The sheet
* @param Worksheet $worksheet The sheet
* @param int $row The row index (1-based)
*
* @return int The width in pixels
*/
public static function sizeRow($sheet, $row = 1)
public static function sizeRow(Worksheet $worksheet, $row = 1)
{
// default font of the workbook
$font = $sheet->getParent()->getDefaultStyle()->getFont();
$font = $worksheet->getParent()->getDefaultStyle()->getFont();
$rowDimensions = $sheet->getRowDimensions();
$rowDimensions = $worksheet->getRowDimensions();
// first find the true row height in pixels (uncollapsed and unhidden)
if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) {
@@ -73,15 +74,14 @@ class Xls
$rowDimension = $rowDimensions[$row];
$rowHeight = $rowDimension->getRowHeight();
$pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10
} elseif ($sheet->getDefaultRowDimension()->getRowHeight() != -1) {
} elseif ($worksheet->getDefaultRowDimension()->getRowHeight() != -1) {
// then we have a default row dimension with explicit height
$defaultRowDimension = $sheet->getDefaultRowDimension();
$rowHeight = $defaultRowDimension->getRowHeight();
$pixelRowHeight = Drawing::pointsToPixels($rowHeight);
$defaultRowDimension = $worksheet->getDefaultRowDimension();
$pixelRowHeight = $defaultRowDimension->getRowHeight(Dimension::UOM_PIXELS);
} else {
// we don't even have any default row dimension. Height depends on default font
$pointRowHeight = Font::getDefaultRowHeightByFont($font);
$pixelRowHeight = Font::fontSizeToPixels($pointRowHeight);
$pixelRowHeight = Font::fontSizeToPixels((int) $pointRowHeight);
}
// now find the effective row height in pixels
@@ -91,7 +91,7 @@ class Xls
$effectivePixelRowHeight = $pixelRowHeight;
}
return $effectivePixelRowHeight;
return (int) $effectivePixelRowHeight;
}
/**
@@ -105,7 +105,7 @@ class Xls
*
* @return int Horizontal measured in pixels
*/
public static function getDistanceX(Worksheet $sheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0)
public static function getDistanceX(Worksheet $worksheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0)
{
$distanceX = 0;
@@ -113,14 +113,14 @@ class Xls
$startColumnIndex = Coordinate::columnIndexFromString($startColumn);
$endColumnIndex = Coordinate::columnIndexFromString($endColumn);
for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) {
$distanceX += self::sizeCol($sheet, Coordinate::stringFromColumnIndex($i));
$distanceX += self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($i));
}
// correct for offsetX in startcell
$distanceX -= (int) floor(self::sizeCol($sheet, $startColumn) * $startOffsetX / 1024);
$distanceX -= (int) floor(self::sizeCol($worksheet, $startColumn) * $startOffsetX / 1024);
// correct for offsetX in endcell
$distanceX -= (int) floor(self::sizeCol($sheet, $endColumn) * (1 - $endOffsetX / 1024));
$distanceX -= (int) floor(self::sizeCol($worksheet, $endColumn) * (1 - $endOffsetX / 1024));
return $distanceX;
}
@@ -136,20 +136,20 @@ class Xls
*
* @return int Vertical distance measured in pixels
*/
public static function getDistanceY(Worksheet $sheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0)
public static function getDistanceY(Worksheet $worksheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0)
{
$distanceY = 0;
// add the widths of the spanning rows
for ($row = $startRow; $row <= $endRow; ++$row) {
$distanceY += self::sizeRow($sheet, $row);
$distanceY += self::sizeRow($worksheet, $row);
}
// correct for offsetX in startcell
$distanceY -= (int) floor(self::sizeRow($sheet, $startRow) * $startOffsetY / 256);
$distanceY -= (int) floor(self::sizeRow($worksheet, $startRow) * $startOffsetY / 256);
// correct for offsetX in endcell
$distanceY -= (int) floor(self::sizeRow($sheet, $endRow) * (1 - $endOffsetY / 256));
$distanceY -= (int) floor(self::sizeRow($worksheet, $endRow) * (1 - $endOffsetY / 256));
return $distanceY;
}
@@ -198,19 +198,17 @@ class Xls
* W is the width of the cell
* H is the height of the cell
*
* @param Worksheet $sheet
* @param string $coordinates E.g. 'A1'
* @param int $offsetX Horizontal offset in pixels
* @param int $offsetY Vertical offset in pixels
* @param int $width Width in pixels
* @param int $height Height in pixels
*
* @return array
* @return null|array
*/
public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height)
public static function oneAnchor2twoAnchor(Worksheet $worksheet, $coordinates, $offsetX, $offsetY, $width, $height)
{
[$column, $row] = Coordinate::coordinateFromString($coordinates);
$col_start = Coordinate::columnIndexFromString($column);
[$col_start, $row] = Coordinate::indexesFromString($coordinates);
$row_start = $row - 1;
$x1 = $offsetX;
@@ -221,10 +219,10 @@ class Xls
$row_end = $row_start; // Row containing bottom right corner of object
// Zero the specified offset if greater than the cell dimensions
if ($x1 >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start))) {
if ($x1 >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start))) {
$x1 = 0;
}
if ($y1 >= self::sizeRow($sheet, $row_start + 1)) {
if ($y1 >= self::sizeRow($worksheet, $row_start + 1)) {
$y1 = 0;
}
@@ -232,37 +230,37 @@ class Xls
$height = $height + $y1 - 1;
// Subtract the underlying cell widths to find the end cell of the image
while ($width >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end))) {
$width -= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end));
while ($width >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end))) {
$width -= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end));
++$col_end;
}
// Subtract the underlying cell heights to find the end cell of the image
while ($height >= self::sizeRow($sheet, $row_end + 1)) {
$height -= self::sizeRow($sheet, $row_end + 1);
while ($height >= self::sizeRow($worksheet, $row_end + 1)) {
$height -= self::sizeRow($worksheet, $row_end + 1);
++$row_end;
}
// Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
// with zero height or width.
if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
return;
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
return null;
}
if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
return;
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
return null;
}
if (self::sizeRow($sheet, $row_start + 1) == 0) {
return;
if (self::sizeRow($worksheet, $row_start + 1) == 0) {
return null;
}
if (self::sizeRow($sheet, $row_end + 1) == 0) {
return;
if (self::sizeRow($worksheet, $row_end + 1) == 0) {
return null;
}
// Convert the pixel values to the percentage value expected by Excel
$x1 = $x1 / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
$y1 = $y1 / self::sizeRow($sheet, $row_start + 1) * 256;
$x2 = ($width + 1) / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
$y2 = ($height + 1) / self::sizeRow($sheet, $row_end + 1) * 256; // Distance to bottom of object
$x1 = $x1 / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
$y1 = $y1 / self::sizeRow($worksheet, $row_start + 1) * 256;
$x2 = ($width + 1) / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
$y2 = ($height + 1) / self::sizeRow($worksheet, $row_end + 1) * 256; // Distance to bottom of object
$startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
$endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);