XML Deserializing a List<> - c#

I have managed to serialize a list of objects of type Word using XML Serialization:
public static void WriteXML(string fileName)
{
System.Xml.Serialization.XmlSerializer writer =
new System.Xml.Serialization.XmlSerializer(typeof(Word));
System.IO.StreamWriter file = new System.IO.StreamWriter(
fileName);
foreach (var word in Words)
{
writer.Serialize(file, word);
}
file.Close();
}
I have a problem with deserializing this list. Im using this code snippet: http://msdn.microsoft.com/en-us/library/vstudio/ms172872.aspx
I changed my code to something like that:
public static void ReadXML(string fileName)
{
System.Xml.Serialization.XmlSerializer reader =
new System.Xml.Serialization.XmlSerializer(typeof(Word));
System.IO.StreamReader file = new System.IO.StreamReader(
fileName);
foreach (????)
{
Word word=new Word();
word = (Word) reader.Deserialize(file);
Words.Add(word); //Words is a List<Word>
}
}
Of course the foreach() loop is not used properly here. I just have no clue how to do this.

First of all, you should not serialize each word one by one. This would result in a single file containing many xmls, which would of course be invalid.
You want to serialize Words (which is List<Word>) . Therefore your serializer creation should be new XmlSerializer(typeof(List<Word>)) and serialization as writer.Serialize(file, Words);
So your code can be like this:
List<Word> Words = ........
WriteXML("a.xml", Words);
var newWords = ReadXML<List<Word>>("a.xml");
public static void WriteXML(string fileName,object obj)
{
using (var f = File.Create(fileName))
{
XmlSerializer ser = new XmlSerializer(obj.GetType());
ser.Serialize(f, obj);
}
}
public static T ReadXML<T>(string fileName)
{
using (var f = File.Open(fileName,FileMode.Open,FileAccess.Read))
{
XmlSerializer ser = new XmlSerializer(typeof(T));
return (T)ser.Deserialize(f);
}
}
PS: Serializable attribute is required only for BinaryFormatter. XmlSerializer doesn't need it.
You can find the details of the attributes XmlSerializer uses here

How can you serialize individual Word object to same file? This is a kind of overriding the file on each iteration. Simply just serialize the Database object instead of separate Word objects this way:
System.Xml.Serialization.XmlSerializer writer =
new System.Xml.Serialization.XmlSerializer(typeof(Database));
System.IO.StreamWriter file = new System.IO.StreamWriter(fileName);
writer.Serialize(file, yourDatabaseObject);
Note: In addition, make sure that Database is marked with Serializable attribute.

Related

C# append object to xml file using serialization

I am trying append a serialized object to an existing xml file beneath the root element, which I thought would be simple but is proving to be a little challenging.
The problem is in the AddShortcut method but I added some more code for completeness.
I believe what I need to do is:
load the file into an XmlDocument.
navigate to the node I want to append beneath (here the node name is Shortcuts).
create some type of writer and then serialize the object.
save the XmlDocument.
The trouble is in steps 2 and 3. I have tried different variations but I think using XPathNavigator somehow to find the "root" node to append under is a step in the right direction.
I have also looked at almost every question on Stack Overflow on the subject.
Any suggestions welcome. Here is my code
class XmlEngine
{
public string FullPath { get; set; } // the full path to the xmlDocument
private readonly XmlDocument xDoc;
public XmlEngine(string fullPath, string startElement, string[] rElements)
{
FullPath = fullPath;
xDoc = new XmlDocument();
CreateXmlFile(FullPath, startElement, rElements);
}
public void CreateXmlFile(string path, string startElement, string[] rElements)
{
try
{
if (!File.Exists(path))
{
// create a txt writer
XmlTextWriter wtr = new XmlTextWriter(path, System.Text.Encoding.UTF8);
// make sure the file is well formatted
wtr.Formatting = Formatting.Indented;
wtr.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
wtr.WriteStartElement(startElement);
wtr.Close();
// write the top level root elements
writeRootElements(path, rElements);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
Console.WriteLine("Could not create file: " + path);
}
}
public void AddShortcut(Shortcut s)
{
xDoc.Load(FullPath);
rootNode = xDoc.AppendChild(xDoc.CreateElement("Shortcuts"));
var serializer = new XmlSerializer(s.GetType());
using (var writer = new StreamWriter(FullPath, true))
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.OmitXmlDeclaration = true;
serializer.Serialize(writer, s);
}
xDoc.Save(FullPath);
}
}
This code sample worked for me:
xml:
<?xml version="1.0" encoding="UTF-8"?>
<Launchpad>
<Shortcuts>
<Shortcut Id="1">
<Type>Folder</Type>
<FullPath>C:\SomePath</FullPath>
<Name>SomeFolderName</Name>
</Shortcut>
</Shortcuts>
</Launchpad>
Method:
public void AddShortcut(Shortcut s)
{
xDoc.Load(FullPath);
var rootNode = xDoc.GetElementsByTagName("Shortcuts")[0];
var nav = rootNode.CreateNavigator();
var emptyNamepsaces = new XmlSerializerNamespaces(new[] {
XmlQualifiedName.Empty
});
using (var writer = nav.AppendChild())
{
var serializer = new XmlSerializer(s.GetType());
writer.WriteWhitespace("");
serializer.Serialize(writer, s, emptyNamepsaces);
writer.Close();
}
xDoc.Save(FullPath);
}
load the file into an XmlDocument.
navigate to the node I want to append beneath (here the node name is Shortcuts).
create some type of writer and then serialize the object.
save the XmlDocument
So:
public void AddShortcut(Shortcut s)
{
// 1. load existing xml
xDoc.Load(FullPath);
// 2. create an XML node from object
XmlElement node = SerializeToXmlElement(s);
// 3. append that node to Shortcuts node under XML root
var shortcutsNode = xDoc.CreateElement("Shortcuts")
shortcutsNode.AppendChild(node);
xDoc.DocumentElement.AppendChild(shortcutsNode);
// 4. save changes
xDoc.Save(FullPath);
}
public static XmlElement SerializeToXmlElement(object o)
{
XmlDocument doc = new XmlDocument();
using(XmlWriter writer = doc.CreateNavigator().AppendChild())
{
new XmlSerializer(o.GetType()).Serialize(writer, o);
}
return doc.DocumentElement;
}
This post

Convert Multiple XML files into single CSV using XSLT and C#

I'm trying to convert XML file to CSV using XSLT. It works. I was able to convert XML and get the output I want using XSLT.
The challenge I'm facing right now is that I have many many XML files in a single location. I want to get ALL the data from all XMLs and put them into a single CSV file. I have a for loop that goes through the folder and gets the XML files and then exports it to CSV. However, every time it converts new XML it overrides the data in the current CSV file. So the end result is I only get one row in the CSV file instead of 500 (if there's 500 xml files).
Here's the C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.IO;
using System.Diagnostics;
namespace XSL
{
class Program
{
public static void Main(string[] args)
{
try
{
//declaring xmlFile
string xmlfile ;
//Loading the XSLT template
String xsltfile = "C:\\Win\\XMLReader\\XSL\\csv.xsl";
//get folder location
string d = DateTime.Now.ToString("yyyyMMdd");
//Console.WriteLine(d.ToString());
//first part of the location path
String firstPath = #"\\tripx-exportm\\output\\Skill 53115;1_";
//full path
string fullPath = firstPath + d.ToString() + "_000000" + #"\\IDX";
//Get files from a folder
string[] filePath = Directory.GetFiles(fullPath, "*.xml");
//get each file in the folder
foreach (string file in filePath)
{
Console.WriteLine(file);
xmlfile = file;
Transform(xmlfile, xsltfile);
}
//Get the count of XML files in the current folder
DirectoryInfo dir = new DirectoryInfo(fullPath);
int count = dir.GetFiles().Length;
Console.WriteLine("Count of XML files: " + count);
//Transform(xmlfile, xsltfile);
Console.WriteLine("press any key");
Console.ReadKey();
}
catch(Exception e){
Console.WriteLine(e.ToString());
}
}
public static void Transform(string xml, string xslt)
{
try
{
//load the xml doc
XPathDocument myDoc = new XPathDocument(xml);
XslCompiledTransform myXL = new XslCompiledTransform();
//load the xslt doc
myXL.Load(xslt);
//create the output
XmlTextWriter myWriter = new XmlTextWriter("result.csv", null);
myXL.Transform(myDoc, null, myWriter);
myWriter.Close();
}
catch (Exception e)
{
Console.WriteLine("exception : {0}", e.ToString());
};
}
}
}
XSLT file
Any suggestions on how I can put the data from multiple XMLs into a single CSV?
Thank you
You need to use the right constructor overload for the XmTextWriter to take a stream, rather than a file name.
The help on MSDN says this about the file name overload:
The filename to write to. If the file exists, it truncates it and
overwrites it with the new content.
So you should to do this to Transform to make it work:
public static void Transform(Stream stream, string xml, string xslt)
{
var myDoc = new XPathDocument(xml);
var myXL = new XslCompiledTransform();
myXL.Load(xslt);
using(var myWriter = new XmlTextWriter(stream, Encoding.Default))
{
myXL.Transform(myDoc, null, myWriter);
myWriter.Flush();
myWriter.Close();
}
}
Then the calling code should look like this:
using (var fs = new FileStream("result.csv", FileMode.Create))
{
foreach (string file in filePath)
{
Transform(fs, file, xsltfile);
}
fs.Flush();
fs.Close();
}
I've taken out the (bad) exception handling, and comments, etc.
I have come up with a different approach. It may not be the best possible solution, but it works for me.
In my code, I added a counter to count the number of XMLs that were processed. It increments.
In my Transform procedure instead of hard coding the name "result.csv", I generate the name:
string Result = "result" + count.ToString() + ".csv";
And I use that name in the XMLWriter
XmlTextWriter myWriter = new XmlTextWriter(Result, null);
This way it generates a single CSV for every XML and it will not override the existing one.
Then I wrote another procedure that combines all the CVSs into one:
private static void JoinCsvFiles(string[] csvFileNames, string outputDestinationPath)
{
StringBuilder sb = new StringBuilder();
bool columnHeadersRead = false;
foreach (string csvFileName in csvFileNames)
{
TextReader tr = new StreamReader(csvFileName);
string columnHeaders = tr.ReadLine();
// Skip appending column headers if already appended
if (!columnHeadersRead)
{
sb.AppendLine(columnHeaders);
columnHeadersRead = true;
}
sb.AppendLine(tr.ReadToEnd());
}
File.WriteAllText(outputDestinationPath, sb.ToString());
}
And in the main, I just call
string[] csvFileNames = Directory.GetFiles(".", "result*.csv");
JoinCsvFiles(csvFileNames, "CsvOutput.csv");
Hope this helps somebody.

XML Deserialization error

I am currently working with an xml file which usually should contain a list of custom objects (List), but from time to time can simply contain a string node with a message. I have a code which deserializes this file:
private T DeserializeFile<T>(string filePath)
{
StreamReader readFileStream = new StreamReader(#filePath);
var serializerObj = new XmlSerializer(typeof(T));
return (T)serializerObj.Deserialize(readFileStream);
}
This works for List but for simple string throws an error (InvalidOperationException - Root element is missing, or " was not expected."). How can I detect the case when the file contains only the string element and return null from the function?
Basically this is what I want to do:
private T DeserializeFile<T>(string filePath)
{
StreamReader readFileStream = new StreamReader(#filePath);
var serializerObj = new XmlSerializer(typeof(T));
try
{
return (T)serializerObj.Deserialize(readFileStream);
}
catch (Exception ex)
{
return null;
}
}
This should do what you're trying to do. It uses XDocument to load and parse the file, so that it can check if there are any elements before trying to deserialize it.
private T DeserializeFile<T>(string filePath)
{
var xdoc = XDocument.Load(filePath);
if (xdoc.Root.Elements().Any())
{
var serializerObj = new XmlSerializer(typeof(T));
return (T)serializerObj.Deserialize(xdoc.CreateReader());
}
else
return default(T);
}
This assumes that you always have valid XML files, with the only difference being whether the root contains more elements or just text, e.g. a list like
<someRoot>
<someObj>
</someObj>
<someObj>
</someObj>
</someRoot>
And a "string" like
<someRoot>just a string</someRoot>

Deserializing string list with "\n" results in empty string

I have been banging my head on this one for a bit. It seems like it must be a simple solution but I have searched the internet and tried quite a few things.
I have a complex object which includes a string list that needs to be serialized into xml and then deserialized.
The serialization code has long since been part of the application and works in countless other scenarios but the issue here appears to be that one of the elements in the string list is a mere new line character (i.e. "\n").
It is my understanding, based on my research, it is serializing as expected (see below) but after deserialization the element contains an empty string (i.e. "") instead of "\n".
Here is the code...
public DoStuff(ItemTypeObj item)
{
string myItem = XmlSerialize<ItemType>(item);
ItemTypeObj myNewItemTypeObj = XmlDeserialize<CustomItem>(myItem)
}
public static string XmlSerialize<T>(T objectToSerialize)
{
string ret = string.Empty;
XmlSerializer s = new XmlSerializer(typeof(T));
using (MemoryStream ms = new MemoryStream())
{
s.Serialize(ms, objectToSerialize);
ms.Position = 0;
using (StreamReader sr = new StreamReader(ms))
{
sRet = sr.ReadToEnd();
}
}
return ret;
}
public static T XmlDeserialize<T>(string serializedObject)
{
T retVal = default(T);
byte[] ba = ASCIIEncoding.UTF8.GetBytes(serializedObject);
using (MemoryStream ms = new MemoryStream(ba))
{
XmlSerializer s = new XmlSerializer(typeof(T));
retVal = (T)s.Deserialize(ms);
}
return retVal;
}
To give you an idea of the data sent in, ItemTypeObj is the object which includes a string List. The string list can be variable length but sample data could look like this...
[0] = "Zero element text \n"
[1] = "[element1]"
[2] = "\n"
[3] = "[element3]"
[4] = "\n"
[5] = "[element5]"
When serialized it will look like this (which seems correct to me):
<Text>
<string>Zero element text
</string>
<string>[element1]</string>
<string>
</string>
<string>[element3]</string>
<string>
</string>
<string>[element5]</string>
<Text>
From what I've read the newlines are represented as expected in the xml above. The issue is after it is deserialized the string list is this:
[0] = "Zero element text \n"
[1] = "[element1]"
[2] = ""
[3] = "[element3]"
[4] = ""
[5] = "[element5]"
Only the newline characters in the elements that also have text (e.g. [0]) will still exist. The other two are replaced with empty string. If I add text to those elements the new line will be retained.
Looking at the byte array in the deserialization, the array element at the location in the serialized string where the "\n" was turns into a 10 (aka LF, new line). Then that does not successfully get turned into "\n" in the Deserialize. Perhaps that is too much to ask.
Any insight would be most appreciated. Thanks.
You'll need to use the XmlReader and XmlWriter classes or the DataContractSerializer.
See: How to keep XmlSerializer from killing NewLines in Strings?
public static string XmlSerialize<T>(T objectToSerialize)
{
XmlSerializer s = new XmlSerializer(typeof(T));
var settings = new XmlWriterSettings
{
NewLineHandling = NewLineHandling.Entitize
};
using(var stream = new StringWriter())
using(var writer = XmlWriter.Create(stream, settings))
{
s.Serialize(writer, objectToSerialize);
return stream.ToString();
}
}
public static T XmlDeserialize<T>(string serializedObject)
{
XmlSerializer s = new XmlSerializer(typeof(T));
using(var stream = new StringReader(serializedObject))
using(var reader = XmlReader.Create(stream))
{
return (T)s.Deserialize(reader);
}
}
Usage:
public class Foo
{
public string Bar { get; set; }
}
var foo = new Foo { Bar = "\n" };
var result = XmlSerialize(foo);
Console.WriteLine(result);
var newFoo = XmlDeserialize<Foo>(result);
Console.WriteLine(newFoo.Bar);
Debug.Assert(newFoo.Bar == "\n");

Error in my XML?

I have the following code:
public class DeserializeAndCompare
{
public static List<string> IntoXML()
{
List<string> PopList = new List<string>();
XmlSerializer serializer = new XmlSerializer(PopList.GetType());
string k = FileToolBox.position0;
FileStream filestreamer = new FileStream(k.ToString(), FileMode.Open);
PopList = (List<string>)serializer.Deserialize(filestreamer);
filestreamer.Close();
return PopList;
}
}
I keep hitting an error with the line:
PopList = (List)serializer.Deserialize(filestreamer);
The error: InvalidOperationException was unhandled, There is an error in XML document(1,1).
In this line:
FileStream filestreamer = new FileStream(k, FileMode.open);
I am trying to reference the 0th position of an array that holds strings. I'm basically going thru my directory, finding any files with a .xml extension and holding the filename paths in an array.
Here is the code for my array:
public static class FileToolBox
{
public static string position0;
public static void FileSearch()
{
//string position0;
//array holding XML file names
string[] array1 = Directory.GetFiles(#"s:\project", "*.xml");
Array.Sort(array1);
Array.Reverse(array1);
Console.WriteLine("Files:");
foreach (string fileName in array1)
{
Console.WriteLine(fileName);
}
position0 = array1[0];
}
public static string Position0
{
get
{
return position0;
}
set
{
position0 = value;
}
}
}
Am i missing something here? How do i get rid of this error?
Thanks in advance for the help.
Your XML file is not well formed, use a tool like XML Spy, XML notepad or open it up in IE and it will give you the error and the line it is on. You most likely have invalid characters like & somewhere in the file
That error is specifically indicating that the XML file being read is malformed. You should start by posting your XML. Also, try opening the XML in Firefox, because it may point out the problem with the XML as well.
Your xml document is not well formed, you need to open your xml file up and analyze it.
There are multiple xml validators on the web, but here's one from w3schools.
Give this a shot:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace Util
{
/// <summary>
/// Not to be confused with System.Xml.Serialization.XmlSerializer, which this uses internally.
///
/// This will convert the public fields and properties of any object to and from an XML string,
/// unless they are marked with NonSerialized() and XmlIgnore() attributes.
/// </summary>
public class XMLSerializer
{
public static Byte[] GetByteArrayFromEncoding(Encoding encoding, string xmlString)
{
return encoding.GetBytes(xmlString);
}
public static String SerializeToXML<T>(T objectToSerialize)
{
return SerializeToXML(objectToSerialize, Encoding.UTF8);
}
public static String SerializeToXML<T>(T objectToSerialize, Encoding encoding)
{
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings =
new XmlWriterSettings { Encoding = encoding, Indent = true };
using (XmlWriter xmlWriter = XmlWriter.Create(sb, settings))
{
if (xmlWriter != null)
{
new XmlSerializer(typeof (T)).Serialize(xmlWriter, objectToSerialize);
}
}
return sb.ToString();
}
public static void DeserializeFromXML<T>(string xmlString, out T deserializedObject) where T : class
{
DeserializeFromXML(xmlString, new UTF8Encoding(), out deserializedObject);
}
public static void DeserializeFromXML<T>(string xmlString, Encoding encoding, out T deserializedObject) where T : class
{
XmlSerializer xs = new XmlSerializer(typeof(T));
using (MemoryStream memoryStream = new MemoryStream(GetByteArrayFromEncoding(encoding, xmlString)))
{
deserializedObject = xs.Deserialize(memoryStream) as T;
}
}
}
}
public static void Main()
{
List<string> PopList = new List<string>{"asdfasdfasdflj", "asdflkjasdflkjasdf", "bljkzxcoiuv", "qweoiuslfj"};
string xmlString = Util.XMLSerializer.SerializeToXML(PopList);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
string fileName = #"C:\temp\test.xml";
xmlDoc.Save(fileName);
string xmlTextFromFile = File.ReadAllText(fileName);
List<string> ListFromFile;
Util.XMLSerializer.DeserializeFromXML(xmlTextFromFile, Encoding.Unicode, out ListFromFile);
foreach(string s in ListFromFile)
{
Console.WriteLine(s);
}
}
Check the output XML file and see what the encoding is, and compare that to what encoding you're trying to read in. I got caught with this problem before because I use a StringBuilder to output the XML string, which writes in UTF-16, but I was trying to read in as UTF-8. Try using Encoding.Unicode and see if that works for you.
Your code will only work with XML files which have the following structure...
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>Hello</string>
<string>World</string>
</ArrayOfString>

Categories