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

Xml   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Test Coverage

Coverage 89.61%

Importance

Changes 0
Metric Value
eloc 62
dl 0
loc 192
ccs 69
cts 77
cp 0.8961
rs 10
c 0
b 0
f 0
wmc 19

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A C14NWithIsoEncoding() 0 27 3
A getXml() 0 10 1
A C14NWithIsoEncodingFlattened() 0 10 1
A getName() 0 3 1
A getSignatureNodeXml() 0 7 1
A getDocumentElement() 0 3 1
A saveXml() 0 5 1
A loadXml() 0 45 4
A getNamespace() 0 5 2
A getSchema() 0 11 3
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 DOMDocument;
32
use DOMElement;
33
use DOMNode;
34
35
/**
36
 * Clase que representa un documento XML.
37
 */
38
class Xml extends DOMDocument implements XmlInterface
39
{
40
    /**
41
     * Constructor del documento XML.
42
     *
43
     * @param string $version Versión del documento XML.
44
     * @param string $encoding Codificación del documento XML.
45
     */
46 78
    public function __construct(
47
        string $version = '1.0',
48
        string $encoding = 'ISO-8859-1'
49
    ) {
50 78
        parent::__construct($version, $encoding);
51
52 78
        $this->formatOutput = true;
53 78
        $this->preserveWhiteSpace = true;
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 2
    public function getDocumentElement(): DOMElement
60
    {
61 2
        return $this->documentElement;
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 1
    public function getName(): string
68
    {
69 1
        return $this->documentElement->tagName;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 3
    public function getNamespace(): ?string
76
    {
77 3
        $namespace = $this->documentElement->getAttribute('xmlns');
78
79 3
        return $namespace !== '' ? $namespace : null;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 2
    public function getSchema(): ?string
86
    {
87 2
        $schemaLocation = $this->documentElement->getAttribute(
88 2
            'xsi:schemaLocation'
89 2
        );
90
91 2
        if (!$schemaLocation || !str_contains($schemaLocation, ' ')) {
92 1
            return null;
93
        }
94
95 1
        return explode(' ', $schemaLocation)[1];
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101 46
    public function loadXml(string $source, int $options = 0): bool
102
    {
103
        // Si no hay un string XML en el origen entonces se lanza excepción.
104 46
        if (empty($source)) {
105
            throw new XmlException(
106
                'El contenido del XML que se desea cargar está vacío.'
107
            );
108
        }
109
110
        // Convertir el XML si es necesario.
111 46
        preg_match(
112 46
            '/<\?xml\s+version="([^"]+)"\s+encoding="([^"]+)"\?>/',
113 46
            $source,
114 46
            $matches
115 46
        );
116
        //$version = $matches[1] ?? $this->xmlVersion;
117 46
        $encoding = strtoupper($matches[2] ?? $this->encoding);
118 46
        if ($encoding === 'UTF-8') {
119 4
            $source = Str::utf8decode($source);
120 4
            $source = str_replace(
121 4
                ' encoding="UTF-8"?>',
122 4
                ' encoding="ISO-8859-1"?>',
123 4
                $source
124 4
            );
125
        }
126
127
        // Obtener estado actual de libxml y cambiarlo antes de cargar el XML
128
        // para obtener los errores en una variable si falla algo.
129 46
        $useInternalErrors = libxml_use_internal_errors(true);
130
131
        // Cargar el XML.
132 46
        $status = parent::loadXml($source, $options);
133
134
        // Obtener errores, limpiarlos y restaurar estado de errores de libxml.
135 46
        $errors = libxml_get_errors();
136 46
        libxml_clear_errors();
137 46
        libxml_use_internal_errors($useInternalErrors);
138
139 46
        if (!$status) {
140 4
            throw new XmlException('Error al cargar el XML.', $errors);
141
        }
142
143
        // Retornar estado de la carga del XML.
144
        // Sólo retornará `true`, pues si falla lanza excepción.
145 42
        return true;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 23
    public function saveXml(?DOMNode $node = null, int $options = 0): string
152
    {
153 23
        $xml = parent::saveXml($node, $options);
154
155 23
        return XmlUtil::fixEntities($xml);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 3
    public function getXml(): string
162
    {
163 3
        $xml = $this->saveXml();
164 3
        $xml = preg_replace(
165 3
            '/<\?xml\s+version="1\.0"\s+encoding="[^"]+"\s*\?>/i',
166 3
            '',
167 3
            $xml
168 3
        );
169
170 3
        return trim($xml);
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 19
    public function C14NWithIsoEncoding(?string $xpath = null): string
177
    {
178
        // Si se proporciona XPath, filtrar los nodos.
179 19
        if ($xpath) {
180 8
            $node = XmlUtil::xpath($this, $xpath)->item(0);
181 8
            if (!$node) {
182 2
                throw new XmlException(sprintf(
183 2
                    'No fue posible obtener el nodo con el XPath %s.',
184 2
                    $xpath
185 2
                ));
186
            }
187 6
            $xml = $node->C14N();
188
        }
189
        // Usar C14N() para todo el documento si no se especifica XPath.
190
        else {
191 13
            $xml = $this->C14N();
192
        }
193
194
        // Corregir XML entities.
195 17
        $xml = XmlUtil::fixEntities($xml);
196
197
        // Convertir el XML aplanado de UTF-8 a ISO-8859-1.
198
        // Requerido porque C14N() siempre entrega los datos en UTF-8.
199 17
        $xml = Str::utf8decode($xml);
200
201
        // Entregar el XML canonicalizado.
202 17
        return $xml;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208 1
    public function C14NWithIsoEncodingFlattened(?string $xpath = null): string
209
    {
210
        // Obtener XML canonicalizado y codificado en ISO8859-1.
211 1
        $xml = $this->C14NWithIsoEncoding($xpath);
212
213
        // Eliminar los espacios entre tags.
214 1
        $xml = preg_replace("/>\s+</", '><', $xml);
215
216
        // Entregar el XML canonicalizado y aplanado.
217 1
        return $xml;
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function getSignatureNodeXml(): ?string
224
    {
225
        $tag = $this->documentElement->tagName;
226
        $xpath = '/*[local-name()="' . $tag . '"]/*[local-name()="Signature"]';
227
        $signatureElement = XmlUtil::xpath($this, $xpath)->item(0);
228
229
        return $signatureElement?->C14N();
230
    }
231
}
232