Files
Matt Nadareski d614379cf5 Libraries
This change looks dramatic, but it's just separating out the already-split namespaces into separate top-level folders. In theory, every single one could be built into their own Nuget package. `SabreTools.IO.Meta` builds the normal Nuget package that is used by all other projects and includes all namespaces. `SabreTools.IO` builds to `SabreTools.IO.Common` to avoid overwriting issues on publish.
2026-03-21 13:55:42 -04:00

198 lines
5.4 KiB
C#

using System;
using System.Data;
using System.IO;
using System.Text;
namespace SabreTools.Text.SeparatedValue
{
public class Writer : IDisposable
{
#region Fields
/// <summary>
/// Set if values should be wrapped in quotes
/// </summary>
public bool Quotes { get; set; } = true;
/// <summary>
/// Set what character should be used as a separator
/// </summary>
public char Separator { get; set; } = ',';
/// <summary>
/// Set if field count should be verified from the first row
/// </summary>
public bool VerifyFieldCount { get; set; } = true;
#endregion
#region Private Properties
/// <summary>
/// Internal stream writer
/// </summary>
private readonly StreamWriter _writer;
/// <summary>
/// Internal value if we've written a header before
/// </summary>
private bool _header = false;
/// <summary>
/// Internal value if we've written our first line before
/// </summary>
private bool _firstRow = false;
/// <summary>
/// Internal value to say how many fields should be written
/// </summary>
private int _fields = -1;
#endregion
#region Constructors
/// <summary>
/// Constructor for writing to a file
/// </summary>
public Writer(string filename)
{
_writer = new StreamWriter(filename);
}
/// <summary>
/// Consturctor for writing to a stream
/// </summary>
public Writer(Stream stream, Encoding encoding)
{
#if NET20 || NET35 || NET40
_writer = new StreamWriter(stream, encoding);
#else
_writer = new StreamWriter(stream, encoding, 1024, leaveOpen: true);
#endif
}
/// <summary>
/// Consturctor for writing to a stream writer
/// </summary>
public Writer(StreamWriter streamWriter)
{
_writer = streamWriter;
}
#endregion
/// <summary>
/// Write a header row
/// </summary>
public void WriteHeader(string?[] headers)
{
// If we haven't written anything out, we can write headers
if (!_header && !_firstRow)
WriteValues(headers);
_header = true;
}
/// <summary>
/// Write a value row
/// </summary>
/// <param name="values">Array representing the values</param>
/// <param name="newline">True to append a newline, false otherwise</param>
/// <exception cref="ArgumentException">
/// Thrown if <see cref="Separator"/> is an invalid character.
/// </exception>
/// <exception cref="DataException">
/// Thrown if the underlying stream is not marked as writable.
/// </exception>
public void WriteValues(object?[] values, bool newline = true)
{
// If the writer can't be used, we error
if (!_writer.BaseStream.CanWrite)
throw new DataException(nameof(_writer));
// If the separator character is invalid, we error
if (Separator == default(char))
throw new ArgumentException(nameof(Separator));
// If we have the first row, set the bool and the field count
if (!_firstRow)
{
_firstRow = true;
if (VerifyFieldCount && _fields == -1)
_fields = values.Length;
}
// Get the number of fields to write out
int fieldCount = values.Length;
if (VerifyFieldCount)
fieldCount = Math.Min(fieldCount, _fields);
// Iterate over the fields, writing out each
bool firstField = true;
for (int i = 0; i < fieldCount; i++)
{
var value = values[i];
if (!firstField)
_writer.Write(Separator);
if (Quotes)
_writer.Write("\"");
_writer.Write(value?.ToString() ?? string.Empty);
if (Quotes)
_writer.Write("\"");
firstField = false;
}
// If we need to pad out the number of fields, add empties
if (VerifyFieldCount && values.Length < _fields)
{
for (int i = 0; i < _fields - values.Length; i++)
{
_writer.Write(Separator);
if (Quotes)
_writer.Write("\"\"");
}
}
// Add a newline, if needed
if (newline)
_writer.WriteLine();
}
/// <summary>
/// Write a generic string
/// </summary>
public void WriteString(string value)
{
if (string.IsNullOrEmpty(value))
return;
_writer.Write(value);
}
/// <summary>
/// Flush the underlying writer
/// </summary>
public void Flush()
{
_writer.Flush();
}
#region IDisposable Implementation
/// <summary>
/// Dispose of the underlying writer
/// </summary>
public void Dispose()
{
_writer.Dispose();
}
#endregion
}
}