I have a class and i need to save this class into an XML file. Since i have more objects from this class i need every object to be added under the same root.
I start out with the xml file as this:
<?xml version="1.0" encoding="utf-8"?>
<root>
</root>
My class looks like this:
class Save
{
string a;
string b;
List<subClass> L1;
List<subClass> L2;
subClass
{
string c;
double d;
}
}
The xml file after saveing should look like this:
<?xml version="1.0" encoding="utf-8"?>
<root>
<object>
<Element1>a</Element1>
<Element2>b</Element2>
<objectListL1>
<Element3>c</Element3>
<Element4>d</Element4>
</objectListL1>
...
<objectListL2>
<Element3>c</Element3>
<Element4>d</Element4>
</objectListL2>
...
</object>
</root>
Of course objectListL1 and objectListL2 are repeated as often as entries in the List are found. I just want to create a class, fill it with all my data and than do class.Save() and it should add a new object entry to my XMLfile.
I think i found an easy solution:
XmlSerializer serializer = new XmlSerializer(typeof(CLASSNAME));
StreamWriter sw = File.AppendText(FILEPATH);
using (sw)
{
serializer.Serialize(sw, OBJECT);
}
This will create a file FILEPATH and serialize everything into it. SInce it is with "AppendText" it can be used with a list ob onjects!
If appending is not needed, instead of the Streamwriter one can use:
using (TextWriter writer = new StreamWriter(FILEPATH))
Related
I am creating a system that stores vehicle data. When I serialize the data using Xml serialization, I get the correct format as shown in the example below:
<?xml version="1.0"?>
<ArrayOfVehicle xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Vehicle>
<Registration>fake1</Registration>
<Model>123</Model>
<Make>test</Make>
<Year>1999</Year>
<Cost>100</Cost>
</Vehicle>
<Vehicle>
<Registration>fake2</Registration>
<Model>321</Model>
<Make>123</Make>
<Year>2000</Year>
<Cost>321</Cost>
</Vehicle>
</ArrayOfVehicle>
The serialization uses a list of vehicles that have the attributes seen in the Xml file. I am trying to figure out how I can delete a vehicle from the list and serialize it back to the Xml file without breaking the format shown above.
The method that I have tried to use to delete the records from the list and serialize and deserialize the data, but when I remove and item, it breaks the format. This is what the Xml file looks like when I remove an item from the list and serialize it:
fake1 123 test 1999 100
Here is my code for removing an item:
for (int i = Business.VehicleList.Count - 1; i >= 0; i--)
{ //Where Business.VehicleList is my list
if (Business.VehicleList[i].Registration == registration)
{
Business.VehicleList.RemoveAt(i);
Business.Save(); //Method for serialization
}
}
Here is the error it throws when I try to deserialize the data again:
System.InvalidOperationException: 'There is an error in XML document (10, 19). XmlException: There are multiple root elements. Line 10, position 19.'
These are my serialization and deserialization methods:
public static void Retrieve()
{
using (FileStream fileStream = new FileStream("C:\\temp\\data.xml", FileMode.OpenOrCreate))
{
using (var reader = new StreamReader(fileStream))
{
if (fileStream.Length <= 0)
{
return;
}
else
{
XmlSerializer deserializer = new XmlSerializer(typeof(List<Vehicle>),
new XmlRootAttribute("ArrayOfVehicle"));
_vehicleList = (List<Vehicle>)deserializer.Deserialize(reader); //This is where the error is thrown
}
}
}
}
public static void Save()
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Vehicle>));
using (FileStream fileStream = new FileStream("C:\\temp\\data.xml", FileMode.Open))
{
serializer.Serialize(fileStream, VehicleList);
fileStream.Close();
}
}
Any suggestions on how to remove a vehicle from my list without it breaking the Xml file?
Here is the source after I tried deleting an item from the vehicle string
<?xml version="1.0"?>
<ArrayOfVehicle xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Vehicle>
<Registration>123</Registration>
<Model>123</Model>
<Make>23</Make>
<Year>2000</Year>
<Cost>123</Cost>
</Vehicle>
</ArrayOfVehicle><Registration>1321</Registration>
<Model>123123</Model>
<Make>312312</Make>
<Year>2000</Year>
<Cost>321</Cost>
</Vehicle>
</ArrayOfVehicle>
In the Save method, new FileStream("C:\\temp\\data.xml", FileMode.Open) will open the existing file without truncating it. So after you write the new XML data to the file, there will be remnants of the old content if the new content is shorter than the old one.
Changing this to new FileStream("C:\\temp\\data.xml", FileMode.Create) will fix the issue.
I think it's because you are trying to de-serialize a malformed xml. Please first, make sure that your serialization method produces correct xml. The reason may be because of closing the stream inside using statement. And also serializing the list before for-loop finishes.
Try removing fileStream.Close(); and also moving Business.Save(); to outside of for-loop.
Here, I made a fiddle with same conditions and it works.
Im trying to deserialize an array of objects from a XML Document.
The document built in the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element>
.....
</element>
<element>
.....
</element>
</root>
But for some reason Im having lots of problems doing so.
This is my function which I call to deserialize it:
public static CardModel[] Load(string text)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "root";
xRoot.IsNullable = true;
XmlSerializer serializer = new XmlSerializer(typeof(CardModel[]),xRoot);
StringReader reader = new StringReader(text);
CardModel[] o = serializer.Deserialize(reader) as CardModel[];
reader.Close();
return o;
}
And I am not sure if its done correctly or not. Because I know that in json you are unable to deserialize an array and you have to do some sort of "hack".
In the CardModel class (which is the element of the array) i use above the class the tag [XmlRoot("root")]. I have also tried to use [XmlRoot("element")] but still im getting stuck.
Afaik you can't directly deserialize into an array but would need a wrapper class like
[Serializable]
[XMLRoot("root")]
public class Root
{
// This does the magic of treating all "element" items nested under the root
// As part of this array
[XmlArray("element")]
public CardModel[] models;
}
And rather deserilialize into that like
public static CardModel[] Load(string text)
{
// I don't think that you need the attribute overwrite here
var serializer = new XmlSerializer(typeof(Root));
using(var reader = new StringReader(text))
{
var root = (Root) serializer.Deserialize(reader);
return root.models;
}
}
Whenever I run the program the xml file simply adds another parts over the original code rather than replacing it.
Here's my files
XML:
<?xml version="1.0" encoding="utf-8" ?>
<stuff>stuff</stuff>
C#:
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
FileStream f = new FileStream(#"C:\ file path", FileMode.Open);
doc.Load(f);
doc.SelectSingleNode("stuff").InnerText = "hi";
doc.Save(f);
}
Resulting XML:
<?xml version="1.0" encoding="utf-8" ?>
<stuff>stuff</stuff><?xml version="1.0" encoding="utf-8"?>
<stuff>hi</stuff>
I'm not sure why this is happening, but I think it has to do with loading stuff to the stream and assigning it more to the same stream.
Maybe you should do the simple way (that works):
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\Temp\Test.xml");
doc.SelectSingleNode("stuff").InnerText = "hi";
doc.Save(#"C:\Temp\Test.xml");
}
The overload for Save that takes the filename will achieve what you are looking for. It overwrites the existing file.
XmlDocument doc = new XmlDocument();
FileStream f = new FileStream(#"C:\file.xml", FileMode.Open);
doc.Load(f);
f.Close();
doc.SelectSingleNode("stuff").InnerText = "hi";
doc.Save(#"C:\file.xml");
I am using WinForms. I have an XML document that looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<MarcusXMLFile xmlns:Responses="http://www.rewardstrike.com/XMLFile1.xml">
<response>
<greatmood>
<yes>
<replytocommand>
<answer>Yes.</answer>
<answer>Yes, sir.</answer>
<answer>Settings.Default.User</answer>
</replytocommand>
</yes>
</greatmood>
</response>
</MarcusXMLFile>
To read this xml document, I use:
private void Responses()
{
string query = String.Format("http://www.rewardstrike.com/XMLFile1.xml");
XmlDocument Responses = new XmlDocument();
Responses.Load(query);
XmlNode channel = Responses.SelectSingleNode("MarcusXMLFile");
if (QEvent == "yesreplytocommand")
{
XmlNodeList yesreplytocommand = Responses.SelectNodes("MarcusXMLFile/response/greatmood/yes/replytocommand/answer");
foreach (XmlNode ans in yesreplytocommand
.Cast<XmlNode>()
.OrderBy(elem => Guid.NewGuid()))
{
response = ans.InnerText;
}
}
}
and then to display:
QEvent = "yesreplytocommand";
Responses();
Console.WriteLine(response);
My problem is when it gets Settings.Default.User and displays it, I want it to display it as c# code so that it actually gets the value from the application. Right now it is actually displaying "Settings.Default.User". How do I do this?
First, you'll need a way to recognize which of your entries are literals and which are expressions. You could do it by adding an attribute to the XML node:
<?xml version="1.0" encoding="utf-8" ?>
<MarcusXMLFile xmlns:Responses="http://www.rewardstrike.com/XMLFile1.xml">
<response>
<greatmood>
<yes>
<replytocommand>
<answer>Yes.</answer>
<answer>Yes, sir.</answer>
<answer expression="true">DefaultSettings.User</answer>
</replytocommand>
</yes>
</greatmood>
</response>
</MarcusXMLFile>
Based on that you can modify your parsing code to either directly use the value from XML or evaluate it instead:
foreach (XmlNode ans in yesreplytocommand
.Cast<XmlNode>()
.OrderBy(elem => Guid.NewGuid()))
{
var attribute = ans.Attributes["expression"];
if (attribute != null && attribute.Value == "true")
{
Console.WriteLine(Evaluate(ans.InnerText));
}
else
{
Console.WriteLine(ans.InnerText);
}
}
There's still the problem of evaluating that expression. There's no easy built-in way to do that from C#. But you could use Dynamic Expresso. This is how Evaluate method could look like:
public string Evaluate(string expression)
{
var interpreter = new Interpreter();
interpreter.SetVariable("DefaultSettings", Settings.Default);
return interpreter.Eval<string>(expression);
}
As you can see, you'll still have to define the expression variables yourself. For the above to work, you will have to use DefaultSettings.User in your XML instead of Settings.Default.User. I already made that change in my sample XML at the beginning of the answer.
You should take a look at XML Serialization.
Really basic on how it works is that it can convert a struct or a class like this:
struct Foo
{
int bar = 0;
Vector2 obj = new Vector2(10, 50);
}
into this:
<?xml version="1.0" ?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"">
<bar>0</bar>
<obj>
<X>10</X>
<Y>50</Y>
</ojb>
</Foo>
And the other way around.
The methods used to load and save code looks like this:
public static void Save(string filepath, Foo foobject)
{
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
using (Stream stream = File.OpenWrite(filepath))
{
serializer.Serialize(stream, foobject);
}
}
public static Foo Load(string filepath)
{
Foo myFoo;
XmlSerializer serializer = new XmlSerializer(typeof(Foo));
using (Stream stream = File.OpenRead(filepath))
{
myFoo = (Foo)serializer.Deserialize(stream);
}
}
It converts xml code to c# code, and other way around.
It cannot convert methods, but it can convert most properties and classes.
I'm trying to serialize an object as XML and have been using a little tester to experiment with different object behaviors when serializing as XML. I know binary serializers are deep and that XML is shallow. However, it does seem that it tries to serialize a List composed within another object when using XML.
My issue is that I get copied data when I serialize a List. Code and output follow:
class Program
{
static void Main(string[] args)
{
TestSerializer original = new TestSerializer();
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(original.GetType());
x.Serialize(Console.Out, original);
Console.WriteLine("\n\n\n");
using (MemoryStream stream = new MemoryStream())
{
x.Serialize(stream, original);
stream.Seek(0, SeekOrigin.Begin);
TestSerializer copy = x.Deserialize(stream) as TestSerializer;
x.Serialize(Console.Out, copy);
}
Console.ReadLine();
}
}
public class TestSerializer
{
public List<string> words = new List<string>();
public TestSerializer()
{
words.Add("word");
words.Add("anotherword");
}
}
And the corresponding output:
<?xml version="1.0" encoding="IBM437"?>
<TestSerializer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=
"http://www.w3.org/2001/XMLSchema">
<words>
<string>word</string>
<string>anotherword</string>
</words>
</TestSerializer>
<?xml version="1.0" encoding="IBM437"?>
<TestSerializer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=
"http://www.w3.org/2001/XMLSchema">
<words>
<string>word</string>
<string>anotherword</string>
<string>word</string>
<string>anotherword</string>
</words>
</TestSerializer>
As you can see, the list content is doubled up when "original" is serialized, then deserialized to "copy". Is there something I am missing as far this is concerned? It seems like there should not be duplicated data.
Put a breakpoint on the constructor of TestSerializer class. You will notice that it is called e.g. on the following line:
TestSerializer copy = x.Deserialize(stream) as TestSerializer;
So when you deserialize the object following happens
First instance of TestSerializer is created (populates the two values in the list) and it executes the default constructor
Dezerialization adds the items from the stream to the created object (now you have 4 items)