diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 87ef286d..64bee564 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,3 +1,10 @@
+### 5.1.0+4732d22 (Released 2024-1-24)
+* Additions:
+ * Increase Parser speed
+ * [[#4732d22](https://github.com/CSBiology/FsSpreadsheet/commit/4732d22e6acbf70b284355149fd4d06932023783)] include parse speed test in js
+ * [[#9aa368f](https://github.com/CSBiology/FsSpreadsheet/commit/9aa368f3cefeaa50c9eb4c13865bb952a57a273c)] add speedtest for excel file reader
+ * [[#f401c96](https://github.com/CSBiology/FsSpreadsheet/commit/f401c9618bfd66648688cfbb8b99e51f46b14bdd)] make RescanRows speed linear
+
### 5.0.2+41eca2f (Released 2023-11-3)
* Bugfixes:
* [[#b45db41](https://github.com/CSBiology/FsSpreadsheet/commit/b45db41a89e78576999afd28a2d7b1e087caf335)] Fxi critical bug in fable compatibility
diff --git a/package.json b/package.json
index 257e29db..4a3608e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@fslab/fsspreadsheet",
- "version": "5.0.2",
+ "version": "5.1.0",
"description": "Minimal spreadsheet creation and manipulation using exceljs io.",
"type": "module",
"main": "Xlsx.js",
diff --git a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
index 70e717b6..d2ee0bcd 100644
--- a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
+++ b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
@@ -225,7 +225,6 @@ module FsExtensions =
xlsxSheets
|> Seq.map (
fun xlsxSheet ->
- let sheetIndex = Sheet.getSheetIndex xlsxSheet //unused?
let sheetId = Sheet.getID xlsxSheet
let xlsxCells =
Spreadsheet.getCellsBySheetID sheetId doc
diff --git a/src/FsSpreadsheet/FsRow.fs b/src/FsSpreadsheet/FsRow.fs
index 35f8b6cd..368e87cc 100644
--- a/src/FsSpreadsheet/FsRow.fs
+++ b/src/FsSpreadsheet/FsRow.fs
@@ -14,7 +14,7 @@ open Fable.Core
/// The FsCellsCollection must only cover 1 row!
/// if given FsCellsCollection has more than 1 row.
[]
-type FsRow (rangeAddress : FsRangeAddress, cells : FsCellsCollection)=
+type FsRow (rangeAddress : FsRangeAddress, cells : FsCellsCollection) =
inherit FsRangeBase(rangeAddress)
diff --git a/src/FsSpreadsheet/FsWorksheet.fs b/src/FsSpreadsheet/FsWorksheet.fs
index 154eba89..c9323ffc 100644
--- a/src/FsSpreadsheet/FsWorksheet.fs
+++ b/src/FsSpreadsheet/FsWorksheet.fs
@@ -144,10 +144,20 @@ type FsWorksheet (name, ?fsRows, ?fsTables, ?fsCellsCollection) =
///
/// Returns the FsRow at the given FsRangeAddress. If it does not exist, it is created and appended first.
///
- member self.RowWithRange(rangeAddress : FsRangeAddress) =
+ /// If true, the FsRow is created and appended without checking if it already exists.
+ member self.RowWithRange(rangeAddress : FsRangeAddress, ?SkipSearch) =
+ let skipSearch = defaultArg SkipSearch false
if rangeAddress.FirstAddress.RowNumber <> rangeAddress.LastAddress.RowNumber then
failwithf "Row may not have a range address spanning over different row indices"
- self.Row(rangeAddress.FirstAddress.RowNumber).RangeAddress <- rangeAddress
+ if skipSearch then
+ let row = FsRow.createAt(rangeAddress.FirstAddress.RowNumber,self.CellCollection)
+ row.RangeAddress <- rangeAddress
+ _rows.Add row
+ row
+ else
+ let row = self.Row(rangeAddress.FirstAddress.RowNumber)
+ row.RangeAddress <- rangeAddress
+ row
///
/// Appends an FsRow to an FsWorksheet if the rowIndex is not already taken.
@@ -361,7 +371,8 @@ type FsWorksheet (name, ?fsRows, ?fsTables, ?fsCellsCollection) =
| Some row ->
row.RangeAddress <- newRange
| None ->
- self.RowWithRange(newRange)
+ // Use SkipSearch flag to avoid checking if row already exists with linear speed, as this is already done in the tryFind above
+ self.RowWithRange(newRange,true) |> ignore
)
diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/FsWorkbook.fs b/tests/FsSpreadsheet.ExcelIO.Tests/FsWorkbook.fs
index ed717270..c320968b 100644
--- a/tests/FsSpreadsheet.ExcelIO.Tests/FsWorkbook.fs
+++ b/tests/FsSpreadsheet.ExcelIO.Tests/FsWorkbook.fs
@@ -65,10 +65,23 @@ let writeAndReadBytes =
)
]
+let performance =
+ testList "Performace" [
+ testCase "ReadBigFile" (fun () ->
+ let sw = Stopwatch()
+ let p = "./TestFiles/BigFile.xlsx"
+ sw.Start()
+ let wb = FsWorkbook.fromXlsxFile(p)
+ sw.Stop()
+ Expect.isLessThan sw.Elapsed.Milliseconds 2000 "Elapsed time should be less than 2000ms"
+ Expect.equal (wb.GetWorksheetAt(1).Rows.Count) 153991 "Row count should be 153991"
+ )
+ ]
[]
let tests =
testList "FsWorkbook" [
writeAndReadBytes
+ performance
]
\ No newline at end of file
diff --git a/tests/FsSpreadsheet.Exceljs.Tests/Workbook.Tests.fs b/tests/FsSpreadsheet.Exceljs.Tests/Workbook.Tests.fs
index 7ab1e3c6..a7e33a52 100644
--- a/tests/FsSpreadsheet.Exceljs.Tests/Workbook.Tests.fs
+++ b/tests/FsSpreadsheet.Exceljs.Tests/Workbook.Tests.fs
@@ -288,9 +288,24 @@ let tests_xlsx = testList "xlsx" [
]
]
+let performance =
+ testList "Performace" [
+ testCaseAsync "ReadBigFile" <| async {
+ let sw = Stopwatch()
+ let p = DefaultTestObject.BigFile.asRelativePathNode
+ sw.Start()
+ let! wb = FsWorkbook.fromXlsxFile(p) |> Async.AwaitPromise
+ sw.Stop()
+ let ms = sw.Elapsed.Milliseconds
+ Expect.isTrue (ms < 2000) $"Elapsed time should be less than 2000ms but was {ms}ms"
+ Expect.equal (wb.GetWorksheetAt(1).Rows.Count) 153991 "Row count should be 153991"
+ }
+ ]
+
let main = testList "JsWorkbook<->FsWorkbook" [
tests_toFsWorkbook
tests_toJsWorkbook
tests_xlsx
+ performance
]
diff --git a/tests/TestUtils/DefaultTestObjects.fs b/tests/TestUtils/DefaultTestObjects.fs
index 5c51fd86..7108a855 100644
--- a/tests/TestUtils/DefaultTestObjects.fs
+++ b/tests/TestUtils/DefaultTestObjects.fs
@@ -18,6 +18,7 @@ type TestFiles =
| ClosedXML
| FsSpreadsheetNET
| FsSpreadsheetJS
+| BigFile
member this.asFileName =
match this with
@@ -27,6 +28,7 @@ type TestFiles =
| ClosedXML -> "TestWorkbook_ClosedXML.xlsx"
| FsSpreadsheetNET -> "TestWorkbook_FsSpreadsheet.net.xlsx"
| FsSpreadsheetJS -> "TestWorkbook_FsSpreadsheet.js.xlsx"
+ | BigFile -> "BigFile.xlsx"
member this.asRelativePath = $"../TestUtils/{testFolder}/{this.asFileName}"
member this.asRelativePathNode = $"./tests/TestUtils/{testFolder}/{this.asFileName}"
diff --git a/tests/TestUtils/TestFiles/BigFile.xlsx b/tests/TestUtils/TestFiles/BigFile.xlsx
new file mode 100644
index 00000000..5a4a4931
Binary files /dev/null and b/tests/TestUtils/TestFiles/BigFile.xlsx differ
diff --git a/tests/TestUtils/TestFiles/TestWorkbook_FsSpreadsheet_WRITE.js.xlsx b/tests/TestUtils/TestFiles/TestWorkbook_FsSpreadsheet_WRITE.js.xlsx
index 8961d2a3..94856428 100644
Binary files a/tests/TestUtils/TestFiles/TestWorkbook_FsSpreadsheet_WRITE.js.xlsx and b/tests/TestUtils/TestFiles/TestWorkbook_FsSpreadsheet_WRITE.js.xlsx differ
diff --git a/tests/TestUtils/TestUtils.fsproj b/tests/TestUtils/TestUtils.fsproj
index 476f1794..0b4d751e 100644
--- a/tests/TestUtils/TestUtils.fsproj
+++ b/tests/TestUtils/TestUtils.fsproj
@@ -6,24 +6,27 @@
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
diff --git a/tests/TestUtils/TestingUtils.fs b/tests/TestUtils/TestingUtils.fs
index dc2ce95c..b854976d 100644
--- a/tests/TestUtils/TestingUtils.fs
+++ b/tests/TestUtils/TestingUtils.fs
@@ -1,6 +1,8 @@
module TestingUtils
open FsSpreadsheet
+open Fable.Core
+
#if FABLE_COMPILER
open Fable.Mocha
#else
@@ -139,4 +141,20 @@ module Test =
let ftestCaseAsync = ftestCaseAsync
- let testList = testList
\ No newline at end of file
+ let testList = testList
+
+open System
+
+[]
+type Stopwatch() =
+ member val StartTime: DateTime option = None with get, set
+ member val StopTime: DateTime option = None with get, set
+ member this.Start() = this.StartTime <- Some DateTime.Now
+ member this.Stop() =
+ match this.StartTime with
+ | Some _ -> this.StopTime <- Some DateTime.Now
+ | None -> failwith "Error. Unable to call `Stop` before `Start`."
+ member this.Elapsed : TimeSpan =
+ match this.StartTime, this.StopTime with
+ | Some start, Some stop -> stop - start
+ | _, _ -> failwith "Error. Unable to call `Elapsed` without calling `Start` and `Stop` before."
\ No newline at end of file