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