Remove child XML node from parent with where clause - c#

Really new to LINQ and XML. I was hoping someone could tell me what I'm doing wrong in trying to remove a child node from an XElement.
Here is a sample of my XML:
(I am trying to remove the "Relation" that corresponds to the user selected relation)
<Bill>
<Element>
<Name>AccountNumber</Name>
<Regex></Regex>
<Left></Left>
<Right></Right>
<Top></Top>
<Bottom></Bottom>
<Index>0</Index>
<Relations></Relations>
</Element>
<Element>
<Name>BillDate</Name>
<Regex></Regex>
<Left></Left>
<Right></Right>
<Top></Top>
<Bottom></Bottom>
<Index>1</Index>
<Relations>
<Relation>AccountNumber.RightOf.Right.0</Relation>
<Relation>AccountNumber.Below.Top.-10</Relation>
<Relation>AccountNumber.Above.Bottom.-10</Relation>
</Relations>
</Element>
if my WPF GUI, When a user clicks delete on a relation, I want to remove only that relation from the parent.
This is one of the many things I have tried:
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
List<RelationsDetailView> details = (List<RelationsDetailView>)DetailsView.ItemsSource;
XElement parentElement = (from Node in ocrVar.Xml.Descendants("Element")
where Node.Element("Index").Value == ocrVar.SelectedItem.Index.ToString()
select Node).FirstOrDefault();
XElement child = parentElement.Element("Relations").Elements("Relation").Where(xel => xel.Element("Relation").Value == (details[DetailsView.SelectedIndex].Relation)).FirstOrDefault();
child.Remove();
ocrVar.Xml.Save(ocrVar.xmlPath);
}

Your Where predicate is incorrect. xel is already the <relation> element, so you don't have to callElement("Relation") again.
You should also replace XElement.Value with (string)XElement to prevent NullReferenceException.
.Where(xel => (string)xel == (details[DetailsView.SelectedIndex].Relation))
Or you can use FirstOrDefault with predicate instead of Where().FirstOrDefault() chain:
XElement child = parentElement.Element("Relations").Elements("Relation").FirstOrDefault(xel => (string)xel == details[DetailsView.SelectedIndex].Relation);

xel.Element("Relation").Value == (details[DetailsView.SelectedIndex].Relation
This condition always return false, maybe you want something like this?
(string)xel.Element("Relation") == (details[DetailsView.SelectedIndex].Relation.ToString())

Related

Cant delete XmlElement using Linq query

I have a very basic linq query to be able to delete one node from xml file.But when ı run this code I am getting this exception below.
Sequence contains no elements
then I have used FirstOrDefault() instead First() ( as mentioned earlier posts) and this time Exection message turned into this
Object reference not set to an instance of an object.
This my code
protected void Page_Load(object sender, EventArgs e)
{
XDocument doc = XDocument.Load(Server.MapPath("Kitaplar.xml"));
var toDelete = (from data in doc.Elements("Kitap") where data.Attribute("id").Value == "1" select data).FirstOrDefault();
toDelete.Remove();
doc.Save(Server.MapPath("Kitaplar.xml"));
}
And this is the xmlfile
<?xml version="1.0" encoding="utf-8"?>
<Kitaplar>
<Kitap id="1">
<Kitapadi>asasa</Kitapadi>
<Yazar>sasas</Yazar>
<Sayfa>22</Sayfa>
</Kitap>
<Kitap id="2">
<Kitapadi>jhjh</Kitapadi>
<Yazar>kjkj</Yazar>
<Sayfa>33</Sayfa>
</Kitap>
<Kitap id="3">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
<Kitap id="4">
<Kitapadi>lkjhg</Kitapadi>
<Yazar>gffd</Yazar>
<Sayfa>988</Sayfa>
</Kitap>
</Kitaplar>
Everyting looks ok to me.what am i doing wrong ?
I took the liberty of writing a method that takes the file, element and ID; then removes the Element accordingly.
private bool DeleteRowWithID(string fileName, string element, string id)
{
XDocument doc = XDocument.Load(fileName);
if (doc.Root == null)
return false;
XElement toRemove = doc.Root.Elements(element).Where(e => e.Attribute("id").Value == id).FirstOrDefault();
if (toRemove == null)
return false;
toRemove.Remove();
doc.Save(fileName);
return true;
}
Above method loads the XmlDocument in an XDocument (which allows LINQ to XML). It checks if the root isn't empty, then finds the element you specified.
It checks if the element exists; then removes that element from the document, and save the made removal.
Last, it returns true to indicate that the element has actually been removed.
If you just want the element and stick to your method, use the following:
XElement toRemove = doc.Root.Elements("Kitap").Where(e => e.Attribute("id").Value == "1").FirstOrDefault();

XML element removal, just need to delete one element not the parent

Basically what I am trying to do is remove a VSLOC from the list. I don't want to remove everything that belongs to it.
<?xml version="1.0"?>
<GarageNumbers>
<G554>
<id>G554</id>
<VSLOC>V002</VSLOC>
<VSLOC>V003</VSLOC>
<VSLOC>V002</VSLOC>
</G554>
<G566>
<id>G566</id>
<VSLOC>V002</VSLOC>
<VSLOC>V003</VSLOC>
<VSLOC>V002</VSLOC>
</G566>
<G572>
<id>G572</id>
<VSLOC>V001</VSLOC>
<VSLOC>V002</VSLOC>
</G572>
</GarageNumbers>
So, what I have setup is a combobox that I select a G# from which brings up all the VSLOC associated with it in a Listbox. What I need to do is to select a item from the list box and remove the line from the listbox and from the xml document using a button. I have all this setup but when I hit the button it deletes G554 and all the elements with in.
So if I want to select V002 from the list in G554 I want it to just remove that VSLOC with that innertext.
XmlDocument xDoc = new XmlDocument();
xDoc.Load(Application.StartupPath + "/xmlData.xml");
foreach (XmlNode xNode in xDoc.SelectNodes("GarageNumbers/G554"))
if (xNode.SelectSingleNode("VSLOC").InnerText == "V002")
xNode.ParentNode.RemoveChild(xNode);
xDoc.Save(Application.StartupPath + "/xmlData.xml");
You should be able to drill down to the desired elements then remove them. For example, assuming your XML is in an XElement, this approach would work:
string targetCategory = "G554";
string vsloc = "V002";
xml.Element(targetCategory)
.Elements("VSLOC")
.Where(e => e.Value == vsloc)
.Remove();
If you're using an XDocument then add the Root property: xml.Root
var xDoc = XDocument.Load(fname);
var node = xDoc.Descendants("VSLOC")
.Where(e => (string)e.Parent.Element("id") == "G554")
.FirstOrDefault();
if (node != null) node.Remove();
xDoc.Save(fname);

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

Search all child nodes of XML node for a value and remove the grandparent node

Trying to use
exportDoc.Root.Elements("string").Where(node => !(node.Element("product").HasElements) || node.Element("product").Element("type").Value != product).Remove();
to remove the nodes in my XML document where the product string I'm searching for doesn't occur. Here is a sample of my XML structure:
<root>
<string id = "Hithere">
<product>
<type>orange</type>
<type>yellow</type>
<type>green</type>
<product>
<element2/>
<element3/>
</string>
<string id ="...">
...
...
</root>
So I need to look under the product element of each string element AND at each of the type elements therein to see if the value of string product (input to the method where this is contained) occurs. At present, it looks like my code only removes the node if the product string I'm searching for matches the value of just the first type element.
The whole point is to remove all string nodes from this xdoc that don't have the product I'm looking for listed under their product element.
You need to change your search condition slightly:
var nodesToRemove = xDoc.Root
.Elements("string")
.Where(node =>
!(node.Element("product").HasElements) ||
node.Element("product").Elements("type").All(x => x.Value != product))
.ToList();
This should match elements which all string:product:types differ from product value (or in other words - if at least one <type> will match your product, it won't be marked for removal).
You can't Remove() while you're still enumerating (deferred execution).
You need something more like:
// untested
var toRemove = exportDoc.Root.Elements("string")
.Where(node => !(node.Element("product").HasElements) ||
node.Element("product").Element("type").Value != product).ToList();
toRemove.Remove();

Why isn't this XElement query working on my xml

My xml looks like:
<nodes>
<node name="somekey">
<item name="subject">blah</item>
<item name="body">body</item>
</node>
</nodes>
And my code so far is:
XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath(String.Format("~/files/{0}/text.xml", "en")));
if (doc != null)
{
XElement element = doc.Elements().Where(e => e.Elements().Any() && e.Attribute("name").Value == "someKey").First();
}
I am getting an error saying:
Sequence contains no elements
Is my query wrong?
I stepped through the code, and it errors out on the line with XElement..
You want something like this:
var element = doc.Descendants("node").Where(x => x.Attribute("name") != null && x.Attribute("name").Value == "somekey").FirstOrDefault();
Edit: Edited to grab first element from result;
You could also use:
var element = doc.Elements()
.Elements()
.Where(e => (e.Elements().Any()
&& e.Attribute("name").Value == "somekey"))
.First();
Explanation:
The doc.Elements() grabs the root element, which is nodes. Then the .Elements() selects the child elements of that, which is just one, node. The .Where() is then performed on that nodeset, which is what you want. The lambda selects those elements that have child elements, and also have an attribute "name" with value "somekey".
Your original code was not getting the Child-of-Child-elements. Hence the original result set was empty.
You could also do this with .Descendants() but that feels a little sloppy and loose, to me.

Categories

Resources