Home > Enterprise >  How to find nodes in an XML
How to find nodes in an XML

Time:12-15

I have loaded the following XML file using xml.Load("myfile.xml"); where xml is of type XmlDocument:

<?xml version="1.0" encoding="ISO-8859-1"?>
    <DTE xmlns="http://www.sii.cl/SiiDte" version="1.0">
        <Documento ID="E000000005T033F0114525415">
            <Encabezado>
                <IdDoc>
                    <TipoDTE>33</TipoDTE>
                    <Folio>114525415</Folio>
                    <FchEmis>2021-11-02</FchEmis>
                    <FmaPago>1</FmaPago>
                    <FchVenc>2021-11-02</FchVenc>
                </IdDoc>
            </Encabezado>
        </Documento>
    </DTE>

How can I get Folionode?

I have tried with:

  xml.DocumentElement.SelectSingleNode("/DTE/Documento/Encabezado/IdDoc/Folio");
  xml.DocumentElement.SelectNodes("DTE/Documento/Encabezado/IdDoc/Folio")
  xml.DocumentElement.SelectSingleNode("//DTE/Documento/Encabezado/IdDoc/Folio");
  xml.DocumentElement.SelectSingleNode("/Documento/Encabezado/IdDoc/Folio");
  xml.DocumentElement.SelectSingleNode("Documento/Encabezado/IdDoc/Folio");
  xml.DocumentElement.SelectSingleNode("/Encabezado/IdDoc/Folio");
  xml.DocumentElement.SelectNodes("/DTE/Documento/Encabezado/IdDoc/Folio")

when I debug xml.DocumentElement I see that the element is DTE so I think xml.DocumentElement.SelectSingleNode("Documento/Encabezado/IdDoc/Folio") should do it.

When I get xml.DocumentElement.FirstChild I get Documento node.

With xml.DocumentElement.FirstChild.FirstChild I get Encabezado node.

With xml.DocumentElement.FirstChild.FirstChild.FirstChild I get IdDoc node.

If I use xml.DocumentElement.FirstChild.FirstChild.FirstChild.SelectSingleNode("Folio"), returned value is null.

If I use xml.DocumentElement.FirstChild.FirstChild.FirstChild.ChildNodes, I get the 5 elements.

Then I could use xml.DocumentElement.FirstChild.FirstChild.FirstChild.ChildNodes[1].InnerText to get Folio value.

I can traverse the XML but, how can I do it to get the element directly?

Thanks Jaime

CodePudding user response:

It is better to use LINQ to XML API for your task. It is available in the .Net Framework since 2007.

The provided XML has a default namespace. It needs to be declared and used, otherwise it is imposable to find any XML element.

c#

void Main()
{
    const string filename = @"e:\Temp\jstuardo.xml";

    XDocument xdoc = XDocument.Load(filename);
    XNamespace ns = xdoc.Root.GetDefaultNamespace();

    string Folio = xdoc.Descendants(ns   "Folio")
      .FirstOrDefault()?.Value;

    Console.WriteLine("Folio='{0}'", Folio);
}

Output

Folio='114525415'

CodePudding user response:

You can try to use the Xpath like below:

XmlDocument doc = new XmlDocument();
doc.Load("myfile.xml");
var node= doc.SelectSingleNode("Documento/Encabezado/IdDoc/[Folio='"114525415"']");

CodePudding user response:

There are few ways to make things up with your issue.

So, we have our XML:

const string MyXML = @"<?xml version=""1.0"" encoding=""ISO-8859-1""?>
                       <DTE xmlns=""http://www.sii.cl/SiiDte"" version=""1.0"">
                           <Documento ID=""E000000005T033F0114525415"">
                               <Encabezado>
                                   <IdDoc>
                                       <TipoDTE>33</TipoDTE>
                                       <Folio>114525415</Folio>
                                       <FchEmis>2021-11-02</FchEmis>
                                       <FmaPago>1</FmaPago>
                                       <FchVenc>2021-11-02</FchVenc>
                                   </IdDoc>
                               </Encabezado>
                           </Documento>
                       </DTE>";

And we need to get Folio node (exactly node, not just value). We can use:


XmlNamespaceManager:

to find descendant node(s) through XML namespace (xmlns) alias in XPath:

// Creating our XmlDocument instance
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(MyXML);

// Initializing XmlNamespaceManager and providing our xmlns with 'SiiDte' alias:
var xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
xmlNamespaceManager.AddNamespace("SiiDte", "http://www.sii.cl/SiiDte");

// Declaring our simple shiny XPath:
var xPath = "descendant::SiiDte:Folio";

// If we need single (first) element:
var folio = xmlDocument.DocumentElement.SelectSingleNode(xPath, xmlNamespaceManager);
// If we need all Folios:
var folios = xmlDocument.DocumentElement.SelectNodes(xPath, xmlNamespaceManager);

XDocument and its Descendants:

from System.Xml.Linq namespace and its XDocument class, to find descendant node(s) just by their tag name <Folio>:

// If we need single (first) element:
var folio = XDocument.Parse(MyXML)
                     .Descendants()
                     .FirstOrDefault(x => x.Name.LocalName == "Folio");
                     // Add System.Linq using to access FirstOrDefault extension method
// If we need all Folios - just replacing FirstOrDefault with Where extension method:
var folios = XDocument.Parse(MyXML)
                      .Descendants()
                      .Where(x => x.Name.LocalName == "Folio"); // and .ToList() if you need
// Or we can use also our XML namespace to filter Descendants:
var ns = (XNamespace)"http://www.sii.cl/SiiDte";
var folios = XDocument.Parse(MyXML).Descendants(ns   "Folio");

Deserialization:

to operate not with XML or nodes, but with some class (e.g. DTE), which represents your XML schema. I'm not sure that I totally understand your XML structure, but anyway as example it could be used.

So we create our classes, which are representation of our XML:

[Serializable, XmlRoot(ElementName = nameof(DTE), Namespace = "http://www.sii.cl/SiiDte")]
public class DTE
{
    [XmlAttribute("version")]
    public string Version { get; set; }

    [XmlElement(nameof(Documento))]
    public List<Documento> Documentacion { get; set; }
}
[Serializable]
public class Documento
{
    [XmlAttribute(nameof(ID))]
    public string ID { get; set; }
    [XmlElement(nameof(Encabezado))]
    public Encabezado Encabezado { get; set; }
}
[Serializable]
public class Encabezado
{
    [XmlElement(nameof(IdDoc))]
    public IDDoc IdDoc { get; set; }
}
[Serializable]
public class IDDoc
{
    [XmlElement(nameof(TipoDTE))]
    public int TipoDTE { get; set; }
    [XmlElement(nameof(Folio))]
    public long Folio { get; set; }
    [XmlElement(nameof(FchEmis))]
    public DateTime FchEmis { get; set; }
    [XmlElement(nameof(FmaPago))]
    public int FmaPago { get; set; }
    [XmlElement(nameof(FchVenc))]
    public DateTime FchVenc { get; set; }
}

Now we can easily create our DTE object with XmlSerializer class and its Deserialize method:

// Declaring our DTE object
var dte = (DTE)null;​
using (var reader = new StringReader(MyXML))
{
    dte = (DTE)new XmlSerializer(typeof(DTE)).Deserialize(reader);
}

Now we can get Folio as property of IdDoc class, which is property of Encabezado class, which in a turn is property of Documento class. Keeping in mind possible null result turns us to use, for example, null-propagation:

var folio = dte?.Documentacion.FirstOrDefault()?.Encabezado?.IdDoc?.Folio;

As Documentacion is a List<Documento> - we use again FirstOrDefault (also may be used ElementAtOrDefault(0)) to "imitate" SelectSingleNode. And for all Folios we can use Select (also with mull-propagation):

var folios = dte?.Documentacion.Select(x => x?.Encabezado?.IdDoc?.Folio);

Sure, we can edit properties if we want or add new:

// Edit
if (dte?.Documentacion.FirstOrDefault() is Documento documento)
    documento.Encabezado.IdDoc.Folio = 112233445566;

// or create and add new
var newDocumento = new Documento
{
    ID = "SomeID",
    Encabezado = new Encabezado
    {
        IdDoc = new IDDoc
        {
            TipoDTE = 123,
            Folio = 112233445566,
            FmaPago = 1,
            FchEmis = DateTime.Now,
            FchVenc = DateTime.Now.AddDays(1)
        }
    }
};
dte.Documentacion.Add(newDocumento);

And finally save back to XML file using Serialization. Here became usable our class and properties attributes (e.g. [Serializable], [XmlElement] etc), which specifies how each property should be named or represented in XML:

using (var xmlWriter = XmlWriter.Create("My.xml", 
                                        new XmlWriterSettings 
                                        { 
                                            Encoding = Encoding.GetEncoding("ISO-8859-1"),
                                            Indent = true 
                                        }))
{
    // We remove default XSI, XSD namespaces and leave only our custom:
    var xmlSerializerNamespaces = new XmlSerializerNamespaces();
    xmlSerializerNamespaces.Add("", "http://www.sii.cl/SiiDte");

    // and saving our DTE object to XML file.
    xmlSerializer.Serialize(xmlWriter, dte, xmlSerializerNamespaces);
}

Remarks

Of course, parse of XML strings could be replaces with loading XML files (by FileStreams) if needed. And of course you can edit DTE (and child) classes with other properties and mark them as XML attributes or XML elements or making collections with XmlArray and XmlArrayItem attributes - whatever, depending on your needs. Also notice about null XML nodes or its values and take care about it with, for example, Nullable<T> (e.g. long?, DateTime?), IsNullable property of XML attributes and some kind of value validation at property setter:

 private long _folio;
 [XmlElement(nameof(Folio), IsNullable = true)]
 public long? Folio
 {
        get => _folio;
        set => _folio = value ?? 0L; // Null-coalescing with default fallback value of 0L
 }

Hope it would be helpful for your future purposes.

  • Related