Passed
Push — master ( 3039c3...ae22ab )
by Esteban De La Fuente
03:20
created

Xml::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Derafu: Biblioteca PHP (Núcleo).
7
 * Copyright (C) Derafu <https://www.derafu.org>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace Derafu\Lib\Core\Package\Prime\Component\Xml\Entity;
26
27
use Derafu\Lib\Core\Helper\Str;
28
use Derafu\Lib\Core\Helper\Xml as XmlUtil;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Exception\XmlException;
31
use Derafu\Lib\Core\Support\Xml\XPathQuery;
32
use DOMDocument;
33
use DOMElement;
34
use DOMNode;
35
36
/**
37
 * Clase que representa un documento XML.
38
 */
39
class Xml extends DOMDocument implements XmlInterface
40
{
41
    /**
42
     * Instancia para facilitar el manejo de XML usando XPath.
43
     *
44
     * @var XPathQuery
45
     */
46
    private XPathQuery $xPathQuery;
47
48
    /**
49
     * Constructor del documento XML.
50
     *
51
     * @param string $version Versión del documento XML.
52
     * @param string $encoding Codificación del documento XML.
53
     */
54 78
    public function __construct(
55
        string $version = '1.0',
56
        string $encoding = 'ISO-8859-1'
57
    ) {
58 78
        parent::__construct($version, $encoding);
59
60 78
        $this->formatOutput = true;
61 78
        $this->preserveWhiteSpace = true;
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 2
    public function getDocumentElement(): DOMElement
68
    {
69 2
        return $this->documentElement;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 1
    public function getName(): string
76
    {
77 1
        return $this->documentElement->tagName;
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 3
    public function getNamespace(): ?string
84
    {
85 3
        $namespace = $this->documentElement->getAttribute('xmlns');
86
87 3
        return $namespace !== '' ? $namespace : null;
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93 2
    public function getSchema(): ?string
94
    {
95 2
        $schemaLocation = $this->documentElement->getAttribute(
96 2
            'xsi:schemaLocation'
97 2
        );
98
99 2
        if (!$schemaLocation || !str_contains($schemaLocation, ' ')) {
100 1
            return null;
101
        }
102
103 1
        return explode(' ', $schemaLocation)[1];
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 46
    public function loadXml(string $source, int $options = 0): bool
110
    {
111
        // Si no hay un string XML en el origen entonces se lanza excepción.
112 46
        if (empty($source)) {
113
            throw new XmlException(
114
                'El contenido del XML que se desea cargar está vacío.'
115
            );
116
        }
117
118
        // Convertir el XML si es necesario.
119 46
        preg_match(
120 46
            '/<\?xml\s+version="([^"]+)"\s+encoding="([^"]+)"\?>/',
121 46
            $source,
122 46
            $matches
123 46
        );
124
        //$version = $matches[1] ?? $this->xmlVersion;
125 46
        $encoding = strtoupper($matches[2] ?? $this->encoding);
126 46
        if ($encoding === 'UTF-8') {
127 4
            $source = Str::utf8decode($source);
128 4
            $source = str_replace(
129 4
                ' encoding="UTF-8"?>',
130 4
                ' encoding="ISO-8859-1"?>',
131 4
                $source
132 4
            );
133
        }
134
135
        // Obtener estado actual de libxml y cambiarlo antes de cargar el XML
136
        // para obtener los errores en una variable si falla algo.
137 46
        $useInternalErrors = libxml_use_internal_errors(true);
138
139
        // Cargar el XML.
140 46
        $status = parent::loadXml($source, $options);
141
142
        // Obtener errores, limpiarlos y restaurar estado de errores de libxml.
143 46
        $errors = libxml_get_errors();
144 46
        libxml_clear_errors();
145 46
        libxml_use_internal_errors($useInternalErrors);
146
147 46
        if (!$status) {
148 4
            throw new XmlException('Error al cargar el XML.', $errors);
149
        }
150
151
        // Retornar estado de la carga del XML.
152
        // Sólo retornará `true`, pues si falla lanza excepción.
153 42
        return true;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 23
    public function saveXml(?DOMNode $node = null, int $options = 0): string
160
    {
161 23
        $xml = parent::saveXml($node, $options);
162
163 23
        return XmlUtil::fixEntities($xml);
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 3
    public function getXml(): string
170
    {
171 3
        $xml = $this->saveXml();
172 3
        $xml = preg_replace(
173 3
            '/<\?xml\s+version="1\.0"\s+encoding="[^"]+"\s*\?>/i',
174 3
            '',
175 3
            $xml
176 3
        );
177
178 3
        return trim($xml);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 19
    public function C14NWithIsoEncoding(?string $xpath = null): string
185
    {
186
        // Si se proporciona XPath, filtrar los nodos.
187 19
        if ($xpath) {
188 8
            $node = XmlUtil::xpath($this, $xpath)->item(0);
189 8
            if (!$node) {
190 2
                throw new XmlException(sprintf(
191 2
                    'No fue posible obtener el nodo con el XPath %s.',
192 2
                    $xpath
193 2
                ));
194
            }
195 6
            $xml = $node->C14N();
196
        }
197
        // Usar C14N() para todo el documento si no se especifica XPath.
198
        else {
199 13
            $xml = $this->C14N();
200
        }
201
202
        // Corregir XML entities.
203 17
        $xml = XmlUtil::fixEntities($xml);
204
205
        // Convertir el XML aplanado de UTF-8 a ISO-8859-1.
206
        // Requerido porque C14N() siempre entrega los datos en UTF-8.
207 17
        $xml = Str::utf8decode($xml);
208
209
        // Entregar el XML canonicalizado.
210 17
        return $xml;
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216 1
    public function C14NWithIsoEncodingFlattened(?string $xpath = null): string
217
    {
218
        // Obtener XML canonicalizado y codificado en ISO8859-1.
219 1
        $xml = $this->C14NWithIsoEncoding($xpath);
220
221
        // Eliminar los espacios entre tags.
222 1
        $xml = preg_replace("/>\s+</", '><', $xml);
223
224
        // Entregar el XML canonicalizado y aplanado.
225 1
        return $xml;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231
    public function getSignatureNodeXml(): ?string
232
    {
233
        $tag = $this->documentElement->tagName;
234
        $xpath = '/*[local-name()="' . $tag . '"]/*[local-name()="Signature"]';
235
        $signatureElement = XmlUtil::xpath($this, $xpath)->item(0);
236
237
        return $signatureElement?->C14N();
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243
    public function query(string $query): string|array|null
244
    {
245
        if (!isset($this->xPathQuery)) {
246
            $this->xPathQuery = new XPathQuery($this);
247
        }
248
249
        return $this->xPathQuery->get($query);
250
    }
251
}
252