Completed
Push — master ( 2ed303...03866a )
by Adrien
02:38
created

SheetHelper::getSheetDataXMLFilePathForSheetId()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 30
ccs 18
cts 18
cp 1
rs 8.439
cc 6
eloc 14
nc 6
nop 1
crap 6
1
<?php
2
3
namespace Box\Spout\Reader\XLSX\Helper;
4
5
use Box\Spout\Reader\Wrapper\XMLReader;
6
use Box\Spout\Reader\XLSX\Sheet;
7
8
/**
9
 * Class SheetHelper
10
 * This class provides helper functions related to XLSX sheets
11
 *
12
 * @package Box\Spout\Reader\XLSX\Helper
13
 */
14
class SheetHelper
15
{
16
    /** Paths of XML files relative to the XLSX file root */
17
    const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
18
    const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
19
20
    /** @var string Path of the XLSX file being read */
21
    protected $filePath;
22
23
    /** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
24
    protected $sharedStringsHelper;
25
26
    /** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
27
    protected $globalFunctionsHelper;
28
29
    /** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
30
    protected $shouldFormatDates;
31
32
    /**
33
     * @param string $filePath Path of the XLSX file being read
34
     * @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings
35
     * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
36
     * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
37
     */
38 84
    public function __construct($filePath, $sharedStringsHelper, $globalFunctionsHelper, $shouldFormatDates)
39
    {
40 84
        $this->filePath = $filePath;
41 84
        $this->sharedStringsHelper = $sharedStringsHelper;
42 84
        $this->globalFunctionsHelper = $globalFunctionsHelper;
43 84
        $this->shouldFormatDates = $shouldFormatDates;
44 84
    }
45
46
    /**
47
     * Returns the sheets metadata of the file located at the previously given file path.
48
     * The paths to the sheets' data are read from the [Content_Types].xml file.
49
     *
50
     * @return Sheet[] Sheets within the XLSX file
51
     */
52 84
    public function getSheets()
53
    {
54 84
        $sheets = [];
55 84
        $sheetIndex = 0;
56
57 84
        $xmlReader = new XMLReader();
58 84
        if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_FILE_PATH)) {
59 84
            while ($xmlReader->read()) {
60 84
                if ($xmlReader->isPositionedOnStartingNode('sheet')) {
61 81
                    $sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex);
62 81
                    $sheetIndex++;
63 84
                } else if ($xmlReader->isPositionedOnEndingNode('sheets')) {
64
                    // stop reading once all sheets have been read
65 84
                    break;
66
                }
67 84
            }
68
69 84
            $xmlReader->close();
70 84
        }
71
72 84
        return $sheets;
73
    }
74
75
    /**
76
     * Returns an instance of a sheet, given the XML node describing the sheet - from "workbook.xml".
77
     * We can find the XML file path describing the sheet inside "workbook.xml.res", by mapping with the sheet ID
78
     * ("r:id" in "workbook.xml", "Id" in "workbook.xml.res").
79
     *
80
     * @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml"
81
     * @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
82
     * @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
83
     */
84 81
    protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased)
85
    {
86 81
        $sheetId = $xmlReaderOnSheetNode->getAttribute('r:id');
87 81
        $escapedSheetName = $xmlReaderOnSheetNode->getAttribute('name');
88
89
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
90 81
        $escaper = new \Box\Spout\Common\Escaper\XLSX();
91 81
        $sheetName = $escaper->unescape($escapedSheetName);
92
93 81
        $sheetDataXMLFilePath = $this->getSheetDataXMLFilePathForSheetId($sheetId);
94
95 81
        return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $this->shouldFormatDates, $sheetIndexZeroBased, $sheetName);
96
    }
97
98
    /**
99
     * @param string $sheetId The sheet ID, as defined in "workbook.xml"
100
     * @return string The XML file path describing the sheet inside "workbook.xml.res", for the given sheet ID
101
     */
102 81
    protected function getSheetDataXMLFilePathForSheetId($sheetId)
103
    {
104 81
        $sheetDataXMLFilePath = '';
105
106
        // find the file path of the sheet, by looking at the "workbook.xml.res" file
107 81
        $xmlReader = new XMLReader();
108 81
        if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_RELS_FILE_PATH)) {
109 81
            while ($xmlReader->read()) {
110 81
                if ($xmlReader->isPositionedOnStartingNode('Relationship')) {
111 81
                    $relationshipSheetId = $xmlReader->getAttribute('Id');
112
113 81
                    if ($relationshipSheetId === $sheetId) {
114
                        // In workbook.xml.rels, it is only "worksheets/sheet1.xml"
115
                        // In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
116 81
                        $sheetDataXMLFilePath = $xmlReader->getAttribute('Target');
117
118
                        // sometimes, the sheet data file path already contains "/xl/"...
119 81
                        if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
120 78
                            $sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
121 78
                            break;
122
                        }
123 3
                    }
124 69
                }
125 81
            }
126
127 81
            $xmlReader->close();
128 81
        }
129
130 81
        return $sheetDataXMLFilePath;
131
    }
132
}
133