Basically, I am having trouble trying to understand where I am going wrong in here..
Basically, I have the following XML:
<Directory>
<CustDirectory name="Name1">
<Person name="Foo" />
<Person name="Goo" />
<Person name="Gentu" />
</CustDirectory>
<CustDirectory name="Name2">
<Person name="F22" />
<Person name="Gentu" />
</CustDirectory>
</Directory>
Using forms, I am updating a list of contacts and I want to write the list to XML depending on which category (stored as a string).
What I have decided to use is therefore LINQ to do it, but, I can't seem to figure out there .Where and have read through questions on stackoverflow and can't seem to figure it out.
Here is my attempt:
var con = e.Element("Directory").Element("CustDirectory").Descendants("Person").Where(p => p.Attribute("name").Value.ToStr == "Name2");
This does not work and throws up a null exception... When I take off the .Where clause, the data contained in the descendent shows correctly.
Could anyone please tell me where I am going wrong in terms of the LINQ query so I can select all the descendants of a particular root?
If I understood correctly your question you are trying to extract all the Person elements that belongs to a given CustDirectory. In this case you should use something more like:
var con = e.Element("Directory").Descendants("CustDirectory").Where(p => p.Attribute("name").Value == "Name2").Elements("Person");
Everything looks right except for the ToStr part.
To select only the Person elements under the CustDirectory with the name Name2 you will need to put your Where on that, like this:
var con = e.Element("Directory").Elements("CustDirectory")
.First(cd => cd.Attribute("name").Value == "Name2").Descendants("Person");
Note that I changed Element("CustDirectory") to Elements("CustDirectory").
You don't need Element("Directory") at the beginning, because e it self is already referenced <Directory> (different case if e is an XDocument instead of XElement as you said in comment). This example able to return <Person> nodes give sample XML posted in question :
var e = XElement.Parse("...");
var con = e.Elements("CustDirectory")
.Where(p => p.Attribute("name").Value == "Name2")
.Elements("Person");
void Main()
{
var xml = #"<Directory>
<CustDirectory name=""Name1"">
<Person name=""Foo""/>
<Person name=""Goo""/>
<Person name=""Gentu""/>
</CustDirectory>
<CustDirectory name=""Name2"">
<Person name=""F22""/>
<Person name=""Gentu""/>
</CustDirectory>
</Directory>";
var xmlDoc = XDocument.Parse(xml);
var con = xmlDoc.Element("Directory")
.Elements("CustDirectory")
.Where(p => p.Attribute("name").Value == "Name2")
.Descendants("Person")
// added bonus to get a specific node
.Where(p => p.Attribute("name").Value == "F22");
Console.WriteLine(con);
}
Related
I would like to get datas from a specific XML format. The XML document looks like that:
<MyXML>
<Sources>
<S1>www.example1.org</S1>
<S2>www.example2.org</S2>
</Sources>
<Books>
<Book id="1">
<Datas>
<Data name="Book_1_Name" tag="1111" />
<Data name="Book_2_Name" tag="2222" />
</Datas>
</Book >
<Book id="2">
<Datas>
<Data name="Book_1_Name" tag="3333" />
<Data name="Book_2_Name" tag="4444" />
</Datas>
</Book >
</Books>
My question is:
How can I get www.example1.org if I know S1?
How can I search "Book_1_name" from Book id=1?
I am using C# with XDocument like this:
XDocument.Load(_XML_path);
var node = _configXml.XPathSelectElement("MyXML/Books/Datas");
You can use XPath, About XPath see this : http://www.xfront.com/xpath/
var _configXml = XDocument.Load(_XML_path);
//how to get S1 element.
var s1 = _configXml.XPathSelectElement("MyXML/Sources/S1");
//how search
var search = _configXml.XPathSelectElements("//Book[#id='1']//Data[#name='Book_1_Name']");
Console.WriteLine(s1.Value);
Console.WriteLine(search.First().Attribute("tag").Value);
You should map the XML to a C# object. Then, you can use the following to get what you want:
XmlSerializer serializer = new XmlSerializer(typeof(MyXML));
var xml = (MyXML)serializer.Deserialize(new StringReader(XDocument.Load(_XML_path)));
var s1 = xml.Sources.S1;
If you want to stick with a XDocument, you can do the following
var books = doc.Element("MyXML").Element("Books");
var bookById = books.Elements("Book")
.Where(b => b.Attribute("id").Value == "1")
.Select(b => b.Element("Datas"));
In the first line, you are selecting the Books node (please note, in a real-world scenario, I'd add some checks here). In the following line you are first getting all Book sub-elements (books.Elements("Book")), checking them for the ID you are searching for (Where(b => b.Attribute("id").Value == "1")) and then select the data node from the respective book. You could re-write this to query syntax
var bookById = from book in books.Elements("Book")
let id = book.Attribute("id")
let datas = book.Element("Datas")
where id != null
&& datas != null
&& id.Value == "1"
select datas;
which is a bit longer, but easier to read.
I have an XML document which I load from the disk
XDocument events = XDocument.Load("Content/GameData/events.xml");
The contents of this xml are the following:
<?xml version='1.0' encoding='UTF-8'?>
<Events>
<level1>
<NarrationEvent code="lvl1_fridge">
I can't even remember when I last ate.
I am not hungry though.
</NarrationEvent>
<NarrationEvent code="lvl1_tv">
Why do I even have a TV?
Oh right, I use it as a screen for my laptop.
</NarrationEvent>
<NarrationEvent code="lvl1_bed">
Oh man, I am beat.
</NarrationEvent>
<NarrationEvent code="lvl1_computer">
Oh, look at that. The project has been compiled.
</NarrationEvent>
</level1>
<level2>
</level2>
<level3>
</level3>
<level4>
</level4>
<cave>
</cave>
</Events>
I use this code here supposedly select the appropriate NarrationEvent element, based on its attribute "code"
IEnumerable<XElement> v =
(from narrationEvent in events.Elements("NarrationEvent")
where (string)narrationEvent.Attribute("code") == code
select narrationEvent);
foreach (XElement page in v)
{
//Console.WriteLine("ff");
narration.Add(page.Value);
}
This returns nothing, my XElement Ienumerable is empty. I used breakpoints and the code value is passed to this method just fine. e.g. "lvl1_bed"
What is wrong with this code?
You can use the Descendants-Method to get your NarrationEvent-Element. I have updated your code accordingly.
IEnumerable<XElement> v = from narrationEvent in events.Descendants("NarrationEvent")
where narrationEvent.Attribute("code").Value == code
select narrationEvent;
<World>
<Animals>
<Tab>
<Dogs id ="1">
<Dog1></Dog1>
<Dog2></Dog2>
<Dog3></Dog3>
</Dogs>
<Dogs id ="2"></Dogs>
<Dogs id ="3"></Dogs>
</Tab>
</Animals>
</World>
How do I get all elements under tag where id == 1?
My Linq query. (doesn't work) why?
XDocument xml= XDocument.Load(xml.xml);
var elements = from e in xml.Descendants("Animals").Descendants("Tab").Elements("Dogs")
where e.Attribute("id").toString().Equals("1")
select c;
Could you check it please?
Thanks!
var result = xdoc.Descendants("World")
.Descendants("Animals")
.Descendants("Tab")
.Elements("Dogs")
.Where(n => n.Attribute("id").Value == "1");
Output:
<Dogs id="1">
<Dog1></Dog1>
<Dog2></Dog2>
<Dog3></Dog3>
</Dogs>
Or use XPath:
xml.XPathSelectElements("/World/Animals/Tab/Dogs[#id=1]")
or
xml.XPathSelectElements("//Dogs[#id=1]")
which would find all Dogs wherever they occurred.
From your sample data I think you want
//from e in xml.Descendants("Animals").Descendants("Tab").Elements("Dogs")
from e in xml.Descendants("Animals").Elements("Tab").Descendants("Dogs")
And in the same line, Descendants("Animals") could be Elements("Animals") if you want to enforce the structure.
For the rest your query looks OK.
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);
Trying to use
exportDoc.Root.Elements("string").Where(node => !(node.Element("product").HasElements) || node.Element("product").Element("type").Value != product).Remove();
to remove the nodes in my XML document where the product string I'm searching for doesn't occur. Here is a sample of my XML structure:
<root>
<string id = "Hithere">
<product>
<type>orange</type>
<type>yellow</type>
<type>green</type>
<product>
<element2/>
<element3/>
</string>
<string id ="...">
...
...
</root>
So I need to look under the product element of each string element AND at each of the type elements therein to see if the value of string product (input to the method where this is contained) occurs. At present, it looks like my code only removes the node if the product string I'm searching for matches the value of just the first type element.
The whole point is to remove all string nodes from this xdoc that don't have the product I'm looking for listed under their product element.
You need to change your search condition slightly:
var nodesToRemove = xDoc.Root
.Elements("string")
.Where(node =>
!(node.Element("product").HasElements) ||
node.Element("product").Elements("type").All(x => x.Value != product))
.ToList();
This should match elements which all string:product:types differ from product value (or in other words - if at least one <type> will match your product, it won't be marked for removal).
You can't Remove() while you're still enumerating (deferred execution).
You need something more like:
// untested
var toRemove = exportDoc.Root.Elements("string")
.Where(node => !(node.Element("product").HasElements) ||
node.Element("product").Element("type").Value != product).ToList();
toRemove.Remove();