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

XMLReader::open()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.024

Importance

Changes 3
Bugs 2 Features 1
Metric Value
c 3
b 2
f 1
dl 0
loc 17
rs 9.2
ccs 6
cts 10
cp 0.6
cc 4
eloc 9
nc 3
nop 3
crap 5.024
1
<?php
2
3
namespace Box\Spout\Reader\Wrapper;
4
5
6
/**
7
 * Class XMLReader
8
 * Wrapper around the built-in XMLReader
9
 * @see \XMLReader
10
 *
11
 * @package Box\Spout\Reader\Wrapper
12
 */
13
class XMLReader extends \XMLReader
14
{
15
    use XMLInternalErrorsHelper;
16
17
    /**
18
     * Set the URI containing the XML to parse
19
     * @see \XMLReader::open
20
     *
21
     * @param string $URI URI pointing to the document
22
   	 * @param string|null|void $encoding The document encoding
23
   	 * @param int $options A bitmask of the LIBXML_* constants
24
     * @return bool TRUE on success or FALSE on failure
25
     */
26 231
    public function open($URI, $encoding = null, $options = 0)
27
    {
28 231
        $wasOpenSuccessful = false;
29 231
        $realPathURI = $this->convertURIToUseRealPath($URI);
30
31
        // HHVM does not check if file exists within zip file
32
        // @link https://github.com/facebook/hhvm/issues/5779
33 231
        if ($this->isRunningHHVM() && $this->isZipStream($realPathURI)) {
34
            if ($this->fileExistsWithinZip($realPathURI)) {
35
                $wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET);
36
            }
37
        } else {
38 231
            $wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET);
39
        }
40
41 231
        return $wasOpenSuccessful;
42
    }
43
44
    /**
45
     * Updates the given URI to use a real path.
46
     * This is to avoid issues on some Windows setup.
47
     *
48
     * @param string $URI URI
49
     * @return string The URI using a real path
50
     */
51 243
    protected function convertURIToUseRealPath($URI)
52
    {
53 243
        $realPathURI = $URI;
54
55 243
        if ($this->isZipStream($URI)) {
56 237
            if (preg_match('/zip:\/\/(.*)#(.*)/', $URI, $matches)) {
57 237
                $documentPath = $matches[1];
58 237
                $documentInsideZipPath = $matches[2];
59 237
                $realPathURI = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
60 237
            }
61 237
        } else {
62 6
            $realPathURI = realpath($URI);
63
        }
64
65 243
        return $realPathURI;
66
    }
67
68
    /**
69
     * Returns whether the given URI is a zip stream.
70
     *
71
     * @param string $URI URI pointing to a document
72
     * @return bool TRUE if URI is a zip stream, FALSE otherwise
73
     */
74 258
    protected function isZipStream($URI)
75
    {
76 258
        return (strpos($URI, 'zip://') === 0);
77
    }
78
79
    /**
80
     * Returns whether the current environment is HHVM
81
     *
82
     * @return bool TRUE if running on HHVM, FALSE otherwise
83
     */
84 231
    protected function isRunningHHVM()
85
    {
86 231
        return defined('HHVM_VERSION');
87
    }
88
89
    /**
90
     * Returns whether the file at the given location exists
91
     *
92
     * @param string $zipStreamURI URI of a zip stream, e.g. "zip://file.zip#path/inside.xml"
93
     * @return bool TRUE if the file exists, FALSE otherwise
94
     */
95 15
    protected function fileExistsWithinZip($zipStreamURI)
96
    {
97 15
        $doesFileExists = false;
98
99 15
        $pattern = '/zip:\/\/([^#]+)#(.*)/';
100 15
        if (preg_match($pattern, $zipStreamURI, $matches)) {
101 15
            $zipFilePath = $matches[1];
102 15
            $innerFilePath = $matches[2];
103
104 15
            $zip = new \ZipArchive();
105 15
            if ($zip->open($zipFilePath) === true) {
106 15
                $doesFileExists = ($zip->locateName($innerFilePath) !== false);
107 15
                $zip->close();
108 15
            }
109 15
        }
110
111 15
        return $doesFileExists;
112
    }
113
114
    /**
115
     * Move to next node in document
116
     * @see \XMLReader::read
117
     *
118
     * @return bool TRUE on success or FALSE on failure
119
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
120
     */
121 225
    public function read()
122
    {
123 225
        $this->useXMLInternalErrors();
124
125 225
        $wasReadSuccessful = parent::read();
126
127 225
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
128
129 225
        return $wasReadSuccessful;
130
    }
131
132
    /**
133
     * Read until the element with the given name is found, or the end of the file.
134
     *
135
     * @param string $nodeName Name of the node to find
136
     * @return bool TRUE on success or FALSE on failure
137
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
138
     */
139 201
    public function readUntilNodeFound($nodeName)
140
    {
141
        do {
142 201
            $wasReadSuccessful = $this->read();
143 201
            $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
144 201
        } while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
145
146 198
        return $wasReadSuccessful;
147
    }
148
149
    /**
150
     * Move cursor to next node skipping all subtrees
151
     * @see \XMLReader::next
152
     *
153
     * @param string|void $localName The name of the next node to move to
154
     * @return bool TRUE on success or FALSE on failure
155
     * @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
156
     */
157 93
    public function next($localName = null)
158
    {
159 93
        $this->useXMLInternalErrors();
160
161 93
        $wasNextSuccessful = parent::next($localName);
162
163 93
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
164
165 87
        return $wasNextSuccessful;
166
    }
167
168
    /**
169
     * @param string $nodeName
170
     * @return bool Whether the XML Reader is currently positioned on the starting node with given name
171
     */
172 222
    public function isPositionedOnStartingNode($nodeName)
173
    {
174 222
        return $this->isPositionedOnNode($nodeName, XMLReader::ELEMENT);
175
    }
176
177
    /**
178
     * @param string $nodeName
179
     * @return bool Whether the XML Reader is currently positioned on the ending node with given name
180
     */
181 159
    public function isPositionedOnEndingNode($nodeName)
182
    {
183 159
        return $this->isPositionedOnNode($nodeName, XMLReader::END_ELEMENT);
184
    }
185
186
    /**
187
     * @param string $nodeName
188
     * @param int $nodeType
189
     * @return bool Whether the XML Reader is currently positioned on the node with given name and type
190
     */
191 222
    private function isPositionedOnNode($nodeName, $nodeType)
192
    {
193
        // In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
194
        // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
195
        // @see https://github.com/box/spout/issues/233
196 222
        $hasPrefix = (strpos($nodeName, ':') !== false);
197 222
        $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
198
199 222
        return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
200
    }
201
}
202