Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.92% covered (success)
95.92%
47 / 49
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
XMLReader
95.92% covered (success)
95.92%
47 / 49
88.89% covered (warning)
88.89%
8 / 9
25
0.00% covered (danger)
0.00%
0 / 1
 getDomFromZip
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 getDomFromString
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 getElements
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 registerNamespace
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getElement
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getAttribute
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 countElements
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 elementExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * This file is part of PHPWord - A pure PHP library for reading and writing
4 * word processing documents.
5 *
6 * PHPWord is free software distributed under the terms of the GNU Lesser
7 * General Public License version 3 as published by the Free Software Foundation.
8 *
9 * For the full copyright and license information, please read the LICENSE
10 * file that was distributed with this source code. For the full list of
11 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
12 *
13 * @see         https://github.com/PHPOffice/PHPWord
14 *
15 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
16 */
17
18namespace PhpOffice\PhpWord\Shared;
19
20use DOMDocument;
21use DOMElement;
22use DOMNodeList;
23use DOMXpath;
24use Exception;
25use InvalidArgumentException;
26use ZipArchive;
27
28/**
29 * XML Reader wrapper.
30 *
31 * @since   0.2.1
32 */
33class XMLReader
34{
35    /**
36     * DOMDocument object.
37     *
38     * @var DOMDocument
39     */
40    private $dom;
41
42    /**
43     * DOMXpath object.
44     *
45     * @var DOMXpath
46     */
47    private $xpath;
48
49    /**
50     * Get DOMDocument from ZipArchive.
51     *
52     * @param string $zipFile
53     * @param string $xmlFile
54     *
55     * @return DOMDocument|false
56     */
57    public function getDomFromZip($zipFile, $xmlFile)
58    {
59        if (file_exists($zipFile) === false) {
60            throw new Exception('Cannot find archive file.');
61        }
62
63        $zip = new ZipArchive();
64        $openStatus = $zip->open($zipFile);
65        if ($openStatus !== true) {
66            /**
67             * Throw an exception since making further calls on the ZipArchive would cause a fatal error.
68             * This prevents fatal errors on corrupt archives and attempts to open old "doc" files.
69             */
70            throw new Exception("The archive failed to load with the following error code: $openStatus");
71        }
72
73        $content = $zip->getFromName(ltrim($xmlFile, '/'));
74        $zip->close();
75
76        if ($content === false) {
77            return false;
78        }
79
80        return $this->getDomFromString($content);
81    }
82
83    /**
84     * Get DOMDocument from content string.
85     *
86     * @param string $content
87     *
88     * @return DOMDocument
89     */
90    public function getDomFromString($content)
91    {
92        if (\PHP_VERSION_ID < 80000) {
93            $originalLibXMLEntityValue = libxml_disable_entity_loader(true);
94        }
95        $this->dom = new DOMDocument();
96        $this->dom->loadXML($content);
97        if (\PHP_VERSION_ID < 80000) {
98            libxml_disable_entity_loader($originalLibXMLEntityValue);
99        }
100
101        return $this->dom;
102    }
103
104    /**
105     * Get elements.
106     *
107     * @param string $path
108     *
109     * @return DOMNodeList<DOMElement>
110     */
111    public function getElements($path, ?DOMElement $contextNode = null)
112    {
113        if ($this->dom === null) {
114            return new DOMNodeList(); // @phpstan-ignore-line
115        }
116        if ($this->xpath === null) {
117            $this->xpath = new DOMXpath($this->dom);
118        }
119
120        $result = @$this->xpath->query($path, $contextNode);
121
122        return empty($result) ? new DOMNodeList() : $result; // @phpstan-ignore-line
123    }
124
125    /**
126     * Registers the namespace with the DOMXPath object.
127     *
128     * @param string $prefix The prefix
129     * @param string $namespaceURI The URI of the namespace
130     *
131     * @return bool true on success or false on failure
132     */
133    public function registerNamespace($prefix, $namespaceURI)
134    {
135        if ($this->dom === null) {
136            throw new InvalidArgumentException('Dom needs to be loaded before registering a namespace');
137        }
138        if ($this->xpath === null) {
139            $this->xpath = new DOMXpath($this->dom);
140        }
141
142        return $this->xpath->registerNamespace($prefix, $namespaceURI);
143    }
144
145    /**
146     * Get element.
147     *
148     * @param string $path
149     *
150     * @return null|DOMElement
151     */
152    public function getElement($path, ?DOMElement $contextNode = null)
153    {
154        $elements = $this->getElements($path, $contextNode);
155        if ($elements->length > 0) {
156            return $elements->item(0);
157        }
158
159        return null;
160    }
161
162    /**
163     * Get element attribute.
164     *
165     * @param string $attribute
166     * @param string $path
167     *
168     * @return null|string
169     */
170    public function getAttribute($attribute, ?DOMElement $contextNode = null, $path = null)
171    {
172        $return = null;
173        if ($path !== null) {
174            $elements = $this->getElements($path, $contextNode);
175            if ($elements->length > 0) {
176                /** @var DOMElement $node Type hint */
177                $node = $elements->item(0);
178                $return = $node->getAttribute($attribute);
179            }
180        } else {
181            if ($contextNode !== null) {
182                $return = $contextNode->getAttribute($attribute);
183            }
184        }
185
186        return ($return == '') ? null : $return;
187    }
188
189    /**
190     * Get element value.
191     *
192     * @param string $path
193     *
194     * @return null|string
195     */
196    public function getValue($path, ?DOMElement $contextNode = null)
197    {
198        $elements = $this->getElements($path, $contextNode);
199        if ($elements->length > 0) {
200            return $elements->item(0)->nodeValue;
201        }
202
203        return null;
204    }
205
206    /**
207     * Count elements.
208     *
209     * @param string $path
210     *
211     * @return int
212     */
213    public function countElements($path, ?DOMElement $contextNode = null)
214    {
215        $elements = $this->getElements($path, $contextNode);
216
217        return $elements->length;
218    }
219
220    /**
221     * Element exists.
222     *
223     * @param string $path
224     *
225     * @return bool
226     */
227    public function elementExists($path, ?DOMElement $contextNode = null)
228    {
229        return $this->getElements($path, $contextNode)->length > 0;
230    }
231}