diff --git a/.github/workflows/manage-issues.yml b/.github/workflows/manage-issues.yml index da2d3dd0..34b85b6c 100644 --- a/.github/workflows/manage-issues.yml +++ b/.github/workflows/manage-issues.yml @@ -5,7 +5,6 @@ on: types: - opened - reopened - - transferred jobs: label_issues: diff --git a/src/Core/ARCtrl.Core.fsproj b/src/Core/ARCtrl.Core.fsproj index f81266b6..f37a0047 100644 --- a/src/Core/ARCtrl.Core.fsproj +++ b/src/Core/ARCtrl.Core.fsproj @@ -18,6 +18,7 @@ + diff --git a/src/Core/Data.fs b/src/Core/Data.fs index 18649f6d..5c028f45 100644 --- a/src/Core/Data.fs +++ b/src/Core/Data.fs @@ -4,11 +4,28 @@ open ARCtrl open ARCtrl.Helper open Fable.Core +module DataAux = + + let nameFromPathAndSelector (path : string) (selector : string) = + sprintf "%s#%s" path selector + + let pathAndSelectorFromName (name : string) = + let parts = name.Split('#') + if parts.Length = 2 then + parts.[0], Some parts.[1] + else + name, None + [] -type Data(?id,?name,?dataType,?format,?selectorFormat,?comments) = +type Data(?id,?name : string,?dataType,?format,?selectorFormat,?comments) = let mutable _id : URI option = id - let mutable _name : string option = name + let mutable _filePath,_selector = + match name with + | Some n -> + let p,s = DataAux.pathAndSelectorFromName n + Some p, s + | None -> None, None let mutable _dataType : DataFile option = dataType let mutable _format : string option = format let mutable _selectorFormat : URI option = selectorFormat @@ -19,8 +36,28 @@ type Data(?id,?name,?dataType,?format,?selectorFormat,?comments) = and set(id) = _id <- id member this.Name - with get() = _name - and set(name) = _name <- name + with get() = + match _filePath,_selector with + | Some p, Some s -> DataAux.nameFromPathAndSelector p s |> Some + | Some p, None -> p |> Some + | None, _ -> None + and set(name) = + match name with + | Some n -> + let p,s = DataAux.pathAndSelectorFromName n + _filePath <- Some p + _selector <- s + | None -> + _filePath <- None + _selector <- None + + member this.FilePath + with get() = _filePath + and set(filePath) = _filePath <- filePath + + member this.Selector + with get() = _selector + and set(selector) = _selector <- selector member this.DataType with get() = _dataType @@ -36,8 +73,7 @@ type Data(?id,?name,?dataType,?format,?selectorFormat,?comments) = member this.Comments with get() = _comments - and set(comments) = _comments <- comments - + and set(comments) = _comments <- comments static member make id name dataType format selectorFormat comments = Data(?id=id,?name=name,?dataType=dataType,?format=format,?selectorFormat=selectorFormat,?comments=comments) diff --git a/src/Core/DataContext.fs b/src/Core/DataContext.fs new file mode 100644 index 00000000..58cb3fdc --- /dev/null +++ b/src/Core/DataContext.fs @@ -0,0 +1,74 @@ +namespace ARCtrl + +open ARCtrl +open ARCtrl.Helper +open Fable.Core + +type DataContext(?id,?name : string,?dataType,?format,?selectorFormat, ?explication, ?unit, ?objectType, ?description, ?generatedBy, ?comments) = + + inherit Data(?id = id,?name = name, ?dataType = dataType, ?format = format, ?selectorFormat = selectorFormat, ?comments = comments) + + let mutable _explication : OntologyAnnotation option = explication + let mutable _unit : OntologyAnnotation option = unit + let mutable _objectType : OntologyAnnotation option = objectType + let mutable _description : string option = description + let mutable _generatedBy : string option = generatedBy + + member this.Explication + with get() = _explication + and set(explication) = _explication <- explication + + member this.Unit + with get() = _unit + and set(unit) = _unit <- unit + + member this.ObjectType + with get() = _objectType + and set(objectType) = _objectType <- objectType + + member this.Description + with get() = _description + and set(description) = _description <- description + + member this.GeneratedBy + with get() = _generatedBy + and set(generatedBy) = _generatedBy <- generatedBy + + + member this.AsData() = + Data(?id = this.ID,?name = this.Name, ?dataType = this.DataType, ?format = this.Format, ?selectorFormat = this.SelectorFormat, comments = this.Comments) + + member this.Copy() = + let copy = new DataContext() + copy.ID <- this.ID + copy.Name <- this.Name + copy.DataType <- this.DataType + copy.Format <- this.Format + copy.SelectorFormat <- this.SelectorFormat + copy.Explication <- this.Explication + copy.Unit <- this.Unit + copy.ObjectType <- this.ObjectType + copy.Description <- this.Description + copy.GeneratedBy <- this.GeneratedBy + copy.Comments <- this.Comments + copy + + override this.GetHashCode() = + [| + HashCodes.boxHashOption this.ID + HashCodes.boxHashOption this.Name + HashCodes.boxHashOption this.DataType + HashCodes.boxHashOption this.Format + HashCodes.boxHashOption this.SelectorFormat + HashCodes.boxHashSeq this.Comments + HashCodes.boxHashOption this.Explication + HashCodes.boxHashOption this.Unit + HashCodes.boxHashOption this.ObjectType + HashCodes.boxHashOption this.Description + HashCodes.boxHashOption this.GeneratedBy + |] + |> HashCodes.boxHashArray + |> fun x -> x :?> int + + override this.Equals(obj) = + HashCodes.hash this = HashCodes.hash obj \ No newline at end of file diff --git a/src/Core/Table/DataMap.fs b/src/Core/Table/DataMap.fs index 3b7fedb3..24e8b508 100644 --- a/src/Core/Table/DataMap.fs +++ b/src/Core/Table/DataMap.fs @@ -8,7 +8,7 @@ open Fable.Core [] module DataMapAux = - + [] let dataMapName = "DataMap" @@ -51,88 +51,106 @@ module DataMapAux = let allowedHeaders = [dataHeader; explicationHeader; unitHeader; objectTypeHeader; descriptionHeader; generatedByHeader] - let validate (headers : ResizeArray) (values : System.Collections.Generic.Dictionary) (raiseException : bool) = - let headersAreValid = - headers - |> Seq.exists (fun h -> - let hasForeignHeader = - not (allowedHeaders |> List.exists (fun ah -> ah = h)) - if raiseException && hasForeignHeader then - failwithf "Header %O is not allowed in DataMap" h - hasForeignHeader - ) - let tableIsValid = ArcTableAux.SanityChecks.validate headers values raiseException - headersAreValid && tableIsValid - -type DataMap(headers: ResizeArray, values: System.Collections.Generic.Dictionary) = - - let _ = DataMapAux.validate headers values true + let getOntologyColumn (f : DataContext -> OntologyAnnotation option) (dataContexts : ResizeArray) = + dataContexts + |> Seq.map (fun dc -> + match f dc with + | Some s -> CompositeCell.Term(s) + | None -> CompositeCell.emptyTerm + ) + |> ResizeArray + + let getStringColumn (f : DataContext -> string option) (dataContexts : ResizeArray) = + dataContexts + |> Seq.map (fun dc -> + match f dc with + | Some s -> CompositeCell.FreeText(s) + | None -> CompositeCell.emptyFreeText + ) + |> ResizeArray + + let setOntologyColumn (f : DataContext -> OntologyAnnotation option -> unit) (column : CompositeCell []) (dataContexts : ResizeArray) = + column + |> Seq.iteri (fun i cell -> + match cell with + | CompositeCell.Term s -> f (dataContexts.[i]) (Some s) + | _ -> () + ) - let table = ArcTable(DataMapAux.dataMapName, headers, values) - let mutable staticHash = 0 + let setStringColumn (f : DataContext -> string option -> unit) (column : CompositeCell []) (dataContexts : ResizeArray) = + column + |> Seq.iteri (fun i cell -> + match cell with + | CompositeCell.FreeText s -> f (dataContexts.[i]) (Some s) + | _ -> () + ) - member this.Headers = table.Headers - member this.Values = table.Values - member this.StaticHash with get() = staticHash and set(value) = staticHash <- value + module SanityChecks = + + let rowIndexInBoundaries (row : int) (dataContexts : ResizeArray) = + if row < 0 then + failwith "Row index must be greater or equal to 0." + if row >= dataContexts.Count then + failwith "Row index must be less than the number of rows." - static member init() = DataMap(ResizeArray(),Dictionary()) + let lengthOfNewColumn (newColumn : CompositeCell []) (dataContexts : ResizeArray) = + if newColumn.Length <> dataContexts.Count then + failwith "Length of new column does not match length of data contexts." - member this.AddColumns(columns : CompositeColumn [], ?skipFillMissing : bool) = - columns |> Array.iter (fun c -> c.Validate(true) |> ignore) - table.AddColumns(columns, ?skipFillMissing = skipFillMissing) - DataMapAux.validate table.Headers table.Values true |> ignore +type DataMap(dataContexts : ResizeArray) = + + let mutable staticHash = 0 + let mutable dataContexts = dataContexts - static member addColumns (columns : CompositeColumn [], ?skipFillMissing : bool) = - fun (dm : DataMap) -> - let dm : DataMap = dm.Copy() - dm.AddColumns(columns, ?skipFillMissing = skipFillMissing) - dm + member this.StaticHash with get() = staticHash and set(value) = staticHash <- value + + member this.DataContexts with get() = dataContexts - member this.Table = table + static member init() = DataMap(ResizeArray()) - member this.TryGetCellAt (row: int, column: int) = table.TryGetCellAt(row, column) - member this.GetExplicationColumn() = - table.GetColumnByHeader(DataMapAux.explicationHeader) + member this.GetExplicationColumn() = + DataMapAux.getOntologyColumn (fun dc -> dc.Explication) dataContexts - member this.AddExplicationColumn(cells : CompositeCell []) = - table.AddColumn(DataMapAux.explicationHeader, cells) + member this.SetExplicationColumn(cells : CompositeCell []) = + DataMapAux.setOntologyColumn (fun dc oa -> dc.Explication <- oa) cells dataContexts member this.GetUnitColumn() = - table.GetColumnByHeader(DataMapAux.unitHeader) + DataMapAux.getOntologyColumn (fun dc -> dc.Unit) dataContexts - member this.AddUnitColumn(cells : CompositeCell []) = - table.AddColumn(DataMapAux.unitHeader, cells) + member this.SetUnitColumn(cells : CompositeCell []) = + DataMapAux.setOntologyColumn (fun dc oa -> dc.Unit <- oa) cells dataContexts - member this.GetDataTypeColumn() = - table.GetColumnByHeader(DataMapAux.objectTypeHeader) + member this.GetObjectTypeColumn() = + DataMapAux.getOntologyColumn (fun dc -> dc.ObjectType) dataContexts - member this.AddDataTypeColumn(cells : CompositeCell []) = - table.AddColumn(DataMapAux.objectTypeHeader, cells) + member this.SetDataTypeColumn(cells : CompositeCell []) = + DataMapAux.setOntologyColumn (fun dc oa -> dc.ObjectType <- oa) cells dataContexts member this.GetDescriptionColumn() = - table.GetColumnByHeader(DataMapAux.descriptionHeader) - - member this.AddDescriptionColumn(cells : CompositeCell []) = - table.AddColumn(DataMapAux.descriptionHeader, cells) + DataMapAux.getStringColumn (fun dc -> dc.Description) dataContexts + member this.SetDescriptionColumn(cells : CompositeCell []) = + DataMapAux.setStringColumn (fun dc s -> dc.Description <- s) cells dataContexts - member this.GetRow(row: int, ?SkipValidation) = table.GetRow(row,?SkipValidation = SkipValidation) + member this.GetDataContext(row: int, ?SkipValidation) = + DataMapAux.SanityChecks.rowIndexInBoundaries row dataContexts + dataContexts.Item(row) - static member getRow(row: int, ?SkipValidation) = - fun (dm : DataMap) -> dm.GetRow(row,?SkipValidation = SkipValidation) + static member getDataContext(row: int, ?SkipValidation) = + fun (dm : DataMap) -> dm.GetDataContext(row,?SkipValidation = SkipValidation) member this.Copy() = DataMap( - ResizeArray(this.Headers), - Dictionary(this.Values) + dataContexts + |> Seq.map (fun dc -> dc.Copy()) + |> ResizeArray ) override this.Equals(obj) = - match obj with - | :? DataMap as dm -> - this.Table.Equals(dm.Table) - | _ -> false + HashCodes.hash this = HashCodes.hash obj override this.GetHashCode() = - this.Table.GetHashCode() \ No newline at end of file + dataContexts + |> HashCodes.boxHashSeq + |> fun x -> x :?> int \ No newline at end of file diff --git a/src/Spreadsheet/AnnotationTable/CompositeCell.fs b/src/Spreadsheet/AnnotationTable/CompositeCell.fs index 74d0e294..ace84f8d 100644 --- a/src/Spreadsheet/AnnotationTable/CompositeCell.fs +++ b/src/Spreadsheet/AnnotationTable/CompositeCell.fs @@ -4,15 +4,6 @@ open ARCtrl open ARCtrl.Helper open FsSpreadsheet -//let fromFsCells (cells : list) : CompositeCell = -// let cellValues = cells |> List.map (fun c -> c.ValueAsString()) -// match cellValues with -// | [v] -> CompositeCell.createFreeText v -// | [v1;v2;v3] -> CompositeCell.createTermFromString(v1,v2,v3) -// | [v1;v2;v3;v4] -> CompositeCell.createUnitizedFromString(v1,v2,v3,v4) -// | _ -> -// failwithf "Dafuq" - let termFromFsCells (tsrCol : int option) (tanCol : int option ) (cells : list) : CompositeCell= let cellValues = cells |> List.map (fun c -> c.ValueAsString()) let tan = Option.map (fun i -> cellValues.[i]) tanCol diff --git a/src/Spreadsheet/DataMapTable/DataMapColumn.fs b/src/Spreadsheet/DataMapTable/DataMapColumn.fs index 84b97854..89986a43 100644 --- a/src/Spreadsheet/DataMapTable/DataMapColumn.fs +++ b/src/Spreadsheet/DataMapTable/DataMapColumn.fs @@ -3,46 +3,75 @@ open ARCtrl open ArcTable open FsSpreadsheet +open DataMapHeader.ActivePattern -let fromFsColumns (columns : list) : CompositeColumn = - let header, cellParser = +let setFromFsColumns (dc : ResizeArray) (columns : list) : ResizeArray = + let cellParser = columns |> List.map (fun c -> c.[1]) |> DataMapHeader.fromFsCells - let l = columns.[0].RangeAddress.LastAddress.RowNumber - let cells = - [| - for i = 2 to l do - columns - |> List.map (fun c -> c.[i]) - |> cellParser - |] - CompositeColumn.create(header,cells) - + for i = 0 to dc.Count - 1 do + columns + |> List.map (fun c -> c.[i+2]) + |> cellParser (dc.[i]) + |> ignore + dc -let toFsColumns (column : CompositeColumn) : FsCell list list = - let isTerm = column.Header.IsTermColumn - let isData = column.Header.IsDataColumn - let header = DataMapHeader.toFsCells column.Header - let cells = column.Cells |> Array.map (CompositeCell.toFsCells isTerm false) - if isTerm then +let toFsColumns (dc : ResizeArray) : FsCell list list = + let commentKeys = + dc + |> Seq.collect (fun dc -> dc.Comments |> Seq.map (fun c -> c.Name |> Option.defaultValue "")) + |> Seq.distinct + |> Seq.toList + let headers = + DataMapHeader.toFsCells commentKeys + let createTerm (oa : OntologyAnnotation option) = + match oa with + | Some oa -> + [ + oa.Name |> Option.defaultValue "" |> FsCell + oa.TermSourceREF |> Option.defaultValue "" |> FsCell + oa.TermAccessionNumber |> Option.defaultValue "" |> FsCell + ] + | None -> + [ + FsCell("") + FsCell("") + FsCell("") + ] + let createText (s : string option) = [ - [header.[0]; for i = 0 to column.Cells.Length - 1 do cells.[i].[0]] - [header.[1]; for i = 0 to column.Cells.Length - 1 do cells.[i].[1]] - [header.[2]; for i = 0 to column.Cells.Length - 1 do cells.[i].[2]] + FsCell(s |> Option.defaultValue "") ] - elif isData then - let hasFormat = column.Cells |> Seq.exists (fun c -> c.AsData.Format.IsSome) - let hasSelectorFormat = column.Cells |> Seq.exists (fun c -> c.AsData.SelectorFormat.IsSome) - + let createData (dc : DataContext) = [ - [header.[0]; for i = 0 to column.Cells.Length - 1 do cells.[i].[0]] - if hasFormat then - [header.[1]; for i = 0 to column.Cells.Length - 1 do cells.[i].[1]] - if hasSelectorFormat then - [header.[2]; for i = 0 to column.Cells.Length - 1 do cells.[i].[2]] + FsCell(dc.Name |> Option.defaultValue "") + FsCell(dc.Format |> Option.defaultValue "") + FsCell(dc.SelectorFormat |> Option.defaultValue "") ] - else + let createRow (dc : DataContext) = [ - [header.[0]; for i = 0 to column.Cells.Length - 1 do cells.[i].[0]] - ] \ No newline at end of file + yield! (createData dc) + yield! (createTerm dc.Explication) + yield! (createTerm dc.Unit) + yield! (createTerm dc.ObjectType) + yield! (createText dc.Description) + yield! (createText dc.GeneratedBy) + yield! ( + commentKeys + |> List.map (fun key -> + dc.Comments + |> Seq.tryFind (fun c -> + Option.defaultValue "" c.Name = key) + |> Option.bind (fun c -> c.Value) + |> Option.defaultValue "" + |> FsCell + ) + ) + ] + [ + headers + for dc in dc do + createRow dc + ] + |> List.transpose \ No newline at end of file diff --git a/src/Spreadsheet/DataMapTable/DataMapHeader.fs b/src/Spreadsheet/DataMapTable/DataMapHeader.fs index 013f60f2..39bcbc7b 100644 --- a/src/Spreadsheet/DataMapTable/DataMapHeader.fs +++ b/src/Spreadsheet/DataMapTable/DataMapHeader.fs @@ -8,9 +8,25 @@ module ActivePattern = open Regex.ActivePatterns - let (|Term|_|) (categoryString : string) (categoryHeader : CompositeHeader) (cells : FsCell list) : (CompositeHeader*(FsCell list -> CompositeCell)) option = + let ontologyAnnotationFromFsCells (tsrCol : int option) (tanCol : int option) (cells : list) : OntologyAnnotation = + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + let tsr = Option.map (fun i -> cellValues.[i]) tsrCol + let tan = Option.map (fun i -> cellValues.[i]) tanCol + OntologyAnnotation(cellValues.[0],?tsr = tsr, ?tan = tan) + + let freeTextFromFsCells (cells : list) : string = + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + cellValues.[0] + + let dataFromFsCells (format : int option) (selectorFormat : int option) (cells : list) : Data = + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + let format = Option.map (fun i -> cellValues.[i]) format + let selectorFormat = Option.map (fun i -> cellValues.[i]) selectorFormat + Data(name = cellValues.[0],?format = format, ?selectorFormat = selectorFormat) + + let (|Term|_|) (categoryString : string) (cells : FsCell list) : ((FsCell list -> OntologyAnnotation)) option = let (|AC|_|) s = - if s = categoryString then Some categoryHeader else None + if s = categoryString then Some 1 else None let (|TSRColumnHeaderRaw|_|) (s : string) = if s.StartsWith("Term Source REF") then Some s else None let (|TANColumnHeaderRaw|_|) (s : string) = @@ -18,105 +34,155 @@ module ActivePattern = let cellValues = cells |> List.map (fun c -> c.ValueAsString()) match cellValues with | [AC header] -> - (header, CompositeCell.termFromFsCells None None) + (ontologyAnnotationFromFsCells None None) |> Some | [AC header; TSRColumnHeaderRaw _; TANColumnHeaderRaw _] -> - (header, CompositeCell.termFromFsCells (Some 1) (Some 2)) + (ontologyAnnotationFromFsCells (Some 1) (Some 2)) |> Some | [AC header; TANColumnHeaderRaw _; TSRColumnHeaderRaw _] -> - (header, CompositeCell.termFromFsCells (Some 2) (Some 1)) + (ontologyAnnotationFromFsCells (Some 2) (Some 1)) |> Some | _ -> None let (|Explication|_|) (cells : FsCell list) = match cells with - | Term DataMapAux.explicationShortHand DataMapAux.explicationHeader r -> - Some r + | Term DataMapAux.explicationShortHand r -> + (fun (dc : DataContext) (cells : FsCell list) -> + dc.Explication <- Some (r cells) + dc + ) + |> Some | _ -> None let (|Unit|_|) (cells : FsCell list) = match cells with - | Term DataMapAux.unitShortHand DataMapAux.unitHeader r -> - Some r + | Term DataMapAux.unitShortHand r -> + (fun (dc : DataContext) (cells : FsCell list) -> + dc.Unit <- Some (r cells) + dc + ) + |> Some | _ -> None let (|ObjectType|_|) (cells : FsCell list) = match cells with - | Term DataMapAux.objectTypeShortHand DataMapAux.objectTypeHeader r -> - Some r + | Term DataMapAux.objectTypeShortHand r -> + (fun (dc : DataContext) (cells : FsCell list) -> + dc.ObjectType <- Some (r cells) + dc + ) + |> Some | _ -> None let (|Description|_|) (cells : FsCell list) = let cellValues = cells |> List.map (fun c -> c.ValueAsString()) match cellValues with - | [DataMapAux.descriptionShortHand] -> Some(DataMapAux.descriptionHeader, CompositeCell.freeTextFromFsCells) + | [DataMapAux.descriptionShortHand] -> + (fun (dc : DataContext) (cells : FsCell list) -> + dc.Description <- Some (freeTextFromFsCells cells) + dc + ) + |> Some | _ -> None let (|GeneratedBy|_|) (cells : FsCell list) = let cellValues = cells |> List.map (fun c -> c.ValueAsString()) match cellValues with - | [DataMapAux.generatedByShortHand] -> Some(DataMapAux.generatedByHeader, CompositeCell.freeTextFromFsCells) + | [DataMapAux.generatedByShortHand] -> + (fun (dc : DataContext) (cells : FsCell list) -> + dc.GeneratedBy <- Some (freeTextFromFsCells cells) + dc + ) + |> Some | _ -> None let (|Data|_|) (cells : FsCell list) = let cellValues = cells |> List.map (fun c -> c.ValueAsString()) match cellValues with | DataMapAux.dataShortHand :: cols -> - - let format = cols |> List.tryFindIndex (fun s -> s.StartsWith("Data Format")) |> Option.map ((+) 1) - let selectorFormat = cols |> List.tryFindIndex (fun s -> s.StartsWith("Data Selector Format")) |> Option.map ((+) 1) - (CompositeHeader.Input (IOType.Data), CompositeCell.dataFromFsCells format selectorFormat) + (fun (dc : DataContext) (cells : FsCell list) -> + let format = cols |> List.tryFindIndex (fun s -> s.StartsWith("Data Format")) |> Option.map ((+) 1) + let selectorFormat = cols |> List.tryFindIndex (fun s -> s.StartsWith("Data Selector Format")) |> Option.map ((+) 1) + let d = dataFromFsCells format selectorFormat cells + dc.FilePath <- d.FilePath + dc.Selector <- d.Selector + dc.Format <- d.Format + dc.SelectorFormat <- d.SelectorFormat + dc + ) |> Some + + | _ -> None + let (|Comment|_|) (cells : FsCell list) = + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + match cellValues with + | [Comment key] -> + (fun (dc : DataContext) (cells : FsCell list) -> + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + let comment = cellValues.[0] + dc.Comments.Add(Comment.create(key,comment)) + dc + ) + |> Some | _ -> None - let (|FreeText|_|) (cells : FsCell list) = + let (|Freetext|_|) (cells : FsCell list) = let cellValues = cells |> List.map (fun c -> c.ValueAsString()) match cellValues with - | [text] -> - (CompositeHeader.FreeText text, CompositeCell.freeTextFromFsCells) - |> Some + | [key] -> + (fun (dc : DataContext) (cells : FsCell list) -> + let cellValues = cells |> List.map (fun c -> c.ValueAsString()) + let comment = cellValues.[0] + dc.Comments.Add(Comment.create(key,comment)) + dc + ) + |> Some | _ -> None open ActivePattern -let fromFsCells (cells : list) : CompositeHeader*(FsCell list -> CompositeCell) = +let fromFsCells (cells : FsCell list) : DataContext -> FsCell list -> DataContext = match cells with - | Data d -> d - | Explication e -> e - | Unit u -> u - | ObjectType ot -> ot - | Description d -> d - | GeneratedBy gb -> gb - | FreeText ft -> ft - | _ -> failwithf "Could not parse header group %O" cells - -let toFsCells (header : CompositeHeader) : list = - match header with - | CompositeHeader.Input IOType.Data -> - [ + | Explication r + | Unit r + | ObjectType r + | Description r + | GeneratedBy r + | Data r + | Comment r + | Freetext r -> + fun (dc : DataContext) (cells : FsCell list) -> r dc cells + | _ -> failwithf "Could not parse data map column: %s" (cells |> List.map (fun c -> c.ValueAsString()) |> String.concat ", ") + +let toFsCells (commentKeys : string list) : list = + [ + yield! [ FsCell("Data") FsCell("Data Format") FsCell("Data Selector Format") - ] - | h when h = DataMapAux.explicationHeader -> - [ + ] + yield! [ FsCell(DataMapAux.explicationShortHand) FsCell("Term Source REF") FsCell("Term Accession Number") ] - | h when h = DataMapAux.unitHeader -> - [ + yield![ FsCell(DataMapAux.unitShortHand) FsCell("Term Source REF") FsCell("Term Accession Number") ] - | h when h = DataMapAux.objectTypeHeader -> - [ + yield![ FsCell(DataMapAux.objectTypeShortHand) FsCell("Term Source REF") FsCell("Term Accession Number") ] - | CompositeHeader.FreeText text -> - [FsCell(text)] - | _ -> failwithf "Could not parse DataMap header %O." header + yield! [ + FsCell(DataMapAux.descriptionShortHand) + ] + yield! [ + FsCell(DataMapAux.generatedByShortHand) + ] + for ck in commentKeys do + yield FsCell(ck) + ] diff --git a/src/Spreadsheet/DataMapTable/DataMapTable.fs b/src/Spreadsheet/DataMapTable/DataMapTable.fs index b188e514..94cd8afd 100644 --- a/src/Spreadsheet/DataMapTable/DataMapTable.fs +++ b/src/Spreadsheet/DataMapTable/DataMapTable.fs @@ -30,24 +30,24 @@ let tryDataMapTable (sheet : FsWorksheet) = |> Seq.tryFind (fun t -> t.Name.StartsWith datamapTablePrefix) /// Groups and parses a collection of single columns into the according ISA composite columns -let composeColumns (columns : seq) : CompositeColumn [] = +let composeColumns (columns : seq) : ResizeArray = + let l = (columns |> Seq.item 0).MaxRowIndex - 1 + let dc = ResizeArray([| for i = 0 to l - 1 do yield DataContext()|]) columns |> Seq.toList |> groupColumnsByHeader - |> List.map DataMapColumn.fromFsColumns - |> List.toArray - + |> List.iter (DataMapColumn.setFromFsColumns dc >> ignore) + dc /// Returns the protocol described by the headers and a function for parsing the values of the matrix to the processes of this protocol let tryFromFsWorksheet (sheet : FsWorksheet) = try match tryDataMapTable sheet with | Some (t: FsTable) -> - let compositeColumns = + let dataContexts = t.GetColumns(sheet.CellCollection) |> composeColumns - DataMap.init() - |> DataMap.addColumns(compositeColumns,skipFillMissing = true) + DataMap(dataContexts) |> Some | None -> None @@ -60,14 +60,11 @@ let toFsWorksheet (table : DataMap) = let ws = FsWorksheet("isa_datamap") // Cancel if there are no columns - if table.Table.Columns.Length = 0 then ws + if table.DataContexts.Count = 0 then ws else let columns = - table.Table.Columns - |> List.ofArray - |> List.sortBy classifyColumnOrder - |> List.collect DataMapColumn.toFsColumns + DataMapColumn.toFsColumns table.DataContexts let maxRow = columns.Head.Length let maxCol = columns.Length let fsTable = ws.Table("datamapTable",FsRangeAddress(FsAddress(1,1),FsAddress(maxRow,maxCol))) @@ -91,4 +88,5 @@ let toFsWorksheet (table : DataMap) = fsTable.Cell(address, ws.CellCollection).SetValueAs value ) ) + ws.RescanRows() ws \ No newline at end of file diff --git a/tests/ARCtrl/ARCtrl.Tests.fs b/tests/ARCtrl/ARCtrl.Tests.fs index 15f56764..8716ee44 100644 --- a/tests/ARCtrl/ARCtrl.Tests.fs +++ b/tests/ARCtrl/ARCtrl.Tests.fs @@ -161,7 +161,7 @@ let private tests_read_contracts = testList "read_contracts" [ let a1 = inv.GetAssay(SimpleISA.Assay.proteomeIdentifer) let datamap = Expect.wantSome a1.DataMap "Proteome Assay was supposed to have datamap" - Expect.equal 2 datamap.Table.RowCount "Datamap was not read correctly" + Expect.equal 2 datamap.DataContexts.Count "Datamap was not read correctly" let a2 = inv.GetAssay(SimpleISA.Assay.metabolomeIdentifer) Expect.isNone a2.DataMap "Metabolome Assay was not supposed to have datamap" @@ -376,7 +376,7 @@ let private tests_updateContracts = testList "update_contracts" [ let isa = arc.ISA.Value let dm = Expect.wantSome (isa.GetAssay(SimpleISA.Assay.proteomeIdentifer).DataMap) "Assay should have datamap" - dm.Values.[(0,1)] <- CompositeCell.createDataFromString("Hello") + dm.GetDataContext(1).Name <- Some "Hello" let contracts = arc.GetUpdateContracts() Expect.equal contracts.Length 1 $"Should contain only assay datamap change contract" diff --git a/tests/Spreadsheet/DataMapTests.fs b/tests/Spreadsheet/DataMapTests.fs index 6d43065f..68c884c7 100644 --- a/tests/Spreadsheet/DataMapTests.fs +++ b/tests/Spreadsheet/DataMapTests.fs @@ -29,30 +29,28 @@ let private simpleTable = Expect.isSome table "Table was not created" let table = table.Value - Expect.equal table.Table.ColumnCount 6 "Wrong number of columns" - Expect.equal table.Table.RowCount 1 "Wrong number of rows" - - let expectedHeaders = - [ - DataMapAux.dataHeader - DataMapAux.explicationHeader - DataMapAux.unitHeader - DataMapAux.objectTypeHeader - DataMapAux.descriptionHeader - DataMapAux.generatedByHeader - ] - Expect.sequenceEqual table.Headers expectedHeaders "Headers did not match" - - let expectedCells = - [ - Data.dataValue - Explication.meanValue - Unit.ppmValue - ObjectType.floatValue - Description.descriptionValue - GeneratedBy.generatedByValue - ] - Expect.sequenceEqual (table.GetRow(0)) expectedCells "Cells did not match" + Expect.equal table.DataContexts.Count 1 "Wrong number of rows" + + let dc = table.GetDataContext(0) + + Expect.equal (dc.AsData()) Data.dataValue "Data did not match" + + let explication = Expect.wantSome dc.Explication "Explication was not set" + Expect.equal explication Explication.meanValue "Explication did not match" + + let unit = Expect.wantSome dc.Unit "Unit was not set" + Expect.equal unit Unit.ppmValue "Unit did not match" + + let objectType = Expect.wantSome dc.ObjectType "ObjectType was not set" + Expect.equal objectType ObjectType.floatValue "ObjectType did not match" + + let description = Expect.wantSome dc.Description "Description was not set" + Expect.equal description Description.descriptionValue "Description did not match" + + let generatedBy = Expect.wantSome dc.GeneratedBy "GeneratedBy was not set" + Expect.equal generatedBy GeneratedBy.generatedByValue "GeneratedBy did not match" + + Expect.isEmpty dc.Comments "Comments should be empty" ) testCase "Write" (fun () -> @@ -84,19 +82,8 @@ let private valuelessTable = Expect.isSome table "Table was not created" let table = table.Value - Expect.equal table.Table.ColumnCount 6 "Wrong number of columns" - Expect.equal table.Table.RowCount 0 "Wrong number of rows" - - let expectedHeaders = - [ - DataMapAux.dataHeader - DataMapAux.explicationHeader - DataMapAux.unitHeader - DataMapAux.objectTypeHeader - DataMapAux.descriptionHeader - DataMapAux.generatedByHeader - ] - Expect.sequenceEqual table.Headers expectedHeaders "Headers did not match" + Expect.equal table.DataContexts.Count 0 "Wrong number of rows" + ) // TODO: What should we do with units of empty columns? //testCase "Write" (fun () -> @@ -143,30 +130,28 @@ let private simpleFile = let table = DataMap.fromFsWorkbook wb - Expect.equal table.Table.ColumnCount 6 "Wrong number of columns" - Expect.equal table.Table.RowCount 1 "Wrong number of rows" - - let expectedHeaders = - [ - DataMapAux.dataHeader - DataMapAux.explicationHeader - DataMapAux.unitHeader - DataMapAux.objectTypeHeader - DataMapAux.descriptionHeader - DataMapAux.generatedByHeader - ] - Expect.sequenceEqual table.Headers expectedHeaders "Headers did not match" - - let expectedCells = - [ - Data.dataValue - Explication.meanValue - Unit.ppmValue - ObjectType.floatValue - Description.descriptionValue - GeneratedBy.generatedByValue - ] - Expect.sequenceEqual (table.GetRow(0)) expectedCells "Cells did not match" + Expect.equal table.DataContexts.Count 1 "Wrong number of rows" + + let dc = table.GetDataContext(0) + + Expect.equal (dc.AsData()) Data.dataValue "Data did not match" + + let explication = Expect.wantSome dc.Explication "Explication was not set" + Expect.equal explication Explication.meanValue "Explication did not match" + + let unit = Expect.wantSome dc.Unit "Unit was not set" + Expect.equal unit Unit.ppmValue "Unit did not match" + + let objectType = Expect.wantSome dc.ObjectType "ObjectType was not set" + Expect.equal objectType ObjectType.floatValue "ObjectType did not match" + + let description = Expect.wantSome dc.Description "Description was not set" + Expect.equal description Description.descriptionValue "Description did not match" + + let generatedBy = Expect.wantSome dc.GeneratedBy "GeneratedBy was not set" + Expect.equal generatedBy GeneratedBy.generatedByValue "GeneratedBy did not match" + + Expect.isEmpty dc.Comments "Comments should be empty" ) testCase "Write" (fun () -> diff --git a/tests/TestingUtils/TestObjects.Spreadsheet/Spreadsheet.DataMap.fs b/tests/TestingUtils/TestObjects.Spreadsheet/Spreadsheet.DataMap.fs index adc5777c..42fd172f 100644 --- a/tests/TestingUtils/TestObjects.Spreadsheet/Spreadsheet.DataMap.fs +++ b/tests/TestingUtils/TestObjects.Spreadsheet/Spreadsheet.DataMap.fs @@ -16,7 +16,6 @@ module Data = let dataValue = Data.create(Name = "MyDataFile.csv#col=1",Format = "text/csv", SelectorFormat = "https://datatracker.ietf.org/doc/html/rfc7111") - |> CompositeCell.Data let dataHeaderV1 = "Data" let dataHeaderV2 = "Data Format" @@ -40,7 +39,6 @@ module Explication = let pValueValue = OntologyAnnotation("p-value","NCIT","http://purl.obolibrary.org/obo/NCIT_C44185") - |> CompositeCell.Term let explicationHeaderV1 = "Explication" let explicationHeaderV2 = "Term Source REF" @@ -63,7 +61,6 @@ module Explication = // Arithmetic Mean, http://purl.obolibrary.org/obo/NCIT_C53319 let meanValue = OntologyAnnotation("Arithmetic Mean","NCIT","http://purl.obolibrary.org/obo/NCIT_C53319") - |> CompositeCell.Term let meanValueV1 = "Arithmetic Mean" let meanValueV2 = "NCIT" @@ -83,7 +80,6 @@ module Unit = let ppmValue = OntologyAnnotation("parts per million","UO","http://purl.obolibrary.org/obo/UO_0000169") - |> CompositeCell.Term let unitHeaderV1 = "Unit" let unitHeaderV2 = "Term Source REF" @@ -107,7 +103,6 @@ module ObjectType = let floatValue = OntologyAnnotation("float","NCIT","http://purl.obolibrary.org/obo/NCIT_C42645") - |> CompositeCell.Term let objectTypeHeaderV1 = "Object Type" let objectTypeHeaderV2 = "Term Source REF" @@ -129,7 +124,7 @@ module ObjectType = module Description = - let descriptionValue = CompositeCell.FreeText "This is a description" + let descriptionValue = "This is a description" let descriptionHeaderV1 = "Description" @@ -143,7 +138,7 @@ module Description = module GeneratedBy = - let generatedByValue = CompositeCell.FreeText "MyTool.exe" + let generatedByValue = "MyTool.exe" let generatedByHeaderV1 = "Generated By"