mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-08-30 20:18:00 +00:00
Xls Reader ListWorksheetInfo and New ListWorksheetDimensions
PR #4687 corrected how Xls Writer generated its Dimensions records. We ignore the Dimensions record on read since it does not affect our processing in the slightest. However, the PR raises the possibility that someone might wish to see the data in the Dimensions record. (The PR did an adequate test for retrieving Dimensions data, but it is not generalizable.) To accommodate such a case, we add a new ListWorksheetDimensions function to Xls Reader, similar to ListWorksheetInfo. As luck would have it, the spreadsheet with which I tested the new function produced incorrect results for ListWorksheetInfo, which was ignoring XLS_TYPE_MULRK records. So I added the necessary code to fix ListWorksheetInfo as well.
This commit is contained in:
@@ -294,6 +294,16 @@ class Xls extends XlsBase
|
||||
return (new Xls\ListFunctions())->listWorksheetInfo2($filename, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @return array<int, array{worksheetName: string, dimensionsMinR: int, dimensionsMinC: int, dimensionsMaxR: int, dimensionsMaxC: int, lastColumnLetter: string}>
|
||||
*/
|
||||
public function listWorksheetDimensions(string $filename): array
|
||||
{
|
||||
return (new Xls\ListFunctions())->listWorksheetDimensions2($filename, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
*/
|
||||
|
||||
@@ -124,6 +124,7 @@ class ListFunctions extends Xls
|
||||
case self::XLS_TYPE_FORMULA:
|
||||
case self::XLS_TYPE_BOOLERR:
|
||||
case self::XLS_TYPE_LABEL:
|
||||
case self::XLS_TYPE_MULRK:
|
||||
$length = self::getUInt2d($xls->data, $xls->pos + 2);
|
||||
$recordData = $xls->readRecordData($xls->data, $xls->pos + 4, $length);
|
||||
|
||||
@@ -131,7 +132,11 @@ class ListFunctions extends Xls
|
||||
$xls->pos += 4 + $length;
|
||||
|
||||
$rowIndex = self::getUInt2d($recordData, 0) + 1;
|
||||
$columnIndex = self::getUInt2d($recordData, 2);
|
||||
if ($code === self::XLS_TYPE_MULRK) {
|
||||
$columnIndex = self::getUInt2d($recordData, $length - 2);
|
||||
} else {
|
||||
$columnIndex = self::getUInt2d($recordData, 2);
|
||||
}
|
||||
|
||||
$tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
|
||||
$tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
|
||||
@@ -160,4 +165,103 @@ class ListFunctions extends Xls
|
||||
|
||||
return $worksheetInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @return array<int, array{worksheetName: string, dimensionsMinR: int, dimensionsMinC: int, dimensionsMaxR: int, dimensionsMaxC: int, lastColumnLetter: string}>
|
||||
*/
|
||||
protected function listWorksheetDimensions2(string $filename, Xls $xls): array
|
||||
{
|
||||
File::assertFile($filename);
|
||||
|
||||
$worksheetInfo = [];
|
||||
|
||||
// Read the OLE file
|
||||
$xls->loadOLE($filename);
|
||||
|
||||
// total byte size of Excel data (workbook global substream + sheet substreams)
|
||||
$xls->dataSize = strlen($xls->data);
|
||||
|
||||
// initialize
|
||||
$xls->pos = 0;
|
||||
$xls->sheets = [];
|
||||
|
||||
// Parse Workbook Global Substream
|
||||
while ($xls->pos < $xls->dataSize) {
|
||||
$code = self::getUInt2d($xls->data, $xls->pos);
|
||||
|
||||
match ($code) {
|
||||
self::XLS_TYPE_BOF => $xls->readBof(),
|
||||
self::XLS_TYPE_SHEET => $xls->readSheet(),
|
||||
self::XLS_TYPE_EOF => $xls->readDefault(),
|
||||
self::XLS_TYPE_CODEPAGE => $xls->readCodepage(),
|
||||
default => $xls->readDefault(),
|
||||
};
|
||||
|
||||
if ($code === self::XLS_TYPE_EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the individual sheets
|
||||
foreach ($xls->sheets as $sheet) {
|
||||
if ($sheet['sheetType'] !== 0x00) {
|
||||
// 0x00: Worksheet
|
||||
// 0x02: Chart
|
||||
// 0x06: Visual Basic module
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmpInfo = [];
|
||||
$tmpInfo['worksheetName'] = StringHelper::convertToString($sheet['name']);
|
||||
$tmpInfo['dimensionsMinR'] = -1;
|
||||
$tmpInfo['dimensionsMaxR'] = -1;
|
||||
$tmpInfo['dimensionsMinC'] = -1;
|
||||
$tmpInfo['dimensionsMaxC'] = -1;
|
||||
$tmpInfo['lastColumnLetter'] = '';
|
||||
|
||||
$xls->pos = $sheet['offset'];
|
||||
|
||||
while ($xls->pos <= $xls->dataSize - 4) {
|
||||
$code = self::getUInt2d($xls->data, $xls->pos);
|
||||
|
||||
switch ($code) {
|
||||
case self::XLS_TYPE_BOF:
|
||||
$xls->readBof();
|
||||
|
||||
break;
|
||||
case self::XLS_TYPE_EOF:
|
||||
$xls->readDefault();
|
||||
|
||||
break 2;
|
||||
case self::XLS_TYPE_DIMENSION:
|
||||
$length = self::getUInt2d($xls->data, $xls->pos + 2);
|
||||
if ($length === 14) {
|
||||
$dimensionsData = substr($xls->data, $xls->pos + 4, $length);
|
||||
$data = unpack('VrwMic/VrwMac/vcolMic/vcolMac/vreserved', $dimensionsData);
|
||||
if (is_array($data)) {
|
||||
/** @var int[] $data */
|
||||
$tmpInfo['dimensionsMinR'] = $data['rwMic'];
|
||||
$tmpInfo['dimensionsMaxR'] = $data['rwMac'];
|
||||
$tmpInfo['dimensionsMinC'] = $data['colMic'];
|
||||
$tmpInfo['dimensionsMaxC'] = $data['colMac'];
|
||||
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['dimensionsMaxC']);
|
||||
}
|
||||
}
|
||||
$xls->readDefault();
|
||||
|
||||
break;
|
||||
default:
|
||||
$xls->readDefault();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheetInfo[] = $tmpInfo;
|
||||
}
|
||||
|
||||
return $worksheetInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_FORMULA = 0x0006;
|
||||
final const XLS_TYPE_EOF = 0x000A;
|
||||
final const XLS_TYPE_PROTECT = 0x0012;
|
||||
final const XLS_TYPE_OBJECTPROTECT = 0x0063;
|
||||
final const XLS_TYPE_SCENPROTECT = 0x00DD;
|
||||
final const XLS_TYPE_PASSWORD = 0x0013;
|
||||
final const XLS_TYPE_HEADER = 0x0014;
|
||||
final const XLS_TYPE_FOOTER = 0x0015;
|
||||
@@ -50,6 +48,7 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_CODEPAGE = 0x0042;
|
||||
final const XLS_TYPE_DEFCOLWIDTH = 0x0055;
|
||||
final const XLS_TYPE_OBJ = 0x005D;
|
||||
final const XLS_TYPE_OBJECTPROTECT = 0x0063;
|
||||
final const XLS_TYPE_COLINFO = 0x007D;
|
||||
final const XLS_TYPE_IMDATA = 0x007F;
|
||||
final const XLS_TYPE_SHEETPR = 0x0081;
|
||||
@@ -62,6 +61,7 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_MULRK = 0x00BD;
|
||||
final const XLS_TYPE_MULBLANK = 0x00BE;
|
||||
final const XLS_TYPE_DBCELL = 0x00D7;
|
||||
final const XLS_TYPE_SCENPROTECT = 0x00DD;
|
||||
final const XLS_TYPE_XF = 0x00E0;
|
||||
final const XLS_TYPE_MERGEDCELLS = 0x00E5;
|
||||
final const XLS_TYPE_MSODRAWINGGROUP = 0x00EB;
|
||||
@@ -70,6 +70,8 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_LABELSST = 0x00FD;
|
||||
final const XLS_TYPE_EXTSST = 0x00FF;
|
||||
final const XLS_TYPE_EXTERNALBOOK = 0x01AE;
|
||||
final const XLS_TYPE_CFHEADER = 0x01B0;
|
||||
final const XLS_TYPE_CFRULE = 0x01B1;
|
||||
final const XLS_TYPE_DATAVALIDATIONS = 0x01B2;
|
||||
final const XLS_TYPE_TXO = 0x01B6;
|
||||
final const XLS_TYPE_HYPERLINK = 0x01B8;
|
||||
@@ -90,13 +92,11 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_FORMAT = 0x041E;
|
||||
final const XLS_TYPE_SHAREDFMLA = 0x04BC;
|
||||
final const XLS_TYPE_BOF = 0x0809;
|
||||
final const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
||||
final const XLS_TYPE_SHEETPROTECTION = 0x0867;
|
||||
final const XLS_TYPE_RANGEPROTECTION = 0x0868;
|
||||
final const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
||||
final const XLS_TYPE_XFEXT = 0x087D;
|
||||
final const XLS_TYPE_PAGELAYOUTVIEW = 0x088B;
|
||||
final const XLS_TYPE_CFHEADER = 0x01B0;
|
||||
final const XLS_TYPE_CFRULE = 0x01B1;
|
||||
final const XLS_TYPE_UNKNOWN = 0xFFFF;
|
||||
|
||||
// Encryption type
|
||||
|
||||
@@ -165,4 +165,50 @@ class InfoNamesTest extends TestCase
|
||||
self::assertSame('Użytkownik Microsoft Office', $properties->getLastModifiedBy());
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
}
|
||||
|
||||
public function testDimensions(): void
|
||||
{
|
||||
$filename = 'tests/data/Reader/XLS/pr.4687.excel.xls';
|
||||
$reader = new Xls();
|
||||
$info = $reader->listWorksheetInfo($filename);
|
||||
$expected = [
|
||||
[
|
||||
'worksheetName' => 'Sheet1',
|
||||
'lastColumnLetter' => 'D',
|
||||
'lastColumnIndex' => 3,
|
||||
'totalRows' => 2,
|
||||
'totalColumns' => 4,
|
||||
'sheetState' => 'visible',
|
||||
],
|
||||
[
|
||||
'worksheetName' => 'Sheet2',
|
||||
'lastColumnLetter' => 'B',
|
||||
'lastColumnIndex' => 1,
|
||||
'totalRows' => 4,
|
||||
'totalColumns' => 2,
|
||||
'sheetState' => 'visible',
|
||||
],
|
||||
];
|
||||
self::assertSame($expected, $info);
|
||||
$info = $reader->listWorksheetDimensions($filename);
|
||||
$expected = [
|
||||
[
|
||||
'worksheetName' => 'Sheet1',
|
||||
'dimensionsMinR' => 0,
|
||||
'dimensionsMaxR' => 2,
|
||||
'dimensionsMinC' => 0,
|
||||
'dimensionsMaxC' => 4,
|
||||
'lastColumnLetter' => 'D',
|
||||
],
|
||||
[
|
||||
'worksheetName' => 'Sheet2',
|
||||
'dimensionsMinR' => 0,
|
||||
'dimensionsMaxR' => 4,
|
||||
'dimensionsMinC' => 0,
|
||||
'dimensionsMaxC' => 2,
|
||||
'lastColumnLetter' => 'B',
|
||||
],
|
||||
];
|
||||
self::assertSame($expected, $info);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user