Skip to content

Commit

Permalink
add stream support to Reader/Csv
Browse files Browse the repository at this point in the history
  • Loading branch information
apreiml committed Oct 5, 2023
1 parent 19ec1b5 commit cbfe864
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/PhpSpreadsheet/Reader/BaseReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.');
Expand Down
80 changes: 53 additions & 27 deletions src/PhpSpreadsheet/Reader/Csv.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,22 +281,20 @@ public function loadSpreadsheetFromString(string $contents): Spreadsheet
}

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

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

private static function guessEncodingNoBom(string $filename): string
/**
* @param string|resource $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 @@ -613,10 +629,17 @@ private static function guessEncodingTestBom(string &$encoding, string $first4,
}
}

private static function guessEncodingBom(string $filename): string
/**
* @param string|resource $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 @@ -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 string|resource $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
19 changes: 19 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Csv/CsvLoadFromStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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), 'r');
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
Expand Up @@ -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+');
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 [
Expand Down

0 comments on commit cbfe864

Please sign in to comment.