how to add a child node inside a nother child node - c#

How do I add another concept node (Concept Key="1234"R) to the child Rssd node where rssd = 3284070
in C#, I'm using XElements to contruct all the XML. Do I need a linq statement?
<Root>
<Rssd Key="3900455" />
<Rssd Key="4442961" />
<Rssd Key="4442961" />
<Rssd Key="4442970" />
<Rssd Key="3284070">
<Concept Key="1662">
<Concept Key="1668">
</Rssd>
</Root>

LINQ is only used to query (to select a part of the dataset), not to modify the dataset. Here, I use it to get the Rssd element where we want to add the new Concept element.
XDocument xDocument = ...
XElement parentElement = (from rssdElement in xDocument.Descendants("Rssd") // Iterates through the collection of all Rssd elements in the document
where rssdElement.Attribute("Key").Value == "3284070" // Filters the elements to get only those which have the correct Key attribute value
select rssdElement).FirstOrDefault(); // Gets the first element that satisfy the above condition (returns null if no element has been found)
if (parentElement == null)
throw new InvalidDataException("No Rssd element found with the key \"3284070\"");
XElement newConceptElement = new XElement("Concept"); // Creates a new Concept element
newConceptElement.Add(new Attribute("Key", "1234")); // Adds an Key attribute to the element with the specified value
parentElement.Add(newConceptElement); // Adds the new Concept element to the Rssd element

XDocument xDOC = XDocument.Load(FilePath);
foreach (XElement xele in xDOC.Descendants("Rssd"))
{
if (xele.Attribute("Key").Value == "3284070")
{
XElement xele1 = XElement.Parse("<Concept Key='1234' />");
xele.Add(xele1);
//Apply a break if you wish to add only one child node else add keep on adding as per your requirement, I am adding only one node
break;
}
}
xDOC.Save(FilePath);

Related

How to remove the sibling node of XML

How to remove the sibling node of XML. I need to remove CreationDateTime value.
I am limited to XMLdocument in C#.
How to use say that i need to select the value element which is next to CreationDateTime Name element.
xmlDoc.Descendants().Where(e => e.Name == "Value").Remove();
<Mic><Attribute>
<Name>CreatedBy</Name>
<Value>MS</Value>
</Attribute>
<Attribute>
<Name>CreationDateTime</Name>
<Value>04/13/2018 19:45:38</Value>
</Attribute></Mic>
Since you are limited to using XmlDocument, you need to get all the Attribute tags and then remove their last child (this is the Value tag).
var doc = new XmlDocument();
doc.Load("YourFile.xml");
// Get all the Attribute tags
var atts = doc.GetElementsByTagName("Attribute");
// Traverse and remove the last child (this is the Value tag)
foreach (XmlNode thisElement in atts)
{
thisElement.RemoveChild(thisElement.LastChild);
}

inserting xml element into multiple nodes

I have the following XML
<ROOT>
<FSM338_Container>
<FSM338_Details>
<RunDate>2013-05-29 09:43:00</RunDate>
<Uic>21690</Uic>
<Date>2013-06-10 00:00:00</Date>
<CASHBREAK>199</CASHBREAK>
<CASHLUNCH>199</CASHLUNCH>
</FSM338_Details>
<FSM338_Details>
<RunDate>2013-05-29 09:43:00</RunDate>
<Uic>21690</Uic>
<Date>2013-06-10 00:00:00</Date>
<CASHBREAK>199</CASHBREAK>
<CASHLUNCH>199</CASHLUNCH>
</FSM338_Details>
</FSM338_Container>
<BillingReport>
<RunDate>2013-05-29 09:43:00</RunDate>
<Uic>21690</Uic>
<Date>2013-06-10 00:00:00</Date>
<gaindacd>1</gaindacd>
<docnum>07000F</docnum>
</BillingReport>
<DataElements>
<unitid>12345</unitid>
<fbocost>0.00</fbo>
</DataElements>
</ROOT>
I need to load the xml doc and add in several elements whenever I find the element named "Uic" . In short if I find "Uic" add in the element <someElement>my stuff here</someElement> at the same level as UIC at all locations.
I'Ve used
XmlDocument xDoc = new XmlDocument();
xDoc.Load(#"path_to_xml.xml");
list = xDoc.GetElementsByTagName("Uic");
I used insertBefore to add in my element but I can get it to copy to only the first element
You can use SelectNodes() method of XmlNode which accepts xpath expression.
XmlNodeList nodes = xDoc.DocumentElement.SelectNodes("Uic");
foreach(XmlNode node in nodes) {
XmlElement element = xDoc.CreateElement("SomeElement");
element.InnerText = "anything";
node.ParentNode.AppendChild(element);
}

What is the difference between Node.SelectNodes(/*) and Node.childNodes?

string XML1 = "<Root><InsertHere></InsertHere></Root>";
string XML2 = "<Root><child1><childnodes>data</childnodes><childnodes>data1</childnodes></child1><child2><childnodes>data</childnodes><childnodes>data1</childnodes></child2></Root>";
Among below mentioned two code samples.. usage of childNodes doesn't copy all the child nodes from XML2. only <child1> is being copied.
string strXpath = "/Root/InsertHere";
XmlDocument xdxmlChildDoc = new XmlDocument();
XmlDocument ParentDoc = new XmlDocument();
ParentDoc.LoadXml(XML1);
xdxmlChildDoc.LoadXml(XML2);
XmlNode xnNewNode = ParentDoc.ImportNode(xdxmlChildDoc.DocumentElement.SelectSingleNode("/Root"), true);
if (xnNewNode != null)
{
XmlNodeList xnChildNodes = xnNewNode.SelectNodes("/*");
if (xnChildNodes != null)
{
foreach (XmlNode xnNode in xnChildNodes)
{
if (xnNode != null)
{
ParentDoc.DocumentElement.SelectSingleNode(strXpath).AppendChild(xnNode);
}
}
}
}
code2:
if (xnNewNode != null)
{
XmlNodeList xnChildNodes = xnNewNode.ChildNodes;
if (xnChildNodes != null)
{
foreach (XmlNode xnNode in xnChildNodes)
{
if (xnNode != null)
{
ParentDoc.DocumentElement.SelectSingleNode(strXpath).AppendChild(xnNode);
}
}
}
}
ParentDoc.OuterXML after executing first sample of code:
<Root>
<InsertHere>
<child1>
<childnodes>data</childnodes>
<childnodes>data1</childnodes>
</child1>
<child2>
<childnodes>data</childnodes>
<childnodes>data1</childnodes>
</child2>
</InsertHere>
</Root>
ParentDoc.OuterXML after executing second sample of Code
<Root>
<InsertHere>
<child1>
<childnodes>data</childnodes>
<childnodes>data1</childnodes>
</child1>
</InsertHere>
</Root>
I have done some debugging of the code, and it shows that xnNewNode.ChildNodes initially also returns 2 child nodes. After one iteration in the loop, the first child is however removed from ChildNodes, and therefore the loop ends prematurely.
If you want to use the ChildNodes property, one workaround is to "transfer" the child node references to an array or list, like this:
var xnChildNodes = xnNewNode.ChildNodes.Cast<XmlNode>().ToArray();
UPDATE
As Tomer W pointed out in his answer, when using XmlNode.AppendChild the inserted node is also removed from its original location. As stated in the MSDN documentation:
If the newChild is already in the tree, it is removed from
its original position and added to its target position.
With SelectNodes you have already created a new node collection, but with ChildNodes you are accessing the original collection.
this is a clearing of what Anders G posted, with more through explanation.
I am surprised the foreach does not fail (Throw Exception) in this situation, but hell.
In code1.
1. Create a NEW COLLECTION of nodes
2. Select nodes to it
3. append to other node => removing from original collection, but not the newly created one.
4 you are removing the node you are adding from the newly collection.
in Code2
1. Reference the ORIGINAL node collection
{child1, child2}
2. append 1st Node away to another collection => removing it from the original collection
{child2}
3. now when the foreach at index 1, it see that it passed the end of the collection. and exit.
this happens a lot when changing a collection that is subject to iteration.
but most the time, the IEnumerator is throwing an Exception when such happens.
hope i made it all clear
I had the same problem and observed, that whitespace nodes seem to have a value attached to the node, which is not the case with other nodes (at least in my application).This method removes the whitespace nodes from the node.ChildNodes list:
private List<XmlNode> findChildnodes(XmlNode node)
{
List<XmlNode> result = new List<XmlNode>();
foreach (XmlNode childnode in node.ChildNodes)
{
if(childnode.Value == null)
{
result.Add(childnode);
}
}
return result;
}
In answer to your question, Node.childNodes is All of the child nodes, whereas Node.SelectNodes(/*) is all of the child nodes that match /*. Only XML elements will match /*, so any attributes, CDATA nodes, text nodes, etc will be excluded.
Nevertheless, the problem occurs because you are changing the collection of nodes while while iterating over them. You cannot do that. The select nodes method returns a list of references to nodes. This is why is works.

Finding element in XDocument?

I have a simple XML
<AllBands>
<Band>
<Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
<Last>1</Last>
<Salary>2</Salary>
</Band>
<Band>
<Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
<Last>1</Last>
<Salary>2</Salary>
</Band>
</AllBands>
However ,
when I want to reach the "Doors band" and to change its ID :
using (var stream = new StringReader(result))
{
XDocument xmlFile = XDocument.Load(stream);
var query = from c in xmlFile.Elements("Band")
select c;
...
query has no results
But
If I write xmlFile.Elements().Elements("Band") so it Does find it.
What is the problem ?
Is the full path from the Root needed ?
And if so , Why did it work without specify AllBands ?
Does the XDocument Navigation require me to know the full level structure down to the required element ?
Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:
var query = from c in xmlFile.Descendants("Band") select c;
Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:
<Band>
<BandProperties Name ="Doors" ID="222" started="1968" />
<Description>regular Band<![CDATA[lalala]]></Description>
<Last>1</Last>
<Salary>2</Salary>
</Band>
You can do it this way:
xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
You should use Root to refer to the root element:
xmlFile.Root.Elements("Band")
If you want to find elements anywhere in the document use Descendants instead:
xmlFile.Descendants("Band")
The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:
var query = from c in xmlFile.Descendants("Band")
My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).
In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral
In short, you can create a GetElement function:
private XElement GetElement(XDocument doc,string elementName)
{
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
return element;
}
}
return null;
}
Which you can then call like this:
XElement element = GetElement(doc,"Band");
Note that this will return null if no matching element is found.
The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:
var query = from c in xmlFile.Root.Elements("Band")
select c;
Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...
private static List<XElement> GetElements(XDocument doc, string elementName)
{
List<XElement> elements = new List<XElement>();
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
elements.Add(element);
}
}
return elements;
}
Call it thus:
var elements = GetElements(xamlFile, "Band");
or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:
var elements = GetElements(xamlFile, "TextBlock");

XElement value in C#

How to get a value of XElement without getting child elements?
An example:
<?xml version="1.0" ?>
<someNode>
someValue
<child>1</child>
<child>2</child>
</someNode>
If i use XElement.Value for <someNode> I get "somevalue<child>1</child><child>2<child>" string but I want to get only "somevalue" without "<child>1</child><child>2<child>" substring.
You can do it slightly more simply than using Descendants - the Nodes method only returns the direct child nodes:
XElement element = XElement.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
Note that this will work even in the case where the child elements came before the text node, like this:
XElement element = XElement.Parse(
#"<someNode><child>1</child><child>2</child>some value</someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
There is no direct way. You'll have to iterate and select. For instance:
var doc = XDocument.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var textNodes = from node in doc.DescendantNodes()
where node is XText
select (XText)node;
foreach (var textNode in textNodes)
{
Console.WriteLine(textNode.Value);
}
I think what you want would be the first descendant node, so something like:
var value = XElement.Descendents.First().Value;
Where XElement is the element representing your <someNode> element.
You can specifically ask for the first text element (which is "somevalue"), so you could also do:
var value = XElement.Descendents.OfType<XText>().First().Value;

Categories