Completed
Branch fix_sheet_ordering_with_xlsx_f... (ea0002)
by Adrien
06:25 queued 02:35
created

SheetHelper::getWorkbookXMLAsXMLElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
rs 9.4285
ccs 7
cts 7
cp 1
cc 2
eloc 6
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Box\Spout\Reader\XLSX\Helper;
4
5
use Box\Spout\Reader\Wrapper\SimpleXMLElement;
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
    /** Namespaces for the XML files */
21
    const MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS = 'http://schemas.openxmlformats.org/package/2006/relationships';
22
    const MAIN_NAMESPACE_FOR_WORKBOOK_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
23
24
    /** @var string Path of the XLSX file being read */
25
    protected $filePath;
26
27
    /** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
28
    protected $sharedStringsHelper;
29
30
    /** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
31
    protected $globalFunctionsHelper;
32
33
    /** @var \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representing the workbook.xml.rels file */
34
    protected $workbookXMLRelsAsXMLElement;
35
36
    /** @var \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representing the workbook.xml file */
37
    protected $workbookXMLAsXMLElement;
38
39
    /**
40
     * @param string $filePath Path of the XLSX file being read
41
     * @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings
42
     * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
43
     */
44 75
    public function __construct($filePath, $sharedStringsHelper, $globalFunctionsHelper)
45
    {
46 75
        $this->filePath = $filePath;
47 75
        $this->sharedStringsHelper = $sharedStringsHelper;
48 75
        $this->globalFunctionsHelper = $globalFunctionsHelper;
49 75
    }
50
51
    /**
52
     * Returns the sheets metadata of the file located at the previously given file path.
53
     * The paths to the sheets' data are read from the [Content_Types].xml file.
54
     *
55
     * @return Sheet[] Sheets within the XLSX file
56
     */
57 75
    public function getSheets()
58
    {
59 75
        $sheets = [];
60
61
        // Starting from "workbook.xml" as this file is the source of truth for the sheets order
62 75
        $workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
63 75
        $sheetNodes = $workbookXMLElement->xpath('//ns:sheet');
64
65 75
        foreach ($sheetNodes as $sheetIndex => $sheetNode) {
66 72
            $sheets[] = $this->getSheetFromSheetXMLNode($sheetNode, $sheetIndex);
67 75
        }
68
69 75
        return $sheets;
70
    }
71
72
    /**
73
     * Returns an instance of a sheet, given the XML node describing the sheet - from "workbook.xml".
74
     * We can find the XML file path describing the sheet inside "workbook.xml.res", by mapping with the sheet ID
75
     * ("r:id" in "workbook.xml", "Id" in "workbook.xml.res").
76
     *
77
     * @param \Box\Spout\Reader\Wrapper\SimpleXMLElement $sheetNode XML Node describing the sheet, as defined in "workbook.xml"
78
     * @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
79
     * @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
80
     */
81 72
    protected function getSheetFromSheetXMLNode($sheetNode, $sheetIndexZeroBased)
82
    {
83
        // To retrieve namespaced attributes, some versions of LibXML will accept prefixing the attribute
84
        // with the namespace directly (tested on LibXML 2.9.3). For older versions (tested on LibXML 2.7.8),
85
        // attributes need to be retrieved without the namespace hint.
86 72
        $sheetId = $sheetNode->getAttribute('r:id');
87 72
        if ($sheetId === null) {
88 72
            $sheetId = $sheetNode->getAttribute('id');
89 72
        }
90
91 72
        $escapedSheetName = $sheetNode->getAttribute('name');
92
93
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
94 72
        $escaper = new \Box\Spout\Common\Escaper\XLSX();
95 72
        $sheetName = $escaper->unescape($escapedSheetName);
96
97
        // find the file path of the sheet, by looking at the "workbook.xml.res" file
98 72
        $workbookXMLResElement = $this->getWorkbookXMLRelsAsXMLElement();
99 72
        $relationshipNodes = $workbookXMLResElement->xpath('//ns:Relationship[@Id="' . $sheetId . '"]');
100 72
        $relationshipNode = $relationshipNodes[0];
101
102
        // In workbook.xml.rels, it is only "worksheets/sheet1.xml"
103
        // In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
104 72
        $sheetDataXMLFilePath = '/xl/' . $relationshipNode->getAttribute('Target');
105
106 72
        return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $sheetIndexZeroBased, $sheetName);
107
    }
108
109
    /**
110
     * Returns a representation of the workbook.xml.rels file, ready to be parsed.
111
     * The returned value is cached.
112
     *
113
     * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representating the workbook.xml.rels file
114
     */
115 72
    protected function getWorkbookXMLRelsAsXMLElement()
116
    {
117 72
        if (!$this->workbookXMLRelsAsXMLElement) {
118 72
            $this->workbookXMLRelsAsXMLElement = $this->getFileAsXMLElementWithNamespace(
119 72
                self::WORKBOOK_XML_RELS_FILE_PATH,
120
                self::MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS
121 72
            );
122 72
        }
123
124 72
        return $this->workbookXMLRelsAsXMLElement;
125
    }
126
127
    /**
128
     * Returns a representation of the workbook.xml file, ready to be parsed.
129
     * The returned value is cached.
130
     *
131
     * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representating the workbook.xml.rels file
132
     */
133 75
    protected function getWorkbookXMLAsXMLElement()
134
    {
135 75
        if (!$this->workbookXMLAsXMLElement) {
136 75
            $this->workbookXMLAsXMLElement = $this->getFileAsXMLElementWithNamespace(
137 75
                self::WORKBOOK_XML_FILE_PATH,
138
                self::MAIN_NAMESPACE_FOR_WORKBOOK_XML
139 75
            );
140 75
        }
141
142 75
        return $this->workbookXMLAsXMLElement;
143
    }
144
145
    /**
146
     * Loads the contents of the given file in an XML parser and register the given XPath namespace.
147
     *
148
     * @param string $xmlFilePath The path of the XML file inside the XLSX file
149
     * @param string $mainNamespace The main XPath namespace to register
150
     * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement The XML element representing the file
151
     */
152 75
    protected function getFileAsXMLElementWithNamespace($xmlFilePath, $mainNamespace)
153
    {
154 75
        $xmlContents = $this->globalFunctionsHelper->file_get_contents('zip://' . $this->filePath . '#' . $xmlFilePath);
155
156 75
        $xmlElement = new SimpleXMLElement($xmlContents);
157 75
        $xmlElement->registerXPathNamespace('ns', $mainNamespace);
158
159 75
        return $xmlElement;
160
    }
161
}
162