group node based on xpath predicate - c#

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

Related

XPath 1.0 select siblings with namespaces

I have the following xml file
<root xmlns="http://mynamespace">
<parent>
<first>text</first>
<second>more</second>
</parent>
<parent>
<first>2</first>
<second>3</second>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>
I'm trying to get first and second children of parent.
C# seems to have problems with the following code (the error is on the last line):
var rawXml = #"<root xmlns=""http://mynamespace"">
<parent>
<first>text</first>
<second>more</second>
<third>hello</third>
</parent>
<parent>
<first>2</first>
<second>3</second>
<parent>
<first>a</first>
<second>b</second>
</parent>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(rawXml);
var ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("m", "http://mynamespace");
var nav = xmlDoc.CreateNavigator();
var parents = nav.Select("//m:parent", ns);
Console.Write($"Got {parents.Count} parents.");
// this does not work
// error: Expression must evaluate to a node-set.
//var siblings = nav.Select("//m:parent/(m:first|m:second)", ns);
// but this does
var siblings = nav.Select("//m:parent/m:first|//m:parent/m:second", ns);
Console.Write($"Got {siblings.Count} children.");
Am I missing something? Is the first XPath expression wrong?
Is the first XPath expression wrong?
Yes, it's not valid XPath 1.0 syntax. You can't have a ( after a / in XPath 1.0.
You can achieve what you're trying to do, without repeating any node names, by using this path:
/m:root/m:parent/*[self::m:first or self::m:second]
Side note: avoid using // unless you have a specific reason to use it. It's bad for performance.

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

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