Proper mime type detection for XLSX files

Heuristics to detect proper mime type for XLSX files expect to see
certain files at the beginning of the XLSX archive. The order in which
the XML files are added therefore matters.
Specifically, "[Content_Types].xml" should be added first, followed by the
files located in the "xl" folder (at least 1 file).
This commit is contained in:
Adrien Loison 2015-12-05 00:13:56 -08:00
parent 05a9a1b60a
commit 728dd3b399
2 changed files with 45 additions and 1 deletions

View File

@ -11,6 +11,7 @@ namespace Box\Spout\Writer\Common\Helper;
class ZipHelper class ZipHelper
{ {
const ZIP_EXTENSION = '.zip'; const ZIP_EXTENSION = '.zip';
const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml';
/** /**
* Zips the root folder and streams the contents of the zip into the given stream * Zips the root folder and streams the contents of the zip into the given stream
@ -61,7 +62,12 @@ class ZipHelper
$folderRealPath = $this->getNormalizedRealPath($folderPath) . '/'; $folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
$itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); $itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($itemIterator as $itemInfo) { // In order to have the file's mime type detected properly, items need to be
// sorted in a particular order...
$itemsInfo = iterator_to_array($itemIterator);
usort($itemsInfo, [$this, 'sortItemsForCorrectMimeTypeDetection']);
foreach ($itemsInfo as $itemInfo) {
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname()); $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath); $itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
@ -73,6 +79,29 @@ class ZipHelper
} }
} }
/**
* On order to have the file's mime type detected properly, files need to be added
* to the zip file in a particular order.
* [Content_Types].xml and files located in "xl" folder should be zipped first.
*
* @param \SplFileInfo $itemInfo1 First item to compare
* @param \SplFileInfo $itemInfo2 Second item to compare
* @return int
*/
protected function sortItemsForCorrectMimeTypeDetection($itemInfo1, $itemInfo2)
{
// Have the "[Content_Types].xml" file be first
if ($itemInfo1->getFilename() === self::CONTENT_TYPES_XML_FILE_NAME) {
return -1;
} else if ($itemInfo2->getFilename() === self::CONTENT_TYPES_XML_FILE_NAME) {
return 1;
} else {
// Then make sure the files in the "xl" folder will go next
// by sorting items in reverse alphabetical order
return strcmp($itemInfo2->getRealPath(), $itemInfo1->getRealPath());
}
}
/** /**
* Returns canonicalized absolute pathname, containing only forward slashes. * Returns canonicalized absolute pathname, containing only forward slashes.
* *

View File

@ -385,6 +385,21 @@ class WriterTest extends \PHPUnit_Framework_TestCase
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"'); $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"');
} }
/**
* @return void
*/
public function testGeneratedFileShouldHaveTheCorrectMimeType()
{
$fileName = 'test_mime_type.xlsx';
$resourcePath = $this->getGeneratedResourcePath($fileName);
$dataRows = [['foo']];
$this->writeToXLSXFile($dataRows, $fileName);
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$this->assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $finfo->file($resourcePath));
}
/** /**
* @param array $allRows * @param array $allRows
* @param string $fileName * @param string $fileName