Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 38 additions & 31 deletions src/ExcelProvider.DesignTime/ExcelProvider.DesignTime.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,72 @@ module internal Helpers =

// Active patterns & operators for parsing strings
let (@?) (s: string) i =
if i >= s.Length then None else Some s.[i]
if i >= s.Length then ValueNone else ValueSome s.[i]

let inline satisfies predicate (charOption: option<char>) =
let inline satisfies predicate (charOption: voption<char>) =
match charOption with
| Some c when predicate c -> charOption
| _ -> None
| ValueSome c when predicate c -> charOption
| _ -> ValueNone

[<return: Struct>]
let (|EOF|_|) =
function
| Some _ -> None
| _ -> Some()
| ValueSome _ -> ValueNone
| _ -> ValueSome()

[<return: Struct>]
let (|LetterDigit|_|) = satisfies Char.IsLetterOrDigit
[<return: Struct>]
let (|Upper|_|) = satisfies Char.IsUpper
[<return: Struct>]
let (|Lower|_|) = satisfies Char.IsLower

let inline forall predicate (source : ReadOnlySpan<_>) =
let mutable state = true
let mutable e = source.GetEnumerator()
while state && e.MoveNext() do
state <- predicate e.Current
state

/// Turns a string into a nice PascalCase identifier
let niceName (set: System.Collections.Generic.HashSet<_>) (s: string) =
if s = s.ToUpper() then
s
else
// Starting to parse a new segment
let rec restart i =
seq {
match s @? i with
| EOF -> ()
| LetterDigit _ & Upper _ -> yield! upperStart i (i + 1)
| LetterDigit _ -> yield! consume i false (i + 1)
| _ -> yield! restart (i + 1)
}
match s @? i with
| EOF -> Seq.empty
| LetterDigit _ & Upper _ -> upperStart i (i + 1)
| LetterDigit _ -> consume i false (i + 1)
| _ -> restart (i + 1)

// Parsed first upper case letter, continue either all lower or all upper
and upperStart from i =
seq {
match s @? i with
| Upper _ -> yield! consume from true (i + 1)
| Lower _ -> yield! consume from false (i + 1)
| _ -> yield! restart (i + 1)
}
match s @? i with
| Upper _ -> consume from true (i + 1)
| Lower _ -> consume from false (i + 1)
| _ -> restart (i + 1)

// Consume are letters of the same kind (either all lower or all upper)
and consume from takeUpper i =
seq {
match s @? i with
| Lower _ when not takeUpper -> yield! consume from takeUpper (i + 1)
| Upper _ when takeUpper -> yield! consume from takeUpper (i + 1)
| Lower _ when not takeUpper -> consume from takeUpper (i + 1)
| Upper _ when takeUpper -> consume from takeUpper (i + 1)
| _ ->
yield from, i
yield! restart i
}
seq {
yield struct(from, i)
yield! restart i
}

// Split string into segments and turn them to PascalCase
let mutable name =
seq {
for i1, i2 in restart 0 do
let sub = s.Substring(i1, i2 - i1)
let sub = s.AsSpan(i1, i2 - i1)

if Seq.forall Char.IsLetterOrDigit sub then
yield sub.[0].ToString().ToUpper() + sub.ToLower().Substring(1)
if forall Char.IsLetterOrDigit sub then
yield Char.ToUpper(sub.[0]).ToString() + sub.Slice(1).ToString().ToLower()
}
|> String.concat ""

Expand Down Expand Up @@ -334,9 +341,9 @@ type public ExcelProvider(cfg: TypeProviderConfig) as this =

let key = (filename, sheetname, range, hasheaders, forcestring)

if dict.ContainsKey(key) then
dict.[key]
else
match dict.TryGetValue key with
| true, kv -> kv
| false, _ ->
let res = ProvidedTypeDefinitionExcelCall key
dict.[key] <- res
res
Expand Down
6 changes: 4 additions & 2 deletions src/ExcelProvider.Runtime/ExcelProvider.Runtime.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace FSharp.Interop.Excel

[<Struct>]
type ExcelFormat =
| Xlsx
| Csv
Expand All @@ -18,6 +19,7 @@ open FSharp.Interop.Excel
[<AutoOpen>]
module internal ExcelAddressing =

[<Struct>]
type Address =
{ Sheet: string; Row: int; Column: int }

Expand Down Expand Up @@ -139,7 +141,7 @@ module internal ExcelAddressing =
let workSheetName =
if worksheets.Contains sheetname then
sheetname
else if sheetname = null || sheetname = "" then
elif isNull sheetname || sheetname = "" then
worksheets.[0].TableName //accept TypeProvider without specific SheetName...
else
failwithf "ExcelProvider: Sheet [%s] does not exist." sheetname
Expand All @@ -157,7 +159,7 @@ module internal ExcelAddressing =
|> Seq.toList

let rangeViewsByColumn =
ranges |> Seq.map rangeViewOffsetRecord |> Seq.concat |> Seq.toList
ranges |> Seq.collect rangeViewOffsetRecord |> Seq.toList

if rangeViewsByColumn |> Seq.distinctBy fst |> Seq.length < rangeViewsByColumn.Length then
failwith "ExcelProvider: Ranges cannot overlap"
Expand Down
3 changes: 1 addition & 2 deletions tests/ExcelProvider.Tests/ExcelProvider.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,7 @@ let ``Cannot create a type referring to a non-existant sheet at runtime``() =
//| Choice2Of2 v -> printfn "2 %A" v
//printfn "%A" diag
let dstr = diag[0].ToString()
dstr.Substring(dstr.IndexOf "typecheck") |> should equal "typecheck error The type provider 'FSharp.Interop.Excel.ExcelProvider.ProviderImplementation+ExcelProvider' reported an error: ExcelProvider: Sheet [C] does not exist."

dstr.Substring(dstr.IndexOf "typecheck").Replace("ExcelProvider: ", "") |> should equal "typecheck error The type provider 'FSharp.Interop.Excel.ExcelProvider.ProviderImplementation+ExcelProvider' reported an error: Sheet [C] does not exist."

[<Test>]
let ``Can load from multiple sheets with range``() =
Expand Down