I am bulding up an XDocument and serializing it to a UTF8 string with the following code:
string xmlString = "";
using (MemoryStream ms = new MemoryStream())
{
using (XmlWriter xw = new XmlTextWriter(ms, Encoding.UTF8))
{
doc.Save(xw);
xw.Flush();
StreamReader sr = new StreamReader(ms);
ms.Seek(0, SeekOrigin.Begin);
xmlString = sr.ReadToEnd();
}
}
This worked fine.
I then needed to toggle whether or not the declarator was serialized to the string. I changed the code to this:
string xmlString = "";
using (MemoryStream ms = new MemoryStream())
{
XmlWriterSettings settings = new XmlWriterSettings()
{
OmitXmlDeclaration = !root.IncludeDeclarator,
Encoding = Encoding.UTF8
};
using (XmlWriter xw = XmlTextWriter.Create(ms, settings))
{
doc.Save(xw);
xw.Flush();
StreamReader sr = new StreamReader(ms);
ms.Seek(0, SeekOrigin.Begin);
xmlString = sr.ReadToEnd();
}
}
This throws the following exception on doc.Save(xw):
The prefix '' cannot be redefined from
'' to 'my_schema_here' within the same
start element tag.
I am trying to figure out why the XDoc can be saved if the writer is "new"ed up, but not if it is ".Create"d. Any ideas?
Jordon
I fixed this by adding the namespace to the name of the root element in the XDocument. Still, it's strange that this isn't necessary if "new XmlTextWriter()" is used instead of "XmlTextWriter.Create()" or "XmlWriter.Create()".
Jordon
Related
I'm trying to get this:
<ATCWaypointEnd id="KLKP">
</ATCWaypointEnd>
But i get this:
<ATCWaypointEnd id="KLKP"></ATCWaypointEnd>
Here is my Code:
writer.WriteStartElement("ATCWaypointEnd");
writer.WriteStartAttribute("id");
writer.WriteString(ICAO);
writer.WriteFullEndElement();
I would recommend creating a class to represent your XML file and then use serialization. That way you can let the framework create the XML elements and you can easily specify whether it should contain line breaks or not (indentation).
You can also use an external tool to generate your POCO classes, for example: https://xmltocsharp.azurewebsites.net/
Using the piece of xml you provided:
// The class representing the XML file
[XmlRoot(ElementName="ATCWaypointEnd")]
public class ATCWaypointEnd
{
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
}
// The method that will return the object as a XML string
public static string GenerateXml(ATCWaypointEnd obj)
{
string xml;
using (var stream = new MemoryStream())
{
var serializer = new XmlSerializer(typeof(ATCWaypointEnd));
var writer = new XmlTextWriter(stream, encoding);
writer.Formatting = Formatting.Indented; // Here
serializer.Serialize(writer, obj);
xml = encoding.GetString(stream.ToArray());
}
return xml;
}
And then in your code you can use like this:
var obj = new ATCWaypointEnd();
obj.Id = "KLKP";
var xml = GenerateXml(obj);
You can do the following:
writer.WriteStartElement("ATCWaypointEnd");
writer.WriteAttributeString("id", ICAO);
writer.WriteString(System.Environment.NewLine);
writer.WriteFullEndElement();
See below for full code:
Add the following "using" statements:
using System.Xml;
using System.IO;
Option 1:
private void WriteXmlData(string ICAO, string filename)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.NewLineChars = Environment.NewLine;
string dirName = System.IO.Path.GetDirectoryName(filename);
if (!System.IO.Directory.Exists(dirName))
{
System.Diagnostics.Debug.WriteLine("Output folder doesn't exist. Creating.");
//create folder if it doesn't exist
System.IO.Directory.CreateDirectory(dirName);
}
using (XmlWriter writer = XmlWriter.Create(filename, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("ATCWaypointEnd");
writer.WriteAttributeString("id", ICAO);
writer.WriteString(System.Environment.NewLine);
writer.WriteFullEndElement();
writer.WriteEndDocument();
writer.Close();
writer.Dispose();
}
}
Usage:
WriteXmlData("KLKP", #"C:\Temp\test.xml");
Option 2:
private string WriteXmlData(string ICAO)
{
string xmlOutput = string.Empty;
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = false;
settings.NewLineChars = Environment.NewLine;
using (MemoryStream ms = new MemoryStream())
{
using (XmlWriter writer = XmlWriter.Create(ms, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("ATCWaypointEnd");
writer.WriteAttributeString("id", ICAO);
writer.WriteString(System.Environment.NewLine);
writer.WriteFullEndElement();
writer.WriteEndDocument();
writer.Close();
writer.Dispose();
}
//reset position
ms.Position = 0;
using (StreamReader sr = new StreamReader(ms))
{
//put XML into string
xmlOutput = sr.ReadToEnd();
//clean up
sr.Close();
sr.Dispose();
}
//clean up
ms.Close();
ms.Dispose();
}
return xmlOutput;
}
Usage:
string output = WriteXmlData("KLKP");
i want the xml encoding to be:
<?xml version="1.0" encoding="windows-1252"?>
To generate encoding like encoding="windows-1252" I wrote this code.
var myns = OS.xmlns;
using (var stringWriter = new StringWriter())
{
var settings = new XmlWriterSettings
{
Encoding = Encoding.GetEncoding(1252),
OmitXmlDeclaration = false
};
using (var writer = XmlWriter.Create(stringWriter, settings))
{
var ns = new XmlSerializerNamespaces();
ns.Add(string.Empty, myns);
var xmlSerializer = new XmlSerializer(OS.GetType(), myns);
xmlSerializer.Serialize(writer, OS,ns);
}
xmlString= stringWriter.ToString();
}
But I am still not getting my expected encoding what am I missing? Please guide me to generate encoding like encoding="windows-1252"?. What do I need to change in my code?
As long as you output the XML directly to a String (through a StringBuilder or a StringWriter) you'll always get UTF-8 or UTF-16 encondings. This is because strings in .NET are internally represented as Unicode characters.
In order to get the proper encoding you'll have to switch to a binary output, such as a Stream.
Here's a quick example:
var settings = new XmlWriterSettings
{
Encoding = Encoding.GetEncoding(1252)
};
using (var buffer = new MemoryStream())
{
using (var writer = XmlWriter.Create(buffer, settings))
{
writer.WriteRaw("<sample></sample>");
}
buffer.Position = 0;
using (var reader = new StreamReader(buffer))
{
Console.WriteLine(reader.ReadToEnd());
Console.Read();
}
}
Related resources:
C# in Depth: Strings in C# and .NET
I am writing some data to XML file...but when I open it all the values are in a single line...how can write it in readable format?ie each node in new line and indentation?
FileStream fs = new FileStream("myfile.xml", FileMode.Create);
XmlWriter w = XmlWriter.Create(fs);
w.WriteStartDocument();
w.WriteStartElement("myfile");
w.WriteElementString("id", id.Text);
w.WriteElementString("date", dateTimePicker1.Text);
w.WriteElementString("version", ver.Text);
w.WriteEndElement();
w.WriteEndDocument();
w.Flush();
fs.Close();
Use a XmlTextWriter instead of XmlWriter and then set the Indentation properties.
Example
string filename = "MyFile.xml";
using (FileStream fileStream = new FileStream(filename, FileMode.Create))
using (StreamWriter sw = new StreamWriter(fileStream))
using (XmlTextWriter xmlWriter = new XmlTextWriter(sw))
{
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.Indentation = 4;
// ... Write elements
}
Following #jumbo comment, this could also be implemented like in .NET 2.
var filename = "MyFile.xml";
var settings = new XmlWriterSettings()
{
Indent = true,
IndentChars = " "
}
using (var w = XmlWriter.Create(filename, settings))
{
// ... Write elements
}
You need to first create an XmlWriterSettings object that specifies your indentation, then when creating your XmlWriter, pass in the XmlWriterSettings after your path.
Additionally, I use the using block to let C# handle the disposing of my resources so that I don't need to worry about losing any resources on an exception.
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings()
{
Indent = true,
IndentChars = "\t",
NewLineOnAttributes = true
};
using (XmlWriter w= XmlWriter.Create("myfile.xml", xmlWriterSettings))
{
w.WriteStartDocument();
w.WriteStartElement("myfile");
w.WriteElementString("id", id.Text);
w.WriteElementString("date", dateTimePicker1.Text);
w.WriteElementString("version", ver.Text);
w.WriteEndElement();
w.WriteEndDocument();
}
}
Check the Settings property:
w.Settings.Indent = true;
Edit: You can't set it directly:
System.Xml.XmlWriter.Create("path", new System.Xml.XmlWriterSettings())
Set the Formatting Property of the XmlTextWriter:
TextWriter textWriter;
var xmlTextWriter = new XmlTextWriter(textWriter);
xmlTextWriter.Formatting = Formatting.Indented;
Use Settings As follow:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
settings.Indent = true;
using (MemoryStream memoryStream = new MemoryStream())
using (XmlWriter xmlDoc = XmlWriter.Create(memoryStream, settings)){
// logic here..
}
This will get you where you want, though, you don't have to use MemoryStream, the importent part is the settings.
A little VB.NET version using XmlWriter plus XmlWriterSettings direct to file:
Dim oSerializer As New XmlSerializer(GetType(MyType), New XmlRootAttribute("MyRootAttributeName"))
Using oXmlWriter As XmlWriter = XmlWriter.Create("MyFilePath", New XmlWriterSettings With {.Indent = True}) //Default encoding is utf-8
oSerializer.Serialize(oXmlWriter, oInstanceOfMyType)
oXmlWriter.Flush()
End Using
Check the doco for other settings and file overwrite options but this is clean and works as expected in lots of situations.
I have a class that i store in a list.
I serialize it ...
XmlDocument xd = new XmlDocument();
MemoryStream ms = new MemoryStream();
XmlSerializer xm = new XmlSerializer(typeof(List<BugWrapper>));
xm.Serialize(ms, _bugs);
StreamReader sr = new StreamReader(ms);
string str = sr.ReadToEnd();
xd.Load(ms);
I looked into str and found it to be empty, the collection however has an object.
Any ideas into why this happens?
Yes - you're saving to the memory stream, leaving it at the end. You need to "rewind" it with:
ms.Position = 0;
just before you create the StreamReader:
xm.Serialize(ms, _bugs);
ms.Position = 0;
StreamReader sr = new StreamReader(ms);
string str = sr.ReadToEnd();
However, you then need to rewind it again before you load into the XmlDocument unless you remove those last two lines, which I suspect were just for debugging. Just for good measure, let's close the memory stream as well when we're done with it:
using (MemoryStream stream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(List<BugWrapper>));
seralizer.Serialize(stream, _bugs);
stream.Position = 0;
XmlDocument doc = new XmlDocument();
doc.Load(stream);
}
When I build XML up from scratch with XmlDocument, the OuterXml property already has everything nicely indented with line breaks. However, if I call LoadXml on some very "compressed" XML (no line breaks or indention) then the output of OuterXml stays that way. So ...
What is the simplest way to get beautified XML output from an instance of XmlDocument?
Based on the other answers, I looked into XmlTextWriter and came up with the following helper method:
static public string Beautify(this XmlDocument doc)
{
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\r\n",
NewLineHandling = NewLineHandling.Replace
};
using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
doc.Save(writer);
}
return sb.ToString();
}
It's a bit more code than I hoped for, but it works just peachy.
As adapted from Erika Ehrli's blog, this should do it:
XmlDocument doc = new XmlDocument();
doc.LoadXml("<item><name>wrench</name></item>");
// Save the document to a file and auto-indent the output.
using (XmlTextWriter writer = new XmlTextWriter("data.xml", null)) {
writer.Formatting = Formatting.Indented;
doc.Save(writer);
}
Or even easier if you have access to Linq
try
{
RequestPane.Text = System.Xml.Linq.XElement.Parse(RequestPane.Text).ToString();
}
catch (System.Xml.XmlException xex)
{
displayException("Problem with formating text in Request Pane: ", xex);
}
A shorter extension method version
public static string ToIndentedString( this XmlDocument doc )
{
var stringWriter = new StringWriter(new StringBuilder());
var xmlTextWriter = new XmlTextWriter(stringWriter) {Formatting = Formatting.Indented};
doc.Save( xmlTextWriter );
return stringWriter.ToString();
}
If the above Beautify method is being called for an XmlDocument that already contains an XmlProcessingInstruction child node the following exception is thrown:
Cannot write XML declaration.
WriteStartDocument method has already
written it.
This is my modified version of the original one to get rid of the exception:
private static string beautify(
XmlDocument doc)
{
var sb = new StringBuilder();
var settings =
new XmlWriterSettings
{
Indent = true,
IndentChars = #" ",
NewLineChars = Environment.NewLine,
NewLineHandling = NewLineHandling.Replace,
};
using (var writer = XmlWriter.Create(sb, settings))
{
if (doc.ChildNodes[0] is XmlProcessingInstruction)
{
doc.RemoveChild(doc.ChildNodes[0]);
}
doc.Save(writer);
return sb.ToString();
}
}
It works for me now, probably you would need to scan all child nodes for the XmlProcessingInstruction node, not just the first one?
Update April 2015:
Since I had another case where the encoding was wrong, I searched for how to enforce UTF-8 without BOM. I found this blog post and created a function based on it:
private static string beautify(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = "\t",
NewLineChars = Environment.NewLine,
NewLineHandling = NewLineHandling.Replace,
Encoding = new UTF8Encoding(false)
};
using (var ms = new MemoryStream())
using (var writer = XmlWriter.Create(ms, settings))
{
doc.Save(writer);
var xmlString = Encoding.UTF8.GetString(ms.ToArray());
return xmlString;
}
}
XmlTextWriter xw = new XmlTextWriter(writer);
xw.Formatting = Formatting.Indented;
public static string FormatXml(string xml)
{
try
{
var doc = XDocument.Parse(xml);
return doc.ToString();
}
catch (Exception)
{
return xml;
}
}
A simple way is to use:
writer.WriteRaw(space_char);
Like this sample code, this code is what I used to create a tree view like structure using XMLWriter :
private void generateXML(string filename)
{
using (XmlWriter writer = XmlWriter.Create(filename))
{
writer.WriteStartDocument();
//new line
writer.WriteRaw("\n");
writer.WriteStartElement("treeitems");
//new line
writer.WriteRaw("\n");
foreach (RootItem root in roots)
{
//indent
writer.WriteRaw("\t");
writer.WriteStartElement("treeitem");
writer.WriteAttributeString("name", root.name);
writer.WriteAttributeString("uri", root.uri);
writer.WriteAttributeString("fontsize", root.fontsize);
writer.WriteAttributeString("icon", root.icon);
if (root.children.Count != 0)
{
foreach (ChildItem child in children)
{
//indent
writer.WriteRaw("\t");
writer.WriteStartElement("treeitem");
writer.WriteAttributeString("name", child.name);
writer.WriteAttributeString("uri", child.uri);
writer.WriteAttributeString("fontsize", child.fontsize);
writer.WriteAttributeString("icon", child.icon);
writer.WriteEndElement();
//new line
writer.WriteRaw("\n");
}
}
writer.WriteEndElement();
//new line
writer.WriteRaw("\n");
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
This way you can add tab or line breaks in the way you are normally used to, i.e. \t or \n
When implementing the suggestions posted here, I had trouble with the text encoding. It seems the encoding of the XmlWriterSettings is ignored, and always overridden by the encoding of the stream. When using a StringBuilder, this is always the text encoding used internally in C#, namely UTF-16.
So here's a version which supports other encodings as well.
IMPORTANT NOTE: The formatting is completely ignored if your XMLDocument object has its preserveWhitespace property enabled when loading the document. This had me stumped for a while, so make sure not to enable that.
My final code:
public static void SaveFormattedXml(XmlDocument doc, String outputPath, Encoding encoding)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";
settings.NewLineChars = "\r\n";
settings.NewLineHandling = NewLineHandling.Replace;
using (MemoryStream memstream = new MemoryStream())
using (StreamWriter sr = new StreamWriter(memstream, encoding))
using (XmlWriter writer = XmlWriter.Create(sr, settings))
using (FileStream fileWriter = new FileStream(outputPath, FileMode.Create))
{
if (doc.ChildNodes.Count > 0 && doc.ChildNodes[0] is XmlProcessingInstruction)
doc.RemoveChild(doc.ChildNodes[0]);
// save xml to XmlWriter made on encoding-specified text writer
doc.Save(writer);
// Flush the streams (not sure if this is really needed for pure mem operations)
writer.Flush();
// Write the underlying stream of the XmlWriter to file.
fileWriter.Write(memstream.GetBuffer(), 0, (Int32)memstream.Length);
}
}
This will save the formatted xml to disk, with the given text encoding.
If you have a string of XML, rather than a doc ready for use, you can do it this way:
var xmlString = "<xml>...</xml>"; // Your original XML string that needs indenting.
xmlString = this.PrettifyXml(xmlString);
private string PrettifyXml(string xmlString)
{
var prettyXmlString = new StringBuilder();
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
var xmlSettings = new XmlWriterSettings()
{
Indent = true,
IndentChars = " ",
NewLineChars = "\r\n",
NewLineHandling = NewLineHandling.Replace
};
using (XmlWriter writer = XmlWriter.Create(prettyXmlString, xmlSettings))
{
xmlDoc.Save(writer);
}
return prettyXmlString.ToString();
}
A more simplified approach based on the accepted answer:
static public string Beautify(this XmlDocument doc) {
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true
};
using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
doc.Save(writer);
}
return sb.ToString();
}
Setting the new line is not necessary. Indent characters also has the default two spaces so I preferred not to set it as well.
Set PreserveWhitespace to true before Load.
var document = new XmlDocument();
document.PreserveWhitespace = true;
document.Load(filename);