Replace XmlNode with another XmlNode - c#

I am working in an older piece of code so I do not have the option to use XDocument.
I am trying to replace one XmlNode with another but from some reason XmlDocument.ReplaceChild() complains that: The node to be removed is not a child of this node.
I don't understand why I am getting this error because I am getting reference to the XmlDocument from the element to be replaced.
private XmlNode ReplaceWithSpan(XmlNode node)
{
if (XmlNodeType.Element == node.NodeType)
{
XmlDocument ownerDocument = node.OwnerDocument;
XmlNode spanNode = ownerDocument.CreateElement("span");
for (int i = 0; i < node.Attributes.Count; i++)
{
XmlAttribute attribute = node.Attributes[i];
AddAttribute(spanNode, attribute.Name, attribute.Value);
}
ownerDocument.ReplaceChild(spanNode, node); //this doesn't work
return spanNode;
}
throw new InvalidCastException(string.Format("Could not replace custom edit node of type {0}.", node.NodeType.ToString()));
}
Can someone where I've gone wrong?
Update
Got it figured out. The problem is that the old node is not a direct child of the owner document. The following change works:
node.ParentNode.ReplaceChild(spanNode, node);
return spanNode;

Related

Pasting xml to console

I need to get data SHILS_V from XML file. I read .xml
XML code for example:
<SVED_PR_GS>
<ZGLV>
<VERSION>99</VERSION>
<DATA>9999</DATA>
<FILENAME>1234</FILENAME>
<FIRSTNAME>1234</FIRSTNAME>
</ZGLV>
<SVD>
<CODE></CODE>
<YEAR></YEAR>
<MONTH></MONTH>
</SVD>
<SV_PR_GS>
<OBSV>
<N_ZAP>1</N_ZAP>
<MO_SV_V>12345</MO_SV_V>
<SNILS_V>123456789</SNILS_V>
</OBSV>
</SV_PR_GS>
</SVED_PR_GS>
My code to read xml:
XmlDocument xml = new XmlDocument();
xml.Load(filename);
Console.WriteLine("this");
XmlNodeList nodes = xml.GetElementsByTagName("SVED_PR_GS/SV_PR_GS");
foreach (XmlNode n in nodes)
{
Console.WriteLine("in loop");
XmlNode snils_v = n["OBSV/SNILS_V"];
Console.WriteLine("Snils V: " + snils_v);
}
Where is the problem?
How to get the information from SNILS_V?
GetElementsByTagName expects a tagname, not an XPath expression, "SV_PR_GS" will work there. And the same goes for the indexer of your XmlNode instance called n, this XPath will not work n["OBSV/SNILS_V"], use a tagname there as well but you have to handle the extra OBSV child there.
Here is your adapted code that produces output for me:
XmlNodeList nodes = xml.GetElementsByTagName("SV_PR_GS");
foreach (XmlNode n in nodes)
{
Console.WriteLine("in loop");
// first get the OBSV element
XmlNode obsv = n["OBSV"];
// now we can reach the other childs
XmlNode snils_v = obsv["SNILS_V"];
// Value will be null for XmlElement types, use InnerText instead
Console.WriteLine("Snils V: {0}" , snils_v.InnerText);
}
Notice that the Value property will return null for XmlNode that are of type XmlElement. In the XmlNode.Value documentation it is stated:
null. You can use the XmlElement.InnerText or XmlElement.InnerXml properties to access the value of the element node.
As your SNILS_V element seem to contain just a value InnerText is appropriate here.
To select a single node from XML. You need to access that object using SelectSingleNode method.
xml.SelectSingleNode("xpath for node")
To get collection of nodes you can write
xml.SelectNodes("xpath for node collection")
You should be able to get the value as follows:
XmlDocument xml = new XmlDocument();
xml.Load(filename);
Console.WriteLine("this");
XmlNodeList nodes = xml.GetElementsByTagName("OBSV");
foreach (XmlNode n in nodes)
{
Console.WriteLine("in loop");
XmlNode snils_v = n.SelectSingleNode("SNILS_V");
Console.WriteLine("Snils V: " + snils_v.InnerText);
}

Why is my xml doc considered an element?

I am trying to load an xml file into an xmlDocument but receive an error that it cannot cast the xmlelement to a xmldocument why?
XML
<VR>
<SubscriberID>xxxx</SubscriberID>
<EmailAddress>m#gmail.com</EmailAddress>
<FirstName>m</FirstName>
<LastName>x</LastName>
<State>CO</State>
<Country/>
<BirthDate>11/16/3004</BirthDate>
<SendEmail>False</SendEmail>
<Preference Value="true" Key="life"/>
<Preference Value="true" Key="yo"/>
</VR>
C# Test
preferenceHelper target = new preferenceHelper(); // TODO: Initialize to an appropriate value
XmlDocument docIn = new XmlDocument();
docIn.Load(#"C:/INTG/trunk/src/VRI.Integration.Email/Test/xmlIn.xml");
XmlDocument expected = null; // I know this will fail in the test, but it should compile, right?
XmlDocument actual;
actual = target.preferencesXmlDoc(docIn);
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
C# function:
public class preferenceHelper
{
public preferenceHelper() { }
XmlDocument docOut = new XmlDocument();
public XmlDocument preferencesXmlDoc(XmlDocument docIn)
{
foreach (XmlDocument root in docIn.SelectNodes("//VR"))
{
foreach (XmlDocument node in root.SelectNodes("//Preference"))
{
XmlNode Name = docIn.CreateElement("Name");
Name.InnerText = node.InnerText = node.Attributes["Key"].Value;
XmlNode Value = docIn.CreateElement("Value");
Value.InnerText = node.InnerText = node.Attributes["Value"].Value;
docOut.CreateElement("Property").AppendChild(Name).AppendChild(Value);
}
}
return docOut;
}
}
Error
Test method Test.preferenceHelperTest.preferencesXmlDocTest threw exception:
System.InvalidCastException: Unable to cast object of type 'System.Xml.XmlElement' to type 'System.Xml.XmlDocument'.
I will not be adding a namespace to the xmlIn, if this is required - how might I load in my xml File?
Where it fails: actual = target.preferencesXmlDoc(docIn);
Thanks
Your problems are in these statements:
foreach (XmlDocument root in SelectNodes(...))
foreach implicitly casts each value in the sequence to the type you specify. The statement is expanded to:
using(var e = sequence.GetEnumerator())
{
while (e.MoveNext())
{
XmlDocument v = (XmlDocument)e.Current;
// loop body
}
}
The reason this is crashing with an InvalidCastException is that the type of node you're selecting is XmlElement, not XmlDocument. To fix the issue, simply switch the type in your foreach statement to XmlElement.
You can also improve readability by using XPath to reach the Preference elements, replacing both loops with a single:
foreach (XmlElement node in docIn.SelectNodes("/VR/Preference"))
Your outer SelectNodes loop is actually completely redundant because //Preference will get all Preference descendants from the root of the document already, not just from that specific child VR.
The proplem is here:
foreach (XmlDocument root in docIn.SelectNodes("//VR"))
and here:
foreach (XmlDocument node in root.SelectNodes("//Preference"))
XmlNode.SelectNodes() returns an XmlNodeList, which is an IEnumerable of XmlNodes. It will not contain any XmlDocuments.
So do this:
foreach (XmlNode root in docIn.SelectNodes("//VR"))
and this:
foreach (XmlElement node in root.SelectNodes("//Preference"))
XmlDocument.SelectNodes("//VR") returns an XmlNodeList, not an XmlDocument. So at the least you need to change your code to:
public XmlDocument preferencesXmlDoc(XmlDocument docIn)
{
foreach (XmlNode root in docIn.SelectNodes("//VR"))
{
foreach (XmlNode node in root.SelectNodes("//Preference"))
{
A document usually has a header:
<?xml version="1.0"?>
Without the header it is considered an element.
Try adding one.

C# code for getting XML element by certain attribute value

I am creating XML document by reading some objects and adding them to proper place (inside xml tree structure). To be able to add it to proper place I need parent XmlNode so I could call parentNode.AppendChild(node);
How can I get XmlNode object if I know value of one of its attributes?
XmlDocument dom = new XmlDocument();
XmlNode parentNode = null;
XmlNode node = dom.CreateElement(item.Title); //item is object that I am writing to xml
XmlAttribute nodeTcmUri = dom.CreateAttribute("tcmUri");
nodeTcmUri.Value = item.Id.ToString();
node.Attributes.Append(nodeTcmUri);
parentNode = ??? - how to get XML node if I know its "tcmUri" attribute value (it is unique value, no other node has same "tcmUri" attribute value)
You can do this using SelectSingleNode function and xpath query as below
XmlNode parentNode = dom.SelectSingleNode("descendant::yournodename[#tcmUri='" + item.Id.ToString() + "']");
Where yournodename has to be replaced with the node name of the parent elements
Try this
XmlDocument doc = new XmlDocument();
doc.LoadXml(content);
XmlNodeList list = doc.SelectNodes("mynode");
foreach (XmlNode item in list)
{
if (item.Attributes["tcmUri"].Value == some_value)
{
// do what you want, item is the element you are looking for
}
}
Use following code:
var nodeList = doc.SelectNodes("<Node Name>[#tcmUri = \"<Value>\"]");
if(list.Count>0)
parentNode = list[0];
Replace <Node Name> with the node name which you want to make the parent node.
Replace the <Value> with the value of tcmUri attribute of the Node which you want to make the parent node.
XPath is your friend :
string xpath = String.Format("//parentTag[#tcmUri='{0}']", "tcmUriValueHere");
//or in case parent node name (parentTag) may varies
//you can use XPath wildcard:
//string xpath = String.Format("//*[#tcmUri='{0}']", "tcmUriValueHere");
parentNode = dom.SelectSingleNode(xpath)

XmlNode.ReplaceChild is complaining that the node I'm trying to remove is not a child despite the fact that I got to the node via ParentNode

I have a simple function that's designed to copy a section of an xml document to another. I want to replace one node with the other so ReplaceChild seems like the logical choice. I keep getting the error 'The reference node is not a child of this node.' though. That seems odd since I found that node by asking for the parent in the first place. Any idea what I'm doing wrong?
private static void KeepSection(XmlDocument newDoc, XmlDocument currentDoc, XmlNamespaceManager nsmgr, string path)
{
XmlNode section = currentDoc.SelectSingleNode(path, nsmgr);
XmlNode newSection = newDoc.SelectSingleNode(path, nsmgr);
if (newSection != null && section != null)
{
XmlNode parent = newSection.ParentNode;
parent.ReplaceChild(newSection, newDoc.ImportNode(section, true));
}
}
It looks like you have your ReplaceChild parameters reversed:
public virtual XmlNode ReplaceChild(
XmlNode newChild,
XmlNode oldChild
)
Actually I was being an idiot. I got the parameters to ReplaceChild the wrong way around. The code should have been,
parent.ReplaceChild(newDoc.ImportNode(section, true), newSection);
Sorry about that!

Removing nodes from an XmlDocument

The following code should find the appropriate project tag and remove it from the XmlDocument, however when I test it, it says:
The node to be removed is not a child of this node.
Does anyone know the proper way to do this?
public void DeleteProject (string projectName)
{
string ccConfigPath = ConfigurationManager.AppSettings["ConfigPath"];
XmlDocument configDoc = new XmlDocument();
configDoc.Load(ccConfigPath);
XmlNodeList projectNodes = configDoc.GetElementsByTagName("project");
for (int i = 0; i < projectNodes.Count; i++)
{
if (projectNodes[i].Attributes["name"] != null)
{
if (projectName == projectNodes[i].Attributes["name"].InnerText)
{
configDoc.RemoveChild(projectNodes[i]);
configDoc.Save(ccConfigPath);
}
}
}
}
UPDATE
Fixed. I did two things:
XmlNode project = configDoc.SelectSingleNode("//project[#name='" + projectName + "']");
Replaced the For loop with an XPath query, which wasn't for fixing it, just because it was a better approach.
The actual fix was:
project.ParentNode.RemoveChild(project);
Thanks Pat and Chuck for this suggestion.
Instead of
configDoc.RemoveChild(projectNodes[i]);
try
projectNodes[i].parentNode.RemoveChild(projectNodes[i]);
try
configDoc.DocumentElement.RemoveChild(projectNodes[i]);
Looks like you need to select the parent node of projectNodes[i] before calling RemoveChild.
When you get sufficiently annoyed by writing it the long way (for me that was fairly soon) you can use a helper extension method provided below. Yay new technology!
public static class Extensions {
...
public static XmlNode RemoveFromParent(this XmlNode node) {
return (node == null) ? null : node.ParentNode.RemoveChild(node);
}
}
...
//some_long_node_expression.parentNode.RemoveChild(some_long_node_expression);
some_long_node_expression.RemoveFromParent();
Is it possible that the project nodes aren't child nodes, but grandchildren or lower? GetElementsByTagName will give you elements from anywhere in the child element tree, IIRC.
It would be handy to see a sample of the XML file you're processing but my guess would be that you have something like this
<Root>
<Blah>
<project>...</project>
</Blah>
</Root>
The error message seems to be because you're trying to remove <project> from the grandparent rather than the direct parent of the project node

Categories

Resources