Skip to content

Instantly share code, notes, and snippets.

@goedecke
Created November 24, 2016 15:40
Show Gist options
  • Star 39 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • Save goedecke/03e9c7c178ff947b1e9d9eaea4bbe369 to your computer and use it in GitHub Desktop.
Save goedecke/03e9c7c178ff947b1e9d9eaea4bbe369 to your computer and use it in GitHub Desktop.
Extraer información de CFDI XML facil con simplexml
<?php
$xml = simplexml_load_file('test.xml');
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('c', $ns['cfdi']);
$xml->registerXPathNamespace('t', $ns['tfd']);
//EMPIEZO A LEER LA INFORMACION DEL CFDI E IMPRIMIRLA
foreach ($xml->xpath('//cfdi:Comprobante') as $cfdiComprobante){
echo $cfdiComprobante['version'];
echo "<br />";
echo $cfdiComprobante['fecha'];
echo "<br />";
echo $cfdiComprobante['sello'];
echo "<br />";
echo $cfdiComprobante['total'];
echo "<br />";
echo $cfdiComprobante['subTotal'];
echo "<br />";
echo $cfdiComprobante['certificado'];
echo "<br />";
echo $cfdiComprobante['formaDePago'];
echo "<br />";
echo $cfdiComprobante['noCertificado'];
echo "<br />";
echo $cfdiComprobante['tipoDeComprobante'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Emisor') as $Emisor){
echo $Emisor['rfc'];
echo "<br />";
echo $Emisor['nombre'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Emisor//cfdi:DomicilioFiscal') as $DomicilioFiscal){
echo $DomicilioFiscal['pais'];
echo "<br />";
echo $DomicilioFiscal['calle'];
echo "<br />";
echo $DomicilioFiscal['estado'];
echo "<br />";
echo $DomicilioFiscal['colonia'];
echo "<br />";
echo $DomicilioFiscal['municipio'];
echo "<br />";
echo $DomicilioFiscal['noExterior'];
echo "<br />";
echo $DomicilioFiscal['codigoPostal'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Emisor//cfdi:ExpedidoEn') as $ExpedidoEn){
echo $ExpedidoEn['pais'];
echo "<br />";
echo $ExpedidoEn['calle'];
echo "<br />";
echo $ExpedidoEn['estado'];
echo "<br />";
echo $ExpedidoEn['colonia'];
echo "<br />";
echo $ExpedidoEn['noExterior'];
echo "<br />";
echo $ExpedidoEn['codigoPostal'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Receptor') as $Receptor){
echo $Receptor['rfc'];
echo "<br />";
echo $Receptor['nombre'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Receptor//cfdi:Domicilio') as $ReceptorDomicilio){
echo $ReceptorDomicilio['pais'];
echo "<br />";
echo $ReceptorDomicilio['calle'];
echo "<br />";
echo $ReceptorDomicilio['estado'];
echo "<br />";
echo $ReceptorDomicilio['colonia'];
echo "<br />";
echo $ReceptorDomicilio['municipio'];
echo "<br />";
echo $ReceptorDomicilio['noExterior'];
echo "<br />";
echo $ReceptorDomicilio['noInterior'];
echo "<br />";
echo $ReceptorDomicilio['codigoPostal'];
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Conceptos//cfdi:Concepto') as $Concepto){
echo "<br />";
echo $Concepto['unidad'];
echo "<br />";
echo $Concepto['importe'];
echo "<br />";
echo $Concepto['cantidad'];
echo "<br />";
echo $Concepto['descripcion'];
echo "<br />";
echo $Concepto['valorUnitario'];
echo "<br />";
echo "<br />";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado') as $Traslado){
echo $Traslado['tasa'];
echo "<br />";
echo $Traslado['importe'];
echo "<br />";
echo $Traslado['impuesto'];
echo "<br />";
echo "<br />";
}
//ESTA ULTIMA PARTE ES LA QUE GENERABA EL ERROR
foreach ($xml->xpath('//t:TimbreFiscalDigital') as $tfd) {
echo $tfd['selloCFD'];
echo "<br />";
echo $tfd['FechaTimbrado'];
echo "<br />";
echo $tfd['UUID'];
echo "<br />";
echo $tfd['noCertificadoSAT'];
echo "<br />";
echo $tfd['version'];
echo "<br />";
echo $tfd['selloSAT'];
}
?>
@KenyaGalicia
Copy link

Hola buenas tardes, alguien sabe si tambien servira para comprobantes tipo i?

@goedecke
Copy link
Author

Si, la base es la misma, solo tienes que cambiar algunas etiquetas del XML

@KenyaGalicia
Copy link

KenyaGalicia commented Nov 21, 2019 via email

@ThorOdinson23
Copy link

$xml = simplexml_load_file('cfdi_ejemplo_factura.xml');
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('c', $ns['cfdi']);
$xml->registerXPathNamespace('t', $ns['tfd']);

//EMPIEZO A LEER LA INFORMACION DEL CFDI E IMPRIMIRLA
foreach ($xml->xpath('//cfdi:Comprobante') as $cfdiComprobante){
echo $cfdiComprobante['Version'];
echo "
";
echo $cfdiComprobante['CondicionesDePago'];
echo "
";
echo $cfdiComprobante['Fecha'];
echo "
";
echo $cfdiComprobante['Folio'];
echo "
";
echo $cfdiComprobante['FormaPago'];
echo "
";
echo $cfdiComprobante['LugarExpedicion'];
echo "
";
echo $cfdiComprobante['MetodoPago'];
echo "
";
echo $cfdiComprobante['Moneda'];
echo "
";
echo $cfdiComprobante['Serie'];
echo "
";
echo $cfdiComprobante['SubTotal'];
echo "
";
echo $cfdiComprobante['TipoCambio'];
echo "
";
echo $cfdiComprobante['TipoDeComprobante'];
echo "
";
echo $cfdiComprobante['Total'];
echo "
";
echo $cfdiComprobante['Certificado'];
echo "
";
echo $cfdiComprobante['NoCertificado'];
echo "
";
echo $cfdiComprobante['Sello'];
echo "
";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Emisor') as $Emisor){
echo $Emisor['Rfc'];
echo "
";
echo $Emisor['Nombre'];
echo "
";
echo $Emisor['RegimenFiscal'];
echo "
";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Receptor') as $Receptor){
echo $Receptor['Rfc'];
echo "
";
echo $Receptor['Nombre'];
echo "
";
echo $Receptor['UsoCFDI'];
echo "
";
}
//////////////////// START cfdi:Conceptos ////////////////////
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Conceptos//cfdi:Concepto') as $Concepto){
echo "
";
echo $Concepto['Cantidad'];
echo "
";
echo $Concepto['Unidad'];
echo "
";
echo $Concepto['NoIdentificacion'];
echo "
";
echo $Concepto['Descripcion'];
echo "
";
echo $Concepto['ValorUnitario'];
echo "
";
echo $Concepto['Importe'];
echo "
";
echo $Concepto['ClaveProdServ'];
echo "
";
echo $Concepto['ClaveUnidad'];
echo "
";
echo "
";
}
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Conceptos//cfdi:Concepto//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado') as $Traslado){
echo $Traslado['Base'];
echo "
";
echo $Traslado['Impuesto'];
echo "
";
echo $Traslado['TipoFactor'];
echo "
";
echo $Traslado['TasaOCuota'];
echo "
";
echo $Traslado['Importe'];
echo "
";
echo "
";
}

//////////////////// END cfdi:Conceptos ////////////////////

//////////////////// START cfdi:Impuestos (TotalImpuestosTrasladados) ////////////////////
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Impuestos') as $Impuestos){
echo "
";
echo $Impuestos['TotalImpuestosTrasladados'];
echo "
";
echo "
";

}
//////////////////// END cfdi:Impuestos (TotalImpuestosTrasladados) ////////////////////

foreach ($xml->xpath('//t:TimbreFiscalDigital') as $tfd) {
echo $tfd['Version'];
echo "
";
echo $tfd['UUID'];
echo "
";
echo $tfd['FechaTimbrado'];
echo "
";
echo $tfd['RfcProvCertif'];
echo "
";
echo $tfd['SelloCFD'];
echo "
";
echo $tfd['NoCertificadoSAT'];
echo "
";
echo $tfd['SelloSAT'];
}

@ThorOdinson23
Copy link

los echo en blanco son
echo "<br/>"

@josue1014
Copy link

Hola... muchas gracias por el codigo, funciona a la perfeccion.... Alguien sabe como interactuar con las mayusculas y minusculas de los nombres de los atributos?... necesito leer CDFI en versiones diferentes y a veces necesito las minusculas ['selloSAT'] y a veces mayusculas ['SelloSAT']? no puedo modificar el codigo cada vez, si no que necesito que sea interactivo y el codigo pueda detectar y leerlo.

Gracias y saludos!

$version = (string)$cfdiComprobante[0]['Version'];

    //Encontrar version del xml
    if ($version == NULL){
      $version = (string)$cfdiComprobante[0]['version'];
    }

Ya con esto sabes con que version estas trabajando.

con un if ($version == "3.2"){
//codigo que hacer si es 3.2
}elseif ($version == "3.3{
//codigo que hacer si es 3.3
}

@goedecke
Copy link
Author

Eso es en cuanto a diferentes versiones pero dentro del texto puedes homogeneizar todo a minusculas o Mayusculas para un tratamiento en el cual sin importar las version te maneje el mismo resultado. strupper strlower

@josue1014
Copy link

Genial!, pero hay alguna solución en la que se pueda iterar el xml como un array anidado, me imagino que debe ser como un foreach()... pero no estoy completamente seguro....

$ruta = //ruta hacia la carpeta donde están tus xml's
$dir = opendir($ruta);

while ($file = readdir($dir)) {
if ($file != "." && $file != ".." && substr($file,-4)==".xml"){
$xml = simplexml_load_file($ruta . $file);
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('c', $ns['cfdi']);
$xml->registerXPathNamespace('t', $ns['tfd']);
..... Aquí va el resto del código
}
}

@goedecke
Copy link
Author

goedecke commented Jan 10, 2020

Genial!, pero hay alguna solución en la que se pueda iterar el xml como un array anidado, me imagino que debe ser como un foreach()... pero no estoy completamente seguro....

$ruta = //ruta hacia la carpeta donde están tus xml's
$dir = opendir($ruta);

while ($file = readdir($dir)) {
if ($file != "." && $file != ".." && substr($file,-4)==".xml"){
$xml = simplexml_load_file($ruta . $file);
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('c', $ns['cfdi']);
$xml->registerXPathNamespace('t', $ns['tfd']);
..... Aquí va el resto del código
}
}

en ves de while ($file = readdir($dir)) usa scandir()
Con esto te saltas los dos puntos
$files = array_slice(scandir('/path/to/directory/'), 2);
y así reduces el uso de memoria y de recursos en paths con cientos de archivos.

y yo manejaría una función por separado

Esto te regresa un arreglo con todos los archivos, en la carpeta o subcarpetas de forma recursiva esto por si tienes alguna distribución por año mes del guardado de tus archivos.

function scanDirectories($rootDir, $allData=array()) {
$invisibleFileNames = array(".", "..", ".htaccess", ".htpasswd");
$dirContent = scandir($rootDir);
foreach($dirContent as $key => $content) {
$path = $rootDir.'/'.$content;
if(!in_array($content, $invisibleFileNames)) {
if(is_file($path) && is_readable($path)) {
$allData[] = $path;
}elseif(is_dir($path) && is_readable($path)) {
$allData = scanDirectories($path, $allData);
}
}
}
return $allData;
}

@josue1014
Copy link

Eso es en cuanto a diferentes versiones pero dentro del texto puedes homogeneizar todo a minusculas o Mayusculas para un tratamiento en el cual sin importar las version te maneje el mismo resultado. strupper strlower

Genial!, pero hay alguna solución en la que se pueda iterar el xml como un array anidado, me imagino que debe ser como un foreach()... pero no estoy completamente seguro....

$ruta = //ruta hacia la carpeta donde están tus xml's
$dir = opendir($ruta);
while ($file = readdir($dir)) {
if ($file != "." && $file != ".." && substr($file,-4)==".xml"){
$xml = simplexml_load_file($ruta . $file);
$ns = $xml->getNamespaces(true);
$xml->registerXPathNamespace('c', $ns['cfdi']);
$xml->registerXPathNamespace('t', $ns['tfd']);
..... Aquí va el resto del código
}
}

en ves de while ($file = readdir($dir)) usa scandir()
Con esto te saltas los dos puntos
$files = array_slice(scandir('/path/to/directory/'), 2);
y así reduces el uso de memoria y de recursos en paths con cientos de archivos.

y yo manejaría una función por separado

Esto te regresa un arreglo con todos los archivos, en la carpeta o subcarpetas de forma recursiva esto por si tienes alguna distribución por año mes del guardado de tus archivos.

function scanDirectories($rootDir, $allData=array()) {
$invisibleFileNames = array(".", "..", ".htaccess", ".htpasswd");
$dirContent = scandir($rootDir);
foreach($dirContent as $key => $content) {
$path = $rootDir.'/'.$content;
if(!in_array($content, $invisibleFileNames)) {
if(is_file($path) && is_readable($path)) {
$allData[] = $path;
}elseif(is_dir($path) && is_readable($path)) {
$allData = scanDirectories($path, $allData);
}
}
}
return $allData;
}

Excelente! Muchas gracias.

@andresaquino
Copy link

Hi!

Por si les es de utilidad, se pueden obtener los namespaces y analizar todo el documento:

$xmlString = 'AQUI EL CFDI'
$xmlObject = simplexml_load_string($xmlString);
$ns = $xmlObject->getNamespaces(true);

Y con una función recursiva, estructurarlo como un arreglo y evitarse el tema de buscar con XPath:

    function XMLNode($XMLNode, $ns)
    {
        //
        $nodes = array();
        $response = array();
        $attributes = array();

        // first item ?
        $_isfirst = true;

        // each namespace
        //  - xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
        //  - xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital"
        foreach ($ns as $eachSpace) {
            //
            // each node
            foreach ($XMLNode->children($eachSpace) as $_tag => $_node) {
                //
                $_value = $this->XMLNode($_node, $ns);

                // exists $tag in $children?
                if (key_exists($_tag, $nodes)) {
                    if ($_isfirst) {
                        $tmp = $nodes[$_tag];
                        unset($nodes[$_tag]);
                        $nodes[] = $tmp;
                        $is_first = false;
                    }
                    $nodes[] = $_value;
                } else {
                    $nodes[$_tag] = $_value;
                }
            }
        }

        //
        $attributes = array_merge(
            $attributes,
            current($XMLNode->attributes())
        );

        // nodes ?
        if (count($nodes)) {
            $response = array_merge(
                $response,
                $nodes
            );
        }

        // attributes ?
        if (count($attributes)) {
            $response = array_merge(
                $response,
                $attributes
            );
        }

        return (empty($response) ? null : $response);
    }

//
$response = XMLNode($xmlObject, $ns);

Si revisan la respuesta, podrán obtener algo como:

   [Concepto] => Array
        (
            [Impuestos] => Array
                (
                    [Traslados] => Array
                        (
                            [Traslado] => Array
                                (
                                    [Base] => 7867.14
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.160000
                                    [Importe] => 1258.74
                                )

                        )

                    [Retenciones] => Array
                        (
                            [0] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 001
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.100000
                                    [Importe] => 786.71
                                )

                            [1] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.106667
                                    [Importe] => 839.16
                                )

                        )

                )

            [ClaveProdServ] => XXXXXXXX
            [Cantidad] => 1
            [ClaveUnidad] => E48
            [Unidad] => Unidad de servicio
            [Descripcion] => HONORARIOS
            [ValorUnitario] => 7867.14
            [Importe] => 7867.14
        )

Finalmente, hace mucho más sentido usar el documento, como un arreglo:

	<cfdi:Complemento>
		<tfd:TimbreFiscalDigital Version="1.1" UUID="0DE6AEB1-E8FG-41CD-881D-A6AB18F0415D" ... />
	</cfdi:Complemento>

echo($response['Complementos']['TimbreFiscalDigital']['UUID']);

My2C

@goedecke
Copy link
Author

Hola... muchas gracias por el codigo, funciona a la perfeccion.... Alguien sabe como interactuar con las mayusculas y minusculas de los nombres de los atributos?... necesito leer CDFI en versiones diferentes y a veces necesito las minusculas ['selloSAT'] y a veces mayusculas ['SelloSAT']? no puedo modificar el codigo cada vez, si no que necesito que sea interactivo y el codigo pueda detectar y leerlo.

Gracias y saludos!

Igual y si conviertes las variables a minúsculas ayuda (strtolower)

@goedecke
Copy link
Author

Hi!

Por si les es de utilidad, se pueden obtener los namespaces y analizar todo el documento:

$xmlString = 'AQUI EL CFDI'
$xmlObject = simplexml_load_string($xmlString);
$ns = $xmlObject->getNamespaces(true);

Y con una función recursiva, estructurarlo como un arreglo y evitarse el tema de buscar con XPath:

    function XMLNode($XMLNode, $ns)
    {
        //
        $nodes = array();
        $response = array();
        $attributes = array();

        // first item ?
        $_isfirst = true;

        // each namespace
        //  - xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
        //  - xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital"
        foreach ($ns as $eachSpace) {
            //
            // each node
            foreach ($XMLNode->children($eachSpace) as $_tag => $_node) {
                //
                $_value = $this->XMLNode($_node, $ns);

                // exists $tag in $children?
                if (key_exists($_tag, $nodes)) {
                    if ($_isfirst) {
                        $tmp = $nodes[$_tag];
                        unset($nodes[$_tag]);
                        $nodes[] = $tmp;
                        $is_first = false;
                    }
                    $nodes[] = $_value;
                } else {
                    $nodes[$_tag] = $_value;
                }
            }
        }

        //
        $attributes = array_merge(
            $attributes,
            current($XMLNode->attributes())
        );

        // nodes ?
        if (count($nodes)) {
            $response = array_merge(
                $response,
                $nodes
            );
        }

        // attributes ?
        if (count($attributes)) {
            $response = array_merge(
                $response,
                $attributes
            );
        }

        return (empty($response) ? null : $response);
    }

//
$response = XMLNode($xmlObject, $ns);

Si revisan la respuesta, podrán obtener algo como:

   [Concepto] => Array
        (
            [Impuestos] => Array
                (
                    [Traslados] => Array
                        (
                            [Traslado] => Array
                                (
                                    [Base] => 7867.14
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.160000
                                    [Importe] => 1258.74
                                )

                        )

                    [Retenciones] => Array
                        (
                            [0] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 001
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.100000
                                    [Importe] => 786.71
                                )

                            [1] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.106667
                                    [Importe] => 839.16
                                )

                        )

                )

            [ClaveProdServ] => XXXXXXXX
            [Cantidad] => 1
            [ClaveUnidad] => E48
            [Unidad] => Unidad de servicio
            [Descripcion] => HONORARIOS
            [ValorUnitario] => 7867.14
            [Importe] => 7867.14
        )

Finalmente, hace mucho más sentido usar el documento, como un arreglo:

	<cfdi:Complemento>
		<tfd:TimbreFiscalDigital Version="1.1" UUID="0DE6AEB1-E8FG-41CD-881D-A6AB18F0415D" ... />
	</cfdi:Complemento>

echo($response['Complementos']['TimbreFiscalDigital']['UUID']);

My2C

Pues si, pero eso te consume mas recursos de memoria y aquí abría que evaluar el tamaño de documentos a leer o bien el numero de items a extraer, y si de convertir a arreglo simple para extraer datos, pues recomiendo que todo lo vuelvas Arreglo y eso es fácil con esto que publique hace como 4 años

https://gist.github.com/goedecke/20dc74132bd928f37d339178863bfbc3

@andresaquino
Copy link

Entiendo.
Prefiero claridad en la implementación (que sea sencillo para el equipo de trabajo), y lo del consumo de memoria pues es relativo, como bien mencionas; depende del tamaño de documentos a leer (¿facturas de 50Mb?) y lo del # de items a extraer, pues.

En fin, fue solo un aporte que muestra que cada quien tiene una visión distinta para lograr sus objetivos.
Saludos.

@jattonio
Copy link

jattonio commented Mar 26, 2020

Hi!

Por si les es de utilidad, se pueden obtener los namespaces y analizar todo el documento:

$xmlString = 'AQUI EL CFDI'
$xmlObject = simplexml_load_string($xmlString);
$ns = $xmlObject->getNamespaces(true);

Y con una función recursiva, estructurarlo como un arreglo y evitarse el tema de buscar con XPath:

    function XMLNode($XMLNode, $ns)
    {
        //
        $nodes = array();
        $response = array();
        $attributes = array();

        // first item ?
        $_isfirst = true;

        // each namespace
        //  - xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
        //  - xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital"
        foreach ($ns as $eachSpace) {
            //
            // each node
            foreach ($XMLNode->children($eachSpace) as $_tag => $_node) {
                //
                $_value = $this->XMLNode($_node, $ns);

                // exists $tag in $children?
                if (key_exists($_tag, $nodes)) {
                    if ($_isfirst) {
                        $tmp = $nodes[$_tag];
                        unset($nodes[$_tag]);
                        $nodes[] = $tmp;
                        $is_first = false;
                    }
                    $nodes[] = $_value;
                } else {
                    $nodes[$_tag] = $_value;
                }
            }
        }

        //
        $attributes = array_merge(
            $attributes,
            current($XMLNode->attributes())
        );

        // nodes ?
        if (count($nodes)) {
            $response = array_merge(
                $response,
                $nodes
            );
        }

        // attributes ?
        if (count($attributes)) {
            $response = array_merge(
                $response,
                $attributes
            );
        }

        return (empty($response) ? null : $response);
    }

//
$response = XMLNode($xmlObject, $ns);

Si revisan la respuesta, podrán obtener algo como:

   [Concepto] => Array
        (
            [Impuestos] => Array
                (
                    [Traslados] => Array
                        (
                            [Traslado] => Array
                                (
                                    [Base] => 7867.14
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.160000
                                    [Importe] => 1258.74
                                )

                        )

                    [Retenciones] => Array
                        (
                            [0] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 001
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.100000
                                    [Importe] => 786.71
                                )

                            [1] => Array
                                (
                                    [Base] => 7867.140000
                                    [Impuesto] => 002
                                    [TipoFactor] => Tasa
                                    [TasaOCuota] => 0.106667
                                    [Importe] => 839.16
                                )

                        )

                )

            [ClaveProdServ] => XXXXXXXX
            [Cantidad] => 1
            [ClaveUnidad] => E48
            [Unidad] => Unidad de servicio
            [Descripcion] => HONORARIOS
            [ValorUnitario] => 7867.14
            [Importe] => 7867.14
        )

Finalmente, hace mucho más sentido usar el documento, como un arreglo:

	<cfdi:Complemento>
		<tfd:TimbreFiscalDigital Version="1.1" UUID="0DE6AEB1-E8FG-41CD-881D-A6AB18F0415D" ... />
	</cfdi:Complemento>

echo($response['Complementos']['TimbreFiscalDigital']['UUID']);

My2C

Excelente código, funciona de maravilla... Gracias por la aportación.. sólo una observación, en la línea del merge() me mandaba un Warning: Warning: array_key_exists() expects parameter 2 to be array, boolean given in

Se corrigió con un cast a array
Código anterior:
$attributes = array_merge(
$attributes,
current($XMLNode->attributes())
);
Código nuevo:
$attributes = array_merge(
$attributes,
(array)current($XMLNode->attributes())
);

Gracias nuevamente por la aportación..
Saludos.

@jattonio
Copy link

Me faltó felicitarte goedecke... excelente código.. muy sencillo de implementar y entender...!!! Gracias nuevamente..

@andresaquino
Copy link

Se corrigió con un cast a array
Código anterior:
$attributes = array_merge(
$attributes,
current($XMLNode->attributes())
);
Código nuevo:
$attributes = array_merge(
$attributes,
(array)current($XMLNode->attributes())
);

Gracias nuevamente por la aportación..

Vale, haré el cambio.. la realidad es que no lo pasé por stan para hacer un análisis del código (amén de las pruebas unitarias/funcionales), gracias por la retro.

@draco3099
Copy link

para los que necesiten leer dentro de Complementos en facturas P

           // verifica que sea P y obtiene el monto
            if($tipocomprobante == 'P') {
              foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Complemento//pago10:Pagos//pago10:Pago') as $Pago10){
                $montop = $Pago10['Monto'];
                //drupal_set_message('monto: '.$montop, 'status', FALSE);
                $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                }

              } 

EDIT: este codigo esta mas limpio

                    `                             // verifica que sea P y obtiene el monto
                           if($tipocomprobante == 'P') {
                             $xml->registerXPathNamespace('p', $ns['pago10']);

                            // set monto pago 10
                          foreach ($xml->xpath('//p:Pago') as $pago10) {
                            $montop = $pago10['Monto'];
                            $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                            }

                            // set id ducumentp rel
                            $documentosRel = $xml->xpath('//p:DoctoRelacionado');
                            foreach ($documentosRel as $key) {
                              $node->field_pago_10_doctorel_id_dcto['und'][0]['value'] = $key['IdDocumento'];
                            }

                          } // fin comprobante P`

para los que necesiten leer dentro de Complementos en facturas P

           // verifica que sea P y obtiene el monto
            if($tipocomprobante == 'P') {
              foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Complemento//pago10:Pagos//pago10:Pago') as $Pago10){
                $montop = $Pago10['Monto'];
                //drupal_set_message('monto: '.$montop, 'status', FALSE);
                $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                }

              } 

EDIT: este codigo esta mas limpio

                    `                             // verifica que sea P y obtiene el monto
                           if($tipocomprobante == 'P') {
                             $xml->registerXPathNamespace('p', $ns['pago10']);

                            // set monto pago 10
                          foreach ($xml->xpath('//p:Pago') as $pago10) {
                            $montop = $pago10['Monto'];
                            $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                            }

                            // set id ducumentp rel
                            $documentosRel = $xml->xpath('//p:DoctoRelacionado');
                            foreach ($documentosRel as $key) {
                              $node->field_pago_10_doctorel_id_dcto['und'][0]['value'] = $key['IdDocumento'];
                            }

                          } // fin comprobante P`

Hola. Estoy recibiendo varios complementos que tienen dos o más pagos y cada pago a su vez tiene sus Documentos relacionados. ¿Cómo puedo leer el complemento de manera que pueda ver qué Documentos relacionados están amparados con cada Pago o monto? Por ejemplo:

cfdi:Complemento
<pago10:Pagos Version="1.0">
<pago10:Pago FechaPago="2019-06-21T12:07:57" FormaDePagoP="03" MonedaP="MXN" Monto="150000.00 ">
<pago10:DoctoRelacionado IdDocumento="UUID1" Serie="PC" Folio="230156" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="75000.00 " ImpPagado="75000.00 " ImpSaldoInsoluto="0.00 "/>
<pago10:DoctoRelacionado IdDocumento="UUID2" Serie="PC" Folio="230160" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="75000.00 " ImpPagado="750000.00 " ImpSaldoInsoluto="0.00 "/>
</pago10:Pago>
<pago10:Pago FechaPago="2019-06-21T14:35:25" FormaDePagoP="03" MonedaP="MXN" Monto="100000.00 ">
<pago10:DoctoRelacionado IdDocumento="UUID3" Serie="PC" Folio="230181" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="20000.00 " ImpPagado="20000.00 " ImpSaldoInsoluto="0.00 "/>
<pago10:DoctoRelacionado IdDocumento="UUID4" Serie="PC" Folio="230236" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="30000.00 " ImpPagado="30000.00 " ImpSaldoInsoluto="0.00 "/>
<pago10:DoctoRelacionado IdDocumento="UUID5" Serie="PC" Folio="230238" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="50000.00 " ImpPagado="50000.00 " ImpSaldoInsoluto="0.00 "/>
</pago10:Pago>
</pago10:Pagos>

De esto quisiera obtener algo como
Pago 1 - Monto 150000 - UUID1 - UUID2
Pago 2 - Monto 100000 - UUID3 - UUID4 - UUID5

¿Es posible hacerlo con ese orden?
Porque lo que consigo es
Pago 1 - Monto
Pago 2- Monto

UUID1
UUID2
UUID3
UUID4
UUID5

@omarlv86
Copy link

Buenas tardes, estaba accediendo a las facturas pero hoy me genero este error y no encuentro el porque surge, si alguien pudiera dar un poco de ayuda lo agradeceria.

This page contains the following errors:
error on line 28 at column 182: xmlns:schemaLocation: 'http://www.sat.gob.mx/registrofiscal http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd' is not a valid URI
Below is a rendering of the page up to the first error.

@andresaquino
Copy link

Buenas tardes, estaba accediendo a las facturas pero hoy me genero este error y no encuentro el porque surge, si alguien pudiera dar un poco de ayuda lo agradeceria.

This page contains the following errors:
error on line 28 at column 182: xmlns:schemaLocation: 'http://www.sat.gob.mx/registrofiscal http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd' is not a valid URI
Below is a rendering of the page up to the first error.

¿tienes más información al respecto?
¿dónde se presenta el error, factura de ejemplo...?

@andresaquino
Copy link

para los que necesiten leer dentro de Complementos en facturas P

           // verifica que sea P y obtiene el monto
            if($tipocomprobante == 'P') {
              foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Complemento//pago10:Pagos//pago10:Pago') as $Pago10){
                $montop = $Pago10['Monto'];
                //drupal_set_message('monto: '.$montop, 'status', FALSE);
                $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                }

              } 

EDIT: este codigo esta mas limpio

                    `                             // verifica que sea P y obtiene el monto
                           if($tipocomprobante == 'P') {
                             $xml->registerXPathNamespace('p', $ns['pago10']);

                            // set monto pago 10
                          foreach ($xml->xpath('//p:Pago') as $pago10) {
                            $montop = $pago10['Monto'];
                            $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                            }

                            // set id ducumentp rel
                            $documentosRel = $xml->xpath('//p:DoctoRelacionado');
                            foreach ($documentosRel as $key) {
                              $node->field_pago_10_doctorel_id_dcto['und'][0]['value'] = $key['IdDocumento'];
                            }

                          } // fin comprobante P`

para los que necesiten leer dentro de Complementos en facturas P

           // verifica que sea P y obtiene el monto
            if($tipocomprobante == 'P') {
              foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Complemento//pago10:Pagos//pago10:Pago') as $Pago10){
                $montop = $Pago10['Monto'];
                //drupal_set_message('monto: '.$montop, 'status', FALSE);
                $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                }

              } 

EDIT: este codigo esta mas limpio

                    `                             // verifica que sea P y obtiene el monto
                           if($tipocomprobante == 'P') {
                             $xml->registerXPathNamespace('p', $ns['pago10']);

                            // set monto pago 10
                          foreach ($xml->xpath('//p:Pago') as $pago10) {
                            $montop = $pago10['Monto'];
                            $node->field_factura_pago10_monto['und'][0]['value'] = $montop;
                            }

                            // set id ducumentp rel
                            $documentosRel = $xml->xpath('//p:DoctoRelacionado');
                            foreach ($documentosRel as $key) {
                              $node->field_pago_10_doctorel_id_dcto['und'][0]['value'] = $key['IdDocumento'];
                            }

                          } // fin comprobante P`

Hola. Estoy recibiendo varios complementos que tienen dos o más pagos y cada pago a su vez tiene sus Documentos relacionados. ¿Cómo puedo leer el complemento de manera que pueda ver qué Documentos relacionados están amparados con cada Pago o monto? Por ejemplo:

cfdi:Complemento <pago10:Pagos Version="1.0"> <pago10:Pago FechaPago="2019-06-21T12:07:57" FormaDePagoP="03" MonedaP="MXN" Monto="150000.00 "> <pago10:DoctoRelacionado IdDocumento="UUID1" Serie="PC" Folio="230156" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="75000.00 " ImpPagado="75000.00 " ImpSaldoInsoluto="0.00 "/> <pago10:DoctoRelacionado IdDocumento="UUID2" Serie="PC" Folio="230160" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="75000.00 " ImpPagado="750000.00 " ImpSaldoInsoluto="0.00 "/> </pago10:Pago> <pago10:Pago FechaPago="2019-06-21T14:35:25" FormaDePagoP="03" MonedaP="MXN" Monto="100000.00 "> <pago10:DoctoRelacionado IdDocumento="UUID3" Serie="PC" Folio="230181" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="20000.00 " ImpPagado="20000.00 " ImpSaldoInsoluto="0.00 "/> <pago10:DoctoRelacionado IdDocumento="UUID4" Serie="PC" Folio="230236" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="30000.00 " ImpPagado="30000.00 " ImpSaldoInsoluto="0.00 "/> <pago10:DoctoRelacionado IdDocumento="UUID5" Serie="PC" Folio="230238" MonedaDR="MXN" MetodoDePagoDR="PPD" NumParcialidad="1 " ImpSaldoAnt="50000.00 " ImpPagado="50000.00 " ImpSaldoInsoluto="0.00 "/> </pago10:Pago> </pago10:Pagos>

De esto quisiera obtener algo como
Pago 1 - Monto 150000 - UUID1 - UUID2
Pago 2 - Monto 100000 - UUID3 - UUID4 - UUID5

¿Es posible hacerlo con ese orden?
Porque lo que consigo es
Pago 1 - Monto
Pago 2- Monto

UUID1
UUID2
UUID3
UUID4
UUID5

Quizás algo así?

foreach ($xml->xpath('//p:Pago') as $pago10) {
    $montop = $pago10['Monto'];
    $node->field_factura_pago10_monto['und'][0]['value'] = $montop;

    // set id ducumentp rel
    $documentosRel = $xml->xpath('//p:DoctoRelacionado');
    foreach ($documentosRel as $key) {
        $node->field_pago_10_doctorel_id_dcto['und'][0]['value'] = $key['IdDocumento'];
    }
}

@goedecke
Copy link
Author

Buenas tardes, estaba accediendo a las facturas pero hoy me genero este error y no encuentro el porque surge, si alguien pudiera dar un poco de ayuda lo agradeceria.

This page contains the following errors:
error on line 28 at column 182: xmlns:schemaLocation: 'http://www.sat.gob.mx/registrofiscal http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd' is not a valid URI
Below is a rendering of the page up to the first error.

Hola viejo tu error es muy simple, tienes dos url en el codigo
'http://www.sat.gob.mx/registrofiscal
y
http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd

Elimina http://www.sat.gob.mx/registrofiscal y con eso debe de funcionar

@omarlv86
Copy link

Buenas tardes, estaba accediendo a las facturas pero hoy me genero este error y no encuentro el porque surge, si alguien pudiera dar un poco de ayuda lo agradeceria.
This page contains the following errors:
error on line 28 at column 182: xmlns:schemaLocation: 'http://www.sat.gob.mx/registrofiscal http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd' is not a valid URI
Below is a rendering of the page up to the first error.

Hola viejo tu error es muy simple, tienes dos url en el codigo
'http://www.sat.gob.mx/registrofiscal
y
http://www.sat.gob.mx/sitio_internet/cfd/cfdiregistrofiscal/cfdiregistrofiscal.xsd

Elimina http://www.sat.gob.mx/registrofiscal y con eso debe de funcionar

Hola hermano, acabo de realizar tu procedimiento y efectivamente eliminando esa linea se puede visualizar la factura, muchas gracias, sabes si hay alguna manera de leer la factura y poder eliminar esa url de manera programada? de ante mano muchas gracias

@chuyoman3000
Copy link

Buenas, tengo un problemita a la hora de tratar de agregar otro xpath un ejemplo seria este $xml->registerXPathNamespace('n', $ns['nomina12']);
No me detecta lo que vendria siendo la key de nomina12 apesar de que este aparezca en los xml, estoy empezando en esto y se me hace algo confuso, un saludo y muchas gracias

@rinconlb
Copy link

rinconlb commented Dec 2, 2021

Es posible leer varios archivos desde un input ? eso ya lo hice solamente con un archivo , necesito extraer información de cada uno de ellos para poder hacer un varios análisis de los ingresos y egresos supongo que es con un foreach o sera mejor subir los archivos a una carpeta y leerlos todos ?

@Genebi
Copy link

Genebi commented Jun 4, 2022

Me fue útil para solucionar un problema en especifico con un nodo en la versión 4.0, muchas gracias.

@andresaquino
Copy link

Me fue útil para solucionar un problema en especifico con un nodo en la versión 4.0, muchas gracias.

ejemplo..., cómo fue tu escenario?

@9014k
Copy link

9014k commented Aug 26, 2022

foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado') as $traslados) {
$t.= (string)$traslados['TipoFactor'] .'-' .(string)$traslados['TasaOCuota'].(string)$traslados['Importe'].'
';
}
Estoy tratando de leer los traslados dentro de impuestos pero tambien me trae los traslados de los conceptos a pesar de que especifico el xpath...

@RichyCapy
Copy link

Buenas! alguna actualizacion para que acepte la version 4 o mayor?

@pedrazacarlos007
Copy link

Esto me funciono para extraer el UUID del xml con la version 4.0

$uuid = (string) $xml->xpath('//tfd:TimbreFiscalDigital/@uuid')[0];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment