Query XML stored in C# string - c#

I have to read some values from XML,below is my sample XML
<?xml version="1.0" encoding="utf-16"?>
<ParentNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ChildNode>
<GrandChild Name="title" Value="Mr" />
<GrandChild Name="Name" Value="Test" />
<GrandChild Name="Age" Value="25" />
<GrandChild Name="Gender" Value="Male" />
</ChildNode>
</ParentNode>
I have to read values of Name and Age nodes, this is how I am doing
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(myXMLstring);
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode");
foreach (XmlNode childrenNode in nodes)
{
}
but this code is running for only once, I tried this inside loop,buts its not running
var gchild= childrenNode.SelectNodes("/GrandChild");
foreach (XmlNode namevalue in gchild)
{
}
How can I get the values of Name and Age node?

Your XML contains only a single ChildNode so the XPATH expression /ParentNode/ChildNode will return only a single result. If you wanted to iterate over the grandchildren you should use /ParentNode/ChildNode/GrandChild or //GrandChild, eg:
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode/GrandChild");
The result will be the same.
A single slash at the start of an XPath expression means that the path starts from the root, so /GrandChild returns nothing because there is no GrandChild note at the root level. A double slash // means wherever in the hierarchy, so //GrandChild will return all GrandChild nodes in the file

SelectNodes uses XPath expressions. In XPath, if the expression stars with / it'll start selecting relative to root.
Just use a relative xpath expression. In your case:
var gchild = childrenNode.SelectNodes("./GrandChild");
Or the equivalent:
var gchild = childrenNode.SelectNodes("GrandChild");
Or, if you only aim to iterate over those GrandChild elements, there's no reason to select the ChildNode first, you could iterate directly:
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode/GrandChild");

Related

Getting an XElement with a namespace via XPathSelectElements

I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}

Search XML file for nodes with specific attribute value in .NET 2

I found answers for searching XML nodes using LINQ, but I am limited to C# with .NET 2.
I want to open a single XML file (~50Kb, all simple text) and search for all <Tool> nodes with attribute name having a specific value.
It seems like XmlDocument.SelectNodes() might be what I'm looking for, but I don't know XPath. Is this the right way and if so what would code look like?
You can use XPath in XmlDocument.SelectNodes such as: SelectNodes("//ElementName[#AttributeName='AttributeValue']")
Xml Sample:
<root>
<element name="value1" />
<element name="value2" />
<element name="value1" />
</root>
C# Sample:
XmlDocument xDoc = new XmlDocument();
// Load Xml
XmlNodeList nodes = xDoc.SelectNodes("//element[#name='value1']");
// nodes.Count == 2
Here you can find some additional XPath samples
think you could do something like that (well, rustic, but you've got the idea), using GetElementsByTagName
var myDocument = new XmlDocument();
myDocument.Load(<pathToYourFile>);
var nodes = myDocument.GetElementsByTagName("Tool");
var resultNodes = new List<XmlNode>();
foreach (XmlNode node in nodes)
{
if (node.Attributes != null && node.Attributes["name"] != null && node.Attributes["name"].Value == "asdf")
resultNodes.Add(node);
}

reading node from xml file in XMLDocument

i am trying to grab the TopicName how should i go after it and try different combination but somehow i am unable to get TopicName below is my source codee...
XmlDocument xdoc = new XmlDocument();//xml doc used for xml parsing
xdoc.Load(
"http://latestpackagingnews.blogspot.com/feeds/posts/default"
);//loading XML in xml doc
XmlNodeList xNodelst = xdoc.DocumentElement.SelectNodes("content");//reading node so that we can traverse thorugh the XML
foreach (XmlNode xNode in xNodelst)//traversing XML
{
//litFeed.Text += "read";
}
sample xml file
<content type="application/xml">
<CatalogItems xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="sitename.xsd">
<CatalogSource Acronym="ABC" OrganizationName="ABC Corporation" />
<CatalogItem Id="3212" CatalogUrl="urlname">
<ContentItem xmlns:content="sitename.xsd" TargetUrl="url">
<content:SelectionSpec ClassList="" ElementList="" />
<content:Language Value="eng" Scheme="ISO 639-2" />
<content:Source Acronym="ABC" OrganizationName="ABC Corporation" />
<content:Topics Scheme="ABC">
<content:Topic TopicName="Marketing" />
<content:Topic TopiccName="Coverage" />
</content:Topics>
</ContentItem>
</CatalogItem>
</CatalogItems>
</content>
The Topic nodes in your XML are using the content namespace - you need to declare and use the XML namespace in your code, then you can use SelectNodes() to grab the nodes of interest - this worked for me:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("content", "sitename.xsd");
var topicNodes = xdoc.SelectNodes("//content:Topic", nsmgr);
foreach (XmlNode node in topicNodes)
{
string topic = node.Attributes["TopicName"].Value;
}
Just as a comparison see how easy this would be with Linq to XML:
XDocument xdoc = XDocument.Load("test.xml");
XNamespace ns = "sitename.xsd";
string topic = xdoc.Descendants(ns + "Topic")
.Select(x => (string)x.Attribute("TopicName"))
.FirstOrDefault();
To get all topics you can replace the last statement with:
var topics = xdoc.Descendants(ns + "Topic")
.Select(x => (string)x.Attribute("TopicName"))
.ToList();
If you just need a specific element, then I'd use XPath:
This is a guide to use XPath in C#:
http://www.codeproject.com/KB/XML/usingXPathNavigator.aspx
And this is the query that will get you a collection of your Topics:
//content/CatalogItems/CatalogItem/ContentItem/content:Topics/content:Topic
You could tweak this query depending on what it is you're trying to accomplish, grabbing just a specific TopicName value:
//content/CatalogItems/CatalogItem/ContentItem/content:Topics/content:Topic/#TopicName
XPath is pretty easy to learn. I've done stuff like this pretty quickly with no prior knowledge.
You can paste you XML and xpath query here to test your queries:
http://www.bit-101.com/xpath/
The following quick and dirty LINQ to XML code obtains your TopicNames and prints them on the console.
XDocument lDoc = XDocument.Load(lXmlDocUri);
foreach (var lElement in lDoc.Element("content").Element(XName.Get("CatalogItems", "sitename.xsd")).Elements(XName.Get("CatalogItem", "sitename.xsd")))
{
foreach (var lContentTopic in lElement.Element(XName.Get("ContentItem", "sitename.xsd")).Element(XName.Get("Topics", "sitename.xsd")).Elements(XName.Get("Topic", "sitename.xsd")))
{
string lTitle = lContentTopic.Attribute("TopicName").Value;
Console.WriteLine(lTitle);
}
}
It'd have been a lot shorter if it wasn't for all the namespaces :) (Instead of "XName.Get" you would just use the name of the element).

Inserting and saving xml using Linq to XML

If i have an XML file settings.xml like below
<Root>
<First>
</First>
</Root>
I Load the XML first using XDocument settings = XDocument.Load("settings.xml")
How should I insert a XML node inside the node First and save it using LINQ-to-XML?
First you need to find the First element. Then you can add other elements and attributes to it.
There are more than one way to find an element in the xml: Elements, Descendants, XPathSelectElement, etc.
var firstElement = settings.Descendants("First").Single();
firstElement.Add(new XElement("NewElement"));
settings.Save(fileName);
// or
var newXml = settings.ToString();
Output:
<Root>
<First>
<NewElement />
</First>
</Root>
Or element with attribute:
firstElement.Add(
new XElement("NewElement", new XAttribute("NewAttribute", "TestValue")));
Output:
<Root>
<First>
<NewElement NewAttribute="TestValue" />
</First>
</Root>
[Edit] The answer to the bonus question. What to do if the first element does not exist and I want to create it:
var root = settings.Element("Root");
var firstElement = root.Element("First");
if (firstElement == null)
{
firstElement = new XElement("First");
root.Add(firstElement);
}
firstElement.Add(new XElement("NewElement"));

Get InnerText from Collection

Is there a way to get the innertext of a node when the node is inside a collection
Currently i have this
Collection<string> DependentNodes = new Collection<string>();
foreach (XmlNode node in nodes)
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
DependentNodes.Add(node.ChildNodes[i].InnerXml);
//the reason i'm using InnerXml is that it will return all the child node of testfixture in one single line,then we can find the category & check if there's dependson
}
}
string selectedtestcase = "abc_somewords";
foreach (string s in DependentNodes)
{
if(s.Contains(selectedtestcase))
{
MessageBox.Show("aaa");
}
}
When i debug string s or the index has this inside of it[in a single line]
<testfixture name="1" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="a" description="a">
<dependencies>
<dependson typename="dependsonthis" />
</dependencies>
</test>
</testfixture>
What i'm trying to do is when we reach "testfixture 1" it will find "abc_somewords" & search the "dependson typename"node(if any) and get the "typename"(which is "dependonthis").
Could you use linq to xml. Something like the below might be a decent start
xml.Elements("categories").Where(x => x.Element("category").Value.Contains(selectedtestcase));
This is off the top of my head so might will need refining
P.S. Use XElement.Load or XElement.Parse to get your xml into XElements
Since you already working with XmlNode you could use a XPath expression to select the desired textfixture node, and select the dependency value:
XmlDocument doc = // ...
XmlNode node = doc.SelectSingleNode("//testfixture[contains(categories/category, \"abc\")]/test/dependencies/dependson/");
if (node != null)
{
MessageBox.Show(node.Attributes["typename"]);
}
This selects the dependson node which belongs to a testfixture node with a category containing "abc". node.Attributes["typename"] will return the value of the typename attribute.
Edited:
Updated XPath expression to the more specific question information
Assumptions
As you are looping in your code and wanting to create a collection I'm assuming the actual Xml File has several testfixture nodes inside such as the below assumed example:
<root>
<testfixture name="1" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="a" description="a">
<dependencies>
<dependson typename="dependsonthis" />
</dependencies>
</test>
</testfixture>
<testfixture name="2" description="a">
<categories>
<category>another_value</category>
</categories>
<test name="b" description="a">
<dependencies>
<dependson typename="secondentry" />
</dependencies>
</test>
</testfixture>
<testfixture name="3" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="c" description="a">
<dependencies>
<dependson typename="thirdentry" />
</dependencies>
</test>
</testfixture>
</root>
The Code using Linq to Xml
To use Linq you must reference the following name spaces:
using System.Linq;
using System.Xml.Linq;
Using Linq To Xml on the above assumed xml file structure would look like this:
// To Load Xml Content from File.
XDocument doc1 = XDocument.Load(#"C:\MyXml.xml");
Collection<string> DependentNodes = new Collection<string>();
var results =
doc1.Root.Elements("testfixture")
.Where(x => x.Element("categories").Element("category").Value.Contains("abc_somewords"))
.Elements("test").Elements("dependencies").Elements("dependson").Attributes("typename").ToArray();
foreach (XAttribute attribute in results)
{
DependentNodes.Add(attribute.Value.Trim());
}
Result
The resulting Collection will contain the following:
As you can see, only the text of the typename attribute has been extracted where the dependson nodes where in a testfixture node which contained a category node with the value of abc_somewords.
Additional Notes
If you read the xml from a string you can also use this:
// To Load Xml Content from a string.
XDocument doc = XDocument.Parse(myXml);
If your complete Xml structure is different, feel free to post it and I change the code to match.
Have Fun.
I don't know what is "nodes" you are using.
Here is code with your requirement(What I understood).
Collection<XmlNode> DependentNodes = new Collection<XmlNode>();
XmlDocument xDoc = new XmlDocument();
xDoc.Load(#"Path_Of_Your_xml");
foreach (XmlNode node in xDoc.SelectNodes("testfixture")) // Here I am accessing only root node. Give Xpath if ur requrement is changed
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
DependentNodes.Add(node.ChildNodes[i]);
}
}
string selectedtestcase = "abc_somewords";
foreach (var s in DependentNodes)
{
if (s.InnerText.Contains(selectedtestcase))
{
Console.Write("aaa");
}
}
using System;
using System.Xml;
namespace ConsoleApplication6
{
class Program
{
private const string XML = "<testfixture name=\"1\" description=\"a\">" +
"<categories>" +
"<category>abc_somewords</category>" +
"</categories>" +
"<test name=\"a\" description=\"a\">" +
"<dependencies>" +
"<dependson typename=\"dependsonthis\" />" +
"</dependencies>" +
"</test>" +
"</testfixture>";
static void Main(string[] args)
{
var document = new XmlDocument();
document.LoadXml(XML);
var testfixture = document.SelectSingleNode("//testfixture[#name = 1]");
var category = testfixture.SelectSingleNode(".//category[contains(text(), 'abc_somewords')]");
if(category != null)
{
var depends = testfixture.SelectSingleNode("//dependson");
Console.Out.WriteLine(depends.Attributes["typename"].Value);
}
Console.ReadKey();
}
}
}
Output: dependsonthis

Categories