Replace a nodeset in XmlDocument with another XmlDocument file - c#

I was wondering what would be the best way to do the following:
There are 2 XML files in a directory "Input.xml" and "Metadata.xml".
Input.xml:
<Root>
<Header>
<ID>1</ID>
<Name>Test</Name>
</Header>
<Body>
<MetaDataSet>
<ID>23568</ID>
<Value>metadatavalue1</Value>
</MetaDataSet>
</Body>
</Root>
Metadata.xml:
<MetaDataSet>
<metadatasetvalue>Test</metadatasetvalue>
<Valid>true</Valid>
</MetaDataSet>
What I would like to do is to be able to replace the node set "MetaDataSet" of "Input.xml" with the entire content of the file "Metadata.xml".
So the resulting output would be:
<Root>
<Header>
<ID>1</ID>
<Name>Test</Name>
</Header>
<Body>
<MetaDataSet>
<metadatasetvalue>Test</metadatasetvalue>
<Valid>true</Valid>
</MetaDataSet>
</Body>
</Root>
Is it possible in c# to replace a nodeset with an XmlDocument?

You can read the two Xml documents in two XmlDocument classes, call SelectSingleNode and then replace the InnerXml of the found element with the OuterXml of the meta DocumentElement, like so:
var i = #"
<Root>
<Header>
<ID>1</ID>
<Name>Test</Name>
</Header>
<Body>
<MetaDataSet>
<ID>23568</ID>
<Value>metadatavalue1</Value>
</MetaDataSet>
</Body>
</Root>";
var m = #"
<MetaDataSet>
<metadatasetvalue>Test</metadatasetvalue>
<Valid>true</Valid>
</MetaDataSet>
";
var input2 = new XmlDocument();
input2.Load(new StringReader(i));
var meta2 = new XmlDocument();
meta2.Load(new StringReader(m));
var body2 = input2.DocumentElement["Body"];
body2.InnerXml = meta2.DocumentElement.OuterXml;
// helper to show the result
sb = new StringBuilder();
using(var xw = XmlWriter.Create(sb)) {
input2.Save(xw);
}
sb.Dump("2");
If you are open to change to the XDocument class you can use ReplaceNodes on an element:
var inDoc = XDocument.Parse(i);
var metaDoc = XDocument.Parse(m);
var body = inDoc.Root.Element("Body");
body.ReplaceNodes(metaDoc.Root);
// helper to show the result
var sb = new StringBuilder();
using(var xw = XmlWriter.Create(sb))
{
inDoc.WriteTo(xw);
}
sb.Dump(); // LinqPad helper

It's also possible using ReplaceWith and XPath with LINQ-to-XML.
Assuming the same init as in #rene's answer for variables i and m:
using System.Xml.Linq;
using System.Xml.XPath;
var inDoc = XElement.Parse(i);
var metaDoc = XElement.Parse(m);
inDoc.XPathSelectElement("//Body/MetaDataSet").ReplaceWith(metaDoc);

Related

How to merge one specific tag into one XML using c#?

I have two XMLs'
XML1:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
</Sequence>
</Data>'
XML2:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I want the output like below
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I.e I want to merge Object tag from XML2 to XML1 using C# code.
Could someone please help me?
You can use XPath to select the nodes you need and then simply use .NET xml using System.Xml
For more information look at https://www.w3schools.com/xml/xpath_intro.asp
Load Xml Documents
I saved the two sample xml-files you provided to separate files and imported them like this
XmlDocument doc1 = new XmlDocument();
XmlDocument doc2 = new XmlDocument();
using (var sw = new StreamReader("xml1.xml"))
{
var text = sw.ReadToEnd();
doc1.LoadXml(text);
}
using (var sw = new StreamReader("xml2.xml"))
{
var text = sw.ReadToEnd();
doc2.LoadXml(text);
}
Select nodes with XPATH
We will take all elements that have the name 'object' and add them to child of the other xml's 'sequence'-element. Therefore we select the 'sequence'-element of one document and the 'object'elements of the other document.
var sequenceNodes = doc1.SelectSingleNode("/Data/Sequence");
var objectNodes = doc2.SelectNodes("/Data/Sequence/Object");
Concat the nodes into one document
Then we take each 'object'-element, import it into the other document-context and append it under the 'sequence'-node
foreach (XmlNode node in objectNodes)
{
XmlNode importedNode = doc1.ImportNode(node, true);
sequenceNodes.AppendChild(importedNode);
}
Output the file
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
doc1.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
File.AppendAllText("out.xml", stringWriter.GetStringBuilder().ToString());
}
The outputfile looks like this:
Please, take a look at this solution:
what-is-the-fastest-way-to-combine-two-xml-files-into-one
Using Union method seems to be what you are seeking.
Run an XSLT transformation:
<xsl:template name="merge">
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<xsl:copy-of select="document('a.xml')//Object"/>
<xsl:copy-of select="document('b.xml')//Object"/>
</Sequence>
</Data>
</xsl:template>
Try following linq which joins all filenames with same value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication166
{
class Program
{
const string FILENAME1 = #"c:\temp\test.xml";
const string FILENAME2 = #"c:\temp\test1.xml";
static void Main(string[] args)
{
XDocument doc1 = XDocument.Load(FILENAME1);
XNamespace ns1 = doc1.Root.GetDefaultNamespace();
XDocument doc2 = XDocument.Load(FILENAME2);
XNamespace ns2 = doc2.Root.GetDefaultNamespace();
var joins= from d1 in doc1.Descendants(ns1 + "Data")
join d2 in doc2.Descendants(ns2 + "Data")
on (string)d1.Element(ns1 + "Filename") equals (string)d2.Element(ns2 + "Filename")
select new { d1 = d1, d2 = d2};
foreach (var join in joins)
{
XElement d2Object = join.d2.Descendants("Object").FirstOrDefault();
join.d1.Descendants("Sequence").FirstOrDefault().Add(XElement.Parse(d2Object.ToString()));
}
}
}
}

I Encounter error when SelectingSingleNode

EDIT: I think the reason is because of "UniversalShipment" xmlns. There is a link but i remove it for confidentiality. I can't pass through that node. help
This is my XML.
<UniversalInterchange xmlns="" version="1.0">
<Header>
<SenderID>1</SenderID>
<RecipientID>2</RecipientID>
</Header>
<Body>
<UniversalShipment xmlns="" version="1.1">
<Shipment>
<CustomizedFieldCollection>
<CustomizedField>
<Key>Documents Checked</Key>
<DataType>Boolean</DataType>
<Value>false</Value>
</CustomizedField>
<CustomizedField>
<Key>Date Completed</Key>
<DataType>DateTime</DataType>
<Value></Value>
</CustomizedField>
</CustomizedFieldCollection>
</Shipment>
</UniversalShipment>
</Body>
</UniversalInterchange>
I received null when getting singlenode. But when i try "Body" only in single node it add to bottom. if i tried to add it to UniversalShipment "Body/UniversalShipment" it encounters and error.
XmlDocument doc=new XmlDocument();
doc.Load("sample.xml");
XmlNode customizedNode = doc.CreateElement("CustomizedField");
XmlNode keyNode = doc.CreateElement("Key");
XmlNode dataNode = doc.CreateElement("DataType");
XmlNode valueNode = doc.CreateElement("Value");
keyNode.InnerText = "hi";
dataNode.InnerText = "hello";
valueNode.InnerText = "bye";
customizedNode.AppendChild(keyNode);
customizedNode.AppendChild(dataNode);
customizedNode.AppendChild(valueNode);
doc.DocumentElement.SelectSingleNode("Body/UniversalShipment/Shipment/CustomizedFieldCollection").AppendChild(customizedNode);
doc.Save("sample.xml");
If you have default namespaces you will need to use an XmlNamespaceManager - however, you have two default namespaces so it's a little more complicated.
As you have completely removed the namespace URIs from your question I've invented my own:
<UniversalInterchange xmlns="firstdefaultnamespace" version="1.0">
<Header>
<SenderID>1</SenderID>
<RecipientID>2</RecipientID>
</Header>
<Body>
<UniversalShipment xmlns="seconddefaultnamespace" version="1.1">
<Shipment>
<CustomizedFieldCollection>
<CustomizedField>
<Key>Documents Checked</Key>
<DataType>Boolean</DataType>
<Value>false</Value>
</CustomizedField>
<CustomizedField>
<Key>Date Completed</Key>
<DataType>DateTime</DataType>
<Value></Value>
</CustomizedField>
</CustomizedFieldCollection>
</Shipment>
</UniversalShipment>
</Body>
</UniversalInterchange>
If your first default namespace has a URI of firstdefaultnamespace and your second default namespace has a URI of seconddefaultnamespace you can do this:
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("ns1", "firstdefaultnamespace");
ns.AddNamespace("ns2", "seconddefaultnamespace");
doc.DocumentElement.SelectSingleNode("ns1:Body/ns2:UniversalShipment/ns2:Shipment/ns2:CustomizedFieldCollection", ns).AppendChild(customizedNode);
However, you will have problems when you save your XML with the new XmlNodes - you are not creating it with any namespace so it will be saved with a new default namespace, which will override the default namespace on the UniversalShipment element.
I would strongly suggest you read more about XML namespacing.
If you want to create your elements and keep them within the inner default namespace, you'll need to do something like this:
const string FirstNamespaceUri = "firstdefaultnamespace";
const string SecondNamespaceUri = "seconddefaultnamespace";
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("ns1", FirstNamespaceUri);
ns.AddNamespace("ns2", SecondNamespaceUri);
XmlNode customizedNode = doc.CreateElement("CustomizedField", SecondNamespaceUri);
XmlNode keyNode = doc.CreateElement("Key", SecondNamespaceUri);
XmlNode dataNode = doc.CreateElement("DataType", SecondNamespaceUri);
XmlNode valueNode = doc.CreateElement("Value", SecondNamespaceUri);
keyNode.InnerText = "hi";
dataNode.InnerText = "hello";
valueNode.InnerText = "bye";
customizedNode.AppendChild(keyNode);
customizedNode.AppendChild(dataNode);
customizedNode.AppendChild(valueNode);
doc.DocumentElement.SelectSingleNode("ns1:Body/ns2:UniversalShipment/ns2:Shipment/ns2:CustomizedFieldCollection", ns).AppendChild(customizedNode);
XPath expression is not formed correctly in you case. Check this link for XPath guide: http://www.w3schools.com/xsl/xpath_syntax.asp
Working Xpath:
doc.DocumentElement.SelectSingleNode("Body/UniversalShipment/Shipment/CustomizedFieldCollection").AppendChild(customizedNode);
If namespaces are specified in the XML, XmlNamespaceManager can be passed to XmlNode.SelectSingleNode as explained here!

xml error:System.Xml.XPath.XPathException: Expression must evaluate to a node-set

I am trying to find a node in my xml file but getting the error ( see title)?
// instantiate XmlDocument and load XML from file
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\temp\test2.xml");
var node = doc.SelectSingleNode("/Offers/Offer/ID=[text()='1']");
var test = node;
xml
<?xml version="1.0" encoding="utf-8"?>
<Offers>
<Offer>
<Model>AAAA</Model>
<ID>1</ID>
<Name>First offer</Name>
</Offer>
<Offer>
<Model>BBBB</Model>
<ID>2</ID>
<Name>Second offer</Name>
</Offer>
</Offers>
Remove the = after ID:
var node = doc.SelectSingleNode("/Offers/Offer/ID=[text()='1']");
becomes:
var node = doc.SelectSingleNode("/Offers/Offer/ID[text()='1']");

Simple Xml parse with Xdocument

I want parse xml in windows store app with Xdocument.
I tried this,but returned with null:
XDocument xDoc;
string title= "";
xDoc = XDocument.Load(url);
var elements = from x in xDoc.Descendants()
select new
{
title = x.Descendants("title").First().Value,
};
foreach (var el in elements)
_title = title;
Xml contents:
<title type='text'>tiitle</title>
<content type='text'> gfgdgdggd</content>
<link rel='related' type='application/atom+xml' href='http....'/>
How can is retrive text from attributes ?
As ZevSpitz already mentioned, your XML is invalid. I modified it a bit to be able to test my code:
<root>
<title type="text">title</title>
<content type="text">gfgdgdggd</content>
</root>
You can retrieve values from the type attributes with the following code:
XDocument xDoc = XDocument.Parse(xml);
var types =
from x in xDoc.Root.Descendants()
select x.Attribute("type").Value;
In my case xml is declared as follows:
private string xml =
#"<root>
<title type=""text"">title</title>
<content type=""text"">gfgdgdggd</content>
</root>";
You can still use your code to load the XML from a URL if the file contents are the same.
Try:
var types =
from e in xDoc.Descendants()
select (string)e.Attribute("type");
foreach (string type in types) {
Console.WriteLine(type);
}

How to get the value of an XML attribute?

I have a XML file:
<SourceMessage xmlns="test.test">
<updated>2011</updated>
<title type="p1"/>
<title type="p2"/>
<title type="p3"/>
<entry>
</entry>
</SourceMessage>
How could I use LINQ to get the <type> attribute of the <title> element, i.e. "p1", "p2" and "p3"?
Use XDocument.Load or XDocument.Parse to load the XML data into an XDocument. Then, using LINQ, you can get the type for each <title> element under the document root as follows:
XNamespace test = "test.test";
XDocument doc = XDocument.Load(file);
// - or -
XDocument doc = XDocument.Parse("<SourceMessage ...");
IEnumerable<string> query = from title in doc.Root.Elements(test + "title")
select (string)title.Attribute("type");
foreach (string item in query)
{
Console.WriteLine(item);
}
Output:
p1
p2
p3
var xElement XElement.Parse(xmlString);
var result = xElement.Descendants("title")
.Select(e => e.Attribute("type").Value);
XDocument xml = XDocument.Parse (#"<SourceMessage xmlns="test.test">
<updated>2011</updated>
<title type="p1"/>
<title type="p2"/>
<title type="p3"/>
<entry>
</entry>
</SourceMessage>");
foreach (var t in xml.Root.Descendants("title"))
Console.Write(t.Attribute("type").Value);

Categories