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
}
Related
I searched a long time in order to get an answer but as i can see is not working.
I have an XML File and I would like to read a specific element from a node.
For example, this is the XML:
<Root>
<TV>
<ID>2</ID>
<Company>Samsung</Company>
<Series>13523dffvc</Series>
<Dimesions>108</Dimesions>
<Type>LED</Type>
<SmartTV>Yes</SmartTV>
<OS>WebOS</OS>
<Price>1993</Price>
</TV>
</Root>
I want to get the ID element in the code as a variable so i can increment it for the next item which i will add.
This is the code at this moment, but i can not find a way to select something from the item itself.
XDocument doc = XDocument.Load("C:TVList.XML");
XElement TV = doc.Root;
var lastElement = TV.Elements("TV").Last()
A query for the last TV's id (this will return 0 if there are no elements):
var lastId = (int) doc.Descendants("TV")
.Elements("ID")
.LastOrDefault();
You might also want the highest id (in case they're not in order):
var maxId = doc.Descendants("TV")
.Select(x => (int)x.Element("ID"))
.DefaultIfEmpty(0)
.Max();
See this fiddle for a working demo.
Use like this to get id value
XDocument doc = XDocument.Load(#"C:\TVList.XML");
XElement root = doc.Element("Root");
XElement tv = root.Element("TV");
XElement id = tv.Element("ID");
string idvalue = id.Value;
also make your <Type>LED</Tip> tag of xml to <Type>LED</Type> for match
I have a very basic linq query to be able to delete one node from xml file.But when ı run this code I am getting this exception below.
Sequence contains no elements
then I have used FirstOrDefault() instead First() ( as mentioned earlier posts) and this time Exection message turned into this
Object reference not set to an instance of an object.
This my code
protected void Page_Load(object sender, EventArgs e)
{
XDocument doc = XDocument.Load(Server.MapPath("Kitaplar.xml"));
var toDelete = (from data in doc.Elements("Kitap") where data.Attribute("id").Value == "1" select data).FirstOrDefault();
toDelete.Remove();
doc.Save(Server.MapPath("Kitaplar.xml"));
}
And this is the xmlfile
<?xml version="1.0" encoding="utf-8"?>
<Kitaplar>
<Kitap id="1">
<Kitapadi>asasa</Kitapadi>
<Yazar>sasas</Yazar>
<Sayfa>22</Sayfa>
</Kitap>
<Kitap id="2">
<Kitapadi>jhjh</Kitapadi>
<Yazar>kjkj</Yazar>
<Sayfa>33</Sayfa>
</Kitap>
<Kitap id="3">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
<Kitap id="4">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
</Kitaplar>
Everyting looks ok to me.what am i doing wrong ?
I took the liberty of writing a method that takes the file, element and ID; then removes the Element accordingly.
private bool DeleteRowWithID(string fileName, string element, string id)
{
XDocument doc = XDocument.Load(fileName);
if (doc.Root == null)
return false;
XElement toRemove = doc.Root.Elements(element).Where(e => e.Attribute("id").Value == id).FirstOrDefault();
if (toRemove == null)
return false;
toRemove.Remove();
doc.Save(fileName);
return true;
}
Above method loads the XmlDocument in an XDocument (which allows LINQ to XML). It checks if the root isn't empty, then finds the element you specified.
It checks if the element exists; then removes that element from the document, and save the made removal.
Last, it returns true to indicate that the element has actually been removed.
If you just want the element and stick to your method, use the following:
XElement toRemove = doc.Root.Elements("Kitap").Where(e => e.Attribute("id").Value == "1").FirstOrDefault();
I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)
I have the following XDocument called XDoc:
<?xml version="1.0" encoding="utf-8"?>
<DatabaseList>
<Database DatabaseName="c2501_data">
<Plugin PluginName="FooPlugin" LastRun="1/21/2013 3:22:08 PM" />
<Plugin PluginName="SpecialPlugin" LastRun="2013-01-21T15:22:09.3791103-05:00" />
<Plugin PluginName="BarPlugin" LastRun="2013-01-21T15:23:13.0964814-05:00" />
</Database>
</DatabaseList>
I'm writing a program that searches to see when the last time a plugin was run on a database, if at all. I use the following two pieces of code to figure out if an entry exists for a plugin on a database:
var h = (from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value=="FooPlugin"
&& el.Attribute("DatabaseName").Value=="c2501_data"
select el.Element("Plugin"));
var e = (from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value=="BarPlugin"
&& el.Attribute("DatabaseName").Value == "c2501_data"
select el.Element("Plugin"));
if ((from el in XDoc.Root.Elements("Database")
where el.Element("Plugin").Attribute("PluginName").Value == "BarPlugin"
&& el.Attribute("DatabaseName").Value == "c2501_data"
select el.Element("Plugin")).Count() == 0)
{
XElement SpecialPlugin = new XElement("Plugin",
new XAttribute("PluginName", "BarPlugin"),
new XAttribute("LastRun", DateTime.Now));
var CurNode = from node in XDoc.Root.Elements("Database")
where (string)node.Attribute("DatabaseName").Value == "c2501_data"
select node;
foreach (var node in CurNode)
node.Add(SpecialPlugin);
XDoc.Save(RuntimesPath);
//XDoc.Root.Elements("Database").Attribute("DatabaseName").
}
The problem that I'm having is that even though there is clearly an entry for BarPlugin, the count will always return 0 and e will always be unable to create an enumberable. Can anyone explain to me why this might be? FooPlugin always works correctly and returns the Plugin information for h.
Thanks for any help.
You're selecting a Database element where it contains a child element called Plugin with a given name. Since you have only one Database element, you're getting the same outer element each time. You then take that database element and return the first Plugin child, which will always be Foo, in this case. You need to find the appropriate Database element and then query through each of the child elements so you can return them:
public static XElement GetPlugin(XDocument XDoc, string databaseName, string pluginName)
{
var h = from database in XDoc.Root.Elements("Database")
where database.Attribute("DatabaseName").Value == databaseName
from plugin in database.Elements("Plugin")
where plugin.Attribute("PluginName").Value == pluginName
select plugin;
return h.FirstOrDefault();
}
Or, if you prefer, in method syntax:
var q = XDoc.Root.Elements("Database")
.Where(db => db.Attribute("DatabaseName").Value == databaseName)
.SelectMany(db => db.Elements("Plugin"))
.Where(plugin => plugin.Attribute("PluginName").Value == pluginName);
return q.FirstOrDefault();
Try this:
var db = XDoc.Root.Elements("Database");
var z = (from el in db.Elements("Plugin")
where el.Attribute("PluginName").Value == "BarPlugin"
&& el.Parent.Attribute("DatabaseName").Value == "c2501_data"
select el).FirstOrDefault();
if(z != null)
.....
I'm using Elements() method, to get all child elements and Parent property to look for the parent element "DatabaseName".
Problem in your code is that your el.Element() is searching only for the first element, thus it can find only "FooPlugin", which is on the first position in the xml.
From MSDN doc Element():
Gets the first (in document order) child element with the specified XName.
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);