Skip to content

Commit

Permalink
Unify compound stream code between normal and mini streams. (#166)
Browse files Browse the repository at this point in the history
Unify ole streams
Fix bug with xls record reader bounds check.
  • Loading branch information
MarkPflug authored May 5, 2024
1 parent 239b562 commit 203f664
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 347 deletions.
3 changes: 3 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Sylvan.Data.Excel Release Notes

_0.4.21_
- Fixes a bug that could cause incorrect behavior when reading certain .xls files.

_0.4.20_
- Fixes a bug where advancing to the next worksheet can result in the incorrect number of columns being reported when reading xlsx files. #160
- Calling field accessor methods (`GetString`, `GetInt32()`, etc) now throw an exception when called when the reader is in an invalid state.
Expand Down
157 changes: 78 additions & 79 deletions source/Sylvan.Data.Excel.Tests/DataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,115 +4,114 @@
using System.Linq;
using System.Threading.Tasks;

namespace Sylvan.Data
namespace Sylvan.Data;

static class DataExtensions
{
static class DataExtensions
public static void ProcessStrings(this IDataReader reader)
{
public static void ProcessStrings(this IDataReader reader)
while (reader.Read())
{
while (reader.Read())
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
reader.GetString(i);
}
reader.GetString(i);
}
}
}

public static void ProcessValues(this IDataReader reader)
public static void ProcessValues(this IDataReader reader)
{
while (reader.Read())
{
while (reader.Read())
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
reader.GetValue(i);
}
reader.GetValue(i);
}
}
}

public static void Process(this IDataReader reader)
public static void Process(this IDataReader reader)
{
TypeCode[] types = new TypeCode[reader.FieldCount];
for (int i = 0; i < reader.FieldCount; i++)
{
TypeCode[] types = new TypeCode[reader.FieldCount];
for (int i = 0; i < reader.FieldCount; i++)
{
var t = reader.GetFieldType(i);
t = Nullable.GetUnderlyingType(t) ?? t;
types[i] = Type.GetTypeCode(t);
}
var t = reader.GetFieldType(i);
t = Nullable.GetUnderlyingType(t) ?? t;
types[i] = Type.GetTypeCode(t);
}

while (reader.Read())
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
if (reader.IsDBNull(i))
continue;
ProcessField(reader, i, types[i]);
}
if (reader.IsDBNull(i))
continue;
ProcessField(reader, i, types[i]);
}
}
}

public static void Process(this DbDataReader reader)
public static void Process(this DbDataReader reader)
{
var cols = reader.GetColumnSchema();
bool[] allowDbNull = cols.Select(c => c.AllowDBNull != false).ToArray();
TypeCode[] types = cols.Select(c => Type.GetTypeCode(c.DataType)).ToArray();
while (reader.Read())
{
var cols = reader.GetColumnSchema();
bool[] allowDbNull = cols.Select(c => c.AllowDBNull != false).ToArray();
TypeCode[] types = cols.Select(c => Type.GetTypeCode(c.DataType)).ToArray();
while (reader.Read())
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
if (allowDbNull[i] && reader.IsDBNull(i))
continue;
ProcessField(reader, i, types[i]);
}
if (allowDbNull[i] && reader.IsDBNull(i))
continue;
ProcessField(reader, i, types[i]);
}
}
}

public static async Task ProcessAsync(this DbDataReader reader)
public static async Task ProcessAsync(this DbDataReader reader)
{
var cols = reader.GetColumnSchema();
bool[] allowDbNull = cols.Select(c => c.AllowDBNull != false).ToArray();
TypeCode[] types = cols.Select(c => Type.GetTypeCode(c.DataType)).ToArray();
while (await reader.ReadAsync())
{
var cols = reader.GetColumnSchema();
bool[] allowDbNull = cols.Select(c => c.AllowDBNull != false).ToArray();
TypeCode[] types = cols.Select(c => Type.GetTypeCode(c.DataType)).ToArray();
while (await reader.ReadAsync())
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
if (allowDbNull[i] && await reader.IsDBNullAsync(i))
continue;
if (allowDbNull[i] && await reader.IsDBNullAsync(i))
continue;

ProcessField(reader, i, types[i]);
}
ProcessField(reader, i, types[i]);
}
}
}

static void ProcessField(this IDataReader reader, int i, TypeCode typeCode)
static void ProcessField(this IDataReader reader, int i, TypeCode typeCode)
{
switch (typeCode)
{
switch (typeCode)
{
case TypeCode.Boolean:
reader.GetBoolean(i);
break;
case TypeCode.Int32:
reader.GetInt32(i);
break;
case TypeCode.DateTime:
reader.GetDateTime(i);
break;
case TypeCode.Single:
reader.GetFloat(i);
break;
case TypeCode.Double:
reader.GetDouble(i);
break;
case TypeCode.Decimal:
reader.GetDecimal(i);
break;
case TypeCode.String:
reader.GetString(i);
break;
default:
// no cheating
throw new NotSupportedException("" + typeCode);
}
case TypeCode.Boolean:
reader.GetBoolean(i);
break;
case TypeCode.Int32:
reader.GetInt32(i);
break;
case TypeCode.DateTime:
reader.GetDateTime(i);
break;
case TypeCode.Single:
reader.GetFloat(i);
break;
case TypeCode.Double:
reader.GetDouble(i);
break;
case TypeCode.Decimal:
reader.GetDecimal(i);
break;
case TypeCode.String:
reader.GetString(i);
break;
default:
// no cheating
throw new NotSupportedException("" + typeCode);
}
}
}
4 changes: 2 additions & 2 deletions source/Sylvan.Data.Excel.Tests/ExcelDataReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Sylvan.Data.Excel;

// the tests defined here will be run against .xls, .xlsx, and .xlsb file
// containing the same content. The expectation is the behavior of the two
// containing the same content. The expectation is the behavior of the three
// implementations is the same, so the same test code can validate the
// behavior of the three formats.
public class XlsxTests
Expand Down Expand Up @@ -124,7 +124,7 @@ public void DateTime()
var epoch = new DateTime(1900, 1, 1);
using var edr = ExcelDataReader.Create(file);
for (int i = 0; i < 22; i++)
{
{
Assert.True(edr.Read());
var value = edr.GetDouble(0);
var vs = value.ToString("G15");
Expand Down
91 changes: 43 additions & 48 deletions source/Sylvan.Data.Excel.Tests/TestStream.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Sylvan.Testing
namespace Sylvan.Testing;

sealed class TestStream : Stream
{
class TestStream : Stream
readonly Stream stream;

public TestStream(Stream stream)
{
Stream stream;
public TestStream(Stream stream)
{
this.stream = stream;
}
this.stream = stream;
}

public override bool CanRead => this.stream.CanRead;
public override bool CanRead => this.stream.CanRead;

public override bool CanSeek => this.stream.CanSeek;
public override bool CanSeek => this.stream.CanSeek;

public override bool CanWrite => this.stream.CanWrite;
public override bool CanWrite => this.stream.CanWrite;

public override long Length => this.stream.Length;
public override long Length => this.stream.Length;

public bool IsClosed { get; private set; }
public bool IsClosed { get; private set; }

public override void Close()
{
this.IsClosed = true;
stream.Close();
base.Close();
}
public override void Close()
{
this.IsClosed = true;
stream.Close();
base.Close();
}

public override long Position {
get => this.stream.Position;
set => this.stream.Position = value;
}
public override long Position {
get => this.stream.Position;
set => this.stream.Position = value;
}

public override void Flush()
{
this.stream.Flush();
}
public override void Flush()
{
this.stream.Flush();
}

public override int Read(byte[] buffer, int offset, int count)
{
return this.stream.Read(buffer, offset, count);
}
public override int Read(byte[] buffer, int offset, int count)
{
return this.stream.Read(buffer, offset, count);
}

public override long Seek(long offset, SeekOrigin origin)
{
return this.stream.Seek(offset, origin);
}
public override long Seek(long offset, SeekOrigin origin)
{
return this.stream.Seek(offset, origin);
}

public override void SetLength(long value)
{
this.stream.SetLength(value);
}
public override void SetLength(long value)
{
this.stream.SetLength(value);
}

public override void Write(byte[] buffer, int offset, int count)
{
this.stream.Write(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
this.stream.Write(buffer, offset, count);
}
}
14 changes: 8 additions & 6 deletions source/Sylvan.Data.Excel/Packaging/Ole2Package+Ole2Entry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -102,15 +103,16 @@ public override string ToString()

public Stream Open()
{
if (this.StreamSize < Package.miniSectorCutoff)
var pkg = this.Package;
if (this.StreamSize < pkg.miniSectorCutoff)
{
var sectors = this.Package.GetMiniStreamSectors(this.StartSector).ToArray();
return new Ole2MiniStream(this.Package, this.Package.miniStream, sectors, StreamSize);
var sectors = pkg.GetMiniStreamSectors(this.StartSector).ToArray();
return new Ole2Stream(pkg.miniStream, sectors, MiniSectorSize, 0, StreamSize);
}
else
{
var sectors = this.Package.GetStreamSectors(this.StartSector).ToArray();
return new Ole2Stream(this.Package, sectors, StreamSize);
var sectors = pkg.GetStreamSectors(this.StartSector).ToArray();
return new Ole2Stream(pkg.stream, sectors, pkg.sectorSize, 1, StreamSize);
}
}
}
Expand Down
Loading

0 comments on commit 203f664

Please sign in to comment.