Copy nested objects from one XmlDocument to another - c#

I am at my wits end on this one. Here's the document I have:
<?xml version="1.0"?>
<TestObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Property1>TestObjectVal1</Property1>
<Property2>TestObjectVal2</Property2>
<Property3>TestObjectVal3</Property3>
<SubObject>
<Prop1>TestObject2Val1</Prop1>
<Prop2>TestObject2Val2</Prop2>
<Prop3>TestObject2Val3</Prop3>
</SubObject>
</TestObject>
I'm trying to copy select portions of it to an new XmlDocument object based on some specified XPaths. I've tried every permutation I can think of. Here's where I'm at now.
var filters = new[] { "Property1", "Property2", "SubObject/Prop1" };
var xmlDoc = GetObjectXml(obj); //Loads the document
var newDoc = (XmlDocument)xmlDoc.Clone();
newDoc.DocumentElement.RemoveAll();
var rootNode = xmlDoc.DocumentElement;
foreach (var filter in filters)
{
var nodes = rootNode.SelectNodes(filter);
foreach (XmlNode node in nodes)
{
var newNode = newDoc.ImportNode(node, true);
newDoc.DocumentElement.AppendChild(newNode);
}
}
What I'm getting back is this:
<?xml version="1.0"?>
<TestObject>
<Property1>TestObjectVal1</Property1>
<Property2>TestObjectVal2</Property2>
<Prop1>TestObject2Val1</Prop1>
</TestObject>
But I want this:
<?xml version="1.0"?>
<TestObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Property1>TestObjectVal1</Property1>
<Property2>TestObjectVal2</Property2>
<SubObject>
<Prop1>TestObject2Val1</Prop1>
</SubObject>
</TestObject>
Any idea what I'm doing wrong?

It looks like the problem is that the xpath method is selecting the inner element, so that the *SubObject" information is getting lost. This code should result in the correct output for your specific example.
foreach (XmlNode node in nodes)
{
XmlElement newNode;
string[] xpathElements = filter.Split('/');
if (xpathElement.Length == 2)
{
newNode = newDoc.CreateElement(filter);
newNode.AppendChild(newDoc.ImportNode(node, true));
}
else
{
newNode = newDoc.ImportNode(node, true);
}
newDoc.DocumentElement.AppendChild(newNode);
}
Note that this code makes restrictive assumptions about what form the "filter" xpath expression must take... namely, it must be of the form "RootElement" or "RootElement/ChildElement" (no attributes an max depth of three). That might be enough depending on your use case, but solving for more general cases would be a bit more tricky ...

Related

trying to read an XMLfile and only get the data out of one child node using c#

i am reading from an XML file and it pulls the correct data but pulls the data from 2 different nodes due to repeating of the attributes "server", "database" and "user"
When the program runs, it runs through both rather than just the one node i need it to run through. How can i make it just get the data from the node <MySql_Connection_string/>
The code is as follows:
XmlDocument doc = new XmlDocument();
doc.Load("xmlConnection.xml");
foreach (XmlNode node in doc.DocumentElement)
{
string databaseConnection = node.Attributes[0].InnerText;
if (databaseConnection == "MySQL")
{
foreach (XmlElement element in doc.DocumentElement)
{
foreach (XmlNode child in node.SelectNodes("//MySQL_Connection_string"))
{
MySQLServer = element.GetAttribute("Server");
MySQLDatabase = element.GetAttribute("Database");
MySQLUser = element.GetAttribute("user");
MySQLPassword = element.GetAttribute("password");
Console.WriteLine(MySQLServer);
Console.WriteLine(MySQLDatabase);
Console.WriteLine(MySQLUser);
Console.WriteLine(MySQLPassword);
}
}
}
}
The console.writeline is simply to see the outputs
the xml code is
<?xml version="1.0" encoding="utf-8"?>
<Database_Connections>
<MySQL_Connection_string Database_Connection="MySQL" Server="***" Database="littering_detection_jf" user="jordan" password="***" />
<PostgreSQL_Connection_string Database_Connection="postgreSQL" host="***" username="Jordan" password="***" database="intuvision" Port="***" />
<SQL_connection_string Database_Connection="SQL" Server="****" Database="anprdb" user="Jordan" password="***" />
</Database_Connections>
Get rid of all the foreach loops except the inner one:
foreach (XmlElement child in doc.SelectNodes("//MySQL_Connection_string"))
{
MySQLServer = child.GetAttribute("Server");
MySQLDatabase = child.GetAttribute("Database");
MySQLUser = child.GetAttribute("user");
MySQLPassword = child.GetAttribute("password");
Console.WriteLine(MySQLServer);
Console.WriteLine(MySQLDatabase);
Console.WriteLine(MySQLUser);
Console.WriteLine(MySQLPassword);
break; // not interested in any additional MySQL_Connection_string elements
}
This assumes that doc.SelectNodes("//MySQL_Connection_string") returns only XML elements, no other types of nodes which is the case here, so I replaced your XmlNode child by XmlElement child.

Looping through all nodes in xml file with c#

I have an xml document with setup similiar to this:
<invoice>
<IssueDate>2015-09-07</IssueDate>
<InvoiceType>380<InvoiceType>
<AccountingSupplierParty>
<Party>
<EndpointID></EndpointID>
<PartyName>
<Name>Company test</Name>
</PartyName>
</Party>
</AccountingSupplierParty>
</invoice>
This is just a little piece of the entire xml document, just to show how the file looks.
I would like to check all the elements too see if they have empty values, such as EndpointID in this example (I need to replace empty values with NA).
This is what I have so far:
public static void AddToEmptyElements()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("testXml.xml");
XmlNodeList nodes = xmlDoc.DocumentElement.ChildNodes;
foreach (XmlNode node in nodes)
{
Console.WriteLine(node.Name);
}
}
However, this code will only loop through the childs of the root node and not all the grandchilds (or great grandchilds) such as the <Party> and <EndpointID> elements. How can I include these elements in the loop?
I'm not in an environment to test code right now, but isn't it possible to write a recursive method to loop further down the nodes?
Something like this (untested code):
private static void handleNode(XmlNode node)
{
if(node.HasChildNodes)
{
foreach(XmlNode child in node.ChildNodes)
{
handleNode(child);
}
}
else
Console.WriteLine(node.Name);
}
then call this method in place of your Console.WrintLine()
As Jon Skeet already said, you could do this using LINQ to XML.
XDocument xml = XDocument.Load(path);
var emptyElements = xml.Descendants().Where(xe => String.IsNullOrEmpty(xe.Value));
foreach (var xe in emptyElements)
xe.Value = "NA";

Deleting node from string with xml structure

I have an string parameter with xml content in it. Basically the string have an XML inside.
string S = funcThatReturnsXML (parameters);
S have the next text:
<?xml version="1.0" encoding="utf-8" ?>
<tagA>
<tagB>
<tagBB>
..
.
.
</tagBB>
.
.
</tagB>
<tagC>
..
..
.
</tagC>
</tagA>
The funcThatReturnsXML (parameters) creates an XmlDocument object but the return it as a string, I cant change this function, to much stuff works with it.
Tried to create XmlDocument objetc but the SelectSingleNode return null.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(S);
XmlNode root = xmlDoc.SelectSingleNode("tagB");
How can I delete from string S (not XML Object) specific node, for example <tagB>
EDIT: this is the XML I tested with:
<?xml version="1.0" ?>
- <Request xmlns:xsi="http://www.mysite.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- <info xmlns="http://www.mysite.com">
<RequestTR>54</RequestTR>
<time>2013-12-22</time>
</info>
- <Parameters xmlns="http://www.mysite.com">
<id>3</id>
<name>2</name>
</Parameters>
<title>Request</title>
</Request>
Try this:
string S = funcThatReturnsXML(parameters);
var doc = XDocument.Parse(S);
var nodeToRemove = doc.Descendants("tagB");
nodeToRemove.Remove();
That will remove all nodes named "tagB" from string S which contains xml.
UPDATE 1:
Sorry, i missed to include one more line:
S = doc.ToString();
My first code above removed "tagB" from doc but didnt save it back to S variable.
UPDATE 2:
I tested with following xml which contain attribute:
<tagA attribute="value">
<tagB>
<tagBB>
</tagBB>
</tagB>
<tagC></tagC>
</tagA>
and the output of Console.WriteLine(S):
<tagA attribute="value">
<tagC></tagC>
</tagA>
UPDATE 3:
Given your updated xml format, I know why my previous code didn't work for you. That was because your xml have namespace (xmlns) declared. The solution is to use LocalName when searching for the node to be removed, that will search for node name while ignoring its namespace. The follwoing example shows how to remove all "info" node:
var doc = XDocument.Parse(S);
var nodeToRemove = doc.Descendants().Where(o => o.Name.LocalName == "info");
nodeToRemove.Remove();
S = doc.ToString();
If you can determine the particular outer element to remove from the returned XML, you could use LINQ to XML:
var returnedXml = funcThatReturnsXML(parameters);
var xmlElementToRemove = funcThatReturnsOuterElement(returnedXml);
var xelement = XElement.Load("XmlDoc.txt");
xelement.Elements().Where(e => e.Name == xmlElementToRemove).Remove();
For example:
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main(string[] args)
{
// pretend this is the funThatReturnsXML return value
var returnedXml = "<tagB><tagBB></tagBB></tagB>";
// get the outer XML element name
var xmlElementToRemove = GetOuterXmlElement(returnedXml);
// load XML from where ever
var xelement = XElement.Load("XmlDoc.txt");
// remove the outer element and all subsequent elements
xelement.Elements().Where(e => e.Name == xmlElementToRemove).Remove();
}
static string GetOuterXmlElement(string xml)
{
var index = xml.IndexOf('>');
return xml.Substring(1, index - 1);
}
}
Note that the above is a "greedy" removal method, if there is more than once element with the name returned via the GetOuterXmlElemet method they will all be removed. If you want a specific instance to be removed then you will require something more sophisticated.
Building on your edit:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(S);
var nodeA = xmlDoc.SelectSingleNode("/tagA");
var nodeB = nodeA.SelectSingleNode("tagB");
nodeA.RemoveChild(nodeB);
To remove (possibly) multiple tagB nodes in unknown positions, you may try:
var bees = xmlDoc.SelectNodes("//tagB");
foreach (XmlNode bee in bees) {
var parent = bee.ParentNode;
parent.RemoveChild(bee);
}

C# how to create a custom xml document

I'm really just trying to create a custom xml document for simple configuration processing.
XmlDocument xDoc = new XmlDocument();
string[] NodeArray = { "Fruits|Fruit", "Vegetables|Veggie"};
foreach (string node in NodeArray)
{
XmlNode xmlNode = xDoc.CreateNode(XmlNodeType.Element,node.Split('|')[0],null);
//xmlNode.Value = node.Split('|')[0];
xmlNode.InnerText = node.Split('|')[1];
xDoc.DocumentElement.AppendChild(xmlNode);
}
What i'm trying to get is this.
<?xml version="1.0" encoding="ISO-8859-1"?>
<Fruits>Fruit</Fruits>
<Vegetables>Veggie</Vegetables>
i get not set to value of an object at xDoc.DocumentElement.AppendChild(xmlNode);
Unfortunately, You can't make that XML structure.
All XML documents must have a single root node. You can't have more.
Try something like this
XmlDocument xDoc = new XmlDocument();
xDoc.AppendChild( xDoc.CreateElement("root"));
string[] NodeArray = { "Fruits|Fruit", "Vegetables|Veggie" };
foreach (string node in NodeArray)
{
XmlNode xmlNode = xDoc.CreateNode(XmlNodeType.Element, node.Split('|')[0], null);
//xmlNode.Value = node.Split('|')[0];
xmlNode.InnerText = node.Split('|')[1];
xDoc.DocumentElement.AppendChild(xmlNode);
}
It is not possible to have that XML structure as XML MUST have a single root element. You may want to try:
<?xml version="1.0" encoding="ISO-8859-1"?>
<items>
<Fruits>Fruit</Fruits>
<Vegetables>Veggie</Vegetables>
</items>

simple xpath not working to find a node

Iam trying to get a particular node with value equals my input parameter,my xpath is like this where b is the node I need
string xpath = "/Batches/Measurement/Batch[market=someval]/b";
<?xml version="1.0" encoding="utf-8" ?>
<Batches>
<Measurement>
<Batch>
<market>someval</market>
<b>someval</b>
</Batch>
</Measurement>
</Batches>
var xmlNode = xmlDoc.SelectNodes(xpath);
no nodes retruned always count 0 ,I checked that the xmldoc is loaded properly.
Your xpath is nearly perfect. Only keep in mind const values have to be put in apostrophe:
"/Batches/Measurement/Batch[market='someval']/b"
Update: C# code example:
XmlNodeList nodeList;
nodeList = root.SelectNodes("/Batches/Measurement/Batch[market='someval']/b");
foreach (XmlNode node in nodeList)
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
Console.WriteLine(node.ChildNodes[i].InnerText);
}
}
The return value of SelectNodes is a nodeList. You have to iterate through it.
And a little bit shorter:
XmlElement root = doc.DocumentElement;
string text;
text = root.SelectSingleNode("/Batches/Measurement/Batch[market='someval']/b").InnerText;
Console.WriteLine(text);
Have you thought about using LINQ to XML?
It is slightly more efficient and shorter clearner syntax for selecting. I know you asked about Xpath so feel free to ignore this. Just making you aware of the option
var doc = XDocument.Load("c:\\tmp\\test.xml");
var result = doc.Descendants().Where(x => x.Element("b") != null)
.Select(x => x.Element("b").Value);

Categories