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

XmlDecoder   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 76.67%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 58
c 1
b 0
f 0
dl 0
loc 150
ccs 46
cts 60
cp 0.7667
rs 10
wmc 23

3 Methods

Rating   Name   Duplication   Size   Complexity  
A nodeCountTwins() 0 9 4
B decode() 0 47 7
C arrayAddChilds() 0 54 12
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 DOMElement;
28
use DOMNodeList;
29
use DOMText;
30
31
/**
32
 * Clase `XmlDecoder` crea un arreglo PHP a partir de un documento XML.
33
 */
34
class XmlDecoder
35
{
36
    /**
37
     * Convierte un documento XML a un arreglo PHP.
38
     *
39
     * @param XmlDocument|DOMElement $documentElement Documento XML que se
40
     * desea convertir a un arreglo de PHP o el elemento donde vamos a hacer la
41
     * conversión si no es el documento XML completo.
42
     * @param array|null $data Arreglo donde se almacenarán los resultados.
43
     * @param bool $twinsAsArray Indica si se deben tratar los nodos gemelos
44
     * como un arreglo.
45
     * @return array Arreglo con la representación del XML.
46
     */
47 17
    public static function decode(
48
        XmlDocument|DOMElement $documentElement,
49
        ?array &$data = null,
50
        bool $twinsAsArray = false
51
    ): array {
52
        // Si no viene un tagElement se busca uno, si no se obtiene se termina
53
        // la generación.
54 17
        $tagElement = $documentElement instanceof DOMElement
55 17
            ? $documentElement
56 17
            : $documentElement->documentElement
57 17
        ;
58 17
        if ($tagElement === null) {
59
            return [];
60
        }
61
62
        // Índice en el arreglo que representa al tag. Además es un nombre de
63
        // variable más corto :)
64 17
        $key = $tagElement->tagName;
65
66
        // Si no hay un arreglo de destino para los datos se crea un arreglo
67
        // con el índice del nodo principal con valor vacío.
68 17
        if ($data === null) {
69
            //$data = [$key => self::getEmptyValue()];
70 17
            $data = [$key => null];
71
        }
72
73
        // Si el tagElement tiene atributos se agregan al arreglo dentro del
74
        // índice especial '@attributes'.
75 17
        if ($tagElement->hasAttributes()) {
76 16
            $data[$key]['@attributes'] = [];
77 16
            foreach ($tagElement->attributes as $attribute) {
78 16
                $data[$key]['@attributes'][$attribute->name] = $attribute->value;
79
            }
80
        }
81
82
        // Si el tagElement tiene nodos hijos se agregan al valor del tag.
83 17
        if ($tagElement->hasChildNodes()) {
84 17
            self::arrayAddChilds(
85 17
                $data,
86 17
                $tagElement,
87 17
                $tagElement->childNodes,
88 17
                $twinsAsArray
89 17
            );
90
        }
91
92
        // Entregar los datos del documento XML como un arreglo.
93 17
        return $data;
94
    }
95
96
    /**
97
     * Agrega nodos hijos de un documento XML a un arreglo PHP.
98
     *
99
     * @param array &$data Arreglo donde se agregarán los nodos hijos.
100
     * @param DOMElement $tagElement Nodo padre del que se extraerán los nodos
101
     * hijos.
102
     * @param DOMNodeList $childs Lista de nodos hijos del nodo padre.
103
     * @param bool $twinsAsArray Indica si se deben tratar los nodos gemelos
104
     * como un arreglo.
105
     * @return void
106
     */
107 17
    private static function arrayAddChilds(
108
        array &$data,
109
        DOMElement $tagElement,
110
        DOMNodeList $childs,
111
        bool $twinsAsArray,
112
    ): void {
113 17
        $key = $tagElement->tagName;
114
        // Se recorre cada uno de los nodos hijos.
115 17
        foreach ($childs as $child) {
116 17
            if ($child instanceof DOMText) {
117 17
                $textContent = trim($child->textContent);
118 17
                if ($textContent !== '') {
119 17
                    if ($tagElement->hasAttributes()) {
120 11
                        $data[$key]['@value'] = $textContent;
121 17
                    } elseif ($childs->length === 1 && empty($data[$key])) {
122 17
                        $data[$key] = $textContent;
123
                    } else {
124 17
                        $array[$key]['@value'] = $textContent;
125
                    }
126
                }
127 17
            } elseif ($child instanceof DOMElement) {
128 17
                $n_twinsNodes = self::nodeCountTwins(
129 17
                    $tagElement,
130 17
                    $child->tagName
131 17
                );
132 17
                if ($n_twinsNodes === 1) {
133 17
                    if ($twinsAsArray) {
134
                        self::decode($child, $data);
135
                    } else {
136 17
                        self::decode($child, $data[$key]);
137
                    }
138
                } else {
139
                    // Se crea una lista para el nodo hijo, pues tiene varios
140
                    // nodos iguales el XML.
141
                    if (!isset($data[$key][$child->tagName])) {
142
                        $data[$key][$child->tagName] = [];
143
                    }
144
145
                    // Se revisa si el nodo hijo es escalar. Si lo es, se añade
146
                    // a la lista directamente.
147
                    $textContent = trim($child->textContent);
148
                    if ($textContent !== '') {
149
                        $data[$key][$child->tagName][] = $textContent;
150
                    }
151
                    // Si el nodo hijo es un escalar, sino que es una lista de
152
                    // nodos, se construye como si fuese un arreglo normal con
153
                    // la llamada a decode().
154
                    else {
155
                        $siguiente = count($data[$key][$child->tagName]);
156
                        $data[$key][$child->tagName][$siguiente] = [];
157
                        self::decode(
158
                            $child,
159
                            $data[$key][$child->tagName][$siguiente],
160
                            true
161
                        );
162
                    }
163
                }
164
            }
165
        }
166
    }
167
168
    /**
169
     * Cuenta los nodos con el mismo nombre hijos de un DOMElement.
170
     *
171
     * @param DOMElement $dom Elemento DOM donde se buscarán los nodos.
172
     * @param string $tagName Nombre del tag a contar.
173
     * @return int Cantidad de nodos hijos con el mismo nombre.
174
     */
175 17
    private static function nodeCountTwins(DOMElement $dom, string $tagName): int
176
    {
177 17
        $twins = 0;
178 17
        foreach ($dom->childNodes as $child) {
179 17
            if ($child instanceof DOMElement && $child->tagName === $tagName) {
180 17
                $twins++;
181
            }
182
        }
183 17
        return $twins;
184
    }
185
}
186