Select single node - c#

From the following xml:
<response>
<content>
<Result xmlns="http://www.test.com/nav/webservices/types">
<Name>Test</Name>
</Result>
</content>
<status>ok</status>
</response>
I am trying to get the value of the Name element the following way but that does not work:
private static void Main()
{
var response = new XmlDocument();
response.Load("Response.xml");
var namespaceManager = new XmlNamespaceManager(response.NameTable);
namespaceManager.AddNamespace("ns", "http://www.test.com/nav/webservices/types");
Console.WriteLine(response.SelectSingleNode("/response/content/Result/Name", namespaceManager).InnerXml);
}
How can I select the Name element?

Your code would have worked just fineif the Xml had defined the namespace with a "ns:" prefix.
But in this case, the namespace is given without any prefix, which sets the default namespace for everything in the Result tag to ".../webservice/types".
To reflect this, you need to modify the Xpath, and tell the XmlDocument that the nodes you are looking for under Resultare in the webservice/types namespace. So your query will look like this:
Console.WriteLine(response.SelectSingleNode(#"/response/content/ns:Result/ns:Name", namespaceManager).InnerXml);

For getting directly the text value of a node there is a text() function, if used in the query it would look like:
/response/content/Result/Name/text()

Try this:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.InnerXml = "<response><content><Result xmlns=\"http://www.test.com/nav/webservices/types\"><Name>Test</Name></Result></content><status>ok</status>";
string elementValue = String.Empty;
if (xmlDoc != null)
{
xNode = xmlDoc.SelectSingleNode("/Result");
xNodeList = xNode.ChildNodes;
foreach (XmlNode node in xNodeList)
{
elementValue = node.InnerText;
}
}

Related

Reading xml file on some condition

I want to use a xml file which is as below
<?xml version="1.0" encoding="utf-8" ?>
<pages>
<page name="Default.aspx">
<method name="Login_click">
<message code="0" description="this is a test description">
<client code="0000000000" description="this is a description for clent 0000000000" />
</message>
</method>
</page>
</pages>
Now I want to create a function like below
public static string GetAppMessage(string pageName, string methodName, string clientCode, string code)
{
var xmlFile = HttpContext.Current.Server.MapPath("~/App_Data/Theme.xml");
var doc = new XmlDocument();
doc.Load(xmlFile);
if (string.IsNullOrEmpty(clientCode))
{
//go to node who page name to pageName
//and read the vlue
}
else
{
//read for a particular client code
}
}
How can I do this.
Edit 1
Do I need to loop through each node or can I reach to a particular node directly and find the decedent nodes.
like below
foreach (XmlNode chldNode in doc.ChildNodes)
{
....
Instead of XmlDocument you can use XDocument and LINQ to xml:
var xmlFile = HttpContext.Current.Server.MapPath("~/App_Data/Theme.xml");
XDocument xmlDoc = XDocument.Load(xmlFile);
var xmlPage = (from page in xmlDoc.Descendants()
where page.Name.LocalName == "page"
&& page.Attribute("name").Value == pageName
select page).FirstOrDefault();
if (xmlPage != null)
{
//do what you need
}
When you use XmlDocument and you know how the XML-file will look like (I mean you know the names of the nodes where the information is inside) then you could do something like this:
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlElement root = doc["NameOfRootNode"];
if (root != null)
{
//For nodes you just need to bypass to get to another subnode:
XmlNode node = root.SelectSingleNode("nameOfAnotherNode");
//For nodes you actually want to do something with, like read text, attribute etc.
if (node != null && node.SelectSingleNode("nameOfOneMoreNode") != null)
{
var xmlElement = node["nameOfOneMoreNode"];
if (xmlElement != null)
{
//Read from the xmlElement you selected and do something with it...
}
}
//...
}
With SelectSingleNode or SelectNodes you can maneuver to a specific known node and can read the InnerText or an Attribute.
You can use XPath to get <page> element by it's name attribute, for example :
string xpath = "//page[#name='{0}']";
XmlNode page = doc.SelectSingleNode(string.Format(xpath, pageName));
//and read the vlue
Basically, above XPath look for <page> element having name attribute equals pageName parameter.

Adding info to a xml file

I have a XML file which contains about 850 XML nodes. Like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
........ 849 more
And I want to add a new Childnode inside each and every Node. So I end up like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
<Description>TestDescription</Description>
</NameValueItem>
........ 849 more
I've tried the following:
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
XmlNodeList nodes = doc.GetElementsByTagName("NameValueItem");
Which gives me all of the nodes, but from here am stuck(guess I need to iterate over all of the nodes and append to each and every) Any examples?
You need something along the lines of this example below. On each of your nodes, you need to create a new element to add to it. I assume you will be getting different values for the InnerText property, but I just used your example.
foreach (var rootNode in nodes)
{
XmlElement element = doc.CreateElement("Description");
element.InnerText = "TestDescription";
root.AppendChild(element);
}
You should just be able to use a foreach loop over your XmlNodeList and insert the node into each XmlNode:
foreach(XmlNode node in nodes)
{
node.AppendChild(new XmlNode()
{
Name = "Description",
Value = [value to insert]
});
}
This can also be done with XDocument using LINQ to XML as such:
XDocument doc = XDocument.Load(xmlDoc);
var updated = doc.Elements("NameValueItem").Select(n => n.Add(new XElement() { Name = "Description", Value = [newvalue]}));
doc.ReplaceWith(updated);
If you don't want to parse XML using proper classes (i.e. XDocument), you can use Regex to find a place to insert your tag and insert it:
string s = #"<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>";
string newTag = "<Description>TestDescription</Description>";
string result = Regex.Replace(s, #"(?<=</Code>)", Environment.NewLine + newTag);
but the best solution is Linq2XML (it's much better, than simple XmlDocument, that is deprecated at now).
string s = #"<root>
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
<NameValueItem>
<Text>Test2</Text>
<Code>Test2</Code>
</NameValueItem>
</root>";
var doc = XDocument.Load(new StringReader(s));
var elms = doc.Descendants("NameValueItem");
foreach (var element in elms)
{
element.Add(new XElement("Description", "TestDescription"));
}
var text = new StringWriter();
doc.Save(text);
Console.WriteLine(text);

Process IMSManifest.xml with XPath and C#

I am unfamiliar with XPath and I'm looking for guidance on simply selecting three values from a file: schemaversion, title and description.
The Xpath expression //title/langstring only matches the element value when I strip out the name spacing information from <manifest> and <lom>.
What is the correct way to search the contents for these values?
Unit test:
[Test]
public void TitleIsNotNull()
{
var manifestManager = new ManifestManager("imsmanifest.xml");
// Code which initializes object and calls GetTitle() is encapsulated.
Assert.IsNotNullOrEmpty(manifestManager.Title);
}
System Under Test:
private string GetTitle()
{
var document = XElement.Parse(_contents);
const string XpathExpression = "//title/langstring";
return (string)document.XPathSelectElement(XpathExpression);
}
_contents (excerpted):
<?xml version="1.0" encoding="utf-8"?>
<manifest xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2
imscp_rootv1p1p2.xsd
http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd
http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
version="1.0"
identifier="ExampleIdGoesHere">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
<lom xsi:schemaLocation="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1
imsmd_rootv1p2p1.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1">
<general>
<title>
<langstring xml:lang="x-none">Example title goes here.</langstring>
</title>
<description>
<langstring xml:lang="x-none">Example description goes here.</langstring>
</description>
</general>
</lom>
</metadata>
Tweaked Code Based on Steven Doggart's solution
//Revised
private string GetTitle()
{
var xmlReader = GetXmlReader();
var document = XElement.Load(xmlReader);
var xmlNamespaceManager = GetXmlNamespaceManager(xmlReader);
const string XpathExpression = "//y:title/y:langstring";
return (string)document.XPathSelectElement(XpathExpression, xmlNamespaceManager);
}
//Private Helpers
private XmlReader GetXmlReader()
{
var contents = new StringReader(_contents);
var xmlReader = XmlReader.Create(contents);
return xmlReader;
}
private XmlNamespaceManager GetXmlNamespaceManager(XmlReader xmlReader)
{
if (xmlReader.NameTable != null)
{
var xmlNamespaceManager = new XmlNamespaceManager(xmlReader.NameTable);
xmlNamespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
xmlNamespaceManager.AddNamespace("y", "http://www.imsglobal.org/xsd/imsmd_rootv1p2p1");
return xmlNamespaceManager;
}
return null;
}
The problem you are having is that the element you are trying to select actually belongs to a certain namespace, but you are not specifying the namespace when you select it. The title and langstring elements both belong to the default namespace. In the XML document, the default namespace is defined as "http://www.imsproject.org/xsd/imscp_rootv1p1p2". With XPath, there is no way to specify a default namespace. If you do not provide a namespace, it always assumes you mean no namespace at all. Therefore, to select that element, you will have to explicitly provide the namespace, like this:
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
namespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
const string XpathExpression = "//x:title/x:langstring";
return (string)document.XPathSelectElement(XpathExpression, namespaceManager);
The trick, however, is getting the XmlNameTable to give to the XmlNamespaceManager. Unfortunately, the XElement class does not provide a way to get the XmlNameTable for the document, so your best bet would be to load it via an XmlReader, which can provide that, like this:
XmlReader reader = XmlReader.Create(new StringReader(_contents));
XElement document = XElement.Load(reader);
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(reader.NameTable);
namespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
const string XpathExpression = "//x:title/x:langstring";
return (string)document.XPathSelectElement(XpathExpression, namespaceManager);
Alternatively, you could use XmlDocument which is slightly easier when dealing with namespaces. Or, you could also choose to use LINQ to select the element instead of XPath.

Parsing XmlDocument with Variable Parent Tags

Assuming I have an XmlDocument like this:
<xmlFile>
<details>
<code1>ADJ</code1>
<code2>ADC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
<details>
<code1>ADJ</code1>
<code2>SCC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
</xmlFile>
I need to process each in an xml file but only shipments that fall under the tags with a child node with a value of "ADC". So far I have:
// Assume there is an XmlDocument named xml
XmlNodeList details= xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText == "ADC ")
{
// Get each shipment and process it accordingly.
}
}
I can't figure out what to do next. Thanks.
Assuming Data\Sample.xml contains xml as mentioned in the question,
Following is the XLINQ query
XElement root = XElement.Parse(File.ReadAllText(#"Data\Sample.xml"));
var adcShipment = root.Descendants().Where(e=>String.Equals(e.Value, "ADC "));
//next query for nodes/elements inside/next to ADC shipments
XPath can simplify your search for matches:
foreach (XmlNode node in xml.SelectNodes("/xmlFile/details[normalize-space(code2)='ADC']"))
{
string foo = node.SelectSingleNode("foo").InnerText;
string bar = node.SelectSingleNode("bar").InnerText;
}
I'm in the process of adding XPath parsing to this library: https://github.com/ChuckSavage/XmlLib/
I modified it so you can do this:
XElement root = XElement.Load(file);
var shipments = root.XPath("details[starts-with(*,'ADC')]/Shipment");
Long-hand that looks like:
var shipments = root.Elements("details")
.Where(x => x.Elements().Any(xx => ((string)xx).StartsWith("ADC")))
.Elements("Shipment");
This is the sort of thing you're after
XmlNodeList details = xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText.Trim() == "ADC")
{
// Get each shipment and process it accordingly.
foreach(XmlNode shipment in node.SelectNodes("Shipment"))
{
var foo = shipment.SelectSingleNode("foo");
var bar = shipment.SelectSingleNode("bar");
}
}
}

XML - how to use namespace prefixes

I have this XML at http://localhost/file.xml:
<?xml version="1.0" encoding="utf-8"?>
<val:Root xmlns:val="http://www.hw-group.com/XMLSchema/ste/values.xsd">
<Agent>
<Version>2.0.3</Version>
<XmlVer>1.01</XmlVer>
<DeviceName>HWg-STE</DeviceName>
<Model>33</Model>
<vendor_id>0</vendor_id>
<MAC>00:0A:DA:01:DA:DA</MAC>
<IP>192.168.1.1</IP>
<MASK>255.255.255.0</MASK>
<sys_name>HWg-STE</sys_name>
<sys_location/>
<sys_contact>
HWg-STE:For more information try http://www.hw-group.com
</sys_contact>
</Agent>
<SenSet>
<Entry>
<ID>215</ID>
<Name>Home</Name>
<Units>C</Units>
<Value>27.7</Value>
<Min>10.0</Min>
<Max>40.0</Max>
<Hyst>0.0</Hyst>
<EmailSMS>1</EmailSMS>
<State>1</State>
</Entry>
</SenSet>
</val:Root>
I am trying to read this from my c# code:
static void Main(string[] args)
{
var xmlDoc = new XmlDocument();
xmlDoc.Load("http://localhost/file.xml");
XmlElement root = xmlDoc.DocumentElement;
// Create an XmlNamespaceManager to resolve the default namespace.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("val", "http://www.hw-group.com/XMLSchema/ste/values.xsd");
XmlNodeList nodes = root.SelectNodes("/val:SenSet/val:Entry");
foreach (XmlNode node in nodes)
{
string name = node["Name"].InnerText;
string value = node["Value"].InnerText;
Console.Write("name\t{0}\value\t{1}", name, value);
}
Console.ReadKey();
}
}
Problem is that the node is empty. I understand this is a common newbie problem when reading XML, still not able to solve what I am doing wrong, probably something with the Namespace "val" ?
You need to pass the namespace manager into the SelectNodes()
method.
Edit: corrected code
XmlNodeList nodes = root.SelectNodes("/val:Root/SenSet/Entry", nsmgr);
Just change you Xpath to:
XmlNodeList nodes1 = root.SelectNodes("/val:Root/SenSet/Entry",nsmgr);
Or:
XmlNodeList nodes = root.SelectNodes("SenSet/Entry");
Your xpath query string should be:
XmlNodeList nodes = root.SelectNodes("/val:Root/SenSet/Entry", nsmgr);
or more concisely,
XmlNodeList nodes = root.SelectNodes("//SenSet/Entry", nsmgr);

Categories

Resources