CryptoSys Home > FirmaSAT > Using GetXmlAttribute to extract attribute values from multiple Retencion elements

Using GetXmlAttribute to extract attribute values from multiple Retencion elements


A question from a user.
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>

1. The Simple Way

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

2. Using simplified Xpath expressions

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.

Counting the number of elements

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

C# code

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!.

Contact us

To contact us or comment on this page, please send us a message.

[Go to top]

This page first published 6 October 2019. Last updated 15 August 2025.