2014-11-01 02:26:07 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2014-11-03 00:05:54 +01:00
|
|
|
namespace System.Unicode.Builder
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
public class UnicodeDataFileReader : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly Stream stream;
|
|
|
|
|
private readonly byte[] byteBuffer;
|
|
|
|
|
private int index;
|
|
|
|
|
private int length;
|
2014-11-15 11:50:43 +01:00
|
|
|
private readonly char fieldSeparator;
|
2014-11-01 02:26:07 +01:00
|
|
|
private bool hasField = false;
|
|
|
|
|
private readonly bool leaveOpen;
|
|
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
public UnicodeDataFileReader(Stream stream, char fieldSeparator)
|
|
|
|
|
: this(stream, fieldSeparator, false)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
public UnicodeDataFileReader(Stream stream, char fieldSeparator, bool leaveOpen)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
this.stream = stream;
|
2014-11-15 11:50:43 +01:00
|
|
|
this.fieldSeparator = fieldSeparator;
|
2014-11-01 02:26:07 +01:00
|
|
|
this.byteBuffer = new byte[8192];
|
|
|
|
|
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;
|
|
|
|
|
}
|
2014-11-16 21:27:57 +01:00
|
|
|
}
|
2014-11-01 02:26:07 +01:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
while (index < length)
|
|
|
|
|
{
|
|
|
|
|
if (byteBuffer[index++] == '\n')
|
|
|
|
|
{
|
2017-06-21 02:28:58 +02:00
|
|
|
if ((index < length || RefillBuffer()) && !IsNewLineOrComment(byteBuffer[index]))
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
hasField = true;
|
|
|
|
|
goto Completed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} while (RefillBuffer());
|
|
|
|
|
|
|
|
|
|
hasField = false;
|
2014-11-16 21:27:57 +01:00
|
|
|
Completed:;
|
2014-11-01 02:26:07 +01:00
|
|
|
return hasField;
|
2014-11-16 21:27:57 +01:00
|
|
|
}
|
2014-11-01 02:26:07 +01:00
|
|
|
|
2014-11-08 22:00:12 +01:00
|
|
|
private string ReadFieldInternal(bool trim)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
using (var buffer = Utf8Buffer.Get())
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
2014-11-15 11:50:43 +01:00
|
|
|
int startOffset;
|
|
|
|
|
int endOffset;
|
2014-11-01 02:26:07 +01:00
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
do
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
2014-11-15 11:50:43 +01:00
|
|
|
startOffset = index;
|
|
|
|
|
endOffset = -1;
|
2014-11-01 02:26:07 +01:00
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
while (index < length)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
2014-11-15 11:50:43 +01:00
|
|
|
byte b = byteBuffer[index];
|
|
|
|
|
|
2014-11-16 21:27:57 +01:00
|
|
|
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
|
2014-11-15 11:50:43 +01:00
|
|
|
{
|
|
|
|
|
endOffset = index;
|
|
|
|
|
hasField = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if (b == fieldSeparator)
|
|
|
|
|
{
|
|
|
|
|
endOffset = index++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
++index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (endOffset >= 0)
|
|
|
|
|
{
|
|
|
|
|
buffer.Append(byteBuffer, startOffset, endOffset - startOffset);
|
2014-11-01 02:26:07 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2014-11-15 11:50:43 +01:00
|
|
|
else if (index > startOffset)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
2014-11-15 11:50:43 +01:00
|
|
|
buffer.Append(byteBuffer, startOffset, index - startOffset);
|
2014-11-01 02:26:07 +01:00
|
|
|
}
|
2014-11-15 11:50:43 +01:00
|
|
|
} while (RefillBuffer());
|
2014-11-01 02:26:07 +01:00
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
return trim ? buffer.ToTrimmedString() : buffer.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-08 22:00:12 +01:00
|
|
|
|
|
|
|
|
/// <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);
|
2014-11-01 02:26:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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];
|
|
|
|
|
|
2014-11-16 21:27:57 +01:00
|
|
|
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
hasField = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
++index;
|
|
|
|
|
|
2014-11-15 11:50:43 +01:00
|
|
|
if (b == fieldSeparator)
|
2014-11-01 02:26:07 +01:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} while (RefillBuffer());
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|