Passed
Pull Request — develop_3.0 (#499)
by Adrien
02:46
created

getWorkbookRelationships()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

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