diff --git a/src/FsSpreadsheet.ExcelIO/Cell.fs b/src/FsSpreadsheet.ExcelIO/Cell.fs
index 47641616..315e88e4 100644
--- a/src/FsSpreadsheet.ExcelIO/Cell.fs
+++ b/src/FsSpreadsheet.ExcelIO/Cell.fs
@@ -36,18 +36,6 @@ module Cell =
///
let setValue (value : string) (cellValue : CellValue) = cellValue.Text <- value
- ///
- /// Takes a DataType and returns the appropriate CellValue.
- ///
- /// DataType is the FsSpreadsheet representation of the CellValue enum in OpenXml.
- let cellValuesFromDataType (dataType : DataType) =
- match dataType with
- | String -> CellValues.String
- | Boolean -> CellValues.Boolean
- | Number -> CellValues.Number
- | Date -> CellValues.Date
- | Empty -> CellValues.Error
-
///
/// Takes a CellValue and returns the appropriate DataType.
///
@@ -97,6 +85,15 @@ module Cell =
let create (dataType : CellValues) (reference : string) (value : CellValue) =
Cell(CellReference = StringValue.FromString reference, DataType = EnumValue(dataType), CellValue = value)
+ ///
+ /// Creates a Cell from a CellValues type case, a "A1" style reference, and a CellValue containing the value string.
+ ///
+ let createWithFormat doc (dataType : CellValues) (reference : string) (cellFormat : CellFormat) (value : CellValue) =
+ let styleSheet = Stylesheet.getOrInit doc
+ let i = Stylesheet.CellFormat.count styleSheet
+ Stylesheet.CellFormat.append cellFormat styleSheet |> ignore
+ Cell(StyleIndex = UInt32Value(uint32 i),CellReference = StringValue.FromString reference, DataType = EnumValue(dataType), CellValue = value)
+
///
/// Sets the preserve attribute of a Cell.
///
@@ -143,20 +140,15 @@ module Cell =
setSpacePreserveAttribute c
else c
- ///
- /// Create a cell using a shared string table, also returns the updated shared string table.
- ///
- let fromValueWithDataType (sharedStringTable : SharedStringTable Option) columnIndex rowIndex (value : string) (dataType : DataType) =
+ let getCellContent (doc : Packaging.SpreadsheetDocument) (value : string) (dataType : DataType) =
+ let sharedStringTable = SharedStringTable.tryGet doc
match dataType with
| DataType.String when sharedStringTable.IsSome->
let sharedStringTable = sharedStringTable.Value
- let reference = CellReference.ofIndices columnIndex (rowIndex)
match SharedStringTable.tryGetIndexByString value sharedStringTable with
| Some i ->
i
|> string
- |> CellValue.create
- |> create CellValues.SharedString reference
| None ->
let updatedSharedStringTable =
sharedStringTable
@@ -165,22 +157,30 @@ module Cell =
updatedSharedStringTable
|> SharedStringTable.count
|> string
- |> CellValue.create
- |> create CellValues.SharedString reference
- |> fun c ->
- if value.EndsWith " " then
- setSpacePreserveAttribute c
- else c
+ |> fun v -> {|DataType = CellValues.SharedString; Value = v; Format = None|}
+ | DataType.String -> {|DataType = CellValues.String; Value = value; Format = None|}
+ | DataType.Boolean -> {|DataType = CellValues.Boolean; Value = value; Format = None|}
+ | DataType.Number -> {|DataType = CellValues.Number; Value = value; Format = None|}
+ | DataType.Date ->
+ let cellFormat = CellFormat(NumberFormatId = UInt32Value 19u, ApplyNumberFormat = BooleanValue true)
+ let value = System.DateTime.Parse(value).ToOADate() |> string
+ {|DataType = CellValues.Number; Value = value; Format = Some cellFormat|}
+ | DataType.Empty -> {|DataType = CellValues.Number; Value = value; Format = None|}
- | _ ->
- let valType = cellValuesFromDataType dataType
- let reference = CellReference.ofIndices columnIndex (rowIndex)
- create valType reference (CellValue.create value)
- |> fun c ->
+ ///
+ /// Create a cell using a shared string table, also returns the updated shared string table.
+ ///
+ let fromValueWithDataType (doc : Packaging.SpreadsheetDocument) columnIndex rowIndex (value : string) (dataType : DataType) =
+ let reference = CellReference.ofIndices columnIndex (rowIndex)
+ let cellContent = getCellContent doc value dataType
+ if cellContent.Format.IsSome then
+ createWithFormat doc cellContent.DataType reference cellContent.Format.Value (CellValue.create cellContent.Value)
+ else
+ create cellContent.DataType reference (CellValue.create cellContent.Value)
+ |> fun c ->
if value.EndsWith " " then
setSpacePreserveAttribute c
else c
-
///
/// Gets "A1"-style Cell reference.
///
diff --git a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
index 1e5a3f75..b9804e3c 100644
--- a/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
+++ b/src/FsSpreadsheet.ExcelIO/FsExtensions.fs
@@ -17,33 +17,53 @@ module FsExtensions =
///
/// Converts a given CellValues to the respective DataType.
///
- static member ofXlsxCellValues (cellValues : CellValues) =
- match cellValues with
- | CellValues.Number -> DataType.Number
- | CellValues.Boolean -> DataType.Boolean
- | CellValues.Date -> DataType.Date
- | CellValues.Error -> DataType.Empty
- | CellValues.InlineString
- | CellValues.SharedString
- | CellValues.String
- | _ -> DataType.String
+ static member ofXlsXCell (doc : Packaging.SpreadsheetDocument) (cell : Cell) =
+
+ //https://stackoverflow.com/a/13178043/12858021
+ //https://stackoverflow.com/a/55425719/12858021
+ // if styleindex is not null and datatype is null we propably have a DateTime field.
+ // if datatype would not be null it could also be boolean, as far as i tested it ~Kevin F 13.10.2023
+ if cell.StyleIndex <> null && cell.DataType = null then
+ try
+ let styleSheet = Stylesheet.get doc
+ let cellFormat : CellFormat = Stylesheet.CellFormat.getAt (int cell.StyleIndex.InnerText) styleSheet
+ if cellFormat <> null then
+ // if numberformatid is between 14 and 18 it is standard date time format.
+ // custom formats are given in the range of 164 to 180, all none default date time formats fall in there.
+ let dateTimeFormats = [14..22]@[164 .. 180] |> List.map (uint32 >> UInt32Value)
+ if List.contains cellFormat.NumberFormatId dateTimeFormats then
+ DataType.Date
+ else
+ DataType.Number
+ else
+ DataType.Number
+ with
+ | _ -> DataType.Number
+ else
+ let cellValues = cell.DataType.Value
+ match cellValues with
+ | CellValues.Number -> DataType.Number
+ | CellValues.Boolean -> DataType.Boolean
+ | CellValues.Date -> DataType.Date
+ | CellValues.Error -> DataType.Empty
+ | CellValues.InlineString
+ | CellValues.SharedString
+ | CellValues.String -> DataType.String
+ | _ -> DataType.Number
type FsCell with
- //member self.ofXlsxCell (sst : Spreadsheet.SharedStringTable option) (xlsxCell:Spreadsheet.Cell) =
- // let v = Cell.getValue sst xlsxCell
- // let row,col = xlsxCell.CellReference.Value |> CellReference.toIndices
- // FsCell.create (int row) (int col) v
///
/// Creates an FsCell on the basis of an XlsxCell. Uses a SharedStringTable if present to get the XlsxCell's value.
- ///
- static member ofXlsxCell (sst : SharedStringTable option) (xlsxCell : Cell) =
+ ///
+ static member ofXlsxCell (doc : Packaging.SpreadsheetDocument) (xlsxCell : Cell) =
+ let sst = Spreadsheet.tryGetSharedStringTable doc
let mutable v = Cell.getValue sst xlsxCell
let setValue x = v <- x
let col, row = xlsxCell.CellReference.Value |> CellReference.toIndices
let dt =
- try DataType.ofXlsxCellValues xlsxCell.DataType.Value
+ try DataType.ofXlsXCell doc xlsxCell
with _ -> DataType.Number // default is number
match dt with
| Date ->
@@ -62,6 +82,9 @@ module FsExtensions =
| _ -> ()
FsCell.createWithDataType dt (int row) (int col) v
+ static member toXlsxCell (doc : Packaging.SpreadsheetDocument) (cell : FsCell) =
+ Cell.fromValueWithDataType doc (uint32 cell.ColumnNumber) (uint32 cell.RowNumber) cell.Value cell.DataType
+
type FsTable with
///
@@ -118,7 +141,7 @@ module FsExtensions =
///
/// Returns the FsWorksheet in the form of an XlsxSpreadsheet.
///
- member self.ToXlsxWorksheet() =
+ member self.ToXlsxWorksheet(doc) =
self.RescanRows()
let sheet = Worksheet.empty()
let sheetData =
@@ -134,7 +157,7 @@ module FsExtensions =
let cells =
cells
|> List.map (fun cell ->
- Cell.fromValueWithDataType None (uint32 cell.ColumnNumber) (uint32 cell.RowNumber) (cell.Value) (cell.DataType)
+ Cell.fromValueWithDataType doc (uint32 cell.ColumnNumber) (uint32 cell.RowNumber) (cell.Value) (cell.DataType)
)
let row = Row.create (uint32 row.Index) (Row.Spans.fromBoundaries min max) cells
SheetData.appendRow row sd |> ignore
@@ -144,8 +167,8 @@ module FsExtensions =
///
/// Returns an FsWorksheet in the form of an XlsxSpreadsheet.
///
- static member toXlsxWorksheet (fsWorksheet : FsWorksheet) =
- fsWorksheet.ToXlsxWorksheet()
+ static member toXlsxWorksheet (fsWorksheet : FsWorksheet, doc) =
+ fsWorksheet.ToXlsxWorksheet(doc)
///
/// Appends the FsTables of this FsWorksheet to a given OpenXmlWorksheetPart in an XlsxWorkbookPart.
@@ -198,25 +221,7 @@ module FsExtensions =
let sheetId = Sheet.getID xlsxSheet
let xlsxCells =
Spreadsheet.getCellsBySheetID sheetId doc
- |> Seq.map (fun c ->
- //https://stackoverflow.com/a/13178043/12858021
- //https://stackoverflow.com/a/55425719/12858021
- // if styleindex is not null and datatype is null we propably have a DateTime field.
- // if datatype would not be null it could also be boolean, as far as i tested it ~Kevin F 13.10.2023
- if c.StyleIndex <> null && c.DataType = null then
- try
- // get cellformat from stylesheet
- let cellFormat : CellFormat = xlsxWorkbookPart.WorkbookStylesPart.Stylesheet.CellFormats.ChildElements.GetItem (int c.StyleIndex.InnerText) :?> CellFormat
- if cellFormat <> null then
- // if numberformatid is between 14 and 18 it is standard date time format.
- // custom formats are given in the range of 164 to 180, all none default date time formats fall in there.
- let dateTimeFormats = [14..22]@[164 .. 180] |> List.map (uint32 >> UInt32Value)
- if List.contains cellFormat.NumberFormatId dateTimeFormats then
- c.DataType <- CellValues.Date
- with
- | _ -> ()
- FsCell.ofXlsxCell sst c
- )
+ |> Seq.map (FsCell.ofXlsxCell doc)
let assocXlsxTables =
xlsxTables
|> Seq.tryPick (fun (sid,ts) -> if sid = sheetId then Some ts else None)
@@ -267,20 +272,24 @@ module FsExtensions =
sr.Close()
wb
- ///
- /// Writes the FsWorkbook into a given MemoryStream.
- ///
- member self.ToStream(stream : MemoryStream) =
- let doc = Spreadsheet.initEmptyOnStream stream
-
+ member self.ToEmptySpreadsheet(doc : Packaging.SpreadsheetDocument) =
+
let workbookPart = Spreadsheet.initWorkbookPart doc
for worksheet in self.GetWorksheets() do
let worksheetPart =
- WorkbookPart.appendWorksheet worksheet.Name (worksheet.ToXlsxWorksheet()) workbookPart
+ WorkbookPart.appendWorksheet worksheet.Name (worksheet.ToXlsxWorksheet(doc)) workbookPart
|> WorkbookPart.getOrInitWorksheetPartByName worksheet.Name
worksheet.AppendTablesToWorksheetPart(workbookPart,worksheetPart)
+
+ ///
+ /// Writes the FsWorkbook into a given MemoryStream.
+ ///
+ member self.ToStream(stream : MemoryStream) =
+ let doc = Spreadsheet.initEmptyOnStream stream
+
+ self.ToEmptySpreadsheet(doc)
//Worksheet.setSheetData sheetData sheet |> ignore
//WorkbookPart.appendWorksheet worksheet.Name sheet workbookPart |> ignore
diff --git a/src/FsSpreadsheet.ExcelIO/FsSpreadsheet.ExcelIO.fsproj b/src/FsSpreadsheet.ExcelIO/FsSpreadsheet.ExcelIO.fsproj
index 47d37afc..41ae4878 100644
--- a/src/FsSpreadsheet.ExcelIO/FsSpreadsheet.ExcelIO.fsproj
+++ b/src/FsSpreadsheet.ExcelIO/FsSpreadsheet.ExcelIO.fsproj
@@ -17,6 +17,7 @@
+
diff --git a/src/FsSpreadsheet.ExcelIO/SharedStringTable.fs b/src/FsSpreadsheet.ExcelIO/SharedStringTable.fs
index fcfd2876..1359fe7a 100644
--- a/src/FsSpreadsheet.ExcelIO/SharedStringTable.fs
+++ b/src/FsSpreadsheet.ExcelIO/SharedStringTable.fs
@@ -92,7 +92,13 @@ module SharedStringTable =
else
index
-
+ ///
+ /// Gets the sharedStringTable of the spreadsheet if it exists, else returns None.
+ ///
+ let tryGet (spreadsheetDocument : SpreadsheetDocument) =
+ try spreadsheetDocument.WorkbookPart.SharedStringTablePart.SharedStringTable |> Some
+ with | _ -> None
+
diff --git a/src/FsSpreadsheet.ExcelIO/Stylesheet.fs b/src/FsSpreadsheet.ExcelIO/Stylesheet.fs
new file mode 100644
index 00000000..0a2503ae
--- /dev/null
+++ b/src/FsSpreadsheet.ExcelIO/Stylesheet.fs
@@ -0,0 +1,61 @@
+namespace FsSpreadsheet.ExcelIO
+
+open DocumentFormat.OpenXml.Spreadsheet
+open DocumentFormat.OpenXml.Packaging
+open DocumentFormat.OpenXml
+
+module Stylesheet =
+
+ //module Font =
+
+ // let getDefault() = Font().Color
+
+
+ let get (doc : SpreadsheetDocument) =
+
+ doc.WorkbookPart.WorkbookStylesPart.Stylesheet
+
+ let getOrInit (doc : SpreadsheetDocument) =
+
+ match doc.WorkbookPart.WorkbookStylesPart with
+ | null ->
+ let ssp = doc.WorkbookPart.AddNewPart()
+ ssp.Stylesheet <- new Stylesheet()
+ ssp.Stylesheet.CellFormats <- new CellFormats()
+ ssp.Stylesheet
+ | ssp -> ssp.Stylesheet
+
+ let tryGet (doc : SpreadsheetDocument) =
+ match doc.WorkbookPart.WorkbookStylesPart with
+ | null -> None
+ | ssp -> Some(ssp.Stylesheet)
+
+ module CellFormat =
+
+ let updateCount (stylesheet : Stylesheet) =
+ let newCount = stylesheet.CellFormats.Elements() |> Seq.length
+ stylesheet.CellFormats.Count <- UInt32Value(uint32 newCount)
+
+ let count (stylesheet : Stylesheet) =
+ if stylesheet.CellFormats = null then 0
+ elif stylesheet.CellFormats.Count = null then 0
+ else stylesheet.CellFormats.Count.Value |> int
+
+ let getAt (index : int) (stylesheet : Stylesheet) =
+ stylesheet.CellFormats.Elements() |> Seq.item index
+
+ let tryGetAt (index : int) (stylesheet : Stylesheet) =
+ stylesheet.CellFormats.Elements() |> Seq.tryItem index
+
+ let setAt (index : int) (cf : CellFormat) (stylesheet : Stylesheet) =
+ if count stylesheet > index then
+ let previousChild = getAt index stylesheet
+ stylesheet.CellFormats.ReplaceChild(cf, previousChild) |> ignore
+ if count stylesheet = index then
+ stylesheet.CellFormats.AppendChild(cf) |> ignore
+ else failwith "Cannot insert style into stylesheet: Index out of range"
+ updateCount stylesheet
+
+ let append (cf : CellFormat) (stylesheet : Stylesheet) =
+ stylesheet.CellFormats.AppendChild(cf) |> ignore
+ updateCount stylesheet
\ No newline at end of file
diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/FsSpreadsheet.ExcelIO.Tests.fsproj b/tests/FsSpreadsheet.ExcelIO.Tests/FsSpreadsheet.ExcelIO.Tests.fsproj
index 59ca306f..30c829b6 100644
--- a/tests/FsSpreadsheet.ExcelIO.Tests/FsSpreadsheet.ExcelIO.Tests.fsproj
+++ b/tests/FsSpreadsheet.ExcelIO.Tests/FsSpreadsheet.ExcelIO.Tests.fsproj
@@ -25,8 +25,13 @@
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/FsSpreadsheet.ExcelIO.Tests/OpenXml/FsExtensions.fs b/tests/FsSpreadsheet.ExcelIO.Tests/OpenXml/FsExtensions.fs
index 7ed25ad2..3dba8572 100644
--- a/tests/FsSpreadsheet.ExcelIO.Tests/OpenXml/FsExtensions.fs
+++ b/tests/FsSpreadsheet.ExcelIO.Tests/OpenXml/FsExtensions.fs
@@ -53,40 +53,44 @@ let testFile2Path = Path.Combine(__SOURCE_DIRECTORY__, "../data", "2EXT02_Protei
[]
let fsExtensionTests =
testList "FsExtensions" [
- testList "DataType" [
- testList "ofXlsxCellValues" [
- let testCvNumber = DataType.ofXlsxCellValues CellValues.Number
- testCase "is correct DataTypeNumber from CellValuesNumber" <| fun _ ->
- Expect.equal testCvNumber DataType.Number "is not the correct DataType"
- let testCvString = DataType.ofXlsxCellValues CellValues.String
- testCase "is correct DataTypeString from CellValuesString" <| fun _ ->
- Expect.equal testCvString DataType.String "is not the correct DataType"
- let testCvSharedString = DataType.ofXlsxCellValues CellValues.SharedString
- testCase "is correct DataTypeString from CellValuesSharedString" <| fun _ ->
- Expect.equal testCvSharedString DataType.String "is not the correct DataType"
- let testCvInlineString = DataType.ofXlsxCellValues CellValues.InlineString
- testCase "is correct DataTypeString from CellValuesInlineString" <| fun _ ->
- Expect.equal testCvInlineString DataType.String "is not the correct DataType"
- let testCvBoolean = DataType.ofXlsxCellValues CellValues.Boolean
- testCase "is correct DataTypeBoolean from CellValuesBoolean" <| fun _ ->
- Expect.equal testCvBoolean DataType.Boolean "is not the correct DataType"
- let testCvDate = DataType.ofXlsxCellValues CellValues.Date
- testCase "is correct DataTypeDate from CellValuesDate" <| fun _ ->
- Expect.equal testCvDate DataType.Date "is not the correct DataType"
- let testCvError = DataType.ofXlsxCellValues CellValues.Error
- testCase "is correct DataTypeEmpty from CellValuesError" <| fun _ ->
- Expect.equal testCvError DataType.Empty "is not the correct DataType"
- ]
- ]
+ //testList "DataType" [
+ // testList "ofXlsxCellValues" [
+ // let stream = new MemoryStream()
+ // let doc = Spreadsheet.initEmptyOnStream stream
+ // let testCvNumber = DataType.ofXlsxCellValues doc CellValues.Number
+ // testCase "is correct DataTypeNumber from CellValuesNumber" <| fun _ ->
+ // Expect.equal testCvNumber DataType.Number "is not the correct DataType"
+ // let testCvString = DataType.ofXlsxCellValues CellValues.String
+ // testCase "is correct DataTypeString from CellValuesString" <| fun _ ->
+ // Expect.equal testCvString DataType.String "is not the correct DataType"
+ // let testCvSharedString = DataType.ofXlsxCellValues CellValues.SharedString
+ // testCase "is correct DataTypeString from CellValuesSharedString" <| fun _ ->
+ // Expect.equal testCvSharedString DataType.String "is not the correct DataType"
+ // let testCvInlineString = DataType.ofXlsxCellValues CellValues.InlineString
+ // testCase "is correct DataTypeString from CellValuesInlineString" <| fun _ ->
+ // Expect.equal testCvInlineString DataType.String "is not the correct DataType"
+ // let testCvBoolean = DataType.ofXlsxCellValues CellValues.Boolean
+ // testCase "is correct DataTypeBoolean from CellValuesBoolean" <| fun _ ->
+ // Expect.equal testCvBoolean DataType.Boolean "is not the correct DataType"
+ // //let testCvDate = DataType.ofXlsxCellValues CellValues.Date
+ // //testCase "is correct DataTypeDate from CellValuesDate" <| fun _ ->
+ // // Expect.equal testCvDate DataType.Date "is not the correct DataType"
+ // let testCvError = DataType.ofXlsxCellValues CellValues.Error
+ // testCase "is correct DataTypeEmpty from CellValuesError" <| fun _ ->
+ // Expect.equal testCvError DataType.Empty "is not the correct DataType"
+ // ]
+ //]
testList "FsCell" [
testList "ofXlsxCell" [
- let testCell = FsCell.ofXlsxCell None dummyXlsxCell
+ let stream = new MemoryStream()
+ let doc = Spreadsheet.initEmptyOnStream stream
+ let testCell = FsCell.ofXlsxCell doc dummyXlsxCell
testCase "is equal in value" <| fun _ ->
Expect.equal testCell.Value dummyXlsxCell.CellValue.Text "values are not equal"
testCase "is equal in address/reference" <| fun _ ->
Expect.equal testCell.Address.Address dummyXlsxCell.CellReference.Value "addresses/references are not equal"
testCase "is equal in DataType/CellValues" <| fun _ ->
- let dtOfCvs = DataType.ofXlsxCellValues dummyXlsxCell.DataType
+ let dtOfCvs = DataType.ofXlsXCell doc dummyXlsxCell
Expect.equal testCell.DataType dtOfCvs "addresses/references are not equal"
]
]