Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Reader\Wrapper;
6
 
1441 ariadna 7
use OpenSpout\Common\Exception\IOException;
1 efrain 8
use OpenSpout\Reader\Exception\XMLProcessingException;
9
use ZipArchive;
10
 
11
/**
12
 * @internal
13
 */
14
final class XMLReader extends \XMLReader
15
{
16
    use XMLInternalErrorsHelper;
17
 
18
    public 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
     *
26
     * @return bool TRUE on success or FALSE on failure
27
     */
28
    public function openFileInZip(string $zipFilePath, string $fileInsideZipPath): bool
29
    {
30
        $wasOpenSuccessful = false;
31
        $realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath);
32
 
33
        // We need to check first that the file we are trying to read really exist because:
34
        //  - PHP emits a warning when trying to open a file that does not exist.
35
        if ($this->fileExistsWithinZip($realPathURI)) {
36
            $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
37
        }
38
 
39
        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
     *
49
     * @return string The real path URI
50
     */
51
    public function getRealPathURIForFileInZip(string $zipFilePath, string $fileInsideZipPath): string
52
    {
53
        // The file path should not start with a '/', otherwise it won't be found
54
        $fileInsideZipPathWithoutLeadingSlash = ltrim($fileInsideZipPath, '/');
55
 
1441 ariadna 56
        $realpath = realpath($zipFilePath);
57
        if (false === $realpath) {
58
            throw new IOException("Could not open {$zipFilePath} for reading! File does not exist.");
59
        }
60
 
61
        return self::ZIP_WRAPPER.$realpath.'#'.$fileInsideZipPathWithoutLeadingSlash;
1 efrain 62
    }
63
 
64
    /**
65
     * Move to next node in document.
66
     *
67
     * @see \XMLReader::read
68
     *
69
     * @throws XMLProcessingException If an error/warning occurred
70
     */
71
    public function read(): bool
72
    {
73
        $this->useXMLInternalErrors();
74
 
75
        $wasReadSuccessful = parent::read();
76
 
77
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
78
 
79
        return $wasReadSuccessful;
80
    }
81
 
82
    /**
83
     * Read until the element with the given name is found, or the end of the file.
84
     *
85
     * @param string $nodeName Name of the node to find
86
     *
87
     * @return bool TRUE on success or FALSE on failure
88
     *
89
     * @throws XMLProcessingException If an error/warning occurred
90
     */
91
    public function readUntilNodeFound(string $nodeName): bool
92
    {
93
        do {
94
            $wasReadSuccessful = $this->read();
95
            $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
96
        } while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
97
 
98
        return $wasReadSuccessful;
99
    }
100
 
101
    /**
102
     * Move cursor to next node skipping all subtrees.
103
     *
104
     * @see \XMLReader::next
105
     *
106
     * @param null|string $localName The name of the next node to move to
107
     *
108
     * @throws XMLProcessingException If an error/warning occurred
109
     */
110
    public function next($localName = null): bool
111
    {
112
        $this->useXMLInternalErrors();
113
 
114
        $wasNextSuccessful = parent::next($localName);
115
 
116
        $this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
117
 
118
        return $wasNextSuccessful;
119
    }
120
 
121
    /**
122
     * @return bool Whether the XML Reader is currently positioned on the starting node with given name
123
     */
124
    public function isPositionedOnStartingNode(string $nodeName): bool
125
    {
126
        return $this->isPositionedOnNode($nodeName, self::ELEMENT);
127
    }
128
 
129
    /**
130
     * @return bool Whether the XML Reader is currently positioned on the ending node with given name
131
     */
132
    public function isPositionedOnEndingNode(string $nodeName): bool
133
    {
134
        return $this->isPositionedOnNode($nodeName, self::END_ELEMENT);
135
    }
136
 
137
    /**
138
     * @return string The name of the current node, un-prefixed
139
     */
140
    public function getCurrentNodeName(): string
141
    {
142
        return $this->localName;
143
    }
144
 
145
    /**
146
     * Returns whether the file at the given location exists.
147
     *
148
     * @param string $zipStreamURI URI of a zip stream, e.g. "zip://file.zip#path/inside.xml"
149
     *
150
     * @return bool TRUE if the file exists, FALSE otherwise
151
     */
152
    private function fileExistsWithinZip(string $zipStreamURI): bool
153
    {
154
        $doesFileExists = false;
155
 
156
        $pattern = '/zip:\/\/([^#]+)#(.*)/';
157
        if (1 === preg_match($pattern, $zipStreamURI, $matches)) {
158
            $zipFilePath = $matches[1];
159
            $innerFilePath = $matches[2];
160
 
161
            $zip = new ZipArchive();
162
            if (true === $zip->open($zipFilePath)) {
163
                $doesFileExists = (false !== $zip->locateName($innerFilePath));
164
                $zip->close();
165
            }
166
        }
167
 
168
        return $doesFileExists;
169
    }
170
 
171
    /**
172
     * @return bool Whether the XML Reader is currently positioned on the node with given name and type
173
     */
174
    private function isPositionedOnNode(string $nodeName, int $nodeType): bool
175
    {
176
        /**
177
         * In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
178
         * So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
179
         *
180
         * @see https://github.com/box/spout/issues/233
181
         */
182
        $hasPrefix = str_contains($nodeName, ':');
183
        $currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
184
 
185
        return $this->nodeType === $nodeType && $currentNodeName === $nodeName;
186
    }
187
}