Remove element from XML based on attribute value? - c#

I was trying to remove a descendant element from an XElement (using .Remove()) and I seem to get a null object reference, and I'm not sure why.
Having looked at the previous question with this title (see here), I found a way to remove it, but I still don't see why the way I tried 1st didn't work.
Can someone enlighten me ?
String xml = "<things>"
+ "<type t='a'>"
+ "<thing id='100'/>"
+ "<thing id='200'/>"
+ "<thing id='300'/>"
+ "</type>"
+ "</things>";
XElement bob = XElement.Parse(xml);
// this doesn't work...
var qry = from element in bob.Descendants()
where element.Attribute("id").Value == "200"
select element;
if (qry.Count() > 0)
qry.First().Remove();
// ...but this does
bob.XPathSelectElement("//thing[#id = '200']").Remove();
Thanks,
Ross

The problem is that the collection you are iterating contains some element that don't have the id attribute. For them, element.Attribute("id") is null, and so trying to access the Value property throws a NullReferenceException.
One way to solve this is to use a cast instead of Value:
var qry = from element in bob.Descendants()
where (string)element.Attribute("id") == "200"
select element;
If an element doesn't have the id attribute, the cast will returns null, which works fine here.
And if you're doing a cast, you can just as well cast to an int?, if you want.

Try the following:
var qry = bob.Descendants()
.Where(el => el .Attribute("id") != null)
.Where(el => el .Attribute("id").Value = "200")
if (qry.Count() > 0)
qry.First().Remove();
You need to test for the presence of the id attribute before getting its value.

Related

XML linq need detail info on exception

I am using xml linq on my project. I am dealing with very large xml's for easy understanding purpose I have mentioned small sample xml.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<StackOverflowReply xmlns="http://xml.stack.com/RRAND01234">
<processStatus>
<statusCode1>P</statusCode1>
<statusCode2>P</statusCode2>
<statusCode3>P</statusCode3>
<statusCode4>P</statusCode4>
</processStatus>
</StackOverflowReply>
</soap:Body>
Following is C# xml linq
XNamespace x = "http://xml.stack.com/RRAND01234";
var result = from StackOverflowReply in XDocument.Parse(Myxml).Descendants(x + "Security_AuthenticateReply")
select new
{
status1 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode1").Value,
status2 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode2").Value,
status3 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode3").Value,
status4 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode4").Value,
status5 = StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode5").Value,
};
Here I am getting exception like "Object reference not set to an instance of an object.". Because the tag
<statusCode5>
was not in my xml.In this case I want to get detail exception message like "Missing tag statusCode5". Please guide me how to get this message from my exception.
There's no easy way (that I'm aware of) to find out exactly what element(s) was/were missing in a LINQ to XML statement. What you can do however is use (string) on the element to handle missing elements - but that can get tricky if you have a chain of elements.
That wouldn't work in your current code:
status5 = (string)StackOverflowReply.Element(x + "processStatus").Element(x + "statusCode5")
Becuase (string) will only work on first element, and the second one is the one that is missing.
You could change your LINQ to focus only on the subnodes, like this:
XNamespace x = "http://xml.stack.com/RRAND01234";
var result = from StackOverflowReply in XDocument.Parse(Myxml).Descendants(x + "processStatus")
select new
{
status1 = (string)StackOverflowReply.Element(x + "statusCode1"),
status2 = (string)StackOverflowReply..Element(x + "statusCode2"),
status3 = (string)StackOverflowReply..Element(x + "statusCode3"),
status4 = (string)StackOverflowReply.Element(x + "statusCode4"),
status5 = (string)StackOverflowReply.Element(x + "statusCode5"),
};
However, if your XML is complex and you have different depths (nested elements), you'll need a more robust solution to avoid a bunch of conditional operator checks or multiple queries.
I have something that might help if that is the case - I'll have to dig it up.
EDIT For More Complex XML
I've had similar challenges with some XML I have to deal with at work. In lieu of an easy way to determine what node was the offending node, and not wanting to have hideously long ternary operators, I wrote an extension method that worked recursively from the specified starting node down to the one I was looking for.
Here's a somewhat simple and contrived example to demonstrate.
<SomeXML>
<Tag1>
<Tag1Child1>Value1</Tag1Child1>
<Tag1Child2>Value2</Tag1Child2>
<Tag1Child3>Value3</Tag1Child3>
<Tag1Child4>Value4</Tag1Child4>
</Tag1>
<Tag2>
<Tag2Child1>
<Tag2Child1Child1>SomeValue1</Tag2Child1Child1>
<Tag2Child1Child2>SomeValue2</Tag2Child1Child2>
<Tag2Child1Child3>SomeValue3</Tag2Child1Child3>
<Tag2Chidl1Child4>SomeValue4</Tag2Child1Child4>
<Tag2Child1>
<Tag2Child2>
<Tag2Child2Child1>
<Tag2Child2Child1Child1 />
<Tag2Child2Child1Child2 />
</Tag2Child2>
</Tag2>
</SomeXML>
In the above XML, I had no way of knowing (prior to parsing) if any of the children elements were empty, so I after some searching and fiddling I came up with the following extension method:
public static XElement GetChildFromPath(this XElement currentElement, List<string> elementNames, int position = 0)
{
if (currentElement == null || !currentElement.HasElements)
{
return currentElement;
}
if (position == elementNames.Count - 1)
{
return currentElement.Element(elementNames[position]);
}
else
{
XElement nextElement = currentElement.Element(elementNames[position]);
return GetChildFromPath(nextElement, elmenentNames, position + 1);
}
}
Basically, the method takes the XElement its called on, plus a List<string> of the elements in path order, with the one I want as the last one, and a position (index in the list), and then works it way down the path until it finds the element in question or runs out of elements in the path. It's not as elegant as I would like it to be, but I haven't had time to refactor it any.
I would use it like this (based on the sample XML above):
MyClass myObj = (from x in XDocument.Parse(myXML).Descendants("SomeXML")
select new MyClass() {
Tag1Child1 = (string)x.GetChildFromPath(new List<string>() {
"Tag1", "Tag1Child1" }),
Tag2Child1Child4 = (string)x.GetChildFromPath(new List<string>() {
"Tag2", "Tag2Child1", "Tag2Child1Child4" }),
Tag2Child2Child1Child2 = (string)x.GetChildFromPath(new List<string>() {
"Tag2", "Tag2Child2", "Tag2Child2Child1",
"Tag2Child2Child1Child2" })
}).SingleOrDefault();
Not as elegant as I'd like it to be, but at least it allows me to parse an XML document that may have missing nodes without blowing chunks. Another option was to do something like:
Tag2Child2Child1Child1 = x.Element("Tag2") == null ?
"" : x.Element("Tag2Child2") == null ?
"" : x.Element("Tag2Child2Child1") == null ?
"" : x.Element("Tag2Child2Child1Child2") == null ?
"" : x.Element("Tag2")
.Element("Tag2Child2")
.Element("Tag2Child2Child1")
.Element("Tag2Child2Child1Child2").Value
That would get really ugly for an object that had dozens of properties.
Anyway, if this is of use to you feel free to use/adapt/modify as you need.

Check if XML node value already exists in xml file using c#

Please note that I'm new to C# and I learn it right now :) I couldn't find something similar to my problem, so I came here.
I have an application in which I add customers (it's in the final stage). All customers are stored in an XML file. Every single customer gets a new customer number. In my xml file I got an XmlNode called CustNo. Now if the user add a new customer and type in a number which already exist, it should pop up a message box to say that this number already exists. I got this c# code:
XDocument xdoc = XDocument.Load(path + "\\save.xml");
var xmlNodeExist = String.Format("Buchhaltung/Customers/CustNo");
var CustNoExist = xdoc.XPathSelectElement(xmlNodeExist);
if (CustNoExist != null)
{
MessageBox.Show("asdf");
}
And my XML file looks like this:
<Buchhaltung>
<Customers>
<CustNo>12</CustNo>
<Surname>Random</Surname>
<Forename>Name</Forename>
<Addr>Address</Addr>
<Zip>12345</Zip>
<Place>New York</Place>
<Phone>1234567890</Phone>
<Mail>example#test.com</Mail>
</Customers>
<Customers>
<CustNo>13</CustNo>
<Surname>Other</Surname>
<Forename>Forename</Forename>
<Addr>My Address</Addr>
<Zip>67890</Zip>
<Place>Manhattan</Place>
<Phone>0987654321</Phone>
<Mail>test#example.com</Mail>
</Customers>
</Buchhaltung>
But then the message box always pops up. What am I doing wrong?
That's because your XPath return all CustNo elements, no matter of it's content.
Try following:
var myNumber = 12;
var xmlNodeExist = String.Format("Buchhaltung/Customers/CustNo[. = {0}]", myNumber.ToString());
or using First and LINQ to XML:
var myNumber = 12;
var xmlNodeExist = "Buchhaltung/Customers/CustNo";
var CustNoExist = xdoc.XPathSelectElements(xmlNodeExist).FirstOrDefault(x => (int)x == myNumber);
You are currently testing for existance of any 'CustNo' element. See this reference about the XPath syntax.
Your XPath should say something like this:
Buchhaltung//Customers[CustNo='12']
which would say "any customers element containing a 'CustNo' element with value = '12'"
Combining that with your current code:
var custNoGivenByCustomer = "12";
var xmlNodeExistsXpath = String.Format("Buchhaltung//Customers[CustNo='{0}']", custNoGivenByCustomer );
var CustNoExist = xdoc.XPathSelectElement(xmlNodeExistsXpath);
You can use LINQ to XML
var number = textBox1.Text;
var CustNoExist = xdoc.Descendants("CustNo").Any(x => (string)x == number);
if(CustNoExist)
{
MessageBox.Show("asdf");
}
This is because you select the CustNo elements regardless of their value. This will filter it to the desired customer number:
int custNo = 12;
var xmlNodeExist = String.Format("Buchhaltung/Customers[CustNo={0}]", custNo);
It selects the Customers elements instead, but since you're just checking for existence, that's unimportant.
W3Schools has a good tutorial/reference on XPath.

Can't remove an element in XML

I have XElement xDoc =
<div id="item123">
<div id="item456">
<h3 id="1483538342">
<span>Dessuten møtte</span>
</h3>
<p>Test!</p>
</div>
</div>
When I try to remove en item with id = "item456" I get an error
System.NullReferenceException: Object reference not set to an instance of an object.
var item = "456";
xDoc.Descendants("div").Where(s => s.Attribute("id").Value == "item" + item).Remove();
I can't understand what is wrong here.
You need to check if the current element (inside the where iteration) has an id attribute, otherwise you will access a null object and get an exception.
var item = "456";
xDoc.Descendants("div").Where(s => s.Attribute("id") != null && s.Attribute("id").Value == "item" + item).Remove();
Your error means that some of div elements do not have id attribute. Thus s.Attribute("id") returns null. Trying to get it's value throws exception. If you will cast attribute to string instead of trying to access it's Value, you will not get error (null will be returned if attribute was not found):
xDoc.Descendants("div")
.Where(d => (string)d.Attribute("id") == "item" + item)
.Remove();
Also thus you are dealing with HTML I suggest you to use appropriate tool - HtmlAgilityPack. Removing your div nodes will look like:
HtmlDocument doc = new HtmlDocument();
doc.Load(path_to_file);
foreach (var div in doc.DocumentNode.SelectNodes("//div[#id='item456']"))
div.Remove();

get xelement attribute value

I have an XElement that looks like this:
<User ID="11" Name="Juan Diaz" LoginName="DN1\jdiaz" xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/" />
How can I use XML to extract the value of the LoginName attribute? I tried the following, but the q2 "Enumeration yielded no results".
var q2 = from node in el.Descendants("User")
let loginName = node.Attribute(ns + "LoginName")
select new { LoginName = (loginName != null) };
foreach (var node in q2)
{
Console.WriteLine("LoginName={0}", node.LoginName);
}
var xml = #"<User ID=""11""
Name=""Juan Diaz""
LoginName=""DN1\jdiaz""
xmlns=""http://schemas.microsoft.com/sharepoint/soap/directory/"" />";
var user = XElement.Parse(xml);
var login = user.Attribute("LoginName").Value; // "DN1\jdiaz"
XmlDocument doc = new XmlDocument();
doc.Load("myFile.xml"); //load your xml file
XmlNode user = doc.getElementByTagName("User"); //find node by tag name
string login = user.Attributes["LoginName"] != null ? user.Attributes["LoginName"].Value : "unknown login";
The last line of code, where it's setting the string login, the format looks like this...
var variable = condition ? A : B;
It's basically saying that if condition is true, variable equals A, otherwise variable equals B.
from the docs for XAttribute.Value:
If you are getting the value and the attribute might not exist, it is more convenient to use the explicit conversion operators, and assign the attribute to a nullable type such as string or Nullable<T> of Int32. If the attribute does not exist, then the nullable type is set to null.
I ended up using string manipulation to get the value, so I'll post that code, but I would still like to see an XML approach if there is one.
string strEl = el.ToString();
string[] words = strEl.Split(' ');
foreach (string word in words)
{
if (word.StartsWith("LoginName"))
{
strEl = word;
int first = strEl.IndexOf("\"");
int last = strEl.LastIndexOf("\"");
string str2 = strEl.Substring(first + 1, last - first - 1);
//str2 = "dn1\jdiaz"
}
}

linq query and nulls

I would like to query with linq some xml file. There are some required and some optional elements. Only required is name - everything else is optional.
If there is some NULL for example cageCode = NULL - it doesnt select anything - I need to add to List of strings - "" - I tried it like below, but it doesnt work. When I have everything filled it works fine, when there is something NULL it doesnt save to list anything.
Could you help me how to set "" to list where is null element?
Thanks!
var queryManufacturer = from dataManufaturer in input.Identification.Manufacturers.Manufacturer
select
new
{
dataManufaturer.name,
dataManufaturer.cageCode,
dataManufaturer.FaxNumber,
dataManufaturer.URL.OriginalString
};
foreach (var a in queryManufacturer)
{
data.Add(a.name);
if (a.cageCode == null) data.Add("");
else data.Add(a.cageCode);
if (a.FaxNumber == null) data.Add("");
else data.Add(a.FaxNumber);
if (a.OriginalString == null) data.Add("");
else data.Add(a.OriginalString);
}
It throws me a null exception if is some of elements in xml file missing - I dont wanna get this exception - I would like just add empty string beside missing element
try this in your Linq to XML query:
select new
{
name = dataManufaturer.name ?? "",
cageCode = dataManufaturer.cageCode ?? "",
FaxNumber = dataManufaturer.FaxNumber ?? "",
OriginalString = dataManufaturer.URL!=null ? dataManufaturer.URL.OriginalString : ""
};

Categories

Resources