I need to create a human-readable XML file. XmlWriter seems to be almost perfect for this, but I'd like to insert line breaks or, in general, custom whitespace where I want them. Neither WriteRaw nor WriteWhitespace seem to work between the attributes in an element. The latter looks like it should work (This method is used to manually format your document), but throws InvalidOperationException (Token StartAttribute in state Element Content would result in an invalid XML document) if used in place of the comments below.
Is there a workaround for XmlWriter or a third-party XML library that supports this?
Example code (LINQPad-statements ready):
var sb = new StringBuilder();
var settings = new XmlWriterSettings {
OmitXmlDeclaration = true,
Indent = true,
IndentChars = " ",
};
using (var writer = XmlWriter.Create(sb, settings)) {
writer.WriteStartElement("X");
writer.WriteAttributeString("A", "1");
// write line break
writer.WriteAttributeString("B", "2");
// write line break
writer.WriteAttributeString("C", "3");
writer.WriteEndElement();
}
Console.WriteLine(sb.ToString());
Actual result:
<X A="1" B="2" C="3" />
Desired result (B and C may be not aligned with A - that's fine, /> can be left on the line with C):
<X A="1"
B="2"
C="3"
/>
You still has the background StringBuilder - use it. :
var sb = new StringBuilder();
var settings = new XmlWriterSettings {
OmitXmlDeclaration = true,
Indent = true,
IndentChars = " ",
};
using (var writer = XmlWriter.Create(sb, settings)) {
writer.WriteStartElement("X");
writer.WriteAttributeString("A", "1");
writer.Flush();
sb.Append("\r\n");
writer.WriteAttributeString("B", "2");
writer.Flush();
sb.Append("\r\n");
writer.WriteAttributeString("C", "3");
writer.WriteEndElement();
}
Console.WriteLine(sb.ToString());
Related
I´m trying to build a fraction of an XML document, to be included in a final document.
When using XDocument, I get an exception:
"This operation would create an incorrectly structured document."
because my fragment has multiple "root" nodes.
As the file is to be included in a larger document, my base elements will not end up in the root of the final document.
XDocument constsDocument = new XDocument(
new XComment($" Consts section generated on {DateTime.Now} "),
new XComment($" First group of constants. "),
FirstTextConsts(MyFirstCollection),
new XComment($" Refrigerant constants. "),
SecondTextConsts(MySecondCollection)
);
// To avoid xml file starting with <?xml version="1.0" encoding="utf-8"?> use stringbuilder and StreamWriter.
StringBuilder sb = new StringBuilder();
XmlWriterSettings xws = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
using (XmlWriter xw = XmlWriter.Create(sb, xws))
{
constsDocument.Save(xw);
}
System.IO.StreamWriter file = new System.IO.StreamWriter(_outputFileName);
file.WriteLine(sb.ToString());
file.Close();
EDIT 1:
The two methods called within the creation of the document (in principal):
private static IEnumerable<XElement> FirstTextConsts(IEnumerable<MyClass> collection)
{
return collection.Select(r => new XElement("const",
new XAttribute("name", $"IDENTIFIER_{r.Id}"),
new XAttribute("translatable", "false"),
new XElement("text",
new XAttribute("lang","en"), r.Name)));
}
private static IEnumerable<XElement> SecondTextConsts(IEnumerable<MyClass> collection)
{
foreach (var obj in collection)
{
if (obj.SomeSelector)
yield return new XElement("const",
new XAttribute("name", $"IDENTIFIER_{r.Id}"),
new XAttribute("translatable", "false"),
new XElement("text",
new XAttribute("lang","en"), r.Name)));
if (obj.SomeOtherSelector)
yield return new XElement("const",
new XAttribute("name", $"IDENTIFIER_{r.Id}"),
new XAttribute("translatable", "true")
);
}
}
EDIT 2:
As I really need the flexibility of XDocument to build a multilevel xml document wit helper methods returning IEnumerable on different levels, I decided to add a phony element and remove it again before writing to file:
XDocument constsDocument = new XDocument(
new XElement("root",
new XComment($" Consts section generated on {DateTime.Now} "),
new XComment($" First group of constants. "),
FirstTextConsts(MyFirstCollection),
new XComment($" Refrigerant constants. "),
SecondTextConsts(MySecondCollection)
)
);
Before writing to file i strip the element:
file.WriteLine(sb.ToString().Replace("<root>" + Environment.NewLine, "").Replace(Environment.NewLine + "</root>", ""));
You can't create an XDocument that is invalid (due to the multiple "root" nodes). You therefore need to create a list of nodes, and write those to the document fragment.
var constsDocument = new List<XNode> {
new XComment($" Consts section generated on {DateTime.Now} "),
new XComment($" First group of constants. "),
new XElement("example"),
new XComment($" Refrigerant constants. "),
new XElement("another")
};
// To avoid xml file starting with <?xml version="1.0" encoding="utf-8"?> use stringbuilder and StreamWriter.
StringBuilder sb = new StringBuilder();
XmlWriterSettings xws = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true,
ConformanceLevel = ConformanceLevel.Fragment
};
using (XmlWriter xw = XmlWriter.Create(sb, xws))
{
foreach (var element in constsDocument)
{
element.WriteTo(xw);
}
}
System.IO.StreamWriter file = new System.IO.StreamWriter(_outputPath);
file.WriteLine(sb.ToString());
file.Close();
EDIT: to use a method which returns an IEnumerable<XElement> inside the object initializer for the List, you would have to change the definition slightly and the way you add the other items:
var constsDocument = new List<IEnumerable<XNode>> {
new [] { new XComment($" Consts section generated on {DateTime.Now} ") },
new [] { new XComment($" First group of constants. ") },
FirstTextConsts(MyFirstCollection),
new [] { new XComment($" Refrigerant constants. ") },
SecondTextConsts(MySecondCollection)
);
...
foreach (var element in constsDocument.SelectMany(n => n))
You could declare that you want XML to be just a fragment and switch from XDocument to XMLDocument:
XmlDocument constDocument = new XmlDocument();
constDocument.CreateComment(" Consts section generated on {DateTime.Now} ");
constDocument.CreateComment(" First group of constants. ");
FirstTextConsts(MyFirstCollection); // Need to be adapted
constDocument.CreateComment(" Refrigerant constants. ");
SecondTextConsts(MySecondCollection); // Need to be adapted
XmlDocumentFragment fragDocument = constDocument.CreateDocumentFragment();
fragDocument.InnerXml = "<const>fragment</const><const>fragment2</const>"; // Need to be adapted
StringBuilder sb = new StringBuilder();
XmlWriterSettings xws = new XmlWriterSettings
{
ConformanceLevel = ConformanceLevel.Fragment,
OmitXmlDeclaration = true,
Indent = true
};
using (XmlWriter xw = XmlWriter.Create(sb, xws))
{
fragDocument.WriteContentTo(xw);
}
System.IO.StreamWriter file = new System.IO.StreamWriter(_outputFileName);
file.WriteLine(sb.ToString());
file.Close();
or add aritificial root element:
"<aritificialroot>" + fragment + "</aritificialroot>"
I am writing an app key to an App.Config file programatically using code like below, but I also want to add a blank line for readability inside the App.Config file after writing/saving the key. Is this possible? If so, how? I could not find an answer.
System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Add(appKey, appKeyValue);
// Save the changes in App.config file.
config.Save(ConfigurationSaveMode.Modified);
It is possible, unfortunately it's not pleasant. Consider the following, which does add the whitespace:
var doc = new XmlDocument();
doc.Load(#"app.config");
doc.PreserveWhitespace = true;
var first = doc.DocumentElement.FirstChild;
doc.DocumentElement.InsertBefore(doc.CreateSignificantWhitespace("\r\n\r\n"), first);
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineChars = "\r\n";
using (var writer = XmlWriter.Create(#"app.config", settings))
{
doc.Save(writer);
}
This unfortunately ignores the XmlWriterSettings.Indent setting after the added significant whitespace node and puts all the nodes on a single line. I'm thinking this may be a bug, or else the significant whitespace is opting into an "all-or-nothing" formatting, where you then have to control the complete formatting of the document.
However, if you'd accept a comment node, this works better:
var doc = new XmlDocument();
doc.Load(#"app.config");
doc.PreserveWhitespace = true;
var first = doc.DocumentElement.FirstChild;
doc.DocumentElement.InsertBefore(doc.CreateComment(""), first);
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineChars = "\r\n";
using (var writer = XmlWriter.Create(#"app.config", settings))
{
doc.Save(writer);
}
I use xml serialization to create my xml snippets. each serialization does not create linebreak at the end resulting in open tags following close tags. See example output below where the close tag is followed in same line open tag
how can i force the serialization object to be in a new line?
maxmumleewayinticks=Instrument.MasterInstrument.TickSize*2;
string filename="c:\\temp\\Strategyxmlfile" + DateTime.Now.Ticks + ".xml";
settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
settings.NewLineChars = "\r\n";
settings.NewLineHandling = NewLineHandling.Replace;
settings.OmitXmlDeclaration = true;
settings.CloseOutput = false;
writer= new StreamWriter(filename);
ns.Add("", "");
// write and close the bar
XmlSerializer serializer = new XmlSerializer(typeof( DecisionBar));
w =XmlWriter.Create(writer,settings);
serializer.Serialize(w, decision,ns);
Output:
<DecisionBar EntryOrExit="ENTRY">
<mfe>0.0001</mfe>
<mae>-0.0002</mae>
<bartime>2012-07-25T21:43:00</bartime>
<frequency>1 MINUTES</frequency>
<HH7>true</HH7>
<crossover>true</crossover>
<currentprofitability>0.0001</currentprofitability>
<entryPointLong>1.032</entryPointLong>
<entryPointShort>1.0308</entryPointShort>
<exitStopFull>1.031</exitStopFull>
<exitStopPartial>0</exitStopPartial>
</DecisionBar><DecisionBar>
<mfe>0.0001</mfe>
<mae>-0.0002</mae>
<bartime>2012-07-25T21:44:00</bartime>
<frequency>1 MINUTES</frequency>
<HH7>false</HH7>
<crossover>false</crossover>
<currentprofitability>0.0001</currentprofitability>
<entryPointLong>0</entryPointLong>
<entryPointShort>0</entryPointShort>
<exitStopFull>0</exitStopFull>
<exitStopPartial>0</exitStopPartial>
</DecisionBar>
Take a look here:
try
{
MemberList g = new MemberList("group name");
g.members[0] = new Member("mem 1");
g.members[1] = new Member("mem 2");
g.members[2] = new Member("mem 3");
StringWriter sw = new StringWriter();
XmlTextWriter tw = new XmlTextWriter(sw);
tw.Formatting = Formatting.Indented;
tw.Indentation = 4;
XmlSerializer ser = new XmlSerializer(typeof(MemberList));
ser.Serialize(tw, g);
tw.Close();
sw.Close();
Console.WriteLine(sw.ToString());
}
catch(Exception exc)
{
Console.WriteLine(exc.Message);
}
This will give you the desired results, although it requires some additional work, not just specifying some options on the XmlSerializer.
Edit: There are a bunch of variations. I found this one Googling; you can do the same.
I am looking on Internet how keep the carriage return from XML data but I could not find the answer, so I'm here :)
The objective is to write in a file the content of a XML data. So, if the value of the node contains some "\r\n" data, the soft need to write them in file in order to create new line, but it doesn't write, even with space:preserve.
Here is my test class:
XElement xRootNode = new XElement("DOCS");
XElement xData = null;
//XNamespace ns = XNamespace.Xml;
//XAttribute spacePreserve = new XAttribute(ns+"space", "preserve");
//xRootNode.Add(spacePreserve);
xData = new XElement("DOC");
xData.Add(new XAttribute("FILENAME", "c:\\titi\\prout.txt"));
xData.Add(new XAttribute("MODE", "APPEND"));
xData.Add("Hi my name is Baptiste\r\nI'm a lazy boy");
xRootNode.Add(xData);
bool result = Tools.writeToFile(xRootNode.ToString());
And here is my process class:
try
{
XElement xRootNode = XElement.Parse(xmlInputFiles);
String filename = xRootNode.Element(xNodeDoc).Attribute(xAttributeFilename).Value.ToString();
Boolean mode = false;
try
{
mode = xRootNode.Element(xNodeDoc).Attribute(xWriteMode).Value.ToString().ToUpper().Equals(xWriteModeAppend);
}
catch (Exception e)
{
mode = false;
}
String value = xRootNode.Element(xNodeDoc).Value;
StreamWriter destFile = new StreamWriter(filename, mode, System.Text.Encoding.Unicode);
destFile.Write(value);
destFile.Close();
return true;
}
catch (Exception e)
{
return false;
}
Does anybody have an idea?
If you want to preserve cr lf in element or attribute content when saving a XDocument or XElement you can do that by using certain XmlWriterSettings, namely NewLineHandling to Entitize:
string fileName = "XmlOuputTest1.xml";
string attValue = "Line1.\r\nLine2.";
string elementValue = "Line1.\r\nLine2.\r\nLine3.";
XmlWriterSettings xws = new XmlWriterSettings();
xws.NewLineHandling = NewLineHandling.Entitize;
XDocument doc = new XDocument(new XElement("root",
new XAttribute("test", attValue),
elementValue));
using (XmlWriter xw = XmlWriter.Create(fileName, xws))
{
doc.Save(xw);
}
doc = XDocument.Load(fileName);
Console.WriteLine("att value: {0}; element value: {1}.",
attValue == doc.Root.Attribute("test").Value,
elementValue == doc.Root.Value);
In that example the value are preserved in the round trip of saving and loading as the output of the sample is "att value: True; element value: True."
Heres a useful link I found for parsing an Xml string with carraige returns, line feeds in it.
howto-correctly-parse-using-xelementparse-for-strings-that-contain-newline-character-in
It may help those who are parsing an Xml string.
For those who can't be bothered to click it says use an XmlTextReader instead
XmlTextReader xtr = new XmlTextReader(new StringReader(xml));
XElement items = XElement.Load(xtr);
foreach (string desc in items.Elements("Item").Select(i => (string)i.Attribute("Description")))
{
Console.WriteLine("|{0}|", desc);
}
I'm using XmlTextWriter and its WriteElementString method, for example:
XmlTextWriter writer = new XmlTextWriter("filename.xml", null);
writer.WriteStartElement("User");
writer.WriteElementString("Username", inputUserName);
writer.WriteElementString("Email", inputEmail);
writer.WriteEndElement();
writer.Close();
The expected XML output is:
<User>
<Username>value</Username>
<Email>value</Email>
</User>
However, if for example inputEmail is empty, the result XML I get as as follows:
<User>
<Username>value</Username>
<Email/>
</User>
Whereas I would expect it to be:
<User>
<Username>value</Username>
<Email></Email>
</User>
What am I doing wrong? Is there a way to achieve my expected result in a simple way using XmlTextWriter?
Your output is correct. An element with no content should be written as <tag/>.
You can force the use of the full tag by calling WriteFullEndElement()
writer.WriteStartElement("Email");
writer.WriteString(inputEmail);
writer.WriteFullEndElement();
That will output <Email></Email> when inputEmail is empty.
If you want to do that more than once, you could create an extension method:
public static void WriteFullElementString(this XmlTextWriter writer,
string localName,
string value)
{
writer.WriteStartElement(localName);
writer.WriteString(value);
writer.WriteFullEndElement();
}
Then your code would become:
writer.WriteStartElement("User");
writer.WriteFullElementString("Username", inputUserName);
writer.WriteFullElementString("Email", inputEmail);
writer.WriteEndElement();
It doesn't fail <Tag/> is just a shortcut for <Tag></Tag>
Your code should be:
using (XmlWriter writer = XmlWriter.Create("filename.xml"))
{
writer.WriteStartElement("User");
writer.WriteElementString("Username", inputUserName);
writer.WriteElementString("Email", inputEmail);
writer.WriteEndElement();
}
This avoids resource leaks in case of exceptions, and uses the proper way to create an XmlReader (since .NET 2.0).
Leaving this here in case someone needs it; since none of the answers above solved it for me, or seemed like overkill.
FileStream fs = new FileStream("file.xml", FileMode.Create);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter w = XmlWriter.Create(fs, settings);
w.WriteStartDocument();
w.WriteStartElement("tag1");
w.WriteStartElement("tag2");
w.WriteAttributeString("attr1", "val1");
w.WriteAttributeString("attr2", "val2");
w.WriteFullEndElement();
w.WriteEndElement();
w.WriteEndDocument();
w.Flush();
fs.Close();
The trick was to set the XmlWriterSettings.Indent = true and add it to the XmlWriter.
Edit:
Alternatively you can also use
w.Formatting = Formatting.Indented;
instead of adding an XmlWriterSettings.
Tried solving this with another approach, might need optimization.
public class SerializeConfig<T> where T : class
{
public static string Serialize(T type)
{
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true,
OmitXmlDeclaration = true
};
var sb = new StringBuilder();
var serializer = new XmlSerializer(type.GetType());
using (var writer = XmlWriter.Create(sb, settings))
{
serializer.Serialize(writer, type);
}
return sb.ToString().FixXmlClosingTags();
}
}
internal static class InsertStringExtention
{
public static string FixXmlClosingTags(this string xmlString)
{
var sb = new StringBuilder();
var xmlTags = xmlString.Split('\r');
foreach (var tag in xmlTags)
{
if (tag.Contains("/>"))
{
var tagValue = tag.Replace("<", "").Replace("/>", "").Trim();
var firstPart = tag.Substring(0, tag.IndexOf('<'));
var newTag = $"{firstPart}<{tagValue}></{tagValue}>";
sb.Append(newTag);
}
else
{
sb.Append(tag);
}
}
return sb.ToString();
}
}