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
Related
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";
}
}
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.
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");
I am querying a xml document using the XPathSelectElement method.
if the node does not exist I would like to insert a node with that path in the same document. The parent nodes should also be created if they do not exist. Is there an easy way to do this without looping through the parents checking if they exist? (Add a new node using XPath)
No, there is not... this is no different than if you were looking for a Directory on a File System, and had to ensure that all of the parent directories were there to.
Example:
if (Directory.Exists(#":c:\test1\test2\blah blah\blah blah2")) ...
It's true that the Directory.CreateDirectory method will create all parents that need to be there to have the child show up, but there is no equivalent in XML (using .NET classes, including LINQ-to-XML).
You'll have to loop through each one manually. I suggest you make a helper method called "EnsureNodeExists" that does that for you :)
static private XmlNode makeXPath(XmlDocument doc, string xpath)
{
return makeXPath(doc, doc as XmlNode, xpath);
}
static private XmlNode makeXPath(XmlDocument doc, XmlNode parent, string xpath)
{
// grab the next node name in the xpath; or return parent if empty
string[] partsOfXPath = xpath.Trim('/').Split('/');
string nextNodeInXPath = partsOfXPath.First();
if (string.IsNullOrEmpty(nextNodeInXPath))
return parent;
// get or create the node from the name
XmlNode node = parent.SelectSingleNode(nextNodeInXPath);
if (node == null)
node = parent.AppendChild(doc.CreateElement(nextNodeInXPath));
// rejoin the remainder of the array as an xpath expression and recurse
string rest = String.Join("/", partsOfXPath.Skip(1).ToArray());
return makeXPath(doc, node, rest);
}
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml("<feed />");
makeXPath(doc, "/feed/entry/data");
XmlElement contentElement = (XmlElement)makeXPath(doc,"/feed/entry/content");
contentElement.SetAttribute("source", "");
Console.WriteLine(doc.OuterXml);
}
This is a bit late, but for the next poor soul to come here and find no help here's the static method I ended up writing. It's not flawless, but should handle a variety of use cases. Essentially I just go from the bottom of the XPath and keep cutting off sections until I find a match, then go back down the path and create what's missing. It will fail if you use a path like /root/test//someNode if someNode doesn't exist, but for a path ../someNode//foo/bar/zoot, if ../someNode//foo exists it should work fine.
public static XElement GetOrCreateNodeAtXPath(XDocument doc, string key)
{
//you don't really need this, but it makes throwing errors easier when you're 10 levels deep in the recursion
return GetOrCreateNodeAtXPath(doc, key, key);
}
private static XElement GetOrCreateNodeAtXPath(XDocument doc, string key, string originalKey)
{
var node = doc.XPathSelectElement(key);
if (node != null)
return node;
if (!key.Contains('/')) //we've reached the root, and couldn't find anything
throw new Exception($"Could not create node at path {originalKey}, no part of path matches document");
var slashIndex = key.LastIndexOf('/');
var newKey = key.Substring(0, slashIndex);
var newNodeName = key.Substring(slashIndex + 1);
var parentNode = GetOrCreateNodeAtXPath(doc, newKey, originalKey);
var childNode = new XElement(newNodeName);
parentNode.Add(childNode);
return childNode;
}
You may wanna swap XDocument with XElement or XNode or something in the signature, I was just dealign with docs so I used docs.
I have a simple function that's designed to copy a section of an xml document to another. I want to replace one node with the other so ReplaceChild seems like the logical choice. I keep getting the error 'The reference node is not a child of this node.' though. That seems odd since I found that node by asking for the parent in the first place. Any idea what I'm doing wrong?
private static void KeepSection(XmlDocument newDoc, XmlDocument currentDoc, XmlNamespaceManager nsmgr, string path)
{
XmlNode section = currentDoc.SelectSingleNode(path, nsmgr);
XmlNode newSection = newDoc.SelectSingleNode(path, nsmgr);
if (newSection != null && section != null)
{
XmlNode parent = newSection.ParentNode;
parent.ReplaceChild(newSection, newDoc.ImportNode(section, true));
}
}
It looks like you have your ReplaceChild parameters reversed:
public virtual XmlNode ReplaceChild(
XmlNode newChild,
XmlNode oldChild
)
Actually I was being an idiot. I got the parameters to ReplaceChild the wrong way around. The code should have been,
parent.ReplaceChild(newDoc.ImportNode(section, true), newSection);
Sorry about that!