Target specific Xml Node in c# - c#

As I said in the Title: I'm trying to delete a specific XML node(in c#).
so I researched and found this:
XmlDocument xmlDoc = new XmlDocument();
XmlNode nodeToDelete = xmlDoc.SelectSingleNode("/root/XMLFileName[#ID="+nodeId+"]");
if (nodeToDelete != null)
{
nodeToDelete.ParentNode.RemoveChild(nodeToDelete);
}
xmlDoc.Save("XMLFileName.xml")
I understand here that he's targeting a specific node ( in this case "[#ID ="+nodeId+)
Ok, my question is can I do the same just in the opposite way, by that I mean if its not nodeId then delete it (just like the "!="-Operator in C#).

First of all, the code you posted uses SelectSingleNode which will allways return only one node.
First thing to do : using SelectNodes whose doc is here.
THe result would be :
List<XmlNode> nodes = new List<XmlNode>(
xmlDoc.SelectNodes("//root/XMLFileName[#ID!="+nodeId+"]")
.Cast<XmlNode>());//execute the query and put it into a list
// as my list is strongly typed, I **must** use cast method
// thanks to this list, I can modify the document without changing the
// collection that foreach is traversing
foreach(var nodeToDelete in nodes){
if (nodeToDelete != null)
{
nodeToDelete.ParentNode.RemoveChild(nodeToDelete);
}
}
xmlDoc.Save("XMLFileName.xml")
You can also use a linq query as you are in C# as explained here.

Related

Add multiple child nodes to xml using xpath

I have a xml like below
<ProcessInvoice>
<ApplicationArea>
<CreationDateTime>2016-06-01 13:15:36</CreationDateTime>
<ApplicationGroup>BBEX</ApplicationGroup>
<MessageType>PROCESSINVOICE</MessageType>
</ApplicationArea>
</ProcessInvoice>
Now I have path and value to add, but it is dynamic.
It can be like following
path-/ProcessInvoice/ApplicationArea/UserArea/Sample1
value-001
path-/ProcessInvoice/ApplicationArea/UserArea/UserAreaLine/Sample1
value-002
if the path is present then i have to add the value, else modify the value.
I can split the path and loop through to find till what node is present and what i have to add but I think there might be more elegant way of doing this.Please help me with the best approach to solve this?
Edit
Note- I will prefer XDocument And XElement.
May be I didnt explain properly.
My xml and node path both are dynamic.
There might be situation where multiple nodes are missing from my xml.
Now problem is i need to identify upto which node is existing in xml and which nodes i need to create.
Thanks
If "Sample1" node always exist, the code will look like this:
XmlDocument doc = new XmlDocument();
doc.Load(FILE);
var userArea = DocumentElement["ProcessInvoice"]["ApplicationArea"]["UserArea"];
foreach (XmlNode element in userArea.ChildNodes)
{
if (element.Name== "Sample1" )
{
XmlNode node == element;
node.InnerText ="001";
}
else if (element.Name == "UserAreaLine")
{
XmlNode node == element["Sample1"];
node.InnerText ="002";
}
}

Read XML Attribute using C#

<Block ID="Ar0010100" BOX="185 211 825 278" ELEMENT_TYPE="h1" SEQ_NO="0" />
This is an example from my XML code. In C# I need to store ONLY ID'S inside of a block element in one variable, and ONLY Box's inside of a block element. I have been trying to do this for two days, and I don't know how to narrow down my question.
XmlNodeList idList = doc.SelectNodes("/Block/ID");
doesn't work... Any version of doc.selectnode, doc.GetElementBy... doesn't return the right element/children/whatever you call it. I'm not able to find documentation that tells me what I'm trying to reference. i don't know if ID or BOX are children, if they're attributes or what. This is my first time using XML, and I can't seem to narrow down my problem.
You can simply use following code
XmlNodeList elemList = doc.GetElementsByTagName("Your Element");
for (int i = 0; i < elemList.Count; i++)
{
string attrVal = elemList[i].Attributes["ID"].Value;
}
Demo: https://dotnetfiddle.net/5PpNPk
the above code is taken from here Read XML Attribute using XmlDocument
The problem is that ID is actually neither child nor part.
It's a node's attribute. You can access it this way:
doc.SelectSingleNode("/Block").GetAttribute("ID")
// or
doc.SelectSingleNode("/Block").Attributes["ID"].Value
Of course, you can iterate through them:
foreach (XmlElement element in doc.SelectNodes("/Block"))
{
Console.WriteLine(element.GetAttribute("ID"));
}
You also can ensure that it contains ID attribute, so, you won't get NullReferenceException or other exception. Use the following XPath:
foreach (XmlElement element in doc.SelectNodes("/Block[#ID]"))
{
Console.WriteLine(element.GetAttribute("ID"));
}
Your attempted xpath tried to find <Block> element having child element <ID>. In xpath, you use # at the beginning of attribute name to reference an attribute, for example /Block/#ID.
Given a correct xpath expression as parameter, SelectNodes() and SelectSingleNode() are capable of returning attributes. Here is an example :
var xml = #"<Block ID=""Ar0010100"" BOX=""185 211 825 278"" ELEMENT_TYPE=""h1"" SEQ_NO=""0"" />";
var doc = new XmlDocument();
doc.LoadXml(xml);
XmlNodeList idList = doc.SelectNodes("/Block/#ID");
foreach(XmlNode id in idList)
{
Console.WriteLine(id.Value);
}
Demo

LINQ: How to return all child elements?

For an application I am working on, I have to display data from an XML File. There's a few transformations being done, but eventually the end result will be displayed in a treeview. When a user then clicks on a node, I want to pop up the details in a listview.
When no node has been selected, I basically use LINQ to grab the details of the first item I encounter.
Here's a simplified version of my XML
<root>
<parent label="parent1">
<child label="child1">
<element1>data</element1>
<element2>data</element2>
...
</child>
<child label="child2">
<element1>data</element1>
<element2>data</element2>
...
</child>
</parent>
</root>
And here's the code used to grab it (After selecting the parent-node that the treeview has been set to by means of an XPAthSelectStatement):
protected void listsSource_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
XElement rootElement = XElement.Load(MapPath(TreeSource.DataFile));
rootElement = rootElement.XPathSelectElement("//parent[#label='parent1']");
XElement parentElement;
parentElement = rootElement;
var query = (from itemElement in parentElement.Descendants("child")
select new
{
varElement1 = itemElement.Element("element1").Value,
varElement2 = itemElement.Element("element2").Value,
...
}).Take(1);
e.result = Query;
}
This works a treat, and I can read out the varElement1 and varElement2 values from there. However, when I try and implement a similar mechanism for when the user actually did select a node, I seem to run into a wall.
My approach was to use another XPatchSelectStatement to get to the actual node:
parentElement = rootElement.XPathSelectElement("//child[#label='" + tvwChildren.SelectedNode.Text + "']");
But I am kind of stumped on how to now get a proper LINQ query built up to read in all elements nested under the child node. I tried using parentElement.Elements(), but that was yielding an error. I also looked at using Nodes(), but with similar results.
I suppose I could use a foreach loop to access the nodes, but then I'm not sure how to get the results into a LINQ query so I can return the same e.Result = query back.
I'm fairly new to LINQ, as you might have guessed, so any hints would be very much appreciated.
Here's the query that will give you the child element (given that there is only one child element with the specified label):
var childElement = rootNode.Descendants("child")
.Single(e=>e.Attribute("label").Value == "child1");
If you have more than one child elements with label="child1" but those elements are under different parent elements you can use the same approach to get first the parent element and then the child element.
Having the above, you can use this query to get all element nodes under the child node:
var elements = childElement.Descendants().Select(e=>e.Value);
I think data binding is much easier in this case.
XDocument doc = XDocument.Load(filePath);
if (doc.Root == null)
{
throw new ApplicationException("invalid data");
}
tvwChildren.Source=doc;
But if you want in this way hope following one helps(not the exact solution)
XElement root = XElement.Load("Employees.xml");
TreeNode rootNode = new TreeNode(root.Name.LocalName);
treeView1.Nodes.Add(rootNode);
foreach(XElement employee in root.Elements())
{
TreeNode employeeNode = new TreeNode("Employee ID :" + employee.Attribute("employeeid").Value);
rootNode.Nodes.Add(employeeNode);
if (employee.HasElements)
{
foreach(XElement employeechild in employee.Descendants())
{
TreeNode childNode = new TreeNode(employeechild.Value);
employeeNode.Nodes.Add(childNode);
}
}
}
And you can try Resharper tool for create better linq statements. It shows possible ones and you can easily convert each for,foreach loops into linq statements.
I'm not entirely sure I understand what you're trying to do, but it sounds like it could be this:
var data =
from p in xml.Root.Elements("parent")
where p.Attribute("label").Value == "parent1"
from c in p.Elements("child")
where c.Attribute("label").Value == "child2"
from d in c.Elements()
select d.Value;
Let me know if that helps.
Using this Xml library you can write your XPath like:
XElement child = rootElement.XPathElement(
"//parent[#label={0}]/child[#label={1}]", "parent1", "child2");

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

Removing nodes from an XmlDocument

The following code should find the appropriate project tag and remove it from the XmlDocument, however when I test it, it says:
The node to be removed is not a child of this node.
Does anyone know the proper way to do this?
public void DeleteProject (string projectName)
{
string ccConfigPath = ConfigurationManager.AppSettings["ConfigPath"];
XmlDocument configDoc = new XmlDocument();
configDoc.Load(ccConfigPath);
XmlNodeList projectNodes = configDoc.GetElementsByTagName("project");
for (int i = 0; i < projectNodes.Count; i++)
{
if (projectNodes[i].Attributes["name"] != null)
{
if (projectName == projectNodes[i].Attributes["name"].InnerText)
{
configDoc.RemoveChild(projectNodes[i]);
configDoc.Save(ccConfigPath);
}
}
}
}
UPDATE
Fixed. I did two things:
XmlNode project = configDoc.SelectSingleNode("//project[#name='" + projectName + "']");
Replaced the For loop with an XPath query, which wasn't for fixing it, just because it was a better approach.
The actual fix was:
project.ParentNode.RemoveChild(project);
Thanks Pat and Chuck for this suggestion.
Instead of
configDoc.RemoveChild(projectNodes[i]);
try
projectNodes[i].parentNode.RemoveChild(projectNodes[i]);
try
configDoc.DocumentElement.RemoveChild(projectNodes[i]);
Looks like you need to select the parent node of projectNodes[i] before calling RemoveChild.
When you get sufficiently annoyed by writing it the long way (for me that was fairly soon) you can use a helper extension method provided below. Yay new technology!
public static class Extensions {
...
public static XmlNode RemoveFromParent(this XmlNode node) {
return (node == null) ? null : node.ParentNode.RemoveChild(node);
}
}
...
//some_long_node_expression.parentNode.RemoveChild(some_long_node_expression);
some_long_node_expression.RemoveFromParent();
Is it possible that the project nodes aren't child nodes, but grandchildren or lower? GetElementsByTagName will give you elements from anywhere in the child element tree, IIRC.
It would be handy to see a sample of the XML file you're processing but my guess would be that you have something like this
<Root>
<Blah>
<project>...</project>
</Blah>
</Root>
The error message seems to be because you're trying to remove <project> from the grandparent rather than the direct parent of the project node

Categories

Resources