Saving XML file to disk - c#

I'm trying to save an XDocument object to disk, as an .xml file. The object seems well formed in the debugger, I'm only having an issue saving the actual file. Here is what I'm doing :
//ofx is an `XElement` containing many sub-elements of `XElement` type
XDocument document = new XDocument(ofx);
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
//destinationPath is a string containing a path, like this
// e.g : "C:\\Users\\Myself\\Desktop"
using (var stream = File.Create(destionationPath + #"export.xml"))
{
List<byte[]> header = new List<byte[]>();
byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine);
List<string> headers = new List<string>();
headers.Add("foo");
// Just adding some headers here on the top of the file.
// I'm pretty sure this is irrelevant for my problem
foreach (string item in headers)
{
header.Add(Encoding.ASCII.GetBytes(item));
header.Add(newline);
}
header.Add(newline);
byte[] bytes = header.SelectMany(a => a).ToArray();
stream.Write(bytes, 0, bytes.Length);
using (var writer = XmlWriter.Create(stream, settings))
{
document.Save(writer);
}
}
Whenever I call this, I have no file in the target directory.
Notes : The directory is well-formed too, as well as the file name. This is verified in the code.

Probably you're creating the path badly. Following the comment it you have
string destionationPath = "C:\\Users\\Myself\\Desktop";
destionationPath + #"export.xml" // evaluates to: "C:\\Users\\Myself\\Desktopexport.xml"
You are missing "\\" between directory and file name.
The following code works fine for me.
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
Tmp();
}
public static void Tmp()
{
//ofx is an `XElement` containing many sub-elements of `XElement` type
XDocument document = new XDocument(new XElement("myName", "myContent"));
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
//destinationPath is a string containing a path, like this
// e.g : "C:\\Users\\Myself\\Desktop"
string destionationPath = "D:\\Temp\\xml_test\\";
using (var stream = File.Create(destionationPath + #"export.xml"))
{
List<byte[]> header = new List<byte[]>();
byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine);
List<string> headers = new List<string>();
headers.Add("foo");
// Just adding some headers here on the top of the file.
// I'm pretty sure this is irrelevant for my problem
foreach (string item in headers)
{
header.Add(Encoding.ASCII.GetBytes(item));
header.Add(newline);
}
header.Add(newline);
byte[] bytes = header.SelectMany(a => a).ToArray();
stream.Write(bytes, 0, bytes.Length);
using (var writer = XmlWriter.Create(stream, settings))
{
document.Save(writer);
}
}
}
}
}
To avoid such situations never create create path by yoursef. Use Path class instead:
Path.Combine(destionationPath, #"export.xml")

Related

Serializing class structure to XML seems to add a NewLine character

The code below serializes XML into a string, then writes it to an XML file (yes quite a bit going on with respect to UTF8 and removal of the Namespace):
var bidsXml = string.Empty;
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
activity = $"Serialize Class INFO to XML to string";
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
{
XmlSerializer xml = new XmlSerializer(info.GetType());
xml.Serialize(writer, info, emptyNamespaces);
bidsXml = Encoding.UTF8.GetString(stream.ToArray());
}
var lastChar = bidsXml.Substring(bidsXml.Length);
var fileName = $"CostOffer_Testing_{DateTime.Now:yyyy.MM.dd_HH.mm.ss}.xml";
var path = $"c:\\temp\\pjm\\{fileName}";
File.WriteAllText(path, bidsXml);
Problem is, serialization to XML seems to introduce a CR/LF (NewLine):
It's easier to see in the XML file:
A workaround is to strip out the "last" character:
bidsXml = bidsXml.Substring(0,bidsXml.Length - 1);
But better is to understand the root cause and resolve without a workaround - any idea why this a NewLine characters is being appended to the XML string?
** EDIT **
I was able to attempt a load into the consumer application (prior to this attempt I used an API to import the XML), and I received a more telling message:
The file you are loading is a binary file, the contents can not be displayed here.
So i suspect an unprintable characters is somehow getting embedded into the file/XML. When I open the file in Notepad++, I see the following (UFF-8-Byte Order Mark) - at least I have something to go on:
So it seems the consumer of my XML does not want BOM (Byte Order Mark) within the stream.
Visiting this site UTF-8 BOM adventures in C#
I've updated my code to use new UTF8Encoding(false)) rather than Encoding.UTF8:
var utf8NoBOM = new UTF8Encoding(false);
var bidsXml = string.Empty;
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
activity = $"Serialize Class INFO to XML to string";
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream, utf8NoBOM))
{
XmlSerializer xml = new XmlSerializer(info.GetType());
xml.Serialize(writer, info, emptyNamespaces);
bidsXml = utf8NoBOM.GetString(stream.ToArray());
}
var fileName = $"CostOffer_Testing_{DateTime.Now:yyyy.MM.dd_HH.mm.ss}.xml";
var path = $"c:\\temp\\pjm\\{fileName}";
File.WriteAllText(path, bidsXml, utf8NoBOM);

Save XML file without formatting

I have a XML file that needs to be saved without formatting, without identation and line breaks. I'm doing it this way:
using (var writer = System.IO.File.CreateText("E:\\nfse.xml"))
{
var doc = new XmlDocument { PreserveWhitespace = false };
doc.Load("E:\\notafinal.xml");
writer.WriteLine(doc.InnerXml);
writer.Flush();
}
But that way I need to create the file, and then I need to change it 3 times, so in the end there are a total of 4 files, the initial one and the result of the 3 changes.
When I save the file, I do it this way:
MemoryStream stream = stringToStream(soapEnvelope);
webRequest.ContentLength = stream.Length;
Stream requestStream = webRequest.GetRequestStream();
stream.WriteTo(requestStream);
document.LoadXml(soapEnvelope);
document.PreserveWhitespace = false;
document.Save(#"E:\\notafinal.xml");
How can I do this without having to create a new document?
If what you want is to eliminate extra space by not formatting the XML file, you could use XmlWriterSettings and XmlWriter, like this:
public void SaveXmlDocToFile(XmlDocument xmlDoc,
string outputFileName,
bool formatXmlFile = false)
{
var settings = new XmlWriterSettings();
if (formatXmlFile)
{
settings.Indent = true;
}
else
{
settings.Indent = false;
settings.NewLineChars = String.Empty;
}
using (var writer = XmlWriter.Create(outputFileName, settings))
xmlDoc.Save(writer);
}
Passing formatXmlFile = false in the parameters will save the XML file without formatting it.

Writing to Filestream and copying to MemoryStream

I want to overwrite or create an xml file on disk, and return the xml from the function. I figured I could do this by copying from FileStream to MemoryStream. But I end up appending a new xml document to the same file, instead of creating a new file each time.
What am I doing wrong? If I remove the copying, everything works fine.
public static string CreateAndSave(IEnumerable<OrderPage> orderPages, string filePath)
{
if (orderPages == null || !orderPages.Any())
{
return string.Empty;
}
var xmlBuilder = new StringBuilder();
var writerSettings = new XmlWriterSettings
{
Indent = true,
Encoding = Encoding.GetEncoding("ISO-8859-1"),
CheckCharacters = false,
ConformanceLevel = ConformanceLevel.Document
};
using (var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
try
{
XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
xmlWriter.WriteStartElement("PRINT_JOB");
WriteXmlAttribute(xmlWriter, "TYPE", "Order Confirmations");
foreach (var page in orderPages)
{
xmlWriter.WriteStartElement("PAGE");
WriteXmlAttribute(xmlWriter, "FORM_TYPE", page.OrderType);
var outBound = page.Orders.SingleOrDefault(x => x.FlightInfo.Direction == FlightDirection.Outbound);
var homeBound = page.Orders.SingleOrDefault(x => x.FlightInfo.Direction == FlightDirection.Homebound);
WriteXmlOrder(xmlWriter, outBound, page.ContailDetails, page.UserId, page.PrintType, FlightDirection.Outbound);
WriteXmlOrder(xmlWriter, homeBound, page.ContailDetails, page.UserId, page.PrintType, FlightDirection.Homebound);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteFullEndElement();
MemoryStream destination = new MemoryStream();
fs.CopyTo(destination);
Log.Progress("Xml string length: {0}", destination.Length);
xmlBuilder.Append(Encoding.UTF8.GetString(destination.ToArray()));
destination.Flush();
destination.Close();
xmlWriter.Flush();
xmlWriter.Close();
}
catch (Exception ex)
{
Log.Warning(ex, "Unhandled exception occured during create of xml. {0}", ex.Message);
throw;
}
fs.Flush();
fs.Close();
}
return xmlBuilder.ToString();
}
Cheers
Jens
FileMode.OpenOrCreate is causing the file contents to be overwritten without shortening, leaving any 'trailing' data from previous runs. If FileMode.Create is used the file will be truncated first. However, to read back the contents you just wrote you will need to use Seek to reset the file pointer.
Also, flush the XmlWriter before copying from the underlying stream.
See also the question Simultaneous Read Write a file in C Sharp (3817477).
The following test program seems to do what you want (less your own logging and Order details).
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Threading.Tasks;
namespace ReadWriteTest
{
class Program
{
static void Main(string[] args)
{
string filePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"Test.xml");
string result = CreateAndSave(new string[] { "Hello", "World", "!" }, filePath);
Console.WriteLine("============== FIRST PASS ==============");
Console.WriteLine(result);
result = CreateAndSave(new string[] { "Hello", "World", "AGAIN", "!" }, filePath);
Console.WriteLine("============== SECOND PASS ==============");
Console.WriteLine(result);
Console.ReadLine();
}
public static string CreateAndSave(IEnumerable<string> orderPages, string filePath)
{
if (orderPages == null || !orderPages.Any())
{
return string.Empty;
}
var xmlBuilder = new StringBuilder();
var writerSettings = new XmlWriterSettings
{
Indent = true,
Encoding = Encoding.GetEncoding("ISO-8859-1"),
CheckCharacters = false,
ConformanceLevel = ConformanceLevel.Document
};
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
try
{
XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
xmlWriter.WriteStartElement("PRINT_JOB");
foreach (var page in orderPages)
{
xmlWriter.WriteElementString("PAGE", page);
}
xmlWriter.WriteFullEndElement();
xmlWriter.Flush(); // Flush from xmlWriter to fs
xmlWriter.Close();
fs.Seek(0, SeekOrigin.Begin); // Go back to read from the begining
MemoryStream destination = new MemoryStream();
fs.CopyTo(destination);
xmlBuilder.Append(Encoding.UTF8.GetString(destination.ToArray()));
destination.Flush();
destination.Close();
}
catch (Exception ex)
{
throw;
}
fs.Flush();
fs.Close();
}
return xmlBuilder.ToString();
}
}
}
For the optimizers out there, the StringBuilder was unnecessary because the string is formed whole and the MemoryStream can be avoided by just wrapping fs in a StreamReader. This would make the code as follows.
public static string CreateAndSave(IEnumerable<string> orderPages, string filePath)
{
if (orderPages == null || !orderPages.Any())
{
return string.Empty;
}
string result;
var writerSettings = new XmlWriterSettings
{
Indent = true,
Encoding = Encoding.GetEncoding("ISO-8859-1"),
CheckCharacters = false,
ConformanceLevel = ConformanceLevel.Document
};
using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
try
{
XmlWriter xmlWriter = XmlWriter.Create(fs, writerSettings);
xmlWriter.WriteStartElement("PRINT_JOB");
foreach (var page in orderPages)
{
xmlWriter.WriteElementString("PAGE", page);
}
xmlWriter.WriteFullEndElement();
xmlWriter.Close(); // Flush from xmlWriter to fs
fs.Seek(0, SeekOrigin.Begin); // Go back to read from the begining
var reader = new StreamReader(fs, writerSettings.Encoding);
result = reader.ReadToEnd();
// reader.Close(); // This would just flush/close fs early(which would be OK)
}
catch (Exception ex)
{
throw;
}
}
return result;
}
I know I'm late, but there seems to be a simpler solution. You want your function to generate xml, write it to a file and return the generated xml. Apparently allocating a string cannot be avoided (because you want it to be returned), same for writing to a file. But reading from a file (as in your and SensorSmith's solutions) can easily be avoided by simply "swapping" the operations - generate xml string and write it to a file. Like this:
var output = new StringBuilder();
var writerSettings = new XmlWriterSettings { /* your settings ... */ };
using (var xmlWriter = XmlWriter.Create(output, writerSettings))
{
// Your xml generation code using the writer
// ...
// You don't need to flush the writer, it will be done automatically
}
// Here the output variable contains the xml, let's take it...
var xml = output.ToString();
// write it to a file...
File.WriteAllText(filePath, xml);
// and we are done :-)
return xml;
IMPORTANT UPDATE: It turns out that the XmlWriter.Create(StringBuider, XmlWriterSettings) overload ignores the Encoding from the settings and always uses "utf-16", so don't use this method if you need other encoding.

Simultaneously writing and validating XML

I have a Write method that serializes objects which use XmlAttributes. It's pretty standard like so:
private bool WriteXml(DirectoryInfo dir)
{
var xml = new XmlSerializer(typeof (Composite));
_filename = Path.Combine(dir.FullName, _composite.Symbol + ".xml");
using (var xmlFile = File.Create(_filename))
{
xml.Serialize(xmlFile, _composite);
}
return true;
}
Apart from trying to read the file I have just written out (with a Schema validator), can I perform XSD validation WHILE the XML is being written?
I can mess around with memory streams before writing it to disk, but it seems in .Net there is usually an elegant way of solving most problems.
The way I've done it is like this for anyone interested:
public Composite Read(Stream stream)
{
_errors = null;
var settings = new XmlReaderSettings();
using (var fileStream = File.OpenRead(XmlComponentsXsd))
{
using (var schemaReader = new XmlTextReader(fileStream))
{
settings.Schemas.Add(null, schemaReader);
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += OnValidationEventHandler;
using (var xmlReader = XmlReader.Create(stream, settings))
{
var serialiser = new XmlSerializer(typeof (Composite));
return (Composite) serialiser.Deserialize(xmlReader);
}
}
}
}
private ValidationEventArgs _errors = null;
private void OnValidationEventHandler(object sender, ValidationEventArgs validationEventArgs)
{
_errors = validationEventArgs;
}
Then instead of writing the XML to file, using a memory stream do something like:
private bool WriteXml(DirectoryInfo dir)
{
var xml = new XmlSerializer(typeof (Composite));
var filename = Path.Combine(dir.FullName, _composite.Symbol + ".xml");
// first write it to memory
var memStream = new MemoryStream();
xml.Serialize(memStream, _composite);
memStream.Position = 0;
Read(memStream);
if (_errors != null)
{
throw new Exception(string.Format("Error writing to {0}. XSD validation failed : {1}", filename, _errors.Message));
}
memStream.Position = 0;
using (var outFile = File.OpenWrite(filename))
{
memStream.CopyTo(outFile);
}
memStream.Dispose();
return true;
}
That way you're always validating against the schema before anything is written to disk.

Using XmlDiffPatch when writing to stream

I am trying to use xmldiffpatch to write to a stream.
The first method is to write my xml to a memory stream.
The second method loads an xml from a file and creates a stream for the patched file to be written into. The third method actually compares the two files. I'm always getting that both files are identical, even though they are not, so I know that I am missing something.
Any help is appreciated!
public MemoryStream FirstXml()
{
string[] names = { "John", "Mohammed", "Marc", "Tamara", "joy" };
MemoryStream ms = new MemoryStream();
XmlTextWriter xtw= new XmlTextWriter(ms, Encoding.UTF8);
xtw.WriteStartDocument();
xtw.WriteStartElement("root");
foreach (string s in names)
{
xtw.WriteStartElement(s);
xtw.WriteEndElement();
}
xtw.WriteEndElement();
xtw.WriteEndDocument();
return ms;
}
public Stream SecondXml()
{
XmlReader finalFile =XmlReader.Create(#"c:\......\something.xml");
MemoryStream ms = FirstXml();
XmlReader originalFile = XmlReader.Create(ms);
MemoryStream ms2 = new MemoryStream();
XmlTextWriter dgw = new XmlTextWriter(ms2, Encoding.UTF8);
GenerateDiffGram(originalFile, finalFile, dgw);
return ms2;
}
public void GenerateDiffGram(XmlReader originalFile, XmlReader finalFile,
XmlWriter dgw)
{
XmlDiff xmldiff = new XmlDiff();
bool bIdentical = xmldiff.Compare(originalFile, finalFile, dgw);
dgw.Close();
StreamReader sr = new StreamReader(SecondXml());
string xmlOutput = sr.ReadToEnd();
if(xmlOutput.Contains("</xd:xmldiff>"))
{Console.WriteLine("Xml files are not identical");
Console.Read();}
else
{Console.WriteLine("Xml files are identical");Console.Read();}
}
The following modified version works.
static void Main()
{
SecondXml();
}
public static string FirstXml()
{
string[] names = { "John", "Mohammed", "Marc", "Tamara", "joy" };
var sw = new StringWriter();
var xtw = new XmlTextWriter(sw);
xtw.WriteStartDocument();
xtw.WriteStartElement("root");
foreach (string s in names)
{
xtw.WriteStartElement(s);
xtw.WriteEndElement();
}
xtw.WriteEndElement();
xtw.WriteEndDocument();
return sw.ToString();
}
public static void SecondXml()
{
string secondXml = File.ReadAllText(#"t:\something.xml");
string firstXml = FirstXml();
Console.WriteLine("Comparing...");
string result = GenerateDiffGram(firstXml, secondXml);
Console.WriteLine(result);
Console.WriteLine();
Console.WriteLine("Finished compare");
Console.Out.Write(firstXml);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(secondXml);
}
public static string GenerateDiffGram(string originalFile, string finalFile)
{
var xmldiff = new XmlDiff();
var r1 = XmlReader.Create(new StringReader(originalFile));
var r2 = XmlReader.Create(new StringReader(finalFile));
var sw = new StringWriter();
var xw = new XmlTextWriter(sw) {Formatting = Formatting.Indented};
bool bIdentical = xmldiff.Compare(r1, r2, xw);
Console.WriteLine();
Console.WriteLine("bIdentical: " + bIdentical);
return sw.ToString();
}
I'm actually not entirely sure what's wrong with your original code. The XML being compared is an empty string in both the first and second readers. Since you're using memory streams as the backing stores anyways, then you won't lose anything by just using strings as the above does.

Categories