Passed
Push — master ( 2f2a34...2f53bf )
by Esteban De La Fuente
08:36
created

XmlDocument::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
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 2
dl 0
loc 8
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 242
    public function __construct(
42
        string $version = '1.0',
43
        string $encoding = 'ISO-8859-1'
44
    ) {
45 242
        parent::__construct($version, $encoding);
46
47 242
        $this->formatOutput = true;
48 242
        $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 1
    public function getName(): string
57
    {
58 1
        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 3
    public function getNamespace(): ?string
69
    {
70 3
        $namespace = $this->documentElement->getAttribute('xmlns');
71
72 3
        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 5
    public function getSchema(): ?string
81
    {
82 5
        $schemaLocation = $this->documentElement->getAttribute(
83 5
            'xsi:schemaLocation'
84 5
        );
85
86 5
        if (!$schemaLocation || !str_contains($schemaLocation, ' ')) {
87 1
            return null;
88
        }
89
90 4
        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 162
    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 162
        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 162
        preg_match('/<\?xml\s+version="([^"]+)"\s+encoding="([^"]+)"\?>/', $source, $matches);
112
        //$version = $matches[1] ?? $this->xmlVersion;
113 162
        $encoding = strtoupper($matches[2] ?? $this->encoding);
114 162
        if ($encoding === 'UTF-8') {
115 6
            $source = XmlUtils::utf2iso($source);
116 6
            $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 162
        $useInternalErrors = libxml_use_internal_errors(true);
122
123
        // Cargar el XML.
124 162
        $status = parent::loadXML($source, $options);
125
126
        // Obtener errores, limpiarlos y restaurar estado de errores de libxml.
127 162
        $errors = libxml_get_errors();
128 162
        libxml_clear_errors();
129 162
        libxml_use_internal_errors($useInternalErrors);
130
131 162
        if (!$status) {
132 4
            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 158
        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 182
    public function saveXML(?DOMNode $node = null, int $options = 0): string
152
    {
153 182
        $xml = parent::saveXML($node, $options);
154
155 182
        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 58
    public function getXML(): string
167
    {
168 58
        $xml = $this->saveXML();
169 58
        $xml = preg_replace(
170 58
            '/<\?xml\s+version="1\.0"\s+encoding="[^"]+"\s*\?>/i',
171 58
            '',
172 58
            $xml
173 58
        );
174
175 58
        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 124
    public function C14NWithIsoEncoding(?string $xpath = null): string
193
    {
194
        // Si se proporciona XPath, filtrar los nodos.
195 124
        if ($xpath) {
196 113
            $node = XmlUtils::xpath($this, $xpath)->item(0);
197 113
            if (!$node) {
198 2
                throw new XmlException(sprintf(
199 2
                    'No fue posible obtener el nodo con el XPath %s.',
200 2
                    $xpath
201 2
                ));
202
            }
203 111
            $xml = $node->C14N();
204
        }
205
        // Usar C14N() para todo el documento si no se especifica XPath.
206
        else {
207 14
            $xml = $this->C14N();
208
        }
209
210
        // Corregir XML entities.
211 122
        $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 122
        $xml = XmlUtils::utf2iso($xml);
216
217
        // Entregar el XML canonicalizado.
218 122
        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 104
    public function C14NWithIsoEncodingFlattened(?string $xpath = null): string
233
    {
234
        // Obtener XML canonicalizado y codificado en ISO8859-1.
235 104
        $xml = $this->C14NWithIsoEncoding($xpath);
236
237
        // Eliminar los espacios entre tags.
238 104
        $xml = preg_replace("/>\s+</", '><', $xml);
239
240
        // Entregar el XML canonicalizado y aplanado.
241 104
        return $xml;
242
    }
243
}
244