Completed
Push — master ( ee5dee...6d44cd )
by Adrien
04:31
created

XMLReader::getCurrentNodeName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 107
    public function openFileInZip($zipFilePath, $fileInsideZipPath)
28
    {
29 107
        $wasOpenSuccessful = false;
30 107
        $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 107
        if ($this->fileExistsWithinZip($realPathURI)) {
36 105
            $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
37
        }
38
39 107
        return $wasOpenSuccessful;
40
    }
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 109
    public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
51
    {
52 109
        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 112
    protected function fileExistsWithinZip($zipStreamURI)
62
    {
63 112
        $doesFileExists = false;
64
65 112
        $pattern = '/zip:\/\/([^#]+)#(.*)/';
66 112
        if (preg_match($pattern, $zipStreamURI, $matches)) {
67 112
            $zipFilePath = $matches[1];
68 112
            $innerFilePath = $matches[2];
69
70 112
            $zip = new \ZipArchive();
71 112
            if ($zip->open($zipFilePath) === true) {
72 112
                $doesFileExists = ($zip->locateName($innerFilePath) !== false);
73 112
                $zip->close();
74
            }
75
        }
76
77 112
        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 105
    public function read()
88
    {
89 105
        $this->useXMLInternalErrors();
90
91 105
        $wasReadSuccessful = parent::read();
92
93 105
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
94
95 105
        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 86
    public function readUntilNodeFound($nodeName)
106
    {
107
        do {
108 86
            $wasReadSuccessful = $this->read();
109 86
            $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
110 86
        } while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
111
112 85
        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 38
    public function next($localName = null)
124
    {
125 38
        $this->useXMLInternalErrors();
126
127 38
        $wasNextSuccessful = parent::next($localName);
128
129 38
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
130
131 36
        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 104
    public function isPositionedOnStartingNode($nodeName)
139
    {
140 104
        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 38
    public function isPositionedOnEndingNode($nodeName)
148
    {
149 38
        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 104
    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 104
        $hasPrefix = (strpos($nodeName, ':') !== false);
163 104
        $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
164
165 104
        return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
166
    }
167
168
    /**
169
     * @return string The name of the current node, un-prefixed
170
     */
171 36
    public function getCurrentNodeName()
172
    {
173 36
        return $this->localName;
174
    }
175
}
176