Skip to content
This repository was archived by the owner on May 26, 2022. It is now read-only.

Commit c48c07d

Browse files
committed
Merge pull request #165 from box/support_xlsx_sheets_random_order
Support XLSX that are defined in random order
2 parents 4bfbb41 + a804be4 commit c48c07d

File tree

6 files changed

+52
-50
lines changed

6 files changed

+52
-50
lines changed

src/Spout/Reader/ODS/Sheet.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ class Sheet implements SheetInterface
1919
/** @var int ID of the sheet */
2020
protected $id;
2121

22-
/** @var int Index of the sheet, based on order of creation (zero-based) */
22+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
2323
protected $index;
2424

2525
/** @var string Name of the sheet */
2626
protected $name;
2727

2828
/**
2929
* @param XMLReader $xmlReader XML Reader, positioned on the "<table:table>" element
30-
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
30+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
3131
* @param string $sheetName Name of the sheet
3232
*/
3333
public function __construct($xmlReader, $sheetIndex, $sheetName)
@@ -48,7 +48,7 @@ public function getRowIterator()
4848

4949
/**
5050
* @api
51-
* @return int Index of the sheet, based on order of creation (zero-based)
51+
* @return int Index of the sheet, based on order in the workbook (zero-based)
5252
*/
5353
public function getIndex()
5454
{

src/Spout/Reader/XLSX/Helper/SheetHelper.php

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313
*/
1414
class SheetHelper
1515
{
16-
/** Extension for XML files */
17-
const XML_EXTENSION = '.xml';
18-
1916
/** Paths of XML files relative to the XLSX file root */
2017
const CONTENT_TYPES_XML_FILE_PATH = '[Content_Types].xml';
2118
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
@@ -79,9 +76,15 @@ public function getSheets()
7976
$sheetNode = $sheetNodes[$i];
8077
$sheetDataXMLFilePath = $sheetNode->getAttribute('PartName');
8178

82-
$sheets[] = $this->getSheetFromXML($sheetDataXMLFilePath, $i);
79+
$sheets[] = $this->getSheetFromXML($sheetDataXMLFilePath);
8380
}
8481

82+
// make sure the sheets are sorted by index
83+
// (as the sheets are not necessarily in this order in the XML file)
84+
usort($sheets, function ($sheet1, $sheet2) {
85+
return ($sheet1->getIndex() - $sheet2->getIndex());
86+
});
87+
8588
return $sheets;
8689
}
8790

@@ -91,60 +94,37 @@ public function getSheets()
9194
* Then we look at "xl/worbook.xml" to find the sheet entry associated to the found ID.
9295
* The entry contains the ID and name of the sheet.
9396
*
94-
* If this piece of data can't be found by parsing the different XML files, the ID will default
95-
* to the sheet index, based on order in [Content_Types].xml. Similarly, the sheet's name will
96-
* default to the data sheet XML file name ("xl/worksheets/sheet2.xml" => "sheet2").
97-
*
9897
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
99-
* @param int $sheetIndexZeroBased Index of the sheet, based on order in [Content_Types].xml (zero-based)
10098
* @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
10199
*/
102-
protected function getSheetFromXML($sheetDataXMLFilePath, $sheetIndexZeroBased)
100+
protected function getSheetFromXML($sheetDataXMLFilePath)
103101
{
104-
$sheetName = $this->getDefaultSheetName($sheetDataXMLFilePath);
105-
106-
/*
107-
* In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
108-
* In workbook.xml.rels, it is only "worksheets/sheet1.xml"
109-
*/
102+
// In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
103+
// In workbook.xml.rels, it is only "worksheets/sheet1.xml"
110104
$sheetDataXMLFilePathInWorkbookXMLRels = ltrim($sheetDataXMLFilePath, '/xl/');
111105

112106
// find the node associated to the given file path
113107
$workbookXMLResElement = $this->getWorkbookXMLRelsAsXMLElement();
114108
$relationshipNodes = $workbookXMLResElement->xpath('//ns:Relationship[@Target="' . $sheetDataXMLFilePathInWorkbookXMLRels . '"]');
109+
$relationshipNode = $relationshipNodes[0];
115110

116-
if (count($relationshipNodes) === 1) {
117-
$relationshipNode = $relationshipNodes[0];
118-
$sheetId = $relationshipNode->getAttribute('Id');
111+
$relationshipSheetId = $relationshipNode->getAttribute('Id');
119112

120-
$workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
121-
$sheetNodes = $workbookXMLElement->xpath('//ns:sheet[@r:id="' . $sheetId . '"]');
113+
$workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
114+
$sheetNodes = $workbookXMLElement->xpath('//ns:sheet[@r:id="' . $relationshipSheetId . '"]');
115+
$sheetNode = $sheetNodes[0];
122116

123-
if (count($sheetNodes) === 1) {
124-
$sheetNode = $sheetNodes[0];
125-
$escapedSheetName = $sheetNode->getAttribute('name');
117+
$escapedSheetName = $sheetNode->getAttribute('name');
118+
$sheetIdOneBased = $sheetNode->getAttribute('sheetId');
119+
$sheetIndexZeroBased = $sheetIdOneBased - 1;
126120

127-
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
128-
$escaper = new \Box\Spout\Common\Escaper\XLSX();
129-
$sheetName = $escaper->unescape($escapedSheetName);
130-
}
131-
}
121+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
122+
$escaper = new \Box\Spout\Common\Escaper\XLSX();
123+
$sheetName = $escaper->unescape($escapedSheetName);
132124

133125
return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $sheetIndexZeroBased, $sheetName);
134126
}
135127

136-
/**
137-
* Returns the default name of the sheet whose data is located
138-
* at the given path.
139-
*
140-
* @param string $sheetDataXMLFilePath Path of the sheet data XML file
141-
* @return string The default sheet name
142-
*/
143-
protected function getDefaultSheetName($sheetDataXMLFilePath)
144-
{
145-
return $this->globalFunctionsHelper->basename($sheetDataXMLFilePath, self::XML_EXTENSION);
146-
}
147-
148128
/**
149129
* Returns a representation of the workbook.xml.rels file, ready to be parsed.
150130
* The returned value is cached.

src/Spout/Reader/XLSX/Sheet.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Sheet implements SheetInterface
1515
/** @var \Box\Spout\Reader\XLSX\RowIterator To iterate over sheet's rows */
1616
protected $rowIterator;
1717

18-
/** @var int Index of the sheet, based on order of creation (zero-based) */
18+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
1919
protected $index;
2020

2121
/** @var string Name of the sheet */
@@ -25,7 +25,7 @@ class Sheet implements SheetInterface
2525
* @param string $filePath Path of the XLSX file being read
2626
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
2727
* @param Helper\SharedStringsHelper Helper to work with shared strings
28-
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
28+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
2929
* @param string $sheetName Name of the sheet
3030
*/
3131
public function __construct($filePath, $sheetDataXMLFilePath, $sharedStringsHelper, $sheetIndex, $sheetName)
@@ -46,7 +46,7 @@ public function getRowIterator()
4646

4747
/**
4848
* @api
49-
* @return int Index of the sheet, based on order of creation (zero-based)
49+
* @return int Index of the sheet, based on order in the workbook (zero-based)
5050
*/
5151
public function getIndex()
5252
{

src/Spout/Writer/Common/Sheet.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Sheet
2424
/** @var array Associative array [SHEET_INDEX] => [SHEET_NAME] keeping track of sheets' name to enforce uniqueness */
2525
protected static $SHEETS_NAME_USED = [];
2626

27-
/** @var int Index of the sheet, based on order of creation (zero-based) */
27+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
2828
protected $index;
2929

3030
/** @var string Name of the sheet */
@@ -34,7 +34,7 @@ class Sheet
3434
protected $stringHelper;
3535

3636
/**
37-
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
37+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
3838
*/
3939
public function __construct($sheetIndex)
4040
{
@@ -45,7 +45,7 @@ public function __construct($sheetIndex)
4545

4646
/**
4747
* @api
48-
* @return int Index of the sheet, based on order of creation (zero-based)
48+
* @return int Index of the sheet, based on order in the workbook (zero-based)
4949
*/
5050
public function getIndex()
5151
{

tests/Spout/Reader/XLSX/ReaderTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ public function testReadForAllWorksheets($resourceName, $expectedNumOfRows, $exp
7373
}
7474
}
7575

76+
/**
77+
* @return void
78+
*/
79+
public function testReadShouldSupportSheetsDefinitionInRandomOrder()
80+
{
81+
$allRows = $this->getAllRowsForFile('two_sheets_with_sheets_definition_in_reverse_order.xlsx');
82+
83+
$expectedRows = [
84+
['s1 - A1', 's1 - B1', 's1 - C1', 's1 - D1', 's1 - E1'],
85+
['s1 - A2', 's1 - B2', 's1 - C2', 's1 - D2', 's1 - E2'],
86+
['s1 - A3', 's1 - B3', 's1 - C3', 's1 - D3', 's1 - E3'],
87+
['s1 - A4', 's1 - B4', 's1 - C4', 's1 - D4', 's1 - E4'],
88+
['s1 - A5', 's1 - B5', 's1 - C5', 's1 - D5', 's1 - E5'],
89+
['s2 - A1', 's2 - B1', 's2 - C1', 's2 - D1', 's2 - E1'],
90+
['s2 - A2', 's2 - B2', 's2 - C2', 's2 - D2', 's2 - E2'],
91+
['s2 - A3', 's2 - B3', 's2 - C3', 's2 - D3', 's2 - E3'],
92+
['s2 - A4', 's2 - B4', 's2 - C4', 's2 - D4', 's2 - E4'],
93+
['s2 - A5', 's2 - B5', 's2 - C5', 's2 - D5', 's2 - E5'],
94+
];
95+
$this->assertEquals($expectedRows, $allRows);
96+
}
97+
7698
/**
7799
* @return void
78100
*/
Binary file not shown.

0 commit comments

Comments
 (0)