How to Access XML Nodes and Retrieve Attribute Values After Loading XML into InStream Variable?

isabtogumonisabtogumon Member Posts: 49
In the context of an AL extension for Dynamics 365 Business Central, I have a scenario where I need to select an XML file using the code provided below and then parse its nodes to extract attribute values. Here's the code snippet for loading the XML into an InStream variable:
if UploadIntoStream(DialogTitle, '', 'XML file(*.xml)|*.xml', TempFileName, InStrm) then
    XmlDocument.ReadFrom(InStrm, xmlDoc)
else
    Error('It is not an XML file');

The uploaded XML file structure is as follows:
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Certificado="FICTITIOUS_CERTIFICATE" PaymentConditions="30" Export="01" Date="2023-08-15T10:30:45" Folio="12345" PaymentMethod="01" ExpeditionLocation="75000" PaymentType="PUE" Currency="USD" CertificateNumber="SAT_CERTIFICATE" Seal="FICTITIOUS_DIGITAL_SEAL" Series="B" Subtotal="500.00" ExchangeRate="1.00" DocumentType="I" Total="575.00" Version="3.3" xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv33.xsd">
  <cfdi:Issuer Name="FICTITIOUS_ISSUER" FiscalRegime="601" Rfc="FICTITIOUS_ISSUER_RFC" />
  <cfdi:Receiver FiscalAddress="76000" Name="FICTITIOUS_RECEIVER" FiscalRegimeReceiver="612" Rfc="FICTITIOUS_RECEIVER_RFC" CfdiUsage="G01" />
  <cfdi:Concepts>
    <cfdi:Concept Quantity="10" ProdServKey="01010101" UnitKey="H87" Description="Sample Product" Amount="500.00" IdentificationNumber="PROD-001" Unit="Piece" UnitValue="50.00">
      <cfdi:Taxes>
        <cfdi:Transfers>
          <cfdi:Transfer Base="500.00" Amount="80.00" Tax="002" RateOrQuota="0.160000" TaxType="Rate" />
        </cfdi:Transfers>
      </cfdi:Taxes>
      <cfdi:CustomsInformation PedimentNumber="15 12345 67890 1234567" />
    </cfdi:Concept>
  </cfdi:Concepts>
  <cfdi:Taxes TotalTransferredTaxes="80.00">
    <cfdi:Transfers>
      <cfdi:Transfer Base="500.00" Amount="80.00" Tax="002" RateOrQuota="0.160000" TaxType="Rate" />
    </cfdi:Transfers>
  </cfdi:Taxes>
  <cfdi:Complement>
    <tfd:DigitalTaxStamp xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital" TimbradoDate="2023-08-15T10:35:22" SATCertificateNumber="SAT_CERTIFICATE" CertifyingEntityRfc="SAT_RFC" CFDigitalSeal="FICTITIOUS_DIGITAL_CFD_SEAL" SATSeal="FICTITIOUS_DIGITAL_SAT_SEAL" UUID="9b824153-a667-4a46-829d" Version="1.1" xsi:schemaLocation="http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd" />
  </cfdi:Complement>
</cfdi:Comprobante>

I would like to know how to:

1. Access specific nodes within this XML structure.
2. Retrieve the values of their attributes using AL in Business Central.
3. Create a loop to handle cases where the XML contains multiple "Concept" nodes, extracting attribute values from each of them.

Best Answer

  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @isabtogumon,

    To retrieve the attributes with your ladst code you can use the code that I posted before, inside the foreach change this:
             Message('%1', xmlNodRoot.AsXmlAttribute());
    

    For this:
              xmlAttrCol := xmlNodRoot.AsXmlElement().Attributes();
              for i := 1 to xmlAttrCol.Count do begin
                    if xmlAttrCol.Get(i, xmlAttr) then
                           Message('%1 = %2', xmlAttr.Name, xmlAttr.Value);
              end;
    

    Regards.

Answers

  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    You could use the "XML Buffer" table to do that, something like this:
    var
    tempXMLBuffer: Record "XML Buffer" temporary;
    TxtVar: Text;
    // xmlDoc: xmlDocument; //The XML you get from the file
    
    xmlDoc.WriteTo(TxtVar)
    tempXMLBuffer.LoadFromText(TxtVar);
    tempXMLBuffer.SetRange(tempXMLBuffer.Type, tempXMLBuffer.Type::Element); // Type is " ",Element,Attribute,"Processing Instruction"
    if tempXMLBuffer.FindSet() then
                    repeat
                        case tempXMLBuffer.Name of  'Concept':
                       ...........
                    until tempXMLBuffer.Next() = 0;
    

    Regards.
  • isabtogumonisabtogumon Member Posts: 49
    @ftornero thanks for replying

    I found an error in the following line of code:
    xmlDoc.WriteTo(TxtVar)
    The error is that the variable TxtVar cannot store more than 250 characters.
  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    How did you declare the variable TxtVar ?

    Must be like this:
    var
    TxtVar: Text;
    

    Regards
  • isabtogumonisabtogumon Member Posts: 49
    Hello @ftornero, I did declare TxtVar as a text type without specifying a length.
  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    Ok, then what type of variable xmlDoc is ?

    Regards.
  • ftorneroftornero Member Posts: 524
    And what version of Business Central are you using.
  • isabtogumonisabtogumon Member Posts: 49
    Hello @ftornero,

    This is part of the code; I declared the variable xmlDoc as an XMLDocument.

    94jta3k4s38d.png

    These are the data from app.json

    3m244g4x0lkk.png

    Regards.
  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    And you get the error in the line circled in red in the image below ?

    l02snnqryozh.png

    That line have not the limitation of 250 chars, the function copy the XML to a Text, and I use it in my code a lot.

    Maybe you cloud check if the XML uploaded is correct.
    if not XmlDocument.ReadFrom(InStrm, xmlDoc) then
       Error('Error loading the XML file');
    

    Regards.
  • isabtogumonisabtogumon Member Posts: 49
    Hello @ftornero,

    The content of the .XML file is correct; it does not trigger the suggested error.

    tn0etao9tt6e.png

    Regards.
  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    In the image looks like the error is in the line
    tmpXMLBuffer.LoadFromText(txtVar);
    

    If that is the case some of the table fields that the function filled may exceed 250 chars.

    So you should need to process the XML on your own, like this:
    var
            xmlNodList: XmlNodeList;
            xmlNod: XmlNode;
            xmlAttrCol: XmlAttributeCollection;
            xmlAttr: XmlAttribute;
            i: Integer;
    ........................
            if xmlDoc.SelectNodes('//*[local-name()="Concept"]', xmlNodList) then begin
                  foreach xmlNod in xmlNodList do begin
                        xmlAttrCol := xmlNod.AsXmlElement().Attributes();
                        for i := 1 to xmlAttrCol.Count do begin
                             if xmlAttrCol.Get(i, xmlAttr) then
                                 Message('%1 = %2', xmlAttr.Name, xmlAttr.Value);
                        end;
                    end;
             end;
    

    Regards
  • ftorneroftornero Member Posts: 524
    Hello @isabtogumon,

    In the meantime, I tested the XML file that you posted and works fine with then XML Buffer table, this is the code to get the attributes of 'Concept':
    pageextension 50102 CustomerListExt extends "Customer List"
    {
        actions
        {
            addafter("&Customer")
            {
                action(ReadXMLBuffer)
                {
                    ApplicationArea = All;
                    Image = XMLFile;
                    Promoted = true;
                    PromotedCategory = Process;
                    PromotedIsBig = true;
                    PromotedOnly = true;
                    Caption = 'ReadXMLBuffer';
                    trigger OnAction()
                    begin
                        ReadXmlTagAttributes('Concept');
                    end;
                }
            }
        }
    
        local procedure ReadXmlTagAttributes(xmlTag: Text)
        var
            Instr: InStream;
            fileName: Text;
            tmpXMLBuffer: Record "XML Buffer" temporary;
            EntryNo: Integer;
    
        begin
            if UploadIntoStream('Choose XML', '', 'XML Files (*.XML)|*.XML', fileName, Instr) then begin
                tmpXMLBuffer.LoadFromStream(InStr);
                tmpXMLBuffer.SetRange(tmpXMLBuffer.Name, xmlTag);
                if tmpXMLBuffer.FindFirst() then begin
                    EntryNo := tmpXMLBuffer."Entry No.";
                    tmpXMLBuffer.SetRange(tmpXMLBuffer.Name);
                    tmpXMLBuffer.SetRange(tmpXMLBuffer."Parent Entry No.", EntryNo);
                    tmpXMLBuffer.SetRange(tmpXMLBuffer."Type", tmpXMLBuffer.type::Attribute);
                    if tmpXMLBuffer.FindSet() then
                        repeat
                            Message('%1 = %2', tmpXMLBuffer.Name, tmpXMLBuffer.Value);
                        until tmpXMLBuffer.Next() = 0;
                end;
            end;
        end;
    }
    

    Regards
  • isabtogumonisabtogumon Member Posts: 49
    Hello @ftornero,

    In the last example, I encountered an error on the line "tmpXMLBuffer.SetRange(tmpXMLBuffer.Name, xmlTag);" this error occurred when using XML files with larger content.

    I used a bit of your previous example, however, I haven't been able to retrieve attribute values, but I can access the 'Concept' node. I've made adjustments to my code:
    if UploadIntoStream(DialogTitle, '', 'XML file(*.xml)|*.xml', TempFileName, InStrm) then begin
       xmlnsMgr.AddNamespace('cfdi', 'http://www.sat.gob.mx/cfd/4');
       XmlDocument.ReadFrom(InStrm, xmlDoc);
       if xmlDoc.SelectNodes('//cfdi:Concept', xmlnsMgr, xmlNodList) then
          foreach xmlNodRoot in xmlNodList do begin
             Message('%1', xmlNodRoot.AsXmlAttribute());
          end;
    end
    else begin
       Error('It is not an XML file');
    end;
    

    Regards.
  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @isabtogumon,

    To retrieve the attributes with your ladst code you can use the code that I posted before, inside the foreach change this:
             Message('%1', xmlNodRoot.AsXmlAttribute());
    

    For this:
              xmlAttrCol := xmlNodRoot.AsXmlElement().Attributes();
              for i := 1 to xmlAttrCol.Count do begin
                    if xmlAttrCol.Get(i, xmlAttr) then
                           Message('%1 = %2', xmlAttr.Name, xmlAttr.Value);
              end;
    

    Regards.
  • isabtogumonisabtogumon Member Posts: 49
    Hello @ftornero,

    I adapted it to my code, and after conducting some tests, it met the requirement. I appreciate your support.

    Regards.
Sign In or Register to comment.