Flatten results from hierachical XML file using Linq to XML - c#

I currently have the following C# with Linq to XML code working but I'm hoping there is a better way to form the Linq query to then be able to extract the results into a flat structure.
Here are the current results formatted as I would like them to appear:
7990697: result_status = Complete
7990756: result_status = Incomplete
I want to be able to access the results using the syntax result.resultStatusId instead of result.meta.ToList()[0].resultStatusId
var xDoc = XDocument.Parse(#"<fs_response status='success' timestamp='2014-07-10 14:38:39'>
<headings>
<heading for='result_status'>Result Status</heading>
<heading for='1'>First Name</heading>
<heading for='2'>Last Name</heading>
</headings>
<results>
<result id='7990697'>
<metas>
<meta id='result_status'>Complete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Bugs</value>
</item>
<item id='2' index='4' type='text'>
<value>Bunny</value>
</item>
</items>
</result>
<result id='7990756'>
<metas>
<meta id='result_status'>Incomplete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Yogi</value>
</item>
<item id='2' index='4' type='text'>
<value>Bear</value>
</item>
</items>
</result>
</results>
<total_results>2</total_results>
</fs_response>");
var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
select new
{
id = x.Attribute("id").Value,
meta = from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
}
};
results.Dump();
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.ToList()[0].resultStatusId,
result.meta.ToList()[0].resultStatus);
//Console.WriteLine("{0}: {1} = {2}",
// result.id,
// result.resultStatusId,
// result.resultStatus);
}

I would change the query to something like this:
var results = xDoc.Element("fs_response")
.Element("results")
.Elements("result")
.Select (e => new
{
id = e.Attribute("id").Value,
meta = e.Element("metas")
.Elements("meta")
.Where (m =>
m.Attribute("id").Value == "result_status")
.Select (m => new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
})
.Single()
});
Some notes:
Notice the change in a few locations from Elements to Element. This indicates that you know there will only be one result with that name, or if you only want the first with that name. Use Elements when you expect multiple results. In some cases in the example above, I'm making assumptions about your xml schema (such as the fact that there will only be one metas Element per result). This may perform faster as it will not have to continue searching for Elements of a specific name once it has found one.
Also see the use of Single(). This indicates that you are expecting one and only one element with the id attribute equal to result_status. If you expect 0 or 1, you can use SingleOrDefault(), or if you want the first in a potential list of matching elements you can use First() or FirstOrDefault(). Once you add this, you will no longer have to call ToList()[0] in the results:
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.resultStatusId,
result.meta.resultStatus);

var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
id = x.Attribute("id").Value,
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value,
};
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}", result.id, result.resultStatusId, result.resultStatus);
}

Related

C# Linq Get XML Value On Condition

I'm having problems finding the correct syntax to use.
I hope the code below will demonstrate the problem better than I can explain it in words. I am trying to retrieve an anonymous type based on matching conditions. I have to first check the Attribute for a match, and return the value only if the condition is met. If the element has an attribute named "value1" I get back the value for the attribute named "value" on the same element.
I put arrows <--- to show pseudocode where the problem is.
Thanks in advance to anyone that can help.
<item name="DataStore" >
<int name="item1" value="895"/>
<int name="item2" value="245"/>
</item>
<item name="DataStore" >
<int name="item1" value="540"/>
<int name="item2" value="97"/>
</item>
var result = from items in doc.Descendants()
where (string)items.Attribute("name") == "DataStore"
select new {
item1Value = items.Attribute("value").Value, <--- where items.Attribute("name") == "item1" ???
item2Value = items.Attribute("value").Value <--- where items.Attribute("name") == "item2" ???
}
To be more clear I need to return a "Tuple" result that has result.value1 and result.value2
So in the end I want to say something like:
foreach (var item in result) {
Console.WriteLine(item.value1); //Prints 895 on first iteration
Console.WroteLine(item.value2); //Prints 245 on first iteration
}
You need to select the child items from the items that you found that matched the DataStore and filter those based on your criteria.
var xml = #"<root><item name=""DataStore"" >
<int name=""item1"" value=""895""/>
<int name=""item2"" value=""245""/>
</item>
<item name=""DataStore"" >
<int name=""item1"" value=""540""/>
<int name=""item2"" value=""97""/>
</item></root>";
var doc = XDocument.Parse(xml);
var result = from items in doc.Descendants()
where (string)items.Attribute("name") == "DataStore"
select new
{
item1Value = (from d in items.Descendants() where d.Attributes("name").FirstOrDefault().Value.Equals("item1") select d.Attributes("value").FirstOrDefault().Value).FirstOrDefault(),
item2Value = (from d in items.Descendants() where d.Attributes("name").FirstOrDefault().Value.Equals("item2") select d.Attributes("value").FirstOrDefault().Value).FirstOrDefault()
};
foreach (var r in result)
{
Console.WriteLine($"Result {r.item1Value} {r.item2Value}");
}
Console.ReadLine();

Deserialize key/value xml into an object

Having some difficulties deserializing the following xml to an object in the most efficient/cleanest possible way:
<item xsi:type="ns2:Map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<key xsi:type="xsd:string">mailinglistid</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">uniqueid</key>
<value xsi:type="xsd:string">1a0d0d2195</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">OSM NL</value>
</item>
</item>
This is a single item along with its properties. As you can see the properties are defined as key/value pairs.
Because they are key value pairs i could not find a way to use the Xml deserialize attributes (as described in this question: How to deserialize xml to object )
Therefore i have created the following alternative:
// Retrieves all the elements from the "return" node in the Mailinglist_allResponse node.
var items = response.ResponseFromServer.Descendants(_engineResponseNamespace + "Mailinglist_allResponse").Descendants("return").Elements();
foreach (var item in items)
{
var values = item.Descendants("item").ToArray();
var list = new EngineMailingList
{
MailingListId =
values.Descendants("key")
.First(v => v.Value == "mailinglistid")
.ElementsAfterSelf("value")
.First()
.Value,
UniqueId =
values.Descendants("key")
.First(v => v.Value == "uniqueid")
.ElementsAfterSelf("value")
.First()
.Value,
Name =
values.Descendants("key")
.First(v => v.Value == "name")
.ElementsAfterSelf("value")
.First()
.Value,
};
result.Add(list);
As you can see this is alot more code than when using the deserialize attributes. Is there any way i could still use these attributes so that my code could be cleaner/ efficient? i am going to have to make alot of these functions otherwise.
I don't think attributes will work because of key/value structure. There is no way for a program to infer from the xml alone what properties an object has. I would make a static extension method helper function to get the values:
public static string GetValue(this XElement element, string key){
return values.Descendants("key")
.First(v => v.Value == key)
.ElementsAfterSelf("value")
.First()
.Value;
}
That way your code could look like this:
MailingListId = values.GetValue("mailinglistid"),
UniqueId = values.GetValue("uniqueid"),
Name = values.GetValue("name")

Get XML nodes attributes and set as properties of List<myType>?

Example XML:
<Root>
<Product value="Candy">
<Item value="Gum" price="1.00"/>
<Item value="Mints" price="0.50"/>
</Product>
</Root>
Let's say I have a class with properties:
public class CandyItems
{
public string Value{get; set;}
public string Price{get; set;}
}
And within my main program class, I have a list:
var Candies = new List<CandyItems>;
I am struggling with a concise way to populate the Candies list, using LINQ.
I could do it in steps, like this:
//Get list of Items within <Product value="Candy">
XElement tempCandies = XDocument.Load("file.xml").Root.Elements("Product").Single(c => c.Attributes("value") == "Candy").Descendants("Item");
//Loop through the elements
foreach(var item in tempCandies){
Candies.Add(new CandyItems{Value = item.Attributes("value"), Price = item.Attributes("price")});
}
But it seems like I could do this more concisely with pure LINQ somehow. Or is there another recommended method?
Try this:-
XDocument xdoc = XDocument.Load(#"Path\Candies.xml");
List<CandyItems> Candies = xdoc.Descendants("Item")
.Select(x => new CandyItems
{
Value = (string)x.Attribute("value"),
Price = (string)x.Attribute("price")
}).ToList();
Although, you have not mentioned but if you want to just fetch Candies and your XML may contain other products too like:-
<Root>
<Product value="Candy">
<Item value="Gum" price="1.00"/>
<Item value="Mints" price="0.50"/>
</Product>
<Product value="Chocolate">
<Item value="MilkChocolate" price="7.00"/>
<Item value="DarkChocolate" price="10.50"/>
</Product>
</Root>
Then you can apply a filter to fetch only Candy products like this:-
List<CandyItems> Candies = xdoc.Descendants("Item")
.Where(x => (string)x.Parent.Attribute("value") == "Candy")
.Select(x => new CandyItems
{
Value = (string)x.Attribute("value"),
Price = (string)x.Attribute("price")
}).ToList();
How about something like this (after loading the document):
var candies =
xdoc.Root.Elements("Product")
.Where(p => p.Attribute("value").Value == "Candy")
.SelectMany(p => p.Descendants("Item").Select(i => new CandyItems {
Value = i.Attribute("value").Value,
Price = i.Attribute("price").Value }));
Note: any and all error handling omitted.

Retrieving XML attributes of unknown quantity

I have requirement to read an XML file. I've never done anything with XML so it's all new territory for me. Please refer to the below XML sample.
-
<GPO xmlns="http://www.microsoft.com/GroupPolicy/Settings" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">+
<Identifier>
<Name>GB Local Server Access</Name>
<IncludeComments>true</IncludeComments>
<CreatedTime>2011-08-03T11:58:18</CreatedTime>
<ModifiedTime>2011-08-03T12:13:41</ModifiedTime>
<ReadTime>2014-10-21T11:32:49.5863908Z</ReadTime>+
<SecurityDescriptor>----------------------------
<FilterDataAvailable>true</FilterDataAvailable>-
<Computer>
<VersionDirectory>18</VersionDirectory>
<VersionSysvol>18</VersionSysvol>
<Enabled>true</Enabled>-
<ExtensionData>-
<Extension xsi:type="q1:SecuritySettings" xmlns:q1="http://www.microsoft.com/GroupPolicy/Settings/Security">-
<q1:RestrictedGroups>-
<q1:GroupName>
<SID xmlns="http://www.microsoft.com/GroupPolicy/Types">S-1-5-21-1547161642-1214440339-682003330-1141792</SID>
<Name xmlns="http://www.microsoft.com/GroupPolicy/Types">CRB\DKCPHGITSCOM</Name>
</q1:GroupName>-
<q1:Memberof>
<SID xmlns="http://www.microsoft.com/GroupPolicy/Types">S-1-5-32-544</SID>
<Name xmlns="http://www.microsoft.com/GroupPolicy/Types">BUILTIN\Administrators</Name>
</q1:Memberof>
</q1:RestrictedGroups>-
<q1:RestrictedGroups>-
<q1:GroupName>
<SID xmlns="http://www.microsoft.com/GroupPolicy/Types">S-1-5-21-1547161642-1214440339-682003330-1151</SID>
<Name xmlns="http://www.microsoft.com/GroupPolicy/Types">CRB\GB Administrators</Name>
</q1:GroupName>-
<q1:Memberof>
<SID xmlns="http://www.microsoft.com/GroupPolicy/Types">S-1-5-32-544</SID>
<Name xmlns="http://www.microsoft.com/GroupPolicy/Types">BUILTIN\Administrators</Name>
</q1:Memberof>
</q1:RestrictedGroups>
Please could you advise on the simplest method that I can use that will allow me to drill down to GPO.Computer.ExtensionData.Extension.RestrictedGroups and then FOR EACH instance of RestrictedGroups return the value of GroupName.Name and MemberOf.Name. I can then incorporate the logic to get this data into an array of some sort ready to be output.
You should use LINQ to XML.
Something like:
//preparing the reusable XName instances:
var q1Namespace = "http://www.microsoft.com/GroupPolicy/Settings/Security";
var groupNameElementName = XName.Get("GroupName", q1Namespace);
var memberOfElementName = XName.Get("Memberof", q1Namespace);
var nameElementName = XName.Get("Name", "http://www.microsoft.com/GroupPolicy/Types");
var data = XDocument.Load(filePath)
.Descendants(XName.Get("RestrictedGroups", q1Namespace))
.Select(group =>
new
{
GroupName =
group.Descendants(groupNameElementName)
.Select(gn => gn.Element(nameElementName).Value)
.FirstOrDefault(),
MemberOfName =
group.Descendants(memberOfElementName)
.Select(gn => gn.Element(nameElementName).Value)
.FirstOrDefault()
});
Then use it in foreach loop:
foreach (var d in data)
{
Console.WriteLine("Group name: {0}, member of name: {1}", d.GroupName, d.MemberOfName);
}
If you'd like to make it safe and provide default values for the names, use something like the below instead of the Select call which finds the Name element:
...
.SelectMany(gn => gn.Elements(nameElementName))
.Select(elem => elem.Value ?? "Name not found")
.DefaultIfEmpty("Name not found")
.FirstOrDefault()
This way you will protect yourself from the cases where the Name element does not exist or has no value.

C# Linq XML, check for specific value and parse to array

I have the following XML:
<?xml version="1.0" ?>
<NewDataSet>
<Data>
<ElementDefinition>
<ID>1</ID>
<QUANTITY>0</QUANTITY>
</ElementDefinition>
<ElementDefinition>
<ID>2</ID>
<QUANTITY>1</QUANTITY>
</ElementDefinition>
</Data>
</NewDataSet>
I need to create an array which contains all ElementDefinitions which contain a QUANTITY element with a value other then 0.
I tried:
var f = XDocument.Load(path);
var xe = f.Root.Elements("QUANTITY").Where(x => x.Value != "0").ToArray();
But that doesn't seem to work. With the above XML the array should contain 1 item, but it stays 0.
After that I need to create a string for each ElementDefinition in the array, the string must contain the value of the corresponding ID element.
For that I tried:
foreach (string x in xe)
{
string ID = //not sure what to do here
}
You want something like this:
var ids = f.Root.Descendants("ElementDefinition")
.Where(x => x.Element("QUANTITY").Value != "0")
.Select(x => x.Element("ID").Value);
As you want the ID, it is not very helpful to select all QUANTITY nodes. Instead, select exactly what you specified in your question:
All ElementDefinitions (Descendants("ElementDefinition")), that have a QUANTITY with a Value other than 0 (Where(x => x.Element("QUANTITY").Value != "0"). From the resulting nodes, select the ID (Select(x => x.Element("ID").Value)).
Yo can replace with
var xe = f.Root.Elements("Data/ElementDefinition/QUANTITY").Where(x => x.Value != "0").ToArray();

Categories

Resources