How to write xpath for this XDocument? - c#

<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()

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

How to change an attribute in a subnode in C#?

I want to change the attribute value (in this case "51") of the "NextMemberId" in an xml file that looks like this:
<File>
<MemberList>
<NextMemberId Value="51" />
<Member Id="1" ..... />
<Member Id="2" ..... />
</MemberList>
</File>
The following code works, but I would like to know if it can be done in a more direct way without having to run a foreach loop:
var memberId = 1;
var memberlist = Doc.DocumentElement.SelectSingleNode("MemberList");
foreach (XmlNode node in memberlist.ChildNodes)
{
var nodeElement = node as XmlElement;
if (nodeElement != null && nodeElement.Name == "NextMemberId")
{
nodeElement.SetAttribute("Value", memberId.ToString());
}
}
Thanks for any inspiration!
The correct path to get NextMemberId from File according to your sample XML would be :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId");
nodeElement.SetAttribute("Value", memberId.ToString());
If there are multiple NextMemberId in your actual XML, and you need to filter by Value attribute, then you can add an XPath predicate similar to what the other answer suggested :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId[#Value=51");
Notice that you can choose to keep or leave single-quotes around 51 depending on whether you want compare the Value as a string or a number, respectively.
You can select single node with specified attribute like this:
var nextMemberIdNode = Doc.DocumentElement.SelectSingleNode("NextMemberId[#Value='51']")

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;
}​

In XML, check if child nodes contain specific values with C#

I'm getting back some XML and this is a section of it:
<pair name="cf_item7" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">0</pair>
</pair>
Within the XML are a number of pairs. The section I need to check with always have a name beginning with cf_item* but this could be cf_item7, cf_item6, or any other number.
I need to check within one of these cf_item* sections, and whether or not it has a cf_id, with the content equalling 34 (it will always be 34).
If 34 exists, I need to check the next node (value), and check if it is equal to 0 or 1.
If it equals 0, I need to output a message to the screen (web page).
I've inherited the original code, which is written in C# .net - Can anyone help as it's not a language I'm familiar with?
I started with this, based on some of the other code, but I'm unsure where to go next.
if (eleName.Contains("cf_item")) {
XmlNode nodes = eleList[i].ChildNodes;
}
First, get the first element with pair name then check if the name attribute contains cf_item.
If yes, get all elements with pair name from that first element.
The result will be in an IEnumerable<XElement> then you can check the first element if it contains 34 and if the second element contains 0 or not.
The code will be something like this:
XDocument xmldata = XDocument.Load(xmlFile);
XElement node = xmldata.Element("pair");
if (node.Attribute("name").Value.Contains("cf_item"))
{
IEnumerable<XElement> nextElmts = node.Elements("pair");
if (nextElmts.ElementAt(0).Attribute("name").Value.Contains("cf_id"))
{
if ((Convert.ToInt32(nextElmts.ElementAt(0).Value) == 34))
{
if (Convert.ToInt32(nextElmts.ElementAt(1).Value) == 0)
{
//do what you want here
Console.WriteLine("it's 0");
}
}
}
}
Note: this is only for one cf_item. You need to modified it if cf_item is more than one.
Using System.Xml.Linq.XElement, you can do something like this:
namespace ConsoleApplication4
{
using System;
using System.Linq;
using System.Xml.Linq;
internal class Program
{
private static void Main(string[] args)
{
var xmlDocument = XElement.Load(#"c:\tmp\foo.xml");
// Loop over all elements having a "name" attribute starting with "cf_item".
foreach (var pairElement in xmlDocument.Elements().Where(x => x.Attributes().Any(y => y.Name == "name" && y.Value.StartsWith("cf_item"))))
{
// Is there a child element, having a "name" attribute set to "cf_id" with a value of "34"?
if (pairElement.Elements().Any(x => x.Attributes().Any(y => y.Name == "name" && y.Value == "cf_id") && x.Value == "34"))
{
// Fetch the single element with a "name" attribute set to "value".
var valueElement = pairElement.Elements().Single(x => x.Attributes().Any(y => y.Name == "name" && y.Value == "value"));
// Output the value of it!
Console.WriteLine(valueElement.Value);
}
else
{
Console.WriteLine("No cf_id set to 34.");
}
}
}
}
}
XML
<root>
<pair name="cf_item7" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">0</pair>
</pair>
<pair name="cf_item8" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">1</pair>
</pair>
<pair name="cf_item9" type="pair">
<pair name="value" type="string">0</pair>
</pair>
</root>
Output
0
1
No cf_id set to 34.
Press any key to continue . . .
This should work:
var valueAfter34 = (from e in doc.Descendants("pair")
where e.Attribute("name").Value.StartsWith("cf_item")
let itemPairs = e.Descendants()
from idElement in itemPairs
where idElement.Attribute("name").Value.Equals("cf_id") &&
idElement.Value == "34"
select idElement.ElementsAfterSelf().FirstOrDefault().Value).FirstOrDefault();
if (valueAfter34 == "0")
{
// show message
}
It assumes there is one pair element with cf_id 34. If that's not the case it should need some fiddling.

select multiple node to remove

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");

Categories