c# xmlnode select single node to remove - c#

I have an XML document that has nodes/values like this:
<data name="btnAutoTrans" xml:space="preserve">
<value>Auto Trans</value>
</data>
<data name="btnDieEngine" xml:space="preserve">
<value>Diesel Engine</value>
</data>
I need to select a single node for a specific name="btnDieEngine" (for example)
but all the code I have tried from searching google always returns NULL value.
The original code I had was
XmlNode node = xmldoc.SelectSingleNode("data[name='btnDieEngine']");
but this returns null.
Any help would be appreciated

Your XPath is invalid. Try that one:
XmlNode node = xmldoc.SelectSingleNode("data[#name='btnDieEngine']");

Related

How to Replace Parent Property of XElement value with New XElement value?

I'm using XElement class and Selecting particular XML Element. I want to Replace the Parent property of XElement xml content with another new XElement xml content.Is there any way to replace the parent xml element with another new.
Example
1.Loding XML 1 document
2.Getting catalog xml elements
XElement old = document.XPathSelectElement(#"cd/catalog");
To replace - getting value from loading XML 2
XElement toreplace = documentReplace.XPathSelectElement(#"cd/first/catalog");
Replacing
old.Parent.ReplaceWith(toreplace)
Here, I'm trying to Replace the old parent property value with XElement toreplace value but parent property is not replacing
XML 1
<cd>
<catalog>
<a>parent</a>
<b>catalog</b>
</catalog>
</cd>
XML 2
<cd>
<first>
<catalog>
<a>first</a>
<b>catalog</b>
</catalog>
</first>
</cd>
O/P wanted
XML 1
<cd>
<catalog>
<a>first</a>
<b>catalog</b>
</catalog>
</cd>

combine two xml_docs C# xmlDocument

I have been using the following SO, How to merge two XmlDocuments in C#, to try and merge two sample xml files into one. Essentially here are my two xml files
test1.xml:
<root>
<Name>
<id>4</id>
<First_name>Tony</First_name>
<last_name>""</last_name>
</Name>
<Name>
<id>6</id>
<First_name>Donny</First_name>
<last_name>Test</last_name>
<middle_name>Ward</middle_name>
</Name>
</root>
And test2.xml
<root>
<Name>
<id>1</id>
<First_name>Nick</First_name>
<last_name>Tester</last_name>
</Name>
<Name>
<id>2</id>
<First_name>Lauren</First_name>
<last_name>MsTester</last_name>
</Name>
</root>
I am using this code snippet it to merge the into the one file but the procedure is not actually combining them. I am a little knew to xmlDocument class and still trying to navigate this issue correctly.
Code:
XmlDocument xmlreader1 = new XmlDocument();
xmlreader1.Load("C:\\Temp\\test1.xml");
XmlDocument xmlreader2 = new XmlDocument();
xmlreader2.Load("C:\\Temp\\test2.xml");
foreach (XmlNode node in xmlreader2.DocumentElement.ChildNodes)
{
XmlNode importedDocument = xmlreader1.ImportNode(node, true);
xmlreader1.DocumentElement.AppendChild(importedDocument);
}
I believe this is correct to take each child node of root (in this case <name>) from test2.xml and append it to test1.xml. I have also tried as:
XmlNode importedDocument = xmlreader1.ImportNode(xmlreader2.DocumentElement, true);
xmlreader1.DocumentElement.AppendChild(importedDocument);
to no avail again any suggestions would be greatly appreciated.
It's not entirely clear what merge operations you need to perform - but if you just need to copy the root node's child elements, that's really simple with LINQ to XML:
XDocument doc1 = XDocument.Load("test1.xml");
XDocument doc2 = XDocument.Load("test2.xml");
doc1.Root.Add(doc2.Root.Elements());
doc1.Save("test3.xml");
This will not perform any actual merging though - if you have elements with the same ID in both files, you'll end up with both elements in the result, rather than one merged element.
Personally I always prefer using LINQ to XML over the old XmlDocument API - I would strongly recommend using it when you have the opportunity to do so.

Need to add xml element to last parent using linq c#

I'm really new to Linq and C# and I'm stuck on what is probably an obvious problem.
I have an existing XML file
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<books>
<book>
<title>This is Title 1</title>
<author>John Doe</author>
<categories>
<category>How to</category>
<category>Technical</category>
</book>
<book>
<title>This is Title 2</title>
<author>Jane Brown</author>
<categories>
<category>Fantasy</category>
</categories>
</book>
</books>
I want to add a 2nd category to the second book in this file.
I've gotten this far:
var thiscat = doc.Root
.Element("book")
.Element("categories");
thiscat.Add(new XElement("category", "novel"));
But this adds a 3rd category to the first book. I need to learn how to point 'thiscat' at the last categories element rather than the first one. I've been sniffing around LastNode but haven't managed to get the syntax right.
This is my first question here. Please let me know if I'm not being clear or if I'm doing anything wrong.
Pete,
Here is an example that will search for the book by title This is Title 2 and add another category.
var elem = doc.Root.Elements("book").FirstOrDefault(x => x.Element("title").Value.Equals("This is Title 2"));
if (elem != null)
{
var category = elem.Element("categories");
category.Add(new XElement("category", "novel"));
}
Edit: More explanatoin.
First of we search the documents book elements for the matching title of This is Title 2 (effectively your second entry). By executing the FirstOrDefault extension method we either the get the first matching element (as XElement) or null.
Because we 'could' get a null value we must check if the value is null if not we move into the next step of locating the categories element. This can be done simply calling the elem.Element() method as we only expect one element.
Finally we add a new XElement to the category element.
Hope this helps.
Cheers.
To answer your question quite literally, you could modify the statement as follows:
var thiscat = doc.Root
.Elements("book")
.Skip(1)
.First()
.Element("categories");
The "Element" function returns the first element of that type found. In this case, we used "Elements" instead to return an IEnumerable containing all of the elements named "book", and then we used the LINQ "skip" function to skip the first (returning another IEnumerable of all the remaining elements), and then we took just the first element in the IEnumerable (back to a single XElement).
Another way you could have gotten to the answer is as follows:
var thiscat = doc.Root
.Element("book")
.ElementsAfterSelf()
.First()
.Element("categories");
ElementsAfterSelf returns an IEnumerable of all the sibling elements after the calling object.
LINQ is a really critical part of programming in C# and it's good to see you're trying to learn it from the beginning. Although your methodology here in adding a specific element to a specific place programmatically is questionable (obviously it is a contrived example), in playing around like this you will probably learn a bit about LINQ and that is always good.
First you should get your second book element.According to your code:
var thiscat = doc.Root
.Element("book")
.Element("categories");
This statement returns just one categories element which belongs to your first book.Because you are using Element instead of Elements. Let's go step by step.
A proper way to get second element is using Descendants like this:
var secondBook = doc.Descendants("book")[1];
Descendants returning a collection of your books.And we are getting second element with indexer.Now we need to select your categories element under the book element.
var categories = secondBook.Element("categories");
Now we have our categories element and we can add our new category and save Xml Document:
categories.Add(new XElement("category", "novel"));
doc.Save(path);
And that's all.If you understand that logic you can modify your html file however you like.Besides you can make all of these in one line:
doc.Descendants("book")[1]
.Element("categories")
.Add(new XElement("category", "novel"));
This should work( slightly lengthy solution as it helps understand the fundamentals better):
XmlElement rootNode = xd.DocumentElement; //gives <books> the root node
XmlNodeList cnodes= rootNode.ChildNodes; //gets the childnodes of <books>
XmlNode secondBook= cnodes.Item(1); //second child of <books> i.e., the <book> you want
XmlNodeList bnodes= secondBook.ChildNodes; //gets the childnodes of that <book>
XmlNode categories= bnodes.Item(2); //gets the third child i.e.,<categories>
//making the new <category> node
string xmlContent = "<category>novel</category>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
XmlNode newNode = doc.DocumentElement;
//making the new node completes
categories.AppendChild(newNode); //append the new node to <categories> as a child

XPath last() does not work properly

I have following xml
<root>
<data>
<person>tewst</person>
<data>
<phone>djk</phone>
<email>dsd</email>
</data>
</data>
</root>
Using c# SelectSingleNode I am trying to get to second "data" node and insert the new element call "phone2" after email tag.
Issue is I can not get to the correct "data" node. This is the code I am using to get there and it does not work. Any help would be appreciated. Thank you
XMLDocument doc = new XMLDocument("xml file here");
var node = doc.SelectSingleNode("//data[last()]");
XMLElement phone1 = doc.CreateElement("phone2");
phone1.InnerText = "12";
node.AppendChild(phone1);
Problem is that node is null.
Solution:
XMLDocument doc = new XMLDocument("xml file here");
var node = doc.SelectSingleNode("(//data)[last()]");
//data[last()] selects data nodes that are their parent's last data child, so it selects 2 nodes in your example:
1) last data child of the root node
<data>
<person>tewst</person>
<data>
<phone>djk</phone>
<email>dsd</email>
</data>
</data>
2) and last data child of the first data node
<data>
<phone>djk</phone>
<email>dsd</email>
</data>
To select the last data in the whole document, use:
(//data)[last()]
//data will generate the node-set for all descendant data elements, nested or not,
[last()] will select the last node of the node-set
with the brackets () around //data, it ensures it's interpreted as //data then [last()], instead of // then data[last()]
There are a couple different ways you can do this.
(1) If you know that the structure of the XML will always be like the above - "root" element, exactly one "data" child, and exactly one "data" child beneath the "data" parent, you can access the second "data" element directly using the XPath expression:
XmlNode node = doc.SelectSingleNode("/root/data/data");
(2) If there are multiple "data" elements beneath the "root" element, then you can do the following:
XmlNodeList nodeList = doc.SelectNodes("//data/data");
This XPath expresion will return the first "data" element beneath every top level "data" element in the document. So for instance, if you had an XML document like this:
<root>
<data>
<person>tewst</person>
<data>
<phone>djk</phone>
<email>dsd</email>
</data>
</data>
<data>
<person>two</person>
<data>
<phone>bbb</phone>
<email>ddd</email>
</data>
</data>
</root>
... the output of this code snipped:
XmlNodeList nodeList = doc.SelectNodes("//data/data");
foreach (XmlNode parentData in nodeList)
Console.WriteLine(parentData.InnerXml.ToString());
... will produce:
<phone>djk</phone><email>dsd</email>
<phone>bbb</phone><email>ddd</email>
Hope this helps.

XPath String that grabs an element with a specific id value

I am trying to create an XPath query/string that grabs a specific element from a XML document. I am attempting to grab the element with the id=38 but my code always returns nothing for some reason.
If you look at my code & the organisation of my XML file can you tell me what XPath I need to grab the element with the id=38?
My code is:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(getProductURL());
XmlNode node = xdoc.DocumentElement.SelectSingleNode("id('38')");
// node always is null for some reason?
The way the xml is organised is like so:
<courseg>
<group isempty="False" isbranch="true" id="1" name="abc">
<group isempty="False" isbranch="true" id="38" name="def"></group>
</group>
</courseg>
The XPath you need is
//*[#id='38']
Here is the example with XDocument:
XDocument xdoc = XDocument.Parse(#"
<courseg>
<group isempty=""False"" isbranch=""true"" id=""1"" name=""abc"">
<group isempty=""False"" isbranch=""true"" id=""38"" name=""def""></group>
</group>
</courseg>");
XElement node = xdoc.Root.XPathSelectElement("//*[#id='38']");
Console.WriteLine(node);
The function id('P38') would select an element with an ID value of P38. But this doesn't just mean "an attribute named 'id'". It means an attribute declared in the DTD or schema as being of type ID. You haven't shown a DTD or schema, and I suspect you don't have one. If you did, and if it declared the id attribute as being of type ID, then your document would be invalid, because an ID value cannot be all-numeric (for legacy SGML reasons, it has to take the form of a name).
In practice the id() function is probably best avoided unless you have severe performance requirements. It's too fragile - it only works when you are validating the source document against a schema or DTD. In XSLT, use key() instead. Alternatively, many processors now recognize the attribute name xml:id as a 'self declaring' ID value without reference to a schema or DTD: use that if your processor supports it.
Use this XPath query:
//*[#id = 38]
It selects every node with id attribute equals to 38. If you have to be more specific, i.e. select group with id attribute equals to 38, use this one:
//group[#id = 38]
When you mention
xdoc.DocumentElement.SelectSingleNode("id('38')"
you are asking xmldocument to search for a child node inside root node whose name is 'id'. But ideally 'id' is an attribute and not a xmlnode.
So you have to use //group[#id = '38'] to get all child node having name 'group' and attribute 'id' with a value of 38

Categories