Completed
Pull Request — master (#810)
by Adrien
15:46 queued 14:19
created

hasSharedStringsXMLFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Box\Spout\Reader\XLSX\Manager;
4
5
use Box\Spout\Common\Exception\IOException;
6
use Box\Spout\Reader\Wrapper\XMLReader;
7
use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
8
9
/**
10
 * Class WorkbookRelationshipsManager
11
 * This class manages the workbook relationships defined in the associated XML file
12
 */
13
class WorkbookRelationshipsManager
14
{
15
    const BASE_PATH = 'xl/';
16
17
    /** Path of workbook relationships XML file inside the XLSX file */
18
    const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
19
20
    /** Relationships types - For Transitional and Strict OOXML */
21
    const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
22
    const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
23
    const RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings';
24
    const RELATIONSHIP_TYPE_STYLES_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/styles';
25
26
    /** Nodes and attributes used to find relevant information in the workbook relationships XML file */
27
    const XML_NODE_RELATIONSHIP = 'Relationship';
28
    const XML_ATTRIBUTE_TYPE = 'Type';
29
    const XML_ATTRIBUTE_TARGET = 'Target';
30
31
    /** @var string Path of the XLSX file being read */
32
    private $filePath;
33
34
    /** @var InternalEntityFactory Factory to create entities */
35
    private $entityFactory;
36
37
    /** @var array Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
38
    private $cachedWorkbookRelationships;
39
40
    /**
41
     * @param string $filePath Path of the XLSX file being read
42
     * @param InternalEntityFactory $entityFactory Factory to create entities
43
     */
44 47
    public function __construct($filePath, $entityFactory)
45
    {
46 47
        $this->filePath = $filePath;
47 47
        $this->entityFactory = $entityFactory;
48 47
    }
49
50
    /**
51
     * @return string The path of the shared string XML file
52
     */
53 40
    public function getSharedStringsXMLFilePath()
54
    {
55 40
        $workbookRelationships = $this->getWorkbookRelationships();
56 40
        $sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]
57 40
            ?? $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT];
58
59
        // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
60 40
        $doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
61 40
        if (!$doesContainBasePath) {
62
            // make sure we return an absolute file path
63 40
            $sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
64
        }
65
66 40
        return $sharedStringsXMLFilePath;
67
    }
68
69
    /**
70
     * @return bool Whether the XLSX file contains a shared string XML file
71
     */
72 41
    public function hasSharedStringsXMLFile()
73
    {
74 41
        $workbookRelationships = $this->getWorkbookRelationships();
75
76 41
        return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS])
77 41
            || isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT]);
78
    }
79
80
    /**
81
     * @return bool Whether the XLSX file contains a styles XML file
82
     */
83 39
    public function hasStylesXMLFile()
84
    {
85 39
        $workbookRelationships = $this->getWorkbookRelationships();
86
87 39
        return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES])
88 39
            || isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT]);
89
    }
90
91
    /**
92
     * @return string The path of the styles XML file
93
     */
94 38
    public function getStylesXMLFilePath()
95
    {
96 38
        $workbookRelationships = $this->getWorkbookRelationships();
97 38
        $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]
98 38
            ?? $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT];
99
100
        // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
101 38
        $doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
102 38
        if (!$doesContainBasePath) {
103
            // make sure we return a full path
104 37
            $stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;
105
        }
106
107 38
        return $stylesXMLFilePath;
108
    }
109
110
    /**
111
     * Reads the workbook.xml.rels and extracts the filename associated to the different types.
112
     * It caches the result so that the file is read only once.
113
     *
114
     * @throws \Box\Spout\Common\Exception\IOException If workbook.xml.rels can't be read
115
     * @return array
116
     */
117 47
    private function getWorkbookRelationships()
118
    {
119 47
        if (!isset($this->cachedWorkbookRelationships)) {
120 47
            $xmlReader = $this->entityFactory->createXMLReader();
121
122 47
            if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_RELS_XML_FILE_PATH) === false) {
123
                throw new IOException('Could not open "' . self::WORKBOOK_RELS_XML_FILE_PATH . '".');
124
            }
125
126 47
            $this->cachedWorkbookRelationships = [];
127
128 47
            while ($xmlReader->readUntilNodeFound(self::XML_NODE_RELATIONSHIP)) {
129 47
                $this->processWorkbookRelationship($xmlReader);
130
            }
131
        }
132
133 47
        return $this->cachedWorkbookRelationships;
134
    }
135
136
    /**
137
     * Extracts and store the data of the current workbook relationship.
138
     *
139
     * @param XMLReader $xmlReader
140
     * @return void
141
     */
142 47
    private function processWorkbookRelationship($xmlReader)
143
    {
144 47
        $type = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TYPE);
145 47
        $target = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
146
147
        // @NOTE: if a type is defined more than once, we overwrite the previous value
148
        // To be changed if we want to get the file paths of sheet XML files for instance.
149 47
        $this->cachedWorkbookRelationships[$type] = $target;
150 47
    }
151
}
152