Cant go deeper than root, LINQ to (Funds)XML, C# - c#

I'm working with a specific FundsXML-Schema trying to get all Assetss of a specific XML-File to iterate through.
Short example of xml-file:
<?xml version="1.0" encoding="utf-8"?>
<FundsXML xmlns="http://www.fundsxml.org/XMLSchema/3.0.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="3.0.5" xsi:schemaLocation="http://www.fundsxml.org/XMLSchema/3.0.5 FundsXML3.0.5.xsd">
<Date>2015-02-27</Date>
...
<AssetMasterData>
<Asset>
<SecurityCodes>
<ISIN>XXXXXXXXXXXX</ISIN>
</SecurityCodes>
</Asset>
...
<Asset>
</AssetMasterData>
</FundsXML>
I want to iterate through Assets in there. I tried:
XDocument xmlTree = XDocument.Load(xmlPath);
XElement root = xmlTree.Root;
foreach (XElement f in root.Descendants())
{
System.Windows.MessageBox.Show(f.Name.ToString() +" ; "+f.Value.ToString());
}
Output: {http://www.fundsxml.org/XMLSchema/3.0.5}Date ; 2015-02-27
The second part would be to read ISIN of each Asset node.
But I hadn't time to do this, because I'm failing at the first part.
EDIT:
Solution was to search for namespace+name:
foreach (XElement f in root.Descendants("{http://www.fundsxml.org/XMLSchema/3.0.5}Asset"))
Best solution in my opinion:
foreach (XElement f in root.Descendants(xmlTree.Root.GetDefaultNamespace()+"Asset"))

As your XML is in a namespace, you need to add the namespace information to the Descendants query.
You can see an example here
You can try to get the
roots.Descendants()
Without filtering and check the nodes that it returns to confirm this.

Based on the sample data you've provided
<Asset></Asset>
doesn't appear to have any data in it. You would need to get
foreach (XElement f in root.Descendants("ISIN"))
I think anyway. If there's no actual text then you will get a blank or empty value?? So it sounds like it's returning what you're asking for??

Related

Need to add xml element to last parent using linq c#

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

Getting XML Elements By ID in C#

XML
<?xml version="1.0" encoding="utf-8" ?>
<animals>
<animal id="fisrt">
<type>Dog</type>
<name>Han</name>
</animal>
<animal id="second">
<type>Cat</type>
<name>Leia</name>
</animal>
</animals>
C#
using System.Xml.Linq;
string id = "second";
var filter = from ab in element.Elements("animal") where ab.Attribute("id").Equals(id) select ab;
foreach (XElement selector in filter)
{
label1.Content = selector.Element("name").Value;
}
What I need help with is selecting elements based on the parent element's id. The goal is to select the name who's parent's id is "second", so I'm trying to get "Leia". The problem I'm encountering is that nothing is happening to the label.
What am I doing wrong and how can I fix this issue. I'm also open to different approach if someone knows of a better way of achieving my goal.
You miss to check the value of attribute:
where ab.Attribute("id").Value.Equals(id)
Hope this help!
How about this:
string name = xdoc.Elements("animal")
.Where (e=>e.Attribute("id")=="first")
.Elements("name")
.Select(e=>e.Value)
.FirstOrDefault();
Essentially you want to put the condition about id attribute inside the where and continue the query.
I know this is the method annotation instead of linq syntax, I prefer it for being easier to read when things get hairy.

Getting to "bottom" of XmlDocument - C#

I have two versions of XmlDocument
Version 1
<?xml version="1.0" encoding="UTF-8"?>
<topElement>
<childElement1>Value</childElement1>
<childElement2>Value</childElement2>
...
</topElement>
Version 2
<?xml version="1.0" encoding="UTF-8"?>
<topElement>
<group1>
<childElement1>Value</childElement1>
<childElement2>Value</childElement2>
</group1>
<group2>
<childElement1>Value</childElement1>
<childElement2>Value</childElement2>
</group2>
</topElement>
In both occasions I need to get all the values for all childElements and add them to collection of a CustomObject.
As far as I understand this could be done through iteration only.
So I get the top node, and go like this:
CustomObject getLow(XmlNode node, CustomObject customObject)
{
foreach (XmlNode n in node.ChildNodes)
{
if (n.HasChildNodes == true)
{
getLow(n);
}
customObject.collection.Add(n.Name, n.InnerText);
}
return customObject;
}
No doubt it is wrong, please can somebody help me getting the correct result in both cases?
You can use Xpath with your XmlDocument:
XmlDocument xmlDoc = new XmlDocument("yourxml.xml");
foreach (XmlNode childElement in xmlDoc.SelectNodes("//childElement"))
{
customObject.collection.Add(childElement.Name, childElement.InnerText);
}
Looping isn't the only way - another way would be to generate an object graph of custom classes with XML attributes and use the XML serilizer to convert the XML into an object graph.
See http://msdn.microsoft.com/en-gb/library/system.xml.serialization.xmlserializer.aspx for details.
An alternative to that would be to use an XDocument and use linq to query the values you want straight out of the XDocument.
See Using Linq and XDocument, can I get all the child elements under parent tag?
Hope this helps
You should be able to use some xPath to get the nodes you are looking for.
Try something like
node.SelectNodes("//*[count(child::*)=0]")

How to get elements by name in XML using LINQ

I've chosen the title here as my problem is I need to get the Item nodes mentioned in the example.
I have the following XML and am having problems using LINQ to query it, I've been able to parse XML before - however I've been stuck on this for hours and hope someone can help.
Here is my XML data below (example data):
<a:entry
xmlns:a="http://www.w3.org/2005/Atom">
<a:id>98765</a:id>
<info>Data Catalogue</info>
<data>
<items>
<item>
<id>123456</id>
<value>Item One</value>
</item>
<item>
<id>654321</id>
<value>Item Two</value>
</item>
</items>
</data>
<items>
<item>
<id>123456</id>
<value>Item One</value>
</item>
<item>
<id>654321</id>
<value>Item Two</value>
</item>
</items>
<a:author>
<a:name>Catalogue</a:name>
</a:author>
</a:entry>
I want to be able to extract the ID from the Item XML tag under Items, however there is an Items Tag with Item entries under data I DO NOT want these nodes at all - I want root/items/id/id if this were expressed as path. I've tried everything I know with LINQ so if someone could help, things to note although this is sample data it is based on the system - the format cannot be changed so that is not an acceptable solution.
I can't seem to determine where I'm going wrong - every LINQ expression I try returns nothing, I think the namespace is an issue and have tried to integrate this but I'm going in circles.
Solution must work in Silverlight and C#
I have tried the following:
IEnumerable<XElement> nodes =
element.Elements().Where(e => e.Name.LocalName == "items")
However this gets me all the "items" including the ones under "data" I don't want those.
If I do the following on my XML I do see the Names of the Elements displayed:
XElement element = XElement.Parse(data);
foreach (XElement node in element.Elements())
{
MessageBox.Show(node.Name.LocalName);
}
However when I do this I cannot see the node names under items at all - I've checked the XElement and it does have the node and when I output the names above it "items" shows up along with info and id!
foreach (XElement node in element.Elements("items"))
{
MessageBox.Show(node.Name.LocalName);
}
Assuming element is your <a:entry> element:
var ids = element.Element("items")
.Elements("item")
.Select(item => item.Element("id").Value);
The Element and Elements methods return only direct children, not all descendants, so it doesn't return the <items> element which is under <data>
I had a blank Namespace Declaration in my XML I hadn't noticed once I added this into my code it worked - forgot LINQ is very NameSpace oriented!
XNamespace ns = "http://example.org/namespace";
var ids = element.Element(ns + "items")
.Elements("item")
.Select(item => item.Element("id").Value);

Merging similar xml documents

I have several XDocuments that look like:
<Test>
<element
location=".\jnk.txt"
status="(modified)"/>
<element
location=".\jnk.xml"
status="(overload)"/>
</Test>
In C#, I create a new XDocument:
XDocument mergedXmlDocs = new XDocument(new XElement("ACResponse"));
And try to add the nodes from the other XDocuments:
for (ti = 0; (ti < 3); ++ti)
{
var query = from xElem in xDocs[(int)ti].Descendants("element")
select new XElement(xElem);
foreach (XElement xElem in query)
{
mergedXmlDocs.Add(xElem);
}
}
At runtime I get an error about how the Add would create a badly-formed document.
What am I doing wrong?
Thanks...
(I saw this question -- Merge XML documents -- but creating an XSLT transform seemed like extra trouble for what seems like a simple operation.)
You are very close. Trying changing the line
mergedXmlDocs.Add(xElem);
to
mergedXmlDocs.Root.Add(xElem);
The problem is that each XML document can only contain 1 root node. Your existing code is trying to add all of the nodes at the root level. You need to add them to the existing top level node instead.
I am not sure what programming language you are using, but for most programming languages there is extensive XML support classes. Most of them allow parsing and even adding of element. I would have 1 main file that I would keep around and then parse each new one adding the elements from the new one into the master.
EDIT: Sorry it looks like you are already doing exactly this.

Categories