select multiple node to remove - c#

It's been hours I'm figuring this but no luck. I'd like to ask how to remove multiple nodes.
When I did Single node I don't have any problem but when I added condition "OR" or else if it always remove the whole nodes.
Hope somebody could help.
Here is what i have done so far:
XML
<ApplDetails>
<NodeLog>
<PName>
test123
</PName>
</NodeLog>
<NodeLog>
<PName>
test125
</PName>
</NodeLog>
<NodeLog>
<PName>
test126
</PName>
</NodeLog>
</ApplDetails>
c# Codes
XmlDocument xdoc1 = new XmlDocument();
xdoc1.Load("D:\\nodes.xml");
foreach (XmlNode node in xdoc1.SelectNodes("ApplDetails/NodeLog"))
{
if (node.SelectSingleNode("PName").InnerText != "test123" || node.SelectSingleNode("PName").InnerText != "test125" )
{
node.ParentNode.RemoveChild(node);
}
}
xdoc1.Save("D:\\nodes.xml");

A value cannot be equal to test123 and at the same time to test125 so the condition is always true (at most one clause can be false - try for test123 and you will get 'test123' != 'test123' || 'test123' != 'test125' - the first clause is false but the second is true so the expression evaluates to true). If you want to remove all the elements whose value is not 'test123' and not 'test125' you need && and not ||.

In LINQ to XML:
var xDoc = XDocument.Load(#"D:\nodes.xml");
xDoc.Root.Elements("NodeLog")
.Where(w => (string)w.Element("PName") == "test123" || (string)w.Element("PName") == "test125")
.Remove();
xDoc.Save(#"D:\nodes.xml");

Related

XDocument: How to find an attribute value in nested xml

Given the working code snippet below, I'm trying to get the target element inside RootChild2.
https://dotnetfiddle.net/eN4xV9
string str =
#"<?xml version=""1.0""?>
<!-- comment at the root level -->
<Root>
<RootChild1>
<Child>Content</Child>
<Child>Content</Child>
<Child>Content</Child>
</RootChild1>
<RootChild2>
<Child>Content</Child>
<Child key='target'>Content</Child>
<Child>Content</Child>
</RootChild2>
</Root>";
XDocument doc = XDocument.Parse(str);
foreach (XElement element in doc.Descendants("RootChild2"))
{
if (element.HasAttributes && element.Element("Child").Attribute("key").Value == "target")
Console.WriteLine("found it");
else
Console.WriteLine("not found");
}
This finds all 3 RootChild2/Child elements, then tests each in turn:
foreach (XElement child in doc.Descendants("RootChild2").Elements("Child"))
{
if ((string)child.Attribute("key") == "target")
Console.WriteLine("found it");
else
Console.WriteLine("not found");
}
There are three problems here:
You're checking if the RootChild2 element has any attributes - and it doesn't
You're only checking the first Child element under each RootChild2 element
You're assuming the attribute is present (by dereferencing the XAttribute)
Here's code which will find all the target elements within a RootChild2:
foreach (XElement element in doc.Descendants("RootChild2"))
{
var targets = element
.Elements("Child")
.Where(child => (string) child.Attribute("key") == "target")
.ToList();
Console.WriteLine($"Found {targets.Count} targets");
foreach (var target in targets)
{
Console.WriteLine($"Target content: {target.Value}");
}
}
Note that the cast of XAttribute to string is a simple way of avoiding null reference issues - because the result of the explicit conversion is null when the source is null. (That's a general pattern in LINQ to XML.)
You are accessing the RootChild2-Element itself through element inside your loop.
Take a look at the following version:
foreach (XElement element in doc.Descendants("RootChild2").Nodes())
{
if (element.HasAttributes && element.Attribute("key").Value == "target")
Console.WriteLine("found it");
else
Console.WriteLine("not found");
}
Now it loops through all nodes of RootChild2.
You should rewrite your foreach loop at little bit, add accessing of child Elements() collection in RootChild2 and check every element in enumeration. You also can check that key attribute is present in element to prevent a potential null reference exception
foreach (XElement element in doc.Descendants("RootChild2").Elements())
{
if (element.HasAttributes && element.Attribute("key")?.Value == "target")
Console.WriteLine("found it");
else
Console.WriteLine("not found");
}
Descendants(XName) returns only elements with matching names, therefore you are getting only one RootChild2 element as the result in your code

Add null as value when element does not exist in XML file C#

In C# I'm using the following to get some elements from an XML file:
var TestCaseDescriptions = doc.SelectNodes("//testcase/htmlcomment");
This works fine and gets the correct information but when my testcase has no htmlcomment it won't add any entry in the XmlNodeList TestCaseDescriptions.
When there's not htmlcomment I would like to have the value "null" as string the TestCaseDescriptions. So in the end I would have an XMLNodeList like
htmlcomment
htmlcomment
null
htmlcomment
htmlcomment
Can anyone describe or create a sample how to make this happen?
var TestCaseDescriptions = doc.SelectNodes("//testcase/htmlcomment");
When there's not htmlcomment I would like to have the value "null" as string the TestCaseDescriptions.
Your problem comes from the fact that if there is no htmlcomment, the number of selected nodes will be one less. The current answer shows what to do when the htmlcomment element is present, but empty, but I think you need this instead, if indeed the whole htmlcomment element is empty:
var testCases = doc.SelectNodes("//testcase");
foreach (XmlElement element in testCases)
{
var description = element.SelectSingleNode("child::htmlcomment");
string results = description == null ? "null" : description.Value;
}​
In above code, you go over each test case, and select the child node htmlcomment of the test case. If not found, SelectSingleNode returns null, so the last line checks for the result and returns "null" in that case, or the node's value otherwise.
To change this result into a node, you will have to create the node as a child to the current node. You said you want an XmlNodeList, so perhaps this works for you:
var testCaseDescriptions = doc.SelectNodes("//testcase");
foreach (XmlElement element in testCaseDescriptions)
{
var comment = element.SelectSingleNode("child::htmlcomment");
if (comment == null)
{
element.AppendChild(
doc.CreateElement("htmlcomment")
.AppendChild(doc.CreateTextNode("none")));
}
}​
After this, the node set is updated.
Note: apparently, the OP mentions that element.SelectSingleNode("child::htmlcomment"); does not work, but element.SelectSingleNode("./htmlcomment"); does, even though technically, these are equal expressions from the point of XPath, and should work according to Microsoft's documentation.
Try this
XmlDocument doc = new XmlDocument();
var TestCaseDescriptions = doc.SelectNodes("//testcase/htmlcomment");
foreach (XmlElement element in TestCaseDescriptions)
{
string results = element.Value == null ? "" : element.Value;
}​

Xpath to find first occurrence of two different elements

Using the example below, I would like to use xPath to find the first occurence of two different elements. For example, I want to figure out if b or d appears first. We can obviously tell that b appears before d (looking top-down, and not at the tree level). But, how can I solve this using xpath?
<a>
<b>
</b>
</a>
<c>
</c>
<d>
</d>
Right now, I find the node (b and d in this case) by getting the first element in the nodeset, which I find using the following code:
String xPathExpression = "//*[local-name()='b']";
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(xPathExpression);
and
String xPathExpression = "//*[local-name()='d']";
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(xPathExpression);
Now using xpath, I just can't figure out which comes first, b or d.
You want to scan the tree in document order (the order the elements occur). As though by chance this is the default search order, and all you've got to do is select the first element which is a <b/> or <d/> node:
//*[local-name() = 'b' or local-name() = 'd'][1]
If you want the name, add another local-name(...) call:
local-name(//*[local-name() = 'b' or local-name() = 'd'][1])
If you wanted to use LINQ to XML to solve the same solution you can try the following:
XDocument xmlDoc = new XDocument(filepath);
XElement first = (from x in xmlDoc.Descendants()
where x.Name == "b" || x.Name == "d"
select x).FirstOrDefault();
Now you can run a simple if statement to determine if "b" or "d" was the first element found that matches our criteria.
if(first.Name.Equals("b")
//code for b being first
else if(first.Name.Equals("d")
//code for d being first
Per a commentator's suggestion your code would is cleaner to use a lambda expression instead of a full LINQ query, but this can sometimes be confusing if you are new to LINQ. The following is the exact same query for my XElement assignment above:
XElement first = xmlDoc.Descendants().FirstOrDefault(x => x.Name == "b" || x.Name == "d");
I hope that's what you're looking for if you were open to not using xpath.
This XPath expressions would work:
//*[local-name()='b' or local-name()='d'][1]
Or for a shorter solution, you could try this:
(//b|//d)[1]
Both expressions will select either b elements or d elements, in the order in which they appear, and only select the first element from the result set.

How to write xpath for this XDocument?

<tags>
<data mode="add" name="ttt" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
<data mode="delete" name="test3d" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
</tags>
I want to check whether "mode" is present in xml or not
xdDiffData.XPathSelectElement("//tags[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags[#mode='delete']") != null
This always gives false..how to do this... ?
If you want to make sure that mode attribute is present in every data element, then you should better iterate all the data elements to look for the mode attribute this way:
XDocument doc = XDocument.Load("XmlFile.xml");
var nodes = doc.Descendants("data");
foreach (var node in nodes)
{
var attrMode = node.Attribute("mode");
if (attrMode == null)
{
// mode attribute is not available for this data element
}
}
Using Linq:
if (nodes.Where(c => c.Attribute("mode") == null).Count() == 0)
{
var result = nodes.All(e =>
e.Attribute("mode").Value.Equals("add") ||
e.Attribute("mode").Value.Equals("delete"));
}
else
{
// 'mode' attribute is missing for one or more 'data' element(s)
}
If result equals to true, then it means all the data elements have mode attribute either set to value "add" or "delete".
You are missing the 'data' element. Try
xdDiffData.XPathSelectElement("//tags/data[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags/data[#mode='delete']") != null
xdDiffData.XPathSelectElement("/tags/data[#mode='add']") != null
I want to check whether "mode" is present in xml or not
Use:
//#mode
if this XPath expression selects a node, this means that an attribute named mode is present in the XML document.
or you could use:
boolean(//#mode)
and this produces a boolean value -- true() or false()

C# check xml node the safe way?

I have the code:
if (Element.SelectSingleNode("/rsp/merged_poco/organizations/organization/name") != null)
this.Organization = Element.SelectSingleNode("/rsp/merged_poco/organizations/organization/name").InnerText;
However there is a changes that "merged_poco" may not have "organizations". Would my code result in an error? Or just that null check suffice?
In my null check should I also check to see if "organizations" and "organization" exist?
Your code should suffice. You can also optimize it as the following:
XmlNode node = Element.SelectSingleNode("/rsp/merged_poco/organizations/organization/name");
if (node != null)
this.Organization = node.InnerText;
If you want to know how exactly does a certain method behave, you should read its documentation:
Return Value
Type: System.Xml.XmlNode
The first XmlNode that matches the XPath query or null if no matching node is found.

Categories