How do I search a XElement with an specific attribute value? - c#

I need to search a child node by a value of one of its attribute, for example say that I have this XML
<Root>
<Child Name="1" foo="a"/>
<Child Name="2" foo="a"/>
<Child Name="3" foo="b"/>
<Child Name="4" foo="c"/>
<OhterTag/>
</Root>
I want to extract the node Child with the attribute Name with value 2 . . . I want the full node not just the tag of the node.
I have tried something like this
root.Elements("Attr").Where(child => child.Attribute("Name").Value == "2");
but I have not work

Use the element name, which is "Child" and it should work the way you have it... And since .Where in this case returns an IEnumerable<XElement>, to get just that Element, use .First() at the end.
root.Elements("Child")
.Where(child => child.Attribute("Name").Value == "2")
.First()
... or you can just use the predicate with .First()
root.Elements("Child")
.First(child => child.Attribute("Name").Value == "2")
Finally you can use .FirstOrDefault() in case the node doesn't exist, to avoid null reference exceptions, as per discussion in below comments, suggested by #HamletHakobyan
root.Elements("Child")
.FirstOrDefault(child => child.Attribute("Name").Value == "2")

You can, if you want to, achieve the same using XPath -
using System.Xml.XPath;
// ...
XElement result = root.XPathSelectElement("//Child[#Name='2']");

Related

Remove child XML node from parent with where clause

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

Linq Xml - Find attributes by value

If I have a simple XML document such as
<case id="37d3c93c-3201-4002-b24f-08e1221c3cb7">
<party id="26dad8c5-9487-48f2-8911-1d78c00095b2">...</party>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<item relatedCaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">...</illustration>
</case>
I have code that changes the id attribute of the case element. I am now looking for some LINQ code that would help me search all elements that have an attribute value that matches the old value so I can replace it with the new value. I do not have a list of attribute names, and would need to search the entire document.
Any tips/ideas?
Thanks!
Something like this:
var matches = doc.Descendants()
.Where(x => x.Attributes()
.Any(attr => attr.Value == oldValue));
Or if you're just trying to replace the values, you only need the attributes themselves:
var attributes = doc.Descendants()
.Attributes()
.Where(attr => attr.Value == oldValue)
.ToList();
foreach (var attribute in attributes)
{
attribute.Value = newValue;
}
It's entirely possible that the copy to a list isn't necessary in this case, but I generally prefer to make a copy when mutating an XDocument to avoid confusing things. (It's certainly necessary when you're removing an element or something like that - just setting the value of an attribute probably doesn't affect things.)

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

How to get all non empty nodes from XElement?

I'm trying to get all nodes from an XElement that actually has a value,
currently I'm using this code:
var nodes = from node in elem.Nodes()
where node.NodeType == XmlNodeType.Element &&
((XElement) node).Value.Length > 0
select node;
Is there a build in operator to do this operation?
Thanks
I don't believe there's anything like this built in. Are you sure you want to include elements that have subelements though? For example:
XElement e = new XElement("Foo", new XElement("Bar"));
Console.WriteLine(e);
Console.WriteLine(e.Value.Length);
This will print:
<Foo>
<Bar />
</Foo>
0
... so Foo would be included as an "empty" node even though it contains another element. Is that definitely what you're after?

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