diff --git a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs index 0f78d68c..146d1e4d 100644 --- a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs +++ b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs @@ -53,7 +53,7 @@ module FsExtensions = /// Returns the FsTable with given FsCellsCollection in the form of an XlsxTable. /// member self.ToXlsxTable(cells : FsCellsCollection) = - + self.RescanFieldNames cells let columns = self.GetFieldNames(cells) |> Seq.map (fun kv -> diff --git a/src/FsSpreadsheet/Tables/FsTable.fs b/src/FsSpreadsheet/Tables/FsTable.fs index 2873a705..34bade18 100644 --- a/src/FsSpreadsheet/Tables/FsTable.fs +++ b/src/FsSpreadsheet/Tables/FsTable.fs @@ -12,7 +12,7 @@ type FsTable (name : string, rangeAddress : FsRangeAddress, ?showTotalsRow : boo inherit FsRangeBase(rangeAddress) - let mutable _name = name + let mutable _name = name.Trim().Replace(" ","_") let mutable _lastRangeAddress = rangeAddress let mutable _showTotalsRow : bool = Option.defaultValue false showTotalsRow @@ -396,4 +396,46 @@ type FsTable (name : string, rangeAddress : FsRangeAddress, ?showTotalsRow : boo /// Returns a deep copy of a given FsTable. /// static member copy (table : FsTable) = - table.Copy() \ No newline at end of file + table.Copy() + + + /// Updates the TableFields according to the range of the table and the underlying cellcollection. + /// + /// For this, maps over the range of the table and sets the header of the table fields to the value of the cell. If no cell value is set, the header value and the underlying cell value are set to a default value. + member this.RescanFieldNames(cellsCollection : FsCellsCollection) = + if this.ShowHeaderRow then + let oldFieldNames = _fieldNames + _fieldNames <- new Dictionary() + let headersRow = this.HeadersRow(); + let mutable cellPos = 0 + for cell in headersRow.Cells(cellsCollection) do + let mutable name = cell.Value //GetString(); + match Dictionary.tryGet name oldFieldNames with + | Some tableField -> + tableField.Index <- cellPos + _fieldNames.Add(name,tableField) + cellPos <- cellPos + 1 + | None -> + + // Be careful here. Fields names may actually be whitespace, but not empty + if (name = null) <> (name = "") then // TO DO: ask: shouldn't this be XOR? + + name <- this.GetUniqueName("Column", cellPos + 1, true) + cell.SetValueAs(name) |> ignore + cell.DataType <- DataType.String + + if (_fieldNames.ContainsKey(name)) then + raise (System.ArgumentException("The header row contains more than one field name '" + name + "'.")) + + _fieldNames.Add(name, new FsTableField(name, cellPos)) + cellPos <- cellPos + 1 + else + + let colCount = base.ColumnCount(); + for i = 1 to colCount do + + if _fieldNames.Values |> Seq.exists (fun v -> v.Index = i - 1) |> not then + + let name = "Column" + string i; + + _fieldNames.Add(name, new FsTableField(name, i - 1)); \ No newline at end of file diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs b/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs index 4803a568..58d8e370 100644 --- a/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs +++ b/tests/FsSpreadsheet.ExcelIO.Tests/Table.fs @@ -14,6 +14,57 @@ let transformTable = Expect.isTrue (table.TotalsRowShown = null) "Check that field of interest is None" FsTable.fromXlsxTable table |> ignore ) + testCase "InfersTableColumnsFromRange" (fun () -> + + // --- Prepare Table --- + let wb = new FsWorkbook() + + let ws = wb.InitWorksheet("New Worksheet") + + let columnNames = [|"My Column 1";"My Column 2"|] + + ws.AddCell(FsCell(columnNames.[0],address=FsAddress("B1"))) |> ignore + ws.AddCell(FsCell(columnNames.[1],address=FsAddress("C1"))) |> ignore + let t = FsTable("My_New_Table", FsRangeAddress("B1:C2")) + + ws.AddTable(t) |> ignore + + // --- Function of interest --- + + let bytes = wb.ToBytes() + + // --- Get Tables --- + + let ms = new System.IO.MemoryStream(bytes) + + let doc = Spreadsheet.fromStream ms false + let xlsxWorkbookPart = Spreadsheet.getWorkbookPart doc + + let tables = + Workbook.get xlsxWorkbookPart + |> Sheet.Sheets.get + |> Sheet.Sheets.getSheets + |> Seq.collect ( + fun s -> + let sid = Sheet.getID s + Worksheet.WorksheetPart.getByID sid xlsxWorkbookPart + |> Worksheet.WorksheetPart.getTables + ) + + + // --- Checks --- + Expect.equal (Seq.length tables) 1 "Check that there is one table" + + let table = Seq.head tables + let tableColumnNames = + table.TableColumns + |> Table.TableColumns.getTableColumns + |> Seq.map (fun tc -> Table.TableColumn.getName tc) + + Expect.sequenceEqual tableColumnNames columnNames "Check that column names match" + + + ) ]