Given an example XML file as such:
<libraries>
<library name="some library">
<book name="my book"/>
<book name="your book"/>
</library>
<library name="another library">
<book name="his book"/>
<book name="her book"/>
</library>
</libraries>
How would one iterate through each library and get only its children? E.g. if I was in the first library element and I went to retrieve all its descendants/children, it would only return with the two books inside it.
I've tried iterating and using XElement.Elements("book"), XElement.Elements(), XElement.Descendants(), etc. but all return every element that is a book (so it would pull the elements from the second library, too). Mostly I think I'm just struggling with understanding how XDocument keeps track of its elements and what's considered a descendant/child.
If possible, if one could explain as to how this would be done with XDocument for an element at any level it'd be appreciated (e.g. if each book had child elements, and if those elements had child elements, etc).
You can iterate over your XML by going through all the descendents of libraries in the following way.
XDocument doc=XDocument.Load(XmlPath);
foreach (var item in doc.Descendants("library"))
IEnumerable<XNode> nodes = item.DescendantNodes();//Here you got book nodes within a library
Sheer,
The problem is you are pulling all elements with "book".
If you want to get only items dependant on the parent element, you will have to supply a proper condition.
var v = from n in doc.Descendants("library")
where n.Attribute("name").Value == "some library"
select n.DescendantNodes();
Now, this will give you element who's name is "some library".
Related
Currently I have a working C# program that works as follows:
Accept .xls template with values (xls is manually created by user)
Save the values (matching fields) to the database
Convert and write .xls to XML. Please see below sample output:
Existing XML Structure
Now, what I want to do is:
Read the existing xml (the created xml)
Insert another set of nodes and subnodes (ReleaseLine and sub nodes). It must accept multiple ReleaseLine.
Save/create the new xml with appended nodes. Please see below output:
This is what I'm looking for:
My existing C# program is simple but the XML nodes and hierarchy is bloody deep. I just created the C# code using new XElement method and passing values for each nodes. Then I simply use xmlDocument.Save() method to write the xml.
[Existing XML Program][3]
To add nodes or append content in existing xml-data I´d use Linq to XML.
XElement xml = XElement.Load("file.xml");
xml.Add( new XElement("uberNode",
new XElement("childNode", content),
new XElement("anotherChildNode", content)));
xml.Save("file.xml");
Here are some other related solutions.
Add to specific node (with example):
Following exisiting XML-data:
`<Names>
<Name>
<prename>John</prename>
<lastname>Snow</lastname>
</Name>
<Name>
<prename>Harry</prename>
<lastname>Harry</lastname>
</Name>
</Names>`
Now I want to add an "age"-tag before the first "prename"-tag and a "family"-tag after the first "lastname"-tag.
XElement xml = XElement.Load("file.xml");
var childrens = xml.DescendantsAndSelf().ToArray();
var first_prename = childrens[2];
var first_lastname = childrens[3];
Console.WriteLine(childrens[0]); //prints out the whole content
first_prename.AddBeforeSelf(new XElement("age", 22));
first_lastname.AddAfterSelf(new XElement("family", new XElement("mother", "paula"), new XElement("father", "paul")));
xml.Save("file.xml");
Outcome:
`<Names>
<Name>
<age>22</age>
<prename>John</prename>
<lastname>Snow</lastname>
<family>
<mother>paula</mother>
<father>paul</father>
</family>
</Name>
<Name>
<prename>Harry</prename>
<lastname>Harry</lastname>
</Name>
</Names>`
I was facing the problem and Linq gave me the easiest way to accomplish that!
There are also other similar way e.g. here. But I tried a bit more and DescendantsAndSelf() made it easier for me to go through.
I found an answer to my question, here is the link http://www.xmlplease.com/add-xml-linq
Using XPathSelectElement method, I was able to find the right node and appended new block of XElement.
I want to modify the Node my XML File look like this.
<Tasks>
<Task>
<Title>Title of the Task</Title>
<Description>Description of the Task</Description>
<Done>false</Done>
</Task>
<Task>
<Title>Title of anotherTask</Title>
<Description>Description of anotherTask</Description>
<Done>true</Done>
</Task>
</Tasks>
I could adress the Node like this:
xmlDoc.SelectSingleNode("/Tasks/Task/Description").InnerText = "My Description";
However I have mulitple Tasks. How do I indicate which is which? I want to change the State of the Task "Done" from false to true.
You could iterate through each resulting node from something like this:
foreach( XmlNode xn in xmlDoc.SelectNodes("//Tasks"))
{
// Do something
}
Doing what you need to do on each node. More info on SelectNodes here: https://msdn.microsoft.com/en-us/library/system.xml.xmlnode.selectnodes%28v=vs.110%29.aspx
If you have control over the design of the XML, perhaps you should consider adding an ID to your task. An ID will allow you to make changes to an specific Task node instead of iterating through them or looking up by Task.Title.
You may also look at these articles:
https://msdn.microsoft.com/en-us/library/bb943906.aspx
How can I iterate though each child node in an XML file?
Having said all this, I feel your question is missing some more information on what is the criteria on when to do that something correctly. Could you expand some more? You will get better answers that way.
I'm really new to Linq and C# and I'm stuck on what is probably an obvious problem.
I have an existing XML file
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<books>
<book>
<title>This is Title 1</title>
<author>John Doe</author>
<categories>
<category>How to</category>
<category>Technical</category>
</book>
<book>
<title>This is Title 2</title>
<author>Jane Brown</author>
<categories>
<category>Fantasy</category>
</categories>
</book>
</books>
I want to add a 2nd category to the second book in this file.
I've gotten this far:
var thiscat = doc.Root
.Element("book")
.Element("categories");
thiscat.Add(new XElement("category", "novel"));
But this adds a 3rd category to the first book. I need to learn how to point 'thiscat' at the last categories element rather than the first one. I've been sniffing around LastNode but haven't managed to get the syntax right.
This is my first question here. Please let me know if I'm not being clear or if I'm doing anything wrong.
Pete,
Here is an example that will search for the book by title This is Title 2 and add another category.
var elem = doc.Root.Elements("book").FirstOrDefault(x => x.Element("title").Value.Equals("This is Title 2"));
if (elem != null)
{
var category = elem.Element("categories");
category.Add(new XElement("category", "novel"));
}
Edit: More explanatoin.
First of we search the documents book elements for the matching title of This is Title 2 (effectively your second entry). By executing the FirstOrDefault extension method we either the get the first matching element (as XElement) or null.
Because we 'could' get a null value we must check if the value is null if not we move into the next step of locating the categories element. This can be done simply calling the elem.Element() method as we only expect one element.
Finally we add a new XElement to the category element.
Hope this helps.
Cheers.
To answer your question quite literally, you could modify the statement as follows:
var thiscat = doc.Root
.Elements("book")
.Skip(1)
.First()
.Element("categories");
The "Element" function returns the first element of that type found. In this case, we used "Elements" instead to return an IEnumerable containing all of the elements named "book", and then we used the LINQ "skip" function to skip the first (returning another IEnumerable of all the remaining elements), and then we took just the first element in the IEnumerable (back to a single XElement).
Another way you could have gotten to the answer is as follows:
var thiscat = doc.Root
.Element("book")
.ElementsAfterSelf()
.First()
.Element("categories");
ElementsAfterSelf returns an IEnumerable of all the sibling elements after the calling object.
LINQ is a really critical part of programming in C# and it's good to see you're trying to learn it from the beginning. Although your methodology here in adding a specific element to a specific place programmatically is questionable (obviously it is a contrived example), in playing around like this you will probably learn a bit about LINQ and that is always good.
First you should get your second book element.According to your code:
var thiscat = doc.Root
.Element("book")
.Element("categories");
This statement returns just one categories element which belongs to your first book.Because you are using Element instead of Elements. Let's go step by step.
A proper way to get second element is using Descendants like this:
var secondBook = doc.Descendants("book")[1];
Descendants returning a collection of your books.And we are getting second element with indexer.Now we need to select your categories element under the book element.
var categories = secondBook.Element("categories");
Now we have our categories element and we can add our new category and save Xml Document:
categories.Add(new XElement("category", "novel"));
doc.Save(path);
And that's all.If you understand that logic you can modify your html file however you like.Besides you can make all of these in one line:
doc.Descendants("book")[1]
.Element("categories")
.Add(new XElement("category", "novel"));
This should work( slightly lengthy solution as it helps understand the fundamentals better):
XmlElement rootNode = xd.DocumentElement; //gives <books> the root node
XmlNodeList cnodes= rootNode.ChildNodes; //gets the childnodes of <books>
XmlNode secondBook= cnodes.Item(1); //second child of <books> i.e., the <book> you want
XmlNodeList bnodes= secondBook.ChildNodes; //gets the childnodes of that <book>
XmlNode categories= bnodes.Item(2); //gets the third child i.e.,<categories>
//making the new <category> node
string xmlContent = "<category>novel</category>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
XmlNode newNode = doc.DocumentElement;
//making the new node completes
categories.AppendChild(newNode); //append the new node to <categories> as a child
Is there a good way in c# to look through an XML node list using DOM and get a node list of only the unique nodes and also a list of each nodes unique possible attributes.
The XMl file in question has nodes of the same name but with different attributes, i want a list of all the possible ones. Also the list of nodes i would like to be only of the unique nodes, rather than having repeats (so node lists i generate at the moment might have contact twice, three time ect within it). And it needs to work for any XML document. Any ideas?
Here is an example:
<book id="bk112">
<author>Galos, Mike</author>
<title>Visual Studio 7: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>49.95</price>
<publish_date>2001-04-16</publish_date>
</book>
<book id="bk162">
<genre>fiction</genre>
<popularity>High</popularity>
<price>20.00</price>
<publish_date>2002-03-12</publish_date>
</book>
<cd id="bk162">
<genre>jaz</genre>
<popularity>High</popularity>
<price>10.00</price>
</cd>
and get some sort of output like:
there are 2 of the type book
there are 1 of the type cd
there are 3 of the type genre
book may have the attributes author, title, genre, price, popularity, publish_date
but in a way that works for any xml file.
In the case of genre it doesnt need to be celver in any way, just know there are 3 genre nodes in the document.
Would this do it?
XDocument xDoc = XDocument.Load("XMLFile1.xml");
List<XElement> distinctDocs = xDoc.Descendants().GroupBy(x => x.Name).Where(x => x.Count() == 1).Select(g => g.Single()).ToList();
Given the following XML document for example:
<?xml version="1.0"?>
<UrdaObject>
<Date>
<Year>2011</Year>
<Month>5</Month>
<Day>18</Day>
<Hours>8</Hours>
<Minutes>47</Minutes>
<Seconds>36</Seconds>
</Date>
<random_value>24</random_value>
</UrdaObject>
And the understanding the child::node() - Selects all child nodes of the current node how would I create an XPath (starting from the root) that would select all child nodes EXCEPT text, comments, and other things that are NOT elements. For example, when using this code to create a tree view in WPF:
// x is some XmlDocument, xmlTree is my WPF TreeView
XmlDataProvider provider = new XmlDataProvider();
provider.Document = x;
Binding binding = new Binding();
binding.Source = provider;
binding.XPath = "child::node()";
xmlTree.SetBinding(TreeView.ItemsSourceProperty, binding);
How would I go about creating my XPath statement so I build a treeview with nodes going all the way down and stopping before the raw text? For example it would generate a view of:
UrdaObject
Date
Year
...
Instead of...
UrdaObject
Date
Year
2011 (Don't want this!)
...
The sample XML files is just for me to explain my situation. The expression should be able to navigate any valid XML file and pull the elements, but not the individual text.
How did we fix this? I had switched all references of child::node() to child::*. However, I had NOT corrected one line in my XAML, which was pulling child::node(). Correcting this line made the application behave correctly... and made me feel silly.
child::node() finds all child nodes. child::* finds all element nodes.
it's as simple as *.
(that gets immediate children, however; if you want all descendant elements, it would be descendant::*)
child::* will exclude text nodes and leave only element nodes
child::text() will include only text nodes
child::node() will include both element and text nodes
http://www.w3.org/TR/xpath/#location-paths
Not sure if this is what you want but could it be done this way?
var doc =XDocument.Parse(#"
<UrdaObject>
<Date>
<Year>2011</Year>
<Month>5</Month>
<Day>18</Day>
<Hours>8</Hours>
<Minutes>47</Minutes>
<Seconds>36</Seconds>
</Date>
<random_value>24</random_value>
</UrdaObject>
");
var query = from s in doc.Descendants()
select s.Name;
foreach (var name in query)
{
Console.WriteLine(name);
}