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

isabtogumon
Member Posts: 50
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:
The uploaded XML file structure is as follows:
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.
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.
0
Best 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.1
Answers
-
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.0 -
@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.0 -
Hello @isabtogumon,
How did you declare the variable TxtVar ?
Must be like this:var TxtVar: Text;
Regards0 -
Hello @ftornero, I did declare TxtVar as a text type without specifying a length.0
-
0
-
And what version of Business Central are you using.0
-
Hello @ftornero,
This is part of the code; I declared the variable xmlDoc as an XMLDocument.
These are the data from app.json
Regards.
0 -
Hello @isabtogumon,
And you get the error in the line circled in red in the image below ?
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.0 -
Hello @ftornero,
The content of the .XML file is correct; it does not trigger the suggested error.
Regards.
0 -
Hello @isabtogumon,
In the image looks like the error is in the linetmpXMLBuffer.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;
Regards0 -
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; }
Regards0 -
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.0 -
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.1 -
Hello @ftornero,
I adapted it to my code, and after conducting some tests, it met the requirement. I appreciate your support.
Regards.0 -
Take, for example this XML file
I use the nodes to read data from an XML file and import the data into the Customer table and the Ship-To-Address Table
The code looks as follows
procedure ReadXML(InStr: InStream)
var
Customer: Record Customer;
ShiptoAddress: Record "Ship-to Address";
xmlDoc: XmlDocument;
CustomerTab: XmlElement;
NodeList: XmlNodeList;
NodeCustomerList: XmlNodeList;
Node: XmlNode;
XmlCustomerNode: XmlNode;
XmlShipToNode: XmlNode;
XmlCustomerElement: XmlElement;
XmlShipToElement: XmlElement;
begin
if XmlDocument.ReadFrom(InStr, xmlDoc) then begin
//Find the root element
if xmlDoc.GetRoot(CustomerTab) then
NodeList := CustomerTab.GetChildElements();
foreach Node in NodeList do begin
XmlCustomerElement := Node.AsXmlElement();
NodeCustomerList := XmlCustomerElement.GetChildElements();
foreach XmlCustomerNode in NodeCustomerList do begin
case XmlCustomerNode.AsXmlElement().Name of
'No.':
Customer.Validate(Customer."No.", XmlCustomerNode.AsXmlElement().InnerText);
'Name':
Customer.Validate(Customer.Name, XmlCustomerNode.AsXmlElement().InnerText);
'Address':
Customer.Validate(Customer.Address, XmlCustomerNode.AsXmlElement().InnerText);
'ShipToAddress':
begin
XmlShipToElement := XmlCustomerNode.AsXmlElement();
ShiptoAddress.Init();
ShiptoAddress.Validate("Customer No.", Customer."No.");
foreach XmlShipToNode in XmlShipToElement.GetChildElements() do begin
case XmlShipToNode.AsXmlElement().Name of
'Code':
ShiptoAddress.Code := XmlShipToNode.AsXmlElement().InnerText;
'ShipName':
ShiptoAddress.Name := XmlShipToNode.AsXmlElement().InnerText;
'PostCode':
ShiptoAddress.Validate("Post Code", XmlShipToNode.AsXmlElement().InnerText);
'Country':
ShiptoAddress.Validate("Country/Region Code", XmlShipToNode.AsXmlElement().InnerText);
end;
if not ShiptoAddress.Insert() then
ShiptoAddress.Modify();
end;
end;
end;
if not Customer.Insert() then
Customer.Modify();
end;
end;
end;
end;
0
Categories
- All Categories
- 73 General
- 73 Announcements
- 66.6K Microsoft Dynamics NAV
- 18.7K NAV Three Tier
- 38.4K NAV/Navision Classic Client
- 3.6K Navision Attain
- 2.4K Navision Financials
- 116 Navision DOS
- 851 Navision e-Commerce
- 1K NAV Tips & Tricks
- 772 NAV Dutch speaking only
- 617 NAV Courses, Exams & Certification
- 2K Microsoft Dynamics-Other
- 1.5K Dynamics AX
- 320 Dynamics CRM
- 111 Dynamics GP
- 10 Dynamics SL
- 1.5K Other
- 990 SQL General
- 383 SQL Performance
- 34 SQL Tips & Tricks
- 35 Design Patterns (General & Best Practices)
- 1 Architectural Patterns
- 10 Design Patterns
- 5 Implementation Patterns
- 53 3rd Party Products, Services & Events
- 1.6K General
- 1.1K General Chat
- 1.6K Website
- 83 Testing
- 1.2K Download section
- 23 How Tos section
- 252 Feedback
- 12 NAV TechDays 2013 Sessions
- 13 NAV TechDays 2012 Sessions