LINQ to read XML file and print results - c#

I got the following XML file (Data.xml):
<root>
<sitecollection name="1A">
<site name="1B">
<maingroup name="1C">
<group name="1D"> </group>
</maingroup>
</site>
</sitecollection>
<sitecollection name="2A">
<site name="2B">
<maingroup name="2C">
<group name="2D"> </group>
</maingroup>
</site>
</sitecollection>
</root>
And I need to print all the all the child elements in this format:
1A
1B
1C
1D
2A
2B
2C
2D
I have the following code so far which needs some adjustment. I could also change it completely if there's an easier method. Thanks for your help
class xmlreader
{
public static void Main()
{
// Xdocument to read XML file
XDocument xdoc = XDocument.Load("Data.xml");
var result = new System.Text.StringBuilder();
var lv1s = from lv1 in xdoc.Descendants("sitecollection")
select new
{
sitecollection = lv1.Attribute("name").Value,
maingroup = lv1.Descendants("group")
};
var lv2s = from lv2 in xdoc.Descendants("site")
select new
{
site = lv2.Attribute("name").Value,
sitetittle = lv2.Descendants()
};
var lv3s = from lv3 in xdoc.Descendants("maingroup")
select new
{
maingroup = lv3.Attribute("name").Value,
};
var lv4s = from lv4 in xdoc.Descendants("group")
select new
{
grouppage = lv4.Attribute("name").Value,
};
// Loop to print results
foreach (var lv1 in lv1s)
{
result.AppendLine(lv1.sitecollection);
foreach (var lv2 in lv2s)
{
result.AppendLine(" " + lv2.Attribute("name").Value);
foreach (var lv3 in lv3s)
{
result.AppendLine(" " + lv3.Attribute("name").Value);
foreach (var lv4 in lv4s)
{
result.AppendLine(" " + lv4.Attribute("name").Value);
}
}
}
}
}
}

With such a uniform hierarchy, recursion can do the job with a lot less code:
void PrintNames(StringBuilder result, string indent, XElement el)
{
var attr = el.Attributes("name");
if (attr != null)
{
result.Append(indent);
result.Append(attr.Value);
result.Append(System.Environment.NewLine);
}
indent = indent + " ";
foreach(var child in el.Elements())
{
PrintNames(result, indent, child);
}
}
...
var sb = new StringBuilder();
PrintNames(sb, String.Empty, xdoc.Root);

How about the following, find all elements with a name attribute, then add spaces based on their depth.
var result = new System.Text.StringBuilder();
var namedElements = doc.Descendants().Where(el => el.Attributes("name")!=null);
foreach(var el in namedElements)
{
int depth = el.Ancestors().Count();
for (int i=0;i<depth;i++)
result.Append(" ");
result.Append(el.Attributes("name").Value);
result.Append(System.Environment.NewLine);
}
NOTE: The above is from memory, so please check the syntax!

Related

Deleted xml elements are not being removed

In the following snippet the final string "removed" still has the deleted elements. I am converting to byte[] first because the final implementation input will be in that form. I am looping thru the saved nodes and deleting them for element document. What gives? All ideas greatly appreciated.
static void Main(string[] args)
{
var str =
#"
<CustomerCareReport xmlns='http://schemas.datacontract.org/2004/07/Foo.Bar.Frameworks.Reporting.Stack.Infrastructure.DataObjects.FireEms' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>
<OtherInformation>
<Documents xmlns:a='http://schemas.datacontract.org/2004/07/Foo.Bar.Frameworks.Reporting.Stack.Infrastructure.DataObjects.Documents'>
<a:Document>
<a:Contents>Z2dn</a:Contents>
<a:Description>yoyo</a:Description>
<a:Id>1</a:Id>
</a:Document>
<a:Document>
<a:Contents>ZmhmaA0KZmZmDQpmamZqZmpmDQo=</a:Contents>
<a:Description>test 2</a:Description>
<a:Id>2</a:Id>
</a:Document>
</Documents>
</OtherInformation>
</CustomerCareReport>
";
var element = XElement.Parse(str);
XNamespace ns = element.Name.Namespace;
// convert to bytes firstbecause final implementation input is byte array
byte[] bytes = Encoding.ASCII.GetBytes(str);
using (var stream = new MemoryStream(bytes))
{
var deleteMarked = new List<XElement>();
var xelement = XElement.Load(stream);
var docs = xelement
.Descendants(ns + "Documents");
foreach (XElement doc in docs.Descendants())
{
if (doc.Name.LocalName.Equals("Document"))
{
var delDoc = doc;
deleteMarked.Add(delDoc);
}
}
foreach (var deldoc in deleteMarked)
{
foreach (var node in deldoc.Elements())
{
node.Remove();
}
}
if (deleteMarked.Count > 0)
{
stream.Close();
bytes = stream.ToArray();
}
var removed = System.Text.Encoding.Default.GetString(bytes);
Console.WriteLine(removed);
}
}

How To Remove Last Node In XML? C#

I am trying to remove the last node from an XML file, but cannot find any good answers for doing this. Here is my code:
XmlReader x = XmlReader.Create(this.PathToSpecialFolder + #"\" + Application.CompanyName + #"\" + Application.ProductName + #"\Recent.xml");
int c = 0;
while (x.Read())
{
if (x.NodeType == XmlNodeType.Element && x.Name == "Path")
{
c++;
if (c <= 10)
{
MenuItem m = new MenuItem() { Header = x.ReadInnerXml() };
m.Click += delegate
{
};
openRecentMenuItem.Items.Add(m);
}
}
}
x.Close();
My XML node structure is as follows...
<RecentFiles>
<File>
<Path>Text Path</Path>
</File>
</RecentFiles>
In my situation, there will be ten nodes maximum, and each time a new one is added, the last must be removed.
You can try this
XmlDocument doc = new XmlDocument();
doc.Load(fileName);
XmlNodeList nodes = doc.SelectNodes("/RecentFiles/File");
nodes[nodes.Count].ParentNode.RemoveChild(nodes[nodes.Count]);
doc.Save(fileName);
It sounds like you want something like:
var doc = XDocument.Load(path);
var lastFile = doc.Descendants("File").LastOrDefault();
if (lastFile != null)
{
lastFile.Remove();
}
// Now save doc or whatever you want to do with it...

How can I read the XML file Node attributes and Child node attributes values?

How can I read the xml file having contents :
<?xml version="1.0" encoding="utf-8"?>
<A>
<B value="1">
<Hash algo="SHA256" value="905C45B51B970434D7159641D9F6A88DC91E9C35030618A729C8E4BE174711AF" />
</B>
<B value="2">
<Hash algo="SHA256" value="649721FF455E9B100E691A3857696350E14364029C34C9438AB3EA9665C91292" />
</B>
<B value="3">
<Hash algo="SHA256" value="90FC91C4B82BF440FDAFECF3303DCA8FB9F2E9D7EFFAE394D8B74D0C7CD7DA10" />
</B>
</A>
In the above xml file I want to read the values of all B tag's "value" attribute values and the tag Hash "algorithm" or "value" attributes value.
Here is the code that I am using:
var settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using (var stm = new FileStream(#"xmlFilePath", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = XmlReader.Create(stm, settings))
{
while (reader.Read())
{
string srcFileHash=null;
reader.ReadToDescendant("B");
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "B")
{
reader.MoveToAttribute("value");
var bValue=reader.Value; // get the B tag attribute value.
//also want to read the <hash value=? and algo=?. but I dnt know how to get these hash tag attributes.
}
}
}
XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Descendants("B")
.Select(b => new
{
BValue = b.Attribute("value").Value,
Alg = b.Element("Hash").Attribute("algo").Value,
AlgValue = b.Element("Hash").Attribute("value").Value,
})
.ToArray();
XElement doc = XElement.Load(yourxmlfilename);//Load the document
foreach (XElement b in doc.Elements("B"))//Iterate all elements B
{
var xAttribute = b.Attribute("value");//get attribute called value
if (xAttribute != null)
{
string v = xAttribute.Value;//now you have the B value
}
foreach (XElement h in b.Elements("Hash"))//Iterate all elements Hash
{
var xAttributev = h.Attribute("value");//get attribute called value
if (xAttributev != null)
{
string hashValue = xAttribute.Value;//now you have the Hash value
}
var xAttributeh = h.Attribute("algo");//get attribute called algo
if (xAttributeh != null)
{
string algorithm = xAttributeh.Value;//now you have the Hash algorithm
}
}
}
Your question is unclear but here is some quick code to do what you want.
XDocument xdoc = XDocument.Load(#"file.xml");
if (xdoc.Root != null)
{
var bs = xdoc.Root.Descendants("B");
foreach (var bNode in bs)
{
Console.Out.WriteLine("B Value: " + bNode.FirstAttribute.Value);
var hash = bNode.XPathSelectElement("Hash");
Console.Out.WriteLine("algo is : " + hash.FirstAttribute.Value);
Console.Out.WriteLine("value is : " + hash.FirstAttribute.NextAttribute.Value);
}
}
I would suggest brushing up on XDocument and XmlDocument (both are still relevant depending on how old your code is :)

Read from XML > Add to Listview

I have some problems getting the data that i read from XML split into seperate columns. Any help this new C# coder would get would be appreciated.
XDocument xmlDoc = XDocument.Load("emails.xml");
var t = from c in xmlDoc.Descendants("dt")
select (string)c.Element("name") + (string)c.Element("email");
foreach (string item in t)
{
listView.Items.Add(item);
}
XDocument xmlDoc = XDocument.Load("emails.xml");
var t = from c in xmlDoc.Descendants("dt")
select new
{
Name = e.Element("name").Value,
EMail = e.Element("email").Value,
};
foreach (var item in t)
{
var lvi = listView.Items.Add(item.Name);
lvi.SubItems.Add(item.EMail);
}

LINQ to read XML

I am using this XML file:
<root>
<level1 name="A">
<level2 name="A1" />
<level2 name="A2" />
</level1>
<level1 name="B">
<level2 name="B1" />
<level2 name="B2" />
</level1>
<level1 name="C" />
</root>
Could someone give me a C# code using LINQ, the simplest way to print this result:
(Note the extra space if it is a level2 node)
A
A1
A2
B
B1
B2
C
Currently I have written this code:
XDocument xdoc = XDocument.Load("data.xml"));
var lv1s = from lv1 in xdoc.Descendants("level1")
select lv1.Attribute("name").Value;
foreach (var lv1 in lv1s)
{
result.AppendLine(lv1);
var lv2s = from lv2 in xdoc...???
}
Try this.
using System.Xml.Linq;
void Main()
{
StringBuilder result = new StringBuilder();
//Load xml
XDocument xdoc = XDocument.Load("data.xml");
//Run query
var lv1s = from lv1 in xdoc.Descendants("level1")
select new {
Header = lv1.Attribute("name").Value,
Children = lv1.Descendants("level2")
};
//Loop through results
foreach (var lv1 in lv1s){
result.AppendLine(lv1.Header);
foreach(var lv2 in lv1.Children)
result.AppendLine(" " + lv2.Attribute("name").Value);
}
Console.WriteLine(result);
}
Or, if you want a more general approach - i.e. for nesting up to "levelN":
void Main()
{
XElement rootElement = XElement.Load(#"c:\events\test.xml");
Console.WriteLine(GetOutline(0, rootElement));
}
private string GetOutline(int indentLevel, XElement element)
{
StringBuilder result = new StringBuilder();
if (element.Attribute("name") != null)
{
result = result.AppendLine(new string(' ', indentLevel * 2) + element.Attribute("name").Value);
}
foreach (XElement childElement in element.Elements())
{
result.Append(GetOutline(indentLevel + 1, childElement));
}
return result.ToString();
}
A couple of plain old foreach loops provides a clean solution:
foreach (XElement level1Element in XElement.Load("data.xml").Elements("level1"))
{
result.AppendLine(level1Element.Attribute("name").Value);
foreach (XElement level2Element in level1Element.Elements("level2"))
{
result.AppendLine(" " + level2Element.Attribute("name").Value);
}
}
Here are a couple of complete working examples that build on the #bendewey & #dommer examples. I needed to tweak each one a bit to get it to work, but in case another LINQ noob is looking for working examples, here you go:
//bendewey's example using data.xml from OP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
class loadXMLToLINQ1
{
static void Main( )
{
//Load xml
XDocument xdoc = XDocument.Load(#"c:\\data.xml"); //you'll have to edit your path
//Run query
var lv1s = from lv1 in xdoc.Descendants("level1")
select new
{
Header = lv1.Attribute("name").Value,
Children = lv1.Descendants("level2")
};
StringBuilder result = new StringBuilder(); //had to add this to make the result work
//Loop through results
foreach (var lv1 in lv1s)
{
result.AppendLine(" " + lv1.Header);
foreach(var lv2 in lv1.Children)
result.AppendLine(" " + lv2.Attribute("name").Value);
}
Console.WriteLine(result.ToString()); //added this so you could see the output on the console
}
}
And next:
//Dommer's example, using data.xml from OP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
class loadXMLToLINQ
{
static void Main( )
{
XElement rootElement = XElement.Load(#"c:\\data.xml"); //you'll have to edit your path
Console.WriteLine(GetOutline(0, rootElement));
}
static private string GetOutline(int indentLevel, XElement element)
{
StringBuilder result = new StringBuilder();
if (element.Attribute("name") != null)
{
result = result.AppendLine(new string(' ', indentLevel * 2) + element.Attribute("name").Value);
}
foreach (XElement childElement in element.Elements())
{
result.Append(GetOutline(indentLevel + 1, childElement));
}
return result.ToString();
}
}
These both compile & work in VS2010 using csc.exe version 4.0.30319.1 and give the exact same output. Hopefully these help someone else who's looking for working examples of code.
EDIT: added #eglasius' example as well since it became useful to me:
//#eglasius example, still using data.xml from OP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
class loadXMLToLINQ2
{
static void Main( )
{
StringBuilder result = new StringBuilder(); //needed for result below
XDocument xdoc = XDocument.Load(#"c:\\deg\\data.xml"); //you'll have to edit your path
var lv1s = xdoc.Root.Descendants("level1");
var lvs = lv1s.SelectMany(l=>
new string[]{ l.Attribute("name").Value }
.Union(
l.Descendants("level2")
.Select(l2=>" " + l2.Attribute("name").Value)
)
);
foreach (var lv in lvs)
{
result.AppendLine(lv);
}
Console.WriteLine(result);//added this so you could see the result
}
}
XDocument xdoc = XDocument.Load("data.xml");
var lv1s = xdoc.Root.Descendants("level1");
var lvs = lv1s.SelectMany(l=>
new string[]{ l.Attribute("name").Value }
.Union(
l.Descendants("level2")
.Select(l2=>" " + l2.Attribute("name").Value)
)
);
foreach (var lv in lvs)
{
result.AppendLine(lv);
}
Ps. You have to use .Root on any of these versions.
Asynchronous loading of the XML file can improve performance, especially if the file is large or if it takes a long time to load. In this example, we use the XDocument.LoadAsync method to load and parse the XML file asynchronously, which can help to prevent the application from becoming unresponsive while the file is being loaded.
Demo: https://dotnetfiddle.net/PGFE7c (using XML string parsing)
Implementation:
XDocument doc;
// Open the XML file using File.OpenRead and pass the stream to
// XDocument.LoadAsync to load and parse the XML asynchronously
using (var stream = File.OpenRead("data.xml"))
{
doc = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None);
}
// Select the level1 elements from the document and create an anonymous object for each element
// with a Name property containing the value of the "name" attribute and a Children property
// containing a collection of the names of the level2 elements
var results = doc.Descendants("level1")
.Select(level1 => new
{
Name = level1.Attribute("name").Value,
Children = level1.Descendants("level2")
.Select(level2 => level2.Attribute("name").Value)
});
foreach (var result in results)
{
Console.WriteLine(result.Name);
foreach (var child in result.Children)
Console.WriteLine(" " + child);
}
Result:
A
  A1
  A2
B
  B1
  B2
C

Categories