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
Changes from 1 commit
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
Prev Previous commit
add stream support to Reader/Csv
  • Loading branch information
apreiml committed Oct 5, 2023
commit 3d56244cd8942eb09df15f5e07624eca73d75a9b
3 changes: 2 additions & 1 deletion src/PhpSpreadsheet/Reader/BaseReader.php
Original file line number Diff line number Diff line change
@@ -194,7 +194,6 @@ public function load($file, int $flags = 0): Spreadsheet
*/
protected function openFile($file): void
{
$fileHandle = false;
if (is_string($file)) {
$filename = $file;
File::assertFile($file);
@@ -204,6 +203,8 @@ protected function openFile($file): void
} 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.');
80 changes: 53 additions & 27 deletions src/PhpSpreadsheet/Reader/Csv.php
Original file line number Diff line number Diff line change
@@ -281,22 +281,20 @@ public function loadSpreadsheetFromString(string $contents): Spreadsheet
}

/**
* @param resource|string $filename
* @param resource|string $file
*/
private function openFileOrMemory($filename): void
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;
@@ -355,26 +353,31 @@ private function openDataUri(string $filename): void
*
* @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;

@@ -559,23 +562,33 @@ public function canRead($file): bool
return false;
}

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

// Trust file extension if any
$extension = strtolower(pathinfo($file, 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($file);
$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);
}

@@ -589,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');
@@ -613,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');
@@ -628,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;
19 changes: 19 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Csv/CsvLoadFromStringTest.php
Original file line number Diff line number Diff line change
@@ -25,4 +25,23 @@ public function testLoadFromString(): void
self::AssertSame('7 , 8', $sheet->getCell('A3')->getValue());
self::AssertSame("12\n13", $sheet->getCell('B4')->getValue());
}

public function testLoadFromStream(): void
{
$data = <<<EOF
1,2,3
4,2+3,6
"7 , 8", 9, 10
11,"12
13",14
EOF;
$stream = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
self::assertNotFalse($stream);
$reader = new Csv();
$spreadsheet = $reader->load($stream);
$sheet = $spreadsheet->getActiveSheet();
self::AssertSame('2+3', $sheet->getCell('B2')->getValue());
self::AssertSame('7 , 8', $sheet->getCell('A3')->getValue());
self::AssertSame("12\n13", $sheet->getCell('B4')->getValue());
}
}
17 changes: 17 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Csv/CsvTest.php
Original file line number Diff line number Diff line change
@@ -96,6 +96,23 @@ public function testCanLoad(bool $expected, string $filename): void
self::assertSame($expected, $reader->canRead($filename));
}

/**
* @dataProvider providerCanLoad
*/
public function testCanLoadFromStream(bool $expected, string $filename): void
{
$reader = new Csv();
$stream = fopen('php://memory', 'r+b');
self::assertNotFalse($stream);

$contents = file_get_contents($filename);
self::assertNotFalse($contents);
fwrite($stream, $contents);
rewind($stream);

self::assertSame($expected, $reader->canRead($stream));
}

public static function providerCanLoad(): array
{
return [