Update XML file with Linq - c#

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

Related

Store and Display xml values in MessageBox

<?xml version="1.0" standalone="yes"?>
<Subject>
<Book>
<Name>ASP.NET</Name>
<Author>ABC</Author>
<Published>2018</Published>
<Price>$100</Price>
</Book>
</Subject>
Above is the xml file i have. I want to store xml nodes and values using Dictionary or Collections in C# and display those on message box using winforms.
Output should be as:
Name: ASP.NET
Author: ABC
Published: 2018
Price: $100
I have tried the following but getting lots of errors...
var doc = XDocument.Load(#"xmlfile.xml");
var rootNodes = doc.Root.DescendantNodes().OfType<XElement>();
var keyValuePairs = from n in rootNodes
select new
{
TagName = n.Name,
TagValue = n.Value
};
var allitems = new Dictionary<string, string>();
foreach (var node in rootNodes)
{
allitems.Add(node.Name.ToString(), node.Value);
//string str = string.Join("",allitems);
MessageBox.Show(allitems);
}
You should first parse the XML into objects (Convert XML String to Object).
Then you can simply implement the ToString() method of that type, to print it up nicely.
I think for this part, you need to convert it into .cs file and XML cannot retrieve the messagebox...
Inside of the .cs file, you also can try to put the code for messagebox
MessageBox.Show( // put something over there and you want to show this output );

how to print the innertext of an element based on attribute search of a particular node using LINQ?

**I have an XML like this-
<?xml version="1.0" encoding="UTF-8"?>
<Tool_Parent>
<tool name="ABCD" id="226">
<category>Centralized</category>
<extension_id>0</extension_id>
<uses_ids>16824943 16824944</uses_ids>
</tool>
<tool name="EFGH" id="228">
<category>Automated</category>
<extension_id>0</extension_id>
<uses_ids>92440 16824</uses_ids>
</tool>
</Tool_Parent>
Based on the id of tool i want to print the uses_ids value,i.e if i search for 228 i should get 92440 16824.
I had tried like-
var toolData = (from toolElement in doc.Descendants("tool")
select new Tool_poco
{
a_Name = tool.Attribute("name").Value,
a_Id = tool.Attribute("id").Value,
e_ExtensionId = tool.Element("extension_id").Value,
e_UsesIds =tool.Element("uses_parm_ids").Value
});
where Tool_poco is a poco class for tool node containing declaration for member variable.
Now I want to get information related to a particular tool id in toolData variable.How to do it?
Note: I have variable like-
searched_Tool_id = Tool_Id_txtBx.Text.ToString();
Please let me know a way through which i can modify my above query for toolData.**
You can modify your query as
Tool_poco toolData = (from el in xelement.Elements("Employee")
where (string)el.Attribute("id") == "226"
select new Tool_poco
{
a_Name = el.Attribute("name").Value,
a_Id = el.Attribute("id").Value,
e_ExtensionId = el.Element("Name").Value,
e_UsesIds = el.Element("uses_ids").Value
}).FirstOrDefault();
You could start by doing something like this once you have an XDocument object loaded and ready:
var xdoc = XDocument.Parse(
#"<?xml version=""1.0"" encoding=""utf-8""?>
<Tool_Parent>
<tool name=""ABCD"" id=""226"">
<category>Centralized</category>
<extension_id>0</extension_id>
<uses_ids>16824943 16824944</uses_ids>
</tool>
<tool name=""EFGH"" id=""228"">
<category>Automated</category>
<extension_id>0</extension_id>
<uses_ids>92440 16824</uses_ids>
</tool>
</Tool_Parent>");
var root = xdoc.Root; // Got to have that root
if (root != null)
{
var id228query = (from toolElement in root.Elements("tool")
where toolElement.HasAttributes
where toolElement.Attribute("id").Value.Equals("228")
let xElement = toolElement.Element("uses_ids")
where xElement != null
select xElement.Value).FirstOrDefault();
Console.WriteLine(id228query);
Console.Read();
}
Output: 92440 16824
**Note: In reference to your example, one possible reason it was not working
for you could be that your xml references an element with name "uses_ids",
however, your query references an element with a similar, but not exact,
spelling with name "uses_parm_ids".**

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.

Linq to XML - Find an element

I am sure that this is basic and probably was asked before, but I am only starting using Linq to XML.
I have a simple XML that i need to read and write to.
<Documents>
...
<Document>
<GUID>09a1f55f-c248-44cd-9460-c0aab7c017c9-0</GUID>
<ArchiveTime>2012-05-15T14:27:58.5270023+02:00</ArchiveTime>
<ArchiveTimeUtc>2012-05-15T12:27:58.5270023Z</ArchiveTimeUtc>
<IndexDatas>
<IndexData>
<Name>Name1</Name>
<Value>Some value</Value>
<DataType>1</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
<IndexData>
<Name>Name2</Name>
<Value>Some value</Value>
<DataType>3</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
...
</IndexDatas>
</Document>
...
</Documents>
I have a "Documents" node that contains bunch of "Document" nodes.
I have GUID of the document and a "IndexData" name.
I need to find the document by GUID and check if it has "IndexData" with some name.
If it does not have it i need to add it.
Any help would be apreciated, as i have problem with reading and searching trough elements.
Currently I am trying to use (in C#):
IEnumerable<XElement> xmlDocuments = from c in XElement
.Load(filePath)
.Elements("Documents")
select c;
// fetch document
XElement documentElementToEdit = (from c in xmlDocuments where
(string)c.Element("GUID").Value == GUID select c).Single();
EDIT
xmlDocuments.Element("Documents").Elements("Document")
This returns no result, even tho xmlDocuments.Element("Documents") does. It looks like i cant get Document nodes from Documents node.
You can find those docs (docs without related name in index data) with below code, after that you could add your elements to the end of IndexData elements.
var relatedDocs = doc.Elements("Document")
.Where(x=>x.Element("GUID").Value == givenValue)
.Where(x=>!x.Element("IndexDatas")
.Elements("IndexData")
.Any(x=>x.Element("Name") == someValue);
This should work:
var x = XDocument.Load(filePath);
// guid in your sample xml is not a valid guid, so I changed it to a random valid one
var requiredGuid = new Guid("E61D174C-9048-438D-A532-17311F57ED9B");
var requiredName = "Name1";
var doc = x.Root
.Elements("Document")
.Where(d => (Guid)d.Element("GUID") == requiredGuid)
.FirstOrDefault();
if(doc != null)
{
var data = doc.Element("IndexDatas")
.Elements("IndexData")
.Where(d => (string)d.Element("Name") == requiredName)
.FirstOrDefault();
if(data != null)
{
// index data found
}
else
{
// index data not found
}
}
else
{
// document not found
}

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

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.

Categories

Resources