Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stream support to IReader #3759

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions src/PhpSpreadsheet/Reader/BaseReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,40 +160,51 @@ protected function processFlags(int $flags): void
}
}

protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
/**
* @param resource|string $file
*/
protected function loadSpreadsheetFromFile($file): Spreadsheet
{
throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method');
}

/**
* Loads Spreadsheet from file.
*
* @param resource|string $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
public function load($file, int $flags = 0): Spreadsheet
{
$this->processFlags($flags);

try {
return $this->loadSpreadsheetFromFile($filename);
return $this->loadSpreadsheetFromFile($file);
} catch (ReaderException $e) {
throw $e;
}
}

/**
* Open file for reading.
*
* @param resource|string $file
*/
protected function openFile(string $filename): void
protected function openFile($file): void
{
$fileHandle = false;
if ($filename) {
File::assertFile($filename);
if (is_string($file)) {
$filename = $file;
File::assertFile($file);

// Open file
$fileHandle = fopen($filename, 'rb');
$fileHandle = fopen($file, 'rb');
} elseif (is_resource($file)) {
$filename = 'stream';
$fileHandle = $file;
} else {
throw new ReaderException('invalid type for file. Only file path or a stream resource is allowed');
}
if ($fileHandle === false) {
throw new ReaderException('Could not open file ' . $filename . ' for reading.');
Expand All @@ -204,8 +215,10 @@ protected function openFile(string $filename): void

/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param resource|string $file
*/
public function listWorksheetInfo(string $filename): array
public function listWorksheetInfo($file): array
{
throw new PhpSpreadsheetException('Reader classes must implement their own listWorksheetInfo() method');
}
Expand All @@ -215,11 +228,13 @@ public function listWorksheetInfo(string $filename): array
* possibly without parsing the whole file to a Spreadsheet object.
* Readers will often have a more efficient method with which
* they can override this method.
*
* @param resource|string $file
*/
public function listWorksheetNames(string $filename): array
public function listWorksheetNames($file): array
{
$returnArray = [];
$info = $this->listWorksheetInfo($filename);
$info = $this->listWorksheetInfo($file);
foreach ($info as $infoArray) {
if (isset($infoArray['worksheetName'])) {
$returnArray[] = $infoArray['worksheetName'];
Expand Down
101 changes: 69 additions & 32 deletions src/PhpSpreadsheet/Reader/Csv.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,13 @@ protected function inferSeparator(): void

/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param resource|string $file
*/
public function listWorksheetInfo(string $filename): array
public function listWorksheetInfo($file): array
{
// Open file
$this->openFileOrMemory($filename);
$this->openFileOrMemory($file);
$fileHandle = $this->fileHandle;

// Skip BOM, if any
Expand Down Expand Up @@ -254,14 +256,16 @@ public function listWorksheetInfo(string $filename): array

/**
* Loads Spreadsheet from file.
*
* @param resource|string $file
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
protected function loadSpreadsheetFromFile($file): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();

// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
return $this->loadIntoExisting($file, $spreadsheet);
}

/**
Expand All @@ -276,20 +280,21 @@ public function loadSpreadsheetFromString(string $contents): Spreadsheet
return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true);
}

private function openFileOrMemory(string $filename): void
/**
* @param resource|string $file
*/
private function openFileOrMemory($file): void
{
// Open file
$fhandle = $this->canRead($filename);
if (!$fhandle) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
if (!$this->canRead($file)) {
throw new Exception($file . ' is an Invalid Spreadsheet file.');
}
if ($this->inputEncoding === self::GUESS_ENCODING) {
$this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
$this->inputEncoding = self::guessEncoding($file, $this->fallbackEncoding);
}
$this->openFile($filename);
$this->openFile($file);
if ($this->inputEncoding !== 'UTF-8') {
fclose($this->fileHandle);
$entireFile = file_get_contents($filename);
$entireFile = stream_get_contents($this->fileHandle);
$fileHandle = fopen('php://memory', 'r+b');
if ($fileHandle !== false && $entireFile !== false) {
$this->fileHandle = $fileHandle;
Expand Down Expand Up @@ -345,25 +350,34 @@ private function openDataUri(string $filename): void

/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*
* @param resource|string $file
*/
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
public function loadIntoExisting($file, Spreadsheet $spreadsheet): Spreadsheet
{
return $this->loadStringOrFile($filename, $spreadsheet, false);
return $this->loadStringOrFile($file, $spreadsheet, false);
}

/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*
* @param resource|string $file
*/
private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
private function loadStringOrFile($file, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
{
// Deprecated in Php8.1
$iniset = $this->setAutoDetect('1');

// Open file
if ($dataUri) {
$this->openDataUri($filename);
if (!is_string($file)) {
throw new \Exception('$file must be an uri');
}
$this->openDataUri($file);
$filename = $file;
} else {
$this->openFileOrMemory($filename);
$this->openFileOrMemory($file);
$filename = 'escape';
}
$fileHandle = $this->fileHandle;

Expand Down Expand Up @@ -539,32 +553,42 @@ public function getEscapeCharacter(): string
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
public function canRead($file): bool
{
// Check if file exists
try {
$this->openFile($filename);
$this->openFile($file);
} catch (ReaderException) {
return false;
}

fclose($this->fileHandle);
rewind($this->fileHandle);

// Trust file extension if any
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (in_array($extension, ['csv', 'tsv'])) {
return true;
if (is_string($file)) {
// Trust file extension if any
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (in_array($extension, ['csv', 'tsv'])) {
return true;
}
}

// Attempt to guess mimetype
$type = mime_content_type($filename);
$type = mime_content_type($this->fileHandle);
$supportedTypes = [
'application/csv',
'text/csv',
'text/plain',
'inode/x-empty',
];

if (is_resource($file)) {
// reading mime types from a stream causes sometimes different results
$supportedTypes[] = 'application/x-empty';
$supportedTypes[] = 'text/html';
}

rewind($this->fileHandle);

return in_array($type, $supportedTypes, true);
}

Expand All @@ -578,10 +602,13 @@ private static function guessEncodingTestNoBom(string &$encoding, string &$conte
}
}

private static function guessEncodingNoBom(string $filename): string
/**
* @param resource|string $file
*/
private static function guessEncodingNoBom($file): string
{
$contents = is_resource($file) ? stream_get_contents($file) : file_get_contents($file);
$encoding = '';
$contents = file_get_contents($filename);
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE');
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE');
self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE');
Expand All @@ -602,10 +629,17 @@ private static function guessEncodingTestBom(string &$encoding, string $first4,
}
}

private static function guessEncodingBom(string $filename): string
/**
* @param resource|string $file
*/
private static function guessEncodingBom($file): string
{
$encoding = '';
$first4 = file_get_contents($filename, false, null, 0, 4);
if (is_resource($file)) {
$first4 = stream_get_contents($file, 0, 4);
} else {
$first4 = file_get_contents($file, false, null, 0, 4);
}
if ($first4 !== false) {
self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8');
self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE');
Expand All @@ -617,11 +651,14 @@ private static function guessEncodingBom(string $filename): string
return $encoding;
}

public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
/**
* @param resource|string $file
*/
public static function guessEncoding($file, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
{
$encoding = self::guessEncodingBom($filename);
$encoding = self::guessEncodingBom($file);
if ($encoding === '') {
$encoding = self::guessEncodingNoBom($filename);
$encoding = self::guessEncodingNoBom($file);
}

return ($encoding === '') ? $dflt : $encoding;
Expand Down
54 changes: 39 additions & 15 deletions src/PhpSpreadsheet/Reader/Gnumeric.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,18 @@ public function __construct()

/**
* Can the current IReader read the file?
*
* @param resource|string $file
*/
public function canRead(string $filename): bool
public function canRead($file): bool
{
if (is_resource($file)) {
return false; // TODO
}

$data = null;
if (File::testFileNoThrow($filename)) {
$data = $this->gzfileGetContents($filename);
if (File::testFileNoThrow($file)) {
$data = $this->gzfileGetContents($file);
if (!str_contains($data, self::NAMESPACE_GNM)) {
$data = '';
}
Expand All @@ -99,16 +105,22 @@ private static function matchXml(XMLReader $xml, string $expectedLocalName): boo

/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
* @param resource|string $file
*/
public function listWorksheetNames(string $filename): array
public function listWorksheetNames($file): array
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an invalid Gnumeric file.');
if (is_resource($file)) {
throw new Exception('file as stream not supported');
}

File::assertFile($file);
if (!$this->canRead($file)) {
throw new Exception($file . ' is an invalid Gnumeric file.');
}

$xml = new XMLReader();
$contents = $this->gzfileGetContents($filename);
$contents = $this->gzfileGetContents($file);
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);

Expand All @@ -128,16 +140,22 @@ public function listWorksheetNames(string $filename): array

/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param resource|string $file
*/
public function listWorksheetInfo(string $filename): array
public function listWorksheetInfo($file): array
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an invalid Gnumeric file.');
if (is_resource($file)) {
throw new Exception('file as stream not supported');
}

File::assertFile($file);
if (!$this->canRead($file)) {
throw new Exception($file . ' is an invalid Gnumeric file.');
}

$xml = new XMLReader();
$contents = $this->gzfileGetContents($filename);
$contents = $this->gzfileGetContents($file);
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);

Expand Down Expand Up @@ -229,15 +247,21 @@ private static function testSimpleXml(mixed $value): SimpleXMLElement

/**
* Loads Spreadsheet from file.
*
* @param resource|string $file
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
protected function loadSpreadsheetFromFile($file): Spreadsheet
{
if (is_resource($file)) {
throw new Exception('file as stream not supported');
}

// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
$spreadsheet->removeSheetByIndex(0);

// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
return $this->loadIntoExisting($file, $spreadsheet);
}

/**
Expand Down
Loading