From b2c343579c30ccc47acce37d029c3786a9266110 Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Tue, 25 Jul 2023 14:56:30 +0400 Subject: [PATCH] xlsx reader now parses headerless objects #44 --- src/FsSpreadsheet.ExcelIO/FsExtensions.fs | 3 ++- src/FsSpreadsheet.ExcelIO/Table.fs | 7 ++++++- tests/FsSpreadsheet.ExcelIO.Tests/Table.fs | 9 ++++++++- tests/FsSpreadsheet.ExcelIO.Tests/TestObjects.fs | 5 ++++- .../data/headerlessTable.xlsx | Bin 0 -> 9300 bytes 5 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 tests/FsSpreadsheet.ExcelIO.Tests/data/headerlessTable.xlsx diff --git a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs index 70855167..bb17a4e5 100644 --- a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs +++ b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs @@ -79,7 +79,8 @@ module FsExtensions = let topLeftBoundary, bottomRightBoundary = Table.getArea table |> Table.Area.toBoundaries let ra = FsRangeAddress(FsAddress(topLeftBoundary), FsAddress(bottomRightBoundary)) let totalsRowShown = if table.TotalsRowShown = null then false else table.TotalsRowShown.Value - FsTable(table.Name, ra, totalsRowShown, true) + let showHeaderRow = if table.HeaderRowCount = null then false else table.HeaderRowCount.Value = 1u + FsTable(table.Name, ra, totalsRowShown, showHeaderRow) /// /// Returns the FsWorksheet associated with the FsTable in a given FsWorkbook. diff --git a/src/FsSpreadsheet.ExcelIO/Table.fs b/src/FsSpreadsheet.ExcelIO/Table.fs index 6a585a83..0469fa84 100644 --- a/src/FsSpreadsheet.ExcelIO/Table.fs +++ b/src/FsSpreadsheet.ExcelIO/Table.fs @@ -25,8 +25,13 @@ module Table = /// Given a "A1:A1"-style area, returns A1-based cell start and end cellReferences. let toBoundaries (area : StringValue) = + area.Value.Split ':' - |> fun a -> a.[0], a.[1] + |> fun a -> + if a.Length = 1 then + area.Value, area.Value + else + a.[0], a.[1] /// Gets the right boundary of the area. let rightBoundary (area : StringValue) = diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs b/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs index 4803a568..293a52fb 100644 --- a/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs +++ b/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs @@ -14,7 +14,14 @@ let transformTable = Expect.isTrue (table.TotalsRowShown = null) "Check that field of interest is None" FsTable.fromXlsxTable table |> ignore ) - + testCase "handleNoHeaders" (fun () -> + let doc = Spreadsheet.fromFile TestObjects.headerLessTablePath false + let wsp = doc.WorkbookPart.WorksheetParts |> Seq.head + let t = wsp |> Worksheet.WorksheetPart.getTables |> Seq.head + let parsed = FsTable.fromXlsxTable t + Expect.equal (parsed.RangeAddress.ToString()) "B8:B8" "Range address should be B8:B8" + Expect.isFalse parsed.ShowHeaderRow "ShowHeaderRow should be false" + ) ] diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/TestObjects.fs b/tests/FsSpreadsheet.ExcelIO.Tests/TestObjects.fs index 95ac5425..3ee871c9 100644 --- a/tests/FsSpreadsheet.ExcelIO.Tests/TestObjects.fs +++ b/tests/FsSpreadsheet.ExcelIO.Tests/TestObjects.fs @@ -37,4 +37,7 @@ let sheet2() = FsCell.createWithDataType DataType.Number 2 4 8 ] |> List.iter (fun c -> ws.Row(c.RowNumber).[c.ColumnNumber].SetValueAs c.Value) - ws \ No newline at end of file + ws + +let headerLessTablePath = + System.IO.Path.Combine(__SOURCE_DIRECTORY__, "data/headerLessTable.xlsx") diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/data/headerlessTable.xlsx b/tests/FsSpreadsheet.ExcelIO.Tests/data/headerlessTable.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1e40c32eb434033245f5cbaafac310d4356d97cd GIT binary patch literal 9300 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 b^S?G&kOBe}E