Get attributes Name and Value of element in C# through System.Linq - c#

I have one custom config file.
<Students>
<student>
<Detail Name="abc" Class="1st Year">
<add key="Main" value="web"/>
<add key="Optional" value="database"/>
</Detail>
</student>
</Students>
I read this file through the IConfigurationHandler interface implementation.
When I read the childNode attributes of Detail element. It return me below result into Immediate Window of IDE.
elem.Attributes.ToObjectArray()
{object[2]}
[0]: {Attribute, Name="key", Value="Main"}
[1]: {Attribute, Name="value", Value="web"}
When I try to write on Console
Console.WriteLine("Value '{0}'",elem.Attributes.ToObjectArray());
it does return me
Value : 'System.Configuration.ConfigXmlAttribute'
elem.Attributes.Item(1) method gives me the Name and Value detail but here I need to pass the index value of attribute which I don't know currently.
I want to get Name and value of attribute through LINQ query and individual display on Console for each childNode attribute as follows:
Value : Name="Key" and Value="Main"
Name="value", Value="web"
How can I achieve that?

If you want to use this Xml Library you can get all the students and their details with this code:
XElement root = XElement.Load(file); // or .Parse(string)
var students = root.Elements("student").Select(s => new
{
Name = s.Get("Detail/Name", string.Empty),
Class = s.Get("Detail/Class", string.Empty),
Items = s.GetElements("Detail/add").Select(add => new
{
Key = add.Get("key", string.Empty),
Value = add.Get("value", string.Empty)
}).ToArray()
}).ToArray();
Then to iterate over them use:
foreach(var student in students)
{
Console.WriteLine(string.Format("{0}: {1}", student.Name, student.Class));
foreach(var item in student.Items)
Console.WriteLine(string.Format(" Key: {0}, Value: {1}", item.Key, item.Value));
}

You can use a Linq Select and string.Join to get the output you want.
string.Join(Environment.NewLine,
elem.Attributes.ToObjectArray()
.Select(a => "Name=" + a.Name + ", Value=" + a.Value)
)

This will get all the attributes of the children of the Detail element as you state in your question.
XDocument x = XDocument.Parse("<Students> <student> <Detail Name=\"abc\" Class=\"1st Year\"> <add key=\"Main\" value=\"web\"/> <add key=\"Optional\" value=\"database\"/> </Detail> </student> </Students>");
var attributes = x.Descendants("Detail")
.Elements()
.Attributes()
.Select(d => new { Name = d.Name, Value = d.Value }).ToArray();
foreach (var attribute in attributes)
{
Console.WriteLine(string.Format("Name={0}, Value={1}", attribute.Name, attribute.Value));
}

If you have the Attributes in an object[] as you wrote, which can be mocked by
var Attributes = new object[]{
new {Name="key", Value="Main"},
new {Name="value", Value="web"}
};
then the issue is that you have anonymous types whose names can't be extracted easily.
Take a look at this code (you can paste it into the main() method of a LinqPad editor window to execute it):
var linq=from a in Attributes
let s = string.Join(",",a).TrimStart('{').TrimEnd('}').Split(',')
select new
{
Value = s[0].Split('=')[1].Trim(),
Name = s[1].Split('=')[1].Trim()
};
//linq.Dump();
Since you cannot access the Name and Value properties of the variable Attributes inside the object[] array because the compiler hides them from you you, the trick is here to use the Join(",", a) method to get around this limitation.
All you need to do afterwards is to trim and split the resulting string and finally create a new object with Value and Name properties.
You can try it out if you uncomment the linq.Dump(); line in LinqPad - it returns what you want and it is furthermore queryable by Linq statements.

Related

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.

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();

How properly work with LINQ to XML?

I have generated such xml file
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>212</numRequest>
<IDWork>12</IDWork>
<NumObject>21</NumObject>
<lvlPriority>2</lvlPriority>
<NumIn1Period>21</NumIn1Period>
</Single_Request>
</Requestes>
My aim is to get IDWork,numRequest and etc elements. I tried to get them this way:
foreach (XElement el in doc.Root.Elements())
{
if (el.Name == "Single_Request")
{
string num = el.Elements("numRequest").Value;
// but he says, that he cant do this .Value because it doest exist at all
}
}
How to fix this?
You have this error, because Elements("numRequest") returns collection of elements, not single element. You should use Element("numRequest") instead.
Also I suggest you to use query for getting elements by name instead of enumerating all elements and verifying their names:
var request = doc.Root.Element("Single_Request");
var num = (int)request.Element("numRequest");
Usually you use anonymous types or custom objects to group values parsed from xml:
var query = from r in doc.Root.Elements("Single_Request")
where (int)r.Attribute("num") == 1 // condition
select new {
NumRequest = (int)request.Element("numRequest"),
IdWork = (int)request.Element("IDWork"),
NumObject = (int)request.Element("NumObject")
};
var request = query.SinlgleOrDefault();
// use request.IdWork

Best way to query XDocument with LINQ?

I have an XML document that contains a series of item nodes that look like this:
<data>
<item>
<label>XYZ</label>
<description>lorem ipsum</description>
<parameter type="id">123</parameter>
<parameter type="name">Adam Savage</parameter>
<parameter type="zip">90210</parameter>
</item>
</data>
and I want to LINQ it into an anonymous type like this:
var mydata =
(from root in document.Root.Elements("item")
select new {
label = (string)root.Element("label"),
description = (string)root.Element("description"),
id = ...,
name = ...,
zip = ...
});
What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?
(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:
If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
You may want to pull all the values at once and convert them into a dictionary
If you're using different data types for the contents, you might want to cast the element instead of using .Value
You may want to create a method to return the matching XElement for a given type, instead of having several queries.
Personally I don't think I'd even use a query expression for this. For example:
static XElement FindParameter(XElement element, string type)
{
return element.Elements("parameter")
.SingleOrDefault(p => (string) p.Attribute("type") == type);
}
Then:
var mydata = from item in document.Root.Elements("item")
select new {
Label = (string) item.Element("label"),
Description = (string) item.Element("description"),
Id = (int) FindParameter(item, "id"),
Name = (string) FindParameter(item, "name"),
Zip = (string) FindParameter(item, "zip")
};
I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.
use XPATH - it is very fast ( except xmlreader - but a lot of if's)
using (var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[#type='id']");
foreach (var x in query.Cast<XElement>())
{
Console.WriteLine( x.Value );
}
}

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);

Categories

Resources