diff --git a/Directory.Build.props b/Directory.Build.props index 12ee1ceb..9a337a6e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,6 +13,7 @@ 4.0.0 net462;netstandard2.0;netstandard2.1;net8.0 + enable latest-recommended ..\ExcelDataReader.snk true diff --git a/README.md b/README.md index ae6688ad..1ddf8830 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,9 @@ The `AsDataSet()` extension method is a convenient helper for quickly getting th | `MergeCells` | returns an array of merged cell ranges in the current sheet. | | `RowHeight` | returns the visual height of the current row in points. May be 0 if the row is hidden. | | `GetColumnWidth()` | returns the width of a column in character units. May be 0 if the column is hidden. | -| `GetFieldType()` | returns the type of a value in the current row. Always one of the types supported by Excel: `double`, `int`, `bool`, `DateTime`, `TimeSpan`, `string`, or `null` if there is no value. | -| `IsDBNull()` | checks if a value in the current row is null. | -| `GetValue()` | returns a value from the current row as an `object`, or `null` if there is no value. | +| `GetFieldType()` | returns the type of a value in the current row. Always one of the types supported by Excel: `double`, `int`, `bool`, `DateTime`, `TimeSpan`, `string`, or `typeof(DBNull)` if there is no value. | +| `IsDBNull()` | returns `true` if a value in the current row is `DBNull` (i.e. the cell is empty). | +| `GetValue()` | returns a value from the current row as an `object`, or `DBNull.Value` if there is no value. | | `GetDouble()`
`GetInt32()`
`GetBoolean()`
`GetDateTime()`
`GetString()` | return a value from the current row cast to their respective type. | | `GetNumberFormatString()` | returns a string containing the formatting codes for a value in the current row, or `null` if there is no value. See also the Formatting section below. | | `GetNumberFormatIndex()` | returns the number format index for a value in the current row. Index values below 164 refer to built-in number formats, otherwise indicate a custom number format. | @@ -161,8 +161,6 @@ var reader = ExcelReaderFactory.CreateReader(stream, new ExcelReaderConfiguratio `CreateReader()`, `CreateBinaryReader()`, `CreateOpenXmlReader()`, and `CreateCsvReader()` require seek support during probing and parsing. If the input stream is non-seekable, ExcelDataReader copies it to a `MemoryStream` first. -This is a 4.0 breaking behavior change: when a non-seekable stream is copied, the original source stream may be consumed even when `LeaveOpen = true`. - ### AsDataSet() configuration options The `AsDataSet()` method accepts an optional configuration object to modify the behavior of the DataSet conversion: @@ -261,7 +259,76 @@ See also: - https://github.com/andersnm/ExcelNumberFormat - https://www.nuget.org/packages/ExcelNumberFormat -## Important note when upgrading from ExcelDataReader 2.x +## Upgrading from 3.x to 4.0 + +### Null cells now return `DBNull.Value` instead of `null` + +`GetValue()` now returns `DBNull.Value` instead of `null` for empty cells, aligning with the `IDataReader` contract. `GetFieldType()` correspondingly returns `typeof(DBNull)` instead of `null`. + +**Before (3.x):** +```c# +var value = reader.GetValue(i); +if (value == null) +{ + // cell is empty +} +``` + +**After (4.0):** +```c# +if (reader.IsDBNull(i)) +{ + // cell is empty +} +// or +var value = reader.GetValue(i); +if (value is DBNull) +{ + // cell is empty +} +``` + +The typed getters (`GetString()`, `GetBoolean()`, `GetDateTime()`, etc.) will throw `InvalidCastException` when called on an empty cell. Always check `IsDBNull(i)` first: + +```c# +// Before (3.x): returned null for empty cells +string value = reader.GetString(i); + +// After (4.0): throws InvalidCastException for empty cells — check first +string? value = reader.IsDBNull(i) ? null : reader.GetString(i); +``` + +### `GetValue()` returns `DateTime` instead of `DateTimeOffset` for strict OpenXml dates + +In strict OpenXml mode, date values were previously returned as `DateTimeOffset`. They are now returned as `DateTime`, consistent with all other date handling in ExcelDataReader. + +```c# +// Before (3.x) +var value = (DateTimeOffset)reader.GetValue(i); + +// After (4.0) +var value = reader.GetDateTime(i); // or (DateTime)reader.GetValue(i) +``` + +### Non-seekable streams are consumed even when `LeaveOpen = true` + +If the input stream is non-seekable, ExcelDataReader copies it to a `MemoryStream`. When this copy occurs, the source stream is fully consumed regardless of the `LeaveOpen` setting. If you need to reuse the source stream, ensure it is seekable before passing it to `CreateReader()`. + +### `TransformValue` callback parameter types changed to nullable + +The `ExcelDataTableConfiguration.TransformValue` delegate type changed from `Func` to `Func`. If you assigned a named method to this property, update its signature: + +```c# +// Before (3.x) +object MyTransform(IExcelDataReader reader, int col, object value) => ...; + +// After (4.0) +object? MyTransform(IExcelDataReader reader, int col, object? value) => ...; +``` + +Anonymous lambdas are unaffected — the compiler infers the parameter types automatically. + +## Upgrading from 2.x to 3.x ExcelDataReader 3 had some breaking changes, and older code may produce error messages similar to: diff --git a/src/ExcelDataReader.ConsoleSample/Program.cs b/src/ExcelDataReader.ConsoleSample/Program.cs index e1f7fff8..c6b2c761 100644 --- a/src/ExcelDataReader.ConsoleSample/Program.cs +++ b/src/ExcelDataReader.ConsoleSample/Program.cs @@ -94,7 +94,8 @@ static Command BuildExcelCommand() return true; // sheetIndex is 0-based; expose as 1-based to the user. - return nameSet.Contains(tableReader.Name) || indexSet.Contains(sheetIndex + 1); + var sheetName = tableReader.Name ?? string.Empty; + return nameSet.Contains(sheetName) || indexSet.Contains(sheetIndex + 1); }, ConfigureDataTable = _ => new ExcelDataTableConfiguration { @@ -229,10 +230,11 @@ static Command BuildCsvCommand() do { sheetNumber++; - if (hasFilter && !nameSet.Contains(reader.Name) && !indexSet.Contains(sheetNumber)) + var sheetName = reader.Name ?? string.Empty; + + if (hasFilter && !nameSet.Contains(sheetName) && !indexSet.Contains(sheetNumber)) continue; - string sheetName = reader.Name; long sheetRows = 0; int sheetCols = 0; string[]? headers = null; diff --git a/src/ExcelDataReader.DataSet/ExcelDataReaderExtensions.cs b/src/ExcelDataReader.DataSet/ExcelDataReaderExtensions.cs index 60a02d12..53740caf 100644 --- a/src/ExcelDataReader.DataSet/ExcelDataReaderExtensions.cs +++ b/src/ExcelDataReader.DataSet/ExcelDataReaderExtensions.cs @@ -15,7 +15,7 @@ public static class ExcelDataReaderExtensions /// The IExcelDataReader instance. /// An optional configuration object to modify the behavior of the conversion. /// A dataset with all workbook contents. - public static DataSet AsDataSet(this IExcelDataReader self, ExcelDataSetConfiguration configuration = null) + public static DataSet AsDataSet(this IExcelDataReader self, ExcelDataSetConfiguration? configuration = null) { configuration ??= new(); @@ -74,7 +74,7 @@ private static DataTable AsDataTable(IExcelDataReader self, ExcelDataTableConfig var first = true; var emptyRows = 0; List mergedCellsList = []; - Dictionary<(int Row, int Column), object> mergeCellValue = []; + Dictionary<(int Row, int Column), object?> mergeCellValue = []; // If need to fill merged cells, check the next row have merged cells var nextRowHaveMergedCell = false; @@ -121,8 +121,8 @@ private static DataTable AsDataTable(IExcelDataReader self, ExcelDataTableConfig } var name = configuration.UseHeaderRow - ? Convert.ToString(self.GetValue(i), CultureInfo.CurrentCulture) - : null; + ? Convert.ToString(self.GetValue(i), CultureInfo.CurrentCulture) ?? string.Empty + : string.Empty; if (string.IsNullOrEmpty(name)) { @@ -224,7 +224,7 @@ private static DataTable AsDataTable(IExcelDataReader self, ExcelDataTableConfig if (configuration.TransformValue != null) { - var transformedValue = configuration.TransformValue(self, i, value); + var transformedValue = configuration.TransformValue(self, i, value is DBNull ? null : value); if (transformedValue != null) value = transformedValue; } @@ -246,12 +246,12 @@ private static bool IsEmptyRow(IExcelDataReader reader, ExcelDataTableConfigurat var value = reader.GetValue(i); if (configuration.TransformValue != null) { - var transformedValue = configuration.TransformValue(reader, i, value); + var transformedValue = configuration.TransformValue(reader, i, value is DBNull ? null : value); if (transformedValue != null) value = transformedValue; } - if (value != null) + if (value != null && value is not DBNull) return false; } @@ -275,10 +275,10 @@ private static void FixDataTypes(DataSet dataset) continue; } - DataTable newTable = null; + DataTable? newTable = null; for (int i = 0; i < table.Columns.Count; i++) { - Type type = null; + Type? type = null; foreach (DataRow row in table.Rows) { if (row.IsNull(i)) diff --git a/src/ExcelDataReader.DataSet/ExcelDataSetConfiguration.cs b/src/ExcelDataReader.DataSet/ExcelDataSetConfiguration.cs index 940eccb2..d179ff1a 100644 --- a/src/ExcelDataReader.DataSet/ExcelDataSetConfiguration.cs +++ b/src/ExcelDataReader.DataSet/ExcelDataSetConfiguration.cs @@ -13,10 +13,10 @@ public class ExcelDataSetConfiguration /// /// Gets or sets a callback to obtain configuration options for a DataTable. /// - public Func ConfigureDataTable { get; set; } + public Func? ConfigureDataTable { get; set; } /// /// Gets or sets a callback to determine whether to include the current sheet in the DataSet. Called once per sheet before ConfigureDataTable. /// - public Func FilterSheet { get; set; } + public Func? FilterSheet { get; set; } } diff --git a/src/ExcelDataReader.DataSet/ExcelDataTableConfiguration.cs b/src/ExcelDataReader.DataSet/ExcelDataTableConfiguration.cs index 812fb4aa..c49c46d9 100644 --- a/src/ExcelDataReader.DataSet/ExcelDataTableConfiguration.cs +++ b/src/ExcelDataReader.DataSet/ExcelDataTableConfiguration.cs @@ -23,7 +23,7 @@ public class ExcelDataTableConfiguration /// /// Gets or sets a callback to determine which row is the header row. Only called when UseHeaderRow = true. /// - public Action ReadHeaderRow { get; set; } + public Action? ReadHeaderRow { get; set; } /// /// Gets or sets a callback to allow a custom implementation of header reading. @@ -32,22 +32,22 @@ public class ExcelDataTableConfiguration /// An example use of this would be to combine multiple header rows. /// NOTE: If this field is set, UseHeaderRow, EmptyColumnNamePrefix, and FilterColumn are ignored. /// - public Func> ReadHeader { get; set; } + public Func>? ReadHeader { get; set; } /// /// Gets or sets a callback to determine whether to include the current row in the DataTable. /// - public Func FilterRow { get; set; } + public Func? FilterRow { get; set; } /// /// Gets or sets a callback to determine whether to include the specific column in the DataTable. Called once per column after reading the headers. /// - public Func FilterColumn { get; set; } + public Func? FilterColumn { get; set; } /// /// Gets or sets a callback to determine whether to transform the cell value. /// - public Func TransformValue { get; set; } + public Func? TransformValue { get; set; } /// /// Gets or sets a value indicating whether merged cells should be filled with their top-left cell's value. diff --git a/src/ExcelDataReader.Tests/AssertUtilities.cs b/src/ExcelDataReader.Tests/AssertUtilities.cs index 5878f45f..4da13a54 100644 --- a/src/ExcelDataReader.Tests/AssertUtilities.cs +++ b/src/ExcelDataReader.Tests/AssertUtilities.cs @@ -11,7 +11,8 @@ public static void DoOpenOfficeTest(IExcelDataReader excelReader) Assert.That(excelReader.GetString(0), Is.EqualTo("column a")); Assert.That(excelReader.GetString(1), Is.EqualTo(" column b")); Assert.That(excelReader.GetString(2), Is.EqualTo(" column b")); - Assert.That(excelReader.GetString(3), Is.Null); + Assert.That(excelReader.IsDBNull(3), Is.True); + Assert.Throws(() => excelReader.GetString(3)); Assert.That(excelReader.GetString(4), Is.EqualTo("column e")); Assert.That(excelReader.GetString(5), Is.EqualTo(" column b")); diff --git a/src/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs b/src/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs index e0aaaafa..094520e9 100644 --- a/src/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs +++ b/src/ExcelDataReader.Tests/ExcelBinaryReaderTest.cs @@ -709,7 +709,8 @@ public void Issue321_MissingEof() for (int i = 0; i < 7; i++) { reader.Read(); - Assert.That(string.IsNullOrEmpty(reader.GetString(1)), Is.True, "Row = " + i); + Assert.That(reader.IsDBNull(1), Is.True, "Row = " + i); + Assert.Throws(() => reader.GetString(1), "Row = " + i); } reader.Read(); @@ -1015,12 +1016,19 @@ public void Issue467_Test_SST_zero_count() reader.Read(); Assert.That(reader.RowCount, Is.EqualTo(10)); Assert.That(reader.FieldCount, Is.EqualTo(10)); - Assert.That(reader.GetString(0), Is.EqualTo(null)); - Assert.That(reader.GetString(2), Is.EqualTo(null)); - Assert.That(reader.GetString(6), Is.EqualTo(null)); + Assert.That(reader.IsDBNull(0), Is.True); + Assert.That(reader.IsDBNull(2), Is.True); + Assert.That(reader.IsDBNull(6), Is.True); + Assert.That(reader.GetFieldType(0), Is.EqualTo(typeof(DBNull))); + Assert.That(reader.GetFieldType(2), Is.EqualTo(typeof(DBNull))); + Assert.That(reader.GetFieldType(6), Is.EqualTo(typeof(DBNull))); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetString(2)); + Assert.Throws(() => reader.GetString(6)); reader.Read(); - Assert.That(reader.GetString(0), Is.EqualTo(null)); + Assert.That(reader.IsDBNull(0), Is.True); + Assert.Throws(() => reader.GetString(0)); reader.Read(); reader.Read(); @@ -1031,7 +1039,8 @@ public void Issue467_Test_SST_zero_count() reader.Read(); reader.Read(); reader.Read(); - Assert.That(reader.GetString(9), Is.EqualTo(null)); + Assert.That(reader.IsDBNull(9), Is.True); + Assert.Throws(() => reader.GetString(9)); } [Test] @@ -1041,36 +1050,36 @@ public void Issue466_BIFF3_Errors() // First row contains formula errors reader.Read(); - Assert.That(reader.GetString(0), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(0)); Assert.That(reader.GetCellError(0), Is.EqualTo(CellError.DIV0)); - Assert.That(reader.GetString(1), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(1)); Assert.That(reader.GetCellError(1), Is.EqualTo(CellError.NA)); - Assert.That(reader.GetString(2), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(2)); Assert.That(reader.GetCellError(2), Is.EqualTo(CellError.VALUE)); - Assert.That(reader.GetString(3), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(3)); Assert.That(reader.GetCellError(3), Is.EqualTo(CellError.NAME)); - Assert.That(reader.GetString(4), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(4)); Assert.That(reader.GetCellError(4), Is.EqualTo(CellError.REF)); // Second row contains error constants reader.Read(); - Assert.That(reader.GetString(0), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(0)); Assert.That(reader.GetCellError(0), Is.EqualTo(CellError.DIV0)); - Assert.That(reader.GetString(1), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(1)); Assert.That(reader.GetCellError(1), Is.EqualTo(CellError.NA)); - Assert.That(reader.GetString(2), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(2)); Assert.That(reader.GetCellError(2), Is.EqualTo(CellError.VALUE)); - Assert.That(reader.GetString(3), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(3)); Assert.That(reader.GetCellError(3), Is.EqualTo(CellError.NAME)); - Assert.That(reader.GetString(4), Is.EqualTo(null)); + Assert.Throws(() => reader.GetString(4)); Assert.That(reader.GetCellError(4), Is.EqualTo(CellError.REF)); } @@ -1168,7 +1177,8 @@ public void Issue525_SstMaterializationReturnsCorrectStrings() reader.Read(); Assert.That(reader.GetString(0), Is.EqualTo("col1")); Assert.That(reader.GetString(4), Is.EqualTo("col5")); - Assert.That(reader.GetString(9), Is.Null); // column 9 is empty in first row + Assert.That(reader.IsDBNull(9), Is.True); // column 9 is empty in first row + Assert.Throws(() => reader.GetString(9)); // Repeated lookups of the same SST index must return the same value Assert.That(reader.GetString(0), Is.EqualTo("col1")); @@ -1211,7 +1221,7 @@ public void Read_XlsmExcel20() reader.Read(); Assert.That(reader.GetValue(0), Is.EqualTo("Record1")); - Assert.That(reader.GetValue(2), Is.EqualTo(null)); + Assert.That(reader.GetValue(2), Is.EqualTo(DBNull.Value)); reader.Read(); Assert.That(reader.GetValue(0), Is.EqualTo(double.NaN)); diff --git a/src/ExcelDataReader.Tests/ExcelCsvReaderTest.cs b/src/ExcelDataReader.Tests/ExcelCsvReaderTest.cs index 256731e8..a46f507b 100644 --- a/src/ExcelDataReader.Tests/ExcelCsvReaderTest.cs +++ b/src/ExcelDataReader.Tests/ExcelCsvReaderTest.cs @@ -26,6 +26,13 @@ public void CsvCommaInQuotes() Assert.That(ds.Tables[0].Rows[1][4], Is.EqualTo("08123")); } + [Test] + public void VisibleStateIsNullForCsv() + { + using var excelReader = ExcelReaderFactory.CreateCsvReader(Configuration.GetTestWorkbook(Path.Combine("csv", "comma_in_quotes.csv"))); + Assert.That(excelReader.VisibleState, Is.Null); + } + [Test] public void Issue443_DepthAlwaysZero_Csv() { diff --git a/src/ExcelDataReader.Tests/ExcelOpenXmlReaderBase.cs b/src/ExcelDataReader.Tests/ExcelOpenXmlReaderBase.cs index 56958e10..d001dda3 100644 --- a/src/ExcelDataReader.Tests/ExcelOpenXmlReaderBase.cs +++ b/src/ExcelDataReader.Tests/ExcelOpenXmlReaderBase.cs @@ -13,7 +13,8 @@ public void Issue525_XlsxSstPreallocationReturnsCorrectStrings() reader.Read(); Assert.That(reader.GetString(0), Is.EqualTo("col1")); Assert.That(reader.GetString(4), Is.EqualTo("col5")); - Assert.That(reader.GetString(9), Is.Null); // column 9 is empty in first row + Assert.That(reader.IsDBNull(9), Is.True); // column 9 is empty in first row + Assert.Throws(() => reader.GetString(9)); reader.Read(); Assert.That(reader.GetString(0), Is.EqualTo("10x10")); diff --git a/src/ExcelDataReader.Tests/ExcelTestBase.cs b/src/ExcelDataReader.Tests/ExcelTestBase.cs index 7a751522..1ba4e91c 100644 --- a/src/ExcelDataReader.Tests/ExcelTestBase.cs +++ b/src/ExcelDataReader.Tests/ExcelTestBase.cs @@ -451,13 +451,13 @@ public void Issue329_Error() // Check errors on first row return null reader.Read(); - Assert.That(reader.GetValue(0), Is.Null); + Assert.That(reader.GetValue(0), Is.EqualTo(DBNull.Value)); Assert.That(reader.GetCellError(0), Is.EqualTo(CellError.DIV0)); - Assert.That(reader.GetValue(1), Is.Null); + Assert.That(reader.GetValue(1), Is.EqualTo(DBNull.Value)); Assert.That(reader.GetCellError(1), Is.EqualTo(CellError.NA)); - Assert.That(reader.GetValue(2), Is.Null); + Assert.That(reader.GetValue(2), Is.EqualTo(DBNull.Value)); Assert.That(reader.GetCellError(2), Is.EqualTo(CellError.VALUE)); Assert.That(reader.RowCount, Is.EqualTo(1)); @@ -470,17 +470,17 @@ public void Issue4031_NullColumn() // DataSet dataSet = excelReader.AsDataSet(true); excelReader.Read(); - Assert.That(excelReader.GetValue(0), Is.Null); + Assert.That(excelReader.GetValue(0), Is.EqualTo(DBNull.Value)); Assert.That(excelReader.GetString(1), Is.EqualTo("a")); Assert.That(excelReader.GetString(2), Is.EqualTo("b")); - Assert.That(excelReader.GetValue(3), Is.Null); + Assert.That(excelReader.GetValue(3), Is.EqualTo(DBNull.Value)); Assert.That(excelReader.GetString(4), Is.EqualTo("d")); excelReader.Read(); - Assert.That(excelReader.GetValue(0), Is.Null); - Assert.That(excelReader.GetValue(1), Is.Null); + Assert.That(excelReader.GetValue(0), Is.EqualTo(DBNull.Value)); + Assert.That(excelReader.GetValue(1), Is.EqualTo(DBNull.Value)); Assert.That(excelReader.GetString(2), Is.EqualTo("Test")); - Assert.That(excelReader.GetValue(3), Is.Null); + Assert.That(excelReader.GetValue(3), Is.EqualTo(DBNull.Value)); Assert.That(excelReader.GetDouble(4), Is.EqualTo(1)); } @@ -911,7 +911,7 @@ static bool IsEmptyOrHiddenRow(IExcelDataReader reader) for (var i = 0; i < reader.FieldCount; i++) { - if (reader.GetValue(i) != null) + if (reader.GetValue(i) is not DBNull) return false; } @@ -922,7 +922,7 @@ static bool IsEmptyRow(IExcelDataReader reader) { for (var i = 0; i < reader.FieldCount; i++) { - if (reader.GetValue(i) != null) + if (reader.GetValue(i) is not DBNull) return false; } diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs index da2ae7a1..df605f03 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffFont.cs @@ -40,65 +40,66 @@ internal XlsBiffFont(byte[] bytes, int biffVersion) // with the FONT record character set table here: // https://www.openoffice.org/sc/excelfileformat.pdf var byteStringCharacterSet = ReadByte(12); + Encoding GetEncodingOrDefault(int codePage) => EncodingHelper.GetEncoding((ushort)codePage) ?? ByteStringEncoding; switch (byteStringCharacterSet) { case 0: // ANSI Latin case 1: // System default - ByteStringEncoding = EncodingHelper.GetEncoding(1252); + ByteStringEncoding = GetEncodingOrDefault(1252); break; case 77: // Apple roman - ByteStringEncoding = EncodingHelper.GetEncoding(10000); + ByteStringEncoding = GetEncodingOrDefault(10000); break; case 128: // ANSI Japanese Shift-JIS - ByteStringEncoding = EncodingHelper.GetEncoding(932); + ByteStringEncoding = GetEncodingOrDefault(932); break; case 129: // ANSI Korean (Hangul) - ByteStringEncoding = EncodingHelper.GetEncoding(949); + ByteStringEncoding = GetEncodingOrDefault(949); break; case 130: // ANSI Korean (Johab) - ByteStringEncoding = EncodingHelper.GetEncoding(1361); + ByteStringEncoding = GetEncodingOrDefault(1361); break; case 134: // ANSI Chinese Simplified GBK - ByteStringEncoding = EncodingHelper.GetEncoding(936); + ByteStringEncoding = GetEncodingOrDefault(936); break; case 136: // ANSI Chinese Traditional BIG5 - ByteStringEncoding = EncodingHelper.GetEncoding(950); + ByteStringEncoding = GetEncodingOrDefault(950); break; case 161: // ANSI Greek - ByteStringEncoding = EncodingHelper.GetEncoding(1253); + ByteStringEncoding = GetEncodingOrDefault(1253); break; case 162: // ANSI Turkish - ByteStringEncoding = EncodingHelper.GetEncoding(1254); + ByteStringEncoding = GetEncodingOrDefault(1254); break; case 163: // ANSI Vietnamese - ByteStringEncoding = EncodingHelper.GetEncoding(1258); + ByteStringEncoding = GetEncodingOrDefault(1258); break; case 177: // ANSI Hebrew - ByteStringEncoding = EncodingHelper.GetEncoding(1255); + ByteStringEncoding = GetEncodingOrDefault(1255); break; case 178: // ANSI Arabic - ByteStringEncoding = EncodingHelper.GetEncoding(1256); + ByteStringEncoding = GetEncodingOrDefault(1256); break; case 186: // ANSI Baltic - ByteStringEncoding = EncodingHelper.GetEncoding(1257); + ByteStringEncoding = GetEncodingOrDefault(1257); break; case 204: // ANSI Cyrillic - ByteStringEncoding = EncodingHelper.GetEncoding(1251); + ByteStringEncoding = GetEncodingOrDefault(1251); break; case 222: // ANSI Thai - ByteStringEncoding = EncodingHelper.GetEncoding(874); + ByteStringEncoding = GetEncodingOrDefault(874); break; case 238: // ANSI Latin II - ByteStringEncoding = EncodingHelper.GetEncoding(1250); + ByteStringEncoding = GetEncodingOrDefault(1250); break; case 255: // OEM Latin - ByteStringEncoding = EncodingHelper.GetEncoding(850); + ByteStringEncoding = GetEncodingOrDefault(850); break; } } } - public Encoding ByteStringEncoding { get; } + public Encoding ByteStringEncoding { get; } = Encoding.GetEncoding(1252); public string GetFontName(Encoding encoding) => _fontName.GetValue(encoding); } diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffSST.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffSST.cs index 1fad9bf9..dd293fde 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffSST.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffSST.cs @@ -8,8 +8,8 @@ namespace ExcelDataReader.Core.BinaryFormat; internal sealed class XlsBiffSST : XlsBiffRecord { private readonly XlsSSTReader _reader = new(); - private string[] _materializedStrings; - private List _strings = []; + private string?[]? _materializedStrings; + private List _strings = []; internal XlsBiffSST(byte[] bytes) : base(bytes) @@ -64,13 +64,13 @@ public void Flush() /// /// Index of string to get. /// Workbook encoding. - /// string value if it was found, empty string otherwise. - public string GetString(uint sstIndex, Encoding encoding) + /// string value if it was found, null otherwise. + public string? GetString(uint sstIndex, Encoding encoding) { if (_materializedStrings == null) { if (sstIndex < _strings.Count) - return _strings[(int)sstIndex].GetValue(encoding); + return _strings[(int)sstIndex]?.GetValue(encoding); return null; } @@ -81,7 +81,7 @@ public string GetString(uint sstIndex, Encoding encoding) if (cached != null) return cached; - var s = _strings[(int)sstIndex].GetValue(encoding); + var s = _strings[(int)sstIndex]?.GetValue(encoding); _materializedStrings[sstIndex] = s; _strings[(int)sstIndex] = null; return s; diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs index 73bbe255..20b219ce 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsBiffStream.cs @@ -22,7 +22,7 @@ internal sealed class XlsBiffStream : IDisposable private int _readAheadStart; private int _readAheadEnd; - public XlsBiffStream(Stream baseStream, int offset = 0, int explicitVersion = 0, BIFFTYPE? defaultType = null, string password = null, byte[] secretKey = null, EncryptionInfo encryption = null) + public XlsBiffStream(Stream baseStream, int offset = 0, int explicitVersion = 0, BIFFTYPE? defaultType = null, string? password = null, byte[]? secretKey = null, EncryptionInfo? encryption = null) { BaseStream = baseStream; Position = offset; @@ -46,7 +46,7 @@ record = Read(); if (secretKey != null) { SecretKey = secretKey; - Encryption = encryption; + Encryption = encryption ?? throw new ArgumentNullException(nameof(encryption)); Cipher = Encryption.CreateCipher(); } else @@ -96,16 +96,16 @@ public int Position public Stream BaseStream { get; } - public byte[] SecretKey { get; } + public byte[]? SecretKey { get; } - public EncryptionInfo Encryption { get; } + public EncryptionInfo? Encryption { get; } - public SymmetricAlgorithm Cipher { get; } + public SymmetricAlgorithm? Cipher { get; } /// /// Gets or sets the ICryptoTransform instance used to decrypt the current block. /// - public ICryptoTransform CipherTransform { get; set; } + public ICryptoTransform? CipherTransform { get; set; } /// /// Gets or sets the current block number being decrypted with CipherTransform. @@ -140,7 +140,7 @@ public void Seek(int offset, SeekOrigin origin) /// Reads record under cursor and advances cursor position to next record. /// /// The record -or- null. - public XlsBiffRecord Read() + public XlsBiffRecord? Read() { // Minimum record size is 4 if ((uint)Position + 4 >= Size) @@ -161,7 +161,7 @@ record = null; /// /// The stream. /// The record -or- null. - public XlsBiffRecord GetRecord(Stream stream) + public XlsBiffRecord? GetRecord(Stream stream) { // Capture the logical record start before consuming header bytes from the read-ahead // buffer; this is the value DecryptRecord() needs for its block-number calculation. @@ -290,7 +290,7 @@ public XlsBiffRecord GetRecord(Stream stream) public void Dispose() { CipherTransform?.Dispose(); - ((IDisposable)Cipher)?.Dispose(); + Cipher?.Dispose(); } private static int GetBiffVersion(XlsBiffBOF bof) @@ -372,6 +372,9 @@ private void CreateBlockDecryptor(int blockNumber) { CipherTransform?.Dispose(); + if (Encryption == null || SecretKey == null || Cipher == null) + throw new InvalidOperationException("Encryption is not initialized."); + var blockKey = Encryption.GenerateBlockKey(blockNumber, SecretKey); CipherTransform = Cipher.CreateDecryptor(blockKey, null); CipherBlock = blockNumber; @@ -382,11 +385,13 @@ private void CreateBlockDecryptor(int blockNumber) /// private void AlignBlockDecryptor(int blockOffset) { + var cipherTransform = CipherTransform ?? throw new InvalidOperationException("Decryptor is not initialized."); + #if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER var bytes = System.Buffers.ArrayPool.Shared.Rent(blockOffset); try { - CryptoHelpers.DecryptBytes(CipherTransform, bytes, blockOffset); + CryptoHelpers.DecryptBytes(cipherTransform, bytes, blockOffset); } finally { @@ -394,12 +399,14 @@ private void AlignBlockDecryptor(int blockOffset) } #else var bytes = new byte[blockOffset]; - CryptoHelpers.DecryptBytes(CipherTransform, bytes, blockOffset); + CryptoHelpers.DecryptBytes(cipherTransform, bytes, blockOffset); #endif } private void DecryptRecord(int startPosition, BIFFRECORDTYPE id, byte[] bytes, int recordSize) { + var encryption = Encryption ?? throw new InvalidOperationException("Encryption is not initialized."); + // Decrypt the last read record, find it's start offset relative to the current stream position int startDecrypt = 4; switch (id) @@ -437,11 +444,13 @@ private void DecryptRecord(int startPosition, BIFFRECORDTYPE id, byte[] bytes, i CreateBlockDecryptor(blockNumber); } - if (Encryption.IsXor) + if (encryption.IsXor) { // Bypass everything and hook into the XorTransform instance to set the XorArrayIndex pr record. // This is a hack to use the XorTransform otherwise transparently to the other encryption methods. - var xorTransform = (XorManaged.XorTransform)CipherTransform; + var xorTransform = CipherTransform as XorManaged.XorTransform; + if (xorTransform == null) + throw new InvalidOperationException("XOR decryptor is not initialized."); xorTransform.XorArrayIndex = offset + recordSize - 4; } @@ -449,7 +458,8 @@ private void DecryptRecord(int startPosition, BIFFRECORDTYPE id, byte[] bytes, i var chunkSize = Math.Min(recordSize - position, 1024 - blockOffset); Array.Copy(bytes, position, inputBlock, 0, chunkSize); - CryptoHelpers.DecryptBytes(CipherTransform, inputBlock, chunkSize, outputBlock); + var cipherTransform = CipherTransform ?? throw new InvalidOperationException("Decryptor is not initialized."); + CryptoHelpers.DecryptBytes(cipherTransform, inputBlock, chunkSize, outputBlock); for (var i = 0; i < chunkSize; i++) { diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsSSTReader.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsSSTReader.cs index 2da14320..2c8763d3 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsSSTReader.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsSSTReader.cs @@ -1,4 +1,6 @@ -namespace ExcelDataReader.Core.BinaryFormat; +using System.Diagnostics.CodeAnalysis; + +namespace ExcelDataReader.Core.BinaryFormat; /// /// Helper class for parsing the BIFF8 Shared String Table (SST). @@ -13,7 +15,7 @@ private enum SstState StringTail, } - private XlsBiffRecord CurrentRecord { get; set; } + private XlsBiffRecord CurrentRecord { get; set; } = null!; /// /// Gets or sets the offset into the current record's byte content. May point at the end when the current record has been parsed entirely. @@ -22,11 +24,11 @@ private enum SstState private SstState CurrentState { get; set; } = SstState.StartStringHeader; - private XlsSSTStringHeader CurrentHeader { get; set; } + private XlsSSTStringHeader CurrentHeader { get; set; } = null!; private int CurrentRemainingCharacters { get; set; } - private byte[] CurrentResult { get; set; } + private byte[] CurrentResult { get; set; } = null!; private int CurrentResultOffset { get; set; } @@ -79,7 +81,7 @@ public IEnumerable ReadStringsFromContinue(XlsBiffContinue sstContin } } - public IXlsString Flush() + public IXlsString? Flush() { if (CurrentState == SstState.StringTail) { @@ -89,7 +91,7 @@ public IXlsString Flush() return null; } - private bool TryReadString(out IXlsString result) + private bool TryReadString([NotNullWhen(true)] out IXlsString? result) { if (CurrentState == SstState.StartStringHeader) { diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs index a63df23a..1532b08f 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsWorkbook.cs @@ -10,7 +10,7 @@ namespace ExcelDataReader.Core.BinaryFormat; /// internal sealed class XlsWorkbook : CommonWorkbook, IWorkbook { - internal XlsWorkbook(Stream stream, string password, Encoding fallbackEncoding) + internal XlsWorkbook(Stream stream, string? password, Encoding fallbackEncoding) { Stream = stream; @@ -45,25 +45,25 @@ internal XlsWorkbook(Stream stream, string password, Encoding fallbackEncoding) public int BiffVersion { get; } - public byte[] SecretKey { get; } + public byte[]? SecretKey { get; } - public EncryptionInfo Encryption { get; } + public EncryptionInfo? Encryption { get; } public Encoding Encoding { get; private set; } - public XlsBiffInterfaceHdr InterfaceHdr { get; set; } + public XlsBiffInterfaceHdr? InterfaceHdr { get; set; } - public XlsBiffRecord Mms { get; set; } + public XlsBiffRecord? Mms { get; set; } - public XlsBiffRecord WriteAccess { get; set; } + public XlsBiffRecord? WriteAccess { get; set; } - public XlsBiffSimpleValueRecord CodePage { get; set; } + public XlsBiffSimpleValueRecord? CodePage { get; set; } - public XlsBiffRecord Dsf { get; set; } + public XlsBiffRecord? Dsf { get; set; } - public XlsBiffRecord Country { get; set; } + public XlsBiffRecord? Country { get; set; } - public XlsBiffSimpleValueRecord Backup { get; set; } + public XlsBiffSimpleValueRecord? Backup { get; set; } public List Fonts { get; } = []; @@ -72,13 +72,13 @@ internal XlsWorkbook(Stream stream, string password, Encoding fallbackEncoding) /// /// Gets or sets the Shared String Table of workbook. /// - public XlsBiffSST SST { get; set; } + public XlsBiffSST? SST { get; set; } - public XlsBiffRecord ExtSST { get; set; } + public XlsBiffRecord? ExtSST { get; set; } public bool IsDate1904 { get; private set; } - public int ResultsCount => Sheets?.Count ?? -1; + public int ResultsCount => Sheets.Count; public int ActiveSheet { get; private set; } @@ -172,7 +172,7 @@ private void ReadWorkbookGlobals(XlsBiffStream biffStream) // of the code page values specified in [CODEPG] or the special value 1200, which means that the // workbook is Unicode. CodePage = codePage; - Encoding = EncodingHelper.GetEncoding(CodePage.Value); + Encoding = EncodingHelper.GetEncoding(CodePage.Value) ?? Encoding; break; case XlsBiffSimpleValueRecord is1904 when rec.Id == BIFFRECORDTYPE.DATE1904: IsDate1904 = is1904.Value == 1; diff --git a/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs b/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs index 8b7a48c3..b12e6be3 100644 --- a/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs +++ b/src/ExcelDataReader/Core/BinaryFormat/XlsWorksheet.cs @@ -36,18 +36,18 @@ public XlsWorksheet(XlsWorkbook workbook, XlsBiffBoundSheet refSheet, Stream str /// public string Name { get; } - public string CodeName { get; private set; } + public string? CodeName { get; private set; } /// /// Gets the visibility of worksheet. /// public string VisibleState { get; } - public HeaderFooter HeaderFooter { get; private set; } + public HeaderFooter? HeaderFooter { get; private set; } - public CellRange[] MergeCells { get; private set; } + public CellRange[] MergeCells { get; private set; } = []; - public List ColumnWidths { get; private set; } + public List ColumnWidths { get; private set; } = []; /// /// Gets the worksheet data offset. @@ -81,7 +81,7 @@ public XlsWorksheet(XlsWorkbook workbook, XlsBiffBoundSheet refSheet, Stream str public int RowCount { get; private set; } - public CellRange Dimension { get; private set; } + public CellRange? Dimension { get; private set; } public bool IsDate1904 { get; private set; } @@ -336,7 +336,7 @@ private Cell ReadSingleCell(XlsBiffStream biffStream, XlsBiffBlankCell cell, int var effectiveStyle = Workbook.GetEffectiveCellStyle(xfIndex, cell.Format); var numberFormatIndex = effectiveStyle.NumberFormatIndex; - object value = null; + object? value = null; CellError? error = null; switch (cell.Id) { @@ -401,7 +401,7 @@ private string GetLabelString(XlsBiffLabelCell cell, ExtendedFormat effectiveSty return cell.GetValue(labelEncoding); } - private XlsBiffFont GetFont(int fontIndex) + private XlsBiffFont? GetFont(int fontIndex) { if (fontIndex < 0 || fontIndex >= Workbook.Fonts.Count) { @@ -411,7 +411,7 @@ private XlsBiffFont GetFont(int fontIndex) return Workbook.Fonts[fontIndex]; } - private object TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell formulaCell, ExtendedFormat effectiveStyle, out CellError? error) + private object? TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell formulaCell, ExtendedFormat effectiveStyle, out CellError? error) { error = null; switch (formulaCell.FormulaType) @@ -430,7 +430,7 @@ private object TryGetFormulaValue(XlsBiffStream biffStream, XlsBiffFormulaCell f } } - private string TryGetFormulaString(XlsBiffStream biffStream, ExtendedFormat effectiveStyle) + private string? TryGetFormulaString(XlsBiffStream biffStream, ExtendedFormat effectiveStyle) { var rec = biffStream.Read(); if (rec is { Id: BIFFRECORDTYPE.SHAREDFMLA }) @@ -516,8 +516,8 @@ private void ReadWorksheetGlobals() if (biffStream.BiffVersion == 0 || (biffStream.BiffType != BIFFTYPE.Worksheet && biffStream.BiffType != BIFFTYPE.MacroSheet)) return; - XlsBiffHeaderFooterString header = null; - XlsBiffHeaderFooterString footer = null; + XlsBiffHeaderFooterString? header = null; + XlsBiffHeaderFooterString? footer = null; var ixfeOffset = -1; @@ -574,7 +574,7 @@ private void ReadWorksheetGlobals() biffFormats.Add((ushort)biffFormats.Count, fmt23); break; case XlsBiffSimpleValueRecord codePage when rec.Id == BIFFRECORDTYPE.CODEPAGE: - Encoding = EncodingHelper.GetEncoding(codePage.Value); + Encoding = EncodingHelper.GetEncoding(codePage.Value) ?? Encoding; break; case XlsBiffHeaderFooterString h when rec.Id == BIFFRECORDTYPE.HEADER && rec.RecordSize > 0: header = h; diff --git a/src/ExcelDataReader/Core/BuiltinNumberFormat.cs b/src/ExcelDataReader/Core/BuiltinNumberFormat.cs index 2f84617e..f93d8726 100644 --- a/src/ExcelDataReader/Core/BuiltinNumberFormat.cs +++ b/src/ExcelDataReader/Core/BuiltinNumberFormat.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Globalization; using System.Text; using ExcelDataReader.Core.NumberFormat; diff --git a/src/ExcelDataReader/Core/Cell.cs b/src/ExcelDataReader/Core/Cell.cs index ffacc1e0..1520e06a 100644 --- a/src/ExcelDataReader/Core/Cell.cs +++ b/src/ExcelDataReader/Core/Cell.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Core; /// diff --git a/src/ExcelDataReader/Core/Column.cs b/src/ExcelDataReader/Core/Column.cs index 984af8e5..8c760ae6 100644 --- a/src/ExcelDataReader/Core/Column.cs +++ b/src/ExcelDataReader/Core/Column.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Core; internal sealed record Column(int Minimum, int Maximum, bool Hidden, double? Width); \ No newline at end of file diff --git a/src/ExcelDataReader/Core/CommonWorkbook.cs b/src/ExcelDataReader/Core/CommonWorkbook.cs index f1201229..4062ee0c 100644 --- a/src/ExcelDataReader/Core/CommonWorkbook.cs +++ b/src/ExcelDataReader/Core/CommonWorkbook.cs @@ -1,5 +1,3 @@ -#nullable enable - using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core; diff --git a/src/ExcelDataReader/Core/CompoundFormat/CompoundDirectoryEntry.cs b/src/ExcelDataReader/Core/CompoundFormat/CompoundDirectoryEntry.cs index 3f250a2f..9e4fdc03 100644 --- a/src/ExcelDataReader/Core/CompoundFormat/CompoundDirectoryEntry.cs +++ b/src/ExcelDataReader/Core/CompoundFormat/CompoundDirectoryEntry.cs @@ -8,7 +8,7 @@ internal sealed class CompoundDirectoryEntry /// /// Gets or sets the name of directory entry. /// - public string EntryName { get; set; } + public string? EntryName { get; set; } /// /// Gets or sets the entry type. diff --git a/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs b/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs index 792ebb98..287d4a9a 100644 --- a/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs +++ b/src/ExcelDataReader/Core/CompoundFormat/CompoundDocument.cs @@ -33,9 +33,9 @@ public CompoundDocument(Stream stream) internal List MiniSectorTable { get; } - internal CompoundDirectoryEntry RootEntry { get; set; } + internal CompoundDirectoryEntry RootEntry { get; set; } = null!; - internal List Entries { get; set; } + internal List Entries { get; set; } = []; // NOTE: DateTime.MaxValue.ToFileTime() fails on Unity in timezones with DST and +~6h offset, like Sidney Australia private static long SafeFileTimeMaxDate { get; } = DateTime.MaxValue.ToFileTimeUtc(); @@ -65,9 +65,9 @@ internal static bool IsCompoundDocument(byte[] probe) #if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER // params ReadOnlySpan avoids the implicit array allocation on modern runtimes. - internal CompoundDirectoryEntry FindEntry(params ReadOnlySpan entryNames) + internal CompoundDirectoryEntry? FindEntry(params ReadOnlySpan entryNames) #else - internal CompoundDirectoryEntry FindEntry(params string[] entryNames) + internal CompoundDirectoryEntry? FindEntry(params string[] entryNames) #endif { foreach (var e in Entries) diff --git a/src/ExcelDataReader/Core/CompoundFormat/CompoundHeader.cs b/src/ExcelDataReader/Core/CompoundFormat/CompoundHeader.cs index 8cf6235b..901a43e8 100644 --- a/src/ExcelDataReader/Core/CompoundFormat/CompoundHeader.cs +++ b/src/ExcelDataReader/Core/CompoundFormat/CompoundHeader.cs @@ -104,5 +104,5 @@ internal sealed class CompoundHeader /// /// Gets or sets the first 109 locations in the DIF sector chain. /// - public List First109DifSectorChain { get; set; } + public List First109DifSectorChain { get; set; } = []; } diff --git a/src/ExcelDataReader/Core/CompoundFormat/CompoundStream.cs b/src/ExcelDataReader/Core/CompoundFormat/CompoundStream.cs index fa4bedd5..81ddbe84 100644 --- a/src/ExcelDataReader/Core/CompoundFormat/CompoundStream.cs +++ b/src/ExcelDataReader/Core/CompoundFormat/CompoundStream.cs @@ -45,7 +45,7 @@ public CompoundStream(CompoundDocument document, Stream baseStream, uint baseSec public List SectorChain { get; } - public List RootSectorChain { get; } + public List? RootSectorChain { get; } public override bool CanRead => true; @@ -57,7 +57,7 @@ public CompoundStream(CompoundDocument document, Stream baseStream, uint baseSec public override long Position { get => Offset - _sectorBufferValidLength + SectorOffset; set => Seek(value, SeekOrigin.Begin); } - private Stream BaseStream { get; set; } + private Stream? BaseStream { get; set; } private CompoundDocument Document { get; } @@ -151,6 +151,13 @@ private void ReadSector() private void ReadMiniSector() { + var baseStream = BaseStream ?? throw new ObjectDisposedException(nameof(CompoundStream)); + + if (RootSectorChain == null) + { + throw new InvalidOperationException("Mini stream sector chain is not initialized."); + } + var sector = SectorChain[SectorChainOffset]; var miniStreamOffset = (int)Document.GetMiniSectorOffset(sector); @@ -163,10 +170,10 @@ private void ReadMiniSector() var rootSector = RootSectorChain[rootSectorIndex]; var rootOffset = miniStreamOffset % Document.Header.SectorSize; - BaseStream.Seek(Document.GetSectorOffset(rootSector) + rootOffset, SeekOrigin.Begin); + baseStream.Seek(Document.GetSectorOffset(rootSector) + rootOffset, SeekOrigin.Begin); var chunkSize = (int)Math.Min(Length - Offset, Document.Header.MiniSectorSize); - if (BaseStream.ReadAtLeast(_sectorBuffer, 0, chunkSize) < chunkSize) + if (baseStream.ReadAtLeast(_sectorBuffer, 0, chunkSize) < chunkSize) { throw new CompoundDocumentException(Errors.ErrorEndOfFile); } @@ -178,11 +185,13 @@ private void ReadMiniSector() private void ReadRegularSector() { + var baseStream = BaseStream ?? throw new ObjectDisposedException(nameof(CompoundStream)); + var sector = SectorChain[SectorChainOffset]; - BaseStream.Seek(Document.GetSectorOffset(sector), SeekOrigin.Begin); + baseStream.Seek(Document.GetSectorOffset(sector), SeekOrigin.Begin); var chunkSize = (int)Math.Min(Length - Offset, Document.Header.SectorSize); - if (BaseStream.ReadAtLeast(_sectorBuffer, 0, chunkSize) < chunkSize) + if (baseStream.ReadAtLeast(_sectorBuffer, 0, chunkSize) < chunkSize) { throw new CompoundDocumentException(Errors.ErrorEndOfFile); } diff --git a/src/ExcelDataReader/Core/CsvFormat/CsvAnalyzer.cs b/src/ExcelDataReader/Core/CsvFormat/CsvAnalyzer.cs index 852bd356..3c24c2e5 100644 --- a/src/ExcelDataReader/Core/CsvFormat/CsvAnalyzer.cs +++ b/src/ExcelDataReader/Core/CsvFormat/CsvAnalyzer.cs @@ -16,8 +16,8 @@ public static void Analyze(Stream stream, char[] separators, Encoding fallbackEn var buffer = new byte[bufferSize]; var bytesRead = stream.ReadAtLeast(buffer, 0, probeSize); - autodetectEncoding = GetEncodingFromBom(buffer, out bomLength); - autodetectEncoding ??= fallbackEncoding; + var detectedEncoding = GetEncodingFromBom(buffer, out bomLength); + autodetectEncoding = detectedEncoding ?? fallbackEncoding; if (separators == null || separators.Length == 0) { @@ -144,7 +144,7 @@ private static void FlushSeparatorsBuffers(char[] separators, SeparatorInfo[] se } } - private static Encoding GetEncodingFromBom(byte[] bom, out int bomLength) + private static Encoding? GetEncodingFromBom(byte[] bom, out int bomLength) { var encodings = new[] { @@ -189,6 +189,6 @@ private sealed class SeparatorInfo public int RowCount { get; set; } - public CsvParser Buffer { get; set; } + public required CsvParser Buffer { get; set; } } } \ No newline at end of file diff --git a/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs b/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs index 6585dd9c..05896455 100644 --- a/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs +++ b/src/ExcelDataReader/Core/CsvFormat/CsvWorkbook.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Text; using ExcelDataReader.Core.NumberFormat; diff --git a/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs b/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs index d54617a1..6a830a97 100644 --- a/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs +++ b/src/ExcelDataReader/Core/CsvFormat/CsvWorksheet.cs @@ -43,11 +43,11 @@ public CsvWorksheet(Stream stream, Encoding fallbackEncoding, char[] autodetectS public string Name => string.Empty; - public string CodeName => null; + public string? CodeName => null; - public string VisibleState => null; + public string? VisibleState => null; - public HeaderFooter HeaderFooter => null; + public HeaderFooter? HeaderFooter => null; public CellRange[] MergeCells => []; @@ -66,7 +66,7 @@ public int RowCount } } - public CellRange Dimension => null; + public CellRange? Dimension => null; public Stream Stream { get; } @@ -78,7 +78,7 @@ public int RowCount public char? EscapeChar { get; } - public List ColumnWidths => null; + public List ColumnWidths => []; public bool TrimWhiteSpace { get; } diff --git a/src/ExcelDataReader/Core/EncodingHelper.cs b/src/ExcelDataReader/Core/EncodingHelper.cs index bf208201..6c03e0c8 100644 --- a/src/ExcelDataReader/Core/EncodingHelper.cs +++ b/src/ExcelDataReader/Core/EncodingHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Text; namespace ExcelDataReader.Core; diff --git a/src/ExcelDataReader/Core/ExtendedFormat.cs b/src/ExcelDataReader/Core/ExtendedFormat.cs index 7676e78e..f4cd54a5 100644 --- a/src/ExcelDataReader/Core/ExtendedFormat.cs +++ b/src/ExcelDataReader/Core/ExtendedFormat.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Core; internal sealed class ExtendedFormat diff --git a/src/ExcelDataReader/Core/Helpers.cs b/src/ExcelDataReader/Core/Helpers.cs index 0f706f21..58ecbcda 100644 --- a/src/ExcelDataReader/Core/Helpers.cs +++ b/src/ExcelDataReader/Core/Helpers.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Globalization; using System.Text; using System.Text.RegularExpressions; diff --git a/src/ExcelDataReader/Core/IWorkbook.cs b/src/ExcelDataReader/Core/IWorkbook.cs index a949d672..22ac93a1 100644 --- a/src/ExcelDataReader/Core/IWorkbook.cs +++ b/src/ExcelDataReader/Core/IWorkbook.cs @@ -1,5 +1,3 @@ -#nullable enable - using ExcelDataReader.Core.NumberFormat; namespace ExcelDataReader.Core; diff --git a/src/ExcelDataReader/Core/IWorksheet.cs b/src/ExcelDataReader/Core/IWorksheet.cs index 4e39163b..517a8421 100644 --- a/src/ExcelDataReader/Core/IWorksheet.cs +++ b/src/ExcelDataReader/Core/IWorksheet.cs @@ -7,17 +7,17 @@ internal interface IWorksheet { string Name { get; } - string CodeName { get; } + string? CodeName { get; } - string VisibleState { get; } + string? VisibleState { get; } - HeaderFooter HeaderFooter { get; } + HeaderFooter? HeaderFooter { get; } int FieldCount { get; } int RowCount { get; } - CellRange Dimension { get; } + CellRange? Dimension { get; } CellRange[] MergeCells { get; } diff --git a/src/ExcelDataReader/Core/NumberFormat/DecimalSection.cs b/src/ExcelDataReader/Core/NumberFormat/DecimalSection.cs index d7b408fd..2bacb8b5 100644 --- a/src/ExcelDataReader/Core/NumberFormat/DecimalSection.cs +++ b/src/ExcelDataReader/Core/NumberFormat/DecimalSection.cs @@ -1,4 +1,6 @@ -namespace ExcelDataReader.Core.NumberFormat; +using System.Diagnostics.CodeAnalysis; + +namespace ExcelDataReader.Core.NumberFormat; internal sealed class DecimalSection { @@ -14,7 +16,7 @@ internal sealed class DecimalSection public required List AfterDecimal { get; init; } - public static bool TryParse(List tokens, out DecimalSection format) + public static bool TryParse(List tokens, [NotNullWhen(true)] out DecimalSection? format) { if (Parser.ParseNumberTokens(tokens, 0, out var beforeDecimal, out var decimalSeparator, out var afterDecimal) == tokens.Count) { @@ -23,9 +25,9 @@ public static bool TryParse(List tokens, out DecimalSection format) format = new DecimalSection() { - BeforeDecimal = beforeDecimal, + BeforeDecimal = beforeDecimal ?? [], DecimalSeparator = decimalSeparator, - AfterDecimal = afterDecimal, + AfterDecimal = afterDecimal ?? [], PercentMultiplier = multiplier, ThousandDivisor = divisor, ThousandSeparator = thousandSeparator diff --git a/src/ExcelDataReader/Core/NumberFormat/ExponentialSection.cs b/src/ExcelDataReader/Core/NumberFormat/ExponentialSection.cs index 36071a56..ce374666 100644 --- a/src/ExcelDataReader/Core/NumberFormat/ExponentialSection.cs +++ b/src/ExcelDataReader/Core/NumberFormat/ExponentialSection.cs @@ -1,4 +1,6 @@ -namespace ExcelDataReader.Core.NumberFormat; +using System.Diagnostics.CodeAnalysis; + +namespace ExcelDataReader.Core.NumberFormat; internal sealed class ExponentialSection { @@ -12,7 +14,7 @@ internal sealed class ExponentialSection public required List Power { get; init; } - public static bool TryParse(List tokens, out ExponentialSection format) + public static bool TryParse(List tokens, [NotNullWhen(true)] out ExponentialSection? format) { format = null; @@ -36,9 +38,9 @@ public static bool TryParse(List tokens, out ExponentialSection format) format = new ExponentialSection() { - BeforeDecimal = beforeDecimal, + BeforeDecimal = beforeDecimal ?? [], DecimalSeparator = decimalSeparator, - AfterDecimal = afterDecimal, + AfterDecimal = afterDecimal ?? [], ExponentialToken = exponentialToken, Power = tokens.GetRange(position, tokens.Count - position) }; diff --git a/src/ExcelDataReader/Core/NumberFormat/FractionSection.cs b/src/ExcelDataReader/Core/NumberFormat/FractionSection.cs index dabdd64d..084da8cf 100644 --- a/src/ExcelDataReader/Core/NumberFormat/FractionSection.cs +++ b/src/ExcelDataReader/Core/NumberFormat/FractionSection.cs @@ -1,28 +1,29 @@ -using System.Globalization; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Text; namespace ExcelDataReader.Core.NumberFormat; internal sealed class FractionSection { - public required List IntegerPart { get; init; } + public required List? IntegerPart { get; init; } public required List Numerator { get; init; } - public required List DenominatorPrefix { get; init; } + public required List? DenominatorPrefix { get; init; } public required List Denominator { get; init; } public required int DenominatorConstant { get; init; } - public required List DenominatorSuffix { get; init; } + public required List? DenominatorSuffix { get; init; } - public required List FractionSuffix { get; init; } + public required List? FractionSuffix { get; init; } - public static bool TryParse(List tokens, out FractionSection format) + public static bool TryParse(List tokens, [NotNullWhen(true)] out FractionSection? format) { - List numeratorParts = null; - List denominatorParts = null; + List? numeratorParts = null; + List? denominatorParts = null; for (var i = 0; i < tokens.Count; i++) { @@ -44,7 +45,7 @@ public static bool TryParse(List tokens, out FractionSection format) GetNumerator(numeratorParts, out var integerPart, out var numeratorPart); - if (!TryGetDenominator(denominatorParts, out var denominatorPrefix, out var denominatorPart, out var denominatorConstant, out var denominatorSuffix, out var fractionSuffix)) + if (denominatorParts == null || !TryGetDenominator(denominatorParts, out var denominatorPrefix, out var denominatorPart, out var denominatorConstant, out var denominatorSuffix, out var fractionSuffix)) { format = null; return false; @@ -64,7 +65,7 @@ public static bool TryParse(List tokens, out FractionSection format) return true; } - private static void GetNumerator(List tokens, out List integerPart, out List numeratorPart) + private static void GetNumerator(List tokens, out List? integerPart, out List numeratorPart) { var hasPlaceholder = false; var hasSpace = false; @@ -109,7 +110,7 @@ private static void GetNumerator(List tokens, out List integerPa } } - private static bool TryGetDenominator(List tokens, out List denominatorPrefix, out List denominatorPart, out int denominatorConstant, out List denominatorSuffix, out List fractionSuffix) + private static bool TryGetDenominator(List tokens, out List? denominatorPrefix, [NotNullWhen(true)] out List? denominatorPart, out int denominatorConstant, out List? denominatorSuffix, out List? fractionSuffix) { var index = 0; var hasPlaceholder = false; diff --git a/src/ExcelDataReader/Core/NumberFormat/NumberFormatString.cs b/src/ExcelDataReader/Core/NumberFormat/NumberFormatString.cs index 645bce19..cdc69947 100644 --- a/src/ExcelDataReader/Core/NumberFormat/NumberFormatString.cs +++ b/src/ExcelDataReader/Core/NumberFormat/NumberFormatString.cs @@ -78,5 +78,5 @@ internal NumberFormatString(string formatString, bool isDateTimeFormat, bool isT private IReadOnlyList
Sections { get; } - private Section GetFirstSection(SectionType type) => Sections.FirstOrDefault(section => section.Type == type); + private Section? GetFirstSection(SectionType type) => Sections.FirstOrDefault(section => section.Type == type); } diff --git a/src/ExcelDataReader/Core/NumberFormat/Parser.cs b/src/ExcelDataReader/Core/NumberFormat/Parser.cs index f2910650..7a874eb4 100644 --- a/src/ExcelDataReader/Core/NumberFormat/Parser.cs +++ b/src/ExcelDataReader/Core/NumberFormat/Parser.cs @@ -4,14 +4,14 @@ namespace ExcelDataReader.Core.NumberFormat; internal static class Parser { - public static Section ParseSection(Tokenizer reader, out bool syntaxError) + public static Section? ParseSection(Tokenizer reader, out bool syntaxError) { bool hasDateParts = false; bool hasDurationParts = false; bool hasGeneralPart = false; bool hasTextPart = false; - Condition condition = null; - Color color = null; + Condition? condition = null; + Color? color = null; List tokens = []; syntaxError = false; @@ -72,10 +72,10 @@ public static Section ParseSection(Tokenizer reader, out bool syntaxError) } SectionType type; - FractionSection fraction = null; - ExponentialSection exponential = null; - DecimalSection number = null; - List generalTextDateDuration = null; + FractionSection? fraction = null; + ExponentialSection? exponential = null; + DecimalSection? number = null; + List? generalTextDateDuration = null; if (hasDateParts) { @@ -135,7 +135,7 @@ public static Section ParseSection(Tokenizer reader, out bool syntaxError) /// Parses as many placeholders and literals needed to format a number with optional decimals. /// Returns number of tokens parsed, or 0 if the tokens didn't form a number. ///
- internal static int ParseNumberTokens(List tokens, int startPosition, out List beforeDecimal, out bool decimalSeparator, out List afterDecimal) + internal static int ParseNumberTokens(List tokens, int startPosition, out List? beforeDecimal, out bool decimalSeparator, out List? afterDecimal) { beforeDecimal = null; afterDecimal = null; @@ -214,7 +214,7 @@ private static void ParseDate(List tokens, out List result) } } - private static string ReadToken(Tokenizer reader, out bool syntaxError) + private static string? ReadToken(Tokenizer reader, out bool syntaxError) { var offset = reader.Position; if ( @@ -279,7 +279,7 @@ private static bool ReadLiteral(Tokenizer reader) return false; } - private static bool TryParseCondition(string token, out Condition result) + private static bool TryParseCondition(string token, out Condition? result) { var tokenizer = new Tokenizer(token); @@ -338,7 +338,7 @@ private static bool ReadConditionValue(Tokenizer tokenizer) return true; } - private static bool TryParseColor(string token, out Color color) + private static bool TryParseColor(string token, out Color? color) { // TODO: Color1..59 var tokenizer = new Tokenizer(token); diff --git a/src/ExcelDataReader/Core/NumberFormat/Section.cs b/src/ExcelDataReader/Core/NumberFormat/Section.cs index 93d8a6bb..68eac4fb 100644 --- a/src/ExcelDataReader/Core/NumberFormat/Section.cs +++ b/src/ExcelDataReader/Core/NumberFormat/Section.cs @@ -4,15 +4,15 @@ internal sealed class Section { public required SectionType Type { get; init; } - public required Color Color { get; init; } + public required Color? Color { get; init; } - public required Condition Condition { get; init; } + public required Condition? Condition { get; init; } - public required ExponentialSection Exponential { get; init; } + public required ExponentialSection? Exponential { get; init; } - public required FractionSection Fraction { get; init; } + public required FractionSection? Fraction { get; init; } - public required DecimalSection Number { get; init; } + public required DecimalSection? Number { get; init; } - public required List GeneralTextDateDurationParts { get; init; } + public required List? GeneralTextDateDurationParts { get; init; } } \ No newline at end of file diff --git a/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryptedPackageStream.cs b/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryptedPackageStream.cs index bf163083..f621fd03 100644 --- a/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryptedPackageStream.cs +++ b/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryptedPackageStream.cs @@ -29,7 +29,7 @@ public AgileEncryptedPackageStream(Stream stream, byte[] key, byte[] iv, Encrypt public override long Position { get => Offset - SegmentLength + SegmentOffset; set => Seek(value, SeekOrigin.Begin); } - private Stream Stream { get; set; } + private Stream? Stream { get; set; } private byte[] Key { get; } @@ -121,11 +121,12 @@ protected override void Dispose(bool disposing) private void ReadSegment() { + var stream = Stream ?? throw new ObjectDisposedException(nameof(AgileEncryptedPackageStream)); var salt = Encryption.GenerateBlockKey(SegmentIndex, IV); // NOTE: +8 skips EncryptedPackage header - Stream.Seek(8 + Offset, SeekOrigin.Begin); - Stream.ReadAtLeast(SegmentBytes, 0, SegmentLength); + stream.Seek(8 + Offset, SeekOrigin.Begin); + stream.ReadAtLeast(SegmentBytes, 0, SegmentLength); using (var cipher = Encryption.CreateCipher()) { diff --git a/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryption.cs b/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryption.cs index db647b03..8397c414 100644 --- a/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryption.cs +++ b/src/ExcelDataReader/Core/OfficeCrypto/AgileEncryption.cs @@ -39,9 +39,9 @@ public AgileEncryption(byte[] bytes) public int HashSize { get; set; } - public byte[] SaltValue { get; set; } + public byte[] SaltValue { get; set; } = []; - public byte[] PasswordSaltValue { get; set; } + public byte[] PasswordSaltValue { get; set; } = []; public CipherIdentifier PasswordCipherAlgorithm { get; set; } @@ -49,11 +49,11 @@ public AgileEncryption(byte[] bytes) public HashIdentifier PasswordHashAlgorithm { get; set; } - public byte[] PasswordEncryptedKeyValue { get; set; } + public byte[] PasswordEncryptedKeyValue { get; set; } = []; - public byte[] PasswordEncryptedVerifierHashInput { get; set; } + public byte[] PasswordEncryptedVerifierHashInput { get; set; } = []; - public byte[] PasswordEncryptedVerifierHashValue { get; set; } + public byte[] PasswordEncryptedVerifierHashValue { get; set; } = []; public int PasswordSpinCount { get; set; } @@ -239,6 +239,9 @@ private void ReadXmlEncryptionInfoStream(XmlReader xmlReader) int.TryParse(xmlReader.GetAttribute("hashSize"), out int hashSize); #pragma warning restore CA1806 // Do not ignore method results + if (cipherAlgorithm == null || cipherChaining == null || hashAlgorithm == null || saltValue == null) + throw new XmlException("Invalid keyData in encryption info."); + SaltValue = Convert.FromBase64String(saltValue); HashSize = hashSize; // given in bytes, also given implicitly by SHA512 KeyBits = keyBits; @@ -308,6 +311,12 @@ private void ReadKeyEncryptor(XmlReader xmlReader) int.TryParse(xmlReader.GetAttribute("keyBits"), out int keyBits); #pragma warning restore CA1806 // Do not ignore method results + if (cipherAlgorithm == null || cipherChaining == null || hashAlgorithm == null || saltValue == null || + encryptedVerifierHashInput == null || encryptedVerifierHashValue == null || encryptedKeyValue == null) + { + throw new XmlException("Invalid encryptedKey in encryption info."); + } + PasswordSaltValue = Convert.FromBase64String(saltValue); PasswordCipherAlgorithm = ParseCipher(cipherAlgorithm/*, blockSize * 8*/); PasswordCipherChaining = ParseCipherMode(cipherChaining); diff --git a/src/ExcelDataReader/Core/OfficeCrypto/RC4Managed.cs b/src/ExcelDataReader/Core/OfficeCrypto/RC4Managed.cs index 77aec5cb..dda7ec40 100644 --- a/src/ExcelDataReader/Core/OfficeCrypto/RC4Managed.cs +++ b/src/ExcelDataReader/Core/OfficeCrypto/RC4Managed.cs @@ -7,12 +7,12 @@ namespace ExcelDataReader.Core.OfficeCrypto; ///
internal sealed class RC4Managed : SymmetricAlgorithm { - public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV) + public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) { return new RC4Transform(rgbKey); } - public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) + public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) { throw new NotImplementedException(); } diff --git a/src/ExcelDataReader/Core/OfficeCrypto/StandardEncryptedPackageStream.cs b/src/ExcelDataReader/Core/OfficeCrypto/StandardEncryptedPackageStream.cs index 703fa3ac..42cde9bf 100644 --- a/src/ExcelDataReader/Core/OfficeCrypto/StandardEncryptedPackageStream.cs +++ b/src/ExcelDataReader/Core/OfficeCrypto/StandardEncryptedPackageStream.cs @@ -19,51 +19,51 @@ public StandardEncryptedPackageStream(Stream underlyingStream, byte[] secretKey, BaseStream = new CryptoStream(underlyingStream, Decryptor, CryptoStreamMode.Read); } - public override bool CanRead => BaseStream.CanRead; + public override bool CanRead => GetBaseStream().CanRead; - public override bool CanSeek => BaseStream.CanSeek; + public override bool CanSeek => GetBaseStream().CanSeek; - public override bool CanWrite => BaseStream.CanWrite; + public override bool CanWrite => GetBaseStream().CanWrite; public override long Length => DecryptedLength; public override long Position { - get => BaseStream.Position; - set => BaseStream.Position = value; + get => GetBaseStream().Position; + set => GetBaseStream().Position = value; } - private CryptoStream BaseStream { get; set; } + private CryptoStream? BaseStream { get; set; } - private SymmetricAlgorithm Cipher { get; set; } + private SymmetricAlgorithm? Cipher { get; set; } - private ICryptoTransform Decryptor { get; set; } + private ICryptoTransform? Decryptor { get; set; } private long DecryptedLength { get; } public override void Flush() { - BaseStream.Flush(); + GetBaseStream().Flush(); } public override int Read(byte[] buffer, int offset, int count) { - return BaseStream.Read(buffer, offset, count); + return GetBaseStream().Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { - return BaseStream.Seek(offset, origin); + return GetBaseStream().Seek(offset, origin); } public override void SetLength(long value) { - BaseStream.SetLength(value); + GetBaseStream().SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { - BaseStream.Write(buffer, offset, count); + GetBaseStream().Write(buffer, offset, count); } protected override void Dispose(bool disposing) @@ -73,7 +73,7 @@ protected override void Dispose(bool disposing) Decryptor?.Dispose(); Decryptor = null; - ((IDisposable)Cipher)?.Dispose(); + Cipher?.Dispose(); Cipher = null; BaseStream?.Dispose(); @@ -82,4 +82,9 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + private CryptoStream GetBaseStream() + { + return BaseStream ?? throw new ObjectDisposedException(nameof(StandardEncryptedPackageStream)); + } } diff --git a/src/ExcelDataReader/Core/OfficeCrypto/XorManaged.cs b/src/ExcelDataReader/Core/OfficeCrypto/XorManaged.cs index 6d63db74..5361e7b2 100644 --- a/src/ExcelDataReader/Core/OfficeCrypto/XorManaged.cs +++ b/src/ExcelDataReader/Core/OfficeCrypto/XorManaged.cs @@ -44,12 +44,12 @@ public XorManaged() { } - public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV) + public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) { return new XorTransform(rgbKey, 0); } - public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) + public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) { throw new NotImplementedException(); } diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffReader.cs index 66999354..3ca853b7 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffReader.cs @@ -1,9 +1,7 @@ -using System.Text; +using System.Text; using ExcelDataReader.Core.OpenXmlFormat.Records; -#nullable enable - namespace ExcelDataReader.Core.OpenXmlFormat.BinaryFormat; internal abstract class BiffReader(Stream stream) : RecordReader diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorkbookReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorkbookReader.cs index 857f82a1..ddee734c 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorkbookReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorkbookReader.cs @@ -1,6 +1,4 @@ -using ExcelDataReader.Core.OpenXmlFormat.Records; - -#nullable enable +using ExcelDataReader.Core.OpenXmlFormat.Records; namespace ExcelDataReader.Core.OpenXmlFormat.BinaryFormat; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorksheetReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorksheetReader.cs index 9ec01d03..ba584cc8 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorksheetReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/BinaryFormat/BiffWorksheetReader.cs @@ -107,7 +107,7 @@ protected override Record ReadOverride(byte[] buffer, uint recordId, uint record // To behave the same as when reading an xml based file. // GetAttribute returns null both if the attribute is missing // or if it is empty. - string codeName = length == 0 ? null : GetString(buffer, 19 + 4, length); + string? codeName = length == 0 ? null : GetString(buffer, 19 + 4, length); return new SheetPrRecord(codeName); } @@ -234,7 +234,7 @@ protected override Record ReadOverride(byte[] buffer, uint recordId, uint record return Record.Default; } - CellRecord ReadCell(object value, CellError? errorValue = null) + CellRecord ReadCell(object? value, CellError? errorValue = null) { int column = (int)GetDWord(buffer, 0); uint xfIndex = GetDWord(buffer, 4) & 0xffffff; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/RecordReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/RecordReader.cs index d9888e63..c1f4dbff 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/RecordReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/RecordReader.cs @@ -1,6 +1,4 @@ -using ExcelDataReader.Core.OpenXmlFormat.Records; - -#nullable enable +using ExcelDataReader.Core.OpenXmlFormat.Records; namespace ExcelDataReader.Core.OpenXmlFormat; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/Records/CellRecord.cs b/src/ExcelDataReader/Core/OpenXmlFormat/Records/CellRecord.cs index c3490797..cb795c94 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/Records/CellRecord.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/Records/CellRecord.cs @@ -1,12 +1,12 @@ namespace ExcelDataReader.Core.OpenXmlFormat.Records; -internal sealed class CellRecord(int columnIndex, int xfIndex, object value, CellError? error) : Record +internal sealed class CellRecord(int columnIndex, int xfIndex, object? value, CellError? error) : Record { public int ColumnIndex { get; } = columnIndex; public int XfIndex { get; } = xfIndex; - public object Value { get; } = value; + public object? Value { get; } = value; public CellError? Error { get; } = error; } diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetPrRecord.cs b/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetPrRecord.cs index 5b3b69d2..fd87c086 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetPrRecord.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetPrRecord.cs @@ -1,6 +1,6 @@ namespace ExcelDataReader.Core.OpenXmlFormat.Records; -internal sealed class SheetPrRecord(string codeName) : Record +internal sealed class SheetPrRecord(string? codeName) : Record { - public string CodeName { get; } = codeName; + public string? CodeName { get; } = codeName; } diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetRecord.cs b/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetRecord.cs index c4535752..c4a4f533 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetRecord.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/Records/SheetRecord.cs @@ -1,14 +1,12 @@ -using System.Globalization; - -#nullable enable +using System.Globalization; namespace ExcelDataReader.Core.OpenXmlFormat.Records; -internal sealed class SheetRecord(string name, uint id, string? rid, string visibleState, string? path) : Record +internal sealed class SheetRecord(string? name, uint id, string? rid, string? visibleState, string? path) : Record { - public string Name { get; } = name; + public string Name { get; } = name ?? string.Empty; - public string VisibleState { get; } = string.IsNullOrEmpty(visibleState) ? "visible" : visibleState.ToLower(CultureInfo.InvariantCulture); + public string VisibleState { get; } = visibleState is { Length: > 0 } state ? state.ToLower(CultureInfo.InvariantCulture) : "visible"; public uint Id { get; } = id; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs index 68f9a194..ed2bc93b 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorkbook.cs @@ -32,9 +32,11 @@ public IEnumerable ReadWorksheets() private void ReadWorkbook() { - using RecordReader reader = _zipWorker.GetWorkbookReader(); + using var reader = _zipWorker.GetWorkbookReader(); + if (reader == null) + return; - while (reader?.Read() is { } record) + while (reader.Read() is { } record) { switch (record) { diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs index 9179ec70..9b2e8986 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XlsxWorksheet.cs @@ -16,13 +16,19 @@ public XlsxWorksheet(ZipWorker document, XlsxWorkbook workbook, SheetRecord refS Path = refSheet.Path; DefaultRowHeight = 15; - if (string.IsNullOrEmpty(Path)) + if (Path is not { Length: > 0 } worksheetPath) + { + ColumnWidths = []; return; + } - using var sheetStream = Document.GetWorksheetReader(Path, !singlePassMode); + using var sheetStream = Document.GetWorksheetReader(worksheetPath, !singlePassMode); if (sheetStream == null) + { + ColumnWidths = []; return; + } int rowIndexMaximum = int.MinValue; int columnIndexMaximum = int.MinValue; @@ -86,21 +92,21 @@ public XlsxWorksheet(ZipWorker document, XlsxWorkbook workbook, SheetRecord refS public int RowCount { get; } - public CellRange Dimension { get; private set; } + public CellRange? Dimension { get; private set; } public string Name { get; } - public string CodeName { get; } + public string? CodeName { get; } public string VisibleState { get; } - public HeaderFooter HeaderFooter { get; } + public HeaderFooter? HeaderFooter { get; } - public CellRange[] MergeCells { get; } + public CellRange[] MergeCells { get; } = []; public List ColumnWidths { get; } - private string Path { get; set; } + private string? Path { get; set; } private double DefaultRowHeight { get; } @@ -110,10 +116,10 @@ public XlsxWorksheet(ZipWorker document, XlsxWorkbook workbook, SheetRecord refS public IEnumerable ReadRows() { - if (string.IsNullOrEmpty(Path)) + if (Path is not { Length: > 0 } worksheetPath) yield break; - using RecordReader sheetStream = Document.GetWorksheetReader(Path, false); + using var sheetStream = Document.GetWorksheetReader(worksheetPath, false); if (sheetStream == null) yield break; @@ -185,7 +191,7 @@ private static bool TryParseToTimeSpan(string s, out TimeSpan result) } } - private object ConvertCellValue(object value, int numberFormatIndex) + private object? ConvertCellValue(object? value, int numberFormatIndex) { switch (value) { @@ -213,13 +219,13 @@ private object ConvertCellValue(object value, int numberFormatIndex) return date; case string s: - NumberFormatString numberFormat = Workbook.GetNumberFormatString(numberFormatIndex, null); - if (numberFormat.IsTimeSpanFormat && TryParseToTimeSpan(s, out var timeSpan)) + NumberFormatString? numberFormat = Workbook.GetNumberFormatString(numberFormatIndex, null); + if (numberFormat?.IsTimeSpanFormat == true && TryParseToTimeSpan(s, out var timeSpan)) { return timeSpan; } - if (numberFormat.IsDateTimeFormat && DateTime.TryParse(s, out DateTime dateTime)) + if (numberFormat?.IsDateTimeFormat == true && DateTime.TryParse(s, out DateTime dateTime)) { return dateTime; } diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/StringHelper.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/StringHelper.cs index df5b856f..bec99951 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/StringHelper.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/StringHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Text; using System.Xml; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlRecordReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlRecordReader.cs index 9d1ce92e..265ef5aa 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlRecordReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlRecordReader.cs @@ -5,13 +5,13 @@ namespace ExcelDataReader.Core.OpenXmlFormat.XmlFormat; internal abstract class XmlRecordReader(XmlReader reader) : RecordReader { - private IEnumerator _enumerator; + private IEnumerator? _enumerator; public XmlProperNamespaces ProperNamespaces { get; set; } = new(reader.IsStartElement() && reader.NamespaceURI == XmlNamespaces.StrictNsSpreadsheetMl); protected XmlReader Reader { get; } = reader; - public override Record Read() + public override Record? Read() { _enumerator ??= ReadOverride().GetEnumerator(); if (_enumerator.MoveNext()) diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlStylesReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlStylesReader.cs index 2c38d386..2db9687f 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlStylesReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlStylesReader.cs @@ -70,7 +70,7 @@ protected override IEnumerable ReadOverride() int.TryParse(Reader.GetAttribute(ANumFmtId), NumberStyles.Integer, CultureInfo.InvariantCulture, out var numFmtId); var formatCode = Reader.GetAttribute(AFormatCode); - yield return new NumberFormatRecord(numFmtId, formatCode); + yield return new NumberFormatRecord(numFmtId, formatCode ?? string.Empty); Reader.Skip(); } else if (!XmlReaderHelper.SkipContent(Reader)) diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorkbookReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorkbookReader.cs index c10a4ff4..8d980731 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorkbookReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorkbookReader.cs @@ -52,9 +52,10 @@ protected override IEnumerable ReadOverride() if (Reader.IsStartElement(ElementSheet, ProperNamespaces.NsSpreadsheetMl)) { var rid = Reader.GetAttribute(AttributeRelationshipId, ProperNamespaces.NsDocumentRelationship); + var sheetId = Reader.GetAttribute(AttributeSheetId); yield return new SheetRecord( Reader.GetAttribute(AttributeName), - uint.Parse(Reader.GetAttribute(AttributeSheetId), CultureInfo.InvariantCulture), + uint.TryParse(sheetId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedSheetId) ? parsedSheetId : 0, rid, Reader.GetAttribute(AttributeVisibleState), rid != null && _worksheetsRels.TryGetValue(rid, out var path) ? path : null); @@ -77,7 +78,7 @@ protected override IEnumerable ReadOverride() { if (Reader.IsStartElement("workbookView", ProperNamespaces.NsSpreadsheetMl)) { - string activeTab = Reader.GetAttribute("activeTab"); + string? activeTab = Reader.GetAttribute("activeTab"); int activeTabInt = int.TryParse(activeTab, out var result) ? result : 0; yield return new WorkbookActRecord(activeTabInt); Reader.Skip(); diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorksheetReader.cs b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorksheetReader.cs index 74b3ecf2..25795643 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorksheetReader.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/XmlFormat/XmlWorksheetReader.cs @@ -195,7 +195,8 @@ protected override IEnumerable ReadOverride() if (Reader.IsStartElement(NMergeCell, ProperNamespaces.NsSpreadsheetMl)) { var cellRefs = Reader.GetAttribute(ARef); - yield return new MergeCellRecord(CellRange.Parse(cellRefs)); + if (!string.IsNullOrEmpty(cellRefs)) + yield return new MergeCellRecord(CellRange.Parse(cellRefs)); Reader.Skip(); } @@ -228,8 +229,8 @@ protected override IEnumerable ReadOverride() var customWidth = Reader.GetAttribute(ACustomWidth); var hidden = Reader.GetAttribute(AHidden); - var maxVal = int.Parse(max, CultureInfo.InvariantCulture); - var minVal = int.Parse(min, CultureInfo.InvariantCulture); + var maxVal = int.TryParse(max, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedMax) ? parsedMax : 0; + var minVal = int.TryParse(min, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedMin) ? parsedMin : 0; double.TryParse(width, NumberStyles.Float, CultureInfo.InvariantCulture, out double widthVal); // Note: column indexes need to be converted to be zero-indexed @@ -275,7 +276,7 @@ protected override IEnumerable ReadOverride() } } - private HeaderFooter ReadHeaderFooter(string nsSpreadsheetMl) + private HeaderFooter? ReadHeaderFooter(string nsSpreadsheetMl) { var differentFirst = Reader.GetAttribute(ADifferentFirst) == "1"; var differentOddEven = Reader.GetAttribute(ADifferentOddEven) == "1"; @@ -350,7 +351,7 @@ private CellRecord ReadCell(int nextColumnIndex, string nsSpreadsheetMl) return new CellRecord(columnIndex, xfIndex, null, null); } - object value = null; + object? value = null; CellError? error = null; while (!Reader.EOF) { @@ -374,7 +375,7 @@ private CellRecord ReadCell(int nextColumnIndex, string nsSpreadsheetMl) return new CellRecord(columnIndex, xfIndex, value, error); - static void ConvertCellValue(string rawValue, string aT, out object value, out CellError? error) + static void ConvertCellValue(string rawValue, string? aT, out object? value, out CellError? error) { const NumberStyles style = NumberStyles.Any; diff --git a/src/ExcelDataReader/Core/OpenXmlFormat/ZipWorker.cs b/src/ExcelDataReader/Core/OpenXmlFormat/ZipWorker.cs index 627f23bf..e4be57ee 100644 --- a/src/ExcelDataReader/Core/OpenXmlFormat/ZipWorker.cs +++ b/src/ExcelDataReader/Core/OpenXmlFormat/ZipWorker.cs @@ -3,8 +3,6 @@ using ExcelDataReader.Core.OpenXmlFormat.BinaryFormat; using ExcelDataReader.Core.OpenXmlFormat.XmlFormat; -#nullable enable - namespace ExcelDataReader.Core.OpenXmlFormat; internal sealed partial class ZipWorker : IDisposable diff --git a/src/ExcelDataReader/Core/ReferenceHelper.cs b/src/ExcelDataReader/Core/ReferenceHelper.cs index f85e7e8d..b121f3e7 100644 --- a/src/ExcelDataReader/Core/ReferenceHelper.cs +++ b/src/ExcelDataReader/Core/ReferenceHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Core; internal static class ReferenceHelper diff --git a/src/ExcelDataReader/Core/Row.cs b/src/ExcelDataReader/Core/Row.cs index 5ea5069b..13562b85 100644 --- a/src/ExcelDataReader/Core/Row.cs +++ b/src/ExcelDataReader/Core/Row.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Core; /// diff --git a/src/ExcelDataReader/ExcelBinaryReader.cs b/src/ExcelDataReader/ExcelBinaryReader.cs index 7d6d0b97..b4dc3665 100644 --- a/src/ExcelDataReader/ExcelBinaryReader.cs +++ b/src/ExcelDataReader/ExcelBinaryReader.cs @@ -5,7 +5,7 @@ namespace ExcelDataReader; internal sealed class ExcelBinaryReader : ExcelDataReader { - public ExcelBinaryReader(Stream stream, string password, Encoding fallbackEncoding, bool singlePassMode = false) + public ExcelBinaryReader(Stream stream, string? password, Encoding fallbackEncoding, bool singlePassMode = false) { Workbook = new XlsWorkbook(stream, password, fallbackEncoding); Workbook.SinglePassMode = singlePassMode; diff --git a/src/ExcelDataReader/ExcelDataReader.cs b/src/ExcelDataReader/ExcelDataReader.cs index e193de8e..7db93066 100644 --- a/src/ExcelDataReader/ExcelDataReader.cs +++ b/src/ExcelDataReader/ExcelDataReader.cs @@ -1,6 +1,6 @@ -using System.Data; -#if NET8_0_OR_GREATER +using System.Data; using System.Diagnostics.CodeAnalysis; +#if NET8_0_OR_GREATER #endif using ExcelDataReader.Core; @@ -15,10 +15,10 @@ internal abstract class ExcelDataReader : IExcelDataReade where TWorkbook : IWorkbook where TWorksheet : IWorksheet { - private IEnumerator _worksheetIterator; - private IEnumerator _rowIterator; - private IEnumerator _cachedWorksheetIterator; - private List _cachedWorksheets; + private IEnumerator? _worksheetIterator; + private IEnumerator? _rowIterator; + private IEnumerator? _cachedWorksheetIterator; + private List? _cachedWorksheets; private int _idx; private bool _singlePassMode; @@ -27,20 +27,20 @@ internal abstract class ExcelDataReader : IExcelDataReade Dispose(false); } - public string Name => _worksheetIterator?.Current?.Name; + public string? Name => _worksheetIterator?.Current?.Name; - public string CodeName => _worksheetIterator?.Current?.CodeName; + public string? CodeName => _worksheetIterator?.Current?.CodeName; - public string VisibleState => _worksheetIterator?.Current?.VisibleState; + public string? VisibleState => _worksheetIterator?.Current?.VisibleState; - public int ActiveSheet => this.Workbook.ActiveSheet; + public int ActiveSheet => GetWorkbook().ActiveSheet; - public bool IsActiveSheet => _idx == this.Workbook.ActiveSheet; + public bool IsActiveSheet => _idx == GetWorkbook().ActiveSheet; - public HeaderFooter HeaderFooter => _worksheetIterator?.Current?.HeaderFooter; + public HeaderFooter? HeaderFooter => _worksheetIterator?.Current?.HeaderFooter; // We shouldn't expose the internal array here. - public CellRange[] MergeCells => _worksheetIterator?.Current?.MergeCells; + public CellRange[] MergeCells => _worksheetIterator?.Current?.MergeCells ?? []; public int Depth => 0; @@ -56,7 +56,7 @@ internal abstract class ExcelDataReader : IExcelDataReade ? throw new InvalidOperationException("RowCount is not available in SinglePassMode.") : (_worksheetIterator?.Current?.RowCount ?? 0); - public CellRange Dimension => _worksheetIterator?.Current?.Dimension; + public CellRange? Dimension => _worksheetIterator?.Current?.Dimension; public int RecordsAffected => throw new NotSupportedException(); @@ -64,9 +64,9 @@ internal abstract class ExcelDataReader : IExcelDataReade protected bool SinglePassMode { set => _singlePassMode = value; } - protected TWorkbook Workbook { get; set; } + protected TWorkbook? Workbook { get; set; } - private Cell?[] RowCells { get; set; } + private Cell?[]? RowCells { get; set; } public object this[int i] => GetValue(i); @@ -76,12 +76,12 @@ internal abstract class ExcelDataReader : IExcelDataReade public byte GetByte(int i) => (byte)GetValue(i); - public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); public char GetChar(int i) => (char)GetValue(i); - public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); public IDataReader GetData(int i) => throw new NotSupportedException(); @@ -97,7 +97,7 @@ public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, i #if NET8_0_OR_GREATER [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] #endif - public Type GetFieldType(int i) => GetValue(i)?.GetType(); + public Type GetFieldType(int i) => GetValue(i)?.GetType() ?? typeof(DBNull); public float GetFloat(int i) => (float)GetValue(i); @@ -123,7 +123,7 @@ public object GetValue(int i) if (RowCells == null) throw new InvalidOperationException("No data exists for the row/column."); - return RowCells[i]?.Value; + return RowCells[i]?.Value ?? DBNull.Value; } public int GetValues(object[] values) @@ -134,30 +134,30 @@ public int GetValues(object[] values) int readingLenth = values.Length > FieldCount ? FieldCount : values.Length; for (int i = 0; i < readingLenth; i++) { - values[i] = RowCells[i]?.Value; + values[i] = RowCells[i]?.Value ?? DBNull.Value; } return readingLenth; } - public bool IsDBNull(int i) => GetValue(i) == null; + public bool IsDBNull(int i) => GetValue(i) is DBNull; - public string GetNumberFormatString(int i) + public string? GetNumberFormatString(int i) { if (RowCells == null) throw new InvalidOperationException("No data exists for the row/column."); if (RowCells[i]?.EffectiveStyle is not { } effectiveStyle) return null; - return Workbook.GetNumberFormatString(effectiveStyle.NumberFormatIndex, null)?.FormatString; + return GetWorkbook().GetNumberFormatString(effectiveStyle.NumberFormatIndex, null)?.FormatString; } - public string GetNumberFormatString(int i, IFormatProvider provider) + public string? GetNumberFormatString(int i, IFormatProvider? provider) { if (RowCells == null) throw new InvalidOperationException("No data exists for the row/column."); if (RowCells[i]?.EffectiveStyle is not { } effectiveStyle) return null; - return Workbook.GetNumberFormatString(effectiveStyle.NumberFormatIndex, provider)?.FormatString; + return GetWorkbook().GetNumberFormatString(effectiveStyle.NumberFormatIndex, provider)?.FormatString; } public int GetNumberFormatIndex(int i) @@ -342,6 +342,11 @@ private IEnumerable ReadWorksheetsWithCache() _cachedWorksheets = []; } + if (Workbook is null) + { + yield break; + } + _cachedWorksheetIterator ??= Workbook.ReadWorksheets().GetEnumerator(); while (_cachedWorksheetIterator.MoveNext()) @@ -365,7 +370,11 @@ private void ReadCurrentRow() Array.Clear(RowCells, 0, RowCells.Length); - foreach (var cell in _rowIterator.Current.Cells) + var rowIterator = _rowIterator; + if (rowIterator == null) + throw new InvalidOperationException("No data exists for the row/column."); + + foreach (var cell in rowIterator.Current.Cells) { if (cell.ColumnIndex >= RowCells.Length) { @@ -384,4 +393,9 @@ private void ReadCurrentRow() RowCells[cell.ColumnIndex] = cell; } } + + private TWorkbook GetWorkbook() + { + return Workbook ?? throw new InvalidOperationException("Workbook is not initialized."); + } } diff --git a/src/ExcelDataReader/ExcelOpenXmlReader.cs b/src/ExcelDataReader/ExcelOpenXmlReader.cs index be5f88d5..590a644f 100644 --- a/src/ExcelDataReader/ExcelOpenXmlReader.cs +++ b/src/ExcelDataReader/ExcelOpenXmlReader.cs @@ -15,7 +15,7 @@ public ExcelOpenXmlReader(Stream stream, bool singlePassMode = false) Reset(); } - private ZipWorker Document { get; set; } + private ZipWorker? Document { get; set; } public override void Close() { diff --git a/src/ExcelDataReader/ExcelReaderConfiguration.cs b/src/ExcelDataReader/ExcelReaderConfiguration.cs index c21e3cd1..a3c995f8 100644 --- a/src/ExcelDataReader/ExcelReaderConfiguration.cs +++ b/src/ExcelDataReader/ExcelReaderConfiguration.cs @@ -16,7 +16,7 @@ public class ExcelReaderConfiguration /// /// Gets or sets the password used to open password protected workbooks. /// - public string Password { get; set; } + public string? Password { get; set; } /// /// Gets or sets an array of CSV separator candidates. The reader autodetects which best fits the input data. Default: , ; TAB | # (CSV only). diff --git a/src/ExcelDataReader/ExcelReaderFactory.cs b/src/ExcelDataReader/ExcelReaderFactory.cs index ce761ad7..8811ab5f 100644 --- a/src/ExcelDataReader/ExcelReaderFactory.cs +++ b/src/ExcelDataReader/ExcelReaderFactory.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using ExcelDataReader.Core.BinaryFormat; using ExcelDataReader.Core.CompoundFormat; using ExcelDataReader.Core.OfficeCrypto; @@ -22,7 +23,7 @@ public static class ExcelReaderFactory /// The file stream. /// The configuration object. /// The excel data reader. - public static IExcelDataReader CreateReader(Stream fileStream, ExcelReaderConfiguration configuration = null) + public static IExcelDataReader CreateReader(Stream fileStream, ExcelReaderConfiguration? configuration = null) { configuration ??= new ExcelReaderConfiguration(); fileStream = PrepareInputStream(fileStream, configuration); @@ -69,7 +70,7 @@ public static IExcelDataReader CreateReader(Stream fileStream, ExcelReaderConfig /// The file stream. /// The configuration object. /// The excel data reader. - public static IExcelDataReader CreateBinaryReader(Stream fileStream, ExcelReaderConfiguration configuration = null) + public static IExcelDataReader CreateBinaryReader(Stream fileStream, ExcelReaderConfiguration? configuration = null) { configuration ??= new ExcelReaderConfiguration(); fileStream = PrepareInputStream(fileStream, configuration); @@ -107,7 +108,7 @@ public static IExcelDataReader CreateBinaryReader(Stream fileStream, ExcelReader /// The file stream. /// The reader configuration -or- to use the default configuration. /// The excel data reader. - public static IExcelDataReader CreateOpenXmlReader(Stream fileStream, ExcelReaderConfiguration configuration = null) + public static IExcelDataReader CreateOpenXmlReader(Stream fileStream, ExcelReaderConfiguration? configuration = null) { configuration ??= new ExcelReaderConfiguration(); fileStream = PrepareInputStream(fileStream, configuration); @@ -144,7 +145,7 @@ public static IExcelDataReader CreateOpenXmlReader(Stream fileStream, ExcelReade /// The file stream. /// The reader configuration -or- to use the default configuration. /// The excel data reader. - public static IExcelDataReader CreateCsvReader(Stream fileStream, ExcelReaderConfiguration configuration = null) + public static IExcelDataReader CreateCsvReader(Stream fileStream, ExcelReaderConfiguration? configuration = null) { configuration ??= new ExcelReaderConfiguration(); fileStream = PrepareInputStream(fileStream, configuration); @@ -152,7 +153,7 @@ public static IExcelDataReader CreateCsvReader(Stream fileStream, ExcelReaderCon return new ExcelCsvReader(fileStream, configuration.FallbackEncoding, configuration.AutodetectSeparators, configuration.AnalyzeInitialCsvRows, configuration.QuoteChar, configuration.TrimWhiteSpace, configuration.EscapeChar); } - private static bool TryGetWorkbook(Stream fileStream, CompoundDocument document, out Stream stream) + private static bool TryGetWorkbook(Stream fileStream, CompoundDocument document, [NotNullWhen(true)] out Stream? stream) { var workbookEntry = document.FindEntry(DirectoryEntryWorkbook, DirectoryEntryBook); if (workbookEntry != null) @@ -170,7 +171,7 @@ private static bool TryGetWorkbook(Stream fileStream, CompoundDocument document, return false; } - private static bool TryGetEncryptedPackage(Stream fileStream, CompoundDocument document, string password, out Stream stream) + private static bool TryGetEncryptedPackage(Stream fileStream, CompoundDocument document, string? password, [NotNullWhen(true)] out Stream? stream) { var encryptedPackage = document.FindEntry(DirectoryEntryEncryptedPackage); var encryptionInfo = document.FindEntry(DirectoryEntryEncryptionInfo); diff --git a/src/ExcelDataReader/HeaderFooter.cs b/src/ExcelDataReader/HeaderFooter.cs index 72fabc7e..2490c3ef 100644 --- a/src/ExcelDataReader/HeaderFooter.cs +++ b/src/ExcelDataReader/HeaderFooter.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader; /// diff --git a/src/ExcelDataReader/IExcelDataReader.cs b/src/ExcelDataReader/IExcelDataReader.cs index 65869e2b..533a4880 100644 --- a/src/ExcelDataReader/IExcelDataReader.cs +++ b/src/ExcelDataReader/IExcelDataReader.cs @@ -10,17 +10,17 @@ public interface IExcelDataReader : IDataReader /// /// Gets the sheet name. /// - string Name { get; } + string? Name { get; } /// /// Gets the sheet VBA code name. /// - string CodeName { get; } + string? CodeName { get; } /// /// Gets the sheet visible state. /// - string VisibleState { get; } + string? VisibleState { get; } /// /// Gets the active sheet. @@ -35,7 +35,7 @@ public interface IExcelDataReader : IDataReader /// /// Gets the sheet header and footer -or- if none set. /// - HeaderFooter HeaderFooter { get; } + HeaderFooter? HeaderFooter { get; } /// /// Gets the list of merged cell ranges, or an empty array if there are none. @@ -56,7 +56,7 @@ public interface IExcelDataReader : IDataReader /// Gets the dimension of the current result. /// /// Main use case is analysis of clipboard data. Potentially unreliable in other use cases. - CellRange Dimension { get; } + CellRange? Dimension { get; } /// /// Gets the height of the current row in points. @@ -74,7 +74,7 @@ public interface IExcelDataReader : IDataReader /// /// The index of the field to find. /// The number format string of the specified field. - string GetNumberFormatString(int i); + string? GetNumberFormatString(int i); /// /// Gets the number format for the specified field using locale-dependent format patterns, @@ -89,7 +89,7 @@ public interface IExcelDataReader : IDataReader /// equivalent to calling . /// /// The number format string of the specified field. - string GetNumberFormatString(int i, IFormatProvider provider); + string? GetNumberFormatString(int i, IFormatProvider? provider); /// /// Gets the number format index for the specified field -or- -1 if there is no value. diff --git a/src/ExcelDataReader/Misc/DateTimeHelper.cs b/src/ExcelDataReader/Misc/DateTimeHelper.cs index 786e1b7c..67fa3859 100644 --- a/src/ExcelDataReader/Misc/DateTimeHelper.cs +++ b/src/ExcelDataReader/Misc/DateTimeHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader.Misc; internal static class DateTimeHelper diff --git a/src/ExcelDataReader/Misc/LeaveOpenStream.cs b/src/ExcelDataReader/Misc/LeaveOpenStream.cs index cf439db5..20f581d9 100644 --- a/src/ExcelDataReader/Misc/LeaveOpenStream.cs +++ b/src/ExcelDataReader/Misc/LeaveOpenStream.cs @@ -1,28 +1,24 @@ -#nullable enable - namespace ExcelDataReader.Misc; internal sealed class LeaveOpenStream(Stream baseStream) : Stream { - public override bool CanRead => BaseStream.CanRead; - - public override bool CanSeek => BaseStream.CanSeek; + public override bool CanRead => baseStream.CanRead; - public override bool CanWrite => BaseStream.CanWrite; + public override bool CanSeek => baseStream.CanSeek; - public override long Length => BaseStream.Length; + public override bool CanWrite => baseStream.CanWrite; - public override long Position { get => BaseStream.Position; set => BaseStream.Position = value; } + public override long Length => baseStream.Length; - private Stream BaseStream { get; } = baseStream; + public override long Position { get => baseStream.Position; set => baseStream.Position = value; } - public override void Flush() => BaseStream.Flush(); + public override void Flush() => baseStream.Flush(); - public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count); + public override int Read(byte[] buffer, int offset, int count) => baseStream.Read(buffer, offset, count); - public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin); + public override long Seek(long offset, SeekOrigin origin) => baseStream.Seek(offset, origin); - public override void SetLength(long value) => BaseStream.SetLength(value); + public override void SetLength(long value) => baseStream.SetLength(value); - public override void Write(byte[] buffer, int offset, int count) => BaseStream.Write(buffer, offset, count); + public override void Write(byte[] buffer, int offset, int count) => baseStream.Write(buffer, offset, count); } diff --git a/src/ExcelDataReader/StreamExtensions.cs b/src/ExcelDataReader/StreamExtensions.cs index 29a7f3f9..03bce723 100644 --- a/src/ExcelDataReader/StreamExtensions.cs +++ b/src/ExcelDataReader/StreamExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace ExcelDataReader; internal static class StreamExtensions