Linq to XML select descendent of descendant each with specific attribute - c#

I have found many articles on getting a descendant with a specific attribute, but I can't seem to find anything on selecting multiple descendants with different attributes with LINQ to XML. So from the following example, I need to select all the Grandchildren where Parent name = Ken AND Child name = Lorna. Potentially, I need to have up to 4 AND clauses as my real XML is deeper than the example below.
I can code selecting all children of Ken, but can't find an example to go deeper than that.
Any help is greatly appreciated
<?xml version="1.0" encoding="UTF-8"?>
<FamilyTree>
<Parent name="Ken">
<Child name="Lorna">
<Grandchild name="Andrew"/>
<Grandchild name="Brian"/>
</Child>
<Child name="Mike">
<Grandchild name="Ann"/>
<Grandchild name="Beth"/>
</Child>
</Parent>
<Parent name="Norma">
<Child name="Owen">
<Grandchild name="Charles"/>
</Child>
<Child name="Peter">
<Grandchild name="Charlotte"/>
</Child>
</Parent>
<Parent name="Quinn">
<Child name="Robert">
<Grandchild name="Debbie"/>
<Grandchild name="Eric"/>
</Child>
<Child name="Susan">
<Grandchild name="Frank"/>
</Child>
</Parent>
</FamilyTree>

There are multiple options here, but I'd suggest the simplest thing is just to check each Grandchild:
var grandchildren = doc
.Descendants("Grandchild")
.Where(x => (string) x.Parent.Parent.Attribute("name") == "Ken" &&
(string) x.Parent.Attribute("name") == "Lorna");
Or you could find all the relevant Child elements and then retrieves their children:
var grandchildren = doc
.Descendants("Child")
.Where(x => (string) x.Parent.Attribute("name") == "Ken" &&
(string) x.Attribute("name") == "Lorna")
.Elements("Grandchild");

Related

C# get XElement attributes from XDocument with treeview path

I'm trying to write a winforms application that displays any opened XML files nodes (and only it's nodes) in TreeView and displays the attributes of the selected node separately (by selected i mean selected in the TreeView) (displayed in a listbox for example). I tried to achieve this by using the following code but it throws an exception saying:'family\parent' has an invalid token.
private void TView__AfterSelect(object sender, TreeViewEventArgs e)
{
var doc = XDocument.Load(businessLayer.InputFilepath);
XElement myElement = doc.Root.XPathSelectElement(TView_.SelectedNode.FullPath);
try
{
foreach (var attribute in myElement.Attributes())
{
listBox1.Items.Add(attribute.Value);
}
}
catch (Exception)
{
}
}
I'm using the following XML file:
<?xml version="1.0" encoding="utf-8" ?>
<family>
<parent>
<id>grandfather</id>
<parent>
<id>father</id>
<parent>
<id>brother</id>
<child>
<id>niece</id>
</child>
</parent>
<parent>
<is>me</is>
<child>
<id>son</id>
</child>
<child>
<id>dauhter</id>
</child>
</parent>
<child>
<id>sister</id>
</child>
</parent>
<parent>
<id>uncle</id>
<parent>
<id>cousin sister</id>
<child>
<id>second cousin</id>
</child>
</parent>
<child>
<id>cousin brother</id>
</child>
</parent>
</parent>
</family>
I have no clue about what else i could try so any help is appreciated.
To avoid that exception you have to replace every double backslash to a slash in the path of you XML file.
string path = treeView1.SelectedNode.FullPath.Replace('\\', '/');
XElement myElement = doc.XPathSelectElement(path);

LINQ to XML: How to get all elements by value

I'm trying to get all elements with a given value, "John", from an xml document.
Is this possible with LINQ to XML?
What I want to achieve is to replace all "John" values with "Wayne". I know this can easily be done with xslt, but I need to do this by code.
My XML:
<Root>
<Parents>
<Parent>
<Name>John</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>John</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>
I have tried this:
XmlDocument doc = new XmlDocument();
doc.Load(#"C:/Temp/test.xml");
var elements = doc.Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
You may use System.Xml.Linq.XDocument. It's more easy to work with.
XDocument doc = XDocument.Load(your file path);
var elements = doc.Descendants("Name").Where(i => i.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
doc.Save(your save file path);
Here is the output:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Parents>
<Parent>
<Name>Wayne</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>Wayne</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>
Here is an approach that will get all elements with the value John, regardless of what element (although only at the same level; you'd have to modify it to look at different levels too; you could use the Descendants approach described previously):
XDocument doc = XDocument.Load(#"C:\temp\test.xml");
var ns = doc.Root.GetDefaultNamespace();
var elements = doc.Element(ns + "Root").Element(ns + "Parents").Elements(ns + "Parent").Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
var stream = new FileStream(#"C:\temp\test.xml", FileMode.Create);
doc.Save(stream);

group node based on xpath predicate

I have strings of xpath like
/root/parent/child
/root/parent/child[1]
/root/parent/child[2]
In the c# code i am checking if the xpath exist in XmlDocument and cloning it like this
//Get the parent node of the node to be cloned
XmlNode NodeTobeCloned = xmlDoc.SelectSingleNode(oRow[0].ToString());
XmlNode DuplicateNode = NodeTobeCloned.CloneNode(true);
DuplicateNode.InnerText = sValue;
//Insert the node after the last child of a commoon parent
NodeTobeCloned.ParentNode.AppendChild(DuplicateNode);
I am getting a result like
<root>
<parent>
<child/>
<child/>
<child/>
</parent>
</root>
I want a result like
<root>
<parent>
<child/>
</parent>
<parent>
<child/> -- [1] -predicates elements
</parent>
<parent>
<child/> -- [2] -predicates elements
</parent>
</root>
how can i append to xmldocument using c#
thank you

XPath for selecting a node under a specific node with zero or more nodes in between

My Xml looks something like this
<root>
<parent name="Iam">
<child1 name="123">
<toy name="wii">
</toy>
</child>
</parent>
<parent name="Iam">
<toy name="wii">
</toy>
</parent>
<parent name="Sam">
<child1 name="999">
<toy name="xbox">
</toy>
</child>
</parent>
</root>
I need to select all <toy> nodes with name="wii" under <parent> with name="Iam". Notice that <toy> can be direct child of <parent>or a grandchild of <parent>(under <child>)
<child>node can have 0 or more cardinality.
I tried using this xpath /parent[#name='Iam']/*/toy[#name='wii'] with XPathNavigator.
`XPathNodeIterator nodeIter = schemaNavigator.Select(schemaXPath, namespaceMgr);`
It is obviously wrong since it fails.
I need xpath for selecting all nodes between <parent> and <toy> with 0 or more cardinality.
I cannot change the format of XML.
This seemed to worked for me:
string schemaXPath = "//parent[#name='Iam']//toy[#name='wii']";
XPathNavigator schemaNavigator = oXmlDocument.CreateNavigator();
XPathNodeIterator nodeIter = schemaNavigator.Select(schemaXPath, namespaceMgr);
while (nodeIter.MoveNext() == true)
{
Console.WriteLine(nodeIter.Current.Name);
}
Hopefully this is what you're looking for.
Cheers!

Merging XML Elements with LINQ

I have two XML documents.
My objective is to replace one of the nodes in the first document with the entire contents of the second Xml documents.
So the first document - Parent looks something like this:
<Root>
<AgencyName = "Some Agency"/>
<Originator = "Some other Agency"/>
<Type = "AnonymousType"/>
<Details/>
</Root>
The second document - children looks like this:
<Root>
<Details>
<Detail1>
...
</Detail1>
<Detail2>
...
</Detail2>
<Detail3>
...
</Detail3>
</Details>
</Root>
The node <Details/> has to be replaced with the contents of the second document.
I am attempting to use Linq to XML to do this. The first document is represented in an XDocument class and the second one is represented in an XElement class. There are several child attributes for <Detail/> and I haven't listed them here.
I am attempting to replace the element from the first document with this XElement class.
If I try something like this,
ParentDoc.Element("Details").ReplaceAll(children);
it is unlikely to work. How should I do the replace?
var doc = XDocument.Load(#"C:\Tools\test.xml");
var doc2 = XDocument.Load(#"C:\Tools\test2.xml");
var children = doc2.Root.Element("Details");
var parentNode = doc.Root.Element("Details");
parentNode.ReplaceWith(children);
By the way, your xml are not correct, so you'll get exceptions.
I tried with
<Root>
<AgencyName name= "Some Agency"/>
<Originator name= "Some other Agency"/>
<Type name= "AnonymousType"/>
<Details/>
</Root>
and
<Root>
<Details>
<Detail1>
asdf
</Detail1>
<Detail2>
asde
</Detail2>
<Detail3>
eere
</Detail3>
</Details>
</Root>
and got
<?xml version="1.0" encoding="utf-8"?>
<Root>
<AgencyName name="Some Agency" />
<Originator name="Some other Agency" />
<Type name="AnonymousType" />
<Details>
<Detail1>
asdf
</Detail1>
<Detail2>
asde
</Detail2>
<Detail3>
eere
</Detail3>
</Details>
</Root>

Categories