Search code examples
c#xmlserializationubl

UBL 2.1 XML deserialization C# list/array


I have trouble to deserialize part of invoice XML.

Tag cac:InvoiceLine is repeating.

I need help to make list/array for element InvoiceLine and do foreach loop and extract values from child elements.

This is example of XML file

<?xml version="1.0" encoding="utf-8"?>
<env:DocumentEnvelope xmlns:env="urn:eFaktura:MinFinrs:envelop:schema">
  <env:DocumentHeader>
    <env:SalesInvoiceId>4372797</env:SalesInvoiceId>
    <env:PurchaseInvoiceId>3935145</env:PurchaseInvoiceId>
    <env:DocumentId>3ff1e4d7-9025-4908-b05b-26094758bd7d</env:DocumentId>
    <env:CreationDate>2022-10-06</env:CreationDate>
    <env:SendingDate>2022-10-06</env:SendingDate>
    <env:DocumentPdf mimeCode="application/pdf"></env:DocumentPdf>
  </env:DocumentHeader>
  <env:DocumentBody>
    <Invoice xmlns:cec="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" 
    xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" 
    xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:sbt="http://mfin.gov.rs/srbdt/srbdtext" 
    xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
      <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:mfin.gov.rs:srbdt:2022#conformant#urn:mfin.gov.rs:srbdtext:2022</cbc:CustomizationID>
      <cbc:ID>24</cbc:ID>
      <cbc:IssueDate>2022-10-06</cbc:IssueDate>
      <cbc:DueDate>2022-10-20</cbc:DueDate>
      <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
      <cbc:Note>PROD</cbc:Note>
      <cbc:DocumentCurrencyCode>RSD</cbc:DocumentCurrencyCode>
      <cac:InvoicePeriod>
        <cbc:StartDate>2022-09-01</cbc:StartDate>
        <cbc:EndDate>2022-09-30</cbc:EndDate>
        <cbc:DescriptionCode>35</cbc:DescriptionCode>
      </cac:InvoicePeriod>
      <cac:AdditionalDocumentReference>
        <cbc:ID>24</cbc:ID>
        <cbc:DocumentType>PRILOG 1</cbc:DocumentType>
        <cac:Attachment>
          <cbc:EmbeddedDocumentBinaryObject mimeCode="application/pdf" encodingCode="base64" filename="24.pdf"></cbc:EmbeddedDocumentBinaryObject>
        </cac:Attachment>
      </cac:AdditionalDocumentReference>
      <cac:AccountingSupplierParty>
        <cac:Party>
          <cbc:EndpointID schemeID="9948">123456789</cbc:EndpointID>
          <cac:PartyIdentification>
            <cbc:ID>81906</cbc:ID>
          </cac:PartyIdentification>
          <cac:PartyName>
            <cbc:Name>SUPPLIER NAME</cbc:Name>
          </cac:PartyName>
          <cac:PostalAddress>
            <cbc:StreetName>SUPPLIER STREET</cbc:StreetName>
            <cbc:CityName>SUPPLIER TOWN</cbc:CityName>
            <cbc:PostalZone>SUPPLIER ZIP CODE</cbc:PostalZone>
            <cac:AddressLine>
              <cbc:Line>STREET,TOWN, ZIP</cbc:Line>
            </cac:AddressLine>
            <cac:Country>
              <cbc:IdentificationCode>RS</cbc:IdentificationCode>
            </cac:Country>
          </cac:PostalAddress>
          <cac:PartyTaxScheme>
            <cbc:CompanyID>RS123456789</cbc:CompanyID>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:PartyTaxScheme>
          <cac:PartyLegalEntity>
            <cbc:RegistrationName>SUPPLIER NAME</cbc:RegistrationName>
            <cbc:CompanyID>98765432</cbc:CompanyID>
          </cac:PartyLegalEntity>
          <cac:Contact>
            <cbc:ElectronicMail>[email protected]</cbc:ElectronicMail>
          </cac:Contact>
        </cac:Party>
      </cac:AccountingSupplierParty>
      <cac:AccountingCustomerParty>
        <cac:Party>
          <cbc:EndpointID schemeID="9948">111111111</cbc:EndpointID>
          <cac:PartyIdentification />
          <cac:PartyName>
            <cbc:Name>CUSTOMER NAME</cbc:Name>
          </cac:PartyName>
          <cac:PostalAddress>
            <cbc:StreetName>CUSTOMER street</cbc:StreetName>
            <cbc:CityName>CUSTOMER town</cbc:CityName>
            <cbc:PostalZone>CUSTOMER zip</cbc:PostalZone>
            <cac:AddressLine>
              <cbc:Line>street, town, zip</cbc:Line>
            </cac:AddressLine>
            <cac:Country>
              <cbc:IdentificationCode>RS</cbc:IdentificationCode>
            </cac:Country>
          </cac:PostalAddress>
          <cac:PartyTaxScheme>
            <cbc:CompanyID>RS111111111</cbc:CompanyID>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:PartyTaxScheme>
          <cac:PartyLegalEntity>
            <cbc:RegistrationName>CUSTOMER NAME</cbc:RegistrationName>
            <cbc:CompanyID>22222222</cbc:CompanyID>
          </cac:PartyLegalEntity>
          <cac:Contact>
            <cbc:ElectronicMail>[email protected]</cbc:ElectronicMail>
          </cac:Contact>
        </cac:Party>
      </cac:AccountingCustomerParty>
      <cac:Delivery>
        <cbc:ActualDeliveryDate>2022-09-30</cbc:ActualDeliveryDate>
      </cac:Delivery>
      <cac:PaymentMeans>
        <cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
        <cbc:PaymentID>223985-2209</cbc:PaymentID>
        <cac:PayeeFinancialAccount>
          <cbc:ID>100-000000-11</cbc:ID>
        </cac:PayeeFinancialAccount>
      </cac:PaymentMeans>
      <cac:TaxTotal>
        <cbc:TaxAmount currencyID="RSD">967.2</cbc:TaxAmount>
        <cac:TaxSubtotal>
          <cbc:TaxableAmount currencyID="RSD">9464</cbc:TaxableAmount>
          <cbc:TaxAmount currencyID="RSD">946.4</cbc:TaxAmount>
          <cac:TaxCategory>
            <cbc:ID>S</cbc:ID>
            <cbc:Percent>10</cbc:Percent>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:TaxCategory>
        </cac:TaxSubtotal>
        <cac:TaxSubtotal>
          <cbc:TaxableAmount currencyID="RSD">104</cbc:TaxableAmount>
          <cbc:TaxAmount currencyID="RSD">20.8</cbc:TaxAmount>
          <cac:TaxCategory>
            <cbc:ID>S</cbc:ID>
            <cbc:Percent>20</cbc:Percent>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:TaxCategory>
        </cac:TaxSubtotal>
      </cac:TaxTotal>
      <cac:LegalMonetaryTotal>
        <cbc:LineExtensionAmount currencyID="RSD">9568</cbc:LineExtensionAmount>
        <cbc:TaxExclusiveAmount currencyID="RSD">9568</cbc:TaxExclusiveAmount>
        <cbc:TaxInclusiveAmount currencyID="RSD">10535.2</cbc:TaxInclusiveAmount>
        <cbc:AllowanceTotalAmount currencyID="RSD">0</cbc:AllowanceTotalAmount>
        <cbc:PrepaidAmount currencyID="RSD">0</cbc:PrepaidAmount>
        <cbc:PayableAmount currencyID="RSD">10535.2</cbc:PayableAmount>
      </cac:LegalMonetaryTotal>
      <cac:InvoiceLine>
        <cbc:ID>1</cbc:ID>
        <cbc:InvoicedQuantity unitCode="MTQ">65</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="RSD">3971.5</cbc:LineExtensionAmount>
        <cac:Item>
          <cbc:Name>line 1 description</cbc:Name>
          <cac:SellersItemIdentification>
            <cbc:ID>1</cbc:ID>
          </cac:SellersItemIdentification>
          <cac:ClassifiedTaxCategory>
            <cbc:ID>S</cbc:ID>
            <cbc:Percent>10</cbc:Percent>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
          <cbc:PriceAmount currencyID="RSD">61.1</cbc:PriceAmount>
          <cbc:BaseQuantity unitCode="MTQ">1</cbc:BaseQuantity>
        </cac:Price>
      </cac:InvoiceLine>
      <cac:InvoiceLine>
        <cbc:ID>2</cbc:ID>
        <cbc:InvoicedQuantity unitCode="MTQ">65</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="RSD">2535</cbc:LineExtensionAmount>
        <cac:Item>
          <cbc:Name>line 2 description</cbc:Name>
          <cac:SellersItemIdentification>
            <cbc:ID>3</cbc:ID>
          </cac:SellersItemIdentification>
          <cac:ClassifiedTaxCategory>
            <cbc:ID>S</cbc:ID>
            <cbc:Percent>10</cbc:Percent>
            <cac:TaxScheme>
              <cbc:ID>VAT</cbc:ID>
            </cac:TaxScheme>
          </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
          <cbc:PriceAmount currencyID="RSD">39</cbc:PriceAmount>
          <cbc:BaseQuantity unitCode="MTQ">1</cbc:BaseQuantity>
        </cac:Price>
      </cac:InvoiceLine>
    </Invoice>
  </env:DocumentBody>
</env:DocumentEnvelope>

I generated classes in Visual Studio

1 Copy XML data to the clipboard

2 In VS, Edit > Paste Special > "Paste Xml as classes"

This is my code for get all other values from xml.


static void Main(string[] args)
        {
            XmlSerializer serializer =
            new XmlSerializer(typeof(XmlStr.DocumentEnvelope));
            // Declare an object variable of the type to be deserialized.
            XmlStr.DocumentEnvelope envelope;
            using (Stream reader = new FileStream(@"C:\\Users\\Desktop\\24.xml", FileMode.Open))
            {
                // Call the Deserialize method to restore the object's state.
                envelope = (XmlStr.DocumentEnvelope)serializer.Deserialize(reader);
            }
            // Write out the properties of the object.
            Console.WriteLine(envelope.DocumentBody.Invoice.ID.Value);
        }

When I use code below, I get error

System.InvalidOperationException: 'There is an error in XML document (2, 2).'

Inner Exception

InvalidOperationException: <DocumentEnvelope xmlns='urn:eFaktura:MinFinrs:envelop:schema'> was not expected.

XmlSerializer serializer =
           new XmlSerializer(typeof(zaglavlje.InvoiceLine[]));
            // Declare an object variable of the type to be deserialized.
            zaglavlje.InvoiceLine[] envelope;
            using (Stream reader = new FileStream(@"C:\\Users\\Desktop\\24.xml", FileMode.Open))
            {
                // Call the Deserialize method to restore the object's state.
                envelope = (zaglavlje.InvoiceLine[])serializer.Deserialize(reader);
            }
            // Write out the properties of the object.
            //Console.WriteLine(envelope);

I would like to get InvoiceLine like this

First loop
ID: 1
InvoicedQuantity: 65
LineExtensionAmount: 3971.5
Item.Name: line 1 description
Price.PriceAmount: 61.1
BaseQuantity: 1
Second loop
ID: 2
InvoicedQuantity: 65
LineExtensionAmount: 2535
Item.Name: line 2 description
Price.PriceAmount: 39
BaseQuantity: 1


Solution

  • Use a customer serialization :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Xml.Schema;
    using System.Xml.Linq;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
                XmlReader reader = XmlReader.Create(FILENAME);
                XmlSerializer serializer = new XmlSerializer(typeof(DocumentEnvelope));
                DocumentEnvelope envelope = (DocumentEnvelope) serializer.Deserialize(reader);
            }
        }
        [XmlRoot(Namespace = "urn:eFaktura:MinFinrs:envelop:schema")]
        public class DocumentEnvelope
        {
            public DocumentBody DocumentBody { get; set; }
        }
        public class DocumentBody
        {
            [XmlElement(Namespace = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2")]
            public Invoice Invoice { get; set; }
        }
        public class Invoice
        {
            [XmlElement(ElementName = "InvoiceLine", Namespace = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2")]
            public List<InvoiceLine> InvoiceLine { get; set; }
        }
        public class InvoiceLine : IXmlSerializable
        {
            public int InvoicedQuantity { get; set; }
            public decimal LineExtensionAmount { get; set; }
            public string ItemName { get; set; }
            public decimal PriceAmount { get; set; }
            public decimal  BaseQuantity { get; set; }
    
            public void WriteXml(XmlWriter writer)
            {
                
            }
    
            public void ReadXml(XmlReader reader)
            {
                XElement invoiceLine = (XElement)XElement.ReadFrom(reader);
                XNamespace ns = invoiceLine.Name.Namespace;
    
    
                InvoicedQuantity = (int)invoiceLine.Elements().Where(x => x.Name.LocalName == "InvoicedQuantity").First();
                LineExtensionAmount = (decimal)invoiceLine.Elements().Where(x => x.Name.LocalName == "LineExtensionAmount").First();
                ItemName = (string)invoiceLine.Descendants().Where(x => x.Name.LocalName == "Name").FirstOrDefault();
                PriceAmount = (decimal)invoiceLine.Descendants().Where(x => x.Name.LocalName == "PriceAmount").FirstOrDefault();
                BaseQuantity = (decimal )invoiceLine.Descendants().Where(x => x.Name.LocalName == "BaseQuantity").FirstOrDefault();
            }
            public XmlSchema GetSchema()
            {
                return (null);
            }
    
        }
    
    
    }