Test Failed
Push — master ( e6f4b5...c3cccf )
by
unknown
15:56
created

XmlDocument::C14NWithIsoEncodingFlattened()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 10
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
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
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Xml;
26
27
use DomDocument;
28
use DOMNode;
29
30
/**
31
 * Clase que representa un documento XML.
32
 */
33
class XmlDocument extends DomDocument
34
{
35
    /**
36
     * Constructor del documento XML.
37
     *
38
     * @param string $version Versión del documento XML.
39
     * @param string $encoding Codificación del documento XML.
40
     */
41 18
    public function __construct(
42
        string $version = '1.0',
43
        string $encoding = 'ISO-8859-1'
44
    ) {
45 18
        parent::__construct($version, $encoding);
46
47 18
        $this->formatOutput = true;
48 18
        $this->preserveWhiteSpace = true;
49
    }
50
51
    /**
52
     * Entrega el nombre del tag raíz del XML.
53
     *
54
     * @return string Nombre del tag raíz.
55
     */
56
    public function getName(): string
57
    {
58
        return $this->documentElement->tagName;
59
    }
60
61
    /**
62
     * Obtiene el espacio de nombres (namespace) del elemento raíz del
63
     * documento XML.
64
     *
65
     * @return string|null Espacio de nombres del documento XML o `null` si no
66
     * está presente.
67
     */
68
    public function getNamespace(): ?string
69
    {
70
        $namespace = $this->documentElement->getAttribute('xmlns');
71
72
        return $namespace !== '' ? $namespace : null;
73
    }
74
75
    /**
76
     * Entrega el nombre del archivo del schema del XML.
77
     *
78
     * @return string|null Nombre del schema o `null` si no se encontró.
79
     */
80 2
    public function getSchema(): ?string
81
    {
82 2
        $schemaLocation = $this->documentElement->getAttribute(
83 2
            'xsi:schemaLocation'
84 2
        );
85
86 2
        if (!$schemaLocation || !str_contains($schemaLocation, ' ')) {
87
            return null;
88
        }
89
90 2
        return explode(' ', $schemaLocation)[1];
91
    }
92
93
    /**
94
     * Carga un string XML en la instancia del documento XML.
95
     *
96
     * @param string $source String con el documento XML a cargar.
97
     * @param int $options Opciones para la carga del XML.
98
     * @return bool `true` si el XML se cargó correctamente.
99
     * @throws XmlException Si no es posible cargar el XML.
100
     */
101 18
    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 18
        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 18
        preg_match('/<\?xml\s+version="([^"]+)"\s+encoding="([^"]+)"\?>/', $source, $matches);
112
        //$version = $matches[1] ?? $this->xmlVersion;
113 18
        $encoding = strtoupper($matches[2] ?? $this->encoding);
114 18
        if ($encoding === 'UTF-8') {
115
            $source = XmlUtils::utf2iso($source);
116
            $source = str_replace(' encoding="UTF-8"?>', ' encoding="ISO-8859-1"?>', $source);
117
        }
118
119
        // Obtener estado actual de libxml y cambiarlo antes de cargar el XML
120
        // para obtener los errores en una variable si falla algo.
121 18
        $useInternalErrors = libxml_use_internal_errors(true);
122
123
        // Cargar el XML.
124 18
        $status = parent::loadXML($source, $options);
125
126
        // Obtener errores, limpiarlos y restaurar estado de errores de libxml.
127 18
        $errors = libxml_get_errors();
128 18
        libxml_clear_errors();
129 18
        libxml_use_internal_errors($useInternalErrors);
130
131 18
        if (!$status) {
132
            throw new XmlException('Error al cargar el XML.', $errors);
133
        }
134
135
        // Retornar estado de la carga del XML.
136
        // Sólo retornará `true`, pues si falla lanza excepción.
137 18
        return true;
138
    }
139
140
    /**
141
     * Genera el documento XML como string.
142
     *
143
     * Wrapper de parent::saveXML() para poder corregir XML entities.
144
     *
145
     * Incluye encabezado del XML con versión y codificación.
146
     *
147
     * @param DOMNode|null $node Nodo a serializar.
148
     * @param int $options Opciones de serialización.
149
     * @return string XML serializado y corregido.
150
     */
151 17
    public function saveXML(?DOMNode $node = null, int $options = 0): string
152
    {
153 17
        $xml = parent::saveXML($node, $options);
154
155 17
        return XmlUtils::fixEntities($xml);
156
    }
157
158
    /**
159
     * Genera el documento XML como string.
160
     *
161
     * Wrapper de saveXML() para generar un string sin el encabezado del XML y
162
     * sin salto de línea inicial o final.
163
     *
164
     * @return string XML serializado y corregido.
165
     */
166 8
    public function getXML(): string
167
    {
168 8
        $xml = $this->saveXML();
169 8
        $xml = preg_replace(
170 8
            '/<\?xml\s+version="1\.0"\s+encoding="[^"]+"\s*\?>/i',
171 8
            '',
172 8
            $xml
173 8
        );
174
175 8
        return trim($xml);
176
    }
177
178
    /**
179
     * Entrega el string XML canonicalizado y con la codificación que
180
     * corresponde (ISO-8859-1).
181
     *
182
     * Esto básicamente usa C14N(), sin embargo, C14N() siempre entrega el XML
183
     * en codificación UTF-8. Por lo que este método permite obtenerlo con C14N
184
     * pero con la codificación correcta de ISO-8859-1. Además se corrigen las
185
     * XML entities.
186
     *
187
     * @param string|null $xpath XPath para consulta al XML y extraer solo una
188
     * parte, desde un tag/nodo específico.
189
     * @return string String XML canonicalizado.
190
     * @throws XmlException En caso de ser pasado un XPath y no encontrarlo.
191
     */
192 11
    public function C14NWithIsoEncoding(?string $xpath = null): string
193
    {
194
        // Si se proporciona XPath, filtrar los nodos.
195 11
        if ($xpath) {
196 11
            $node = XmlUtils::xpath($this, $xpath)->item(0);
197 11
            if (!$node) {
198 1
                throw new XmlException(sprintf(
199 1
                    'No fue posible obtener el nodo con el XPath %s.',
200 1
                    $xpath
201 1
                ));
202
            }
203 10
            $xml = $node->C14N();
204
        }
205
        // Usar C14N() para todo el documento si no se especifica XPath.
206
        else {
207 2
            $xml = $this->C14N();
208
        }
209
210
        // Corregir XML entities.
211 10
        $xml = XmlUtils::fixEntities($xml);
212
213
        // Convertir el XML aplanado de UTF-8 a ISO-8859-1.
214
        // Requerido porque C14N() siempre entrega los datos en UTF-8.
215 10
        $xml = XmlUtils::utf2iso($xml);
216
217
        // Entregar el XML canonicalizado.
218 10
        return $xml;
219
    }
220
221
    /**
222
     * Entrega el string XML canonicalizado, con la codificación que
223
     * corresponde (ISO-8859-1) y aplanado.
224
     *
225
     * Es un wrapper de C14NWithIsoEncoding() que aplana el XML resultante.
226
     *
227
     * @param string|null $xpath XPath para consulta al XML y extraer solo una
228
     * parte, desde un tag/nodo específico.
229
     * @return string String XML canonicalizado y aplanado.
230
     * @throws XmlException En caso de ser pasado un XPath y no encontrarlo.
231
     */
232 5
    public function C14NWithIsoEncodingFlattened(?string $xpath = null): string
233
    {
234
        // Obtener XML canonicalizado y codificado en ISO8859-1.
235 5
        $xml = $this->C14NWithIsoEncoding($xpath);
236
237
        // Eliminar los espacios entre tags.
238 5
        $xml = preg_replace("/>\s+</", '><', $xml);
239
240
        // Entregar el XML canonicalizado y aplanado.
241 5
        return $xml;
242
    }
243
}
244