Completed
Pull Request — master (#450)
by Adrien
02:31
created

XMLReader::isPositionedOnNode()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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