Change Value of nested node - c#

This seems like a simple question but I can't seem to get started on a working solution. The final goal is to change the value of the ConstantValue element highlighted below. My strategy is to find the Component node and drill down from there. The problem is that keep returning a null and I'm not sure why. Below is the code I'm using a the xml I'm using. Any hints would be great.
XDocument xmlDoc = XDocument.Parse(str);
var items = xmlDoc.Descendants("Component")
.Where(x => x.Attribute("Name").Value == "axesInterface")
.FirstOrDefault();
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Engineering version="V17" />
<DocumentInfo>
</DocumentInfo>
<SW.Blocks.FB ID="0">
<AttributeList>
<Interface><Sections></Sections></Interface>
<MemoryLayout>Optimized</MemoryLayout>
<MemoryReserve>100</MemoryReserve>
<Name>EM00_CM01_Warp1</Name>
<Number>31650</Number>
<ProgrammingLanguage>LAD</ProgrammingLanguage>
<SetENOAutomatically>false</SetENOAutomatically>
</AttributeList>
<ObjectList>
<SW.Blocks.CompileUnit ID="4" CompositionName="CompileUnits">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4">
<Parts>
<Access Scope="GlobalVariable" UId="27">
<Symbol>
<Component Name="HMIAxisCtrl_Interface" />
<Component Name="axesInterface" AccessModifier="Array">
<Access Scope="LiteralConstant">
<Constant>
<ConstantType>DInt</ConstantType>
<ConstantValue>0</ConstantValue>
</Constant>
</Access>
</Component>
</Symbol>
</Access>
</Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.Blocks.CompileUnit>
</ObjectList>
</SW.Blocks.FB>
</Document>

You can use XQuery to get the correct node:
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
//
var nm = new XmlNamespaceManager(new NameTable());
nm.AddNamespace("sm", "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4");
var node = xdoc.XPathSelectElement(#"//sm:Access[#Scope=""LiteralConstant""]/sm:Constant/sm:ConstantValue", nm);
node.Value = "Something Else";
dotnetfiddle
For multiple nodes, change XPathSelectElement to XPathSelectElements

On your XML example, you can simply get specified node with this piece of code at any depth:
XDocument xmlDoc = XDocument.Parse(str);
XElement node = xmlDoc.Descendants().FirstOrDefault(x => x.Name.LocalName == "ConstantValue");
node.Value = "New value";
xmlDoc.Save(somewhere);
If there may be multiple nodes with "ConstantValue" name - then just replace FirstOrDefault with Where and work with filtered by names IEnumerable<XElement>.
Why it isn't find by x.Name == "ConstantValue":
EDIT: added samples.
// That's your parent node
var componentNode = xmlDoc.Descendants()
.Where(x => x.Name.LocalName == "Component"
&& (string)x.Attribute("Name") == "axesInterface");
// Get first ConstantValue node from parent Component node
var constantValueNode = componentNode.Descendants()
.FirstOrDefault(x => x.Name.LocalName == "ConstantValue");
// or get all ConstantValue nodes from parent Component node
var constantValueNodes = componentNode.Descendants()
.Where(x => x.Name.LocalName == "ConstantValue");
if (constantValueNode is XElement)
constantValueNode.Value = "New value";
You can create extension method to get parent node by specifying level:
public static class Extensions
{
public static XElement GetParentNode(this XElement element, int parentLevel)
{
XElement node = element.Parent;
for (int i = 0; i < parentLevel; i++)
node = node.Parent;
return node;
}
}
In your example, var parent = constantValueNode.GetParentNode(2); will return Component node (with attribute Name="axesInterface"). var parent = constantValueNode.GetParentNode(12); will return root "Document" node.

Related

How can name value paired XML entries be accessed directly?

I have a vendor provided XML file that I need to modify programmatically. The items (nodes, elements, attributes) are several levels deep and have multiple name value paired entries.
<root>
<VendorEntries>
<VendorEntry Name="Entry1">
<Attributes>
<Attribute Name="A" Value="abc"/>
<Attribute Name="B" Value="xyz"/>
</Attributes>
</VendorEntry>
<VendorEntry Name="Entry2">
<Attributes>
<Attribute Name="A" Value="lmn"/>
<Attribute Name="B" Value="qrs"/>
</Attributes>
</VendorEntry>
</VendorItems>
</root>
When looping through the following (in the VS2015 debugger), I see each of the ChildNodes, but don't see how to gain access to Entry1/A so it can be updated from "abc" to "efg"...
XmlDocument vendorXML = new XmlDocument();
vendorXML.Load(#"C:\path\file.xml");
XmlNodeList entries= vendorXML.SelectNodes("/root/VendorEntries/VendorEntry");
foreach (XmlNode entry in entries) { // /root/VendorEntries/VendorEntry(s) nodes
XmlAttribute entryName = entry.Attributes["Name"];
Console.WriteLine($"{entry.Name} {entryName.Value}"); // VendorEntry
foreach (XmlNode atNodes in entry.ChildNodes) { // /root/VendorEntries/VendorEntry/Attributes(s) nodes
foreach (XmlNode atNode in atNodes.ChildNodes) { // /root/VendorEntries/VendorEntry/Attributes/Attribute(s) nodes
XmlAttribute atName = atNode.Attributes["Name"];
XmlAttribute atValue = atNode.Attributes["Value"];
Console.WriteLine($"..{atNode.Name} {atName.Value} {atValue.Value}"); // ..Attribute Name Value>
if (entryName.Value.Equals("SOME_ENTRY") && atName.Value.Equals("SOME_PARAM"))
{
atValue.Value = "NEW PARAM ENTRY";
}
}
}
}
vendorXML.Save(#"C:\path\file.xml");
Modified: (Thanks to elgonzo) the code works now.
However, I still don't see a way to directly access the specific attribute to modify without looping through all of the ones that don't need modification. Does someone have a way to do this?
With XDocument, you can use Linq to XML to select specifically what you want to modify:
var vendorXml = XDocument.Load(#"c:\path\file.xml");
vendorXml.Descendants("VendorEntry")
.Where(a => a.Attribute("Name").Value == "Entry1")
.Descendants("Attribute")
.SingleOrDefault(a => a.Attribute("Name").Value == "A")
.SetAttributeValue("Value", "efg");
Or, as #Prany suggested, you can select the element using XPath:
vendorXml
.XPathSelectElement("//VendorEntry[#Name='Entry1']/Attributes/Attribute[#Name='A']")
.SetAttributeValue("Value", "efg");
Or if for some reason you want to use XmlDocument, you can use the same approach with that:
XmlDocument vendorXml = new XmlDocument();
vendorXml.Load(#"c:\path\file.xml");
var node = (XmlElement)vendorXml.SelectSingleNode("//VendorEntry[#Name='Entry1']/Attributes/Attribute[#Name='A']");
node.SetAttribute("Value", "efg");
You can use XpathSelectElement
XDocument doc = XDocument.Load(path);
string value = doc.XPathSelectElement("//VendorEntries/VendorEntry[1]/Attributes/Attribute[1]").LastAttribute.Value;
//This will select value from Entry1/A
I like using Xml Linq and putting results in a nested dictionary :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, Dictionary<string, XElement>> dict = doc.Descendants("VendorEntry")
.GroupBy(x => (string)x.Attribute("Name"), y => y.Descendants("Attribute")
.GroupBy(a => (string)a.Attribute("Name"), b => b)
.ToDictionary(a => a.Key, b => b.FirstOrDefault()))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
Dictionary<string, XElement> entry2 = dict["Entry2"];
entry2["B"].SetAttributeValue("Value", "xyz");
doc.Save(FILENAME);
}
}
}

How to delete nodes from XML with exact string match in c#

I have below xml. I want to delete those nodes which doesn't contains attributes. in below xml I want to delete last 4 nodes which doesn't have attributes.
XML
<products>
<product system="kn-movies" code="UR">Unrated</product>
<product system="mu-movies" code="UR">Unrated</product>
<product system="na-movies" code="UR">Unrated</product>
<product system="fj-movies" code="UR">Unrated</product>
<product>Unrated (Unrated )</product>
<product>Unrated (Unrated )</product>
<product>Unrated (Без классификации )</product>
<product>Unrated (غير مصنف )</product>
</products>
I had tried this c# code
var ratingNode = document.Descendants("rating").Where(t => t != null && t.Equals(Convert.ToString(nodeItem))).FirstOrDefault();
if (ratingNode != null)
{
ratingNode.Remove();
}
but it doesn't worked for me. please help me out where i m doing mistakes.
System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Load(filepath);
var newList = doc.Root.Descendants().ToList().RemoveAll(x => x.Attributes() == null || x.Attributes().Count() <= 0);
Hope it helps!!
Hope this will do trick
XmlNodeList nodes = xmlDocument.GetElementsByTagName("products");
foreach(XmlNode node in nodes)
{
if(node.Attributes.Count == 0)
{
node.RemoveAll;
}
}
You could use Linq to Xml and query for elements which has no attributes, then you just need Remove call to remove those elements.
XDocument doc = XDocument.Load(filepath);
doc.Root
.Descendants() // flattens the structure
.Where(x=> x.Attributes().Count() <= 0) // filter elements which has no attributes
.Remove(); // Remove them
Check this Demo

Get child node attribute value based on parent node attribute value

I have a xml given below
<session xmlns="http://test.net/schema/session/1.0" name="test" start="2015-07-01T07:20:31.425Z">
<download>
<filename value="/UAT/Incoming/ Status/Archive/8-22-2011 3-20-14 PM306.xml" />
<result success="false">
<message>Timeout waiting .</message>
</result>
</download>
</session>
I want to select the message node value only if Result node value is false.
I donot want to check on hard coded parent node like node download because it may change
Can anyone help me please..
XDocument doc = XDocument.Load("your xml file path");
var result = doc.Elements().
First(e => e.Name == "download")
.Elements().First(e => e.Name == "result");
if (result.Attributes().First(a => a.Name == "success").Value == "false")
return result.Elements().First(e => e.Name == "message").Value;
This worked for me:
XNamespace ns = XNamespace.Get("http://test.net/schema/session/1.0");
IEnumerable<XElement> failures =
doc
.Descendants(ns + "download")
.Concat(doc.Descendants(ns + "upload"))
.Elements(ns + "result")
.Elements(ns + "message")
.Where(e => e.Parent.Attributes("success").Any(a => !(bool)a));
From your input I got this:
First of all you need to set the namespace for your XML file like this:-
XNamespace ns = "http://test.net/schema/session/1.0";
I will start looking into descendants of download because I need to find the result element which contains the success attribute (whose value we want to check). Thus, a simple where condition will filter those nodes for us and finally we can select the message node.
Update:
You can use Concat if you want to search both download & upload like this:-
IEnumerable<XElement> result = xdoc.Descendants(ns + "download")
.Concat(xdoc.Descendants(ns + "upload"))
.Where(x => x.Element(ns + "result") != null &&
(string)x.Element(ns + "result").Attribute("success") == "false")
.Select(x => x.Element(ns +"result").Element(ns +"message"));
I am also checking in the where clause if result node exist or not by checking for null, otherwise it may result in Null reference exception.
Using some XPath, the following piece of code should work
string xml = #"<session xmlns=""http://test.net/schema/session/1.0"" name=""test"" start=""2015-07-01T07:20:31.425Z"">
<download>
<filename value=""/UAT/Incoming/ Status/Archive/8-22-2011 3-20-14 PM306.xml"" />
<result success=""false"">
<message>Timeout waiting .</message>
</result>
</download>
</session>";
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(xml);
// Here, you load the namespace used in your xml. You'll need it later in your XPath queries
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("test", "http://test.net/schema/session/1.0");
// XPath to select the "result" node whose attribute "success" equals false
XmlElement xelt = xdoc.DocumentElement.SelectSingleNode("descendant::test:download/test:result[#success=\"false\"]", nsmgr) as XmlElement;
// return the "message" node
return xelt.FirstChild as XmlElement;
As already mentioned, you'll find more details about XPath expression on this link: https://msdn.microsoft.com/en-us/library/d271ytdx%28v=vs.110%29.aspx

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

Filter XDocument more efficiently

I would like to filter with high performance XML elements from an XML document.
Take for instance this XML file with contacts:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="asistentes.xslt"?>
<contactlist evento="Cena Navidad 2010" empresa="company">
<contact type="1" id="1">
<name>Name1</name>
<email>xxxx#zzzz.es</email>
<confirmado>SI</confirmado>
</contact>
<contact type="1" id="2">
<name>Name2</name>
<email>xxxxxxxxx#zzzze.es</email>
<confirmado>Sin confirmar</confirmado>
</contact>
</contaclist>
My current code to filter from this XML document:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #" the xml above";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("contact")) {
Console.WriteLine(element);
var id = element.Attribute("id").Value;
var valor = element.Descendants("confirmado").ToList()[0].Value;
var email = element.Descendants("email").ToList()[0].Value;
var name = element.Descendants("name").ToList()[0].Value;
if (valor.ToString() == "SI") { }
}
}
}
What would be the best way to optimize this code to filter on <confirmado> element content?
var doc = XDocument.Parse(xml);
var query = from contact in doc.Root.Elements("contact")
let confirmado = (string)contact.Element("confirmado")
where confirmado == "SI"
select new
{
Id = (int)contact.Attribute("id"),
Name = (string)contact.Element("name"),
Email = (string)contact.Element("email"),
Valor = confirmado
};
foreach (var contact in query)
{
...
}
Points of interest:
doc.Root.Elements("contact") selects only the <contact> elements in the document root, instead of searching the whole document for <contact> elements.
The XElement.Element method returns the first child element with the given name. No need to convert the child elements to a list and take the first element.
The XElement and XAttribute classes provide a wide selection of convenient conversion operators.
You could use LINQ:
foreach (XElement element in doc.Descendants("contact").Where(c => c.Element("confirmado").Value == "SI"))

Categories