Passed
Pull Request — develop_3.0 (#497)
by Adrien
02:50
created

XMLReader   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 17
lcom 2
cbo 1
dl 0
loc 162
ccs 44
cts 44
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A openFileInZip() 0 14 2
A getRealPathURIForFileInZip() 0 4 1
A fileExistsWithinZip() 0 18 3
A read() 0 10 1
A readUntilNodeFound() 0 9 3
A next() 0 10 1
A isPositionedOnStartingNode() 0 4 1
A isPositionedOnEndingNode() 0 4 1
A isPositionedOnNode() 0 10 3
A getCurrentNodeName() 0 4 1
1
<?php
2
3
namespace Box\Spout\Reader\Wrapper;
4
5
/**
6
 * Class XMLReader
7
 * Wrapper around the built-in XMLReader
8
 * @see \XMLReader
9
 */
10
class XMLReader extends \XMLReader
11
{
12
    use XMLInternalErrorsHelper;
13
14
    const ZIP_WRAPPER = 'zip://';
15
16
    /**
17
     * Opens the XML Reader to read a file located inside a ZIP file.
18
     *
19
     * @param string $zipFilePath Path to the ZIP file
20
     * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
21
     * @return bool TRUE on success or FALSE on failure
22
     */
23 111
    public function openFileInZip($zipFilePath, $fileInsideZipPath)
24
    {
25 111
        $wasOpenSuccessful = false;
26 111
        $realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath);
27
28
        // We need to check first that the file we are trying to read really exist because:
29
        //  - PHP emits a warning when trying to open a file that does not exist.
30
        //  - HHVM does not check if file exists within zip file (@link https://github.com/facebook/hhvm/issues/5779)
31 111
        if ($this->fileExistsWithinZip($realPathURI)) {
32 109
            $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
33
        }
34
35 111
        return $wasOpenSuccessful;
36
    }
37
38
    /**
39
     * Returns the real path for the given path components.
40
     * This is useful to avoid issues on some Windows setup.
41
     *
42
     * @param string $zipFilePath Path to the ZIP file
43
     * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
44
     * @return string The real path URI
45
     */
46 113
    public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
47
    {
48 113
        return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPath);
49
    }
50
51
    /**
52
     * Returns whether the file at the given location exists
53
     *
54
     * @param string $zipStreamURI URI of a zip stream, e.g. "zip://file.zip#path/inside.xml"
55
     * @return bool TRUE if the file exists, FALSE otherwise
56
     */
57 116
    protected function fileExistsWithinZip($zipStreamURI)
58
    {
59 116
        $doesFileExists = false;
60
61 116
        $pattern = '/zip:\/\/([^#]+)#(.*)/';
62 116
        if (preg_match($pattern, $zipStreamURI, $matches)) {
63 116
            $zipFilePath = $matches[1];
64 116
            $innerFilePath = $matches[2];
65
66 116
            $zip = new \ZipArchive();
67 116
            if ($zip->open($zipFilePath) === true) {
68 116
                $doesFileExists = ($zip->locateName($innerFilePath) !== false);
69 116
                $zip->close();
70
            }
71
        }
72
73 116
        return $doesFileExists;
74
    }
75
76
    /**
77
     * Move to next node in document
78
     * @see \XMLReader::read
79
     *
80
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
81
     * @return bool TRUE on success or FALSE on failure
82
     */
83 109
    public function read()
84
    {
85 109
        $this->useXMLInternalErrors();
86
87 109
        $wasReadSuccessful = parent::read();
88
89 109
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
90
91 109
        return $wasReadSuccessful;
92
    }
93
94
    /**
95
     * Read until the element with the given name is found, or the end of the file.
96
     *
97
     * @param string $nodeName Name of the node to find
98
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
99
     * @return bool TRUE on success or FALSE on failure
100
     */
101 87
    public function readUntilNodeFound($nodeName)
102
    {
103
        do {
104 87
            $wasReadSuccessful = $this->read();
105 87
            $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
106 87
        } while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
107
108 87
        return $wasReadSuccessful;
109
    }
110
111
    /**
112
     * Move cursor to next node skipping all subtrees
113
     * @see \XMLReader::next
114
     *
115
     * @param string|null $localName The name of the next node to move to
116
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
117
     * @return bool TRUE on success or FALSE on failure
118
     */
119 39
    public function next($localName = null)
120
    {
121 39
        $this->useXMLInternalErrors();
122
123 39
        $wasNextSuccessful = parent::next($localName);
124
125 39
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
126
127 37
        return $wasNextSuccessful;
128
    }
129
130
    /**
131
     * @param string $nodeName
132
     * @return bool Whether the XML Reader is currently positioned on the starting node with given name
133
     */
134 108
    public function isPositionedOnStartingNode($nodeName)
135
    {
136 108
        return $this->isPositionedOnNode($nodeName, self::ELEMENT);
137
    }
138
139
    /**
140
     * @param string $nodeName
141
     * @return bool Whether the XML Reader is currently positioned on the ending node with given name
142
     */
143 13
    public function isPositionedOnEndingNode($nodeName)
144
    {
145 13
        return $this->isPositionedOnNode($nodeName, self::END_ELEMENT);
146
    }
147
148
    /**
149
     * @param string $nodeName
150
     * @param int $nodeType
151
     * @return bool Whether the XML Reader is currently positioned on the node with given name and type
152
     */
153 108
    private function isPositionedOnNode($nodeName, $nodeType)
154
    {
155
        // In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
156
        // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
157
        // @see https://github.com/box/spout/issues/233
158 108
        $hasPrefix = (strpos($nodeName, ':') !== false);
159 108
        $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
160
161 108
        return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
162
    }
163
164
    /**
165
     * @return string The name of the current node, un-prefixed
166
     */
167 37
    public function getCurrentNodeName()
168
    {
169 37
        return $this->localName;
170
    }
171
}
172