Handling C# XML Deserialization with Namespaces - c#

I have been wrestling with deserializing the following XML document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" mc:Ignorable="w14 w15">
<w:zoom w:percent="100"></w:zoom>
<w:proofState w:spelling="clean" w:grammar="clean"></w:proofState>
<w:defaultTabStop w:val="720"></w:defaultTabStop>
<w:characterSpacingControl w:val="doNotCompress"></w:characterSpacingControl>
<w:compat>
<w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="15"></w:compatSetting>
<w:compatSetting w:name="overrideTableStyleFontSizeAndJustification" w:uri="http://schemas.microsoft.com/office/word" w:val="1"></w:compatSetting>
<w:compatSetting w:name="enableOpenTypeFeatures" w:uri="http://schemas.microsoft.com/office/word" w:val="1"></w:compatSetting>
<w:compatSetting w:name="doNotFlipMirrorIndents" w:uri="http://schemas.microsoft.com/office/word" w:val="1"></w:compatSetting>
<w:compatSetting w:name="differentiateMultirowTableHeaders" w:uri="http://schemas.microsoft.com/office/word" w:val="1"></w:compatSetting>
</w:compat>
<w:rsids>
<w:rsidRoot w:val="00B31FC7"></w:rsidRoot>
<w:rsid w:val="00251096"></w:rsid>
<w:rsid w:val="00481AA7"></w:rsid>
<w:rsid w:val="005C6856"></w:rsid>
<w:rsid w:val="00661DE2"></w:rsid>
<w:rsid w:val="00984D97"></w:rsid>
<w:rsid w:val="00A06ADC"></w:rsid>
<w:rsid w:val="00B31FC7"></w:rsid>
</w:rsids>
<m:mathPr>
<m:mathFont m:val="Cambria Math"></m:mathFont>
<m:brkBin m:val="before"></m:brkBin>
<m:brkBinSub m:val="--"></m:brkBinSub>
<m:smallFrac m:val="0"></m:smallFrac>
<m:dispDef></m:dispDef>
<m:lMargin m:val="0"></m:lMargin>
<m:rMargin m:val="0"></m:rMargin>
<m:defJc m:val="centerGroup"></m:defJc>
<m:wrapIndent m:val="1440"></m:wrapIndent>
<m:intLim m:val="subSup"></m:intLim>
<m:naryLim m:val="undOvr"></m:naryLim>
</m:mathPr>
<w:themeFontLang w:val="en-US"></w:themeFontLang>
<w:clrSchemeMapping w:bg1="light1" w:t1="dark1" w:bg2="light2" w:t2="dark2" w:accent1="accent1" w:accent2="accent2" w:accent3="accent3" w:accent4="accent4" w:accent5="accent5" w:accent6="accent6" w:hyperlink="hyperlink" w:followedHyperlink="followedHyperlink"></w:clrSchemeMapping>
<w:shapeDefaults>
<o:shapedefaults v:ext="edit" spidmax="1026"></o:shapedefaults>
<o:shapelayout v:ext="edit">
<o:idmap v:ext="edit" data="1"></o:idmap>
</o:shapelayout>
</w:shapeDefaults>
<w:decimalSymbol w:val="."></w:decimalSymbol>
<w:listSeparator w:val=","></w:listSeparator>
<w15:chartTrackingRefBased></w15:chartTrackingRefBased>
<w15:docId w15:val="{23720E07-DD19-46BC-8098-ED32713AB32B}"></w15:docId>
</w:settings>
I am only interested in what is contained within the rsids element. So I thought I could create classes that looked like this:
[XmlRoot(ElementName ="settings", Namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main")]
public class rsids
{
[XmlElement(ElementName ="rsids",Namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main")]
public List<rsid> Rsids { get; set; }
}
public class rsid
{
[XmlAttribute(Namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main")]
public static string val { get; set; }
}
I am attempting to deserialize like this:
XDocument xdoc = XDocument.Load(file);
using (TextReader reader = new StringReader(xdoc.ToString()))
{
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(rsids));
StreamReader sr = new StreamReader(file);
rsids SettingsXml = (rsids)xmlSerializer.Deserialize(sr);
foreach (var rsid in SettingsXml.Rsids)
{
Console.WriteLine(rsid.val.Count());
}
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Console.ReadLine();
}
}
However, I am getting the following error: "Value cannot be null". This is my first attempt at deserializing an XML document with Namespaces. I have browsed the community and found plenty of articles of folks having similar issues however, after trying some of those solutions I am just as confused as when I started and just going in circles. I want to understand this. Some of those posted solutions out there seem to indicate I only have to add a blank Namespace attribute to my decorators (Namespace ="") and others show the actual namespace uri being referenced but only for the root element leaving blanks in subsequent elements \ attributes. I am more looking for the education as to 'why'\'when' to use one method over another and an example of how to accomplish this given the XML below. I appreciate any help you can provide.
Cheers

You're not too far off.
Your XmlElement attribute implies multiple rsids elements. What you want is a single rsids element containing multiple rsid elements. The easiest way to do this is using the XmlArray and XmlArrayItem attributes.
The val property shouldn't be static
Due to what looks like a bug in XmlSerializer, you need to include Form = XmlSchemaForm.Qualified in your XmlAttribute attribute.
You can also omit most of your Namespace properties as they'll be inherited, and ElementName doesn't have to be specified explicitly.
Putting all that together:
[XmlRoot(Namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main")]
public class settings
{
[XmlArray("rsids")]
[XmlArrayItem("rsid")]
public List<rsid> Rsids { get; set; }
}
public class rsid
{
[XmlAttribute(Form = XmlSchemaForm.Qualified)]
public string val { get; set; }
}
Of course, if that's all you want then a simple LINQ to XML query would be a lot easier:
XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
var rsids = doc.Descendants(w + "rsid")
.Attributes(w + "val")
.Select(x => x.Value);
See this fiddle for a working demo of both approaches.

Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
int[] rsids = doc.Descendants().Where(x => x.Name.LocalName == "rsids").Select(x => new {
rsids = x.Elements().Select(y => int.Parse((string)y.Attribute(x.GetNamespaceOfPrefix("w") + "val"), System.Globalization.NumberStyles.HexNumber))
}).Select(x => x.rsids).FirstOrDefault().Select(x => x).ToArray();
}
}
}

Related

take data from xml c#

its my xml file:
<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>Tytu�1</dc:title>
<dc:creator>autor1</dc:creator>
<dc:subject>Tem1</dc:subject>
<dc:description>O1</dc:description>
<dc:publisher>wydawca1</dc:publisher>
<dc:contributor>wsp�tw�rca1</dc:contributor>
<dc:date>data wydania1</dc:date>
<dc:type>typ zasobu1</dc:type>
<dc:format>format1</dc:format>
<dc:identifier>identyfikator1</dc:identifier>
<dc:source>�r�d�o1</dc:source>
<dc:language>j�zyk1</dc:language>
<dc:relation>powi�zania1</dc:relation>
<dc:coverage>zakres1</dc:coverage>
<dc:rights>prawa1</dc:rights>
</metadata>
Here is my code:
using System;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var dc = "dc";
XDocument doc = XDocument.Load("C:\\Users\\Mateusz\\Desktop\\DublinCore.xml");
var authors = doc.Descendants( dc + "subject");
foreach (var author in authors)
{
Console.WriteLine(author.Value);
}
Console.ReadLine();
}
}
}
I want view in console information about subject.
I think problem is in using namespace.
For example:
<Author>Author</Author> code will work
<dc:Author>Author</Author> code doesn't work
Can someone help me with it?
You need to use XNamespace. So your code becomes:
static void Main(string[] args)
{
XNamespace dc = "http://purl.org/dc/elements/1.1/";
XDocument doc = XDocument.Load("C:\\Users\\Mateusz\\Desktop\\DublinCore.xml");
var authors = doc.Descendants(dc + "subject");
foreach (var author in authors)
{
Console.WriteLine(author.Value);
}
Console.ReadLine();
}
Incidentally it is rather confusing when you use a variable name authors to extract the subject nodes!

How can I make XmlSerializer Deserialize tell me about typos on tag names

I know that most of the time, the Deserialize method of XmlSerializer will complain if there's something wrong (for example, if there is a typo). However, I've found an example where it doesn't complain, when I would have expected it to; and I'd like to know if there's a way of being told about the problem.
The example code below contains 3 things: an good example which works as expected, and example which would complain (commented out) and an example which does not complain, which is the one I want to know how to tell that there is something wrong.
Note: I appreciate that one possible route would be XSD validation; but that really feels like a sledgehammer to crack what seems like a simpler problem. For example, if I was writing a deserializer which had unexpected data that it didn't know what to do with, I'd make my code complain about it.
I've used NUnit (NuGet package) for assertions; but you don't really need it, just comment out the Assert lines - you can see what I'm expecting.
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using NUnit.Framework;
public static class Program
{
public static void Main()
{
string goodExampleXml = #"<?xml version=""1.0"" encoding=""utf-8""?><Example><Weathers><Weather>Sunny</Weather></Weathers></Example>";
var goodExample = Load(goodExampleXml);
Assert.That(goodExample, Is.Not.Null);
Assert.That(goodExample.Weathers, Is.Not.Null);
Assert.That(goodExample.Weathers, Has.Length.EqualTo(1));
Assert.That(goodExample.Weathers.First(), Is.EqualTo(Weather.Sunny));
string badExampleXmlWhichWillComplainXml = #"<?xml version=""1.0"" encoding=""utf-8""?><Example><Weathers><Weather>Suny</Weather></Weathers></Example>";
// var badExampleWhichWillComplain = Load(badExampleXmlWhichWillComplainXml); // this would complain, quite rightly, so I've commented it out
string badExampleXmlWhichWillNotComplain = #"<?xml version=""1.0"" encoding=""utf-8""?><Example><Weathers><Weathe>Sunny</Weathe></Weathers></Example>";
var badExample = Load(badExampleXmlWhichWillNotComplain);
Assert.That(badExample, Is.Not.Null);
Assert.That(badExample.Weathers, Is.Not.Null);
// clearly, the following two assertions will fail because I mis-typed the tag name; but I want to know there has been a problem before this point.
Assert.That(badExample.Weathers, Has.Length.EqualTo(1));
Assert.That(badExample.Weathers.First(), Is.EqualTo(Weather.Sunny));
}
private static Example Load(string serialized)
{
byte[] byteArray = Encoding.UTF8.GetBytes(serialized);
var xmlSerializer = new XmlSerializer(typeof(Example));
using var stream = new MemoryStream(byteArray, false);
return (Example)xmlSerializer.Deserialize(stream);
}
}
public enum Weather
{
Sunny,
Cloudy,
Rainy,
Windy,
Stormy,
Snowy,
}
public class Example
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Serialized XML")]
[XmlArray("Weathers")]
[XmlArrayItem("Weather")]
public Weather[] Weathers { get; set; }
}
Having looked at Microsoft's published source code for XmlSerializer, it became apparent that there are events that you can subscribe to (which is what I was hoping for); but they aren't exposed on the XmlSerializer itself... you have to inject a struct containing them into the constructor.
So I've been able to modify the code from the question to have an event handler which gets called when an unknown node is encountered (which is exactly what I was after). You need one extra using, over the ones given in the question...
using System.Xml;
and then here is the modified "Load" method...
private static Example Load(string serialized)
{
XmlDeserializationEvents events = new XmlDeserializationEvents();
events.OnUnknownNode = (sender, e) => System.Diagnostics.Debug.WriteLine("Unknown Node: " + e.Name);
var xmlSerializer = new XmlSerializer(typeof(Example));
using var reader = XmlReader.Create(new StringReader(serialized));
return (Example)xmlSerializer.Deserialize(reader, events);
}
So now I just need to do something more valuable than just write a line to the Debug output.
Note that more events are available, as described on the XmlDeserializationEvents page, and I'll probably pay attention to each of them.
I tested following and it works
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =#"<?xml version=""1.0"" encoding=""utf-8"" ?>
<Example>
<Weathers>Sunny</Weathers>
<Weathers>Cloudy</Weathers>
<Weathers>Rainy</Weathers>
<Weathers>Windy</Weathers>
<Weathers>Stormy</Weathers>
<Weathers>Snowy</Weathers>
</Example>";
StringReader sReader = new StringReader(xml);
XmlReader reader = XmlReader.Create(sReader);
XmlSerializer serializer = new XmlSerializer(typeof(Example));
Example example = (Example)serializer.Deserialize(reader);
}
}
public enum Weather
{
Sunny,
Cloudy,
Rainy,
Windy,
Stormy,
Snowy,
}
public class Example
{
[XmlElement("Weathers")]
public Weather[] Weathers { get; set; }
}
}

Get information from XML file in C# requested in dialog

I'm trying to parse/get the information of an XML file where I have saved the setting values.
I would like to open a dialog, where the user can select the .xml file and after that get the information and load the settings.
The XML file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration version="1.2" createDate="2018-07-17T10:00:00">
<AutoScale>1</Autoscale>
<Threshold>2142</Threshold>
<MinAuto>14</MinAuto>
<MinMan>1</MinMan>
<MaxMan>1</MaxMan>
<BlueBackground>1</BlueBackground>
<Contour>1</Contour>
<Rotate>180</Rotate>
<Flip>Vertical</Flip>
</Configuration>
My code (in C#) looks like this:
using (var openFileDialogXML = new OpenFileDialog()){
System.IO.Stream myStream = null;
openFileDialogXML.InitialDirectory = #System.Environment.CurrentDirectory;
openFileDialogXML.Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*";
openFileDialogXML.FilterIndex = 1;
openFileDialogXML.RestoreDirectory = true;
DialogResult dr = openFileDialogXML.ShowDialog();
if (dr == System.Windows.Forms.DialogResult.OK)
{
using (XmlReader reader = XmlReader.Create(openFileDialogXML.FileName))
{
reader.MoveToContent();
var version = reader.GetAttribute("version");
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "AutoScale":
//Get AutoScale value
break;
case "Threshold":
break;
case "MinAuto":
break;
case "MinMan":
break;
case "MaxMan":
break;
}
}
}
}
I'm open to use any parser but I would like to read it element by element because it could happen that we add new settings in the future.
Can you please help me/ give me some advice about how I can reach this?
I like using Xml Linq and putting results into a dictionary so when new items are added the xml parser doesn't have to change :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication53
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, string> dict = doc.Element("Configuration").Elements()
.GroupBy(x => x.Name.LocalName, y => (string)y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
I would suggest to use DataContract and load the XML into specified object. When your configuration file changes, you would need to update also the Entity.
[DataContract]
public class MyXmlClass
{
[DataMember]
public int PropertyToSerialize { get; set; }
}
You can then use DataContractSerializer as described here - https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/serialization-and-deserialization
It will be much easier for you to work with object than parsing XML manually :)
Some quick and dirty answer if you want to parse it manually:
using System.Xml;
[...]
XmlTextReader xtr = new XmlTextReader(GetResourceStream("config.xml"));
while (xtr.Read())
{
if (xtr.AttributeCount == 0)
continue;
if (xtr.LocalName == "Configuration")
{
string version = xtr.GetAttribute("version");
string date = xtr.GetAttribute("createDate");
Console.WriteLine($"version={version} - date = {date}")
}
else if (xtr.LocalName == "AutoScale")
{
string autoscale = xtr.ReadString();
Console.WriteLine($"autoscale={autoscale}")
}
[...]
}
xtr.Close();
I didn't try the code, if you need more start by looking XmlTextReader examples or documentation (stackoverflow should have plenty of them)

Read Mulitple childs and extract data xmlReader in c#

XML :
<InformationTuples>
<InformationTuple>
<Name>documentClass</Name>
<value format="" valueset="{rechnung}" originalValue="Rechnung" start="0" end="0" LD="0" doc="C:\b4enviam-service-test\inputDir\031a0933-2616-4d8e-8a79-56746ae0e160/Invoice_51029062.pdf">Rechnung</value>
<EntityType>Class</EntityType>
<state>New </state>
<need>Mandatory </need>
<extractionmethod>
</extractionmethod>
<weight>1</weight>
<precondition type="optional">All</precondition>
</InformationTuple>
<InformationTuple>
<Name>SAPNr.</Name>
<value format="" valueset="" originalValue="4352020616" start="0" end="0" LD="0" doc="C:\b4enviam-service-test\inputDir\031a0933-2616-4d8e-8a79-56746ae0e160/Invoice_51029062.pdf">4352020616</value>
<EntityType>KB.GTInovice</EntityType>
<state>New </state>
<need>Mandatory </need>
<extractionmethod>
</extractionmethod>
<weight>1</weight>
<precondition type="optional">all</precondition>
</InformationTuple>
<InformationTuple>
<Name>GT-Invoice</Name>
<value format="" valueset="" originalValue="" start="0" end="0" LD="0" doc="">
</value>
<EntityType>KB.GTInovice</EntityType>
<state>New </state>
<need>Mandatory </need>
<extractionmethod>
</extractionmethod>
<weight>1</weight>
<precondition type="optional">all</precondition>
</InformationTuple>
</InformationTuples>
C#
reader.ReadToFollowing("InformationTuple");
reader2.ReadToFollowing("InformationTuple");
do
{
subtree = reader2.ReadSubtree();
subtree.ReadToFollowing("Name");
Debug.WriteLine(subtree.ReadElementContentAsString());
reader2.ReadToNextSibling("InfromationTuple");
} while (reader.ReadToNextSibling("InformationTuple"))
I'm trying for a while now to extract data from multiple childs in XML using c# but didn't successful. I have tried multiple code snippets but unable to extract data.
Like i have to extract the data given in three information tuples, but functions given in the XMLreader not working properly reader pointer break after single loop iteration (unable to move to second InformationTuple), even i have tried two different reader pointer but its now giving exception.
Need little help ,
Thanks
You can read the first <Name> element inside each <InformationTuple> as follows. Introduce the following extension methods:
public static partial class XmlReaderExtensions
{
public static IEnumerable<string> ReadAllElementContentsAsString(this XmlReader reader, string localName, string namespaceURI)
{
while (reader.ReadToFollowing(localName, namespaceURI))
yield return reader.ReadElementContentAsString();
}
public static IEnumerable<XmlReader> ReadAllSubtrees(this XmlReader reader, string localName, string namespaceURI)
{
while (reader.ReadToFollowing(localName, namespaceURI))
using (var subReader = reader.ReadSubtree())
yield return subReader;
}
}
And then do:
foreach (var name in reader.ReadAllSubtrees("InformationTuple", "")
.Select(r => r.ReadAllElementContentsAsString("Name", "").First()))
{
// Process the name somehow
Debug.WriteLine(name);
}
If you want to only read the first <Name> element of each <InformationTuple> element inside each <InformationTuples> container, you can restrict the scope of the search by composing calls to ReadAllSubtrees() using SelectMany():
foreach (var name in reader.ReadAllSubtrees("InformationTuples", "")
.SelectMany(r => r.ReadAllSubtrees("InformationTuple", ""))
.Select(r => r.ReadAllElementContentsAsString("Name", "").First()))
{
// Process the name somehow
Debug.WriteLine(name);
}
Some notes:
You don't close (or dispose) your ReadSubtree() subtree reader when you are done with it. From the docs:
You should not perform any operations on the original reader until the new reader has been closed. This action is not supported and can result in unpredictable behavior.
Thus you must close or dispose this nested reader before advancing the outer reader.
You have too many calls to reader.ReadToFollowing("InformationTuple"); at the beginning. Perhaps you meant to do reader.ReadToFollowing("InformationTuples");?
To ensure there is one and only one <Name> element for each <InformationTuple> replace First() with .Single().
If there might be multiple <Name> nodes for each <InformationTuple> and you want to read all of them, do:
foreach (var name in reader.ReadAllSubtrees("InformationTuple", "")
.SelectMany(r => r.ReadAllElementContentsAsString("Name", "")))
{
// Process the name somehow
Demo fiddle here.
The code below I used a lot and will not create any errors.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
while (!reader.EOF)
{
if (reader.Name != "InformationTuple")
{
reader.ReadToFollowing("InformationTuple");
}
if (!reader.EOF)
{
XElement subtree = (XElement)XElement.ReadFrom(reader);
Info.info.Add(new Info() { state = (string)subtree.Element("state"), weight = (int)subtree.Element("weight") });
}
}
}
}
public class Info
{
public static List<Info> info = new List<Info>();
public string state { get; set; }
public int weight { get; set; }
}
}

how to read & write xml file in C# not rely on the tag name?

Thank you very much for reading my question.
the bottom is the sample of my xml file.please refer that.
i did some xml files before, but by "CMarkXml". "IntoElement, OutofElement", is very clear.
but when C#...i was lost..
1: how to read & write my xml file without using the tag name. i see some articles about operation on xml file by c#, but all assumed that known the tag name.
2: if without tag name, it is very difficult or not recommend. then how to read & write my xml file by XmlDocument? (sorry, but no Ling please, i am very faint with that...).
3: my idear is, for the xml file, get out some section, we still could parse the section by xmldocument.
4: for the write/modify the xml file, of course, should contain delete some section, delete some "leaf", change the attributes...
Thank you very much for reading the long question, and any help i will very appreciate. If you have a good sample code but not continent paste them here, could you send it to "erlvde#gmail.com"?
<root>
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
....(other <bb>)
</b>
</root>
Read your xml into XmlDocument:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml("XML HERE");
Access child nodes:
xmlDocument.ChildNodes[1]
But it's also true that it's very error prone
You can also check if you have child nodes at all:
xmlDocument.HasChildNodes
And get number of child nodes:
xmlDocument.ChildNodes.Count
It looks to me like your elements names contain identifiers. If that is the case, and you have control over the XML schema, I would highly recommend changing your XML to contain elements and/or attributes indicating your identifiers and then use the built in XmlSerializer class for serializing to and from XML. It has many modifiers available, such as XmlElement and XmlAttribute among many others, for formatting the output.
Here is a tutorial to get you started.
If possible, change your XML to something like following which would make it far simpler to manipulate...again if changing the schema is a possibility.
<root>
<a>i belong to a</a>
<b>
<bb id="1">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb id="2">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
</b>
</root>
Edit this edit reflects the changes you made to your XML
Here is a simple console application which will serialize an object to an XML file and then rehydrate it.
Expected XML
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
</b>
</root>
Simple Console Application Demonstration
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var items = new root
{
a = "i belong to a",
b = new List<bb>
{
new bb
{
bbClassProperty = new List<int>
{
1,
2,
3,
4,
5
}
},
new bb
{
bbClassProperty= new List<int>
{
1,
2,
3,
4,
5
}
}
}
};
XmlSerializer serializer = new XmlSerializer(typeof(root));
using (var textWriter = new StreamWriter(#"C:\root.xml"))
{
serializer.Serialize(textWriter, items);
textWriter.Close();
}
using (var stream = new StreamReader(#"C:\root.xml"))
{
var yourObject = serializer.Deserialize(stream);
}
Console.Read();
}
}
#region [Classes]
public class root
{
public string a { get; set; }
public List<bb> b { get; set; }
}
public class bb
{
[XmlElement("bb")]
public List<int> bbClassProperty { get; set; }
}
#endregion
}
Look into the ChildNodes (and similar) properties and methods on your XmlElement object. These will let you iterate over the children of a node and you can then ask that node for its name.
If you have a XmlNode object, you can use XMLNode.FirstChild to get the child, if it has any. You can also use XMLNode.NextSibling to get the next Node of the same parent node.
Why can't you use the names of the nodes? It's the easiest and most common way. Especially if you use XPath or similar.
XPath is also the answer to your second question.
U can use the class XML reader, a simple example is given here.
using System;
using System.Xml;
class Program
{
static void Main()
{
// Create an XML reader for this file.
using (XmlReader reader = XmlReader.Create("perls.xml"))
{
while (reader.Read())
{
// Only detect start elements.
if (reader.IsStartElement())
{
// Get element name and switch on it.
switch (reader.Name)
{
case "perls":
// Detect this element.
Console.WriteLine("Start <perls> element.");
break;
case "article":
// Detect this article element.
Console.WriteLine("Start <article> element.");
// Search for the attribute name on this current node.
string attribute = reader["name"];
if (attribute != null)
{
Console.WriteLine(" Has attribute name: " + attribute);
}
// Next read will contain text.
if (reader.Read())
{
Console.WriteLine(" Text node: " + reader.Value.Trim());
}
break;
}
}
}
}
}
}
The input file text is:
<?xml version="1.0" encoding="utf-8" ?>
<perls>
<article name="backgroundworker">
Example text.
</article>
<article name="threadpool">
More text.
</article>
<article></article>
<article>Final text.</article>
</perls>
Output
Start element.
Start element.
Has attribute name: backgroundworker
Text node: Example text.
Start element.
Has attribute name: threadpool
Text node: More text.
Start element.
Text node:
Start element.
Text node: Final text.enter code here
You can use the following code to if the file does not contain the headers, in the example above.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
reader = XmlReader.Create(filePath, settings)
Would something like this help?
void Iterate(XmlNode parent) {
//do something with
//parent.Name
//parent.Value
//parent.Attributes
foreach(XmlNode child in parent.ChildNodes) {
Iterate(child);
}
}
XmlDocument document = new XmlDocument();
document.Load(filename);
XmlNode parent = document.DocumentElement;
Iterate(parent);
You could also store it like that (sorry for any syntactical error, didn't run it)
public class Document {
public Element DocumentElement { set; get; }
private void Load(string fileName) {
XmlDocument document = new XmlDocument();
document.Load(fileName);
DocumentElement = new Element(this, null);
DocumentElement.Load(document.DocumentElement);
}
}
public class Element {
public string Name { set; get; }
public string Value { set; get; }
//other attributes
private Document document = null;
private Element parent = null;
public Element Parent { get { return parent; } }
public List<Element> Children { set; get; }
private int order = 0;
public Element(Document document, Element parent) {
Name = "";
Value = "";
Children = new List<LayoutElement>();
this.document = document;
this.parent = parent;
order = parent != null ? parent.Children.Count + 1 : 1;
}
private Element GetSibling(bool left) {
if(parent == null) return null;
int add = left ? -1 : +1;
Element sibling = parent.Children.Find(child => child.order == order + add);
return sibling;
}
public Element GetLeftSibling() {
return GetSibling(true);
}
public Element GetRightSibling() {
return GetSibling(false);
}
public void Load(XmlNode node) {
Name = node.Name;
Value = node.Value;
//other attributes
foreach(XmlNode nodeChild in node.Children) {
Element child = new Element(document, this);
child.Load(nodeChild);
Children.Add(child);
}
}
}
Document document = new Document();
document.Load(fileName);
For changing/deleting right now you could iterate the tree and find elements by name, but since name is not unique, you would affect many elements at once. You could add an unique id in every tag like
<bb id="bb1"/>
Then read it in Load function like
id = ((XmlElement)node).GetAttribute("id");
and use this id to iterate through the tree. Sorry I don't have time right now to provide something more detailed.

Categories