1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* LibreDTE |
5
|
|
|
* Copyright (C) SASCO SpA (https://sasco.cl) |
6
|
|
|
* |
7
|
|
|
* Este programa es software libre: usted puede redistribuirlo y/o |
8
|
|
|
* modificarlo bajo los términos de la Licencia Pública General Affero de GNU |
9
|
|
|
* publicada por la Fundación para el Software Libre, ya sea la versión |
10
|
|
|
* 3 de la Licencia, o (a su elección) cualquier versión posterior de la |
11
|
|
|
* misma. |
12
|
|
|
* |
13
|
|
|
* Este programa se distribuye con la esperanza de que sea útil, pero |
14
|
|
|
* SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita |
15
|
|
|
* MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO. |
16
|
|
|
* Consulte los detalles de la Licencia Pública General Affero de GNU para |
17
|
|
|
* 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
|
|
|
* En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>. |
22
|
|
|
*/ |
23
|
|
|
|
24
|
|
|
namespace sasco\LibreDTE; |
25
|
|
|
|
26
|
|
|
// errores de XML se almacenarán internamente y no serán procesados por PHP |
27
|
|
|
// se deberán recuperar con: libxml_get_errors() |
28
|
|
|
libxml_use_internal_errors(true); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Clase para trabajar con XMLs |
32
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
33
|
|
|
* @version 2017-01-20 |
34
|
|
|
*/ |
35
|
|
|
class XML extends \DomDocument |
36
|
|
|
{ |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Constructor de la clase XML |
40
|
|
|
* @param version Versión del documento XML |
41
|
|
|
* @param encoding Codificación del documento XML |
42
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
43
|
|
|
* @version 2015-08-05 |
44
|
|
|
*/ |
45
|
|
|
public function __construct($version = '1.0', $encoding = 'ISO-8859-1') |
46
|
|
|
{ |
47
|
|
|
parent::__construct($version, $encoding); |
48
|
|
|
$this->formatOutput = true; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Método que genera nodos XML a partir de un arreglo |
53
|
|
|
* @param data Arreglo con los datos que se usarán para generar XML |
54
|
|
|
* @param namespace Arreglo con el espacio de nombres para el XML que se generará (URI y prefijo) |
55
|
|
|
* @param parent DOMElement padre para los elementos, o =null para que sea la raíz |
56
|
|
|
* @return Objeto \sasco\LibreDTE\XML |
57
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
58
|
|
|
* @version 2017-10-22 |
59
|
|
|
*/ |
60
|
|
|
public function generate(array $data, array $namespace = null, \DOMElement &$parent = null) |
61
|
|
|
{ |
62
|
|
|
if ($parent===null) { |
63
|
|
|
$parent = &$this; |
64
|
|
|
} |
65
|
|
|
foreach ($data as $key => $value) { |
66
|
|
|
if ($key=='@attributes') { |
67
|
|
|
if ($value!==false) { |
68
|
|
|
foreach ($value as $attr => $val) { |
69
|
|
|
if ($val!==false) { |
70
|
|
|
$parent->setAttribute($attr, $val); |
|
|
|
|
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
} else if ($key=='@value') { |
75
|
|
|
$parent->nodeValue = $this->sanitize($value); |
76
|
|
|
} else { |
77
|
|
|
if (is_array($value)) { |
78
|
|
|
if (!empty($value)) { |
79
|
|
|
$keys = array_keys($value); |
80
|
|
|
if (!is_int($keys[0])) { |
81
|
|
|
$value = [$value]; |
82
|
|
|
} |
83
|
|
|
foreach ($value as $value2) { |
84
|
|
|
if ($namespace) { |
85
|
|
|
$Node = $this->createElementNS($namespace[0], $namespace[1].':'.$key); |
86
|
|
|
} else { |
87
|
|
|
$Node = $this->createElement($key); |
88
|
|
|
} |
89
|
|
|
$parent->appendChild($Node); |
90
|
|
|
$this->generate($value2, $namespace, $Node); |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
} else { |
94
|
|
|
if (is_object($value) and $value instanceof \DOMElement) { |
|
|
|
|
95
|
|
|
$Node = $this->importNode($value, true); |
96
|
|
|
$parent->appendChild($Node); |
97
|
|
|
} else { |
98
|
|
|
if ($value!==false) { |
99
|
|
|
if ($namespace) { |
100
|
|
|
$Node = $this->createElementNS($namespace[0], $namespace[1].':'.$key, $this->iso2utf($this->sanitize($value))); |
101
|
|
|
} else { |
102
|
|
|
$Node = $this->createElement($key, $this->iso2utf($this->sanitize($value))); |
103
|
|
|
} |
104
|
|
|
$parent->appendChild($Node); |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
return $this; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Método que sanitiza los valores que son asignados a los tags del XML |
115
|
|
|
* @param txt String que que se asignará como valor al nodo XML |
116
|
|
|
* @return String sanitizado |
117
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
118
|
|
|
* @version 2015-09-02 |
119
|
|
|
*/ |
120
|
|
|
private function sanitize($txt) |
121
|
|
|
{ |
122
|
|
|
// si no se paso un texto o bien es un número no se hace nada |
123
|
|
|
if (!$txt or is_numeric($txt)) |
|
|
|
|
124
|
|
|
return $txt; |
125
|
|
|
// convertir "predefined entities" de XML |
126
|
|
|
$txt = str_replace( |
127
|
|
|
['&', '&', '<', '<', '>', '>', '"', '"', ''', '''], |
128
|
|
|
['&', '&', '<', '<', '>', '>', '"', '"', '\'', '\''], |
129
|
|
|
$txt |
130
|
|
|
); |
131
|
|
|
$txt = str_replace('&', '&', $txt); |
132
|
|
|
/*$txt = str_replace( |
133
|
|
|
['&', '"', '\''], |
134
|
|
|
['&', '"', '''], |
135
|
|
|
$txt |
136
|
|
|
);*/ |
137
|
|
|
// entregar texto sanitizado |
138
|
|
|
return $txt; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Método que carga un string XML en el Objeto |
143
|
|
|
* @param source String con el documento XML a cargar |
144
|
|
|
* @param options Opciones para la carga del XML |
145
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
146
|
|
|
* @version 2016-11-21 |
147
|
|
|
*/ |
148
|
|
|
public function loadXML($source, $options = null) |
149
|
|
|
{ |
150
|
|
|
$tRetorno = $source ? parent::load($this->iso2utf($source), $options) : false; |
|
|
|
|
151
|
|
|
|
152
|
|
|
if (!$tRetorno){ |
153
|
|
|
$tRetorno = parent::loadXML($this->iso2utf($source), $options); |
154
|
|
|
} |
155
|
|
|
if(!$tRetorno){ |
156
|
|
|
foreach (libxml_get_errors() as $error) { |
157
|
|
|
echo "Error: " . $error->message; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
return $tRetorno; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Método para realizar consultas XPATH al documento XML |
165
|
|
|
* @param expression Expresión XPath a ejecutar |
166
|
|
|
* @return DOMNodeList |
167
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
168
|
|
|
* @version 2015-08-05 |
169
|
|
|
*/ |
170
|
|
|
public function xpath($expression) |
171
|
|
|
{ |
172
|
|
|
return (new \DOMXPath($this))->query($expression); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Método que entrega el código XML aplanado y con la codificación que |
177
|
|
|
* corresponde |
178
|
|
|
* @param xpath XPath para consulta al XML y extraer sólo una parte |
179
|
|
|
* @return String con código XML aplanado |
180
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
181
|
|
|
* @version 2017-01-20 |
182
|
|
|
*/ |
183
|
|
|
public function getFlattened($xpath = null) |
184
|
|
|
{ |
185
|
|
|
if ($xpath) { |
186
|
|
|
$node = $this->xpath($xpath)->item(0); |
187
|
|
|
if (!$node) |
188
|
|
|
return false; |
189
|
|
|
$xml = $this->utf2iso($node->C14N()); |
190
|
|
|
$xml = $this->fixEntities($xml); |
191
|
|
|
} else { |
192
|
|
|
$xml = $this->C14N(); |
193
|
|
|
} |
194
|
|
|
$xml = preg_replace("/\>\n\s+\</", '><', $xml); |
195
|
|
|
$xml = preg_replace("/\>\n\t+\</", '><', $xml); |
196
|
|
|
$xml = preg_replace("/\>\n+\</", '><', $xml); |
197
|
|
|
return trim($xml); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Método que codifica el string como ISO-8859-1 si es que fue pasado como |
202
|
|
|
* UTF-8 |
203
|
|
|
* @param string String en UTF-8 o ISO-8859-1 |
204
|
|
|
* @return String en ISO-8859-1 |
205
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
206
|
|
|
* @version 2016-04-03 |
207
|
|
|
*/ |
208
|
|
|
private function utf2iso($string) |
209
|
|
|
{ |
210
|
|
|
return mb_detect_encoding($string, ['UTF-8', 'ISO-8859-1']) != 'ISO-8859-1' ? utf8_decode($string) : $string; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Método que codifica el string como UTF-8 si es que fue pasado como |
215
|
|
|
* ISO-8859-1 |
216
|
|
|
* @param string String en UTF-8 o ISO-8859-1 |
217
|
|
|
* @return String en ISO-8859-1 |
218
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
219
|
|
|
* @version 2016-04-03 |
220
|
|
|
*/ |
221
|
|
|
private function iso2utf($string) |
222
|
|
|
{ |
223
|
|
|
return $string; |
224
|
|
|
//return mb_detect_encoding($string, ['ISO-8859-1', 'UTF-8']) == 'ISO-8859-1' ? utf8_encode($string) : $string; |
|
|
|
|
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Método que convierte el XML a un arreglo |
229
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
230
|
|
|
* @version 2016-06-11 |
231
|
|
|
*/ |
232
|
|
|
public function toArray(\DOMElement $dom = null, array &$array = null, $arregloNodos = false) |
233
|
|
|
{ |
234
|
|
|
// determinar valores de parámetros |
235
|
|
|
if (!$dom) |
236
|
|
|
$dom = $this->documentElement; |
237
|
|
|
if (!$dom) |
238
|
|
|
return false; |
239
|
|
|
if ($array===null) |
240
|
|
|
$array = [$dom->tagName => null]; |
241
|
|
|
// agregar atributos del nodo |
242
|
|
|
if ($dom->hasAttributes()) { |
243
|
|
|
$array[$dom->tagName]['@attributes'] = []; |
244
|
|
|
foreach ($dom->attributes as $attribute) { |
245
|
|
|
$array[$dom->tagName]['@attributes'][$attribute->name] = $attribute->value; |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
// agregar nodos hijos |
249
|
|
|
if ($dom->hasChildNodes()) { |
250
|
|
|
foreach($dom->childNodes as $child) { |
251
|
|
|
if ($child instanceof \DOMText) { |
252
|
|
|
$textContent = trim($child->textContent); |
253
|
|
|
if ($textContent!="") { |
254
|
|
|
if ($dom->childNodes->length==1 and empty($array[$dom->tagName])) { |
|
|
|
|
255
|
|
|
$array[$dom->tagName] = $textContent; |
256
|
|
|
} else |
257
|
|
|
$array[$dom->tagName]['@value'] = $textContent; |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
else if ($child instanceof \DOMElement) { |
261
|
|
|
$nodos_gemelos = $this->countTwins($dom, $child->tagName); |
262
|
|
|
if ($nodos_gemelos==1) { |
263
|
|
|
if ($arregloNodos) |
264
|
|
|
$this->toArray($child, $array); |
265
|
|
|
else |
266
|
|
|
$this->toArray($child, $array[$dom->tagName]); |
267
|
|
|
} |
268
|
|
|
// crear arreglo con nodos hijos que tienen el mismo nombre de tag |
269
|
|
|
else { |
270
|
|
|
if (!isset($array[$dom->tagName][$child->tagName])) |
271
|
|
|
$array[$dom->tagName][$child->tagName] = []; |
272
|
|
|
$siguiente = count($array[$dom->tagName][$child->tagName]); |
273
|
|
|
$array[$dom->tagName][$child->tagName][$siguiente] = []; |
274
|
|
|
$this->toArray($child, $array[$dom->tagName][$child->tagName][$siguiente], true); |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
// entregar arreglo |
280
|
|
|
return $array; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Método que cuenta los nodos con el mismo nombre hijos deun DOMElement |
285
|
|
|
* No sirve usar: $dom->getElementsByTagName($tagName)->length ya que esto |
286
|
|
|
* entrega todos los nodos con el nombre, sean hijos, nietos, etc. |
287
|
|
|
* @return Cantidad de nodos hijos con el mismo nombre en el DOMElement |
288
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
289
|
|
|
* @version 2015-09-07 |
290
|
|
|
*/ |
291
|
|
|
private function countTwins(\DOMElement $dom, $tagName) |
292
|
|
|
{ |
293
|
|
|
$twins = 0; |
294
|
|
|
foreach ($dom->childNodes as $child) { |
295
|
|
|
if ($child instanceof \DOMElement and $child->tagName==$tagName) |
|
|
|
|
296
|
|
|
$twins++; |
297
|
|
|
} |
298
|
|
|
return $twins; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Método que entrega los errores de libxml que pueden existir |
303
|
|
|
* @return Arreglo con los errores XML que han ocurrido |
304
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
305
|
|
|
* @version 2015-09-18 |
306
|
|
|
*/ |
307
|
|
|
public function getErrors() |
308
|
|
|
{ |
309
|
|
|
$errors = []; |
310
|
|
|
foreach (libxml_get_errors() as $e) |
311
|
|
|
$errors[] = $e->message; |
312
|
|
|
return $errors; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Método que entrega el nombre del tag raíz del XML |
317
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
318
|
|
|
* @version 2015-12-14 |
319
|
|
|
*/ |
320
|
|
|
public function getName() |
321
|
|
|
{ |
322
|
|
|
return $this->documentElement->tagName; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Método que entrega el nombre del archivo del schema del XML |
327
|
|
|
* @return Nombre del schema o bien =false si no se encontró |
328
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
329
|
|
|
* @version 2015-12-14 |
330
|
|
|
*/ |
331
|
|
|
public function getSchema() |
332
|
|
|
{ |
333
|
|
|
$schemaLocation = $this->documentElement->getAttribute('xsi:schemaLocation'); |
334
|
|
|
if (!$schemaLocation or strpos($schemaLocation, ' ')===false) |
|
|
|
|
335
|
|
|
return false; |
|
|
|
|
336
|
|
|
list($uri, $xsd) = explode(' ', $schemaLocation); |
|
|
|
|
337
|
|
|
return $xsd; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Wrapper para saveXML() y corregir entities |
342
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
343
|
|
|
* @version 2017-01-20 |
344
|
|
|
*/ |
345
|
|
|
public function saveXML(\DOMNode $node = null, $options = null) |
346
|
|
|
{ |
347
|
|
|
$xml = parent::saveXML($node, $options); |
348
|
|
|
$xml = $this->fixEntities($xml); |
349
|
|
|
return $xml; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Wrapper para C14N() y corregir entities |
354
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
355
|
|
|
* @version 2017-01-20 |
356
|
|
|
*/ |
357
|
|
|
public function C14N($exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) |
358
|
|
|
{ |
359
|
|
|
$xml = parent::C14N($exclusive, $with_comments, $xpath, $ns_prefixes); |
360
|
|
|
$xml = $this->fixEntities($xml); |
361
|
|
|
return $xml; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Método que corrige las entities ' (') y " (") ya que el SII no |
366
|
|
|
* respeta el estándar y las requiere convertidas |
367
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
368
|
|
|
* @version 2017-01-20 |
369
|
|
|
*/ |
370
|
|
|
private function fixEntities($xml) |
371
|
|
|
{ |
372
|
|
|
$newXML = ''; |
373
|
|
|
$n_letras = strlen($xml); |
374
|
|
|
$convertir = false; |
375
|
|
|
for ($i=0; $i<$n_letras; ++$i) { |
376
|
|
|
if ($xml[$i]=='>') |
377
|
|
|
$convertir = true; |
378
|
|
|
if ($xml[$i]=='<') |
379
|
|
|
$convertir = false; |
380
|
|
|
if ($convertir) { |
381
|
|
|
$l = $xml[$i]=='\'' ? ''' : ($xml[$i]=='"' ? '"' : $xml[$i]); |
382
|
|
|
} else { |
383
|
|
|
$l = $xml[$i]; |
384
|
|
|
} |
385
|
|
|
$newXML .= $l; |
386
|
|
|
} |
387
|
|
|
return $newXML; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
} |
391
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.