I'm having a following XML, in that I'm having two Nodes namely "param" but the Names are different. I need to get the value by Name from the node param. Kindly look at the code
void Main()
{
string _commentXml = string.Format("<root>{0}{1}</root>"
, "<param name=\"Super\">Parameter One</param>"
, "<param name=\"Supreme\">Parameter Two</param>");
XmlDocument _comment = new XmlDocument();
_comment.LoadXml(_commentXml);
XElement element = XElement.Load(_comment.DocumentElement.CreateNavigator().ReadSubtree());
TryGetElementValue(element, "param").Dump();
}
public string TryGetElementValue(XElement parentEl, string elementName, string defaultValue = null)
{
var foundEl = parentEl.Element(elementName);
if (foundEl != null)
{
var xyz = foundEl.Elements("param");
if (xyz != null)
{
return xyz.First(x => x.Attribute("name").Value == "Super").Value;
}
}
return defaultValue;
}
I can't able to get the value of param with name=Super
I refereed one of the stack-overflow question which is opt for this requirement but I can't.
Referred: XDocument get XML element by the value of its name attribute
Kindly assist me.
Why all this mess?
XDocument has a Descendants method and with linq it's easy:
var xdoc = XDocument.Parse(_commentXml);
var xel = xdoc.Descendants("param")
.Where(xElement => xElement.Attribute("name")?.Value == "Super");
You can also user XPath.
var element = doc.XPathSelectElement("/path/to/element/I/want");
In your case it would be something like this:
var element = doc.XPathSelectElement("/root/param[#name="Super"]");
Check here for more info:
https://stackoverflow.com/a/11224645/1582065
Related
My xml looks like this....
<?xml version="1.0" encoding="utf-8"?>
<messwerte>
<messwert>
<tag>1</tag>
<niederschlag>46</niederschlag>
<temperatur>7,6</temperatur>
<druck>4,6</druck>
</messwert>
......
</messwerte>
Now, I wanna give a a specific day where I want to change "niederschlag" "temperatur" and "druck" and I tried this:
public static void WriteXML(int day, double[] mess, string path)
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlElement nieder = doc.SelectSingleNode("/messwerte/messwert" + Convert.ToString(day) + "/niederschlag") as XmlElement;
if (nieder != null)
{
nieder.InnerText = Convert.ToString(mess[0]);
}
}
And it wont work.
And I know it's baaaad and super basic but i cant get it to work.......
I would suggest the reason it won't work for you, is you're trying to do 2 different things with one xpath string.
First you have to find the messwert element with a tag element that has an InnerText value matching the day value you're passing in.
Once you've identified the right element you want to change the InnerText of the niederschlag element.
Even though writing this out makes it seem quite complicated, leveraging a LINQ query can simplify it tremendously:
public static void WriteXML(int day, double[] mess, string path)
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
var nieder = (from XmlElement element in doc.GetElementsByTagName("messwert")
where element.SelectSingleNode("tag").InnerText == day.ToString()
select element).First().SelectSingleNode("niederschlag");
if (nieder != null)
{
nieder.InnerText = mess[0].ToString();
}
doc.Save(path);
}
This code assumes your data is strongly controlled and that you'll never be looking for a day that isn't there.
If this isn't the case you'll have to assign the query including the First() method to a temporary variable, and check if it's null.
Something like this should work:
public static void WriteXML(int day, double[] mess, string path)
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
var messwert = (from XmlElement element in doc.GetElementsByTagName("messwert")
where element.SelectSingleNode("tag").InnerText == day.ToString()
select element).FirstOrDefault();
if(messwert == null)
{
throw new ArgumentException($"The day value, doesn't exist. the value passed is {day}");
}
var nieder = messwert.SelectSingleNode("niederschlag");
if (nieder != null)
{
nieder.InnerText = mess[0].ToString();
}
doc.Save(path);
}
I have an XML file as follow
<NODE1 attribute1 = "SomeValue" attribute2 = "SomeOtherValue" />
<NODE2 attribute3 = "SomeValue" attribute4 = "SomeOtherValue" />
Now I am given only the attribute name say "attribute3". How can I get the name of node?
Add the following namespace at the top of your file:
using System.Xml.Linq;
And try this (assuming that input.xml is the path to your XML file):
var xml = XDocument.Load("input.xml");
string nodeName;
var node = xml.Descendants()
.FirstOrDefault(e => e.Attribute("attribute3") != null);
if (node != null)
nodeName = node.Name.LocalName;
With LINQ to XML:
XDocument xdoc = XDocument.Load(path_to_xml);
var nodes = xdoc.Descendants().Where(e => e.Attribute("attribute3") != null);
Or with XPath (as Marvin suggested):
var nodes = xdoc.XPathSelectElements("//*[#attribute3]");
Both queries will return collection of XElement nodes which have attribute attribute3 defined. You can get first of them with FirstOrDefault. If you want to get just name, use node.Name.LocalName.
UPDATE: I do not recommend you to use XmlDocument, but if you already manipulating this xml document, then loading it second time with XDocument could be inefficient. So, you can select nodes with XPathNavigator:
var doc = new XmlDocument();
doc.Load(path_to_xml);
var naviagator = doc.CreateNavigator();
var nodeIterator = naviagator.Select("//*[#attribute3]");
try in this way
string nodeName;
if(Node.Attributes.Cast<XmlAttribute>().Any(x => x.Name == "attribute3"))
{
nodeName=Node.Name;
}
Three part question.
Is it possible to locate a specific XML node by a child inside of it to retrieve other children of the parent? Example:
<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>F2039-39</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>
So for this, I would like to locate the appropriate Appliance node by searching for "Refrigerator" or "Toaster", and retrieve the brand from it.
The second part of this question is this: Is this a stupid way to do it? Would using an attribute in the Appliance tag make this a lot easier? If so, how would I locate it that way?
As for the third part, once I locate the Appliance, how would I go about changing say, the Model, of that particular appliance?
Using XLinq, you can perform this query fairly naturally:
// Given:
// var xdoc = XDocument.Load(...);
// string applianceName = "Toaster";
// Find the appliance node who has a sub-element <Name> matching the appliance
var app = xdoc.Root
.Descendants("Appliance")
.SingleOrDefault(e => (string)e.Element("Name") == applianceName);
// If we've found one and it matches, make a change
if (app != null)
{
if (((string)app.Element("Model")).StartsWith("B8d-k30"))
{
app.Element("Model").Value = "B8d-k30 Mark II";
}
}
xdoc.Save(#"output.xml"); // save changes back to the document
Well if you are using XmlDocument
foreach(XmlNode applianceNode in
myDocument.DocumentElement.SelectNodes("Kitchen/Applicance[Name='Refrigerator']")
{
XmlNode modelNode = applicianceNode.SelectSingleNode("Model").InnerText = SomeOtherValue;
}
if you made the name tag an attribute (applicanceName) it would make little difference to this.
foreach(XmlNode applianceNode in
myDocument.DocumentElement.SelectNodes("Kitchen/Applicance[#applianceName='Refrigerator']")
{
// ...
}
string xml = #"<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>F2039-39</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>";
XDocument xdoc = XDocument.Parse(xml);
string newModel = "B8d-k45";
var matchingElement = (from appliance in xdoc.Descendants("Appliance")
where appliance.Element("Name").Value == "Toaster"
select appliance).FirstOrDefault();
if (matchingElement != null)
{
matchingElement.Element("Model").Value = newModel;
}
Console.WriteLine(xdoc.ToString());
Necromancing.
Yes, it's even simpler with XPath, and works completely without Linq:
Just use .. to get to the parent node (on the second thought, Linq will be easier when using "ordinalignorecase")
public static void CreateNewHouse()
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.XmlResolver = null;
doc.Load(#"d:\House.xml");
foreach (System.Xml.XmlNode modelNode in doc.DocumentElement
.SelectNodes("/House/Kitchen/Appliance/Name[text()='Refrigerator']/../Model"))
{
modelNode.InnerText = "A New Value";
}
doc.Save(#"d:\MyHouse.xml");
}
MyHouse.xml:
<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>A New Value</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>
If you need it case-insensitive, replace text() with this:
translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
(for ASCII/english-only) and of course change "Refrigerator" to lowercase ("refrigerator")
If the XML-document has a default-namespace, you need to supply it in Select*Node, e.g.
xnImageTag.SelectSingleNode("./dft:Source", nsmgr);
where
System.Xml.XmlNamespaceManager nsmgr = GetReportNamespaceManager(doc);
public static System.Xml.XmlNamespaceManager GetReportNamespaceManager(System.Xml.XmlDocument doc)
{
if (doc == null)
throw new ArgumentNullException("doc");
System.Xml.XmlNamespaceManager nsmgr = new System.Xml.XmlNamespaceManager(doc.NameTable);
// <Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
if (doc.DocumentElement != null)
{
string strNamespace = doc.DocumentElement.NamespaceURI;
System.Console.WriteLine(strNamespace);
nsmgr.AddNamespace("dft", strNamespace);
return nsmgr;
}
nsmgr.AddNamespace("dft", "http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition");
// nsmgr.AddNamespace("dft", "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition");
return nsmgr;
} // End Function GetReportNamespaceManager
I'm calling a WebService with HttpWebRequest.Create method and read it with StreamReader, (below method do this job):
private string ReadWebMethod(string address)
{
var myRequest = (HttpWebRequest)HttpWebRequest.Create(address);
myRequest.Method = "POST";
using (var responseReader = new StreamReader(myRequest.GetResponse().GetResponseStream()))
return responseReader.ReadToEnd();
}
This method works well and output a string like this:
<ArrayOfAppObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
<AppObject>
<Name>MyApp</Name>
<Image>StoreApp.png</Image>
<Version>SM2.1.0</Version>
</AppObject>
</ArrayOfAppObject>
Now I want to have a look in this string, So I use XElemnet and parse the string. (below code):
XElement x = XElement.Parse(ReadWebMethod(address));
Now, When I foreach, x.Elements("AppObject"), it doesnt return any item and skip the foreach.
My foreach is like this:
foreach (var item in x.Elements("AppObject"))
{
listApplication.Add(new AppObject { Image = item.Element("Image").Value, Name = item.Element("Name").Value, Version = item.Element("Version").Value });
}
I create a local string and remove attributes after "ArrayOfAppObject" (xmlns:xsi="htt...)(some where name it xmlnamespace) and test it again, And it works as well and foreach does not skipped!
SO, How can I use the xml with namespace?
use XDocument class
using System.Xml.Linq;
//...
var xml = ReadWebMethod(address);
var xdocument = System.Xml.Linq.XDocument.Parse(xml);
updates
as your XML data define the namespace.. xmlns="http://tempuri.org/"
You must declare full XName with valid namespace. to access each element value
XName theElementName = XName.Get("AppObject", "http://tempuri.org/");
//or alternate way..
XName theElementName = XName.Get("{http://tempuri.org/}AppObject");
here 's sample test method
[TestMethod]
public void ParseXmlElement()
{
XDocument xdoc = XDocument.Parse(this.mockXml);
XName appTag = XName.Get("{http://tempuri.org/}AppObject");
XName nameTag = XName.Get("{http://tempuri.org/}Name");
XName imageTag = XName.Get("{http://tempuri.org/}Image");
XElement objElement = xdoc.Root.Element(appTag);
Assert.IsNotNull(objElement, "<AppObject> not found");
Assert.AreEqual("{http://tempuri.org/}AppObject", objElement.Name);
XElement nameElement = objElement.Element(nameTag);
Assert.IsNotNull(nameElement, "<Name> not found");
Assert.AreEqual("MyApp", nameElement.Value);
XElement imageElement = objElement.Element(imageTag);
Assert.IsNotNull(imageElement, "<Image> not found");
Assert.AreEqual("StoreApp.png", imageElement.Value);
}
using Xml.Linq this way..
[TestMethod]
public void ParseXmlLinq()
{
XDocument xdoc = XDocument.Parse(this.mockXml);
XElement app = xdoc.Root.Elements()
.FirstOrDefault(e => e.Name == XName.Get("AppObject", "http://tempuri.org/"));
Assert.IsNotNull(app, "<AppObject> not found");
XElement img = app.Elements()
.FirstOrDefault(x => x.Name == XName.Get("Image", "http://tempuri.org/"));
Assert.IsNotNull(img, "<Image> not found");
Assert.AreEqual("StoreApp.png", img.Value);
}
Note that I just mock up and parse string from your XML.
I am a beginner to XML and XPath in C#. Here is an example of my XML doc:
<root>
<folder1>
...
<folderN>
...
<nodeMustExist>...
<nodeToBeUpdated>some value</nodeToBeUpdated>
....
</root>
What I need is to update the value of nodeToBeUdpated if the node exists or add this node after the nodeMustExist if nodeToBeUpdated is not there. The prototype of the function is something like this:
void UpdateNode(
xmlDocument xml,
string nodeMustExist,
string nodeToBeUpdte,
string newVal
)
{
/*
search for XMLNode with name = nodeToBeUpdate in xml
to XmlNodeToBeUpdated (XmlNode type?)
if (xmlNodeToBeUpdated != null)
{
xmlNodeToBeUpdated.value(?) = newVal;
}
else
{
search for nodeMustExist in xml to xmlNodeMustExist obj
if ( xmlNodeMustExist != null )
{
add xmlNodeToBeUpdated as next node
xmlNodeToBeUpdte.value = newVal;
}
}
*/
}
Maybe there are other better and simplified way to do this. Any advice?
By the way, if nodeToBeUpdated appears more than once in other places, I just want to update the first one.
This is to update all nodes in folder:
public void UpdateNodes(XmlDocument doc, string newVal)
{
XmlNodeList folderNodes = doc.SelectNodes("folder");
if (folderNodes.Count > 0)
foreach (XmlNode folderNode in folderNodes)
{
XmlNode updateNode = folderNode.SelectSingleNode("nodeToBeUpdated");
XmlNode mustExistNode = folderNode.SelectSingleNode("nodeMustExist"); ;
if (updateNode != null)
{
updateNode.InnerText = newVal;
}
else if (mustExistNode != null)
{
XmlNode node = folderNode.OwnerDocument.CreateNode(XmlNodeType.Element, "nodeToBeUpdated", null);
node.InnerText = newVal;
folderNode.AppendChild(node);
}
}
}
If you want to update a particular node, you cannot pass string nodeToBeUpdte, but you will have to pass the XmlNode of the XmlDocument.
I have omitted the passing of node names in the function since nodes names are unlikely to change and can be hardcoded. However, you can pass these to the functions and use the strings instead of hardcoded node names.
The XPath expression that selects all instances of <nodeToBeUpdated> would be this:
/root/folder[nodeMustExist]/nodeToBeUpdated
or, in a more generic form:
/root/folder[*[name() = 'nodeMustExist']]/*[name() = 'nodeToBeUpdated']
suitable for:
void UpdateNode(xmlDocument xml,
string nodeMustExist,
string nodeToBeUpdte,
string newVal)
{
string xPath = "/root/folder[*[name() = '{0}']]/*[name() = '{1}']";
xPath = String.Format(xPath, nodeMustExist, nodeToBeUpdte);
foreach (XmlNode n in xml.SelectNodes(xPath))
{
n.Value = newVal;
}
}
Have a look at the SelectSingleNode method MSDN Doc
your xpath wants to be something like "//YourNodeNameHere" ;
once you have found that node you can then traverse back up the tree to get to the 'nodeMustExist' node:
XmlNode nodeMustExistNode = yourNode.Parent["nodeMustExist];