How to get an independent copy of an XDocument? - c#

I'm trying to create a new XDocument as follows:
var xmlString = _documentDictionary[documentKey].ToString(SaveOptions.DisableFormatting);
XDocument xml = XDocument.Parse(xmlString);
I now have xml which I would have though was a stand-alone instance of a document because I extracted the string from the original document and created a new one from that.
But when I modify xml and then inspect the _documentDictionary[documentKey] I can see that the original document has been modified also.
How can I get a new independent document from the existing collection that I have?
Note:
I've tried these but it doesn't work:
var xmlString = _documentDictionary[documentKey].ToString(SaveOptions.DisableFormatting);
var copyDoc = new XDocument(xmlString);
and
var copyDoc = new XDocument(_documentDictionary[documentKey]);

There is a copy constructor defined for XDocument class:
var newDoc = new XDocument(xml);
You use this constructor to make a deep copy of an XDocument.
This constructor traverses all nodes and attributes in the document
specified in the other parameter, and creates copies of all nodes as
it assembles the newly initialized XDocument.
Quick test
var doc = new XDocument(new XElement("Test"));
var doc2 = new XDocument(doc);
doc.Root.Name = "Test2";
string name = doc.Root.Name.ToString();
string name2 = doc2.Root.Name.ToString();
name is "Test2" and name2 is "Test", what proofs that changes made on doc don't affect doc2.

Try to copy constructor, like;
var newDoc = new XDocument(xml);
From MSDN:
You use this constructor to make a deep copy of an XDocument.
This constructor traverses all nodes and attributes in the document
specified in the other parameter, and creates copies of all nodes as
it assembles the newly initialized XDocument.

Related

How to read a node in a serialized XML file?

I'm trying to read a node in a serialized XML file. Here is the the first part of the XML file (I'm using a screen cap because pasting ended up with weird formatting):
And this is the code I'm using to read the XML and the error it's throwing:
I'm trying to read the <ScenarioDescription> node.
As per request, here's the entire XML file. Unfortunately it's just a complete mess. Here is a link to the XML file.
You should You would need to specify the namespace. In this particular case, the default namespace is used to declare the http://schemas.datacontract.org/2004/07/ModelLib namespace.
var xml = new XmlDocument();
xml.LoadXml(str);
XmlNamespaceManager ns = new XmlNamespaceManager(xml.NameTable);
ns.AddNamespace("x", xml.DocumentElement.NamespaceURI);
var root = xml.DocumentElement;
var test = root.SelectSingleNode("//x:ScenarioDescription",ns);
var scenarioText = test.InnerText;
You can use the following code to access the ScenarioDescription node and its InnerText
var document = new XmlDocument();
document.Load(s);
var root = document.DocumentElement;
var node = root["ScenarioDescription"];
var text = node?.InnerText;
SelectSingleNode accepts the xpath expression, you simply can use XmlElement indexer instead. Otherwise you'll need to create a XmlNamespaceManager instance and add your root namespace to it
You can supply the XmlNamespaceManager object as a parameter to the
SelectNodes or SelectSingleNode method of the XmlDocument class to
execute XPath query expressions that reference namespace-qualified
element and attribute name

How to generate an XML file dynamically using XDocument?

As I wrote in the subject itself , how can I do that?
Note that solution like this are not appropriate as I want to create child nodes dynamically through running..
new XDocument(
new XElement("root",
new XElement("someNode", "someValue")
)
)
.Save("foo.xml");
I guess this was clear enough the first time but I will write it again:
I need to be able to add child nodes to given parent node while running, in the current syntax I've written this is static generated xml which doesn't contribute me at all because all is known in advance, which is not as my case.
How would you do it with Xdocument, is there away?
If a document has a defined structure and should be filled with dynamic data, you can go like this:
// Setup base structure:
var doc = new XDocument(root);
var root = new XElement("items");
doc.Add(root);
// Retrieve some runtime data:
var data = new[] { 1, 2, 3, 4, 5 };
// Generate the rest of the document based on runtime data:
root.Add(data.Select(x => new XElement("item", x)));
Very simple
Please update your code accordingly
XmlDocument xml = new XmlDocument();
XmlElement root = xml.CreateElement("children");
xml.AppendChild(root);
XmlComment comment = xml.CreateComment("Children below...");
root.AppendChild(comment);
for(int i = 1; i < 10; i++)
{
XmlElement child = xml.CreateElement("child");
child.SetAttribute("age", i.ToString());
root.AppendChild(child);
}
string s = xml.OuterXml;

C# - Unable to save a customized linq-to-xml file, with new elements in it?

I'm creating a new XDocument and inserting a root element "profiles" in it, then saving.
if (!System.IO.File.Exists("profiles.xml"))
{
XDocument doc = new XDocument(
new XElement("profiles")
);
doc.Save("profiles.xml", SaveOptions.None);
}
And then later I wanna take users input and add profiles into the already created xml file:
XElement profile =
new XElement(Player.Name,
new XElement("level", Player.Level),
new XElement("cash", Player.Cash)
);
XDocument doc = XDocument.Load("profiles.xml");
List<XElement> profiles = doc.Root.Elements().ToList();
for (int i = 0; i < profiles.Count; i++)
{
if (profiles[i].Name.ToString() == Player.name)
{
profiles[i] = profile;
return;
}
}
profile.Add(profile);
doc.Save("profiles.xml", SaveOptions.None);
But for some reason, it will never add any new profiles?
EDIT: Also, if I manually create a new profile into the xml file, it won't customize either, so the problem is within Saving the file?
You're never actually doing anything to change any of the elements within the XDocument that doc refers to:
If you find an element with the existing name, you're modifying the list, but that won't modify the document. You probably want to use XElement.ReplaceWith:
profiles[i].ReplaceWith(profile);
Note that in this case you're not even trying to save the XML file again (due to the return statement), so it's not really clear what you're trying to achieve in this case.
If you don't find the element, you're adding the profile element to itself, which certainly isn't going to modify the document. I suspect you want:
doc.Root.Add(profile);
In other words, add the new profile element as a new final child of the root element.
EDIT: Here's a different approach to try instead - I'm assuming any one name should only occur once:
XDocument doc = XDocument.Load("profiles.xml");
var existingElement = doc.Root
.Elements()
.Where(x => x.Name.ToString() == Player.name)
.FirstOrDefault();
if (existingElement != null)
{
existingElement.ReplaceWith(profile);
}
else
{
doc.Root.Add(profile);
}
doc.Save("profiles.xml", SaveOptions.None);
Also, I would strongly advise you not to use the player name as the element name. Use it as an attribute value or text value instead, e.g.
XElement profile =
new XElement("player",
new XAttribute("name", Player.Name),
new Attribute("level", Player.Level),
new XAttribute("cash", Player.Cash)
);
That way you won't have problems if the player name has spaces etc. You'd then need to change your query to:
var existingElement = doc.Root
.Elements()
.Where(x => (string) x.Attribute("name)" == Player.name)
.FirstOrDefault();

Change XML root element name

I have XML stored in string variable:
<ItemMasterList><ItemMaster><fpartno>xxx</fpartno><frev>000</frev><fac>Default</fac></ItemMaster></ItemMasterList>
Here I want to change XML tag <ItemMasterList> to <Masterlist>. How can I do this?
System.Xml.XmlDocument and the associated classes in that same namespace will prove invaluable to you here.
XmlDocument doc = new XmlDocument();
doc.LoadXml(yourString);
XmlDocument docNew = new XmlDocument();
XmlElement newRoot = docNew.CreateElement("MasterList");
docNew.AppendChild(newRoot);
newRoot.InnerXml = doc.DocumentElement.InnerXml;
String xml = docNew.OuterXml;
I know i am a bit late, but just have to add this answer as no one seems to know about this.
XDocument doc = XDocument.Parse("<ItemMasterList><ItemMaster><fpartno>xxx</fpartno><frev>000</frev><fac>Default</fac></ItemMaster></ItemMasterList>");
doc.Root.Name = "MasterList";
Which returns the following:
<MasterList>
<ItemMaster>
<fpartno>xxx</fpartno>
<frev>000</frev>
<fac>Default</fac>
</ItemMaster>
</MasterList>
You can use LINQ to XML to parse the XML string, create a new root and add the child elements and attributes of the original root to the new root:
XDocument doc = XDocument.Parse("<ItemMasterList>...</ItemMasterList>");
XDocument result = new XDocument(
new XElement("Masterlist", doc.Root.Attributes(), doc.Root.Nodes()));
Using the XmlDocument way, you can do this as follows (and keep the tree intact):
XmlDocument oldDoc = new XmlDocument();
oldDoc.LoadXml("<ItemMasterList><ItemMaster><fpartno>xxx</fpartno><frev>000</frev><fac>Default</fac></ItemMaster></ItemMasterList>");
XmlNode node = oldDoc.SelectSingleNode("ItemMasterList");
XmlDocument newDoc = new XmlDocument();
XmlElement ele = newDoc.CreateElement("MasterList");
ele.InnerXml = node.InnerXml;
If you now use ele.OuterXml is will return: (you you just need the string, otherwise use XmlDocument.AppendChild(ele) and you will be able to use the XmlDocument object some more)
<MasterList>
<ItemMaster>
<fpartno>xxx</fpartno>
<frev>000</frev>
<fac>Default</fac>
</ItemMaster>
</MasterList>
As pointed by Will A, we can do it that way but for case where InnerXml equals the OuterXml the following solution will work out:
// Create a new Xml doc object with root node as "NewRootNode" and
// copy the inner content from old doc object using the LastChild.
XmlDocument docNew = new XmlDocument();
XmlElement newRoot = docNew.CreateElement("NewRootNode");
docNew.AppendChild(newRoot);
// The below line solves the InnerXml equals the OuterXml Problem
newRoot.InnerXml = oldDoc.LastChild.InnerXml;
string xmlText = docNew.OuterXml;

How to use XmlReader class?

I want to save and load my xml data using XmlReader. But I don't know how to use this class. Can you give me a sample code for start?
MSDN has a simple example to get you started here.
If you're interested in reading and writing XML documents, and not just specifically using the XmlReader class, there's a nice article covering a few of your options here.
But if you just want to get started and play around, try this:
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
XmlReader reader = XmlReader.Create("file.xml", settings);
Personally I have switched away from XMLReader to System.XML.Linq.XDocument to manage my XML data files. This way I can easily pull data from xml into objects and manage them like any other object in my program. When I am done manipulating them I can just save the changes back out the the xml file at any time.
//Load my xml document
XDocument myData = XDocument.Load(PhysicalApplicationPath + "/Data.xml");
//Create my new object
HelpItem newitem = new HelpItem();
newitem.Answer = answer;
newitem.Question = question;
newitem.Category = category;
//Find the Parent Node and then add the new item to it.
XElement helpItems = myData.Descendants("HelpItems").First();
helpItems.Add(newitem.XmlHelpItem());
//then save it back out to the file system
myData.Save(PhysicalApplicationPath + "/Data.xml");
If I want to use this data in an easily managed data set I can bind it to a list of my objects.
List<HelpItem> helpitems = (from helpitem in myData.Descendants("HelpItem")
select new HelpItem
{
Category = helpitem.Element("Category").Value,
Question = helpitem.Element("Question").Value,
Answer = helpitem.Element("Answer").Value,
}).ToList<HelpItem>();
Now it can be passed around and manipulated with any inherent functions of my object class.
For convenience my class has a function to create itself as an xml node.
public XElement XmlHelpItem()
{
XElement helpitem = new XElement("HelpItem");
XElement category = new XElement("Category", Category);
XElement question = new XElement("Question", Question);
XElement answer = new XElement("Answer", Answer);
helpitem.Add(category);
helpitem.Add(question);
helpitem.Add(answer);
return helpitem;
}
You should use the Create method instead of using new, since XmlReader is an abstract class using the Factory pattern.
var xmlReader = XmlReader.Create("xmlfile.xml");
From the excellent C# 3.0 in a Nutshell, consider looking at the sample code from chapter 11.

Categories

Resources