diff --git a/CHANGELOG.md b/CHANGELOG.md
index e235e6f..4d34d70 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### Added
+ - CsvWriter for creating CSV files + Settings
+ - AppendText method for IcdFile
+ - IcdStreamWriter, a wrapper for a StreamWriter
+ - New XML conversion framework for performance improvements
+### Changed
+ - XmlUtils is now using the improved XML conversion framework
## [5.0.0] - 2018-09-14
### Added
diff --git a/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs b/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs
index 027455d..9b0b95a 100644
--- a/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs
+++ b/ICD.Common.Utils.Tests/Xml/XmlUtilsTest.cs
@@ -90,6 +90,7 @@ namespace ICD.Common.Utils.Tests.Xml
{
paths.Add(args.Path);
nodes.Add(args.Outer);
+ return true;
}
);
diff --git a/ICD.Common.Utils/Csv/CsvWriter.cs b/ICD.Common.Utils/Csv/CsvWriter.cs
new file mode 100644
index 0000000..0911129
--- /dev/null
+++ b/ICD.Common.Utils/Csv/CsvWriter.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Linq;
+using ICD.Common.Properties;
+using ICD.Common.Utils.IO;
+
+namespace ICD.Common.Utils.Csv
+{
+ public sealed class CsvWriter : IDisposable
+ {
+ private const string QUOTATION_MARK = "\"";
+ private const string DOUBLE_QUOTE_MARK = "\"\"";
+
+ private readonly IcdTextWriter m_Writer;
+
+ private readonly string m_Seperator;
+ private readonly string m_LineTerminator;
+ private readonly bool m_AlwaysEscape;
+
+ private bool m_NewLine;
+
+ ///
+ /// Constructor.
+ ///
+ public CsvWriter(IcdTextWriter writer, bool spaceAfterComma, bool alwaysEscape, string newline, params string[] header)
+ {
+ m_NewLine = true;
+ m_Writer = writer;
+ m_Seperator = spaceAfterComma ? ", " : ",";
+ m_AlwaysEscape = alwaysEscape;
+ m_LineTerminator = newline;
+
+ if(header.Any())
+ AppendRow(header);
+ }
+
+ ~CsvWriter()
+ {
+ Dispose();
+ }
+
+ ///
+ /// Calls ToString() for each item and adds the row to the builder.
+ ///
+ ///
+ [PublicAPI]
+ public void AppendRow(params object[] row)
+ {
+ foreach (object value in row)
+ AppendValue(value);
+ AppendNewline();
+ }
+
+ ///
+ /// Adds the row to the builder.
+ ///
+ ///
+ [PublicAPI]
+ public void AppendRow(params string[] row)
+ {
+ foreach (string value in row)
+ AppendValue(value);
+ AppendNewline();
+ }
+
+ ///
+ /// Calls ToString() on the item and adds it to the builder.
+ ///
+ ///
+ [PublicAPI]
+ public void AppendValue(object value)
+ {
+ AppendValue(string.Format("{0}", value));
+ }
+
+ ///
+ /// Adds a value to the builder.
+ ///
+ ///
+ [PublicAPI]
+ public void AppendValue(string value)
+ {
+ if (!m_NewLine)
+ m_Writer.WrappedTextWriter.Write(m_Seperator);
+
+ if (m_AlwaysEscape || value.Contains(","))
+ {
+ value = value.Replace(QUOTATION_MARK, DOUBLE_QUOTE_MARK);
+
+ // Append the value, surrounded by quotes
+ m_Writer.WrappedTextWriter.Write(QUOTATION_MARK);
+ m_Writer.WrappedTextWriter.Write(value);
+ m_Writer.WrappedTextWriter.Write(QUOTATION_MARK);
+ }
+ else
+ {
+ m_Writer.WrappedTextWriter.Write(value);
+ }
+
+ m_NewLine = false;
+ }
+
+ ///
+ /// Adds a New Line To the Builder
+ ///
+ [PublicAPI]
+ public void AppendNewline()
+ {
+ m_Writer.WrappedTextWriter.Write(m_LineTerminator);
+
+ m_NewLine = true;
+ }
+
+ public void Dispose()
+ {
+ m_Writer.Dispose();
+ }
+
+ ///
+ /// Instantiates a new CsvWriter with the properties given in the CsvWriterSettings.
+ ///
+ ///
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public static CsvWriter Create(IcdTextWriter writer, CsvWriterSettings settings, params string[] header)
+ {
+ return new CsvWriter(writer,
+ settings.InsertSpaceAfterComma,
+ settings.AlwaysEscapeEveryValue,
+ settings.NewLineSequence,
+ header);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICD.Common.Utils/Csv/CsvWriterSettings.cs b/ICD.Common.Utils/Csv/CsvWriterSettings.cs
new file mode 100644
index 0000000..249abdf
--- /dev/null
+++ b/ICD.Common.Utils/Csv/CsvWriterSettings.cs
@@ -0,0 +1,46 @@
+using ICD.Common.Properties;
+
+namespace ICD.Common.Utils.Csv
+{
+ public sealed class CsvWriterSettings
+ {
+ private bool m_InsertSpaceAfterComma = true;
+ private bool m_AlwaysEscapeEveryValue = true;
+ private string m_NewLineSequence = IcdEnvironment.NewLine;
+
+ ///
+ /// Gets/Sets whether to insert a space between elements, after the comma
+ /// Defaults to true.
+ ///
+ [PublicAPI]
+ public bool InsertSpaceAfterComma
+ {
+ get { return m_InsertSpaceAfterComma; }
+ set { m_InsertSpaceAfterComma = value; }
+ }
+
+ ///
+ /// Gets/Sets whether to always escape the values.
+ /// If true, values are recorded surrounded by quotes, regardless of if they contain a comma or not. Quotes are escaped.
+ /// If false, values are recorded as the value without quotes, unless escaping is required.
+ /// Defaults to true.
+ ///
+ [PublicAPI]
+ public bool AlwaysEscapeEveryValue
+ {
+ get { return m_AlwaysEscapeEveryValue; }
+ set { m_AlwaysEscapeEveryValue = value; }
+ }
+
+ ///
+ /// Gets/Sets the newline character or characters to deliniate records.
+ /// Defaults to System.NewLine.
+ ///
+ [PublicAPI]
+ public string NewLineSequence
+ {
+ get { return m_NewLineSequence; }
+ set { m_NewLineSequence = value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
index 289cef7..ba191bc 100644
--- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
+++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
@@ -85,6 +85,8 @@
+
+
@@ -109,6 +111,7 @@
+
@@ -195,12 +198,17 @@
+
+
+
+
+
diff --git a/ICD.Common.Utils/IO/IcdFile.cs b/ICD.Common.Utils/IO/IcdFile.cs
index 7e01e98..c31696e 100644
--- a/ICD.Common.Utils/IO/IcdFile.cs
+++ b/ICD.Common.Utils/IO/IcdFile.cs
@@ -94,5 +94,11 @@ namespace ICD.Common.Utils.IO
{
return new IcdFileStream(File.Create(path));
}
+
+ [PublicAPI]
+ public static IcdStreamWriter AppendText(string path)
+ {
+ return new IcdStreamWriter(File.AppendText(path));
+ }
}
}
diff --git a/ICD.Common.Utils/IO/IcdStreamWriter.cs b/ICD.Common.Utils/IO/IcdStreamWriter.cs
new file mode 100644
index 0000000..4d5604a
--- /dev/null
+++ b/ICD.Common.Utils/IO/IcdStreamWriter.cs
@@ -0,0 +1,22 @@
+using System;
+#if SIMPLSHARP
+using Crestron.SimplSharp.CrestronIO;
+#elif STANDARD
+using System.IO;
+#endif
+
+namespace ICD.Common.Utils.IO
+{
+ public sealed class IcdStreamWriter : IcdTextWriter
+ {
+ public StreamWriter WrappedStreamWriter { get { return WrappedTextWriter as StreamWriter; } }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public IcdStreamWriter(StreamWriter baseStreamWriter) : base(baseStreamWriter)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICD.Common.Utils/Xml/AbstractGenericXmlConverter.cs b/ICD.Common.Utils/Xml/AbstractGenericXmlConverter.cs
new file mode 100644
index 0000000..e8fafcf
--- /dev/null
+++ b/ICD.Common.Utils/Xml/AbstractGenericXmlConverter.cs
@@ -0,0 +1,181 @@
+using System;
+#if SIMPLSHARP
+using Crestron.SimplSharp.CrestronXml;
+#else
+using System.Xml;
+#endif
+using ICD.Common.Properties;
+
+namespace ICD.Common.Utils.Xml
+{
+ public abstract class AbstractGenericXmlConverter : AbstractXmlConverter
+ {
+ ///
+ /// Creates a new instance of T.
+ ///
+ ///
+ protected abstract T Instantiate();
+
+ ///
+ /// Writes the XML representation of the object.
+ ///
+ ///
+ ///
+ /// The value.
+ public sealed override void WriteXml(IcdXmlTextWriter writer, string elementName, object value)
+ {
+ if (writer == null)
+ throw new ArgumentNullException("writer");
+
+ if (value == null)
+ {
+ writer.WriteElementString(elementName, null);
+ return;
+ }
+
+ WriteXml(writer, elementName, (T)value);
+ }
+
+ ///
+ /// Writes the XML representation of the object.
+ ///
+ ///
+ ///
+ /// The value.
+ [PublicAPI]
+ public void WriteXml(IcdXmlTextWriter writer, string elementName, T value)
+ {
+ if (writer == null)
+ throw new ArgumentNullException("writer");
+
+ if (value == null)
+ {
+ writer.WriteElementString(elementName, null);
+ return;
+ }
+
+ writer.WriteStartElement(elementName);
+ {
+ WriteAttributes(writer, value);
+ WriteElements(writer, value);
+ }
+ writer.WriteEndElement();
+ }
+
+ ///
+ /// Override to write attributes to the root element.
+ ///
+ ///
+ ///
+ protected virtual void WriteAttributes(IcdXmlTextWriter writer, T value)
+ {
+ }
+
+ ///
+ /// Override to write elements to the writer.
+ ///
+ ///
+ ///
+ protected virtual void WriteElements(IcdXmlTextWriter writer, T value)
+ {
+ }
+
+ ///
+ /// Reads the XML representation of the object.
+ ///
+ /// The XmlReader to read from.
+ ///
+ /// The object value.
+ ///
+ public sealed override object ReadXml(IcdXmlReader reader)
+ {
+ return ReadXmlTyped(reader);
+ }
+
+ ///
+ /// Reads the XML representation of the object.
+ ///
+ /// The XmlReader to read from.
+ ///
+ /// The object value.
+ ///
+ [PublicAPI]
+ public virtual T ReadXmlTyped(IcdXmlReader reader)
+ {
+ // Read into the first node
+ if (reader.NodeType != XmlNodeType.Element && !reader.ReadToNextElement())
+ throw new FormatException();
+
+ T output = default(T);
+ bool instantiated = false;
+
+ while (true)
+ {
+ if (!instantiated)
+ {
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.Element:
+ if (reader.IsEmptyElement)
+ return default(T);
+
+ output = Instantiate();
+ instantiated = true;
+
+ // Read the root attributes
+ while (reader.MoveToNextAttribute())
+ ReadAttribute(reader, output);
+
+ // Read out of the root element
+ if (!reader.Read())
+ throw new FormatException();
+ continue;
+
+ default:
+ // Keep reading until we reach the root element.
+ if (!reader.Read())
+ throw new FormatException();
+ continue;
+ }
+ }
+
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.Element:
+ ReadElement(reader, output);
+ continue;
+
+ case XmlNodeType.EndElement:
+ // Read out of the end element
+ reader.Read();
+ return output;
+
+ default:
+ if (!reader.Read())
+ return output;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Override to handle the current attribute.
+ ///
+ ///
+ ///
+ protected virtual void ReadAttribute(IcdXmlReader reader, T instance)
+ {
+ }
+
+ ///
+ /// Override to handle the current element.
+ ///
+ ///
+ ///
+ protected virtual void ReadElement(IcdXmlReader reader, T instance)
+ {
+ // Skip the element
+ reader.Skip();
+ }
+ }
+}
diff --git a/ICD.Common.Utils/Xml/AbstractXmlConverter.cs b/ICD.Common.Utils/Xml/AbstractXmlConverter.cs
new file mode 100644
index 0000000..23a818c
--- /dev/null
+++ b/ICD.Common.Utils/Xml/AbstractXmlConverter.cs
@@ -0,0 +1,25 @@
+using ICD.Common.Properties;
+
+namespace ICD.Common.Utils.Xml
+{
+ public abstract class AbstractXmlConverter : IXmlConverter
+ {
+ ///
+ /// Writes the XML representation of the object.
+ ///
+ ///
+ ///
+ /// The value.
+ public abstract void WriteXml(IcdXmlTextWriter writer, string elementName, object value);
+
+ ///
+ /// Reads the XML representation of the object.
+ ///
+ /// The XmlReader to read from.
+ ///
+ /// The object value.
+ ///
+ [PublicAPI]
+ public abstract object ReadXml(IcdXmlReader reader);
+ }
+}
diff --git a/ICD.Common.Utils/Xml/DefaultXmlConverter.cs b/ICD.Common.Utils/Xml/DefaultXmlConverter.cs
new file mode 100644
index 0000000..da72eea
--- /dev/null
+++ b/ICD.Common.Utils/Xml/DefaultXmlConverter.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace ICD.Common.Utils.Xml
+{
+ public sealed class DefaultXmlConverter : AbstractXmlConverter
+ {
+ ///
+ /// Writes the XML representation of the object.
+ ///
+ ///
+ ///
+ /// The value.
+ public override void WriteXml(IcdXmlTextWriter writer, string elementName, object value)
+ {
+ string elementString = IcdXmlConvert.ToString(value);
+
+ writer.WriteElementString(elementName, elementString);
+ }
+
+ ///
+ /// Reads the XML representation of the object.
+ ///
+ /// The XmlReader to read from.
+ ///
+ /// The object value.
+ ///
+ public override object ReadXml(IcdXmlReader reader)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/ICD.Common.Utils/Xml/IXmlConverter.cs b/ICD.Common.Utils/Xml/IXmlConverter.cs
new file mode 100644
index 0000000..e695040
--- /dev/null
+++ b/ICD.Common.Utils/Xml/IXmlConverter.cs
@@ -0,0 +1,22 @@
+namespace ICD.Common.Utils.Xml
+{
+ public interface IXmlConverter
+ {
+ ///
+ /// Writes the XML representation of the object.
+ ///
+ ///
+ ///
+ /// The value.
+ void WriteXml(IcdXmlTextWriter writer, string elementName, object value);
+
+ ///
+ /// Reads the XML representation of the object.
+ ///
+ /// The XmlReader to read from.
+ ///
+ /// The object value.
+ ///
+ object ReadXml(IcdXmlReader reader);
+ }
+}
diff --git a/ICD.Common.Utils/Xml/IcdXmlConvert.cs b/ICD.Common.Utils/Xml/IcdXmlConvert.cs
index e9ed1a5..eda85b7 100644
--- a/ICD.Common.Utils/Xml/IcdXmlConvert.cs
+++ b/ICD.Common.Utils/Xml/IcdXmlConvert.cs
@@ -1,4 +1,6 @@
using System;
+using System.Text;
+using ICD.Common.Utils.IO;
#if SIMPLSHARP
using Crestron.SimplSharp.CrestronXml;
#else
@@ -9,6 +11,89 @@ namespace ICD.Common.Utils.Xml
{
public static class IcdXmlConvert
{
+ ///
+ /// Serializes the given instance to an xml string.
+ ///
+ ///
+ ///
+ ///
+ public static string SerializeObject(string elementName, object value)
+ {
+ if (value == null)
+ return ToString(null);
+
+ IXmlConverter converter = XmlConverterAttribute.GetConverterForInstance(value);
+
+ StringBuilder builder = new StringBuilder();
+
+ using (IcdStringWriter stringWriter = new IcdStringWriter(builder))
+ {
+ using (IcdXmlTextWriter writer = new IcdXmlTextWriter(stringWriter))
+ converter.WriteXml(writer, elementName, value);
+ }
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Deserializes the given xml to an instance of the given type.
+ ///
+ ///
+ ///
+ ///
+ public static T DeserializeObject(string xml)
+ {
+ return (T)DeserializeObject(typeof(T), xml);
+ }
+
+ ///
+ /// Deserializes the given xml to an instance of the given type.
+ ///
+ ///
+ ///
+ ///
+ private static object DeserializeObject(Type type, string xml)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+
+ using (IcdXmlReader reader = new IcdXmlReader(xml))
+ return DeserializeObject(type, reader);
+ }
+
+ ///
+ /// Deserializes the current node to an instance of the given type.
+ ///
+ ///
+ ///
+ ///
+ public static T DeserializeObject(IcdXmlReader reader)
+ {
+ if (reader == null)
+ throw new ArgumentNullException("reader");
+
+ return (T)DeserializeObject(typeof(T), reader);
+ }
+
+ ///
+ /// Deserializes the current node to an instance of the given type.
+ ///
+ ///
+ ///
+ ///
+ public static object DeserializeObject(Type type, IcdXmlReader reader)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+
+ if (reader == null)
+ throw new ArgumentNullException("reader");
+
+ IXmlConverter converter = XmlConverterAttribute.GetConverterForType(type);
+
+ return converter.ReadXml(reader);
+ }
+
public static string ToString(int value)
{
return XmlConvert.ToString(value);
diff --git a/ICD.Common.Utils/Xml/XmlConverterAttribute.cs b/ICD.Common.Utils/Xml/XmlConverterAttribute.cs
new file mode 100644
index 0000000..af24969
--- /dev/null
+++ b/ICD.Common.Utils/Xml/XmlConverterAttribute.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using ICD.Common.Utils.Attributes;
+
+namespace ICD.Common.Utils.Xml
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
+ public sealed class XmlConverterAttribute : AbstractIcdAttribute
+ {
+ private static readonly Dictionary s_InstanceTypeToConverter;
+ private static readonly Dictionary s_ConverterTypeToConverter;
+
+ private readonly Type m_ConverterType;
+
+ ///
+ /// Gets the converter type.
+ ///
+ public Type ConverterType { get { return m_ConverterType; } }
+
+ ///
+ /// Static constructor.
+ ///
+ static XmlConverterAttribute()
+ {
+ s_InstanceTypeToConverter = new Dictionary();
+ s_ConverterTypeToConverter = new Dictionary();
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public XmlConverterAttribute(Type converterType)
+ {
+ m_ConverterType = converterType;
+ }
+
+ ///
+ /// Gets the XML converter for the given instance.
+ ///
+ ///
+ ///
+ public static IXmlConverter GetConverterForInstance(object value)
+ {
+ return value == null ? LazyLoadConverter(typeof(DefaultXmlConverter)) : GetConverterForType(value.GetType());
+ }
+
+ ///
+ /// Gets the XML converter for the given type.
+ ///
+ ///
+ ///
+ public static IXmlConverter GetConverterForType(Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type");
+
+ IXmlConverter converter;
+ if (!s_InstanceTypeToConverter.TryGetValue(type, out converter))
+ {
+ XmlConverterAttribute attribute = AttributeUtils.GetClassAttribute(type);
+ Type converterType = attribute == null ? typeof(DefaultXmlConverter) : attribute.ConverterType;
+
+ converter = LazyLoadConverter(converterType);
+ s_InstanceTypeToConverter[type] = converter;
+ }
+
+ return converter;
+ }
+
+ ///
+ /// Lazy-loads the converter of the given type.
+ ///
+ ///
+ ///
+ private static IXmlConverter LazyLoadConverter(Type converterType)
+ {
+ if (converterType == null)
+ throw new ArgumentNullException("converterType");
+
+ IXmlConverter converter;
+ if (!s_ConverterTypeToConverter.TryGetValue(converterType, out converter))
+ {
+ converter = ReflectionUtils.CreateInstance(converterType) as IXmlConverter;
+ s_ConverterTypeToConverter[converterType] = converter;
+ }
+
+ return converter;
+ }
+ }
+}
diff --git a/ICD.Common.Utils/Xml/XmlReaderExtensions.cs b/ICD.Common.Utils/Xml/XmlReaderExtensions.cs
index 613a9c6..e7bd60f 100644
--- a/ICD.Common.Utils/Xml/XmlReaderExtensions.cs
+++ b/ICD.Common.Utils/Xml/XmlReaderExtensions.cs
@@ -115,7 +115,7 @@ namespace ICD.Common.Utils.Xml
///
///
[PublicAPI]
- public static void Recurse(this IcdXmlReader extends, Action callback)
+ public static void Recurse(this IcdXmlReader extends, Func callback)
{
if (extends == null)
throw new ArgumentNullException("extends");
diff --git a/ICD.Common.Utils/Xml/XmlUtils.cs b/ICD.Common.Utils/Xml/XmlUtils.cs
index 226bc1a..0f559f4 100644
--- a/ICD.Common.Utils/Xml/XmlUtils.cs
+++ b/ICD.Common.Utils/Xml/XmlUtils.cs
@@ -138,7 +138,7 @@ namespace ICD.Common.Utils.Xml
///
///
[PublicAPI]
- public static void Recurse(string xml, Action callback)
+ public static void Recurse(string xml, Func callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
@@ -152,7 +152,7 @@ namespace ICD.Common.Utils.Xml
///
///
///
- private static void Recurse(string xml, Stack path, Action callback)
+ private static void Recurse(string xml, Stack path, Func callback)
{
if (path == null)
throw new ArgumentNullException("path");
@@ -168,10 +168,11 @@ namespace ICD.Common.Utils.Xml
path.Push(childReader.Name);
string[] pathOutput = path.Reverse().ToArray(path.Count);
- callback(new XmlRecursionEventArgs(xml, pathOutput));
-
- foreach (string child in childReader.GetChildElementsAsString())
- Recurse(child, path, callback);
+ if (callback(new XmlRecursionEventArgs(xml, pathOutput)))
+ {
+ foreach (string child in childReader.GetChildElementsAsString())
+ Recurse(child, path, callback);
+ }
path.Pop();
}