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 Folio
node?
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 FileStream
s) 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.