package and depencies
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
abstract class BaseReader implements IReader
|
||||
{
|
||||
@@ -38,7 +40,7 @@ abstract class BaseReader implements IReader
|
||||
* Restrict which sheets should be loaded?
|
||||
* This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded.
|
||||
*
|
||||
* @var array of string
|
||||
* @var null|string[]
|
||||
*/
|
||||
protected $loadSheetsOnly;
|
||||
|
||||
@@ -66,9 +68,9 @@ abstract class BaseReader implements IReader
|
||||
return $this->readDataOnly;
|
||||
}
|
||||
|
||||
public function setReadDataOnly($pValue)
|
||||
public function setReadDataOnly($readCellValuesOnly)
|
||||
{
|
||||
$this->readDataOnly = (bool) $pValue;
|
||||
$this->readDataOnly = (bool) $readCellValuesOnly;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -78,9 +80,9 @@ abstract class BaseReader implements IReader
|
||||
return $this->readEmptyCells;
|
||||
}
|
||||
|
||||
public function setReadEmptyCells($pValue)
|
||||
public function setReadEmptyCells($readEmptyCells)
|
||||
{
|
||||
$this->readEmptyCells = (bool) $pValue;
|
||||
$this->readEmptyCells = (bool) $readEmptyCells;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -90,9 +92,9 @@ abstract class BaseReader implements IReader
|
||||
return $this->includeCharts;
|
||||
}
|
||||
|
||||
public function setIncludeCharts($pValue)
|
||||
public function setIncludeCharts($includeCharts)
|
||||
{
|
||||
$this->includeCharts = (bool) $pValue;
|
||||
$this->includeCharts = (bool) $includeCharts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -102,13 +104,13 @@ abstract class BaseReader implements IReader
|
||||
return $this->loadSheetsOnly;
|
||||
}
|
||||
|
||||
public function setLoadSheetsOnly($value)
|
||||
public function setLoadSheetsOnly($sheetList)
|
||||
{
|
||||
if ($value === null) {
|
||||
if ($sheetList === null) {
|
||||
return $this->setLoadAllSheets();
|
||||
}
|
||||
|
||||
$this->loadSheetsOnly = is_array($value) ? $value : [$value];
|
||||
$this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -125,9 +127,9 @@ abstract class BaseReader implements IReader
|
||||
return $this->readFilter;
|
||||
}
|
||||
|
||||
public function setReadFilter(IReadFilter $pValue)
|
||||
public function setReadFilter(IReadFilter $readFilter)
|
||||
{
|
||||
$this->readFilter = $pValue;
|
||||
$this->readFilter = $readFilter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -137,25 +139,58 @@ abstract class BaseReader implements IReader
|
||||
return $this->securityScanner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open file for reading.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*/
|
||||
protected function openFile($pFilename): void
|
||||
protected function processFlags(int $flags): void
|
||||
{
|
||||
if ($pFilename) {
|
||||
File::assertFile($pFilename);
|
||||
|
||||
// Open file
|
||||
$fileHandle = fopen($pFilename, 'rb');
|
||||
} else {
|
||||
$fileHandle = false;
|
||||
if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) {
|
||||
$this->setIncludeCharts(true);
|
||||
}
|
||||
if ($fileHandle !== false) {
|
||||
$this->fileHandle = $fileHandle;
|
||||
} else {
|
||||
throw new ReaderException('Could not open file ' . $pFilename . ' for reading.');
|
||||
if (((bool) ($flags & self::READ_DATA_ONLY)) === true) {
|
||||
$this->setReadDataOnly(true);
|
||||
}
|
||||
if (((bool) ($flags & self::SKIP_EMPTY_CELLS)) === true) {
|
||||
$this->setReadEmptyCells(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*
|
||||
* @param int $flags the optional second parameter flags may be used to identify specific elements
|
||||
* that should be loaded, but which won't be loaded by default, using these values:
|
||||
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
|
||||
*/
|
||||
public function load(string $filename, int $flags = 0): Spreadsheet
|
||||
{
|
||||
$this->processFlags($flags);
|
||||
|
||||
try {
|
||||
return $this->loadSpreadsheetFromFile($filename);
|
||||
} catch (ReaderException $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open file for reading.
|
||||
*/
|
||||
protected function openFile(string $filename): void
|
||||
{
|
||||
$fileHandle = false;
|
||||
if ($filename) {
|
||||
File::assertFile($filename);
|
||||
|
||||
// Open file
|
||||
$fileHandle = fopen($filename, 'rb');
|
||||
}
|
||||
if ($fileHandle === false) {
|
||||
throw new ReaderException('Could not open file ' . $filename . ' for reading.');
|
||||
}
|
||||
|
||||
$this->fileHandle = $fileHandle;
|
||||
}
|
||||
}
|
||||
|
@@ -2,13 +2,19 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
|
||||
class Csv extends BaseReader
|
||||
{
|
||||
const DEFAULT_FALLBACK_ENCODING = 'CP1252';
|
||||
const GUESS_ENCODING = 'guess';
|
||||
const UTF8_BOM = "\xEF\xBB\xBF";
|
||||
const UTF8_BOM_LEN = 3;
|
||||
const UTF16BE_BOM = "\xfe\xff";
|
||||
@@ -32,10 +38,17 @@ class Csv extends BaseReader
|
||||
private $inputEncoding = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Delimiter.
|
||||
* Fallback encoding if guess strikes out.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $fallbackEncoding = self::DEFAULT_FALLBACK_ENCODING;
|
||||
|
||||
/**
|
||||
* Delimiter.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $delimiter;
|
||||
|
||||
/**
|
||||
@@ -66,38 +79,85 @@ class Csv extends BaseReader
|
||||
*/
|
||||
private $escapeCharacter = '\\';
|
||||
|
||||
/**
|
||||
* Callback for setting defaults in construction.
|
||||
*
|
||||
* @var ?callable
|
||||
*/
|
||||
private static $constructorCallback;
|
||||
|
||||
/**
|
||||
* Attempt autodetect line endings (deprecated after PHP8.1)?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $testAutodetect = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $castFormattedNumberToNumeric = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $preserveNumericFormatting = false;
|
||||
|
||||
/** @var bool */
|
||||
private $preserveNullString = false;
|
||||
|
||||
/**
|
||||
* Create a new CSV Reader instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$callback = self::$constructorCallback;
|
||||
if ($callback !== null) {
|
||||
$callback($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input encoding.
|
||||
* Set a callback to change the defaults.
|
||||
*
|
||||
* @param string $pValue Input encoding, eg: 'UTF-8'
|
||||
*
|
||||
* @return $this
|
||||
* The callback must accept the Csv Reader object as the first parameter,
|
||||
* and it should return void.
|
||||
*/
|
||||
public function setInputEncoding($pValue)
|
||||
public static function setConstructorCallback(?callable $callback): void
|
||||
{
|
||||
$this->inputEncoding = $pValue;
|
||||
self::$constructorCallback = $callback;
|
||||
}
|
||||
|
||||
public static function getConstructorCallback(): ?callable
|
||||
{
|
||||
return self::$constructorCallback;
|
||||
}
|
||||
|
||||
public function setInputEncoding(string $encoding): self
|
||||
{
|
||||
$this->inputEncoding = $encoding;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input encoding.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInputEncoding()
|
||||
public function getInputEncoding(): string
|
||||
{
|
||||
return $this->inputEncoding;
|
||||
}
|
||||
|
||||
public function setFallbackEncoding(string $fallbackEncoding): self
|
||||
{
|
||||
$this->fallbackEncoding = $fallbackEncoding;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFallbackEncoding(): string
|
||||
{
|
||||
return $this->fallbackEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move filepointer past any BOM marker.
|
||||
*/
|
||||
@@ -138,129 +198,33 @@ class Csv extends BaseReader
|
||||
return;
|
||||
}
|
||||
|
||||
$potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~'];
|
||||
$counts = [];
|
||||
foreach ($potentialDelimiters as $delimiter) {
|
||||
$counts[$delimiter] = [];
|
||||
}
|
||||
|
||||
// Count how many times each of the potential delimiters appears in each line
|
||||
$numberLines = 0;
|
||||
while (($line = $this->getNextLine()) !== false && (++$numberLines < 1000)) {
|
||||
$countLine = [];
|
||||
for ($i = strlen($line) - 1; $i >= 0; --$i) {
|
||||
$char = $line[$i];
|
||||
if (isset($counts[$char])) {
|
||||
if (!isset($countLine[$char])) {
|
||||
$countLine[$char] = 0;
|
||||
}
|
||||
++$countLine[$char];
|
||||
}
|
||||
}
|
||||
foreach ($potentialDelimiters as $delimiter) {
|
||||
$counts[$delimiter][] = $countLine[$delimiter]
|
||||
?? 0;
|
||||
}
|
||||
}
|
||||
$inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure);
|
||||
|
||||
// If number of lines is 0, nothing to infer : fall back to the default
|
||||
if ($numberLines === 0) {
|
||||
$this->delimiter = reset($potentialDelimiters);
|
||||
if ($inferenceEngine->linesCounted() === 0) {
|
||||
$this->delimiter = $inferenceEngine->getDefaultDelimiter();
|
||||
$this->skipBOM();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently)
|
||||
$meanSquareDeviations = [];
|
||||
$middleIdx = floor(($numberLines - 1) / 2);
|
||||
|
||||
foreach ($potentialDelimiters as $delimiter) {
|
||||
$series = $counts[$delimiter];
|
||||
sort($series);
|
||||
|
||||
$median = ($numberLines % 2)
|
||||
? $series[$middleIdx]
|
||||
: ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
|
||||
|
||||
if ($median === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meanSquareDeviations[$delimiter] = array_reduce(
|
||||
$series,
|
||||
function ($sum, $value) use ($median) {
|
||||
return $sum + ($value - $median) ** 2;
|
||||
}
|
||||
) / count($series);
|
||||
}
|
||||
|
||||
// ... and pick the delimiter with the smallest mean square deviation (in case of ties, the order in potentialDelimiters is respected)
|
||||
$min = INF;
|
||||
foreach ($potentialDelimiters as $delimiter) {
|
||||
if (!isset($meanSquareDeviations[$delimiter])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($meanSquareDeviations[$delimiter] < $min) {
|
||||
$min = $meanSquareDeviations[$delimiter];
|
||||
$this->delimiter = $delimiter;
|
||||
}
|
||||
}
|
||||
$this->delimiter = $inferenceEngine->infer();
|
||||
|
||||
// If no delimiter could be detected, fall back to the default
|
||||
if ($this->delimiter === null) {
|
||||
$this->delimiter = reset($potentialDelimiters);
|
||||
$this->delimiter = $inferenceEngine->getDefaultDelimiter();
|
||||
}
|
||||
|
||||
$this->skipBOM();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next full line from the file.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function getNextLine()
|
||||
{
|
||||
$line = '';
|
||||
$enclosure = ($this->escapeCharacter === '' ? ''
|
||||
: ('(?<!' . preg_quote($this->escapeCharacter, '/') . ')'))
|
||||
. preg_quote($this->enclosure, '/');
|
||||
|
||||
do {
|
||||
// Get the next line in the file
|
||||
$newLine = fgets($this->fileHandle);
|
||||
|
||||
// Return false if there is no next line
|
||||
if ($newLine === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the new line to the line passed in
|
||||
$line = $line . $newLine;
|
||||
|
||||
// Drop everything that is enclosed to avoid counting false positives in enclosures
|
||||
$line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
|
||||
|
||||
// See if we have any enclosures left in the line
|
||||
// if we still have an enclosure then we need to read the next line as well
|
||||
} while (preg_match('/(' . $enclosure . ')/', $line) > 0);
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listWorksheetInfo($pFilename)
|
||||
public function listWorksheetInfo(string $filename): array
|
||||
{
|
||||
// Open file
|
||||
$this->openFileOrMemory($pFilename);
|
||||
$this->openFileOrMemory($filename);
|
||||
$fileHandle = $this->fileHandle;
|
||||
|
||||
// Skip BOM, if any
|
||||
@@ -276,9 +240,11 @@ class Csv extends BaseReader
|
||||
$worksheetInfo[0]['totalColumns'] = 0;
|
||||
|
||||
// Loop through each line of the file in turn
|
||||
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
|
||||
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
|
||||
while (is_array($rowData)) {
|
||||
++$worksheetInfo[0]['totalRows'];
|
||||
$worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
|
||||
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
|
||||
}
|
||||
|
||||
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
|
||||
@@ -292,52 +258,116 @@ class Csv extends BaseReader
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function load($pFilename)
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||
return $this->loadIntoExisting($filename, $spreadsheet);
|
||||
}
|
||||
|
||||
private function openFileOrMemory($pFilename): void
|
||||
/**
|
||||
* Loads Spreadsheet from string.
|
||||
*/
|
||||
public function loadSpreadsheetFromString(string $contents): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true);
|
||||
}
|
||||
|
||||
private function openFileOrMemory(string $filename): void
|
||||
{
|
||||
// Open file
|
||||
$fhandle = $this->canRead($pFilename);
|
||||
$fhandle = $this->canRead($filename);
|
||||
if (!$fhandle) {
|
||||
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
|
||||
}
|
||||
$this->openFile($pFilename);
|
||||
if ($this->inputEncoding === self::GUESS_ENCODING) {
|
||||
$this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
|
||||
}
|
||||
$this->openFile($filename);
|
||||
if ($this->inputEncoding !== 'UTF-8') {
|
||||
fclose($this->fileHandle);
|
||||
$entireFile = file_get_contents($pFilename);
|
||||
$entireFile = file_get_contents($filename);
|
||||
$this->fileHandle = fopen('php://memory', 'r+b');
|
||||
$data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
|
||||
fwrite($this->fileHandle, $data);
|
||||
$this->skipBOM();
|
||||
if ($this->fileHandle !== false && $entireFile !== false) {
|
||||
$data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
|
||||
fwrite($this->fileHandle, $data);
|
||||
$this->skipBOM();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setTestAutoDetect(bool $value): self
|
||||
{
|
||||
$this->testAutodetect = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setAutoDetect(?string $value): ?string
|
||||
{
|
||||
$retVal = null;
|
||||
if ($value !== null && $this->testAutodetect) {
|
||||
$retVal2 = @ini_set('auto_detect_line_endings', $value);
|
||||
if (is_string($retVal2)) {
|
||||
$retVal = $retVal2;
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
public function castFormattedNumberToNumeric(
|
||||
bool $castFormattedNumberToNumeric,
|
||||
bool $preserveNumericFormatting = false
|
||||
): void {
|
||||
$this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric;
|
||||
$this->preserveNumericFormatting = $preserveNumericFormatting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open data uri for reading.
|
||||
*/
|
||||
private function openDataUri(string $filename): void
|
||||
{
|
||||
$fileHandle = fopen($filename, 'rb');
|
||||
if ($fileHandle === false) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ReaderException('Could not open file ' . $filename . ' for reading.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$this->fileHandle = $fileHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
|
||||
{
|
||||
$lineEnding = ini_get('auto_detect_line_endings');
|
||||
ini_set('auto_detect_line_endings', true);
|
||||
return $this->loadStringOrFile($filename, $spreadsheet, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||
*/
|
||||
private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
|
||||
{
|
||||
// Deprecated in Php8.1
|
||||
$iniset = $this->setAutoDetect('1');
|
||||
|
||||
// Open file
|
||||
$this->openFileOrMemory($pFilename);
|
||||
if ($dataUri) {
|
||||
$this->openDataUri($filename);
|
||||
} else {
|
||||
$this->openFileOrMemory($filename);
|
||||
}
|
||||
$fileHandle = $this->fileHandle;
|
||||
|
||||
// Skip BOM, if any
|
||||
@@ -356,11 +386,16 @@ class Csv extends BaseReader
|
||||
$outRow = 0;
|
||||
|
||||
// Loop through each line of the file in turn
|
||||
while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
|
||||
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
|
||||
$valueBinder = Cell::getValueBinder();
|
||||
$preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion();
|
||||
while (is_array($rowData)) {
|
||||
$noOutputYet = true;
|
||||
$columnLetter = 'A';
|
||||
foreach ($rowData as $rowDatum) {
|
||||
if ($rowDatum != '' && $this->readFilter->readCell($columnLetter, $currentRow)) {
|
||||
$this->convertBoolean($rowDatum, $preserveBooleanString);
|
||||
$numberFormatMask = $this->convertFormattedNumber($rowDatum);
|
||||
if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) {
|
||||
if ($this->contiguous) {
|
||||
if ($noOutputYet) {
|
||||
$noOutputYet = false;
|
||||
@@ -369,65 +404,97 @@ class Csv extends BaseReader
|
||||
} else {
|
||||
$outRow = $currentRow;
|
||||
}
|
||||
// Set basic styling for the value (Note that this could be overloaded by styling in a value binder)
|
||||
$sheet->getCell($columnLetter . $outRow)->getStyle()
|
||||
->getNumberFormat()
|
||||
->setFormatCode($numberFormatMask);
|
||||
// Set cell value
|
||||
$sheet->getCell($columnLetter . $outRow)->setValue($rowDatum);
|
||||
}
|
||||
++$columnLetter;
|
||||
}
|
||||
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
|
||||
++$currentRow;
|
||||
}
|
||||
|
||||
// Close file
|
||||
fclose($fileHandle);
|
||||
|
||||
ini_set('auto_detect_line_endings', $lineEnding);
|
||||
$this->setAutoDetect($iniset);
|
||||
|
||||
// Return
|
||||
return $spreadsheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delimiter.
|
||||
* Convert string true/false to boolean, and null to null-string.
|
||||
*
|
||||
* @return string
|
||||
* @param mixed $rowDatum
|
||||
*/
|
||||
public function getDelimiter()
|
||||
private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void
|
||||
{
|
||||
if (is_string($rowDatum) && !$preserveBooleanString) {
|
||||
if (strcasecmp(Calculation::getTRUE(), $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) {
|
||||
$rowDatum = true;
|
||||
} elseif (strcasecmp(Calculation::getFALSE(), $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) {
|
||||
$rowDatum = false;
|
||||
}
|
||||
} else {
|
||||
$rowDatum = $rowDatum ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert numeric strings to int or float values.
|
||||
*
|
||||
* @param mixed $rowDatum
|
||||
*/
|
||||
private function convertFormattedNumber(&$rowDatum): string
|
||||
{
|
||||
$numberFormatMask = NumberFormat::FORMAT_GENERAL;
|
||||
if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) {
|
||||
$numeric = str_replace(
|
||||
[StringHelper::getThousandsSeparator(), StringHelper::getDecimalSeparator()],
|
||||
['', '.'],
|
||||
$rowDatum
|
||||
);
|
||||
|
||||
if (is_numeric($numeric)) {
|
||||
$decimalPos = strpos($rowDatum, StringHelper::getDecimalSeparator());
|
||||
if ($this->preserveNumericFormatting === true) {
|
||||
$numberFormatMask = (strpos($rowDatum, StringHelper::getThousandsSeparator()) !== false)
|
||||
? '#,##0' : '0';
|
||||
if ($decimalPos !== false) {
|
||||
$decimals = strlen($rowDatum) - $decimalPos - 1;
|
||||
$numberFormatMask .= '.' . str_repeat('0', min($decimals, 6));
|
||||
}
|
||||
}
|
||||
|
||||
$rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric;
|
||||
}
|
||||
}
|
||||
|
||||
return $numberFormatMask;
|
||||
}
|
||||
|
||||
public function getDelimiter(): ?string
|
||||
{
|
||||
return $this->delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set delimiter.
|
||||
*
|
||||
* @param string $delimiter Delimiter, eg: ','
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDelimiter($delimiter)
|
||||
public function setDelimiter(?string $delimiter): self
|
||||
{
|
||||
$this->delimiter = $delimiter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enclosure.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEnclosure()
|
||||
public function getEnclosure(): string
|
||||
{
|
||||
return $this->enclosure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set enclosure.
|
||||
*
|
||||
* @param string $enclosure Enclosure, defaults to "
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEnclosure($enclosure)
|
||||
public function setEnclosure(string $enclosure): self
|
||||
{
|
||||
if ($enclosure == '') {
|
||||
$enclosure = '"';
|
||||
@@ -437,104 +504,64 @@ class Csv extends BaseReader
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sheet index.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSheetIndex()
|
||||
public function getSheetIndex(): int
|
||||
{
|
||||
return $this->sheetIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sheet index.
|
||||
*
|
||||
* @param int $pValue Sheet index
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSheetIndex($pValue)
|
||||
public function setSheetIndex(int $indexValue): self
|
||||
{
|
||||
$this->sheetIndex = $pValue;
|
||||
$this->sheetIndex = $indexValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Contiguous.
|
||||
*
|
||||
* @param bool $contiguous
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContiguous($contiguous)
|
||||
public function setContiguous(bool $contiguous): self
|
||||
{
|
||||
$this->contiguous = (bool) $contiguous;
|
||||
$this->contiguous = $contiguous;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Contiguous.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getContiguous()
|
||||
public function getContiguous(): bool
|
||||
{
|
||||
return $this->contiguous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set escape backslashes.
|
||||
*
|
||||
* @param string $escapeCharacter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setEscapeCharacter($escapeCharacter)
|
||||
public function setEscapeCharacter(string $escapeCharacter): self
|
||||
{
|
||||
$this->escapeCharacter = $escapeCharacter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get escape backslashes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEscapeCharacter()
|
||||
public function getEscapeCharacter(): string
|
||||
{
|
||||
return $this->escapeCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the current IReader read the file?
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename)
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
// Check if file exists
|
||||
try {
|
||||
$this->openFile($pFilename);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->openFile($filename);
|
||||
} catch (ReaderException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose($this->fileHandle);
|
||||
|
||||
// Trust file extension if any
|
||||
$extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION));
|
||||
$extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION));
|
||||
if (in_array($extension, ['csv', 'tsv'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to guess mimetype
|
||||
$type = mime_content_type($pFilename);
|
||||
$type = mime_content_type($filename);
|
||||
$supportedTypes = [
|
||||
'application/csv',
|
||||
'text/csv',
|
||||
@@ -594,7 +621,7 @@ class Csv extends BaseReader
|
||||
return $encoding;
|
||||
}
|
||||
|
||||
public static function guessEncoding(string $filename, string $dflt = 'CP1252'): string
|
||||
public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
|
||||
{
|
||||
$encoding = self::guessEncodingBom($filename);
|
||||
if ($encoding === '') {
|
||||
@@ -603,4 +630,16 @@ class Csv extends BaseReader
|
||||
|
||||
return ($encoding === '') ? $dflt : $encoding;
|
||||
}
|
||||
|
||||
public function setPreserveNullString(bool $value): self
|
||||
{
|
||||
$this->preserveNullString = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPreserveNullString(): bool
|
||||
{
|
||||
return $this->preserveNullString;
|
||||
}
|
||||
}
|
||||
|
151
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php
vendored
Normal file
151
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Csv;
|
||||
|
||||
class Delimiter
|
||||
{
|
||||
protected const POTENTIAL_DELIMETERS = [',', ';', "\t", '|', ':', ' ', '~'];
|
||||
|
||||
/** @var resource */
|
||||
protected $fileHandle;
|
||||
|
||||
/** @var string */
|
||||
protected $escapeCharacter;
|
||||
|
||||
/** @var string */
|
||||
protected $enclosure;
|
||||
|
||||
/** @var array */
|
||||
protected $counts = [];
|
||||
|
||||
/** @var int */
|
||||
protected $numberLines = 0;
|
||||
|
||||
/** @var ?string */
|
||||
protected $delimiter;
|
||||
|
||||
/**
|
||||
* @param resource $fileHandle
|
||||
*/
|
||||
public function __construct($fileHandle, string $escapeCharacter, string $enclosure)
|
||||
{
|
||||
$this->fileHandle = $fileHandle;
|
||||
$this->escapeCharacter = $escapeCharacter;
|
||||
$this->enclosure = $enclosure;
|
||||
|
||||
$this->countPotentialDelimiters();
|
||||
}
|
||||
|
||||
public function getDefaultDelimiter(): string
|
||||
{
|
||||
return self::POTENTIAL_DELIMETERS[0];
|
||||
}
|
||||
|
||||
public function linesCounted(): int
|
||||
{
|
||||
return $this->numberLines;
|
||||
}
|
||||
|
||||
protected function countPotentialDelimiters(): void
|
||||
{
|
||||
$this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []);
|
||||
$delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS);
|
||||
|
||||
// Count how many times each of the potential delimiters appears in each line
|
||||
$this->numberLines = 0;
|
||||
while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) {
|
||||
$this->countDelimiterValues($line, $delimiterKeys);
|
||||
}
|
||||
}
|
||||
|
||||
protected function countDelimiterValues(string $line, array $delimiterKeys): void
|
||||
{
|
||||
$splitString = str_split($line, 1);
|
||||
if (is_array($splitString)) {
|
||||
$distribution = array_count_values($splitString);
|
||||
$countLine = array_intersect_key($distribution, $delimiterKeys);
|
||||
|
||||
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
|
||||
$this->counts[$delimiter][] = $countLine[$delimiter] ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function infer(): ?string
|
||||
{
|
||||
// Calculate the mean square deviations for each delimiter
|
||||
// (ignoring delimiters that haven't been found consistently)
|
||||
$meanSquareDeviations = [];
|
||||
$middleIdx = floor(($this->numberLines - 1) / 2);
|
||||
|
||||
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
|
||||
$series = $this->counts[$delimiter];
|
||||
sort($series);
|
||||
|
||||
$median = ($this->numberLines % 2)
|
||||
? $series[$middleIdx]
|
||||
: ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
|
||||
|
||||
if ($median === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meanSquareDeviations[$delimiter] = array_reduce(
|
||||
$series,
|
||||
function ($sum, $value) use ($median) {
|
||||
return $sum + ($value - $median) ** 2;
|
||||
}
|
||||
) / count($series);
|
||||
}
|
||||
|
||||
// ... and pick the delimiter with the smallest mean square deviation
|
||||
// (in case of ties, the order in potentialDelimiters is respected)
|
||||
$min = INF;
|
||||
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
|
||||
if (!isset($meanSquareDeviations[$delimiter])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($meanSquareDeviations[$delimiter] < $min) {
|
||||
$min = $meanSquareDeviations[$delimiter];
|
||||
$this->delimiter = $delimiter;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next full line from the file.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function getNextLine()
|
||||
{
|
||||
$line = '';
|
||||
$enclosure = ($this->escapeCharacter === '' ? ''
|
||||
: ('(?<!' . preg_quote($this->escapeCharacter, '/') . ')'))
|
||||
. preg_quote($this->enclosure, '/');
|
||||
|
||||
do {
|
||||
// Get the next line in the file
|
||||
$newLine = fgets($this->fileHandle);
|
||||
|
||||
// Return false if there is no next line
|
||||
if ($newLine === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the new line to the line passed in
|
||||
$line = $line . $newLine;
|
||||
|
||||
// Drop everything that is enclosed to avoid counting false positives in enclosures
|
||||
$line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
|
||||
|
||||
// See if we have any enclosures left in the line
|
||||
// if we still have an enclosure then we need to read the next line as well
|
||||
} while (preg_match('/(' . $enclosure . ')/', $line) > 0);
|
||||
|
||||
return ($line !== '') ? $line : false;
|
||||
}
|
||||
}
|
@@ -7,13 +7,13 @@ class DefaultReadFilter implements IReadFilter
|
||||
/**
|
||||
* Should this cell be read?
|
||||
*
|
||||
* @param string $column Column address (as a string value like "A", or "IV")
|
||||
* @param string $columnAddress Column address (as a string value like "A", or "IV")
|
||||
* @param int $row Row number
|
||||
* @param string $worksheetName Optional worksheet name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function readCell($column, $row, $worksheetName = '')
|
||||
public function readCell($columnAddress, $row, $worksheetName = '')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\PageMargins;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup as WorksheetPageSetup;
|
||||
@@ -14,21 +15,16 @@ class PageSetup
|
||||
*/
|
||||
private $spreadsheet;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $gnm;
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet, string $gnm)
|
||||
public function __construct(Spreadsheet $spreadsheet)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
$this->gnm = $gnm;
|
||||
}
|
||||
|
||||
public function printInformation(SimpleXMLElement $sheet): self
|
||||
{
|
||||
if (isset($sheet->PrintInformation)) {
|
||||
if (isset($sheet->PrintInformation, $sheet->PrintInformation[0])) {
|
||||
$printInformation = $sheet->PrintInformation[0];
|
||||
|
||||
$scale = (string) $printInformation->Scale->attributes()['percentage'];
|
||||
$pageOrder = (string) $printInformation->order;
|
||||
$orientation = (string) $printInformation->orientation;
|
||||
@@ -68,7 +64,7 @@ class PageSetup
|
||||
|
||||
private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array
|
||||
{
|
||||
foreach ($sheet->PrintInformation->Margins->children($this->gnm, true) as $key => $margin) {
|
||||
foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) {
|
||||
$marginAttributes = $margin->attributes();
|
||||
$marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt
|
||||
// Convert value in points to inches
|
||||
|
164
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php
vendored
Normal file
164
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Properties
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
*/
|
||||
protected $spreadsheet;
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
}
|
||||
|
||||
private function docPropertiesOld(SimpleXMLElement $gnmXML): void
|
||||
{
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
foreach ($gnmXML->Summary->Item as $summaryItem) {
|
||||
$propertyName = $summaryItem->name;
|
||||
$propertyValue = $summaryItem->{'val-string'};
|
||||
switch ($propertyName) {
|
||||
case 'title':
|
||||
$docProps->setTitle(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'comments':
|
||||
$docProps->setDescription(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'keywords':
|
||||
$docProps->setKeywords(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'category':
|
||||
$docProps->setCategory(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'manager':
|
||||
$docProps->setManager(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'author':
|
||||
$docProps->setCreator(trim($propertyValue));
|
||||
$docProps->setLastModifiedBy(trim($propertyValue));
|
||||
|
||||
break;
|
||||
case 'company':
|
||||
$docProps->setCompany(trim($propertyValue));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void
|
||||
{
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
foreach ($officePropertyDC as $propertyName => $propertyValue) {
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
switch ($propertyName) {
|
||||
case 'title':
|
||||
$docProps->setTitle($propertyValue);
|
||||
|
||||
break;
|
||||
case 'subject':
|
||||
$docProps->setSubject($propertyValue);
|
||||
|
||||
break;
|
||||
case 'creator':
|
||||
$docProps->setCreator($propertyValue);
|
||||
$docProps->setLastModifiedBy($propertyValue);
|
||||
|
||||
break;
|
||||
case 'date':
|
||||
$creationDate = $propertyValue;
|
||||
$docProps->setModified($creationDate);
|
||||
|
||||
break;
|
||||
case 'description':
|
||||
$docProps->setDescription($propertyValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void
|
||||
{
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
|
||||
if ($propertyValue !== null) {
|
||||
$attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META);
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
switch ($propertyName) {
|
||||
case 'keyword':
|
||||
$docProps->setKeywords($propertyValue);
|
||||
|
||||
break;
|
||||
case 'initial-creator':
|
||||
$docProps->setCreator($propertyValue);
|
||||
$docProps->setLastModifiedBy($propertyValue);
|
||||
|
||||
break;
|
||||
case 'creation-date':
|
||||
$creationDate = $propertyValue;
|
||||
$docProps->setCreated($creationDate);
|
||||
|
||||
break;
|
||||
case 'user-defined':
|
||||
if ($attributes) {
|
||||
[, $attrName] = explode(':', (string) $attributes['name']);
|
||||
$this->userDefinedProperties($attrName, $propertyValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function userDefinedProperties(string $attrName, string $propertyValue): void
|
||||
{
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
switch ($attrName) {
|
||||
case 'publisher':
|
||||
$docProps->setCompany($propertyValue);
|
||||
|
||||
break;
|
||||
case 'category':
|
||||
$docProps->setCategory($propertyValue);
|
||||
|
||||
break;
|
||||
case 'manager':
|
||||
$docProps->setManager($propertyValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void
|
||||
{
|
||||
$officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE);
|
||||
if (!empty($officeXML)) {
|
||||
$officeDocXML = $officeXML->{'document-meta'};
|
||||
$officeDocMetaXML = $officeDocXML->meta;
|
||||
|
||||
foreach ($officeDocMetaXML as $officePropertyData) {
|
||||
$officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC);
|
||||
$this->docPropertiesDC($officePropertyDC);
|
||||
|
||||
$officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META);
|
||||
$this->docPropertiesMeta($officePropertyMeta);
|
||||
}
|
||||
} elseif (isset($gnmXML->Summary)) {
|
||||
$this->docPropertiesOld($gnmXML);
|
||||
}
|
||||
}
|
||||
}
|
281
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php
vendored
Normal file
281
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Styles
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
*/
|
||||
private $spreadsheet;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $readDataOnly = false;
|
||||
|
||||
/** @var array */
|
||||
public static $mappings = [
|
||||
'borderStyle' => [
|
||||
'0' => Border::BORDER_NONE,
|
||||
'1' => Border::BORDER_THIN,
|
||||
'2' => Border::BORDER_MEDIUM,
|
||||
'3' => Border::BORDER_SLANTDASHDOT,
|
||||
'4' => Border::BORDER_DASHED,
|
||||
'5' => Border::BORDER_THICK,
|
||||
'6' => Border::BORDER_DOUBLE,
|
||||
'7' => Border::BORDER_DOTTED,
|
||||
'8' => Border::BORDER_MEDIUMDASHED,
|
||||
'9' => Border::BORDER_DASHDOT,
|
||||
'10' => Border::BORDER_MEDIUMDASHDOT,
|
||||
'11' => Border::BORDER_DASHDOTDOT,
|
||||
'12' => Border::BORDER_MEDIUMDASHDOTDOT,
|
||||
'13' => Border::BORDER_MEDIUMDASHDOTDOT,
|
||||
],
|
||||
'fillType' => [
|
||||
'1' => Fill::FILL_SOLID,
|
||||
'2' => Fill::FILL_PATTERN_DARKGRAY,
|
||||
'3' => Fill::FILL_PATTERN_MEDIUMGRAY,
|
||||
'4' => Fill::FILL_PATTERN_LIGHTGRAY,
|
||||
'5' => Fill::FILL_PATTERN_GRAY125,
|
||||
'6' => Fill::FILL_PATTERN_GRAY0625,
|
||||
'7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
|
||||
'8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
|
||||
'9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
|
||||
'10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
|
||||
'11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
|
||||
'12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
|
||||
'13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
|
||||
'14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
|
||||
'15' => Fill::FILL_PATTERN_LIGHTUP,
|
||||
'16' => Fill::FILL_PATTERN_LIGHTDOWN,
|
||||
'17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
|
||||
'18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
|
||||
],
|
||||
'horizontal' => [
|
||||
'1' => Alignment::HORIZONTAL_GENERAL,
|
||||
'2' => Alignment::HORIZONTAL_LEFT,
|
||||
'4' => Alignment::HORIZONTAL_RIGHT,
|
||||
'8' => Alignment::HORIZONTAL_CENTER,
|
||||
'16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
|
||||
'32' => Alignment::HORIZONTAL_JUSTIFY,
|
||||
'64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
|
||||
],
|
||||
'underline' => [
|
||||
'1' => Font::UNDERLINE_SINGLE,
|
||||
'2' => Font::UNDERLINE_DOUBLE,
|
||||
'3' => Font::UNDERLINE_SINGLEACCOUNTING,
|
||||
'4' => Font::UNDERLINE_DOUBLEACCOUNTING,
|
||||
],
|
||||
'vertical' => [
|
||||
'1' => Alignment::VERTICAL_TOP,
|
||||
'2' => Alignment::VERTICAL_BOTTOM,
|
||||
'4' => Alignment::VERTICAL_CENTER,
|
||||
'8' => Alignment::VERTICAL_JUSTIFY,
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
$this->readDataOnly = $readDataOnly;
|
||||
}
|
||||
|
||||
public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void
|
||||
{
|
||||
if ($sheet->Styles->StyleRegion !== null) {
|
||||
$this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol);
|
||||
}
|
||||
}
|
||||
|
||||
private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void
|
||||
{
|
||||
foreach ($styleRegion as $style) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$styleAttributes = $style->attributes();
|
||||
if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
|
||||
$cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
|
||||
|
||||
$styleAttributes = $style->Style->attributes();
|
||||
|
||||
$styleArray = [];
|
||||
// We still set the number format mask for date/time values, even if readDataOnly is true
|
||||
// so that we can identify whether a float is a float or a date value
|
||||
$formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null;
|
||||
if ($formatCode && Date::isDateTimeFormatCode($formatCode)) {
|
||||
$styleArray['numberFormat']['formatCode'] = $formatCode;
|
||||
}
|
||||
if ($this->readDataOnly === false && $styleAttributes !== null) {
|
||||
// If readDataOnly is false, we set all formatting information
|
||||
$styleArray['numberFormat']['formatCode'] = $formatCode;
|
||||
$styleArray = $this->readStyle($styleArray, $styleAttributes, /** @scrutinizer ignore-type */ $style);
|
||||
}
|
||||
$this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
|
||||
{
|
||||
if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
|
||||
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
|
||||
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
|
||||
} elseif (isset($srssb->Diagonal)) {
|
||||
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
|
||||
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
|
||||
} elseif (isset($srssb->{'Rev-Diagonal'})) {
|
||||
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
|
||||
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
|
||||
{
|
||||
$ucDirection = ucfirst($direction);
|
||||
if (isset($srssb->$ucDirection)) {
|
||||
$styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
|
||||
}
|
||||
}
|
||||
|
||||
private function calcRotation(SimpleXMLElement $styleAttributes): int
|
||||
{
|
||||
$rotation = (int) $styleAttributes->Rotation;
|
||||
if ($rotation >= 270 && $rotation <= 360) {
|
||||
$rotation -= 360;
|
||||
}
|
||||
$rotation = (abs($rotation) > 90) ? 0 : $rotation;
|
||||
|
||||
return $rotation;
|
||||
}
|
||||
|
||||
private static function addStyle(array &$styleArray, string $key, string $value): void
|
||||
{
|
||||
if (array_key_exists($value, self::$mappings[$key])) {
|
||||
$styleArray[$key] = self::$mappings[$key][$value];
|
||||
}
|
||||
}
|
||||
|
||||
private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
|
||||
{
|
||||
if (array_key_exists($value, self::$mappings[$key])) {
|
||||
$styleArray[$key1][$key] = self::$mappings[$key][$value];
|
||||
}
|
||||
}
|
||||
|
||||
private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
|
||||
{
|
||||
$styleArray = [];
|
||||
if ($borderAttributes !== null) {
|
||||
if (isset($borderAttributes['Color'])) {
|
||||
$styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
|
||||
}
|
||||
|
||||
self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']);
|
||||
}
|
||||
|
||||
return $styleArray;
|
||||
}
|
||||
|
||||
private static function parseGnumericColour(string $gnmColour): string
|
||||
{
|
||||
[$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
|
||||
$gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||
$gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||
$gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
|
||||
|
||||
return $gnmR . $gnmG . $gnmB;
|
||||
}
|
||||
|
||||
private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
|
||||
{
|
||||
$RGB = self::parseGnumericColour((string) $styleAttributes['Fore']);
|
||||
$styleArray['font']['color']['rgb'] = $RGB;
|
||||
$RGB = self::parseGnumericColour((string) $styleAttributes['Back']);
|
||||
$shade = (string) $styleAttributes['Shade'];
|
||||
if (($RGB !== '000000') || ($shade !== '0')) {
|
||||
$RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']);
|
||||
if ($shade === '1') {
|
||||
$styleArray['fill']['startColor']['rgb'] = $RGB;
|
||||
$styleArray['fill']['endColor']['rgb'] = $RGB2;
|
||||
} else {
|
||||
$styleArray['fill']['endColor']['rgb'] = $RGB;
|
||||
$styleArray['fill']['startColor']['rgb'] = $RGB2;
|
||||
}
|
||||
self::addStyle2($styleArray, 'fill', 'fillType', $shade);
|
||||
}
|
||||
}
|
||||
|
||||
private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
|
||||
{
|
||||
$startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
|
||||
$startRow = $styleAttributes['startRow'] + 1;
|
||||
|
||||
$endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
|
||||
$endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
|
||||
|
||||
$endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
|
||||
$cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
|
||||
|
||||
return $cellRange;
|
||||
}
|
||||
|
||||
private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
|
||||
{
|
||||
self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']);
|
||||
self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']);
|
||||
$styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
|
||||
$styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
|
||||
$styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
|
||||
$styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
|
||||
|
||||
$this->addColors($styleArray, $styleAttributes);
|
||||
|
||||
$fontAttributes = $style->Style->Font->attributes();
|
||||
if ($fontAttributes !== null) {
|
||||
$styleArray['font']['name'] = (string) $style->Style->Font;
|
||||
$styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
|
||||
$styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
|
||||
$styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
|
||||
$styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
|
||||
self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']);
|
||||
|
||||
switch ($fontAttributes['Script']) {
|
||||
case '1':
|
||||
$styleArray['font']['superscript'] = true;
|
||||
|
||||
break;
|
||||
case '-1':
|
||||
$styleArray['font']['subscript'] = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($style->Style->StyleBorder)) {
|
||||
$srssb = $style->Style->StyleBorder;
|
||||
$this->addBorderStyle($srssb, $styleArray, 'top');
|
||||
$this->addBorderStyle($srssb, $styleArray, 'bottom');
|
||||
$this->addBorderStyle($srssb, $styleArray, 'left');
|
||||
$this->addBorderStyle($srssb, $styleArray, 'right');
|
||||
$this->addBorderDiagonal($srssb, $styleArray);
|
||||
}
|
||||
// TO DO
|
||||
/*
|
||||
if (isset($style->Style->HyperLink)) {
|
||||
$hyperlink = $style->Style->HyperLink->attributes();
|
||||
}
|
||||
*/
|
||||
|
||||
return $styleArray;
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
@@ -18,7 +19,6 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Throwable;
|
||||
|
||||
/** PhpSpreadsheet root directory */
|
||||
class Html extends BaseReader
|
||||
{
|
||||
/**
|
||||
@@ -122,6 +122,7 @@ class Html extends BaseReader
|
||||
], // Italic
|
||||
];
|
||||
|
||||
/** @var array */
|
||||
protected $rowspan = [];
|
||||
|
||||
/**
|
||||
@@ -135,16 +136,12 @@ class Html extends BaseReader
|
||||
|
||||
/**
|
||||
* Validate that the current file is an HTML file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename)
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
// Check if file exists
|
||||
try {
|
||||
$this->openFile($pFilename);
|
||||
$this->openFile($filename);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
@@ -159,19 +156,19 @@ class Html extends BaseReader
|
||||
return $startWithTag && $containsTags && $endsWithTag;
|
||||
}
|
||||
|
||||
private function readBeginning()
|
||||
private function readBeginning(): string
|
||||
{
|
||||
fseek($this->fileHandle, 0);
|
||||
|
||||
return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
|
||||
return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
|
||||
}
|
||||
|
||||
private function readEnding()
|
||||
private function readEnding(): string
|
||||
{
|
||||
$meta = stream_get_meta_data($this->fileHandle);
|
||||
$filename = $meta['uri'];
|
||||
|
||||
$size = filesize($filename);
|
||||
$size = (int) filesize($filename);
|
||||
if ($size === 0) {
|
||||
return '';
|
||||
}
|
||||
@@ -183,54 +180,50 @@ class Html extends BaseReader
|
||||
|
||||
fseek($this->fileHandle, $size - $blockSize);
|
||||
|
||||
return fread($this->fileHandle, $blockSize);
|
||||
return (string) fread($this->fileHandle, $blockSize);
|
||||
}
|
||||
|
||||
private static function startsWithTag($data)
|
||||
private static function startsWithTag(string $data): bool
|
||||
{
|
||||
return '<' === substr(trim($data), 0, 1);
|
||||
}
|
||||
|
||||
private static function endsWithTag($data)
|
||||
private static function endsWithTag(string $data): bool
|
||||
{
|
||||
return '>' === substr(trim($data), -1, 1);
|
||||
}
|
||||
|
||||
private static function containsTags($data)
|
||||
private static function containsTags(string $data): bool
|
||||
{
|
||||
return strlen($data) !== strlen(strip_tags($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function load($pFilename)
|
||||
public function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||
return $this->loadIntoExisting($filename, $spreadsheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input encoding.
|
||||
*
|
||||
* @deprecated no use is made of this property
|
||||
*
|
||||
* @param string $pValue Input encoding, eg: 'ANSI'
|
||||
* @param string $inputEncoding Input encoding, eg: 'ANSI'
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @deprecated no use is made of this property
|
||||
*/
|
||||
public function setInputEncoding($pValue)
|
||||
public function setInputEncoding($inputEncoding)
|
||||
{
|
||||
$this->inputEncoding = $pValue;
|
||||
$this->inputEncoding = $inputEncoding;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -238,11 +231,11 @@ class Html extends BaseReader
|
||||
/**
|
||||
* Get input encoding.
|
||||
*
|
||||
* @deprecated no use is made of this property
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @deprecated no use is made of this property
|
||||
*/
|
||||
public function getInputEncoding()
|
||||
{
|
||||
@@ -250,13 +243,17 @@ class Html extends BaseReader
|
||||
}
|
||||
|
||||
// Data Array used for testing only, should write to Spreadsheet object on completion of tests
|
||||
|
||||
/** @var array */
|
||||
protected $dataArray = [];
|
||||
|
||||
/** @var int */
|
||||
protected $tableLevel = 0;
|
||||
|
||||
/** @var array */
|
||||
protected $nestedColumn = ['A'];
|
||||
|
||||
protected function setTableStartColumn($column)
|
||||
protected function setTableStartColumn(string $column): string
|
||||
{
|
||||
if ($this->tableLevel == 0) {
|
||||
$column = 'A';
|
||||
@@ -267,18 +264,25 @@ class Html extends BaseReader
|
||||
return $this->nestedColumn[$this->tableLevel];
|
||||
}
|
||||
|
||||
protected function getTableStartColumn()
|
||||
protected function getTableStartColumn(): string
|
||||
{
|
||||
return $this->nestedColumn[$this->tableLevel];
|
||||
}
|
||||
|
||||
protected function releaseTableStartColumn()
|
||||
protected function releaseTableStartColumn(): string
|
||||
{
|
||||
--$this->tableLevel;
|
||||
|
||||
return array_pop($this->nestedColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush cell.
|
||||
*
|
||||
* @param string $column
|
||||
* @param int|string $row
|
||||
* @param mixed $cellContent
|
||||
*/
|
||||
protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
|
||||
{
|
||||
if (is_string($cellContent)) {
|
||||
@@ -301,7 +305,7 @@ class Html extends BaseReader
|
||||
private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
|
||||
{
|
||||
$attributeArray = [];
|
||||
foreach ($child->attributes as $attribute) {
|
||||
foreach (($child->attributes ?? []) as $attribute) {
|
||||
$attributeArray[$attribute->name] = $attribute->value;
|
||||
}
|
||||
|
||||
@@ -320,24 +324,25 @@ class Html extends BaseReader
|
||||
{
|
||||
if ($child->nodeName === 'title') {
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
$sheet->setTitle($cellContent, true, false);
|
||||
$sheet->setTitle($cellContent, true, true);
|
||||
$cellContent = '';
|
||||
} else {
|
||||
$this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray);
|
||||
}
|
||||
}
|
||||
|
||||
private static $spanEtc = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
|
||||
private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
|
||||
|
||||
private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
|
||||
{
|
||||
if (in_array($child->nodeName, self::$spanEtc)) {
|
||||
if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) {
|
||||
if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
|
||||
$sheet->getComment($column . $row)
|
||||
->getText()
|
||||
->createTextRun($child->textContent);
|
||||
} else {
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
}
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
|
||||
if (isset($this->formats[$child->nodeName])) {
|
||||
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
|
||||
@@ -404,11 +409,11 @@ class Html extends BaseReader
|
||||
}
|
||||
}
|
||||
|
||||
private static $h1Etc = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
|
||||
private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
|
||||
|
||||
private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
|
||||
{
|
||||
if (in_array($child->nodeName, self::$h1Etc)) {
|
||||
if (in_array((string) $child->nodeName, self::H1_ETC, true)) {
|
||||
if ($this->tableLevel > 0) {
|
||||
// If we're inside a table, replace with a \n
|
||||
$cellContent .= $cellContent ? "\n" : '';
|
||||
@@ -469,7 +474,7 @@ class Html extends BaseReader
|
||||
if ($child->nodeName === 'table') {
|
||||
$this->flushCell($sheet, $column, $row, $cellContent);
|
||||
$column = $this->setTableStartColumn($column);
|
||||
if ($this->tableLevel > 1) {
|
||||
if ($this->tableLevel > 1 && $row > 1) {
|
||||
--$row;
|
||||
}
|
||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||
@@ -527,14 +532,14 @@ class Html extends BaseReader
|
||||
private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void
|
||||
{
|
||||
if (isset($attributeArray['width'])) {
|
||||
$sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
|
||||
$sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width());
|
||||
}
|
||||
}
|
||||
|
||||
private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void
|
||||
{
|
||||
if (isset($attributeArray['height'])) {
|
||||
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
|
||||
$sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,7 +619,7 @@ class Html extends BaseReader
|
||||
{
|
||||
foreach ($element->childNodes as $child) {
|
||||
if ($child instanceof DOMText) {
|
||||
$domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
|
||||
$domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? ''));
|
||||
if (is_string($cellContent)) {
|
||||
// simply append the text if the cell content is a plain text string
|
||||
$cellContent .= $domText;
|
||||
@@ -630,32 +635,44 @@ class Html extends BaseReader
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
|
||||
{
|
||||
// Validate
|
||||
if (!$this->canRead($pFilename)) {
|
||||
throw new Exception($pFilename . ' is an Invalid HTML file.');
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an Invalid HTML file.');
|
||||
}
|
||||
|
||||
// Create a new DOM object
|
||||
$dom = new DOMDocument();
|
||||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
|
||||
$convert = $this->securityScanner->scanFile($filename);
|
||||
$lowend = "\u{80}";
|
||||
$highend = "\u{10ffff}";
|
||||
$regexp = "/[$lowend-$highend]/u";
|
||||
/** @var callable */
|
||||
$callback = [self::class, 'replaceNonAscii'];
|
||||
$convert = preg_replace_callback($regexp, $callback, $convert);
|
||||
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
|
||||
} catch (Throwable $e) {
|
||||
$loaded = false;
|
||||
}
|
||||
if ($loaded === false) {
|
||||
throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
|
||||
throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
|
||||
}
|
||||
|
||||
return $this->loadDocument($dom, $spreadsheet);
|
||||
}
|
||||
|
||||
private static function replaceNonAscii(array $matches): string
|
||||
{
|
||||
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreadsheet from content.
|
||||
*
|
||||
@@ -667,12 +684,19 @@ class Html extends BaseReader
|
||||
$dom = new DOMDocument();
|
||||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
|
||||
$convert = $this->securityScanner->scan($content);
|
||||
$lowend = "\u{80}";
|
||||
$highend = "\u{10ffff}";
|
||||
$regexp = "/[$lowend-$highend]/u";
|
||||
/** @var callable */
|
||||
$callback = [self::class, 'replaceNonAscii'];
|
||||
$convert = preg_replace_callback($regexp, $callback, $convert);
|
||||
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
|
||||
} catch (Throwable $e) {
|
||||
$loaded = false;
|
||||
}
|
||||
if ($loaded === false) {
|
||||
throw new Exception('Failed to load content as a DOM Document');
|
||||
throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
|
||||
}
|
||||
|
||||
return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
|
||||
@@ -714,13 +738,13 @@ class Html extends BaseReader
|
||||
/**
|
||||
* Set sheet index.
|
||||
*
|
||||
* @param int $pValue Sheet index
|
||||
* @param int $sheetIndex Sheet index
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSheetIndex($pValue)
|
||||
public function setSheetIndex($sheetIndex)
|
||||
{
|
||||
$this->sheetIndex = $pValue;
|
||||
$this->sheetIndex = $sheetIndex;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -735,12 +759,11 @@ class Html extends BaseReader
|
||||
* TODO :
|
||||
* - Implement to other propertie, such as border
|
||||
*
|
||||
* @param Worksheet $sheet
|
||||
* @param int $row
|
||||
* @param string $column
|
||||
* @param array $attributeArray
|
||||
*/
|
||||
private function applyInlineStyle(&$sheet, $row, $column, $attributeArray): void
|
||||
private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void
|
||||
{
|
||||
if (!isset($attributeArray['style'])) {
|
||||
return;
|
||||
@@ -773,6 +796,7 @@ class Html extends BaseReader
|
||||
$value = explode(':', $st);
|
||||
$styleName = isset($value[0]) ? trim($value[0]) : null;
|
||||
$styleValue = isset($value[1]) ? trim($value[1]) : null;
|
||||
$styleValueString = (string) $styleValue;
|
||||
|
||||
if (!$styleName) {
|
||||
continue;
|
||||
@@ -781,7 +805,7 @@ class Html extends BaseReader
|
||||
switch ($styleName) {
|
||||
case 'background':
|
||||
case 'background-color':
|
||||
$styleColor = $this->getStyleColor($styleValue);
|
||||
$styleColor = $this->getStyleColor($styleValueString);
|
||||
|
||||
if (!$styleColor) {
|
||||
continue 2;
|
||||
@@ -791,7 +815,7 @@ class Html extends BaseReader
|
||||
|
||||
break;
|
||||
case 'color':
|
||||
$styleColor = $this->getStyleColor($styleValue);
|
||||
$styleColor = $this->getStyleColor($styleValueString);
|
||||
|
||||
if (!$styleColor) {
|
||||
continue 2;
|
||||
@@ -802,27 +826,27 @@ class Html extends BaseReader
|
||||
break;
|
||||
|
||||
case 'border':
|
||||
$this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
|
||||
$this->setBorderStyle($cellStyle, $styleValueString, 'allBorders');
|
||||
|
||||
break;
|
||||
|
||||
case 'border-top':
|
||||
$this->setBorderStyle($cellStyle, $styleValue, 'top');
|
||||
$this->setBorderStyle($cellStyle, $styleValueString, 'top');
|
||||
|
||||
break;
|
||||
|
||||
case 'border-bottom':
|
||||
$this->setBorderStyle($cellStyle, $styleValue, 'bottom');
|
||||
$this->setBorderStyle($cellStyle, $styleValueString, 'bottom');
|
||||
|
||||
break;
|
||||
|
||||
case 'border-left':
|
||||
$this->setBorderStyle($cellStyle, $styleValue, 'left');
|
||||
$this->setBorderStyle($cellStyle, $styleValueString, 'left');
|
||||
|
||||
break;
|
||||
|
||||
case 'border-right':
|
||||
$this->setBorderStyle($cellStyle, $styleValue, 'right');
|
||||
$this->setBorderStyle($cellStyle, $styleValueString, 'right');
|
||||
|
||||
break;
|
||||
|
||||
@@ -848,7 +872,7 @@ class Html extends BaseReader
|
||||
break;
|
||||
|
||||
case 'font-family':
|
||||
$cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
|
||||
$cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString));
|
||||
|
||||
break;
|
||||
|
||||
@@ -867,25 +891,25 @@ class Html extends BaseReader
|
||||
break;
|
||||
|
||||
case 'text-align':
|
||||
$cellStyle->getAlignment()->setHorizontal($styleValue);
|
||||
$cellStyle->getAlignment()->setHorizontal($styleValueString);
|
||||
|
||||
break;
|
||||
|
||||
case 'vertical-align':
|
||||
$cellStyle->getAlignment()->setVertical($styleValue);
|
||||
$cellStyle->getAlignment()->setVertical($styleValueString);
|
||||
|
||||
break;
|
||||
|
||||
case 'width':
|
||||
$sheet->getColumnDimension($column)->setWidth(
|
||||
str_replace('px', '', $styleValue)
|
||||
(new CssDimension($styleValue ?? ''))->width()
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case 'height':
|
||||
$sheet->getRowDimension($row)->setRowHeight(
|
||||
str_replace('px', '', $styleValue)
|
||||
(new CssDimension($styleValue ?? ''))->height()
|
||||
);
|
||||
|
||||
break;
|
||||
@@ -899,7 +923,7 @@ class Html extends BaseReader
|
||||
|
||||
case 'text-indent':
|
||||
$cellStyle->getAlignment()->setIndent(
|
||||
(int) str_replace(['px'], '', $styleValue)
|
||||
(int) str_replace(['px'], '', $styleValueString)
|
||||
);
|
||||
|
||||
break;
|
||||
@@ -910,17 +934,18 @@ class Html extends BaseReader
|
||||
/**
|
||||
* Check if has #, so we can get clean hex.
|
||||
*
|
||||
* @param $value
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getStyleColor($value)
|
||||
{
|
||||
if (strpos($value, '#') === 0) {
|
||||
$value = (string) $value;
|
||||
if (strpos($value ?? '', '#') === 0) {
|
||||
return substr($value, 1);
|
||||
}
|
||||
|
||||
return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup((string) $value);
|
||||
return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -967,7 +992,7 @@ class Html extends BaseReader
|
||||
);
|
||||
}
|
||||
|
||||
private static $borderMappings = [
|
||||
private const BORDER_MAPPINGS = [
|
||||
'dash-dot' => Border::BORDER_DASHDOT,
|
||||
'dash-dot-dot' => Border::BORDER_DASHDOTDOT,
|
||||
'dashed' => Border::BORDER_DASHED,
|
||||
@@ -986,7 +1011,7 @@ class Html extends BaseReader
|
||||
|
||||
public static function getBorderMappings(): array
|
||||
{
|
||||
return self::$borderMappings;
|
||||
return self::BORDER_MAPPINGS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -998,7 +1023,7 @@ class Html extends BaseReader
|
||||
*/
|
||||
public function getBorderStyle($style)
|
||||
{
|
||||
return (array_key_exists($style, self::$borderMappings)) ? self::$borderMappings[$style] : null;
|
||||
return self::BORDER_MAPPINGS[$style] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1011,7 +1036,15 @@ class Html extends BaseReader
|
||||
$borderStyle = Border::BORDER_NONE;
|
||||
$color = null;
|
||||
} else {
|
||||
[, $borderStyle, $color] = explode(' ', $styleValue);
|
||||
$borderArray = explode(' ', $styleValue);
|
||||
$borderCount = count($borderArray);
|
||||
if ($borderCount >= 3) {
|
||||
$borderStyle = $borderArray[1];
|
||||
$color = $borderArray[2];
|
||||
} else {
|
||||
$borderStyle = $borderArray[0];
|
||||
$color = $borderArray[1] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$cellStyle->applyFromArray([
|
||||
|
@@ -7,11 +7,11 @@ interface IReadFilter
|
||||
/**
|
||||
* Should this cell be read?
|
||||
*
|
||||
* @param string $column Column address (as a string value like "A", or "IV")
|
||||
* @param string $columnAddress Column address (as a string value like "A", or "IV")
|
||||
* @param int $row Row number
|
||||
* @param string $worksheetName Optional worksheet name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function readCell($column, $row, $worksheetName = '');
|
||||
public function readCell($columnAddress, $row, $worksheetName = '');
|
||||
}
|
||||
|
@@ -4,6 +4,12 @@ namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
interface IReader
|
||||
{
|
||||
public const LOAD_WITH_CHARTS = 1;
|
||||
|
||||
public const READ_DATA_ONLY = 2;
|
||||
|
||||
public const SKIP_EMPTY_CELLS = 4;
|
||||
|
||||
/**
|
||||
* IReader constructor.
|
||||
*/
|
||||
@@ -11,12 +17,8 @@ interface IReader
|
||||
|
||||
/**
|
||||
* Can the current IReader read the file?
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename);
|
||||
public function canRead(string $filename): bool;
|
||||
|
||||
/**
|
||||
* Read data only?
|
||||
@@ -32,11 +34,11 @@ interface IReader
|
||||
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information.
|
||||
* Set to false (the default) to advise the Reader to read both data and formatting for cells.
|
||||
*
|
||||
* @param bool $pValue
|
||||
* @param bool $readDataOnly
|
||||
*
|
||||
* @return IReader
|
||||
*/
|
||||
public function setReadDataOnly($pValue);
|
||||
public function setReadDataOnly($readDataOnly);
|
||||
|
||||
/**
|
||||
* Read empty cells?
|
||||
@@ -52,11 +54,11 @@ interface IReader
|
||||
* Set to true (the default) to advise the Reader read data values for all cells, irrespective of value.
|
||||
* Set to false to advise the Reader to ignore cells containing a null value or an empty string.
|
||||
*
|
||||
* @param bool $pValue
|
||||
* @param bool $readEmptyCells
|
||||
*
|
||||
* @return IReader
|
||||
*/
|
||||
public function setReadEmptyCells($pValue);
|
||||
public function setReadEmptyCells($readEmptyCells);
|
||||
|
||||
/**
|
||||
* Read charts in workbook?
|
||||
@@ -74,11 +76,11 @@ interface IReader
|
||||
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
|
||||
* Set to false (the default) to discard charts.
|
||||
*
|
||||
* @param bool $pValue
|
||||
* @param bool $includeCharts
|
||||
*
|
||||
* @return IReader
|
||||
*/
|
||||
public function setIncludeCharts($pValue);
|
||||
public function setIncludeCharts($includeCharts);
|
||||
|
||||
/**
|
||||
* Get which sheets to load
|
||||
@@ -120,14 +122,19 @@ interface IReader
|
||||
*
|
||||
* @return IReader
|
||||
*/
|
||||
public function setReadFilter(IReadFilter $pValue);
|
||||
public function setReadFilter(IReadFilter $readFilter);
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename The name of the file to load
|
||||
* @param int $flags Flags that can change the behaviour of the Writer:
|
||||
* self::LOAD_WITH_CHARTS Load any charts that are defined (if the Reader supports Charts)
|
||||
* self::READ_DATA_ONLY Read only data, not style or structure information, from the file
|
||||
* self::SKIP_EMPTY_CELLS Don't read empty cells (cells that contain a null value,
|
||||
* empty string, or a string containing only whitespace characters)
|
||||
*
|
||||
* @return \PhpOffice\PhpSpreadsheet\Spreadsheet
|
||||
*/
|
||||
public function load($pFilename);
|
||||
public function load(string $filename, int $flags = 0);
|
||||
}
|
||||
|
@@ -2,17 +2,15 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use DOMAttr;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
@@ -23,11 +21,14 @@ use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Throwable;
|
||||
use XMLReader;
|
||||
use ZipArchive;
|
||||
|
||||
class Ods extends BaseReader
|
||||
{
|
||||
const INITIAL_FILE = 'content.xml';
|
||||
|
||||
/**
|
||||
* Create a new Ods Reader instance.
|
||||
*/
|
||||
@@ -39,46 +40,43 @@ class Ods extends BaseReader
|
||||
|
||||
/**
|
||||
* Can the current IReader read the file?
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename)
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
|
||||
$mimeType = 'UNKNOWN';
|
||||
|
||||
// Load file
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($pFilename) === true) {
|
||||
// check if it is an OOXML archive
|
||||
$stat = $zip->statName('mimetype');
|
||||
if ($stat && ($stat['size'] <= 255)) {
|
||||
$mimeType = $zip->getFromName($stat['name']);
|
||||
} elseif ($zip->statName('META-INF/manifest.xml')) {
|
||||
$xml = simplexml_load_string(
|
||||
$this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
$namespacesContent = $xml->getNamespaces(true);
|
||||
if (isset($namespacesContent['manifest'])) {
|
||||
$manifest = $xml->children($namespacesContent['manifest']);
|
||||
foreach ($manifest as $manifestDataSet) {
|
||||
$manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
|
||||
if ($manifestAttributes->{'full-path'} == '/') {
|
||||
$mimeType = (string) $manifestAttributes->{'media-type'};
|
||||
if (File::testFileNoThrow($filename, '')) {
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($filename) === true) {
|
||||
// check if it is an OOXML archive
|
||||
$stat = $zip->statName('mimetype');
|
||||
if (!empty($stat) && ($stat['size'] <= 255)) {
|
||||
$mimeType = $zip->getFromName($stat['name']);
|
||||
} elseif ($zip->statName('META-INF/manifest.xml')) {
|
||||
$xml = simplexml_load_string(
|
||||
$this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
$namespacesContent = $xml->getNamespaces(true);
|
||||
if (isset($namespacesContent['manifest'])) {
|
||||
$manifest = $xml->children($namespacesContent['manifest']);
|
||||
foreach ($manifest as $manifestDataSet) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
|
||||
if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') {
|
||||
$mimeType = (string) $manifestAttributes->{'media-type'};
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
|
||||
@@ -87,24 +85,19 @@ class Ods extends BaseReader
|
||||
/**
|
||||
* Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function listWorksheetNames($pFilename)
|
||||
public function listWorksheetNames($filename)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($pFilename) !== true) {
|
||||
throw new ReaderException('Could not open ' . $pFilename . ' for reading! Error opening file.');
|
||||
}
|
||||
File::assertFile($filename, self::INITIAL_FILE);
|
||||
|
||||
$worksheetNames = [];
|
||||
|
||||
$xml = new XMLReader();
|
||||
$xml->xml(
|
||||
$this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
|
||||
$this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
|
||||
null,
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
@@ -114,7 +107,7 @@ class Ods extends BaseReader
|
||||
$xml->read();
|
||||
while ($xml->read()) {
|
||||
// Quickly jump through to the office:body node
|
||||
while ($xml->name !== 'office:body') {
|
||||
while (self::getXmlName($xml) !== 'office:body') {
|
||||
if ($xml->isEmptyElement) {
|
||||
$xml->read();
|
||||
} else {
|
||||
@@ -123,12 +116,13 @@ class Ods extends BaseReader
|
||||
}
|
||||
// Now read each node until we find our first table:table node
|
||||
while ($xml->read()) {
|
||||
if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$xmlName = self::getXmlName($xml);
|
||||
if ($xmlName == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
// Loop through each table:table node reading the table:name attribute for each worksheet name
|
||||
do {
|
||||
$worksheetNames[] = $xml->getAttribute('table:name');
|
||||
$xml->next();
|
||||
} while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
|
||||
} while (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,24 +133,19 @@ class Ods extends BaseReader
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listWorksheetInfo($pFilename)
|
||||
public function listWorksheetInfo($filename)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
File::assertFile($filename, self::INITIAL_FILE);
|
||||
|
||||
$worksheetInfo = [];
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($pFilename) !== true) {
|
||||
throw new ReaderException('Could not open ' . $pFilename . ' for reading! Error opening file.');
|
||||
}
|
||||
|
||||
$xml = new XMLReader();
|
||||
$xml->xml(
|
||||
$this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
|
||||
$this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
|
||||
null,
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
@@ -166,7 +155,7 @@ class Ods extends BaseReader
|
||||
$xml->read();
|
||||
while ($xml->read()) {
|
||||
// Quickly jump through to the office:body node
|
||||
while ($xml->name !== 'office:body') {
|
||||
while (self::getXmlName($xml) !== 'office:body') {
|
||||
if ($xml->isEmptyElement) {
|
||||
$xml->read();
|
||||
} else {
|
||||
@@ -175,7 +164,7 @@ class Ods extends BaseReader
|
||||
}
|
||||
// Now read each node until we find our first table:table node
|
||||
while ($xml->read()) {
|
||||
if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
if (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$worksheetNames[] = $xml->getAttribute('table:name');
|
||||
|
||||
$tmpInfo = [
|
||||
@@ -190,7 +179,7 @@ class Ods extends BaseReader
|
||||
$currCells = 0;
|
||||
do {
|
||||
$xml->read();
|
||||
if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$rowspan = $xml->getAttribute('table:number-rows-repeated');
|
||||
$rowspan = empty($rowspan) ? 1 : $rowspan;
|
||||
$tmpInfo['totalRows'] += $rowspan;
|
||||
@@ -200,22 +189,22 @@ class Ods extends BaseReader
|
||||
$xml->read();
|
||||
do {
|
||||
$doread = true;
|
||||
if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
if (!$xml->isEmptyElement) {
|
||||
++$currCells;
|
||||
$xml->next();
|
||||
$doread = false;
|
||||
}
|
||||
} elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
} elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
|
||||
$mergeSize = $xml->getAttribute('table:number-columns-repeated');
|
||||
$currCells += (int) $mergeSize;
|
||||
}
|
||||
if ($doread) {
|
||||
$xml->read();
|
||||
}
|
||||
} while ($xml->name != 'table:table-row');
|
||||
} while (self::getXmlName($xml) != 'table:table-row');
|
||||
}
|
||||
} while ($xml->name != 'table:table');
|
||||
} while (self::getXmlName($xml) != 'table:table');
|
||||
|
||||
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
|
||||
$tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
|
||||
@@ -229,39 +218,40 @@ class Ods extends BaseReader
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
* Counteract Phpstan caching.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
* @phpstan-impure
|
||||
*/
|
||||
public function load($pFilename)
|
||||
private static function getXmlName(XMLReader $xml): string
|
||||
{
|
||||
return $xml->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
*/
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||
return $this->loadIntoExisting($filename, $spreadsheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
|
||||
$timezoneObj = new DateTimeZone('Europe/London');
|
||||
$GMT = new DateTimeZone('UTC');
|
||||
File::assertFile($filename, self::INITIAL_FILE);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($pFilename) !== true) {
|
||||
throw new Exception("Could not open {$pFilename} for reading! Error opening file.");
|
||||
}
|
||||
$zip->open($filename);
|
||||
|
||||
// Meta
|
||||
|
||||
@@ -292,7 +282,7 @@ class Ods extends BaseReader
|
||||
|
||||
$dom = new DOMDocument('1.01', 'UTF-8');
|
||||
$dom->loadXML(
|
||||
$this->securityScanner->scan($zip->getFromName('content.xml')),
|
||||
$this->securityScanner->scan($zip->getFromName(self::INITIAL_FILE)),
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
|
||||
@@ -303,8 +293,10 @@ class Ods extends BaseReader
|
||||
|
||||
$pageSettings->readStyleCrossReferences($dom);
|
||||
|
||||
// Content
|
||||
$autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
|
||||
$definedNameReader = new DefinedNames($spreadsheet, $tableNs);
|
||||
|
||||
// Content
|
||||
$spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
|
||||
->item(0)
|
||||
->getElementsByTagNameNS($officeNs, 'spreadsheet');
|
||||
@@ -320,7 +312,7 @@ class Ods extends BaseReader
|
||||
|
||||
// Check loadSheetsOnly
|
||||
if (
|
||||
isset($this->loadSheetsOnly)
|
||||
$this->loadSheetsOnly !== null
|
||||
&& $worksheetName
|
||||
&& !in_array($worksheetName, $this->loadSheetsOnly)
|
||||
) {
|
||||
@@ -335,7 +327,7 @@ class Ods extends BaseReader
|
||||
}
|
||||
$spreadsheet->setActiveSheetIndex($worksheetID);
|
||||
|
||||
if ($worksheetName) {
|
||||
if ($worksheetName || is_numeric($worksheetName)) {
|
||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
||||
// formula cells... during the load, all formulae should be correct, and we're simply
|
||||
// bringing the worksheet name in line with the formula, not the reverse
|
||||
@@ -373,18 +365,25 @@ class Ods extends BaseReader
|
||||
break;
|
||||
case 'table-row':
|
||||
if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
|
||||
$rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
|
||||
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
|
||||
} else {
|
||||
$rowRepeats = 1;
|
||||
}
|
||||
|
||||
$columnID = 'A';
|
||||
foreach ($childNode->childNodes as $key => $cellData) {
|
||||
// @var \DOMElement $cellData
|
||||
|
||||
/** @var DOMElement $cellData */
|
||||
foreach ($childNode->childNodes as $cellData) {
|
||||
if ($this->getReadFilter() !== null) {
|
||||
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
|
||||
++$columnID;
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
||||
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
|
||||
} else {
|
||||
$colRepeats = 1;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $colRepeats; ++$i) {
|
||||
++$columnID;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -499,22 +498,7 @@ class Ods extends BaseReader
|
||||
case 'date':
|
||||
$type = DataType::TYPE_NUMERIC;
|
||||
$value = $cellData->getAttributeNS($officeNs, 'date-value');
|
||||
|
||||
$dateObj = new DateTime($value, $GMT);
|
||||
$dateObj->setTimeZone($timezoneObj);
|
||||
[$year, $month, $day, $hour, $minute, $second] = explode(
|
||||
' ',
|
||||
$dateObj->format('Y m d H i s')
|
||||
);
|
||||
|
||||
$dataValue = Date::formattedPHPToExcel(
|
||||
(int) $year,
|
||||
(int) $month,
|
||||
(int) $day,
|
||||
(int) $hour,
|
||||
(int) $minute,
|
||||
(int) $second
|
||||
);
|
||||
$dataValue = Date::convertIsoDate($value);
|
||||
|
||||
if ($dataValue != floor($dataValue)) {
|
||||
$formatting = NumberFormat::FORMAT_DATE_XLSX15
|
||||
@@ -532,7 +516,7 @@ class Ods extends BaseReader
|
||||
|
||||
$dataValue = Date::PHPToExcel(
|
||||
strtotime(
|
||||
'01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS'))
|
||||
'01-01-1970 ' . implode(':', /** @scrutinizer ignore-type */ sscanf($timeValue, 'PT%dH%dM%dS') ?? [])
|
||||
)
|
||||
);
|
||||
$formatting = NumberFormat::FORMAT_DATE_TIME4;
|
||||
@@ -549,7 +533,7 @@ class Ods extends BaseReader
|
||||
if ($hasCalculatedValue) {
|
||||
$type = DataType::TYPE_FORMULA;
|
||||
$cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
|
||||
$cellDataFormula = $this->convertToExcelFormulaValue($cellDataFormula);
|
||||
$cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula);
|
||||
}
|
||||
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
||||
@@ -605,31 +589,7 @@ class Ods extends BaseReader
|
||||
}
|
||||
|
||||
// Merged cells
|
||||
if (
|
||||
$cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
|
||||
|| $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
|
||||
) {
|
||||
if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
|
||||
$columnTo = $columnID;
|
||||
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
|
||||
$columnIndex = Coordinate::columnIndexFromString($columnID);
|
||||
$columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
|
||||
$columnIndex -= 2;
|
||||
|
||||
$columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
|
||||
}
|
||||
|
||||
$rowTo = $rowID;
|
||||
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
|
||||
$rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
|
||||
}
|
||||
|
||||
$cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
|
||||
$spreadsheet->getActiveSheet()->mergeCells($cellRange);
|
||||
}
|
||||
}
|
||||
$this->processMergedCells($cellData, $tableNs, $type, $columnID, $rowID, $spreadsheet);
|
||||
|
||||
++$columnID;
|
||||
}
|
||||
@@ -638,18 +598,92 @@ class Ods extends BaseReader
|
||||
break;
|
||||
}
|
||||
}
|
||||
$pageSettings->setVisibilityForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
|
||||
$pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
|
||||
++$worksheetID;
|
||||
}
|
||||
|
||||
$this->readDefinedRanges($spreadsheet, $workbookData, $tableNs);
|
||||
$this->readDefinedExpressions($spreadsheet, $workbookData, $tableNs);
|
||||
$autoFilterReader->read($workbookData);
|
||||
$definedNameReader->read($workbookData);
|
||||
}
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
|
||||
if ($zip->locateName('settings.xml') !== false) {
|
||||
$this->processSettings($zip, $spreadsheet);
|
||||
}
|
||||
|
||||
// Return
|
||||
return $spreadsheet;
|
||||
}
|
||||
|
||||
private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void
|
||||
{
|
||||
$dom = new DOMDocument('1.01', 'UTF-8');
|
||||
$dom->loadXML(
|
||||
$this->securityScanner->scan($zip->getFromName('settings.xml')),
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
//$xlinkNs = $dom->lookupNamespaceUri('xlink');
|
||||
$configNs = $dom->lookupNamespaceUri('config');
|
||||
//$oooNs = $dom->lookupNamespaceUri('ooo');
|
||||
$officeNs = $dom->lookupNamespaceUri('office');
|
||||
$settings = $dom->getElementsByTagNameNS($officeNs, 'settings')
|
||||
->item(0);
|
||||
$this->lookForActiveSheet($settings, $spreadsheet, $configNs);
|
||||
$this->lookForSelectedCells($settings, $spreadsheet, $configNs);
|
||||
}
|
||||
|
||||
private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
|
||||
{
|
||||
/** @var DOMElement $t */
|
||||
foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) {
|
||||
if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') {
|
||||
try {
|
||||
$spreadsheet->setActiveSheetIndexByName($t->nodeValue ?? '');
|
||||
} catch (Throwable $e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
|
||||
{
|
||||
/** @var DOMElement $t */
|
||||
foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) {
|
||||
if ($t->getAttributeNs($configNs, 'name') === 'Tables') {
|
||||
foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) {
|
||||
$setRow = $setCol = '';
|
||||
$wsname = $ws->getAttributeNs($configNs, 'name');
|
||||
foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) {
|
||||
$attrName = $configItem->getAttributeNs($configNs, 'name');
|
||||
if ($attrName === 'CursorPositionX') {
|
||||
$setCol = $configItem->nodeValue;
|
||||
}
|
||||
if ($attrName === 'CursorPositionY') {
|
||||
$setRow = $configItem->nodeValue;
|
||||
}
|
||||
}
|
||||
$this->setSelected($spreadsheet, $wsname, "$setCol", "$setRow");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void
|
||||
{
|
||||
if (is_numeric($setCol) && is_numeric($setRow)) {
|
||||
$sheet = $spreadsheet->getSheetByName($wsname);
|
||||
if ($sheet !== null) {
|
||||
$sheet->setSelectedCells([(int) $setCol + 1, (int) $setRow + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan element.
|
||||
*
|
||||
@@ -667,13 +701,9 @@ class Ods extends BaseReader
|
||||
|
||||
// Multiple spaces?
|
||||
/** @var DOMAttr $cAttr */
|
||||
/** @scrutinizer ignore-call */
|
||||
$cAttr = $child->attributes->getNamedItem('c');
|
||||
if ($cAttr) {
|
||||
$multiplier = (int) $cAttr->nodeValue;
|
||||
} else {
|
||||
$multiplier = 1;
|
||||
}
|
||||
|
||||
$multiplier = self::getMultiplier($cAttr);
|
||||
$str .= str_repeat(' ', $multiplier);
|
||||
}
|
||||
|
||||
@@ -685,6 +715,17 @@ class Ods extends BaseReader
|
||||
return $str;
|
||||
}
|
||||
|
||||
private static function getMultiplier(?DOMAttr $cAttr): int
|
||||
{
|
||||
if ($cAttr) {
|
||||
$multiplier = (int) $cAttr->nodeValue;
|
||||
} else {
|
||||
$multiplier = 1;
|
||||
}
|
||||
|
||||
return $multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $is
|
||||
*
|
||||
@@ -698,98 +739,38 @@ class Ods extends BaseReader
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function convertToExcelAddressValue(string $openOfficeAddress): string
|
||||
{
|
||||
$excelAddress = $openOfficeAddress;
|
||||
private function processMergedCells(
|
||||
DOMElement $cellData,
|
||||
string $tableNs,
|
||||
string $type,
|
||||
string $columnID,
|
||||
int $rowID,
|
||||
Spreadsheet $spreadsheet
|
||||
): void {
|
||||
if (
|
||||
$cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
|
||||
|| $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
|
||||
) {
|
||||
if (($type !== DataType::TYPE_NULL) || ($this->readDataOnly === false)) {
|
||||
$columnTo = $columnID;
|
||||
|
||||
// Cell range 3-d reference
|
||||
// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
|
||||
// and assume that the second worksheet reference is the same as the first
|
||||
$excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu', '$1!$2:$4', $excelAddress);
|
||||
// Cell range reference in another sheet
|
||||
$excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', '$1!$2:$3', $excelAddress);
|
||||
// Cell reference in another sheet
|
||||
$excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+)/miu', '$1!$2', $excelAddress);
|
||||
// Cell range reference
|
||||
$excelAddress = preg_replace('/\.([^\.]+):\.([^\.]+)/miu', '$1:$2', $excelAddress);
|
||||
// Simple cell reference
|
||||
$excelAddress = preg_replace('/\.([^\.]+)/miu', '$1', $excelAddress);
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
|
||||
$columnIndex = Coordinate::columnIndexFromString($columnID);
|
||||
$columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
|
||||
$columnIndex -= 2;
|
||||
|
||||
return $excelAddress;
|
||||
}
|
||||
$columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
|
||||
}
|
||||
|
||||
private function convertToExcelFormulaValue(string $openOfficeFormula): string
|
||||
{
|
||||
$temp = explode('"', $openOfficeFormula);
|
||||
$tKey = false;
|
||||
foreach ($temp as &$value) {
|
||||
// Only replace in alternate array entries (i.e. non-quoted blocks)
|
||||
if ($tKey = !$tKey) {
|
||||
// Cell range reference in another sheet
|
||||
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
|
||||
// Cell reference in another sheet
|
||||
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value);
|
||||
// Cell range reference
|
||||
$value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value);
|
||||
// Simple cell reference
|
||||
$value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value);
|
||||
$rowTo = $rowID;
|
||||
|
||||
$value = Calculation::translateSeparator(';', ',', $value, $inBraces);
|
||||
if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
|
||||
$rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
|
||||
}
|
||||
|
||||
$cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
|
||||
$spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
// Then rebuild the formula string
|
||||
$excelFormula = implode('"', $temp);
|
||||
|
||||
return $excelFormula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any Named Ranges that are defined in this spreadsheet.
|
||||
*/
|
||||
private function readDefinedRanges(Spreadsheet $spreadsheet, DOMElement $workbookData, string $tableNs): void
|
||||
{
|
||||
$namedRanges = $workbookData->getElementsByTagNameNS($tableNs, 'named-range');
|
||||
foreach ($namedRanges as $definedNameElement) {
|
||||
$definedName = $definedNameElement->getAttributeNS($tableNs, 'name');
|
||||
$baseAddress = $definedNameElement->getAttributeNS($tableNs, 'base-cell-address');
|
||||
$range = $definedNameElement->getAttributeNS($tableNs, 'cell-range-address');
|
||||
|
||||
$baseAddress = $this->convertToExcelAddressValue($baseAddress);
|
||||
$range = $this->convertToExcelAddressValue($range);
|
||||
|
||||
$this->addDefinedName($spreadsheet, $baseAddress, $definedName, $range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any Named Formulae that are defined in this spreadsheet.
|
||||
*/
|
||||
private function readDefinedExpressions(Spreadsheet $spreadsheet, DOMElement $workbookData, string $tableNs): void
|
||||
{
|
||||
$namedExpressions = $workbookData->getElementsByTagNameNS($tableNs, 'named-expression');
|
||||
foreach ($namedExpressions as $definedNameElement) {
|
||||
$definedName = $definedNameElement->getAttributeNS($tableNs, 'name');
|
||||
$baseAddress = $definedNameElement->getAttributeNS($tableNs, 'base-cell-address');
|
||||
$expression = $definedNameElement->getAttributeNS($tableNs, 'expression');
|
||||
|
||||
$baseAddress = $this->convertToExcelAddressValue($baseAddress);
|
||||
$expression = $this->convertToExcelFormulaValue($expression);
|
||||
|
||||
$this->addDefinedName($spreadsheet, $baseAddress, $definedName, $expression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assess scope and store the Defined Name.
|
||||
*/
|
||||
private function addDefinedName(Spreadsheet $spreadsheet, string $baseAddress, string $definedName, string $value): void
|
||||
{
|
||||
[$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true);
|
||||
$worksheet = $spreadsheet->getSheetByName($sheetReference);
|
||||
// Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
|
||||
if ($worksheet !== null) {
|
||||
$spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php
vendored
Normal file
45
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
|
||||
class AutoFilter extends BaseLoader
|
||||
{
|
||||
public function read(DOMElement $workbookData): void
|
||||
{
|
||||
$this->readAutoFilters($workbookData);
|
||||
}
|
||||
|
||||
protected function readAutoFilters(DOMElement $workbookData): void
|
||||
{
|
||||
$databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges');
|
||||
|
||||
foreach ($databases as $autofilters) {
|
||||
foreach ($autofilters->childNodes as $autofilter) {
|
||||
$autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address');
|
||||
if ($autofilterRange !== null) {
|
||||
$baseAddress = FormulaTranslator::convertToExcelAddressValue($autofilterRange);
|
||||
$this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string
|
||||
{
|
||||
if ($node !== null && $node->attributes !== null) {
|
||||
$attribute = $node->attributes->getNamedItemNS(
|
||||
$this->tableNs,
|
||||
$attributeName
|
||||
);
|
||||
|
||||
if ($attribute !== null) {
|
||||
return $attribute->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php
vendored
Normal file
27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||
|
||||
use DOMElement;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
abstract class BaseLoader
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
*/
|
||||
protected $spreadsheet;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tableNs;
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet, string $tableNs)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
$this->tableNs = $tableNs;
|
||||
}
|
||||
|
||||
abstract public function read(DOMElement $workbookData): void;
|
||||
}
|
66
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php
vendored
Normal file
66
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||
|
||||
use DOMElement;
|
||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DefinedNames extends BaseLoader
|
||||
{
|
||||
public function read(DOMElement $workbookData): void
|
||||
{
|
||||
$this->readDefinedRanges($workbookData);
|
||||
$this->readDefinedExpressions($workbookData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any Named Ranges that are defined in this spreadsheet.
|
||||
*/
|
||||
protected function readDefinedRanges(DOMElement $workbookData): void
|
||||
{
|
||||
$namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range');
|
||||
foreach ($namedRanges as $definedNameElement) {
|
||||
$definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
|
||||
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
||||
$range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address');
|
||||
|
||||
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
|
||||
$range = FormulaTranslator::convertToExcelAddressValue($range);
|
||||
|
||||
$this->addDefinedName($baseAddress, $definedName, $range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any Named Formulae that are defined in this spreadsheet.
|
||||
*/
|
||||
protected function readDefinedExpressions(DOMElement $workbookData): void
|
||||
{
|
||||
$namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression');
|
||||
foreach ($namedExpressions as $definedNameElement) {
|
||||
$definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
|
||||
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
||||
$expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression');
|
||||
|
||||
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
|
||||
$expression = substr($expression, strpos($expression, ':=') + 1);
|
||||
$expression = FormulaTranslator::convertToExcelFormulaValue($expression);
|
||||
|
||||
$this->addDefinedName($baseAddress, $definedName, $expression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assess scope and store the Defined Name.
|
||||
*/
|
||||
private function addDefinedName(string $baseAddress, string $definedName, string $value): void
|
||||
{
|
||||
[$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true);
|
||||
$worksheet = $this->spreadsheet->getSheetByName($sheetReference);
|
||||
// Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
|
||||
if ($worksheet !== null) {
|
||||
$this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value));
|
||||
}
|
||||
}
|
||||
}
|
97
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
vendored
Normal file
97
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
|
||||
class FormulaTranslator
|
||||
{
|
||||
public static function convertToExcelAddressValue(string $openOfficeAddress): string
|
||||
{
|
||||
$excelAddress = $openOfficeAddress;
|
||||
|
||||
// Cell range 3-d reference
|
||||
// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
|
||||
// and assume that the second worksheet reference is the same as the first
|
||||
$excelAddress = (string) preg_replace(
|
||||
[
|
||||
'/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu',
|
||||
'/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', // Cell range reference in another sheet
|
||||
'/\$?([^\.]+)\.([^\.]+)/miu', // Cell reference in another sheet
|
||||
'/\.([^\.]+):\.([^\.]+)/miu', // Cell range reference
|
||||
'/\.([^\.]+)/miu', // Simple cell reference
|
||||
],
|
||||
[
|
||||
'$1!$2:$4',
|
||||
'$1!$2:$3',
|
||||
'$1!$2',
|
||||
'$1:$2',
|
||||
'$1',
|
||||
],
|
||||
$excelAddress
|
||||
);
|
||||
|
||||
return $excelAddress;
|
||||
}
|
||||
|
||||
public static function convertToExcelFormulaValue(string $openOfficeFormula): string
|
||||
{
|
||||
$temp = explode(Calculation::FORMULA_STRING_QUOTE, $openOfficeFormula);
|
||||
$tKey = false;
|
||||
$inMatrixBracesLevel = 0;
|
||||
$inFunctionBracesLevel = 0;
|
||||
foreach ($temp as &$value) {
|
||||
// @var string $value
|
||||
// Only replace in alternate array entries (i.e. non-quoted blocks)
|
||||
// so that conversion isn't done in string values
|
||||
$tKey = $tKey === false;
|
||||
if ($tKey) {
|
||||
$value = (string) preg_replace(
|
||||
[
|
||||
'/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference in another sheet
|
||||
'/\[\$?([^\.]+)\.([^\.]+)\]/miu', // Cell reference in another sheet
|
||||
'/\[\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference
|
||||
'/\[\.([^\.]+)\]/miu', // Simple cell reference
|
||||
],
|
||||
[
|
||||
'$1!$2:$3',
|
||||
'$1!$2',
|
||||
'$1:$2',
|
||||
'$1',
|
||||
],
|
||||
$value
|
||||
);
|
||||
// Convert references to defined names/formulae
|
||||
$value = str_replace('$$', '', $value);
|
||||
|
||||
// Convert ODS function argument separators to Excel function argument separators
|
||||
$value = Calculation::translateSeparator(';', ',', $value, $inFunctionBracesLevel);
|
||||
|
||||
// Convert ODS matrix separators to Excel matrix separators
|
||||
$value = Calculation::translateSeparator(
|
||||
';',
|
||||
',',
|
||||
$value,
|
||||
$inMatrixBracesLevel,
|
||||
Calculation::FORMULA_OPEN_MATRIX_BRACE,
|
||||
Calculation::FORMULA_CLOSE_MATRIX_BRACE
|
||||
);
|
||||
$value = Calculation::translateSeparator(
|
||||
'|',
|
||||
';',
|
||||
$value,
|
||||
$inMatrixBracesLevel,
|
||||
Calculation::FORMULA_OPEN_MATRIX_BRACE,
|
||||
Calculation::FORMULA_CLOSE_MATRIX_BRACE
|
||||
);
|
||||
|
||||
$value = (string) preg_replace('/COM\.MICROSOFT\./ui', '', $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Then rebuild the formula string
|
||||
$excelFormula = implode('"', $temp);
|
||||
|
||||
return $excelFormula;
|
||||
}
|
||||
}
|
@@ -8,16 +8,41 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class PageSettings
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $officeNs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $stylesNs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $stylesFo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $tableNs;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $tableStylesCrossReference = [];
|
||||
|
||||
private $pageLayoutStyles = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $masterStylesCrossReference = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $masterPrintStylesCrossReference = [];
|
||||
|
||||
public function __construct(DOMDocument $styleDom)
|
||||
@@ -32,6 +57,7 @@ class PageSettings
|
||||
$this->officeNs = $styleDom->lookupNamespaceUri('office');
|
||||
$this->stylesNs = $styleDom->lookupNamespaceUri('style');
|
||||
$this->stylesFo = $styleDom->lookupNamespaceUri('fo');
|
||||
$this->tableNs = $styleDom->lookupNamespaceUri('table');
|
||||
}
|
||||
|
||||
private function readPageSettingStyles(DOMDocument $styleDom): void
|
||||
@@ -54,10 +80,10 @@ class PageSettings
|
||||
$marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom');
|
||||
$header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0];
|
||||
$headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
|
||||
$marginHeader = $headerProperties->getAttributeNS($this->stylesFo, 'min-height');
|
||||
$marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
|
||||
$footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0];
|
||||
$footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
|
||||
$marginFooter = $footerProperties->getAttributeNS($this->stylesFo, 'min-height');
|
||||
$marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
|
||||
|
||||
$this->pageLayoutStyles[$styleName] = (object) [
|
||||
'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT,
|
||||
@@ -98,12 +124,33 @@ class PageSettings
|
||||
foreach ($styleXReferences as $styleXreferenceSet) {
|
||||
$styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name');
|
||||
$stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name');
|
||||
$styleFamilyName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'family');
|
||||
if (!empty($styleFamilyName) && $styleFamilyName === 'table') {
|
||||
$styleVisibility = 'true';
|
||||
foreach ($styleXreferenceSet->getElementsByTagNameNS($this->stylesNs, 'table-properties') as $tableProperties) {
|
||||
$styleVisibility = $tableProperties->getAttributeNS($this->tableNs, 'display');
|
||||
}
|
||||
$this->tableStylesCrossReference[$styleXRefName] = $styleVisibility;
|
||||
}
|
||||
if (!empty($stylePageLayoutName)) {
|
||||
$this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setVisibilityForWorksheet(Worksheet $worksheet, string $styleName): void
|
||||
{
|
||||
if (!array_key_exists($styleName, $this->tableStylesCrossReference)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$worksheet->setSheetState(
|
||||
$this->tableStylesCrossReference[$styleName] === 'false'
|
||||
? Worksheet::SHEETSTATE_HIDDEN
|
||||
: Worksheet::SHEETSTATE_VISIBLE
|
||||
);
|
||||
}
|
||||
|
||||
public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void
|
||||
{
|
||||
if (!array_key_exists($styleName, $this->masterStylesCrossReference)) {
|
||||
|
@@ -20,16 +20,18 @@ class Properties
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
$officeProperty = $xml->children($namespacesMeta['office']);
|
||||
foreach ($officeProperty as $officePropertyData) {
|
||||
// @var \SimpleXMLElement $officePropertyData
|
||||
if (isset($namespacesMeta['dc'])) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']);
|
||||
$this->setCoreProperties($docProps, $officePropertiesDC);
|
||||
}
|
||||
|
||||
$officePropertyMeta = (object) [];
|
||||
$officePropertyMeta = null;
|
||||
if (isset($namespacesMeta['dc'])) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
|
||||
}
|
||||
$officePropertyMeta = $officePropertyMeta ?? [];
|
||||
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
|
||||
$this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps);
|
||||
}
|
||||
@@ -55,9 +57,7 @@ class Properties
|
||||
|
||||
break;
|
||||
case 'date':
|
||||
$creationDate = strtotime($propertyValue);
|
||||
$docProps->setCreated($creationDate);
|
||||
$docProps->setModified($creationDate);
|
||||
$docProps->setModified($propertyValue);
|
||||
|
||||
break;
|
||||
case 'description':
|
||||
@@ -86,8 +86,7 @@ class Properties
|
||||
|
||||
break;
|
||||
case 'creation-date':
|
||||
$creationDate = strtotime($propertyValue);
|
||||
$docProps->setCreated($creationDate);
|
||||
$docProps->setCreated($propertyValue);
|
||||
|
||||
break;
|
||||
case 'user-defined':
|
||||
|
@@ -3,7 +3,6 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Security;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
|
||||
class XmlScanner
|
||||
{
|
||||
@@ -18,6 +17,11 @@ class XmlScanner
|
||||
|
||||
private static $libxmlDisableEntityLoaderValue;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $shutdownRegistered = false;
|
||||
|
||||
public function __construct($pattern = '<!DOCTYPE')
|
||||
{
|
||||
$this->pattern = $pattern;
|
||||
@@ -25,7 +29,10 @@ class XmlScanner
|
||||
$this->disableEntityLoaderCheck();
|
||||
|
||||
// A fatal error will bypass the destructor, so we register a shutdown here
|
||||
register_shutdown_function([__CLASS__, 'shutdown']);
|
||||
if (!self::$shutdownRegistered) {
|
||||
self::$shutdownRegistered = true;
|
||||
register_shutdown_function([__CLASS__, 'shutdown']);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance(Reader\IReader $reader)
|
||||
@@ -45,7 +52,7 @@ class XmlScanner
|
||||
|
||||
public static function threadSafeLibxmlDisableEntityLoaderAvailability()
|
||||
{
|
||||
if (PHP_MAJOR_VERSION == 7) {
|
||||
if (PHP_MAJOR_VERSION === 7) {
|
||||
switch (PHP_MINOR_VERSION) {
|
||||
case 2:
|
||||
return PHP_RELEASE_VERSION >= 1;
|
||||
@@ -63,7 +70,7 @@ class XmlScanner
|
||||
|
||||
private function disableEntityLoaderCheck(): void
|
||||
{
|
||||
if (Settings::getLibXmlDisableEntityLoader() && \PHP_VERSION_ID < 80000) {
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
$libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
|
||||
|
||||
if (self::$libxmlDisableEntityLoaderValue === null) {
|
||||
@@ -90,6 +97,17 @@ class XmlScanner
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
/** @param mixed $arg */
|
||||
private static function forceString($arg): string
|
||||
{
|
||||
return is_string($arg) ? $arg : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $xml
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function toUtf8($xml)
|
||||
{
|
||||
$pattern = '/encoding="(.*?)"/';
|
||||
@@ -97,7 +115,7 @@ class XmlScanner
|
||||
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
|
||||
|
||||
if ($charset !== 'UTF-8') {
|
||||
$xml = mb_convert_encoding($xml, 'UTF-8', $charset);
|
||||
$xml = self::forceString(mb_convert_encoding($xml, 'UTF-8', $charset));
|
||||
|
||||
$result = preg_match($pattern, $xml, $matches);
|
||||
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
|
||||
@@ -112,18 +130,21 @@ class XmlScanner
|
||||
/**
|
||||
* Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks.
|
||||
*
|
||||
* @param mixed $xml
|
||||
* @param false|string $xml
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function scan($xml)
|
||||
{
|
||||
if (!is_string($xml)) {
|
||||
$xml = '';
|
||||
}
|
||||
$this->disableEntityLoaderCheck();
|
||||
|
||||
$xml = $this->toUtf8($xml);
|
||||
|
||||
// Don't rely purely on libxml_disable_entity_loader()
|
||||
$pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/';
|
||||
$pattern = '/\\0?' . implode('\\0?', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\\0?/';
|
||||
|
||||
if (preg_match($pattern, $xml)) {
|
||||
throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
|
||||
@@ -65,21 +64,17 @@ class Slk extends BaseReader
|
||||
|
||||
/**
|
||||
* Validate that the current file is a SYLK file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename)
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
try {
|
||||
$this->openFile($pFilename);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->openFile($filename);
|
||||
} catch (ReaderException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read sample data (first 2 KB will do)
|
||||
$data = fread($this->fileHandle, 2048);
|
||||
$data = (string) fread($this->fileHandle, 2048);
|
||||
|
||||
// Count delimiters in file
|
||||
$delimiterCount = substr_count($data, ';');
|
||||
@@ -94,12 +89,12 @@ class Slk extends BaseReader
|
||||
return $hasDelimiter && $hasId;
|
||||
}
|
||||
|
||||
private function canReadOrBust(string $pFilename): void
|
||||
private function canReadOrBust(string $filename): void
|
||||
{
|
||||
if (!$this->canRead($pFilename)) {
|
||||
throw new ReaderException($pFilename . ' is an Invalid SYLK file.');
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new ReaderException($filename . ' is an Invalid SYLK file.');
|
||||
}
|
||||
$this->openFile($pFilename);
|
||||
$this->openFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,15 +102,15 @@ class Slk extends BaseReader
|
||||
*
|
||||
* @deprecated no use is made of this property
|
||||
*
|
||||
* @param string $pValue Input encoding, eg: 'ANSI'
|
||||
* @param string $inputEncoding Input encoding, eg: 'ANSI'
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setInputEncoding($pValue)
|
||||
public function setInputEncoding($inputEncoding)
|
||||
{
|
||||
$this->inputEncoding = $pValue;
|
||||
$this->inputEncoding = $inputEncoding;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -137,19 +132,19 @@ class Slk extends BaseReader
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listWorksheetInfo($pFilename)
|
||||
public function listWorksheetInfo($filename)
|
||||
{
|
||||
// Open file
|
||||
$this->canReadOrBust($pFilename);
|
||||
$this->canReadOrBust($filename);
|
||||
$fileHandle = $this->fileHandle;
|
||||
rewind($fileHandle);
|
||||
|
||||
$worksheetInfo = [];
|
||||
$worksheetInfo[0]['worksheetName'] = basename($pFilename, '.slk');
|
||||
$worksheetInfo[0]['worksheetName'] = basename($filename, '.slk');
|
||||
|
||||
// loop through one row (line) at a time in the file
|
||||
$rowIndex = 0;
|
||||
@@ -169,7 +164,7 @@ class Slk extends BaseReader
|
||||
foreach ($rowData as $rowDatum) {
|
||||
switch ($rowDatum[0]) {
|
||||
case 'X':
|
||||
$columnIndex = substr($rowDatum, 1) - 1;
|
||||
$columnIndex = (int) substr($rowDatum, 1) - 1;
|
||||
|
||||
break;
|
||||
case 'Y':
|
||||
@@ -196,21 +191,17 @@ class Slk extends BaseReader
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function load($pFilename)
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||
return $this->loadIntoExisting($filename, $spreadsheet);
|
||||
}
|
||||
|
||||
private $colorArray = [
|
||||
private const COLOR_ARRAY = [
|
||||
'FF00FFFF', // 0 - cyan
|
||||
'FF000000', // 1 - black
|
||||
'FFFFFFFF', // 2 - white
|
||||
@@ -221,7 +212,7 @@ class Slk extends BaseReader
|
||||
'FFFF00FF', // 7 - magenta
|
||||
];
|
||||
|
||||
private $fontStyleMappings = [
|
||||
private const FONT_STYLE_MAPPINGS = [
|
||||
'B' => 'bold',
|
||||
'I' => 'italic',
|
||||
'U' => 'underline',
|
||||
@@ -235,7 +226,8 @@ class Slk extends BaseReader
|
||||
$key = false;
|
||||
foreach ($temp as &$value) {
|
||||
// Only count/replace in alternate array entries
|
||||
if ($key = !$key) {
|
||||
$key = $key === false;
|
||||
if ($key) {
|
||||
preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
|
||||
// Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
|
||||
// through the formula from left to right. Reversing means that we work right to left.through
|
||||
@@ -251,7 +243,7 @@ class Slk extends BaseReader
|
||||
}
|
||||
// Bracketed R references are relative to the current row
|
||||
if ($rowReference[0] == '[') {
|
||||
$rowReference = $row + trim($rowReference, '[]');
|
||||
$rowReference = (int) $row + (int) trim($rowReference, '[]');
|
||||
}
|
||||
$columnReference = $cellReference[4][0];
|
||||
// Empty C reference is the current column
|
||||
@@ -260,9 +252,9 @@ class Slk extends BaseReader
|
||||
}
|
||||
// Bracketed C references are relative to the current column
|
||||
if ($columnReference[0] == '[') {
|
||||
$columnReference = $column + trim($columnReference, '[]');
|
||||
$columnReference = (int) $column + (int) trim($columnReference, '[]');
|
||||
}
|
||||
$A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
|
||||
$A1CellReference = Coordinate::stringFromColumnIndex((int) $columnReference) . $rowReference;
|
||||
|
||||
$value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
|
||||
}
|
||||
@@ -298,6 +290,15 @@ class Slk extends BaseReader
|
||||
case 'E':
|
||||
$this->processFormula($rowDatum, $hasCalculatedValue, $cellDataFormula, $row, $column);
|
||||
|
||||
break;
|
||||
case 'A':
|
||||
$comment = substr($rowDatum, 1);
|
||||
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
|
||||
$spreadsheet->getActiveSheet()
|
||||
->getComment("$columnLetter$row")
|
||||
->getText()
|
||||
->createText($comment);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -357,9 +358,9 @@ class Slk extends BaseReader
|
||||
$this->addWidth($spreadsheet, $columnWidth, $startCol, $endCol);
|
||||
}
|
||||
|
||||
private $styleSettingsFont = ['D' => 'bold', 'I' => 'italic'];
|
||||
private const STYLE_SETTINGS_FONT = ['D' => 'bold', 'I' => 'italic'];
|
||||
|
||||
private $styleSettingsBorder = [
|
||||
private const STYLE_SETTINGS_BORDER = [
|
||||
'B' => 'bottom',
|
||||
'L' => 'left',
|
||||
'R' => 'right',
|
||||
@@ -372,10 +373,10 @@ class Slk extends BaseReader
|
||||
$iMax = strlen($styleSettings);
|
||||
for ($i = 0; $i < $iMax; ++$i) {
|
||||
$char = $styleSettings[$i];
|
||||
if (array_key_exists($char, $this->styleSettingsFont)) {
|
||||
$styleData['font'][$this->styleSettingsFont[$char]] = true;
|
||||
} elseif (array_key_exists($char, $this->styleSettingsBorder)) {
|
||||
$styleData['borders'][$this->styleSettingsBorder[$char]]['borderStyle'] = Border::BORDER_THIN;
|
||||
if (array_key_exists($char, self::STYLE_SETTINGS_FONT)) {
|
||||
$styleData['font'][self::STYLE_SETTINGS_FONT[$char]] = true;
|
||||
} elseif (array_key_exists($char, self::STYLE_SETTINGS_BORDER)) {
|
||||
$styleData['borders'][self::STYLE_SETTINGS_BORDER[$char]]['borderStyle'] = Border::BORDER_THIN;
|
||||
} elseif ($char == 'S') {
|
||||
$styleData['fill']['fillType'] = \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_PATTERN_GRAY125;
|
||||
} elseif ($char == 'M') {
|
||||
@@ -409,7 +410,7 @@ class Slk extends BaseReader
|
||||
private function addStyle(Spreadsheet &$spreadsheet, array $styleData, string $row, string $column): void
|
||||
{
|
||||
if ((!empty($styleData)) && $column > '' && $row > '') {
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($column);
|
||||
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
|
||||
$spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData);
|
||||
}
|
||||
}
|
||||
@@ -419,14 +420,14 @@ class Slk extends BaseReader
|
||||
if ($columnWidth > '') {
|
||||
if ($startCol == $endCol) {
|
||||
$startCol = Coordinate::stringFromColumnIndex((int) $startCol);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth);
|
||||
} else {
|
||||
$startCol = Coordinate::stringFromColumnIndex($startCol);
|
||||
$endCol = Coordinate::stringFromColumnIndex($endCol);
|
||||
$startCol = Coordinate::stringFromColumnIndex((int) $startCol);
|
||||
$endCol = Coordinate::stringFromColumnIndex((int) $endCol);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth);
|
||||
do {
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth);
|
||||
} while ($startCol != $endCol);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth);
|
||||
} while ($startCol !== $endCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,7 +450,7 @@ class Slk extends BaseReader
|
||||
|
||||
break;
|
||||
case 'M':
|
||||
$formatArray['font']['size'] = substr($rowDatum, 1) / 20;
|
||||
$formatArray['font']['size'] = ((float) substr($rowDatum, 1)) / 20;
|
||||
|
||||
break;
|
||||
case 'L':
|
||||
@@ -469,7 +470,7 @@ class Slk extends BaseReader
|
||||
{
|
||||
if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) {
|
||||
$fontColor = $matches[1] % 8;
|
||||
$formatArray['font']['color']['argb'] = $this->colorArray[$fontColor];
|
||||
$formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,8 +479,8 @@ class Slk extends BaseReader
|
||||
$styleSettings = substr($rowDatum, 1);
|
||||
$iMax = strlen($styleSettings);
|
||||
for ($i = 0; $i < $iMax; ++$i) {
|
||||
if (array_key_exists($styleSettings[$i], $this->fontStyleMappings)) {
|
||||
$formatArray['font'][$this->fontStyleMappings[$styleSettings[$i]]] = true;
|
||||
if (array_key_exists($styleSettings[$i], self::FONT_STYLE_MAPPINGS)) {
|
||||
$formatArray['font'][self::FONT_STYLE_MAPPINGS[$styleSettings[$i]]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,14 +502,14 @@ class Slk extends BaseReader
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
|
||||
{
|
||||
// Open file
|
||||
$this->canReadOrBust($pFilename);
|
||||
$this->canReadOrBust($filename);
|
||||
$fileHandle = $this->fileHandle;
|
||||
rewind($fileHandle);
|
||||
|
||||
@@ -517,7 +518,7 @@ class Slk extends BaseReader
|
||||
$spreadsheet->createSheet();
|
||||
}
|
||||
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
|
||||
$spreadsheet->getActiveSheet()->setTitle(substr(basename($pFilename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH));
|
||||
$spreadsheet->getActiveSheet()->setTitle(substr(basename($filename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH));
|
||||
|
||||
// Loop through file
|
||||
$column = $row = '';
|
||||
@@ -578,13 +579,13 @@ class Slk extends BaseReader
|
||||
/**
|
||||
* Set sheet index.
|
||||
*
|
||||
* @param int $pValue Sheet index
|
||||
* @param int $sheetIndex Sheet index
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSheetIndex($pValue)
|
||||
public function setSheetIndex($sheetIndex)
|
||||
{
|
||||
$this->sheetIndex = $pValue;
|
||||
$this->sheetIndex = $sheetIndex;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ class Color
|
||||
if ($color <= 0x07 || $color >= 0x40) {
|
||||
// special built-in color
|
||||
return Color\BuiltIn::lookup($color);
|
||||
} elseif (isset($palette, $palette[$color - 8])) {
|
||||
} elseif (isset($palette[$color - 8])) {
|
||||
// palette color, color index 0x08 maps to pallete index 0
|
||||
return $palette[$color - 8];
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
|
||||
|
||||
class BIFF5
|
||||
{
|
||||
protected static $map = [
|
||||
private const BIFF5_COLOR_MAP = [
|
||||
0x08 => '000000',
|
||||
0x09 => 'FFFFFF',
|
||||
0x0A => 'FF0000',
|
||||
@@ -72,10 +72,6 @@ class BIFF5
|
||||
*/
|
||||
public static function lookup($color)
|
||||
{
|
||||
if (isset(self::$map[$color])) {
|
||||
return ['rgb' => self::$map[$color]];
|
||||
}
|
||||
|
||||
return ['rgb' => '000000'];
|
||||
return ['rgb' => self::BIFF5_COLOR_MAP[$color] ?? '000000'];
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
|
||||
|
||||
class BIFF8
|
||||
{
|
||||
protected static $map = [
|
||||
private const BIFF8_COLOR_MAP = [
|
||||
0x08 => '000000',
|
||||
0x09 => 'FFFFFF',
|
||||
0x0A => 'FF0000',
|
||||
@@ -72,10 +72,6 @@ class BIFF8
|
||||
*/
|
||||
public static function lookup($color)
|
||||
{
|
||||
if (isset(self::$map[$color])) {
|
||||
return ['rgb' => self::$map[$color]];
|
||||
}
|
||||
|
||||
return ['rgb' => '000000'];
|
||||
return ['rgb' => self::BIFF8_COLOR_MAP[$color] ?? '000000'];
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
|
||||
|
||||
class BuiltIn
|
||||
{
|
||||
protected static $map = [
|
||||
private const BUILTIN_COLOR_MAP = [
|
||||
0x00 => '000000',
|
||||
0x01 => 'FFFFFF',
|
||||
0x02 => 'FF0000',
|
||||
@@ -26,10 +26,6 @@ class BuiltIn
|
||||
*/
|
||||
public static function lookup($color)
|
||||
{
|
||||
if (isset(self::$map[$color])) {
|
||||
return ['rgb' => self::$map[$color]];
|
||||
}
|
||||
|
||||
return ['rgb' => '000000'];
|
||||
return ['rgb' => self::BUILTIN_COLOR_MAP[$color] ?? '000000'];
|
||||
}
|
||||
}
|
||||
|
49
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
vendored
Normal file
49
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
|
||||
class ConditionalFormatting
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $types = [
|
||||
0x01 => Conditional::CONDITION_CELLIS,
|
||||
0x02 => Conditional::CONDITION_EXPRESSION,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $operators = [
|
||||
0x00 => Conditional::OPERATOR_NONE,
|
||||
0x01 => Conditional::OPERATOR_BETWEEN,
|
||||
0x02 => Conditional::OPERATOR_NOTBETWEEN,
|
||||
0x03 => Conditional::OPERATOR_EQUAL,
|
||||
0x04 => Conditional::OPERATOR_NOTEQUAL,
|
||||
0x05 => Conditional::OPERATOR_GREATERTHAN,
|
||||
0x06 => Conditional::OPERATOR_LESSTHAN,
|
||||
0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
|
||||
0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
|
||||
];
|
||||
|
||||
public static function type(int $type): ?string
|
||||
{
|
||||
if (isset(self::$types[$type])) {
|
||||
return self::$types[$type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function operator(int $operator): ?string
|
||||
{
|
||||
if (isset(self::$operators[$operator])) {
|
||||
return self::$operators[$operator];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
72
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
vendored
Normal file
72
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
|
||||
class DataValidationHelper
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $types = [
|
||||
0x00 => DataValidation::TYPE_NONE,
|
||||
0x01 => DataValidation::TYPE_WHOLE,
|
||||
0x02 => DataValidation::TYPE_DECIMAL,
|
||||
0x03 => DataValidation::TYPE_LIST,
|
||||
0x04 => DataValidation::TYPE_DATE,
|
||||
0x05 => DataValidation::TYPE_TIME,
|
||||
0x06 => DataValidation::TYPE_TEXTLENGTH,
|
||||
0x07 => DataValidation::TYPE_CUSTOM,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $errorStyles = [
|
||||
0x00 => DataValidation::STYLE_STOP,
|
||||
0x01 => DataValidation::STYLE_WARNING,
|
||||
0x02 => DataValidation::STYLE_INFORMATION,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private static $operators = [
|
||||
0x00 => DataValidation::OPERATOR_BETWEEN,
|
||||
0x01 => DataValidation::OPERATOR_NOTBETWEEN,
|
||||
0x02 => DataValidation::OPERATOR_EQUAL,
|
||||
0x03 => DataValidation::OPERATOR_NOTEQUAL,
|
||||
0x04 => DataValidation::OPERATOR_GREATERTHAN,
|
||||
0x05 => DataValidation::OPERATOR_LESSTHAN,
|
||||
0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL,
|
||||
0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL,
|
||||
];
|
||||
|
||||
public static function type(int $type): ?string
|
||||
{
|
||||
if (isset(self::$types[$type])) {
|
||||
return self::$types[$type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function errorStyle(int $errorStyle): ?string
|
||||
{
|
||||
if (isset(self::$errorStyles[$errorStyle])) {
|
||||
return self::$errorStyles[$errorStyle];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function operator(int $operator): ?string
|
||||
{
|
||||
if (isset(self::$operators[$operator])) {
|
||||
return self::$operators[$operator];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
class ErrorCode
|
||||
{
|
||||
protected static $map = [
|
||||
private const ERROR_CODE_MAP = [
|
||||
0x00 => '#NULL!',
|
||||
0x07 => '#DIV/0!',
|
||||
0x0F => '#VALUE!',
|
||||
@@ -23,10 +23,6 @@ class ErrorCode
|
||||
*/
|
||||
public static function lookup($code)
|
||||
{
|
||||
if (isset(self::$map[$code])) {
|
||||
return self::$map[$code];
|
||||
}
|
||||
|
||||
return false;
|
||||
return self::ERROR_CODE_MAP[$code] ?? false;
|
||||
}
|
||||
}
|
||||
|
@@ -71,6 +71,27 @@ class Escher
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
private const WHICH_ROUTINE = [
|
||||
self::DGGCONTAINER => 'readDggContainer',
|
||||
self::DGG => 'readDgg',
|
||||
self::BSTORECONTAINER => 'readBstoreContainer',
|
||||
self::BSE => 'readBSE',
|
||||
self::BLIPJPEG => 'readBlipJPEG',
|
||||
self::BLIPPNG => 'readBlipPNG',
|
||||
self::OPT => 'readOPT',
|
||||
self::TERTIARYOPT => 'readTertiaryOPT',
|
||||
self::SPLITMENUCOLORS => 'readSplitMenuColors',
|
||||
self::DGCONTAINER => 'readDgContainer',
|
||||
self::DG => 'readDg',
|
||||
self::SPGRCONTAINER => 'readSpgrContainer',
|
||||
self::SPCONTAINER => 'readSpContainer',
|
||||
self::SPGR => 'readSpgr',
|
||||
self::SP => 'readSp',
|
||||
self::CLIENTTEXTBOX => 'readClientTextbox',
|
||||
self::CLIENTANCHOR => 'readClientAnchor',
|
||||
self::CLIENTDATA => 'readClientData',
|
||||
];
|
||||
|
||||
/**
|
||||
* Load Escher stream data. May be a partial Escher stream.
|
||||
*
|
||||
@@ -91,84 +112,9 @@ class Escher
|
||||
while ($this->pos < $this->dataSize) {
|
||||
// offset: 2; size: 2: Record Type
|
||||
$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
|
||||
|
||||
switch ($fbt) {
|
||||
case self::DGGCONTAINER:
|
||||
$this->readDggContainer();
|
||||
|
||||
break;
|
||||
case self::DGG:
|
||||
$this->readDgg();
|
||||
|
||||
break;
|
||||
case self::BSTORECONTAINER:
|
||||
$this->readBstoreContainer();
|
||||
|
||||
break;
|
||||
case self::BSE:
|
||||
$this->readBSE();
|
||||
|
||||
break;
|
||||
case self::BLIPJPEG:
|
||||
$this->readBlipJPEG();
|
||||
|
||||
break;
|
||||
case self::BLIPPNG:
|
||||
$this->readBlipPNG();
|
||||
|
||||
break;
|
||||
case self::OPT:
|
||||
$this->readOPT();
|
||||
|
||||
break;
|
||||
case self::TERTIARYOPT:
|
||||
$this->readTertiaryOPT();
|
||||
|
||||
break;
|
||||
case self::SPLITMENUCOLORS:
|
||||
$this->readSplitMenuColors();
|
||||
|
||||
break;
|
||||
case self::DGCONTAINER:
|
||||
$this->readDgContainer();
|
||||
|
||||
break;
|
||||
case self::DG:
|
||||
$this->readDg();
|
||||
|
||||
break;
|
||||
case self::SPGRCONTAINER:
|
||||
$this->readSpgrContainer();
|
||||
|
||||
break;
|
||||
case self::SPCONTAINER:
|
||||
$this->readSpContainer();
|
||||
|
||||
break;
|
||||
case self::SPGR:
|
||||
$this->readSpgr();
|
||||
|
||||
break;
|
||||
case self::SP:
|
||||
$this->readSp();
|
||||
|
||||
break;
|
||||
case self::CLIENTTEXTBOX:
|
||||
$this->readClientTextbox();
|
||||
|
||||
break;
|
||||
case self::CLIENTANCHOR:
|
||||
$this->readClientAnchor();
|
||||
|
||||
break;
|
||||
case self::CLIENTDATA:
|
||||
$this->readClientData();
|
||||
|
||||
break;
|
||||
default:
|
||||
$this->readDefault();
|
||||
|
||||
break;
|
||||
$routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault';
|
||||
if (method_exists($this, $routine)) {
|
||||
$this->$routine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,16 +127,16 @@ class Escher
|
||||
private function readDefault(): void
|
||||
{
|
||||
// offset 0; size: 2; recVer and recInstance
|
||||
$verInstance = Xls::getUInt2d($this->data, $this->pos);
|
||||
//$verInstance = Xls::getUInt2d($this->data, $this->pos);
|
||||
|
||||
// offset: 2; size: 2: Record Type
|
||||
$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
|
||||
//$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
|
||||
|
||||
// bit: 0-3; mask: 0x000F; recVer
|
||||
$recVer = (0x000F & $verInstance) >> 0;
|
||||
//$recVer = (0x000F & $verInstance) >> 0;
|
||||
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -209,7 +155,7 @@ class Escher
|
||||
|
||||
// record is a container, read contents
|
||||
$dggContainer = new DggContainer();
|
||||
$this->object->setDggContainer($dggContainer);
|
||||
$this->applyAttribute('setDggContainer', $dggContainer);
|
||||
$reader = new self($dggContainer);
|
||||
$reader->load($recordData);
|
||||
}
|
||||
@@ -220,7 +166,7 @@ class Escher
|
||||
private function readDgg(): void
|
||||
{
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -239,7 +185,7 @@ class Escher
|
||||
|
||||
// record is a container, read contents
|
||||
$bstoreContainer = new BstoreContainer();
|
||||
$this->object->setBstoreContainer($bstoreContainer);
|
||||
$this->applyAttribute('setBstoreContainer', $bstoreContainer);
|
||||
$reader = new self($bstoreContainer);
|
||||
$reader->load($recordData);
|
||||
}
|
||||
@@ -262,45 +208,45 @@ class Escher
|
||||
|
||||
// add BSE to BstoreContainer
|
||||
$BSE = new BSE();
|
||||
$this->object->addBSE($BSE);
|
||||
$this->applyAttribute('addBSE', $BSE);
|
||||
|
||||
$BSE->setBLIPType($recInstance);
|
||||
|
||||
// offset: 0; size: 1; btWin32 (MSOBLIPTYPE)
|
||||
$btWin32 = ord($recordData[0]);
|
||||
//$btWin32 = ord($recordData[0]);
|
||||
|
||||
// offset: 1; size: 1; btWin32 (MSOBLIPTYPE)
|
||||
$btMacOS = ord($recordData[1]);
|
||||
//$btMacOS = ord($recordData[1]);
|
||||
|
||||
// offset: 2; size: 16; MD4 digest
|
||||
$rgbUid = substr($recordData, 2, 16);
|
||||
//$rgbUid = substr($recordData, 2, 16);
|
||||
|
||||
// offset: 18; size: 2; tag
|
||||
$tag = Xls::getUInt2d($recordData, 18);
|
||||
//$tag = Xls::getUInt2d($recordData, 18);
|
||||
|
||||
// offset: 20; size: 4; size of BLIP in bytes
|
||||
$size = Xls::getInt4d($recordData, 20);
|
||||
//$size = Xls::getInt4d($recordData, 20);
|
||||
|
||||
// offset: 24; size: 4; number of references to this BLIP
|
||||
$cRef = Xls::getInt4d($recordData, 24);
|
||||
//$cRef = Xls::getInt4d($recordData, 24);
|
||||
|
||||
// offset: 28; size: 4; MSOFO file offset
|
||||
$foDelay = Xls::getInt4d($recordData, 28);
|
||||
//$foDelay = Xls::getInt4d($recordData, 28);
|
||||
|
||||
// offset: 32; size: 1; unused1
|
||||
$unused1 = ord($recordData[32]);
|
||||
//$unused1 = ord($recordData[32]);
|
||||
|
||||
// offset: 33; size: 1; size of nameData in bytes (including null terminator)
|
||||
$cbName = ord($recordData[33]);
|
||||
|
||||
// offset: 34; size: 1; unused2
|
||||
$unused2 = ord($recordData[34]);
|
||||
//$unused2 = ord($recordData[34]);
|
||||
|
||||
// offset: 35; size: 1; unused3
|
||||
$unused3 = ord($recordData[35]);
|
||||
//$unused3 = ord($recordData[35]);
|
||||
|
||||
// offset: 36; size: $cbName; nameData
|
||||
$nameData = substr($recordData, 36, $cbName);
|
||||
//$nameData = substr($recordData, 36, $cbName);
|
||||
|
||||
// offset: 36 + $cbName, size: var; the BLIP data
|
||||
$blipData = substr($recordData, 36 + $cbName);
|
||||
@@ -329,17 +275,17 @@ class Escher
|
||||
$pos = 0;
|
||||
|
||||
// offset: 0; size: 16; rgbUid1 (MD4 digest of)
|
||||
$rgbUid1 = substr($recordData, 0, 16);
|
||||
//$rgbUid1 = substr($recordData, 0, 16);
|
||||
$pos += 16;
|
||||
|
||||
// offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
|
||||
if (in_array($recInstance, [0x046B, 0x06E3])) {
|
||||
$rgbUid2 = substr($recordData, 16, 16);
|
||||
//$rgbUid2 = substr($recordData, 16, 16);
|
||||
$pos += 16;
|
||||
}
|
||||
|
||||
// offset: var; size: 1; tag
|
||||
$tag = ord($recordData[$pos]);
|
||||
//$tag = ord($recordData[$pos]);
|
||||
++$pos;
|
||||
|
||||
// offset: var; size: var; the raw image data
|
||||
@@ -348,7 +294,7 @@ class Escher
|
||||
$blip = new Blip();
|
||||
$blip->setData($data);
|
||||
|
||||
$this->object->setBlip($blip);
|
||||
$this->applyAttribute('setBlip', $blip);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,17 +316,17 @@ class Escher
|
||||
$pos = 0;
|
||||
|
||||
// offset: 0; size: 16; rgbUid1 (MD4 digest of)
|
||||
$rgbUid1 = substr($recordData, 0, 16);
|
||||
//$rgbUid1 = substr($recordData, 0, 16);
|
||||
$pos += 16;
|
||||
|
||||
// offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
|
||||
if ($recInstance == 0x06E1) {
|
||||
$rgbUid2 = substr($recordData, 16, 16);
|
||||
//$rgbUid2 = substr($recordData, 16, 16);
|
||||
$pos += 16;
|
||||
}
|
||||
|
||||
// offset: var; size: 1; tag
|
||||
$tag = ord($recordData[$pos]);
|
||||
//$tag = ord($recordData[$pos]);
|
||||
++$pos;
|
||||
|
||||
// offset: var; size: var; the raw image data
|
||||
@@ -389,7 +335,7 @@ class Escher
|
||||
$blip = new Blip();
|
||||
$blip->setData($data);
|
||||
|
||||
$this->object->setBlip($blip);
|
||||
$this->applyAttribute('setBlip', $blip);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,10 +365,10 @@ class Escher
|
||||
// offset: 0; size: 2; recVer and recInstance
|
||||
|
||||
// bit: 4-15; mask: 0xFFF0; recInstance
|
||||
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -434,7 +380,7 @@ class Escher
|
||||
private function readSplitMenuColors(): void
|
||||
{
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -453,9 +399,9 @@ class Escher
|
||||
|
||||
// record is a container, read contents
|
||||
$dgContainer = new DgContainer();
|
||||
$this->object->setDgContainer($dgContainer);
|
||||
$this->applyAttribute('setDgContainer', $dgContainer);
|
||||
$reader = new self($dgContainer);
|
||||
$escher = $reader->load($recordData);
|
||||
$reader->load($recordData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,7 +410,7 @@ class Escher
|
||||
private function readDg(): void
|
||||
{
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -489,13 +435,13 @@ class Escher
|
||||
if ($this->object instanceof DgContainer) {
|
||||
// DgContainer
|
||||
$this->object->setSpgrContainer($spgrContainer);
|
||||
} else {
|
||||
} elseif ($this->object instanceof SpgrContainer) {
|
||||
// SpgrContainer
|
||||
$this->object->addChild($spgrContainer);
|
||||
}
|
||||
|
||||
$reader = new self($spgrContainer);
|
||||
$escher = $reader->load($recordData);
|
||||
$reader->load($recordData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,14 +454,14 @@ class Escher
|
||||
|
||||
// add spContainer to spgrContainer
|
||||
$spContainer = new SpContainer();
|
||||
$this->object->addChild($spContainer);
|
||||
$this->applyAttribute('addChild', $spContainer);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
|
||||
// record is a container, read contents
|
||||
$reader = new self($spContainer);
|
||||
$escher = $reader->load($recordData);
|
||||
$reader->load($recordData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,7 +470,7 @@ class Escher
|
||||
private function readSpgr(): void
|
||||
{
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -538,10 +484,10 @@ class Escher
|
||||
// offset: 0; size: 2; recVer and recInstance
|
||||
|
||||
// bit: 4-15; mask: 0xFFF0; recInstance
|
||||
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -555,10 +501,10 @@ class Escher
|
||||
// offset: 0; size: 2; recVer and recInstance
|
||||
|
||||
// bit: 4-15; mask: 0xFFF0; recInstance
|
||||
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
|
||||
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -599,23 +545,22 @@ class Escher
|
||||
// offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height
|
||||
$endOffsetY = Xls::getUInt2d($recordData, 16);
|
||||
|
||||
// set the start coordinates
|
||||
$this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));
|
||||
$this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));
|
||||
$this->applyAttribute('setStartOffsetX', $startOffsetX);
|
||||
$this->applyAttribute('setStartOffsetY', $startOffsetY);
|
||||
$this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));
|
||||
$this->applyAttribute('setEndOffsetX', $endOffsetX);
|
||||
$this->applyAttribute('setEndOffsetY', $endOffsetY);
|
||||
}
|
||||
|
||||
// set the start offsetX
|
||||
$this->object->setStartOffsetX($startOffsetX);
|
||||
|
||||
// set the start offsetY
|
||||
$this->object->setStartOffsetY($startOffsetY);
|
||||
|
||||
// set the end coordinates
|
||||
$this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));
|
||||
|
||||
// set the end offsetX
|
||||
$this->object->setEndOffsetX($endOffsetX);
|
||||
|
||||
// set the end offsetY
|
||||
$this->object->setEndOffsetY($endOffsetY);
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function applyAttribute(string $name, $value): void
|
||||
{
|
||||
if (method_exists($this->object, $name)) {
|
||||
$this->object->$name($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,7 +569,7 @@ class Escher
|
||||
private function readClientData(): void
|
||||
{
|
||||
$length = Xls::getInt4d($this->data, $this->pos + 4);
|
||||
$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
//$recordData = substr($this->data, $this->pos + 8, $length);
|
||||
|
||||
// move stream pointer to next record
|
||||
$this->pos += 8 + $length;
|
||||
@@ -652,7 +597,7 @@ class Escher
|
||||
$opidOpid = (0x3FFF & $opid) >> 0;
|
||||
|
||||
// bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier
|
||||
$opidFBid = (0x4000 & $opid) >> 14;
|
||||
//$opidFBid = (0x4000 & $opid) >> 14;
|
||||
|
||||
// bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data
|
||||
$opidFComplex = (0x8000 & $opid) >> 15;
|
||||
@@ -671,7 +616,9 @@ class Escher
|
||||
$value = $op;
|
||||
}
|
||||
|
||||
$this->object->setOPT($opidOpid, $value);
|
||||
if (method_exists($this->object, 'setOPT')) {
|
||||
$this->object->setOPT($opidOpid, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,20 +4,37 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
class MD5
|
||||
{
|
||||
// Context
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $a;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $b;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $c;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $d;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $allOneBits;
|
||||
|
||||
/**
|
||||
* MD5 stream constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::$allOneBits = self::signedInt(0xffffffff);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
@@ -27,8 +44,8 @@ class MD5
|
||||
public function reset(): void
|
||||
{
|
||||
$this->a = 0x67452301;
|
||||
$this->b = 0xEFCDAB89;
|
||||
$this->c = 0x98BADCFE;
|
||||
$this->b = self::signedInt(0xEFCDAB89);
|
||||
$this->c = self::signedInt(0x98BADCFE);
|
||||
$this->d = 0x10325476;
|
||||
}
|
||||
|
||||
@@ -56,8 +73,9 @@ class MD5
|
||||
*
|
||||
* @param string $data Data to add
|
||||
*/
|
||||
public function add($data): void
|
||||
public function add(string $data): void
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$words = array_values(unpack('V16', $data));
|
||||
|
||||
$A = $this->a;
|
||||
@@ -65,10 +83,10 @@ class MD5
|
||||
$C = $this->c;
|
||||
$D = $this->d;
|
||||
|
||||
$F = ['self', 'f'];
|
||||
$G = ['self', 'g'];
|
||||
$H = ['self', 'h'];
|
||||
$I = ['self', 'i'];
|
||||
$F = [self::class, 'f'];
|
||||
$G = [self::class, 'g'];
|
||||
$H = [self::class, 'h'];
|
||||
$I = [self::class, 'i'];
|
||||
|
||||
// ROUND 1
|
||||
self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478);
|
||||
@@ -142,43 +160,51 @@ class MD5
|
||||
self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb);
|
||||
self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391);
|
||||
|
||||
$this->a = ($this->a + $A) & 0xffffffff;
|
||||
$this->b = ($this->b + $B) & 0xffffffff;
|
||||
$this->c = ($this->c + $C) & 0xffffffff;
|
||||
$this->d = ($this->d + $D) & 0xffffffff;
|
||||
$this->a = ($this->a + $A) & self::$allOneBits;
|
||||
$this->b = ($this->b + $B) & self::$allOneBits;
|
||||
$this->c = ($this->c + $C) & self::$allOneBits;
|
||||
$this->d = ($this->d + $D) & self::$allOneBits;
|
||||
}
|
||||
|
||||
private static function f($X, $Y, $Z)
|
||||
private static function f(int $X, int $Y, int $Z): int
|
||||
{
|
||||
return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z
|
||||
}
|
||||
|
||||
private static function g($X, $Y, $Z)
|
||||
private static function g(int $X, int $Y, int $Z): int
|
||||
{
|
||||
return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z
|
||||
}
|
||||
|
||||
private static function h($X, $Y, $Z)
|
||||
private static function h(int $X, int $Y, int $Z): int
|
||||
{
|
||||
return $X ^ $Y ^ $Z; // X XOR Y XOR Z
|
||||
}
|
||||
|
||||
private static function i($X, $Y, $Z)
|
||||
private static function i(int $X, int $Y, int $Z): int
|
||||
{
|
||||
return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z)
|
||||
}
|
||||
|
||||
private static function step($func, &$A, $B, $C, $D, $M, $s, $t): void
|
||||
/** @param float|int $t may be float on 32-bit system */
|
||||
private static function step(callable $func, int &$A, int $B, int $C, int $D, int $M, int $s, $t): void
|
||||
{
|
||||
$A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff;
|
||||
$t = self::signedInt($t);
|
||||
$A = (int) ($A + call_user_func($func, $B, $C, $D) + $M + $t) & self::$allOneBits;
|
||||
$A = self::rotate($A, $s);
|
||||
$A = ($B + $A) & 0xffffffff;
|
||||
$A = (int) ($B + $A) & self::$allOneBits;
|
||||
}
|
||||
|
||||
private static function rotate($decimal, $bits)
|
||||
/** @param float|int $result may be float on 32-bit system */
|
||||
private static function signedInt($result): int
|
||||
{
|
||||
return is_int($result) ? $result : (int) (PHP_INT_MIN + $result - 1 - PHP_INT_MAX);
|
||||
}
|
||||
|
||||
private static function rotate(int $decimal, int $bits): int
|
||||
{
|
||||
$binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT);
|
||||
|
||||
return bindec(substr($binary, $bits) . substr($binary, 0, $bits));
|
||||
return self::signedInt(bindec(substr($binary, $bits) . substr($binary, 0, $bits)));
|
||||
}
|
||||
}
|
||||
|
@@ -4,11 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||
|
||||
class RC4
|
||||
{
|
||||
// Context
|
||||
protected $s = [];
|
||||
/** @var int[] */
|
||||
protected $s = []; // Context
|
||||
|
||||
/** @var int */
|
||||
protected $i = 0;
|
||||
|
||||
/** @var int */
|
||||
protected $j = 0;
|
||||
|
||||
/**
|
||||
|
@@ -6,7 +6,10 @@ use PhpOffice\PhpSpreadsheet\Style\Border as StyleBorder;
|
||||
|
||||
class Border
|
||||
{
|
||||
protected static $map = [
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $borderStyleMap = [
|
||||
0x00 => StyleBorder::BORDER_NONE,
|
||||
0x01 => StyleBorder::BORDER_THIN,
|
||||
0x02 => StyleBorder::BORDER_MEDIUM,
|
||||
@@ -23,18 +26,10 @@ class Border
|
||||
0x0D => StyleBorder::BORDER_SLANTDASHDOT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Map border style
|
||||
* OpenOffice documentation: 2.5.11.
|
||||
*
|
||||
* @param int $index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function lookup($index)
|
||||
public static function lookup(int $index): string
|
||||
{
|
||||
if (isset(self::$map[$index])) {
|
||||
return self::$map[$index];
|
||||
if (isset(self::$borderStyleMap[$index])) {
|
||||
return self::$borderStyleMap[$index];
|
||||
}
|
||||
|
||||
return StyleBorder::BORDER_NONE;
|
||||
|
50
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php
vendored
Normal file
50
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
|
||||
class CellAlignment
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $horizontalAlignmentMap = [
|
||||
0 => Alignment::HORIZONTAL_GENERAL,
|
||||
1 => Alignment::HORIZONTAL_LEFT,
|
||||
2 => Alignment::HORIZONTAL_CENTER,
|
||||
3 => Alignment::HORIZONTAL_RIGHT,
|
||||
4 => Alignment::HORIZONTAL_FILL,
|
||||
5 => Alignment::HORIZONTAL_JUSTIFY,
|
||||
6 => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $verticalAlignmentMap = [
|
||||
0 => Alignment::VERTICAL_TOP,
|
||||
1 => Alignment::VERTICAL_CENTER,
|
||||
2 => Alignment::VERTICAL_BOTTOM,
|
||||
3 => Alignment::VERTICAL_JUSTIFY,
|
||||
];
|
||||
|
||||
public static function horizontal(Alignment $alignment, int $horizontal): void
|
||||
{
|
||||
if (array_key_exists($horizontal, self::$horizontalAlignmentMap)) {
|
||||
$alignment->setHorizontal(self::$horizontalAlignmentMap[$horizontal]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function vertical(Alignment $alignment, int $vertical): void
|
||||
{
|
||||
if (array_key_exists($vertical, self::$verticalAlignmentMap)) {
|
||||
$alignment->setVertical(self::$verticalAlignmentMap[$vertical]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function wrap(Alignment $alignment, int $wrap): void
|
||||
{
|
||||
$alignment->setWrapText((bool) $wrap);
|
||||
}
|
||||
}
|
39
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php
vendored
Normal file
39
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
|
||||
class CellFont
|
||||
{
|
||||
public static function escapement(Font $font, int $escapement): void
|
||||
{
|
||||
switch ($escapement) {
|
||||
case 0x0001:
|
||||
$font->setSuperscript(true);
|
||||
|
||||
break;
|
||||
case 0x0002:
|
||||
$font->setSubscript(true);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $underlineMap = [
|
||||
0x01 => Font::UNDERLINE_SINGLE,
|
||||
0x02 => Font::UNDERLINE_DOUBLE,
|
||||
0x21 => Font::UNDERLINE_SINGLEACCOUNTING,
|
||||
0x22 => Font::UNDERLINE_DOUBLEACCOUNTING,
|
||||
];
|
||||
|
||||
public static function underline(Font $font, int $underline): void
|
||||
{
|
||||
if (array_key_exists($underline, self::$underlineMap)) {
|
||||
$font->setUnderline(self::$underlineMap[$underline]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,10 @@ use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
|
||||
class FillPattern
|
||||
{
|
||||
protected static $map = [
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static $fillPatternMap = [
|
||||
0x00 => Fill::FILL_NONE,
|
||||
0x01 => Fill::FILL_SOLID,
|
||||
0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
|
||||
@@ -38,8 +41,8 @@ class FillPattern
|
||||
*/
|
||||
public static function lookup($index)
|
||||
{
|
||||
if (isset(self::$map[$index])) {
|
||||
return self::$map[$index];
|
||||
if (isset(self::$fillPatternMap[$index])) {
|
||||
return self::$fillPatternMap[$index];
|
||||
}
|
||||
|
||||
return Fill::FILL_NONE;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,33 +4,43 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class AutoFilter
|
||||
{
|
||||
private $worksheet;
|
||||
/**
|
||||
* @var Table|Worksheet
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* @var SimpleXMLElement
|
||||
*/
|
||||
private $worksheetXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml)
|
||||
/**
|
||||
* @param Table|Worksheet $parent
|
||||
*/
|
||||
public function __construct($parent, SimpleXMLElement $worksheetXml)
|
||||
{
|
||||
$this->worksheet = $workSheet;
|
||||
$this->parent = $parent;
|
||||
$this->worksheetXml = $worksheetXml;
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
// Remove all "$" in the auto filter range
|
||||
$autoFilterRange = preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref']);
|
||||
$autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
|
||||
if (strpos($autoFilterRange, ':') !== false) {
|
||||
$this->readAutoFilter($autoFilterRange, $this->worksheetXml);
|
||||
}
|
||||
}
|
||||
|
||||
private function readAutoFilter($autoFilterRange, $xmlSheet): void
|
||||
private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void
|
||||
{
|
||||
$autoFilter = $this->worksheet->getAutoFilter();
|
||||
$autoFilter = $this->parent->getAutoFilter();
|
||||
$autoFilter->setRange($autoFilterRange);
|
||||
|
||||
foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
|
||||
@@ -39,15 +49,15 @@ class AutoFilter
|
||||
if ($filterColumn->filters) {
|
||||
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
|
||||
$filters = $filterColumn->filters;
|
||||
if ((isset($filters['blank'])) && ($filters['blank'] == 1)) {
|
||||
if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
|
||||
$column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
|
||||
}
|
||||
// Standard filters are always an OR join, so no join rule needs to be set
|
||||
// Entries can be either filter elements
|
||||
foreach ($filters->filter as $filterRule) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
|
||||
$column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
|
||||
}
|
||||
|
||||
// Or Date Group elements
|
||||
@@ -61,6 +71,7 @@ class AutoFilter
|
||||
// Check for dynamic filters
|
||||
$this->readTopTenAutoFilter($filterColumn, $column);
|
||||
}
|
||||
$autoFilter->setEvaluated(true);
|
||||
}
|
||||
|
||||
private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void
|
||||
@@ -68,7 +79,7 @@ class AutoFilter
|
||||
foreach ($filters->dateGroupItem as $dateGroupItem) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(
|
||||
null,
|
||||
'',
|
||||
[
|
||||
'year' => (string) $dateGroupItem['year'],
|
||||
'month' => (string) $dateGroupItem['month'],
|
||||
@@ -82,14 +93,14 @@ class AutoFilter
|
||||
}
|
||||
}
|
||||
|
||||
private function readCustomAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
|
||||
private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
|
||||
{
|
||||
if ($filterColumn->customFilters) {
|
||||
if (isset($filterColumn, $filterColumn->customFilters)) {
|
||||
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
|
||||
$customFilters = $filterColumn->customFilters;
|
||||
// Custom filters can an AND or an OR join;
|
||||
// and there should only ever be one or two entries
|
||||
if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) {
|
||||
if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) {
|
||||
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
|
||||
}
|
||||
foreach ($customFilters->customFilter as $filterRule) {
|
||||
@@ -101,15 +112,15 @@ class AutoFilter
|
||||
}
|
||||
}
|
||||
|
||||
private function readDynamicAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
|
||||
private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
|
||||
{
|
||||
if ($filterColumn->dynamicFilter) {
|
||||
if (isset($filterColumn, $filterColumn->dynamicFilter)) {
|
||||
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
|
||||
// We should only ever have one dynamic filter
|
||||
foreach ($filterColumn->dynamicFilter as $filterRule) {
|
||||
// Operator is undefined, but always treated as EQUAL
|
||||
$column->createRule()->setRule(
|
||||
null,
|
||||
'',
|
||||
(string) $filterRule['val'],
|
||||
(string) $filterRule['type']
|
||||
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
|
||||
@@ -123,19 +134,21 @@ class AutoFilter
|
||||
}
|
||||
}
|
||||
|
||||
private function readTopTenAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
|
||||
private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
|
||||
{
|
||||
if ($filterColumn->top10) {
|
||||
if (isset($filterColumn, $filterColumn->top10)) {
|
||||
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
|
||||
// We should only ever have one top10 filter
|
||||
foreach ($filterColumn->top10 as $filterRule) {
|
||||
$column->createRule()->setRule(
|
||||
(((isset($filterRule['percent'])) && ($filterRule['percent'] == 1))
|
||||
(
|
||||
((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1'))
|
||||
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
|
||||
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
|
||||
),
|
||||
(string) $filterRule['val'],
|
||||
(((isset($filterRule['top'])) && ($filterRule['top'] == 1))
|
||||
(
|
||||
((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1'))
|
||||
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
|
||||
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
|
||||
)
|
||||
|
@@ -4,16 +4,19 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
class BaseParserClass
|
||||
{
|
||||
protected static function boolean($value)
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected static function boolean($value): bool
|
||||
{
|
||||
if (is_object($value)) {
|
||||
$value = (string) $value;
|
||||
$value = (string) $value; // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
return $value === strtolower('true');
|
||||
return $value === 'true' || $value === 'TRUE';
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,17 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class ColumnAndRowAttributes extends BaseParserClass
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var ?SimpleXMLElement */
|
||||
private $worksheetXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
|
||||
@@ -71,11 +74,7 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IReadFilter $readFilter
|
||||
* @param bool $readDataOnly
|
||||
*/
|
||||
public function load(?IReadFilter $readFilter = null, $readDataOnly = false): void
|
||||
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void
|
||||
{
|
||||
if ($this->worksheetXml === null) {
|
||||
return;
|
||||
@@ -91,6 +90,10 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
$rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
|
||||
}
|
||||
|
||||
if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) {
|
||||
$readFilter = null;
|
||||
}
|
||||
|
||||
// set columns/rows attributes
|
||||
$columnsAttributesAreSet = [];
|
||||
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
|
||||
@@ -119,7 +122,7 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes)
|
||||
private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool
|
||||
{
|
||||
foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
|
||||
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
|
||||
@@ -130,19 +133,23 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
return false;
|
||||
}
|
||||
|
||||
private function readColumnAttributes(SimpleXMLElement $worksheetCols, $readDataOnly)
|
||||
private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array
|
||||
{
|
||||
$columnAttributes = [];
|
||||
|
||||
foreach ($worksheetCols->col as $column) {
|
||||
$startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
|
||||
$endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
|
||||
++$endColumn;
|
||||
for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
|
||||
$columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
|
||||
foreach ($worksheetCols->col as $columnx) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$column = $columnx->attributes();
|
||||
if ($column !== null) {
|
||||
$startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
|
||||
$endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
|
||||
++$endColumn;
|
||||
for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
|
||||
$columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
|
||||
|
||||
if ((int) ($column['max']) == 16384) {
|
||||
break;
|
||||
if ((int) ($column['max']) == 16384) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,28 +157,31 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
return $columnAttributes;
|
||||
}
|
||||
|
||||
private function readColumnRangeAttributes(SimpleXMLElement $column, $readDataOnly)
|
||||
private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array
|
||||
{
|
||||
$columnAttributes = [];
|
||||
|
||||
if ($column['style'] && !$readDataOnly) {
|
||||
$columnAttributes['xfIndex'] = (int) $column['style'];
|
||||
if ($column !== null) {
|
||||
if (isset($column['style']) && !$readDataOnly) {
|
||||
$columnAttributes['xfIndex'] = (int) $column['style'];
|
||||
}
|
||||
if (isset($column['hidden']) && self::boolean($column['hidden'])) {
|
||||
$columnAttributes['visible'] = false;
|
||||
}
|
||||
if (isset($column['collapsed']) && self::boolean($column['collapsed'])) {
|
||||
$columnAttributes['collapsed'] = true;
|
||||
}
|
||||
if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) {
|
||||
$columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
|
||||
}
|
||||
if (isset($column['width'])) {
|
||||
$columnAttributes['width'] = (float) $column['width'];
|
||||
}
|
||||
}
|
||||
if (self::boolean($column['hidden'])) {
|
||||
$columnAttributes['visible'] = false;
|
||||
}
|
||||
if (self::boolean($column['collapsed'])) {
|
||||
$columnAttributes['collapsed'] = true;
|
||||
}
|
||||
if (((int) $column['outlineLevel']) > 0) {
|
||||
$columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
|
||||
}
|
||||
$columnAttributes['width'] = (float) $column['width'];
|
||||
|
||||
return $columnAttributes;
|
||||
}
|
||||
|
||||
private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes)
|
||||
private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool
|
||||
{
|
||||
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
|
||||
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
|
||||
@@ -182,25 +192,29 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
return false;
|
||||
}
|
||||
|
||||
private function readRowAttributes(SimpleXMLElement $worksheetRow, $readDataOnly)
|
||||
private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array
|
||||
{
|
||||
$rowAttributes = [];
|
||||
|
||||
foreach ($worksheetRow as $row) {
|
||||
if ($row['ht'] && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (self::boolean($row['hidden'])) {
|
||||
$rowAttributes[(int) $row['r']]['visible'] = false;
|
||||
}
|
||||
if (self::boolean($row['collapsed'])) {
|
||||
$rowAttributes[(int) $row['r']]['collapsed'] = true;
|
||||
}
|
||||
if ((int) $row['outlineLevel'] > 0) {
|
||||
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if ($row['s'] && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
|
||||
foreach ($worksheetRow as $rowx) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$row = $rowx->attributes();
|
||||
if ($row !== null) {
|
||||
if (isset($row['ht']) && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
|
||||
$rowAttributes[(int) $row['r']]['visible'] = false;
|
||||
}
|
||||
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
|
||||
$rowAttributes[(int) $row['r']]['collapsed'] = true;
|
||||
}
|
||||
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
|
||||
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if (isset($row['s']) && !$readDataOnly) {
|
||||
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,16 +2,30 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Conditional;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
|
||||
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
use stdClass;
|
||||
|
||||
class ConditionalStyles
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var SimpleXMLElement */
|
||||
private $worksheetXml;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $ns;
|
||||
|
||||
/** @var array */
|
||||
private $dxfs;
|
||||
|
||||
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = [])
|
||||
@@ -23,26 +37,135 @@ class ConditionalStyles
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
$selectedCells = $this->worksheet->getSelectedCells();
|
||||
|
||||
$this->setConditionalStyles(
|
||||
$this->worksheet,
|
||||
$this->readConditionalStyles($this->worksheetXml)
|
||||
$this->readConditionalStyles($this->worksheetXml),
|
||||
$this->worksheetXml->extLst
|
||||
);
|
||||
|
||||
$this->worksheet->setSelectedCells($selectedCells);
|
||||
}
|
||||
|
||||
private function readConditionalStyles($xmlSheet)
|
||||
public function loadFromExt(StyleReader $styleReader): void
|
||||
{
|
||||
$selectedCells = $this->worksheet->getSelectedCells();
|
||||
|
||||
$this->ns = $this->worksheetXml->getNamespaces(true);
|
||||
$this->setConditionalsFromExt(
|
||||
$this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader)
|
||||
);
|
||||
|
||||
$this->worksheet->setSelectedCells($selectedCells);
|
||||
}
|
||||
|
||||
private function setConditionalsFromExt(array $conditionals): void
|
||||
{
|
||||
foreach ($conditionals as $conditionalRange => $cfRules) {
|
||||
ksort($cfRules);
|
||||
// Priority is used as the key for sorting; but may not start at 0,
|
||||
// so we use array_values to reset the index after sorting.
|
||||
$this->worksheet->getStyle($conditionalRange)
|
||||
->setConditionalStyles(array_values($cfRules));
|
||||
}
|
||||
}
|
||||
|
||||
private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array
|
||||
{
|
||||
$conditionals = [];
|
||||
|
||||
if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
|
||||
$conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']);
|
||||
if (!$conditionalFormattingRuleXml->conditionalFormattings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) {
|
||||
$extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']);
|
||||
if (!$extFormattingRangeXml->sqref) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqref = (string) $extFormattingRangeXml->sqref;
|
||||
$extCfRuleXml = $extFormattingXml->cfRule;
|
||||
|
||||
$attributes = $extCfRuleXml->attributes();
|
||||
if (!$attributes) {
|
||||
continue;
|
||||
}
|
||||
$conditionType = (string) $attributes->type;
|
||||
if (
|
||||
!Conditional::isValidConditionType($conditionType) ||
|
||||
$conditionType === Conditional::CONDITION_DATABAR
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$priority = (int) $attributes->priority;
|
||||
|
||||
$conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
|
||||
$cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader);
|
||||
$conditional->setStyle($cfStyle);
|
||||
$conditionals[$sqref][$priority] = $conditional;
|
||||
}
|
||||
}
|
||||
|
||||
return $conditionals;
|
||||
}
|
||||
|
||||
private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional
|
||||
{
|
||||
$conditionType = (string) $attributes->type;
|
||||
$operatorType = (string) $attributes->operator;
|
||||
|
||||
$operands = [];
|
||||
foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
|
||||
$operands[] = (string) $cfRuleOperandsXml;
|
||||
}
|
||||
|
||||
$conditional = new Conditional();
|
||||
$conditional->setConditionType($conditionType);
|
||||
$conditional->setOperatorType($operatorType);
|
||||
if (
|
||||
$conditionType === Conditional::CONDITION_CONTAINSTEXT ||
|
||||
$conditionType === Conditional::CONDITION_NOTCONTAINSTEXT ||
|
||||
$conditionType === Conditional::CONDITION_BEGINSWITH ||
|
||||
$conditionType === Conditional::CONDITION_ENDSWITH ||
|
||||
$conditionType === Conditional::CONDITION_TIMEPERIOD
|
||||
) {
|
||||
$conditional->setText(array_pop($operands) ?? '');
|
||||
}
|
||||
$conditional->setConditions($operands);
|
||||
|
||||
return $conditional;
|
||||
}
|
||||
|
||||
private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style
|
||||
{
|
||||
$cfStyle = new Style(false, true);
|
||||
if ($extCfRuleXml->dxf) {
|
||||
$styleXML = $extCfRuleXml->dxf->children();
|
||||
|
||||
if ($styleXML->borders) {
|
||||
$styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
|
||||
}
|
||||
if ($styleXML->fill) {
|
||||
$styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
|
||||
}
|
||||
}
|
||||
|
||||
return $cfStyle;
|
||||
}
|
||||
|
||||
private function readConditionalStyles(SimpleXMLElement $xmlSheet): array
|
||||
{
|
||||
$conditionals = [];
|
||||
foreach ($xmlSheet->conditionalFormatting as $conditional) {
|
||||
foreach ($conditional->cfRule as $cfRule) {
|
||||
if (
|
||||
((string) $cfRule['type'] == Conditional::CONDITION_NONE
|
||||
|| (string) $cfRule['type'] == Conditional::CONDITION_CELLIS
|
||||
|| (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT
|
||||
|| (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS
|
||||
|| (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS
|
||||
|| (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION)
|
||||
&& isset($this->dxfs[(int) ($cfRule['dxfId'])])
|
||||
) {
|
||||
if (Conditional::isValidConditionType((string) $cfRule['type']) && isset($this->dxfs[(int) ($cfRule['dxfId'])])) {
|
||||
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
|
||||
} elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) {
|
||||
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
|
||||
}
|
||||
}
|
||||
@@ -51,23 +174,25 @@ class ConditionalStyles
|
||||
return $conditionals;
|
||||
}
|
||||
|
||||
private function setConditionalStyles(Worksheet $worksheet, array $conditionals): void
|
||||
private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void
|
||||
{
|
||||
foreach ($conditionals as $ref => $cfRules) {
|
||||
foreach ($conditionals as $cellRangeReference => $cfRules) {
|
||||
ksort($cfRules);
|
||||
$conditionalStyles = $this->readStyleRules($cfRules);
|
||||
$conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst);
|
||||
|
||||
// Extract all cell references in $ref
|
||||
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref)));
|
||||
// Extract all cell references in $cellRangeReference
|
||||
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference)));
|
||||
foreach ($cellBlocks as $cellBlock) {
|
||||
$worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function readStyleRules($cfRules)
|
||||
private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
|
||||
{
|
||||
$conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
|
||||
$conditionalStyles = [];
|
||||
|
||||
foreach ($cfRules as $cfRule) {
|
||||
$objConditional = new Conditional();
|
||||
$objConditional->setConditionType((string) $cfRule['type']);
|
||||
@@ -75,23 +200,91 @@ class ConditionalStyles
|
||||
|
||||
if ((string) $cfRule['text'] != '') {
|
||||
$objConditional->setText((string) $cfRule['text']);
|
||||
} elseif ((string) $cfRule['timePeriod'] != '') {
|
||||
$objConditional->setText((string) $cfRule['timePeriod']);
|
||||
}
|
||||
|
||||
if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
|
||||
$objConditional->setStopIfTrue(true);
|
||||
}
|
||||
|
||||
if (count($cfRule->formula) > 1) {
|
||||
foreach ($cfRule->formula as $formula) {
|
||||
$objConditional->addCondition((string) $formula);
|
||||
if (count($cfRule->formula) >= 1) {
|
||||
foreach ($cfRule->formula as $formulax) {
|
||||
$formula = (string) $formulax;
|
||||
if ($formula === 'TRUE') {
|
||||
$objConditional->addCondition(true);
|
||||
} elseif ($formula === 'FALSE') {
|
||||
$objConditional->addCondition(false);
|
||||
} else {
|
||||
$objConditional->addCondition($formula);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$objConditional->addCondition((string) $cfRule->formula);
|
||||
$objConditional->addCondition('');
|
||||
}
|
||||
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
|
||||
|
||||
if (isset($cfRule->dataBar)) {
|
||||
$objConditional->setDataBar(
|
||||
$this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line
|
||||
);
|
||||
} else {
|
||||
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
|
||||
}
|
||||
|
||||
$conditionalStyles[] = $objConditional;
|
||||
}
|
||||
|
||||
return $conditionalStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SimpleXMLElement|stdClass $cfRule
|
||||
*/
|
||||
private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar
|
||||
{
|
||||
$dataBar = new ConditionalDataBar();
|
||||
//dataBar attribute
|
||||
if (isset($cfRule->dataBar['showValue'])) {
|
||||
$dataBar->setShowValue((bool) $cfRule->dataBar['showValue']);
|
||||
}
|
||||
|
||||
//dataBar children
|
||||
//conditionalFormatValueObjects
|
||||
$cfvoXml = $cfRule->dataBar->cfvo;
|
||||
$cfvoIndex = 0;
|
||||
foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) {
|
||||
if ($cfvoIndex === 0) {
|
||||
$dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
|
||||
}
|
||||
if ($cfvoIndex === 1) {
|
||||
$dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
|
||||
}
|
||||
++$cfvoIndex;
|
||||
}
|
||||
|
||||
//color
|
||||
if (isset($cfRule->dataBar->color)) {
|
||||
$dataBar->setColor((string) $cfRule->dataBar->color['rgb']);
|
||||
}
|
||||
//extLst
|
||||
$this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions);
|
||||
|
||||
return $dataBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SimpleXMLElement|stdClass $cfRule
|
||||
*/
|
||||
private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void
|
||||
{
|
||||
if (isset($cfRule->extLst)) {
|
||||
$ns = $cfRule->extLst->getNamespaces(true);
|
||||
foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) {
|
||||
$extId = (string) $ext->children($ns['x14'])->id;
|
||||
if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') {
|
||||
$dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,10 @@ use SimpleXMLElement;
|
||||
|
||||
class DataValidations
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var SimpleXMLElement */
|
||||
private $worksheetXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml)
|
||||
@@ -22,7 +24,7 @@ class DataValidations
|
||||
{
|
||||
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
|
||||
// Uppercase coordinate
|
||||
$range = strtoupper($dataValidation['sqref']);
|
||||
$range = strtoupper((string) $dataValidation['sqref']);
|
||||
$rangeSet = explode(' ', $range);
|
||||
foreach ($rangeSet as $range) {
|
||||
$stRange = $this->worksheet->shrinkRangeToFit($range);
|
||||
@@ -34,16 +36,18 @@ class DataValidations
|
||||
$docValidation->setType((string) $dataValidation['type']);
|
||||
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
|
||||
$docValidation->setOperator((string) $dataValidation['operator']);
|
||||
$docValidation->setAllowBlank($dataValidation['allowBlank'] != 0);
|
||||
$docValidation->setShowDropDown($dataValidation['showDropDown'] == 0);
|
||||
$docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0);
|
||||
$docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0);
|
||||
$docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN));
|
||||
// showDropDown is inverted (works as hideDropDown if true)
|
||||
$docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN));
|
||||
$docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN));
|
||||
$docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN));
|
||||
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
|
||||
$docValidation->setError((string) $dataValidation['error']);
|
||||
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
|
||||
$docValidation->setPrompt((string) $dataValidation['prompt']);
|
||||
$docValidation->setFormula1((string) $dataValidation->formula1);
|
||||
$docValidation->setFormula2((string) $dataValidation->formula2);
|
||||
$docValidation->setSqref($range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,16 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Hyperlinks
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var array */
|
||||
private $hyperlinks = [];
|
||||
|
||||
public function __construct(Worksheet $workSheet)
|
||||
@@ -19,40 +22,44 @@ class Hyperlinks
|
||||
|
||||
public function readHyperlinks(SimpleXMLElement $relsWorksheet): void
|
||||
{
|
||||
foreach ($relsWorksheet->Relationship as $element) {
|
||||
if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
|
||||
$this->hyperlinks[(string) $element['Id']] = (string) $element['Target'];
|
||||
foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) {
|
||||
$element = Xlsx::getAttributes($elementx);
|
||||
if ($element->Type == Namespaces::HYPERLINK) {
|
||||
$this->hyperlinks[(string) $element->Id] = (string) $element->Target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setHyperlinks(SimpleXMLElement $worksheetXml): void
|
||||
{
|
||||
foreach ($worksheetXml->hyperlink as $hyperlink) {
|
||||
$this->setHyperlink($hyperlink, $this->worksheet);
|
||||
foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) {
|
||||
if ($hyperlink !== null) {
|
||||
$this->setHyperlink($hyperlink, $this->worksheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void
|
||||
{
|
||||
// Link url
|
||||
$linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
$linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT);
|
||||
|
||||
foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) {
|
||||
$attributes = Xlsx::getAttributes($hyperlink);
|
||||
foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) {
|
||||
$cell = $worksheet->getCell($cellReference);
|
||||
if (isset($linkRel['id'])) {
|
||||
$hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null;
|
||||
if (isset($hyperlink['location'])) {
|
||||
$hyperlinkUrl .= '#' . (string) $hyperlink['location'];
|
||||
if (isset($attributes['location'])) {
|
||||
$hyperlinkUrl .= '#' . (string) $attributes['location'];
|
||||
}
|
||||
$cell->getHyperlink()->setUrl($hyperlinkUrl);
|
||||
} elseif (isset($hyperlink['location'])) {
|
||||
$cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']);
|
||||
} elseif (isset($attributes['location'])) {
|
||||
$cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']);
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
if (isset($hyperlink['tooltip'])) {
|
||||
$cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']);
|
||||
if (isset($attributes['tooltip'])) {
|
||||
$cell->getHyperlink()->setTooltip((string) $attributes['tooltip']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
118
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
vendored
Normal file
118
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
class Namespaces
|
||||
{
|
||||
const SCHEMAS = 'http://schemas.openxmlformats.org';
|
||||
|
||||
const RELATIONSHIPS = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
||||
|
||||
// This one used in Reader\Xlsx
|
||||
const CORE_PROPERTIES = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';
|
||||
|
||||
// This one used in Reader\Xlsx\Properties
|
||||
const CORE_PROPERTIES2 = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties';
|
||||
|
||||
const THUMBNAIL = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail';
|
||||
|
||||
const THEME = 'http://schemas.openxmlformats.org/package/2006/relationships/theme';
|
||||
|
||||
const THEME2 = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme';
|
||||
|
||||
const COMPATIBILITY = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
|
||||
|
||||
const MAIN = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
||||
|
||||
const RELATIONSHIPS_DRAWING = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing';
|
||||
|
||||
const DRAWINGML = 'http://schemas.openxmlformats.org/drawingml/2006/main';
|
||||
|
||||
const CHART = 'http://schemas.openxmlformats.org/drawingml/2006/chart';
|
||||
|
||||
const CHART_ALTERNATE = 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart';
|
||||
|
||||
const RELATIONSHIPS_CHART = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart';
|
||||
|
||||
const SPREADSHEET_DRAWING = 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing';
|
||||
|
||||
const SCHEMA_OFFICE_DOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
||||
|
||||
const COMMENTS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments';
|
||||
|
||||
const RELATIONSHIPS_CUSTOM_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties';
|
||||
|
||||
const RELATIONSHIPS_EXTENDED_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties';
|
||||
|
||||
const RELATIONSHIPS_CTRLPROP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp';
|
||||
|
||||
const CUSTOM_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties';
|
||||
|
||||
const EXTENDED_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties';
|
||||
|
||||
const PROPERTIES_VTYPES = 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes';
|
||||
|
||||
const HYPERLINK = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
|
||||
|
||||
const OFFICE_DOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
|
||||
|
||||
const SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
|
||||
|
||||
const STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
|
||||
|
||||
const IMAGE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
|
||||
|
||||
const VML = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing';
|
||||
|
||||
const WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
|
||||
|
||||
const CHARTSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet';
|
||||
|
||||
const SCHEMA_MICROSOFT = 'http://schemas.microsoft.com/office/2006/relationships';
|
||||
|
||||
const EXTENSIBILITY = 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility';
|
||||
|
||||
const VBA = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
|
||||
|
||||
const VBA_SIGNATURE = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
|
||||
|
||||
const DATA_VALIDATIONS1 = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main';
|
||||
|
||||
const DATA_VALIDATIONS2 = 'http://schemas.microsoft.com/office/excel/2006/main';
|
||||
|
||||
const CONTENT_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
||||
|
||||
const RELATIONSHIPS_PRINTER_SETTINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings';
|
||||
|
||||
const RELATIONSHIPS_TABLE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table';
|
||||
|
||||
const SPREADSHEETML_AC = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac';
|
||||
|
||||
const DC_ELEMENTS = 'http://purl.org/dc/elements/1.1/';
|
||||
|
||||
const DC_TERMS = 'http://purl.org/dc/terms/';
|
||||
|
||||
const DC_DCMITYPE = 'http://purl.org/dc/dcmitype/';
|
||||
|
||||
const SCHEMA_INSTANCE = 'http://www.w3.org/2001/XMLSchema-instance';
|
||||
|
||||
const URN_EXCEL = 'urn:schemas-microsoft-com:office:excel';
|
||||
|
||||
const URN_MSOFFICE = 'urn:schemas-microsoft-com:office:office';
|
||||
|
||||
const URN_VML = 'urn:schemas-microsoft-com:vml';
|
||||
|
||||
const SCHEMA_PURL = 'http://purl.oclc.org/ooxml';
|
||||
|
||||
const PURL_OFFICE_DOCUMENT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument';
|
||||
|
||||
const PURL_RELATIONSHIPS = 'http://purl.oclc.org/ooxml/officeDocument/relationships';
|
||||
|
||||
const PURL_MAIN = 'http://purl.oclc.org/ooxml/spreadsheetml/main';
|
||||
|
||||
const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main';
|
||||
|
||||
const PURL_CHART = 'http://purl.oclc.org/ooxml/drawingml/chart';
|
||||
|
||||
const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet';
|
||||
}
|
@@ -8,8 +8,10 @@ use SimpleXMLElement;
|
||||
|
||||
class PageSetup extends BaseParserClass
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var ?SimpleXMLElement */
|
||||
private $worksheetXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
|
||||
@@ -18,16 +20,17 @@ class PageSetup extends BaseParserClass
|
||||
$this->worksheetXml = $worksheetXml;
|
||||
}
|
||||
|
||||
public function load(array $unparsedLoadedData)
|
||||
public function load(array $unparsedLoadedData): array
|
||||
{
|
||||
if (!$this->worksheetXml) {
|
||||
$worksheetXml = $this->worksheetXml;
|
||||
if ($worksheetXml === null) {
|
||||
return $unparsedLoadedData;
|
||||
}
|
||||
|
||||
$this->margins($this->worksheetXml, $this->worksheet);
|
||||
$unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData);
|
||||
$this->headerFooter($this->worksheetXml, $this->worksheet);
|
||||
$this->pageBreaks($this->worksheetXml, $this->worksheet);
|
||||
$this->margins($worksheetXml, $this->worksheet);
|
||||
$unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData);
|
||||
$this->headerFooter($worksheetXml, $this->worksheet);
|
||||
$this->pageBreaks($worksheetXml, $this->worksheet);
|
||||
|
||||
return $unparsedLoadedData;
|
||||
}
|
||||
@@ -45,7 +48,7 @@ class PageSetup extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData)
|
||||
private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array
|
||||
{
|
||||
if ($xmlSheet->pageSetup) {
|
||||
$docPageSetup = $worksheet->getPageSetup();
|
||||
@@ -75,9 +78,13 @@ class PageSetup extends BaseParserClass
|
||||
$docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']);
|
||||
}
|
||||
|
||||
$relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
|
||||
$relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
|
||||
if (isset($relAttributes['id'])) {
|
||||
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
|
||||
$relid = (string) $relAttributes['id'];
|
||||
if (substr($relid, -2) !== 'ps') {
|
||||
$relid .= 'ps';
|
||||
}
|
||||
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,8 +9,10 @@ use SimpleXMLElement;
|
||||
|
||||
class Properties
|
||||
{
|
||||
/** @var XmlScanner */
|
||||
private $securityScanner;
|
||||
|
||||
/** @var DocumentProperties */
|
||||
private $docProps;
|
||||
|
||||
public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps)
|
||||
@@ -19,28 +21,39 @@ class Properties
|
||||
$this->docProps = $docProps;
|
||||
}
|
||||
|
||||
private function extractPropertyData($propertyData)
|
||||
/**
|
||||
* @param mixed $obj
|
||||
*/
|
||||
private static function nullOrSimple($obj): ?SimpleXMLElement
|
||||
{
|
||||
return simplexml_load_string(
|
||||
return ($obj instanceof SimpleXMLElement) ? $obj : null;
|
||||
}
|
||||
|
||||
private function extractPropertyData(string $propertyData): ?SimpleXMLElement
|
||||
{
|
||||
// okay to omit namespace because everything will be processed by xpath
|
||||
$obj = simplexml_load_string(
|
||||
$this->securityScanner->scan($propertyData),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
|
||||
return self::nullOrSimple($obj);
|
||||
}
|
||||
|
||||
public function readCoreProperties($propertyData): void
|
||||
public function readCoreProperties(string $propertyData): void
|
||||
{
|
||||
$xmlCore = $this->extractPropertyData($propertyData);
|
||||
|
||||
if (is_object($xmlCore)) {
|
||||
$xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
|
||||
$xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/');
|
||||
$xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
|
||||
$xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS);
|
||||
$xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS);
|
||||
$xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2);
|
||||
|
||||
$this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
|
||||
$this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
|
||||
$this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type
|
||||
$this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type
|
||||
$this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type
|
||||
$this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type
|
||||
$this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
|
||||
$this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
|
||||
$this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
|
||||
@@ -49,7 +62,7 @@ class Properties
|
||||
}
|
||||
}
|
||||
|
||||
public function readExtendedProperties($propertyData): void
|
||||
public function readExtendedProperties(string $propertyData): void
|
||||
{
|
||||
$xmlCore = $this->extractPropertyData($propertyData);
|
||||
|
||||
@@ -63,7 +76,7 @@ class Properties
|
||||
}
|
||||
}
|
||||
|
||||
public function readCustomProperties($propertyData): void
|
||||
public function readCustomProperties(string $propertyData): void
|
||||
{
|
||||
$xmlCore = $this->extractPropertyData($propertyData);
|
||||
|
||||
@@ -85,8 +98,12 @@ class Properties
|
||||
}
|
||||
}
|
||||
|
||||
private static function getArrayItem(array $array, $key = 0)
|
||||
/**
|
||||
* @param null|array|false $array
|
||||
* @param mixed $key
|
||||
*/
|
||||
private static function getArrayItem($array, $key = 0): ?SimpleXMLElement
|
||||
{
|
||||
return $array[$key] ?? null;
|
||||
return is_array($array) ? ($array[$key] ?? null) : null;
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,10 @@ use SimpleXMLElement;
|
||||
|
||||
class SheetViewOptions extends BaseParserClass
|
||||
{
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
/** @var ?SimpleXMLElement */
|
||||
private $worksheetXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
|
||||
@@ -17,20 +19,18 @@ class SheetViewOptions extends BaseParserClass
|
||||
$this->worksheetXml = $worksheetXml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $readDataOnly
|
||||
*/
|
||||
public function load($readDataOnly = false): void
|
||||
public function load(bool $readDataOnly, Styles $styleReader): void
|
||||
{
|
||||
if ($this->worksheetXml === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->worksheetXml->sheetPr)) {
|
||||
$this->tabColor($this->worksheetXml->sheetPr);
|
||||
$this->codeName($this->worksheetXml->sheetPr);
|
||||
$this->outlines($this->worksheetXml->sheetPr);
|
||||
$this->pageSetup($this->worksheetXml->sheetPr);
|
||||
$sheetPr = $this->worksheetXml->sheetPr;
|
||||
$this->tabColor($sheetPr, $styleReader);
|
||||
$this->codeName($sheetPr);
|
||||
$this->outlines($sheetPr);
|
||||
$this->pageSetup($sheetPr);
|
||||
}
|
||||
|
||||
if (isset($this->worksheetXml->sheetFormatPr)) {
|
||||
@@ -42,15 +42,16 @@ class SheetViewOptions extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function tabColor(SimpleXMLElement $sheetPr): void
|
||||
private function tabColor(SimpleXMLElement $sheetPr, Styles $styleReader): void
|
||||
{
|
||||
if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) {
|
||||
$this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']);
|
||||
if (isset($sheetPr->tabColor)) {
|
||||
$this->worksheet->getTabColor()->setARGB($styleReader->readColor($sheetPr->tabColor));
|
||||
}
|
||||
}
|
||||
|
||||
private function codeName(SimpleXMLElement $sheetPr): void
|
||||
private function codeName(SimpleXMLElement $sheetPrx): void
|
||||
{
|
||||
$sheetPr = $sheetPrx->attributes() ?? [];
|
||||
if (isset($sheetPr['codeName'])) {
|
||||
$this->worksheet->setCodeName((string) $sheetPr['codeName'], false);
|
||||
}
|
||||
@@ -59,9 +60,10 @@ class SheetViewOptions extends BaseParserClass
|
||||
private function outlines(SimpleXMLElement $sheetPr): void
|
||||
{
|
||||
if (isset($sheetPr->outlinePr)) {
|
||||
$attr = $sheetPr->outlinePr->attributes() ?? [];
|
||||
if (
|
||||
isset($sheetPr->outlinePr['summaryRight']) &&
|
||||
!self::boolean((string) $sheetPr->outlinePr['summaryRight'])
|
||||
isset($attr['summaryRight']) &&
|
||||
!self::boolean((string) $attr['summaryRight'])
|
||||
) {
|
||||
$this->worksheet->setShowSummaryRight(false);
|
||||
} else {
|
||||
@@ -69,8 +71,8 @@ class SheetViewOptions extends BaseParserClass
|
||||
}
|
||||
|
||||
if (
|
||||
isset($sheetPr->outlinePr['summaryBelow']) &&
|
||||
!self::boolean((string) $sheetPr->outlinePr['summaryBelow'])
|
||||
isset($attr['summaryBelow']) &&
|
||||
!self::boolean((string) $attr['summaryBelow'])
|
||||
) {
|
||||
$this->worksheet->setShowSummaryBelow(false);
|
||||
} else {
|
||||
@@ -82,9 +84,10 @@ class SheetViewOptions extends BaseParserClass
|
||||
private function pageSetup(SimpleXMLElement $sheetPr): void
|
||||
{
|
||||
if (isset($sheetPr->pageSetUpPr)) {
|
||||
$attr = $sheetPr->pageSetUpPr->attributes() ?? [];
|
||||
if (
|
||||
isset($sheetPr->pageSetUpPr['fitToPage']) &&
|
||||
!self::boolean((string) $sheetPr->pageSetUpPr['fitToPage'])
|
||||
isset($attr['fitToPage']) &&
|
||||
!self::boolean((string) $attr['fitToPage'])
|
||||
) {
|
||||
$this->worksheet->getPageSetup()->setFitToPage(false);
|
||||
} else {
|
||||
@@ -93,8 +96,9 @@ class SheetViewOptions extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function sheetFormat(SimpleXMLElement $sheetFormatPr): void
|
||||
private function sheetFormat(SimpleXMLElement $sheetFormatPrx): void
|
||||
{
|
||||
$sheetFormatPr = $sheetFormatPrx->attributes() ?? [];
|
||||
if (
|
||||
isset($sheetFormatPr['customHeight']) &&
|
||||
self::boolean((string) $sheetFormatPr['customHeight']) &&
|
||||
@@ -117,18 +121,19 @@ class SheetViewOptions extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function printOptions(SimpleXMLElement $printOptions): void
|
||||
private function printOptions(SimpleXMLElement $printOptionsx): void
|
||||
{
|
||||
if (self::boolean((string) $printOptions['gridLinesSet'])) {
|
||||
$printOptions = $printOptionsx->attributes() ?? [];
|
||||
if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) {
|
||||
$this->worksheet->setShowGridlines(true);
|
||||
}
|
||||
if (self::boolean((string) $printOptions['gridLines'])) {
|
||||
if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) {
|
||||
$this->worksheet->setPrintGridlines(true);
|
||||
}
|
||||
if (self::boolean((string) $printOptions['horizontalCentered'])) {
|
||||
if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) {
|
||||
$this->worksheet->getPageSetup()->setHorizontalCentered(true);
|
||||
}
|
||||
if (self::boolean((string) $printOptions['verticalCentered'])) {
|
||||
if (isset($printOptions['verticalCentered']) && self::boolean((string) $printOptions['verticalCentered'])) {
|
||||
$this->worksheet->getPageSetup()->setVerticalCentered(true);
|
||||
}
|
||||
}
|
||||
|
@@ -3,23 +3,31 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class SheetViews extends BaseParserClass
|
||||
{
|
||||
/** @var SimpleXMLElement */
|
||||
private $sheetViewXml;
|
||||
|
||||
/** @var SimpleXMLElement */
|
||||
private $sheetViewAttributes;
|
||||
|
||||
/** @var Worksheet */
|
||||
private $worksheet;
|
||||
|
||||
public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
|
||||
{
|
||||
$this->sheetViewXml = $sheetViewXml;
|
||||
$this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes());
|
||||
$this->worksheet = $workSheet;
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
$this->topLeft();
|
||||
$this->zoomScale();
|
||||
$this->view();
|
||||
$this->gridLines();
|
||||
@@ -30,15 +38,15 @@ class SheetViews extends BaseParserClass
|
||||
if (isset($this->sheetViewXml->pane)) {
|
||||
$this->pane();
|
||||
}
|
||||
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) {
|
||||
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) {
|
||||
$this->selection();
|
||||
}
|
||||
}
|
||||
|
||||
private function zoomScale(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['zoomScale'])) {
|
||||
$zoomScale = (int) ($this->sheetViewXml['zoomScale']);
|
||||
if (isset($this->sheetViewAttributes->zoomScale)) {
|
||||
$zoomScale = (int) ($this->sheetViewAttributes->zoomScale);
|
||||
if ($zoomScale <= 0) {
|
||||
// setZoomScale will throw an Exception if the scale is less than or equals 0
|
||||
// that is OK when manually creating documents, but we should be able to read all documents
|
||||
@@ -48,8 +56,8 @@ class SheetViews extends BaseParserClass
|
||||
$this->worksheet->getSheetView()->setZoomScale($zoomScale);
|
||||
}
|
||||
|
||||
if (isset($this->sheetViewXml['zoomScaleNormal'])) {
|
||||
$zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']);
|
||||
if (isset($this->sheetViewAttributes->zoomScaleNormal)) {
|
||||
$zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal);
|
||||
if ($zoomScaleNormal <= 0) {
|
||||
// setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
|
||||
// that is OK when manually creating documents, but we should be able to read all documents
|
||||
@@ -62,43 +70,50 @@ class SheetViews extends BaseParserClass
|
||||
|
||||
private function view(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['view'])) {
|
||||
$this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']);
|
||||
if (isset($this->sheetViewAttributes->view)) {
|
||||
$this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view);
|
||||
}
|
||||
}
|
||||
|
||||
private function topLeft(): void
|
||||
{
|
||||
if (isset($this->sheetViewAttributes->topLeftCell)) {
|
||||
$this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell);
|
||||
}
|
||||
}
|
||||
|
||||
private function gridLines(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['showGridLines'])) {
|
||||
if (isset($this->sheetViewAttributes->showGridLines)) {
|
||||
$this->worksheet->setShowGridLines(
|
||||
self::boolean((string) $this->sheetViewXml['showGridLines'])
|
||||
self::boolean((string) $this->sheetViewAttributes->showGridLines)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function headers(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['showRowColHeaders'])) {
|
||||
if (isset($this->sheetViewAttributes->showRowColHeaders)) {
|
||||
$this->worksheet->setShowRowColHeaders(
|
||||
self::boolean((string) $this->sheetViewXml['showRowColHeaders'])
|
||||
self::boolean((string) $this->sheetViewAttributes->showRowColHeaders)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function direction(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['rightToLeft'])) {
|
||||
if (isset($this->sheetViewAttributes->rightToLeft)) {
|
||||
$this->worksheet->setRightToLeft(
|
||||
self::boolean((string) $this->sheetViewXml['rightToLeft'])
|
||||
self::boolean((string) $this->sheetViewAttributes->rightToLeft)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function showZeros(): void
|
||||
{
|
||||
if (isset($this->sheetViewXml['showZeros'])) {
|
||||
if (isset($this->sheetViewAttributes->showZeros)) {
|
||||
$this->worksheet->getSheetView()->setShowZeros(
|
||||
self::boolean((string) $this->sheetViewXml['showZeros'])
|
||||
self::boolean((string) $this->sheetViewAttributes->showZeros)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -108,17 +123,18 @@ class SheetViews extends BaseParserClass
|
||||
$xSplit = 0;
|
||||
$ySplit = 0;
|
||||
$topLeftCell = null;
|
||||
$paneAttributes = $this->sheetViewXml->pane->attributes();
|
||||
|
||||
if (isset($this->sheetViewXml->pane['xSplit'])) {
|
||||
$xSplit = (int) ($this->sheetViewXml->pane['xSplit']);
|
||||
if (isset($paneAttributes->xSplit)) {
|
||||
$xSplit = (int) ($paneAttributes->xSplit);
|
||||
}
|
||||
|
||||
if (isset($this->sheetViewXml->pane['ySplit'])) {
|
||||
$ySplit = (int) ($this->sheetViewXml->pane['ySplit']);
|
||||
if (isset($paneAttributes->ySplit)) {
|
||||
$ySplit = (int) ($paneAttributes->ySplit);
|
||||
}
|
||||
|
||||
if (isset($this->sheetViewXml->pane['topLeftCell'])) {
|
||||
$topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell'];
|
||||
if (isset($paneAttributes->topLeftCell)) {
|
||||
$topLeftCell = (string) $paneAttributes->topLeftCell;
|
||||
}
|
||||
|
||||
$this->worksheet->freezePane(
|
||||
@@ -129,10 +145,12 @@ class SheetViews extends BaseParserClass
|
||||
|
||||
private function selection(): void
|
||||
{
|
||||
$sqref = (string) $this->sheetViewXml->selection['sqref'];
|
||||
$sqref = explode(' ', $sqref);
|
||||
$sqref = $sqref[0];
|
||||
|
||||
$this->worksheet->setSelectedCells($sqref);
|
||||
$attributes = $this->sheetViewXml->selection->attributes();
|
||||
if ($attributes !== null) {
|
||||
$sqref = (string) $attributes->sqref;
|
||||
$sqref = explode(' ', $sqref);
|
||||
$sqref = $sqref[0];
|
||||
$this->worksheet->setSelectedCells($sqref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||
@@ -12,194 +13,321 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Protection;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
use SimpleXMLElement;
|
||||
use stdClass;
|
||||
|
||||
class Styles extends BaseParserClass
|
||||
{
|
||||
/**
|
||||
* Theme instance.
|
||||
*
|
||||
* @var Theme
|
||||
* @var ?Theme
|
||||
*/
|
||||
private static $theme = null;
|
||||
private $theme;
|
||||
|
||||
/** @var array */
|
||||
private $workbookPalette = [];
|
||||
|
||||
/** @var array */
|
||||
private $styles = [];
|
||||
|
||||
/** @var array */
|
||||
private $cellStyles = [];
|
||||
|
||||
/** @var SimpleXMLElement */
|
||||
private $styleXml;
|
||||
|
||||
public function __construct(SimpleXMLElement $styleXml)
|
||||
/** @var string */
|
||||
private $namespace = '';
|
||||
|
||||
public function setNamespace(string $namespace): void
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
public function setWorkbookPalette(array $palette): void
|
||||
{
|
||||
$this->workbookPalette = $palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast SimpleXMLElement to bool to overcome Scrutinizer problem.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function castBool($value): bool
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement
|
||||
{
|
||||
$attr = null;
|
||||
if (self::castBool($value)) {
|
||||
$attr = $value->attributes('');
|
||||
if ($attr === null || count($attr) === 0) {
|
||||
$attr = $value->attributes($this->namespace);
|
||||
}
|
||||
}
|
||||
|
||||
return Xlsx::testSimpleXml($attr);
|
||||
}
|
||||
|
||||
public function setStyleXml(SimpleXmlElement $styleXml): void
|
||||
{
|
||||
$this->styleXml = $styleXml;
|
||||
}
|
||||
|
||||
public function setStyleBaseData(?Theme $theme = null, $styles = [], $cellStyles = []): void
|
||||
public function setTheme(Theme $theme): void
|
||||
{
|
||||
self::$theme = $theme;
|
||||
$this->theme = $theme;
|
||||
}
|
||||
|
||||
public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void
|
||||
{
|
||||
$this->theme = $theme;
|
||||
$this->styles = $styles;
|
||||
$this->cellStyles = $cellStyles;
|
||||
}
|
||||
|
||||
private static function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
|
||||
public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
|
||||
{
|
||||
$fontStyle->setName((string) $fontStyleXml->name['val']);
|
||||
$fontStyle->setSize((float) $fontStyleXml->sz['val']);
|
||||
|
||||
if (isset($fontStyleXml->name)) {
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->name);
|
||||
if (isset($attr['val'])) {
|
||||
$fontStyle->setName((string) $attr['val']);
|
||||
}
|
||||
}
|
||||
if (isset($fontStyleXml->sz)) {
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->sz);
|
||||
if (isset($attr['val'])) {
|
||||
$fontStyle->setSize((float) $attr['val']);
|
||||
}
|
||||
}
|
||||
if (isset($fontStyleXml->b)) {
|
||||
$fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val']));
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->b);
|
||||
$fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val']));
|
||||
}
|
||||
if (isset($fontStyleXml->i)) {
|
||||
$fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val']));
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->i);
|
||||
$fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val']));
|
||||
}
|
||||
if (isset($fontStyleXml->strike)) {
|
||||
$fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val']));
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->strike);
|
||||
$fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val']));
|
||||
}
|
||||
$fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color));
|
||||
$fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color));
|
||||
|
||||
if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) {
|
||||
$fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
|
||||
} elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) {
|
||||
$fontStyle->setUnderline((string) $fontStyleXml->u['val']);
|
||||
}
|
||||
|
||||
if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) {
|
||||
$verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']);
|
||||
if ($verticalAlign === 'superscript') {
|
||||
$fontStyle->setSuperscript(true);
|
||||
if (isset($fontStyleXml->u)) {
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->u);
|
||||
if (!isset($attr['val'])) {
|
||||
$fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
|
||||
} else {
|
||||
$fontStyle->setUnderline((string) $attr['val']);
|
||||
}
|
||||
if ($verticalAlign === 'subscript') {
|
||||
$fontStyle->setSubscript(true);
|
||||
}
|
||||
if (isset($fontStyleXml->vertAlign)) {
|
||||
$attr = $this->getStyleAttributes($fontStyleXml->vertAlign);
|
||||
if (isset($attr['val'])) {
|
||||
$verticalAlign = strtolower((string) $attr['val']);
|
||||
if ($verticalAlign === 'superscript') {
|
||||
$fontStyle->setSuperscript(true);
|
||||
} elseif ($verticalAlign === 'subscript') {
|
||||
$fontStyle->setSubscript(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
|
||||
private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
|
||||
{
|
||||
if ($numfmtStyleXml->count() === 0) {
|
||||
if ((string) $numfmtStyleXml['formatCode'] !== '') {
|
||||
$numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode']));
|
||||
|
||||
return;
|
||||
}
|
||||
$numfmt = $numfmtStyleXml->attributes();
|
||||
if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) {
|
||||
$numfmtStyle->setFormatCode((string) $numfmt['formatCode']);
|
||||
$numfmt = $this->getStyleAttributes($numfmtStyleXml);
|
||||
if (isset($numfmt['formatCode'])) {
|
||||
$numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode']));
|
||||
}
|
||||
}
|
||||
|
||||
private static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
|
||||
public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
|
||||
{
|
||||
if ($fillStyleXml->gradientFill) {
|
||||
/** @var SimpleXMLElement $gradientFill */
|
||||
$gradientFill = $fillStyleXml->gradientFill[0];
|
||||
if (!empty($gradientFill['type'])) {
|
||||
$fillStyle->setFillType((string) $gradientFill['type']);
|
||||
$attr = $this->getStyleAttributes($gradientFill);
|
||||
if (!empty($attr['type'])) {
|
||||
$fillStyle->setFillType((string) $attr['type']);
|
||||
}
|
||||
$fillStyle->setRotation((float) ($gradientFill['degree']));
|
||||
$gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
|
||||
$fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
|
||||
$fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
|
||||
$fillStyle->setRotation((float) ($attr['degree']));
|
||||
$gradientFill->registerXPathNamespace('sml', Namespaces::MAIN);
|
||||
$fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
|
||||
$fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
|
||||
} elseif ($fillStyleXml->patternFill) {
|
||||
$patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid';
|
||||
$fillStyle->setFillType($patternType);
|
||||
$defaultFillStyle = Fill::FILL_NONE;
|
||||
if ($fillStyleXml->patternFill->fgColor) {
|
||||
$fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true));
|
||||
} else {
|
||||
$fillStyle->getStartColor()->setARGB('FF000000');
|
||||
$fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true));
|
||||
$defaultFillStyle = Fill::FILL_SOLID;
|
||||
}
|
||||
if ($fillStyleXml->patternFill->bgColor) {
|
||||
$fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true));
|
||||
$fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true));
|
||||
$defaultFillStyle = Fill::FILL_SOLID;
|
||||
}
|
||||
|
||||
$type = '';
|
||||
if ((string) $fillStyleXml->patternFill['patternType'] !== '') {
|
||||
$type = (string) $fillStyleXml->patternFill['patternType'];
|
||||
} else {
|
||||
$attr = $this->getStyleAttributes($fillStyleXml->patternFill);
|
||||
$type = (string) $attr['patternType'];
|
||||
}
|
||||
$patternType = ($type === '') ? $defaultFillStyle : $type;
|
||||
|
||||
$fillStyle->setFillType($patternType);
|
||||
}
|
||||
}
|
||||
|
||||
private static function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
|
||||
public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
|
||||
{
|
||||
$diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']);
|
||||
$diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']);
|
||||
if (!$diagonalUp && !$diagonalDown) {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
|
||||
} elseif ($diagonalUp && !$diagonalDown) {
|
||||
$diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp');
|
||||
$diagonalUp = self::boolean($diagonalUp);
|
||||
$diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown');
|
||||
$diagonalDown = self::boolean($diagonalDown);
|
||||
if ($diagonalUp === false) {
|
||||
if ($diagonalDown === false) {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
|
||||
} else {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
|
||||
}
|
||||
} elseif ($diagonalDown === false) {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
|
||||
} elseif (!$diagonalUp && $diagonalDown) {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
|
||||
} else {
|
||||
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
|
||||
}
|
||||
|
||||
self::readBorder($borderStyle->getLeft(), $borderStyleXml->left);
|
||||
self::readBorder($borderStyle->getRight(), $borderStyleXml->right);
|
||||
self::readBorder($borderStyle->getTop(), $borderStyleXml->top);
|
||||
self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
|
||||
self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
|
||||
$this->readBorder($borderStyle->getLeft(), $borderStyleXml->left);
|
||||
$this->readBorder($borderStyle->getRight(), $borderStyleXml->right);
|
||||
$this->readBorder($borderStyle->getTop(), $borderStyleXml->top);
|
||||
$this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
|
||||
$this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
|
||||
}
|
||||
|
||||
private static function readBorder(Border $border, SimpleXMLElement $borderXml): void
|
||||
private function getAttribute(SimpleXMLElement $xml, string $attribute): string
|
||||
{
|
||||
if (isset($borderXml['style'])) {
|
||||
$border->setBorderStyle((string) $borderXml['style']);
|
||||
$style = '';
|
||||
if ((string) $xml[$attribute] !== '') {
|
||||
$style = (string) $xml[$attribute];
|
||||
} else {
|
||||
$attr = $this->getStyleAttributes($xml);
|
||||
if (isset($attr[$attribute])) {
|
||||
$style = (string) $attr[$attribute];
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
private function readBorder(Border $border, SimpleXMLElement $borderXml): void
|
||||
{
|
||||
$style = $this->getAttribute($borderXml, 'style');
|
||||
if ($style !== '') {
|
||||
$border->setBorderStyle((string) $style);
|
||||
}
|
||||
if (isset($borderXml->color)) {
|
||||
$border->getColor()->setARGB(self::readColor($borderXml->color));
|
||||
$border->getColor()->setARGB($this->readColor($borderXml->color));
|
||||
}
|
||||
}
|
||||
|
||||
private static function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
|
||||
public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
|
||||
{
|
||||
$alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']);
|
||||
$alignment->setVertical((string) $alignmentXml->alignment['vertical']);
|
||||
$horizontal = $this->getAttribute($alignmentXml, 'horizontal');
|
||||
$alignment->setHorizontal($horizontal);
|
||||
$vertical = $this->getAttribute($alignmentXml, 'vertical');
|
||||
$alignment->setVertical((string) $vertical);
|
||||
|
||||
$textRotation = 0;
|
||||
if ((int) $alignmentXml->alignment['textRotation'] <= 90) {
|
||||
$textRotation = (int) $alignmentXml->alignment['textRotation'];
|
||||
} elseif ((int) $alignmentXml->alignment['textRotation'] > 90) {
|
||||
$textRotation = 90 - (int) $alignmentXml->alignment['textRotation'];
|
||||
$textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
|
||||
if ($textRotation > 90) {
|
||||
$textRotation = 90 - $textRotation;
|
||||
}
|
||||
$alignment->setTextRotation($textRotation);
|
||||
|
||||
$alignment->setTextRotation((int) $textRotation);
|
||||
$alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText']));
|
||||
$alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit']));
|
||||
$alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0);
|
||||
$alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0);
|
||||
$wrapText = $this->getAttribute($alignmentXml, 'wrapText');
|
||||
$alignment->setWrapText(self::boolean((string) $wrapText));
|
||||
$shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit');
|
||||
$alignment->setShrinkToFit(self::boolean((string) $shrinkToFit));
|
||||
$indent = (int) $this->getAttribute($alignmentXml, 'indent');
|
||||
$alignment->setIndent(max($indent, 0));
|
||||
$readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder');
|
||||
$alignment->setReadOrder(max($readingOrder, 0));
|
||||
}
|
||||
|
||||
private function readStyle(Style $docStyle, $style): void
|
||||
private static function formatGeneral(string $formatString): string
|
||||
{
|
||||
if ($style->numFmt instanceof SimpleXMLElement) {
|
||||
self::readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
|
||||
if ($formatString === 'GENERAL') {
|
||||
$formatString = NumberFormat::FORMAT_GENERAL;
|
||||
}
|
||||
|
||||
return $formatString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read style.
|
||||
*
|
||||
* @param SimpleXMLElement|stdClass $style
|
||||
*/
|
||||
public function readStyle(Style $docStyle, $style): void
|
||||
{
|
||||
if ($style instanceof SimpleXMLElement) {
|
||||
$this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
|
||||
} else {
|
||||
$docStyle->getNumberFormat()->setFormatCode($style->numFmt);
|
||||
$docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt));
|
||||
}
|
||||
|
||||
if (isset($style->font)) {
|
||||
self::readFontStyle($docStyle->getFont(), $style->font);
|
||||
$this->readFontStyle($docStyle->getFont(), $style->font);
|
||||
}
|
||||
|
||||
if (isset($style->fill)) {
|
||||
self::readFillStyle($docStyle->getFill(), $style->fill);
|
||||
$this->readFillStyle($docStyle->getFill(), $style->fill);
|
||||
}
|
||||
|
||||
if (isset($style->border)) {
|
||||
self::readBorderStyle($docStyle->getBorders(), $style->border);
|
||||
$this->readBorderStyle($docStyle->getBorders(), $style->border);
|
||||
}
|
||||
|
||||
if (isset($style->alignment->alignment)) {
|
||||
self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
|
||||
if (isset($style->alignment)) {
|
||||
$this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
|
||||
}
|
||||
|
||||
// protection
|
||||
if (isset($style->protection)) {
|
||||
$this->readProtectionLocked($docStyle, $style);
|
||||
$this->readProtectionHidden($docStyle, $style);
|
||||
$this->readProtectionLocked($docStyle, $style->protection);
|
||||
$this->readProtectionHidden($docStyle, $style->protection);
|
||||
}
|
||||
|
||||
// top-level style settings
|
||||
if (isset($style->quotePrefix)) {
|
||||
$docStyle->setQuotePrefix(true);
|
||||
$docStyle->setQuotePrefix((bool) $style->quotePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
private function readProtectionLocked(Style $docStyle, $style): void
|
||||
/**
|
||||
* Read protection locked attribute.
|
||||
*/
|
||||
public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void
|
||||
{
|
||||
if (isset($style->protection['locked'])) {
|
||||
if (self::boolean((string) $style->protection['locked'])) {
|
||||
$locked = '';
|
||||
if ((string) $style['locked'] !== '') {
|
||||
$locked = (string) $style['locked'];
|
||||
} else {
|
||||
$attr = $this->getStyleAttributes($style);
|
||||
if (isset($attr['locked'])) {
|
||||
$locked = (string) $attr['locked'];
|
||||
}
|
||||
}
|
||||
if ($locked !== '') {
|
||||
if (self::boolean($locked)) {
|
||||
$docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
|
||||
} else {
|
||||
$docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
|
||||
@@ -207,10 +335,22 @@ class Styles extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private function readProtectionHidden(Style $docStyle, $style): void
|
||||
/**
|
||||
* Read protection hidden attribute.
|
||||
*/
|
||||
public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void
|
||||
{
|
||||
if (isset($style->protection['hidden'])) {
|
||||
if (self::boolean((string) $style->protection['hidden'])) {
|
||||
$hidden = '';
|
||||
if ((string) $style['hidden'] !== '') {
|
||||
$hidden = (string) $style['hidden'];
|
||||
} else {
|
||||
$attr = $this->getStyleAttributes($style);
|
||||
if (isset($attr['hidden'])) {
|
||||
$hidden = (string) $attr['hidden'];
|
||||
}
|
||||
}
|
||||
if ($hidden !== '') {
|
||||
if (self::boolean((string) $hidden)) {
|
||||
$docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
|
||||
} else {
|
||||
$docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
|
||||
@@ -218,18 +358,26 @@ class Styles extends BaseParserClass
|
||||
}
|
||||
}
|
||||
|
||||
private static function readColor($color, $background = false)
|
||||
public function readColor(SimpleXMLElement $color, bool $background = false): string
|
||||
{
|
||||
if (isset($color['rgb'])) {
|
||||
return (string) $color['rgb'];
|
||||
} elseif (isset($color['indexed'])) {
|
||||
return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
|
||||
} elseif (isset($color['theme'])) {
|
||||
if (self::$theme !== null) {
|
||||
$returnColour = self::$theme->getColourByIndex((int) $color['theme']);
|
||||
if (isset($color['tint'])) {
|
||||
$tintAdjust = (float) $color['tint'];
|
||||
$returnColour = Color::changeBrightness($returnColour, $tintAdjust);
|
||||
$attr = $this->getStyleAttributes($color);
|
||||
if (isset($attr['rgb'])) {
|
||||
return (string) $attr['rgb'];
|
||||
}
|
||||
if (isset($attr['indexed'])) {
|
||||
$indexedColor = (int) $attr['indexed'];
|
||||
if ($indexedColor >= count($this->workbookPalette)) {
|
||||
return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? '';
|
||||
}
|
||||
|
||||
return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? '';
|
||||
}
|
||||
if (isset($attr['theme'])) {
|
||||
if ($this->theme !== null) {
|
||||
$returnColour = $this->theme->getColourByIndex((int) $attr['theme']);
|
||||
if (isset($attr['tint'])) {
|
||||
$tintAdjust = (float) $attr['tint'];
|
||||
$returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust);
|
||||
}
|
||||
|
||||
return 'FF' . $returnColour;
|
||||
@@ -239,7 +387,7 @@ class Styles extends BaseParserClass
|
||||
return ($background) ? 'FFFFFFFF' : 'FF000000';
|
||||
}
|
||||
|
||||
public function dxfs($readDataOnly = false)
|
||||
public function dxfs(bool $readDataOnly = false): array
|
||||
{
|
||||
$dxfs = [];
|
||||
if (!$readDataOnly && $this->styleXml) {
|
||||
@@ -253,7 +401,8 @@ class Styles extends BaseParserClass
|
||||
}
|
||||
// Cell Styles
|
||||
if ($this->styleXml->cellStyles) {
|
||||
foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) {
|
||||
foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) {
|
||||
$cellStyle = Xlsx::getAttributes($cellStylex);
|
||||
if ((int) ($cellStyle['builtinId']) == 0) {
|
||||
if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
|
||||
// Set default style
|
||||
@@ -270,13 +419,20 @@ class Styles extends BaseParserClass
|
||||
return $dxfs;
|
||||
}
|
||||
|
||||
public function styles()
|
||||
public function styles(): array
|
||||
{
|
||||
return $this->styles;
|
||||
}
|
||||
|
||||
private static function getArrayItem($array, $key = 0)
|
||||
/**
|
||||
* Get array item.
|
||||
*
|
||||
* @param mixed $array (usually array, in theory can be false)
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
private static function getArrayItem($array, int $key = 0)
|
||||
{
|
||||
return $array[$key] ?? null;
|
||||
return is_array($array) ? ($array[$key] ?? null) : null;
|
||||
}
|
||||
}
|
||||
|
113
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
vendored
Normal file
113
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class TableReader
|
||||
{
|
||||
/**
|
||||
* @var Worksheet
|
||||
*/
|
||||
private $worksheet;
|
||||
|
||||
/**
|
||||
* @var SimpleXMLElement
|
||||
*/
|
||||
private $tableXml;
|
||||
|
||||
public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
|
||||
{
|
||||
$this->worksheet = $workSheet;
|
||||
$this->tableXml = $tableXml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Table into the Worksheet.
|
||||
*/
|
||||
public function load(): void
|
||||
{
|
||||
// Remove all "$" in the table range
|
||||
$tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? '');
|
||||
if (strpos($tableRange, ':') !== false) {
|
||||
$this->readTable($tableRange, $this->tableXml);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Table from xml.
|
||||
*/
|
||||
private function readTable(string $tableRange, SimpleXMLElement $tableXml): void
|
||||
{
|
||||
$table = new Table($tableRange);
|
||||
$table->setName((string) $tableXml['displayName']);
|
||||
$table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0');
|
||||
$table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1');
|
||||
|
||||
$this->readTableAutoFilter($table, $tableXml->autoFilter);
|
||||
$this->readTableColumns($table, $tableXml->tableColumns);
|
||||
$this->readTableStyle($table, $tableXml->tableStyleInfo);
|
||||
|
||||
(new AutoFilter($table, $tableXml))->load();
|
||||
$this->worksheet->addTable($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads TableAutoFilter from xml.
|
||||
*/
|
||||
private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void
|
||||
{
|
||||
if ($autoFilterXml->filterColumn === null) {
|
||||
$table->setAllowFilter(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($autoFilterXml->filterColumn as $filterColumn) {
|
||||
$column = $table->getColumnByOffset((int) $filterColumn['colId']);
|
||||
$column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads TableColumns from xml.
|
||||
*/
|
||||
private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void
|
||||
{
|
||||
$offset = 0;
|
||||
foreach ($tableColumnsXml->tableColumn as $tableColumn) {
|
||||
$column = $table->getColumnByOffset($offset++);
|
||||
|
||||
if ($table->getShowTotalsRow()) {
|
||||
if ($tableColumn['totalsRowLabel']) {
|
||||
$column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']);
|
||||
}
|
||||
|
||||
if ($tableColumn['totalsRowFunction']) {
|
||||
$column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableColumn->calculatedColumnFormula) {
|
||||
$column->setColumnFormula((string) $tableColumn->calculatedColumnFormula);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads TableStyle from xml.
|
||||
*/
|
||||
private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
|
||||
{
|
||||
$tableStyle = new TableStyle();
|
||||
$tableStyle->setTheme((string) $tableStyleInfoXml['name']);
|
||||
$tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1');
|
||||
$tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1');
|
||||
$tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1');
|
||||
$tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1');
|
||||
$table->setStyle($tableStyle);
|
||||
}
|
||||
}
|
@@ -21,16 +21,16 @@ class Theme
|
||||
/**
|
||||
* Colour Map.
|
||||
*
|
||||
* @var array of string
|
||||
* @var string[]
|
||||
*/
|
||||
private $colourMap;
|
||||
|
||||
/**
|
||||
* Create a new Theme.
|
||||
*
|
||||
* @param mixed $themeName
|
||||
* @param mixed $colourSchemeName
|
||||
* @param mixed $colourMap
|
||||
* @param string $themeName
|
||||
* @param string $colourSchemeName
|
||||
* @param string[] $colourMap
|
||||
*/
|
||||
public function __construct($themeName, $colourSchemeName, $colourMap)
|
||||
{
|
||||
@@ -41,9 +41,11 @@ class Theme
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Theme Name.
|
||||
* Not called by Reader, never accessible any other time.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getThemeName()
|
||||
{
|
||||
@@ -51,9 +53,11 @@ class Theme
|
||||
}
|
||||
|
||||
/**
|
||||
* Get colour Scheme Name.
|
||||
* Not called by Reader, never accessible any other time.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getColourSchemeName()
|
||||
{
|
||||
@@ -63,31 +67,12 @@ class Theme
|
||||
/**
|
||||
* Get colour Map Value by Position.
|
||||
*
|
||||
* @param mixed $index
|
||||
* @param int $index
|
||||
*
|
||||
* @return string
|
||||
* @return null|string
|
||||
*/
|
||||
public function getColourByIndex($index)
|
||||
{
|
||||
if (isset($this->colourMap[$index])) {
|
||||
return $this->colourMap[$index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
foreach ($vars as $key => $value) {
|
||||
if ((is_object($value)) && ($key != '_parent')) {
|
||||
$this->$key = clone $value;
|
||||
} else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
return $this->colourMap[$index] ?? null;
|
||||
}
|
||||
}
|
||||
|
153
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php
vendored
Normal file
153
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class WorkbookView
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
*/
|
||||
private $spreadsheet;
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mainNS
|
||||
*/
|
||||
public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void
|
||||
{
|
||||
if ($this->spreadsheet->getSheetCount() == 0) {
|
||||
$this->spreadsheet->createSheet();
|
||||
}
|
||||
// Default active sheet index to the first loaded worksheet from the file
|
||||
$this->spreadsheet->setActiveSheetIndex(0);
|
||||
|
||||
$workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView;
|
||||
if ($readDataOnly !== true && !empty($workbookView)) {
|
||||
$workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView));
|
||||
// active sheet index
|
||||
$activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index
|
||||
// keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet
|
||||
if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
|
||||
$this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]);
|
||||
}
|
||||
|
||||
$this->horizontalScroll($workbookViewAttributes);
|
||||
$this->verticalScroll($workbookViewAttributes);
|
||||
$this->sheetTabs($workbookViewAttributes);
|
||||
$this->minimized($workbookViewAttributes);
|
||||
$this->autoFilterDateGrouping($workbookViewAttributes);
|
||||
$this->firstSheet($workbookViewAttributes);
|
||||
$this->visibility($workbookViewAttributes);
|
||||
$this->tabRatio($workbookViewAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function testSimpleXml($value): SimpleXMLElement
|
||||
{
|
||||
return ($value instanceof SimpleXMLElement)
|
||||
? $value
|
||||
: new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
|
||||
}
|
||||
|
||||
public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement
|
||||
{
|
||||
return self::testSimpleXml($value === null ? $value : $value->attributes($ns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an 'xsd:boolean' XML value to a PHP boolean value.
|
||||
* A valid 'xsd:boolean' XML value can be one of the following
|
||||
* four values: 'true', 'false', '1', '0'. It is case sensitive.
|
||||
*
|
||||
* Note that just doing '(bool) $xsdBoolean' is not safe,
|
||||
* since '(bool) "false"' returns true.
|
||||
*
|
||||
* @see https://www.w3.org/TR/xmlschema11-2/#boolean
|
||||
*
|
||||
* @param string $xsdBoolean An XML string value of type 'xsd:boolean'
|
||||
*
|
||||
* @return bool Boolean value
|
||||
*/
|
||||
private function castXsdBooleanToBool(string $xsdBoolean): bool
|
||||
{
|
||||
if ($xsdBoolean === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $xsdBoolean;
|
||||
}
|
||||
|
||||
private function horizontalScroll(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->showHorizontalScroll)) {
|
||||
$showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll;
|
||||
$this->spreadsheet->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
|
||||
}
|
||||
}
|
||||
|
||||
private function verticalScroll(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->showVerticalScroll)) {
|
||||
$showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll;
|
||||
$this->spreadsheet->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
|
||||
}
|
||||
}
|
||||
|
||||
private function sheetTabs(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->showSheetTabs)) {
|
||||
$showSheetTabs = (string) $workbookViewAttributes->showSheetTabs;
|
||||
$this->spreadsheet->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
|
||||
}
|
||||
}
|
||||
|
||||
private function minimized(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->minimized)) {
|
||||
$minimized = (string) $workbookViewAttributes->minimized;
|
||||
$this->spreadsheet->setMinimized($this->castXsdBooleanToBool($minimized));
|
||||
}
|
||||
}
|
||||
|
||||
private function autoFilterDateGrouping(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->autoFilterDateGrouping)) {
|
||||
$autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping;
|
||||
$this->spreadsheet->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
|
||||
}
|
||||
}
|
||||
|
||||
private function firstSheet(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->firstSheet)) {
|
||||
$firstSheet = (string) $workbookViewAttributes->firstSheet;
|
||||
$this->spreadsheet->setFirstSheetIndex((int) $firstSheet);
|
||||
}
|
||||
}
|
||||
|
||||
private function visibility(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->visibility)) {
|
||||
$visibility = (string) $workbookViewAttributes->visibility;
|
||||
$this->spreadsheet->setVisibility($visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private function tabRatio(SimpleXMLElement $workbookViewAttributes): void
|
||||
{
|
||||
if (isset($workbookViewAttributes->tabRatio)) {
|
||||
$tabRatio = (string) $workbookViewAttributes->tabRatio;
|
||||
$this->spreadsheet->setTabRatio((int) $tabRatio);
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,24 +2,23 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\File;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
@@ -45,62 +44,18 @@ class Xml extends BaseReader
|
||||
|
||||
private $fileContents = '';
|
||||
|
||||
private static $mappings = [
|
||||
'borderStyle' => [
|
||||
'1continuous' => Border::BORDER_THIN,
|
||||
'1dash' => Border::BORDER_DASHED,
|
||||
'1dashdot' => Border::BORDER_DASHDOT,
|
||||
'1dashdotdot' => Border::BORDER_DASHDOTDOT,
|
||||
'1dot' => Border::BORDER_DOTTED,
|
||||
'1double' => Border::BORDER_DOUBLE,
|
||||
'2continuous' => Border::BORDER_MEDIUM,
|
||||
'2dash' => Border::BORDER_MEDIUMDASHED,
|
||||
'2dashdot' => Border::BORDER_MEDIUMDASHDOT,
|
||||
'2dashdotdot' => Border::BORDER_MEDIUMDASHDOTDOT,
|
||||
'2dot' => Border::BORDER_DOTTED,
|
||||
'2double' => Border::BORDER_DOUBLE,
|
||||
'3continuous' => Border::BORDER_THICK,
|
||||
'3dash' => Border::BORDER_MEDIUMDASHED,
|
||||
'3dashdot' => Border::BORDER_MEDIUMDASHDOT,
|
||||
'3dashdotdot' => Border::BORDER_MEDIUMDASHDOTDOT,
|
||||
'3dot' => Border::BORDER_DOTTED,
|
||||
'3double' => Border::BORDER_DOUBLE,
|
||||
],
|
||||
'fillType' => [
|
||||
'solid' => Fill::FILL_SOLID,
|
||||
'gray75' => Fill::FILL_PATTERN_DARKGRAY,
|
||||
'gray50' => Fill::FILL_PATTERN_MEDIUMGRAY,
|
||||
'gray25' => Fill::FILL_PATTERN_LIGHTGRAY,
|
||||
'gray125' => Fill::FILL_PATTERN_GRAY125,
|
||||
'gray0625' => Fill::FILL_PATTERN_GRAY0625,
|
||||
'horzstripe' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
|
||||
'vertstripe' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
|
||||
'reversediagstripe' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
|
||||
'diagstripe' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
|
||||
'diagcross' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
|
||||
'thickdiagcross' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
|
||||
'thinhorzstripe' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
|
||||
'thinvertstripe' => Fill::FILL_PATTERN_LIGHTVERTICAL,
|
||||
'thinreversediagstripe' => Fill::FILL_PATTERN_LIGHTUP,
|
||||
'thindiagstripe' => Fill::FILL_PATTERN_LIGHTDOWN,
|
||||
'thinhorzcross' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
|
||||
'thindiagcross' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
|
||||
],
|
||||
];
|
||||
|
||||
public static function xmlMappings(): array
|
||||
{
|
||||
return self::$mappings;
|
||||
return array_merge(
|
||||
Style\Fill::FILL_MAPPINGS,
|
||||
Style\Border::BORDER_MAPPINGS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the current IReader read the file?
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRead($pFilename)
|
||||
public function canRead(string $filename): bool
|
||||
{
|
||||
// Office xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
// Excel xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
@@ -114,11 +69,11 @@ class Xml extends BaseReader
|
||||
|
||||
$signature = [
|
||||
'<?xml version="1.0"',
|
||||
'<?mso-application progid="Excel.Sheet"?>',
|
||||
'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet',
|
||||
];
|
||||
|
||||
// Open file
|
||||
$data = file_get_contents($pFilename);
|
||||
$data = file_get_contents($filename);
|
||||
|
||||
// Why?
|
||||
//$data = str_replace("'", '"', $data); // fix headers with single quote
|
||||
@@ -136,9 +91,9 @@ class Xml extends BaseReader
|
||||
// Retrieve charset encoding
|
||||
if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $data, $matches)) {
|
||||
$charSet = strtoupper($matches[1]);
|
||||
if (1 == preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet)) {
|
||||
if (preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet) === 1) {
|
||||
$data = StringHelper::convertEncoding($data, 'UTF-8', $charSet);
|
||||
$data = preg_replace('/(<?xml.*encoding=[\'"]).*?([\'"].*?>)/um', '$1' . 'UTF-8' . '$2', $data, 1);
|
||||
$data = (string) preg_replace('/(<?xml.*encoding=[\'"]).*?([\'"].*?>)/um', '$1' . 'UTF-8' . '$2', $data, 1);
|
||||
}
|
||||
}
|
||||
$this->fileContents = $data;
|
||||
@@ -149,20 +104,20 @@ class Xml extends BaseReader
|
||||
/**
|
||||
* Check if the file is a valid SimpleXML.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return false|SimpleXMLElement
|
||||
*/
|
||||
public function trySimpleXMLLoadString($pFilename)
|
||||
public function trySimpleXMLLoadString($filename)
|
||||
{
|
||||
try {
|
||||
$xml = simplexml_load_string(
|
||||
$this->securityScanner->scan($this->fileContents ?: file_get_contents($pFilename)),
|
||||
$this->securityScanner->scan($this->fileContents ?: file_get_contents($filename)),
|
||||
'SimpleXMLElement',
|
||||
Settings::getLibXmlLoaderOptions()
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
|
||||
throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e);
|
||||
}
|
||||
$this->fileContents = '';
|
||||
|
||||
@@ -172,26 +127,29 @@ class Xml extends BaseReader
|
||||
/**
|
||||
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listWorksheetNames($pFilename)
|
||||
public function listWorksheetNames($filename)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
if (!$this->canRead($pFilename)) {
|
||||
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
|
||||
}
|
||||
|
||||
$worksheetNames = [];
|
||||
|
||||
$xml = $this->trySimpleXMLLoadString($pFilename);
|
||||
$xml = $this->trySimpleXMLLoadString($filename);
|
||||
if ($xml === false) {
|
||||
throw new Exception("Problem reading {$filename}");
|
||||
}
|
||||
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
foreach ($xml_ss->Worksheet as $worksheet) {
|
||||
$worksheet_ss = $worksheet->attributes($namespaces['ss']);
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
$worksheetNames[] = (string) $worksheet_ss['Name'];
|
||||
}
|
||||
|
||||
@@ -201,27 +159,30 @@ class Xml extends BaseReader
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listWorksheetInfo($pFilename)
|
||||
public function listWorksheetInfo($filename)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
if (!$this->canRead($pFilename)) {
|
||||
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
|
||||
}
|
||||
|
||||
$worksheetInfo = [];
|
||||
|
||||
$xml = $this->trySimpleXMLLoadString($pFilename);
|
||||
$xml = $this->trySimpleXMLLoadString($filename);
|
||||
if ($xml === false) {
|
||||
throw new Exception("Problem reading {$filename}");
|
||||
}
|
||||
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
$worksheetID = 1;
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
foreach ($xml_ss->Worksheet as $worksheet) {
|
||||
$worksheet_ss = $worksheet->attributes($namespaces['ss']);
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
|
||||
$tmpInfo = [];
|
||||
$tmpInfo['worksheetName'] = '';
|
||||
@@ -271,161 +232,53 @@ class Xml extends BaseReader
|
||||
|
||||
/**
|
||||
* Loads Spreadsheet from file.
|
||||
*
|
||||
* @param string $pFilename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function load($pFilename)
|
||||
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
|
||||
{
|
||||
// Create new Spreadsheet
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$spreadsheet->removeSheetByIndex(0);
|
||||
|
||||
// Load into this instance
|
||||
return $this->loadIntoExisting($pFilename, $spreadsheet);
|
||||
}
|
||||
|
||||
private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
|
||||
{
|
||||
$returnValue = false;
|
||||
$styleAttributeValue = strtolower($styleAttributeValue);
|
||||
foreach ($styleList as $style) {
|
||||
if ($styleAttributeValue == strtolower($style)) {
|
||||
$styleAttributeValue = $style;
|
||||
$returnValue = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
protected static function hex2str($hex)
|
||||
{
|
||||
return mb_chr((int) hexdec($hex[1]), 'UTF-8');
|
||||
return $this->loadIntoExisting($filename, $spreadsheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads from file into Spreadsheet instance.
|
||||
*
|
||||
* @param string $pFilename
|
||||
* @param string $filename
|
||||
*
|
||||
* @return Spreadsheet
|
||||
*/
|
||||
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
|
||||
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
|
||||
{
|
||||
File::assertFile($pFilename);
|
||||
if (!$this->canRead($pFilename)) {
|
||||
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
|
||||
File::assertFile($filename);
|
||||
if (!$this->canRead($filename)) {
|
||||
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
|
||||
}
|
||||
|
||||
$xml = $this->trySimpleXMLLoadString($pFilename);
|
||||
$xml = $this->trySimpleXMLLoadString($filename);
|
||||
if ($xml === false) {
|
||||
throw new Exception("Problem reading {$filename}");
|
||||
}
|
||||
|
||||
$namespaces = $xml->getNamespaces(true);
|
||||
|
||||
$docProps = $spreadsheet->getProperties();
|
||||
if (isset($xml->DocumentProperties[0])) {
|
||||
foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
|
||||
$stringValue = (string) $propertyValue;
|
||||
switch ($propertyName) {
|
||||
case 'Title':
|
||||
$docProps->setTitle($stringValue);
|
||||
(new Properties($spreadsheet))->readProperties($xml, $namespaces);
|
||||
|
||||
break;
|
||||
case 'Subject':
|
||||
$docProps->setSubject($stringValue);
|
||||
|
||||
break;
|
||||
case 'Author':
|
||||
$docProps->setCreator($stringValue);
|
||||
|
||||
break;
|
||||
case 'Created':
|
||||
$creationDate = strtotime($stringValue);
|
||||
$docProps->setCreated($creationDate);
|
||||
|
||||
break;
|
||||
case 'LastAuthor':
|
||||
$docProps->setLastModifiedBy($stringValue);
|
||||
|
||||
break;
|
||||
case 'LastSaved':
|
||||
$lastSaveDate = strtotime($stringValue);
|
||||
$docProps->setModified($lastSaveDate);
|
||||
|
||||
break;
|
||||
case 'Company':
|
||||
$docProps->setCompany($stringValue);
|
||||
|
||||
break;
|
||||
case 'Category':
|
||||
$docProps->setCategory($stringValue);
|
||||
|
||||
break;
|
||||
case 'Manager':
|
||||
$docProps->setManager($stringValue);
|
||||
|
||||
break;
|
||||
case 'Keywords':
|
||||
$docProps->setKeywords($stringValue);
|
||||
|
||||
break;
|
||||
case 'Description':
|
||||
$docProps->setDescription($stringValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($xml->CustomDocumentProperties)) {
|
||||
foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
|
||||
$propertyAttributes = $propertyValue->attributes($namespaces['dt']);
|
||||
$propertyName = preg_replace_callback('/_x([0-9a-f]{4})_/i', ['self', 'hex2str'], $propertyName);
|
||||
$propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
|
||||
switch ((string) $propertyAttributes) {
|
||||
case 'string':
|
||||
$propertyType = Properties::PROPERTY_TYPE_STRING;
|
||||
$propertyValue = trim($propertyValue);
|
||||
|
||||
break;
|
||||
case 'boolean':
|
||||
$propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
|
||||
$propertyValue = (bool) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'integer':
|
||||
$propertyType = Properties::PROPERTY_TYPE_INTEGER;
|
||||
$propertyValue = (int) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'float':
|
||||
$propertyType = Properties::PROPERTY_TYPE_FLOAT;
|
||||
$propertyValue = (float) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'dateTime.tz':
|
||||
$propertyType = Properties::PROPERTY_TYPE_DATE;
|
||||
$propertyValue = strtotime(trim($propertyValue));
|
||||
|
||||
break;
|
||||
}
|
||||
$docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->parseStyles($xml, $namespaces);
|
||||
$this->styles = (new Style())->parseStyles($xml, $namespaces);
|
||||
|
||||
$worksheetID = 0;
|
||||
$xml_ss = $xml->children($namespaces['ss']);
|
||||
|
||||
foreach ($xml_ss->Worksheet as $worksheet) {
|
||||
$worksheet_ss = $worksheet->attributes($namespaces['ss']);
|
||||
/** @var null|SimpleXMLElement $worksheetx */
|
||||
foreach ($xml_ss->Worksheet as $worksheetx) {
|
||||
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
|
||||
$worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
|
||||
|
||||
if (
|
||||
(isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
|
||||
(!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))
|
||||
isset($this->loadSheetsOnly, $worksheet_ss['Name']) &&
|
||||
(!in_array($worksheet_ss['Name'], /** @scrutinizer ignore-type */ $this->loadSheetsOnly))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -433,6 +286,7 @@ class Xml extends BaseReader
|
||||
// Create new Worksheet
|
||||
$spreadsheet->createSheet();
|
||||
$spreadsheet->setActiveSheetIndex($worksheetID);
|
||||
$worksheetName = '';
|
||||
if (isset($worksheet_ss['Name'])) {
|
||||
$worksheetName = (string) $worksheet_ss['Name'];
|
||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
||||
@@ -444,7 +298,7 @@ class Xml extends BaseReader
|
||||
// locally scoped defined names
|
||||
if (isset($worksheet->Names[0])) {
|
||||
foreach ($worksheet->Names[0] as $definedName) {
|
||||
$definedName_ss = $definedName->attributes($namespaces['ss']);
|
||||
$definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
|
||||
$name = (string) $definedName_ss['Name'];
|
||||
$definedValue = (string) $definedName_ss['RefersTo'];
|
||||
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
|
||||
@@ -458,7 +312,7 @@ class Xml extends BaseReader
|
||||
$columnID = 'A';
|
||||
if (isset($worksheet->Table->Column)) {
|
||||
foreach ($worksheet->Table->Column as $columnData) {
|
||||
$columnData_ss = $columnData->attributes($namespaces['ss']);
|
||||
$columnData_ss = self::getAttributes($columnData, $namespaces['ss']);
|
||||
if (isset($columnData_ss['Index'])) {
|
||||
$columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
|
||||
}
|
||||
@@ -475,14 +329,14 @@ class Xml extends BaseReader
|
||||
$additionalMergedCells = 0;
|
||||
foreach ($worksheet->Table->Row as $rowData) {
|
||||
$rowHasData = false;
|
||||
$row_ss = $rowData->attributes($namespaces['ss']);
|
||||
$row_ss = self::getAttributes($rowData, $namespaces['ss']);
|
||||
if (isset($row_ss['Index'])) {
|
||||
$rowID = (int) $row_ss['Index'];
|
||||
}
|
||||
|
||||
$columnID = 'A';
|
||||
foreach ($rowData->Cell as $cell) {
|
||||
$cell_ss = $cell->attributes($namespaces['ss']);
|
||||
$cell_ss = self::getAttributes($cell, $namespaces['ss']);
|
||||
if (isset($cell_ss['Index'])) {
|
||||
$columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
|
||||
}
|
||||
@@ -504,14 +358,14 @@ class Xml extends BaseReader
|
||||
$columnTo = $columnID;
|
||||
if (isset($cell_ss['MergeAcross'])) {
|
||||
$additionalMergedCells += (int) $cell_ss['MergeAcross'];
|
||||
$columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']);
|
||||
$columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']));
|
||||
}
|
||||
$rowTo = $rowID;
|
||||
if (isset($cell_ss['MergeDown'])) {
|
||||
$rowTo = $rowTo + $cell_ss['MergeDown'];
|
||||
}
|
||||
$cellRange .= ':' . $columnTo . $rowTo;
|
||||
$spreadsheet->getActiveSheet()->mergeCells($cellRange);
|
||||
$spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE);
|
||||
}
|
||||
|
||||
$hasCalculatedValue = false;
|
||||
@@ -524,7 +378,7 @@ class Xml extends BaseReader
|
||||
$cellData = $cell->Data;
|
||||
$cellValue = (string) $cellData;
|
||||
$type = DataType::TYPE_NULL;
|
||||
$cellData_ss = $cellData->attributes($namespaces['ss']);
|
||||
$cellData_ss = self::getAttributes($cellData, $namespaces['ss']);
|
||||
if (isset($cellData_ss['Type'])) {
|
||||
$cellDataType = $cellData_ss['Type'];
|
||||
switch ($cellDataType) {
|
||||
@@ -556,7 +410,8 @@ class Xml extends BaseReader
|
||||
break;
|
||||
case 'DateTime':
|
||||
$type = DataType::TYPE_NUMERIC;
|
||||
$cellValue = Date::PHPToExcel(strtotime($cellValue . ' UTC'));
|
||||
$dateTime = new DateTime($cellValue, new DateTimeZone('UTC'));
|
||||
$cellValue = Date::PHPToExcel($dateTime);
|
||||
|
||||
break;
|
||||
case 'Error':
|
||||
@@ -581,14 +436,7 @@ class Xml extends BaseReader
|
||||
}
|
||||
|
||||
if (isset($cell->Comment)) {
|
||||
$commentAttributes = $cell->Comment->attributes($namespaces['ss']);
|
||||
$author = 'unknown';
|
||||
if (isset($commentAttributes->Author)) {
|
||||
$author = (string) $commentAttributes->Author;
|
||||
}
|
||||
$node = $cell->Comment->Data->asXML();
|
||||
$annotation = strip_tags($node);
|
||||
$spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor($author)->setText($this->parseRichText($annotation));
|
||||
$this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID);
|
||||
}
|
||||
|
||||
if (isset($cell_ss['StyleID'])) {
|
||||
@@ -597,7 +445,8 @@ class Xml extends BaseReader
|
||||
//if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
|
||||
// $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
|
||||
//}
|
||||
$spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
|
||||
$spreadsheet->getActiveSheet()->getStyle($cellRange)
|
||||
->applyFromArray($this->styles[$style]);
|
||||
}
|
||||
}
|
||||
++$columnID;
|
||||
@@ -610,7 +459,7 @@ class Xml extends BaseReader
|
||||
if ($rowHasData) {
|
||||
if (isset($row_ss['Height'])) {
|
||||
$rowHeight = $row_ss['Height'];
|
||||
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
|
||||
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,7 +480,7 @@ class Xml extends BaseReader
|
||||
$activeWorksheet = $spreadsheet->setActiveSheetIndex(0);
|
||||
if (isset($xml->Names[0])) {
|
||||
foreach ($xml->Names[0] as $definedName) {
|
||||
$definedName_ss = $definedName->attributes($namespaces['ss']);
|
||||
$definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
|
||||
$name = (string) $definedName_ss['Name'];
|
||||
$definedValue = (string) $definedName_ss['RefersTo'];
|
||||
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
|
||||
@@ -646,254 +495,39 @@ class Xml extends BaseReader
|
||||
return $spreadsheet;
|
||||
}
|
||||
|
||||
protected function parseRichText($is)
|
||||
protected function parseCellComment(
|
||||
SimpleXMLElement $comment,
|
||||
array $namespaces,
|
||||
Spreadsheet $spreadsheet,
|
||||
string $columnID,
|
||||
int $rowID
|
||||
): void {
|
||||
$commentAttributes = $comment->attributes($namespaces['ss']);
|
||||
$author = 'unknown';
|
||||
if (isset($commentAttributes->Author)) {
|
||||
$author = (string) $commentAttributes->Author;
|
||||
}
|
||||
|
||||
$node = $comment->Data->asXML();
|
||||
$annotation = strip_tags((string) $node);
|
||||
$spreadsheet->getActiveSheet()->getComment($columnID . $rowID)
|
||||
->setAuthor($author)
|
||||
->setText($this->parseRichText($annotation));
|
||||
}
|
||||
|
||||
protected function parseRichText(string $annotation): RichText
|
||||
{
|
||||
$value = new RichText();
|
||||
|
||||
$value->createText($is);
|
||||
$value->createText($annotation);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function parseStyles(SimpleXMLElement $xml, array $namespaces): void
|
||||
private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
|
||||
{
|
||||
if (!isset($xml->Styles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($xml->Styles[0] as $style) {
|
||||
$style_ss = $style->attributes($namespaces['ss']);
|
||||
$styleID = (string) $style_ss['ID'];
|
||||
$this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
|
||||
foreach ($style as $styleType => $styleData) {
|
||||
$styleAttributes = $styleData->attributes($namespaces['ss']);
|
||||
switch ($styleType) {
|
||||
case 'Alignment':
|
||||
$this->parseStyleAlignment($styleID, $styleAttributes);
|
||||
|
||||
break;
|
||||
case 'Borders':
|
||||
$this->parseStyleBorders($styleID, $styleData, $namespaces);
|
||||
|
||||
break;
|
||||
case 'Font':
|
||||
$this->parseStyleFont($styleID, $styleAttributes);
|
||||
|
||||
break;
|
||||
case 'Interior':
|
||||
$this->parseStyleInterior($styleID, $styleAttributes);
|
||||
|
||||
break;
|
||||
case 'NumberFormat':
|
||||
$this->parseStyleNumberFormat($styleID, $styleAttributes);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $styleID
|
||||
*/
|
||||
private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes): void
|
||||
{
|
||||
$verticalAlignmentStyles = [
|
||||
Alignment::VERTICAL_BOTTOM,
|
||||
Alignment::VERTICAL_TOP,
|
||||
Alignment::VERTICAL_CENTER,
|
||||
Alignment::VERTICAL_JUSTIFY,
|
||||
];
|
||||
$horizontalAlignmentStyles = [
|
||||
Alignment::HORIZONTAL_GENERAL,
|
||||
Alignment::HORIZONTAL_LEFT,
|
||||
Alignment::HORIZONTAL_RIGHT,
|
||||
Alignment::HORIZONTAL_CENTER,
|
||||
Alignment::HORIZONTAL_CENTER_CONTINUOUS,
|
||||
Alignment::HORIZONTAL_JUSTIFY,
|
||||
];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = (string) $styleAttributeValue;
|
||||
switch ($styleAttributeKey) {
|
||||
case 'Vertical':
|
||||
if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
|
||||
$this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Horizontal':
|
||||
if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
|
||||
$this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'WrapText':
|
||||
$this->styles[$styleID]['alignment']['wrapText'] = true;
|
||||
|
||||
break;
|
||||
case 'Rotate':
|
||||
$this->styles[$styleID]['alignment']['textRotation'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static $borderPositions = ['top', 'left', 'bottom', 'right'];
|
||||
|
||||
/**
|
||||
* @param $styleID
|
||||
*/
|
||||
private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces): void
|
||||
{
|
||||
$diagonalDirection = '';
|
||||
$borderPosition = '';
|
||||
foreach ($styleData->Border as $borderStyle) {
|
||||
$borderAttributes = $borderStyle->attributes($namespaces['ss']);
|
||||
$thisBorder = [];
|
||||
$style = (string) $borderAttributes->Weight;
|
||||
$style .= strtolower((string) $borderAttributes->LineStyle);
|
||||
$thisBorder['borderStyle'] = self::$mappings['borderStyle'][$style] ?? Border::BORDER_NONE;
|
||||
foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
|
||||
switch ($borderStyleKey) {
|
||||
case 'Position':
|
||||
$borderStyleValue = strtolower((string) $borderStyleValue);
|
||||
if (in_array($borderStyleValue, self::$borderPositions)) {
|
||||
$borderPosition = $borderStyleValue;
|
||||
} elseif ($borderStyleValue == 'diagonalleft') {
|
||||
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN;
|
||||
} elseif ($borderStyleValue == 'diagonalright') {
|
||||
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Color':
|
||||
$borderColour = substr($borderStyleValue, 1);
|
||||
$thisBorder['color']['rgb'] = $borderColour;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($borderPosition) {
|
||||
$this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
|
||||
} elseif ($diagonalDirection) {
|
||||
$this->styles[$styleID]['borders']['diagonalDirection'] = $diagonalDirection;
|
||||
$this->styles[$styleID]['borders']['diagonal'] = $thisBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static $underlineStyles = [
|
||||
Font::UNDERLINE_NONE,
|
||||
Font::UNDERLINE_DOUBLE,
|
||||
Font::UNDERLINE_DOUBLEACCOUNTING,
|
||||
Font::UNDERLINE_SINGLE,
|
||||
Font::UNDERLINE_SINGLEACCOUNTING,
|
||||
];
|
||||
|
||||
private function parseStyleFontUnderline(string $styleID, string $styleAttributeValue): void
|
||||
{
|
||||
if (self::identifyFixedStyleValue(self::$underlineStyles, $styleAttributeValue)) {
|
||||
$this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
private function parseStyleFontVerticalAlign(string $styleID, string $styleAttributeValue): void
|
||||
{
|
||||
if ($styleAttributeValue == 'Superscript') {
|
||||
$this->styles[$styleID]['font']['superscript'] = true;
|
||||
}
|
||||
if ($styleAttributeValue == 'Subscript') {
|
||||
$this->styles[$styleID]['font']['subscript'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $styleID
|
||||
*/
|
||||
private function parseStyleFont(string $styleID, SimpleXMLElement $styleAttributes): void
|
||||
{
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = (string) $styleAttributeValue;
|
||||
switch ($styleAttributeKey) {
|
||||
case 'FontName':
|
||||
$this->styles[$styleID]['font']['name'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
case 'Size':
|
||||
$this->styles[$styleID]['font']['size'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
case 'Color':
|
||||
$this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'Bold':
|
||||
$this->styles[$styleID]['font']['bold'] = true;
|
||||
|
||||
break;
|
||||
case 'Italic':
|
||||
$this->styles[$styleID]['font']['italic'] = true;
|
||||
|
||||
break;
|
||||
case 'Underline':
|
||||
$this->parseStyleFontUnderline($styleID, $styleAttributeValue);
|
||||
|
||||
break;
|
||||
case 'VerticalAlign':
|
||||
$this->parseStyleFontVerticalAlign($styleID, $styleAttributeValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $styleID
|
||||
*/
|
||||
private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes): void
|
||||
{
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
switch ($styleAttributeKey) {
|
||||
case 'Color':
|
||||
$this->styles[$styleID]['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
$this->styles[$styleID]['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'PatternColor':
|
||||
$this->styles[$styleID]['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'Pattern':
|
||||
$lcStyleAttributeValue = strtolower((string) $styleAttributeValue);
|
||||
$this->styles[$styleID]['fill']['fillType'] = self::$mappings['fillType'][$lcStyleAttributeValue] ?? Fill::FILL_NONE;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $styleID
|
||||
*/
|
||||
private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes): void
|
||||
{
|
||||
$fromFormats = ['\-', '\ '];
|
||||
$toFormats = ['-', ' '];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
|
||||
switch ($styleAttributeValue) {
|
||||
case 'Short Date':
|
||||
$styleAttributeValue = 'dd/mm/yyyy';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($styleAttributeValue > '') {
|
||||
$this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
|
||||
}
|
||||
}
|
||||
return ($simple === null)
|
||||
? new SimpleXMLElement('<xml></xml>')
|
||||
: ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,12 @@ class PageSettings
|
||||
if (isset($xmlX->WorksheetOptions->PageSetup)) {
|
||||
foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) {
|
||||
foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) {
|
||||
/** @scrutinizer ignore-call */
|
||||
$pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']);
|
||||
if (!$pageSetupAttributes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($pageSetupKey) {
|
||||
case 'Layout':
|
||||
$this->setLayout($printDefaults, $pageSetupAttributes);
|
||||
@@ -115,7 +120,7 @@ class PageSettings
|
||||
|
||||
private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void
|
||||
{
|
||||
$printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation) ?: PageSetup::ORIENTATION_PORTRAIT;
|
||||
$printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT;
|
||||
$printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false;
|
||||
$printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false;
|
||||
}
|
||||
|
157
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php
vendored
Normal file
157
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Properties
|
||||
{
|
||||
/**
|
||||
* @var Spreadsheet
|
||||
*/
|
||||
protected $spreadsheet;
|
||||
|
||||
public function __construct(Spreadsheet $spreadsheet)
|
||||
{
|
||||
$this->spreadsheet = $spreadsheet;
|
||||
}
|
||||
|
||||
public function readProperties(SimpleXMLElement $xml, array $namespaces): void
|
||||
{
|
||||
$this->readStandardProperties($xml);
|
||||
$this->readCustomProperties($xml, $namespaces);
|
||||
}
|
||||
|
||||
protected function readStandardProperties(SimpleXMLElement $xml): void
|
||||
{
|
||||
if (isset($xml->DocumentProperties[0])) {
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
|
||||
foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
|
||||
$propertyValue = (string) $propertyValue;
|
||||
|
||||
$this->processStandardProperty($docProps, $propertyName, $propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void
|
||||
{
|
||||
if (isset($xml->CustomDocumentProperties)) {
|
||||
$docProps = $this->spreadsheet->getProperties();
|
||||
|
||||
foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
|
||||
$propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']);
|
||||
$propertyName = (string) preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName);
|
||||
|
||||
$this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function processStandardProperty(
|
||||
DocumentProperties $docProps,
|
||||
string $propertyName,
|
||||
string $stringValue
|
||||
): void {
|
||||
switch ($propertyName) {
|
||||
case 'Title':
|
||||
$docProps->setTitle($stringValue);
|
||||
|
||||
break;
|
||||
case 'Subject':
|
||||
$docProps->setSubject($stringValue);
|
||||
|
||||
break;
|
||||
case 'Author':
|
||||
$docProps->setCreator($stringValue);
|
||||
|
||||
break;
|
||||
case 'Created':
|
||||
$docProps->setCreated($stringValue);
|
||||
|
||||
break;
|
||||
case 'LastAuthor':
|
||||
$docProps->setLastModifiedBy($stringValue);
|
||||
|
||||
break;
|
||||
case 'LastSaved':
|
||||
$docProps->setModified($stringValue);
|
||||
|
||||
break;
|
||||
case 'Company':
|
||||
$docProps->setCompany($stringValue);
|
||||
|
||||
break;
|
||||
case 'Category':
|
||||
$docProps->setCategory($stringValue);
|
||||
|
||||
break;
|
||||
case 'Manager':
|
||||
$docProps->setManager($stringValue);
|
||||
|
||||
break;
|
||||
case 'Keywords':
|
||||
$docProps->setKeywords($stringValue);
|
||||
|
||||
break;
|
||||
case 'Description':
|
||||
$docProps->setDescription($stringValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function processCustomProperty(
|
||||
DocumentProperties $docProps,
|
||||
string $propertyName,
|
||||
?SimpleXMLElement $propertyValue,
|
||||
SimpleXMLElement $propertyAttributes
|
||||
): void {
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN;
|
||||
|
||||
switch ((string) $propertyAttributes) {
|
||||
case 'string':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
|
||||
break;
|
||||
case 'boolean':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
|
||||
$propertyValue = (bool) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'integer':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER;
|
||||
$propertyValue = (int) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'float':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT;
|
||||
$propertyValue = (float) $propertyValue;
|
||||
|
||||
break;
|
||||
case 'dateTime.tz':
|
||||
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
|
||||
$propertyValue = trim((string) $propertyValue);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
|
||||
}
|
||||
|
||||
protected function hex2str(array $hex): string
|
||||
{
|
||||
return mb_chr((int) hexdec($hex[1]), 'UTF-8');
|
||||
}
|
||||
|
||||
private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
|
||||
{
|
||||
return ($simple === null)
|
||||
? new SimpleXMLElement('<xml></xml>')
|
||||
: ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
|
||||
}
|
||||
}
|
83
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php
vendored
Normal file
83
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Style
|
||||
{
|
||||
/**
|
||||
* Formats.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $styles = [];
|
||||
|
||||
public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
|
||||
{
|
||||
if (!isset($xml->Styles)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alignmentStyleParser = new Style\Alignment();
|
||||
$borderStyleParser = new Style\Border();
|
||||
$fontStyleParser = new Style\Font();
|
||||
$fillStyleParser = new Style\Fill();
|
||||
$numberFormatStyleParser = new Style\NumberFormat();
|
||||
|
||||
foreach ($xml->Styles[0] as $style) {
|
||||
$style_ss = self::getAttributes($style, $namespaces['ss']);
|
||||
$styleID = (string) $style_ss['ID'];
|
||||
$this->styles[$styleID] = $this->styles['Default'] ?? [];
|
||||
|
||||
$alignment = $border = $font = $fill = $numberFormat = [];
|
||||
|
||||
foreach ($style as $styleType => $styleDatax) {
|
||||
$styleData = $styleDatax ?? new SimpleXMLElement('<xml></xml>');
|
||||
$styleAttributes = $styleData->attributes($namespaces['ss']);
|
||||
|
||||
switch ($styleType) {
|
||||
case 'Alignment':
|
||||
if ($styleAttributes) {
|
||||
$alignment = $alignmentStyleParser->parseStyle($styleAttributes);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Borders':
|
||||
$border = $borderStyleParser->parseStyle($styleData, $namespaces);
|
||||
|
||||
break;
|
||||
case 'Font':
|
||||
if ($styleAttributes) {
|
||||
$font = $fontStyleParser->parseStyle($styleAttributes);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Interior':
|
||||
if ($styleAttributes) {
|
||||
$fill = $fillStyleParser->parseStyle($styleAttributes);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'NumberFormat':
|
||||
if ($styleAttributes) {
|
||||
$numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat);
|
||||
}
|
||||
|
||||
return $this->styles;
|
||||
}
|
||||
|
||||
protected static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
|
||||
{
|
||||
return ($simple === null)
|
||||
? new SimpleXMLElement('<xml></xml>')
|
||||
: ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
|
||||
}
|
||||
}
|
58
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php
vendored
Normal file
58
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment as AlignmentStyles;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Alignment extends StyleBase
|
||||
{
|
||||
protected const VERTICAL_ALIGNMENT_STYLES = [
|
||||
AlignmentStyles::VERTICAL_BOTTOM,
|
||||
AlignmentStyles::VERTICAL_TOP,
|
||||
AlignmentStyles::VERTICAL_CENTER,
|
||||
AlignmentStyles::VERTICAL_JUSTIFY,
|
||||
];
|
||||
|
||||
protected const HORIZONTAL_ALIGNMENT_STYLES = [
|
||||
AlignmentStyles::HORIZONTAL_GENERAL,
|
||||
AlignmentStyles::HORIZONTAL_LEFT,
|
||||
AlignmentStyles::HORIZONTAL_RIGHT,
|
||||
AlignmentStyles::HORIZONTAL_CENTER,
|
||||
AlignmentStyles::HORIZONTAL_CENTER_CONTINUOUS,
|
||||
AlignmentStyles::HORIZONTAL_JUSTIFY,
|
||||
];
|
||||
|
||||
public function parseStyle(SimpleXMLElement $styleAttributes): array
|
||||
{
|
||||
$style = [];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = (string) $styleAttributeValue;
|
||||
switch ($styleAttributeKey) {
|
||||
case 'Vertical':
|
||||
if (self::identifyFixedStyleValue(self::VERTICAL_ALIGNMENT_STYLES, $styleAttributeValue)) {
|
||||
$style['alignment']['vertical'] = $styleAttributeValue;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Horizontal':
|
||||
if (self::identifyFixedStyleValue(self::HORIZONTAL_ALIGNMENT_STYLES, $styleAttributeValue)) {
|
||||
$style['alignment']['horizontal'] = $styleAttributeValue;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'WrapText':
|
||||
$style['alignment']['wrapText'] = true;
|
||||
|
||||
break;
|
||||
case 'Rotate':
|
||||
$style['alignment']['textRotation'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
}
|
98
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php
vendored
Normal file
98
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border as BorderStyle;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Borders;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Border extends StyleBase
|
||||
{
|
||||
protected const BORDER_POSITIONS = [
|
||||
'top',
|
||||
'left',
|
||||
'bottom',
|
||||
'right',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public const BORDER_MAPPINGS = [
|
||||
'borderStyle' => [
|
||||
'1continuous' => BorderStyle::BORDER_THIN,
|
||||
'1dash' => BorderStyle::BORDER_DASHED,
|
||||
'1dashdot' => BorderStyle::BORDER_DASHDOT,
|
||||
'1dashdotdot' => BorderStyle::BORDER_DASHDOTDOT,
|
||||
'1dot' => BorderStyle::BORDER_DOTTED,
|
||||
'1double' => BorderStyle::BORDER_DOUBLE,
|
||||
'2continuous' => BorderStyle::BORDER_MEDIUM,
|
||||
'2dash' => BorderStyle::BORDER_MEDIUMDASHED,
|
||||
'2dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT,
|
||||
'2dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT,
|
||||
'2dot' => BorderStyle::BORDER_DOTTED,
|
||||
'2double' => BorderStyle::BORDER_DOUBLE,
|
||||
'3continuous' => BorderStyle::BORDER_THICK,
|
||||
'3dash' => BorderStyle::BORDER_MEDIUMDASHED,
|
||||
'3dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT,
|
||||
'3dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT,
|
||||
'3dot' => BorderStyle::BORDER_DOTTED,
|
||||
'3double' => BorderStyle::BORDER_DOUBLE,
|
||||
],
|
||||
];
|
||||
|
||||
public function parseStyle(SimpleXMLElement $styleData, array $namespaces): array
|
||||
{
|
||||
$style = [];
|
||||
|
||||
$diagonalDirection = '';
|
||||
$borderPosition = '';
|
||||
foreach ($styleData->Border as $borderStyle) {
|
||||
$borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']);
|
||||
$thisBorder = [];
|
||||
$styleType = (string) $borderAttributes->Weight;
|
||||
$styleType .= strtolower((string) $borderAttributes->LineStyle);
|
||||
$thisBorder['borderStyle'] = self::BORDER_MAPPINGS['borderStyle'][$styleType] ?? BorderStyle::BORDER_NONE;
|
||||
|
||||
foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) {
|
||||
$borderStyleValue = (string) $borderStyleValuex;
|
||||
switch ($borderStyleKey) {
|
||||
case 'Position':
|
||||
[$borderPosition, $diagonalDirection] =
|
||||
$this->parsePosition($borderStyleValue, $diagonalDirection);
|
||||
|
||||
break;
|
||||
case 'Color':
|
||||
$borderColour = substr($borderStyleValue, 1);
|
||||
$thisBorder['color']['rgb'] = $borderColour;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($borderPosition) {
|
||||
$style['borders'][$borderPosition] = $thisBorder;
|
||||
} elseif ($diagonalDirection) {
|
||||
$style['borders']['diagonalDirection'] = $diagonalDirection;
|
||||
$style['borders']['diagonal'] = $thisBorder;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array
|
||||
{
|
||||
$borderStyleValue = strtolower($borderStyleValue);
|
||||
|
||||
if (in_array($borderStyleValue, self::BORDER_POSITIONS)) {
|
||||
$borderPosition = $borderStyleValue;
|
||||
} elseif ($borderStyleValue === 'diagonalleft') {
|
||||
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN;
|
||||
} elseif ($borderStyleValue === 'diagonalright') {
|
||||
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP;
|
||||
}
|
||||
|
||||
return [$borderPosition ?? null, $diagonalDirection];
|
||||
}
|
||||
}
|
63
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php
vendored
Normal file
63
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill as FillStyles;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Fill extends StyleBase
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public const FILL_MAPPINGS = [
|
||||
'fillType' => [
|
||||
'solid' => FillStyles::FILL_SOLID,
|
||||
'gray75' => FillStyles::FILL_PATTERN_DARKGRAY,
|
||||
'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY,
|
||||
'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY,
|
||||
'gray125' => FillStyles::FILL_PATTERN_GRAY125,
|
||||
'gray0625' => FillStyles::FILL_PATTERN_GRAY0625,
|
||||
'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
|
||||
'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe
|
||||
'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe
|
||||
'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe
|
||||
'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
|
||||
'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
|
||||
'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL,
|
||||
'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL,
|
||||
'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP,
|
||||
'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN,
|
||||
'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
|
||||
'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
|
||||
],
|
||||
];
|
||||
|
||||
public function parseStyle(SimpleXMLElement $styleAttributes): array
|
||||
{
|
||||
$style = [];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) {
|
||||
$styleAttributeValue = (string) $styleAttributeValuex;
|
||||
switch ($styleAttributeKey) {
|
||||
case 'Color':
|
||||
$style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
$style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'PatternColor':
|
||||
$style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'Pattern':
|
||||
$lcStyleAttributeValue = strtolower((string) $styleAttributeValue);
|
||||
$style['fill']['fillType']
|
||||
= self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
}
|
79
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
vendored
Normal file
79
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font as FontUnderline;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class Font extends StyleBase
|
||||
{
|
||||
protected const UNDERLINE_STYLES = [
|
||||
FontUnderline::UNDERLINE_NONE,
|
||||
FontUnderline::UNDERLINE_DOUBLE,
|
||||
FontUnderline::UNDERLINE_DOUBLEACCOUNTING,
|
||||
FontUnderline::UNDERLINE_SINGLE,
|
||||
FontUnderline::UNDERLINE_SINGLEACCOUNTING,
|
||||
];
|
||||
|
||||
protected function parseUnderline(array $style, string $styleAttributeValue): array
|
||||
{
|
||||
if (self::identifyFixedStyleValue(self::UNDERLINE_STYLES, $styleAttributeValue)) {
|
||||
$style['font']['underline'] = $styleAttributeValue;
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
protected function parseVerticalAlign(array $style, string $styleAttributeValue): array
|
||||
{
|
||||
if ($styleAttributeValue == 'Superscript') {
|
||||
$style['font']['superscript'] = true;
|
||||
}
|
||||
if ($styleAttributeValue == 'Subscript') {
|
||||
$style['font']['subscript'] = true;
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
public function parseStyle(SimpleXMLElement $styleAttributes): array
|
||||
{
|
||||
$style = [];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = (string) $styleAttributeValue;
|
||||
switch ($styleAttributeKey) {
|
||||
case 'FontName':
|
||||
$style['font']['name'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
case 'Size':
|
||||
$style['font']['size'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
case 'Color':
|
||||
$style['font']['color']['rgb'] = substr($styleAttributeValue, 1);
|
||||
|
||||
break;
|
||||
case 'Bold':
|
||||
$style['font']['bold'] = true;
|
||||
|
||||
break;
|
||||
case 'Italic':
|
||||
$style['font']['italic'] = true;
|
||||
|
||||
break;
|
||||
case 'Underline':
|
||||
$style = $this->parseUnderline($style, $styleAttributeValue);
|
||||
|
||||
break;
|
||||
case 'VerticalAlign':
|
||||
$style = $this->parseVerticalAlign($style, $styleAttributeValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
}
|
33
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php
vendored
Normal file
33
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
class NumberFormat extends StyleBase
|
||||
{
|
||||
public function parseStyle(SimpleXMLElement $styleAttributes): array
|
||||
{
|
||||
$style = [];
|
||||
|
||||
$fromFormats = ['\-', '\ '];
|
||||
$toFormats = ['-', ' '];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
|
||||
|
||||
switch ($styleAttributeValue) {
|
||||
case 'Short Date':
|
||||
$styleAttributeValue = 'dd/mm/yyyy';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($styleAttributeValue > '') {
|
||||
$style['numberFormat']['formatCode'] = $styleAttributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
}
|
32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php
vendored
Normal file
32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
abstract class StyleBase
|
||||
{
|
||||
protected static function identifyFixedStyleValue(array $styleList, string &$styleAttributeValue): bool
|
||||
{
|
||||
$returnValue = false;
|
||||
|
||||
$styleAttributeValue = strtolower($styleAttributeValue);
|
||||
foreach ($styleList as $style) {
|
||||
if ($styleAttributeValue == strtolower($style)) {
|
||||
$styleAttributeValue = $style;
|
||||
$returnValue = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
protected static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
|
||||
{
|
||||
return ($simple === null)
|
||||
? new SimpleXMLElement('<xml></xml>')
|
||||
: ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user