Reading Xml file using LINQ in C# - c#

I have a list of String
List<String> lst=new List<String>{"A","B","C"}
And an xml file like
<Root>
<ChildList>
<Childs>
<Child Name="a1" Val="A"/>
<Child Name="a2" val="A"/>
<Child Name="b1" val="B"/>
</Childs>
</ChildList>
</Root>
i need to read contets of the xml file and add to a dictionary
Dictionary<String,List<String>> dict
where the dictionary key is the items in the "lst" and value is the attribute value of "Name" from the file
So the result will be like
Key(String) Value(List<String>)
"A" "a1","a2"
"B" "b1"
"C" null
now i'm using nested for loop for this
Is there any wau to do this using LINQ to XML
Thanks in advance

I think this will do it:
XDocument doc = XDocument.Load("foo.xml");
ILookup<string, string> lookup = doc.Descendants("Childs")
.First()
.Elements("Child")
.ToLookup(x => (string) x.Attribute("Val"),
x => (string) x.Attribute("Name"));
var dictionary = lst.ToDictionary(x => x,
x => lookup[x].ToList().NullIfEmpty());
Using a helper method:
public static List<T> NullIfEmpty<T>(this List<T> list)
{
return list.Count == 0 ? null : list;
}
If you don't mind having an empty list instead of null for items which aren't in the XML file, the second statement can be simplified, with no need for the helper method:
var dictionary = lst.ToDictionary(x => x, x => lookup[x].ToList());
Note that I've structured this so that it only needs to go through the XML file once, instead of searching through the file once for each element in the list.

var xml = #"<Root>
<ChildList>
<Childs>
<Child Name=""a1"" Val=""A""/>
<Child Name=""a2"" Val=""A""/>
<Child Name=""b1"" Val=""B""/>
</Childs>
</ChildList>
</Root>";
var lst= new List<String> { "A", "B", "C" };
var doc = XDocument.Parse(xml);
var dict = (from item in lst
select new
{
Key = item,
Value = (from elem in doc.Root.Element("ChildList").Element("Childs").Elements("Child")
where (string)elem.Attribute("Val") == item
select (string)elem.Attribute("Name")).ToList()
}).ToDictionary(i => i.Key, i => i.Value);
This can be made more efficient. I iterate over the elements once for every item in lst. I'll properly come up with another solution later if others don't come up with one.

Related

Linq statement in C# to extract data from XElement

I have a List containing elements like this:
{<d:element m:type="SP.KeyValue" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:Key>Path</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>}
I'd like help to discern the Linq statement required to extract only the "https://my.home.site.com" values from said List<>. The catch here is that we cannot only use the <d:Value> because only XElements in this list that has a <d:Key> value of Path, like in the example above, actually contain URLs in the <d:Value> key.
Does anyone know the magic Linq statement that would perform said data extract?
Assuming your data is coming from an XML file similar to this:
<?xml version="1.0"?>
<root xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:element m:type="SP.KeyValue">
<d:Key>Path</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>
<d:element m:type="SP.KeyValue">
<d:Key>NotPath</d:Key>
<d:Value>https://my.home.site.com</d:Value>
<d:ValueType>Edm.String</d:ValueType>
</d:element>
</root>
The following code:
XElement root = XElement.Load("Some file");
List<string> urls;
//Query Syntax
urls = (from e in root.Elements(d + "element")
where e.Element(d + "Key").Value == "Path"
select e.Element(d + "Value").Value);
//Or
//Method Syntax
urls = (from e in root.Elements(d + "element")
where e.Element(d + "Key").Value == "Path"
select e.Element(d + "Value").Value).ToList();
Console.WriteLine(string.Join(",", urls));
Will result in (note that it ignores the "NotPath" key):
https://my.home.site.com
You can check out a live example here and check out this for more XElement information.
if you actually have a List of XElement:
var list = new List<XElement>(); //however you get your XElement collection
var values = list.Where(x => x.Elements().First(e => e.Name.LocalName == "Key").Value == "Path")
.Select(x => x.Elements().First(e => e.Name.LocalName == "Value").Value)
if you have an XDocument, you'd just modify the beginning of the query slightly.
I think that problem if with naespace declaration. Try this:
string xml = "<d:element m:type=\"SP.KeyValue\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\">"+
"<d:Key>Path</d:Key>"+
"<d:Value>https://my.home.site.com</d:Value>"+
"<d:ValueType>Edm.String</d:ValueType>"+
"</d:element>";
XDocument xmlObj = XDocument.Parse(xml);
XNamespace ns_d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
var result = xmlObj.Descendants(ns_d + "Value").Select(x => x.Value);

Parsing xml into anonymous type

I am trying to parse the xml below to load the id_name/rel_no pairs into an anonymous type collection. I am having a problem when looping through the collection and when element is missing in one of the elements. Is there a way not to load a particular pair when one of the elements id_name or rel_no is missing?
I get InvalidOperationException (sequence contains no elements) when the loop gets to that particular pair with missing element.
Thanks for any suggestions.
XDocument xdata = XDocument.Parse(data);
var query = from dox in xdata.Descendants("Inc")
select new
{
IDName= dox.Element("id_name").Value,
RelNo= dox.Descendants("rel_no").First().Value
};
XML
<Data>
<Inc>
<id_name>test</id_name>
<Relationships>
<Relationship>
<rel_no>004</rel_no>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test2</id_name>
<Relationships>
<Relationship>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test3</id_name>
<Relationships>
<Relationship>
<rel_no>006</rel_no>
</Relationship>
</Relationships>
</Inc>
</Data>
Accessing in a loop
foreach (var record in query)
{
}
var xdata = XDocument.Parse(data);
var items = xdata.Descendants("Inc")
.Select(d => new
{
DName = (string)d.Element("id_name"),
RelNo = ((string)d.Descendants("rel_no").FirstOrDefault() ?? "")
})
.ToList();

Read specific element from XElement

I want to bind the MultipleCheckbox items from Choice Column of sharepoint List to asp.net CheckBoxListItem using c#. I am retriving information of List using XELEMENT as:
XElement listStructure;
listStructure = proxy.GetList("WebsiteSubscriber");
<Field Type="MultiChoice" DisplayName="Area" Required="FALSE" EnforceUniqueValues="FALSE" Indexed="FALSE" FillInChoice="FALSE" ID="{16cc1615-a490-44de-a870-c7ebe603e2cc}" SourceID="{2c8a80ea-38c5-48f7-9d7d-400d445a5e64}" StaticName="Area" Name="Area" ColName="ntext2" RowOrdinal="0">
<Default>Articles</Default>
<CHOICES>
<CHOICE>Articles</CHOICE>
<CHOICE>Websites</CHOICE>
<CHOICE>Books</CHOICE>
</CHOICES>
</Field>
I want to read choices from this XML. and get values only "Articles,Websites,Books"
Note: This XML may contain many sections with <choices> i want to fetech it by <fieldType> or the DisplayName="Area" attribute and get values in c#.
var xml = #"<Field Type=""MultiChoice"" DisplayName=""Area"" Required=""FALSE"" EnforceUniqueValues=""FALSE"" Indexed=""FALSE"" FillInChoice=""FALSE"" ID=""{16cc1615-a490-44de-a870-c7ebe603e2cc}"" SourceID=""{2c8a80ea-38c5-48f7-9d7d-400d445a5e64}"" StaticName=""Area"" Name=""Area"" ColName=""ntext2"" RowOrdinal=""0"">
<Default>Articles</Default>
<CHOICES>
<CHOICE>Articles</CHOICE>
<CHOICE>Websites</CHOICE>
<CHOICE>Books</CHOICE>
</CHOICES>
</Field>";
var doc = XDocument.Parse(xml);
XElement element =
doc.Descendants("Field")
.First(field => (string)field.Attribute("DisplayName") == "Area");
string[] result =
element.Descendants("CHOICE")
.Select(v => (string)v)
.ToArray();
Console.WriteLine(string.Join(Environment.NewLine, result));
prints:
Articles
Websites
Books
Try this:
var temp =
listStructure.Descendants("Field")
.Where(i => i.Attribute("DisplayName").Value == "Area")
.Select(i => i.Descendants("CHOICE")
.Select(j => j.Value)).ToList();
List<string> result = new List<string>();
foreach (IEnumerable<string> item in temp)
{
result.AddRange(item);
}
//result: Articles; Websites; Books

Update XML file with Linq

I'm having trouble trying to update my xml file with a new value. I have a class Person, which only contains 2 strings, name and description. I populate this list and write it as an XML file. Then I populate a new list, which contains many of the same names, but some of them contains descriptions that the other list did not contain. How can I check if the name in the current XML file contains a value other than "no description", which is the default for "nothing"?
Part of the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Names>
<Person ID="2">
<Name>Aaron</Name>
<Description>No description</Description>
</Person>
<Person ID="2">
<Name>Abdi</Name>
<Description>No description</Description>
</Person>
</Names>
And this is the method for writing the list to the xml file:
public static void SaveAllNames(List<Person> names)
{
XDocument data = XDocument.Load(#"xml\boys\Names.xml");
foreach (Person person in names)
{
XElement newPerson = new XElement("Person",
new XElement("Name", person.Name),
new XElement("Description", person.Description)
);
newPerson.SetAttributeValue("ID", GetNextAvailableID());
data.Element("Names").Add(newPerson);
}
data.Save(#"xml\boys\Names.xml");
}
In the foreach loop how do I check if the person's name is already there, and then check if the description is something other than "no description", and if it is, update it with the new information?
I'm not sure I understand properly what you want, but I'm assuming you want to update the description only when the name is already there and the description is currently No description (which you should probably change to an empty string, BTW).
You could put all the Persons into a Dictionary based by name:
var doc = …;
var persons = doc.Root.Elements()
.ToDictionary(x => (string)x.Element("Name"), x => x);
and then query it:
if (persons.ContainsKey(name))
{
var description = persons[name].Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
That is, if you care about performance. If you don't, you don't need the dictionary:
var person = doc.Root.Elements("Person")
.SingleOrDefault(x => (string)x.Element("Name") == name);
if (person != null)
{
var description = person.Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
You can use the Nodes-Method on XElement and check manually.
But i will advise you to use the XPathEvaluate-Extension Method
For XPath expression take a look at:
How to check if an element exists in the xml using xpath?
I think you could create a peoplelist which only contains people not in the xml.
like ↓
var containlist = (from p in data.Descendants("Name") select p.Value).ToList();
var result = (from p in peoplelist where !containlist.Contains(p.Name) select p).ToList();
so that , you would no need to change anything with your exist method ...
just call it after..
SaveAllNames(result);

How can I rewrite this Linq to XML query?

I am preferring LINQ to XML over XMLReader because it feels much easier to use. However, I know I'm doing it wrong somewhere. I'm not looking for faster execution or anything, just a cleaner rewrite. I can't tell if it'll fit cleanly into the from foo in bar where foo is some condition form, but there's got to be a cleaner way.
I am not using anonymous objects here and instead outputting as strings because I am excluding some code which will adapt the data into existing objects - however, it is kind of a messy structure currently which I need to refactor.
My XML looks like this.
<?xml version="1.0" encoding="utf-8" ?>
<entity name="Something">
<Component type="RenderComponent">
<Material type="string">fur</Material>
<Reflectiveness type="int">678</Reflectiveness>
</Component>
<Component type="HealthComponent">
<Sound type="int">60</Sound>
</Component>
</entity>
And my code:
static void Main(string[] args)
{
XDocument xdoc = XDocument.Load(#"..\..\XMLFile1.xml");
List<string> comNames = new List<string>();
Dictionary<string, string> paramValues = new Dictionary<string, string>();
List<string> paramTypes = new List<string>();
foreach (XElement e in xdoc.Root.Elements("Component"))
{
string type = e.Attribute("type").Value;
foreach(XElement param in e.Elements())
{
paramValues.Add(param.Name.LocalName, param.Value);
paramTypes.Add(param.Attributes().First().Value);
}
Console.WriteLine(" \n\n\n");
comNames.Add(type);
}
}
Giving outputs of:
comNames - RenderComponent, HealthComponent
paramValues - (Material, fur), (Reflectiveness, 678), (Sound, 60)
paramTypes - string, int, int
If it clears it up somewhat, the general layout of the file:
Root node entity with a name attribute (forgot in this example)
n Component nodes with type attributes
Each Component node has a number of child nodes which have names, a type attribute, and a value.
I think, you can do something like this:
XDocument xdoc = XDocument.Load(#"..\..\XMLFile1.xml");
var data =
xdoc.Root.Elements("Component")
.SelectMany(e => new
{
type = e.Attribute("type").Value,
paramValues = e.Elements()
.Select(x => new KeyValuePair<string,
string>
(x.Name.LocalName,
x.Value)),
paramType = e.Elements()
.Select(x => x.Attributes().First()
.Value)
});
List<string> comNames = data.Select(x => x.type).ToList();
List<string> paramTypes = data.Select(x => x.paramType).ToList();
Dictionary<string, string> paramValues = data.Select(x => x.paramValues)
.ToDictionary(x => x.Key,
x => x.Value);
But honestly, I think your original code is just fine.

Categories

Resources