I am trying to extract the Importe attribute values for the Retenciones elements in the following CFDi v3.3 document. There are two Concepto elements each with two Retencion elements. I expect the values 400.00, 426.67, 1000.00 and 1066.67 in order.
How do I do this?
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" Version="3.3" ... > <!-- [cut] --> <cfdi:Conceptos> <cfdi:Concepto ClaveProdServ="80131502" Cantidad="1" ClaveUnidad="H87" Descripcion="RENTA DEL DEPARTAMENTO 1 DE LA CALLE DE AYUNTAMIENTO 3" ValorUnitario="4000.00" Importe="4000.00"> <cfdi:Impuestos> <cfdi:Traslados> <cfdi:Traslado Base="4000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="640.00" /> </cfdi:Traslados> <cfdi:Retenciones> <cfdi:Retencion Base="4000.00" Impuesto="001" TipoFactor="Tasa" TasaOCuota="0.100000" Importe="400.00" /> <cfdi:Retencion Base="4000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.106667" Importe="426.67" /> </cfdi:Retenciones> </cfdi:Impuestos> <cfdi:CuentaPredial Numero="002043080007" /> </cfdi:Concepto> <cfdi:Concepto ClaveProdServ="80131502" Cantidad="1" ClaveUnidad="H87" Descripcion="RENTA DEL DEPARTAMENTO 3-A-BIS DE LA CALLE DE AYUNTAMIENTO 3" ValorUnitario="10000.00" Importe="10000.00"> <cfdi:Impuestos> <cfdi:Traslados> <cfdi:Traslado Base="10000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="1600.00" /> </cfdi:Traslados> <cfdi:Retenciones> <cfdi:Retencion Base="10000.00" Impuesto="001" TipoFactor="Tasa" TasaOCuota="0.100000" Importe="1000.00" /> <cfdi:Retencion Base="10000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.106667" Importe="1066.67" /> </cfdi:Retenciones> </cfdi:Impuestos> <cfdi:CuentaPredial Numero="002043080007" /> </cfdi:Concepto> </cfdi:Conceptos> <!-- [cut] --> </cfdi:Comprobante>
The simple way using the FirmaSAT command-line utility. These find the 1st, 2nd, 3rd and 4th occurrences respectively.
> firmasat attribute -a Importe -e Retencion DOC.xml 400.00 > firmasat attribute -a Importe -e Retencion[2] DOC.xml 426.67 > firmasat attribute -a Importe -e Retencion[3] DOC.xml 1000.00 > firmasat attribute -a Importe -e Retencion[4] DOC.xml 1066.67
These are described in the manual: Simplified XPath expressions for the elementName parameter
> firmasat attribute -a Importe -e "//Concepto[1]//Retencion[1]" DOC.xml 400.00 > firmasat attribute -a Importe -e "//Concepto[1]//Retencion[2]" DOC.xml 426.67 > firmasat attribute -a Importe -e "//Concepto[2]//Retencion[1]" DOC.xml 1000.00 > firmasat attribute -a Importe -e "//Concepto[2]//Retencion[2]" DOC.xml 1066.67
Where, for example, "//Concepto[2]//Retencion[1]"
selects the first Retencion
element found anywhere inside the second Concepto
element found anywhere in the document.
To count the number of elements, increment the predicate [N]
for N=1,2,...
until !NO MATCH!
is found.
> firmasat attribute -a "" -e "//Concepto[1]" DOC.xml cfdi:Concepto > firmasat attribute -a "" -e "//Concepto[2]" DOC.xml cfdi:Concepto > firmasat attribute -a "" -e "//Concepto[3]" DOC.xml Error code -21: Match not found/No se pudo encontrar datos coincidente (NO_MATCH_ERROR): !NO MATCH! Attribute '//Concepto[3]/@' not found/Atributo no encontrado > firmasat attribute -a "" -e "//Concepto[1]//Retencion[1]" DOC.xml cfdi:Retencion > firmasat attribute -a "" -e "//Concepto[1]//Retencion[2]" DOC.xml cfdi:Retencion > firmasat attribute -a "" -e "//Concepto[1]//Retencion[3]" DOC.xml Error code -21: Match not found/No se pudo encontrar datos coincidente (NO_MATCH_ERROR): !NO MATCH! Attribute '//Concepto[1]//Retencion[3]/@' not found/Atributo no encontrado
string fname = "DOC.xml"; string attrName = "Importe"; int i, j; string s, elemName; // First check we have a valid doc with at least one Concepto element s = Sat.GetXmlAttribute(fname, "", "Concepto"); if (s.Length == 0 || s == Sat.XmlNoMatch()) { Console.WriteLine("FILE: {0}: INVALID DOCUMENT: {1}", fname, Sat.LastError()); return; } // For each Concepto element, get each Retencion element for (i = 1; ; i++) { elemName = string.Format("//Concepto[{0}]", i); s = Sat.GetXmlAttribute(fname, "", elemName); if (s.Length == 0 || s == Sat.XmlNoMatch()) break; for (j = 1; ; j++) { // Extract the "Importe" attribute value from each Retencion element elemName = string.Format("//Concepto[{0}]//Retencion[{1}]", i, j); s = Sat.GetXmlAttribute(fname, attrName, elemName); if ((s.Length == 0 && Sat.LastError().Length > 0) || s == Sat.XmlNoMatch()) break; // OK, we got an attribute value we want... Console.WriteLine("{0}/@{1}={2}", elemName, attrName, s); } }
Output:
//Concepto[1]//Retencion[1]/@Importe=400.00 //Concepto[1]//Retencion[2]/@Importe=426.67 //Concepto[2]//Retencion[1]/@Importe=1000.00 //Concepto[2]//Retencion[2]/@Importe=1066.67
Note that Sat.GetXmlAttribute()
returns the empty string ""
either if there is an error (file not found, invalid query)
or if the attribute value actually is the empty string (e.g. foo=""
).
In the former case, Sat.LastError()
will contain a nonzero-length error string. In the latter case it will be empty.
If the XML file is valid and the query is valid but the element could not be found, then the string given by Sat.XmlNoMatch()
will be returned.
By default, this is !NO MATCH!
.
To contact us or comment on this page, please send us a message.
This page first published 6 October 2019. Last updated 15 August 2025.