Files
NetUnicodeInfo/UnicodeInformation.Builder/UnicodeDataFileReader.cs
T
2014-11-08 22:00:12 +01:00

270 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public class UnicodeDataFileReader : IDisposable
{
private struct Utf8Buffer
{
private readonly UnicodeDataFileReader reader;
private int length;
public Utf8Buffer(UnicodeDataFileReader reader)
{
this.reader = reader;
this.length = 0;
}
public int Length { get { return length; } }
private void EnsureExtraCapacity(int count)
{
if (count < 0) throw new ArgumentOutOfRangeException("requiredExtraCapacity");
if (reader.utf8StringBuffer.Length < checked(length + count))
Array.Resize(ref reader.utf8StringBuffer, Math.Max(length + count, reader.utf8StringBuffer.Length << 1));
}
public void Append(byte[] value, int startIndex, int count)
{
if (value == null) throw new ArgumentNullException("value");
if (startIndex >= value.Length) throw new ArgumentOutOfRangeException("startIndex");
if (checked(count += startIndex) > value.Length) throw new ArgumentOutOfRangeException("count");
EnsureExtraCapacity(value.Length);
var buffer = reader.utf8StringBuffer;
for (int i = startIndex; i < count; ++i)
{
buffer[length++] = value[i];
}
}
public override string ToString()
{
return length > 0 ? Encoding.UTF8.GetString(reader.utf8StringBuffer, 0, length) : string.Empty;
}
public string ToTrimmedString()
{
if (length == 0) return string.Empty;
var buffer = reader.utf8StringBuffer;
int start = 0;
int end = length;
while (buffer[start] == ' ') if (++start == length) return string.Empty;
while (buffer[--end] == ' ') ;
return length > 0 ? Encoding.UTF8.GetString(reader.utf8StringBuffer, start, end - start + 1) : string.Empty;
}
}
private readonly Stream stream;
private readonly byte[] byteBuffer;
private byte[] utf8StringBuffer;
private int index;
private int length;
private bool hasField = false;
private readonly bool leaveOpen;
public UnicodeDataFileReader(Stream stream)
: this(stream, false)
{
}
public UnicodeDataFileReader(Stream stream, bool leaveOpen)
{
this.stream = stream;
this.byteBuffer = new byte[8192];
this.utf8StringBuffer = new byte[100];
this.leaveOpen = leaveOpen;
}
public void Dispose()
{
if (!leaveOpen) stream.Dispose();
}
private bool RefillBuffer()
{
// Evilish line of code. 😈
return (length = stream.Read(byteBuffer, 0, byteBuffer.Length)) != (index = 0);
}
private static bool IsNewLineOrComment(byte b)
{
return b == '\n' || b == '#';
}
/// <summary>Moves the stream to the next valid data row.</summary>
/// <returns><see langword="true"/> if data is available; <see langword="false"/> otherwise.</returns>
public bool MoveToNextLine()
{
if (length == 0)
{
if (RefillBuffer())
{
if (!IsNewLineOrComment(byteBuffer[index]))
{
hasField = true;
goto Completed;
}
}
else
{
return false;
}
}
do
{
while (index < length)
{
if (byteBuffer[index++] == '\n')
{
if (index < length && !IsNewLineOrComment(byteBuffer[index]))
{
hasField = true;
goto Completed;
}
}
}
} while (RefillBuffer());
hasField = false;
Completed: ;
return hasField;
}
private string ReadFieldInternal(bool trim)
{
if (length == 0) throw new InvalidOperationException();
if (!hasField) return null;
else if (index >= length) RefillBuffer();
// If the current character is a new line or a comment, we are at the end of a line.
if (IsNewLineOrComment(byteBuffer[index]))
{
if (hasField)
{
hasField = false;
return string.Empty;
}
else
{
return null;
}
}
var buffer = new Utf8Buffer(this);
int startOffset;
int endOffset;
do
{
startOffset = index;
endOffset = -1;
while (index < length)
{
byte b = byteBuffer[index];
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
{
endOffset = index;
hasField = false;
break;
}
else if(b == ';')
{
endOffset = index++;
break;
}
else
{
++index;
}
}
if (endOffset >= 0)
{
buffer.Append(byteBuffer, startOffset, endOffset - startOffset);
break;
}
else if (index > startOffset)
{
buffer.Append(byteBuffer, startOffset, index - startOffset);
}
} while (RefillBuffer());
return trim ? buffer.ToTrimmedString() : buffer.ToString();
}
/// <summary>Reads the next data field.</summary>
/// <remarks>This method will return <see langword="null"/> on end of line.</remarks>
/// <returns>The text value of the read field, if available; <see langword="null"/> otherwise.</returns>
public string ReadField()
{
return ReadFieldInternal(false);
}
/// <summary>Reads the next data field as a trimmed value.</summary>
/// <remarks>This method will return <see langword="null"/> on end of line.</remarks>
/// <returns>The trimmed text value of the read field, if available; <see langword="null"/> otherwise.</returns>
public string ReadTrimmedField()
{
return ReadFieldInternal(true);
}
/// <summary>Skips the next data field.</summary>
/// <remarks>This method will return <see langword="false"/> on end of line.</remarks>
/// <returns><see langword="true"/> if a field was skipped; <see langword="false"/> otherwise.</returns>
public bool SkipField()
{
if (length == 0) throw new InvalidOperationException();
if (!hasField) return false;
else if (index >= length) RefillBuffer();
// If the current character is a new line or a comment, we are at the end of a line.
if (IsNewLineOrComment(byteBuffer[index]))
{
hasField = false;
return false;
}
do
{
while (index < length)
{
byte b = byteBuffer[index];
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
{
hasField = false;
return true;
}
else
{
++index;
if (b == ';')
{
return true;
}
}
}
} while (RefillBuffer());
return true;
}
}
}