Skip to content
Merged
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
36 changes: 34 additions & 2 deletions src/FSharp.Data.Csv.Core/CsvInference.fs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ let internal inferCellType
preferOptionals
missingValues
inferenceMode
strictBooleans
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot You;ll need to fix up the code in TypeProviderInstantiation.fs too for Csv since you've added an argument

        match args.[0] with
        | "Csv" ->
            Csv { Sample = args.[1]
                  Separators = args.[2]
                  InferRows = Int32.MaxValue
                  Schema = args.[3].Replace(';', ',')
                  HasHeaders = args.[4] |> bool.Parse
                  IgnoreErrors = false
                  SkipRows = 0
                  AssumeMissingValues = args.[5] |> bool.Parse
                  PreferOptionals = args.[6] |> bool.Parse
                  Quote = '"'
                  MissingValues = args.[7]
                  Culture = args.[8]
                  Encoding = args.[9]
                  CacheRows = false
                  ResolutionFolder = ""
                  EmbeddedResource = ""
                  PreferDateOnly = false }

Check whole file carefully

cultureInfo
unit
(value: string)
Expand All @@ -138,7 +139,32 @@ let internal inferCellType
elif String.IsNullOrWhiteSpace value then
InferedType.Null
else
StructuralInference.getInferedTypeFromString unitsOfMeasureProvider inferenceMode cultureInfo value unit
let inferedType =
StructuralInference.getInferedTypeFromString unitsOfMeasureProvider inferenceMode cultureInfo value unit

if strictBooleans then
// With StrictBooleans=true, only "true"/"false" trigger bool inference.
// 0/1 are treated as integers, and "yes"/"no" as strings.
match inferedType with
| InferedType.Primitive(typ, unt, optional, overrides) ->
if typ = typeof<Bit0> || typ = typeof<Bit1> then
// 0 and 1 become plain integers
InferedType.Primitive(typeof<int>, unt, optional, overrides)
elif typ = typeof<bool> then
let trimmed = value.Trim()

if
String.Compare(trimmed, "true", StringComparison.OrdinalIgnoreCase) = 0
|| String.Compare(trimmed, "false", StringComparison.OrdinalIgnoreCase) = 0
then
inferedType // "true"/"false" remain bool
else
InferedType.Primitive(typeof<string>, None, optional, overrides) // "yes"/"no" become string
else
inferedType
| _ -> inferedType
else
inferedType

let internal parseHeaders headers numberOfColumns schema unitsOfMeasureProvider =

Expand Down Expand Up @@ -278,6 +304,7 @@ let internal inferType
inferRows
missingValues
inferenceMode
strictBooleans
cultureInfo
assumeMissingValues
preferOptionals
Expand Down Expand Up @@ -330,6 +357,7 @@ let internal inferType
preferOptionals
missingValues
inferenceMode
strictBooleans
cultureInfo
unit
value
Expand Down Expand Up @@ -427,6 +455,7 @@ let internal inferColumnTypes
inferRows
missingValues
inferenceMode
strictBooleans
cultureInfo
assumeMissingValues
preferOptionals
Expand All @@ -439,6 +468,7 @@ let internal inferColumnTypes
inferRows
missingValues
inferenceMode
strictBooleans
cultureInfo
assumeMissingValues
preferOptionals
Expand Down Expand Up @@ -466,7 +496,8 @@ type CsvFile with
schema,
assumeMissingValues,
preferOptionals,
unitsOfMeasureProvider
unitsOfMeasureProvider,
?strictBooleans
) =

let headerNamesAndUnits, schema =
Expand All @@ -479,6 +510,7 @@ type CsvFile with
inferRows
missingValues
inferenceMode
(defaultArg strictBooleans false)
cultureInfo
assumeMissingValues
preferOptionals
Expand Down
10 changes: 7 additions & 3 deletions src/FSharp.Data.DesignTime/Csv/CsvProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type public CsvProvider(cfg: TypeProviderConfig) as this =
let resolutionFolder = args.[14] :?> string
let resource = args.[15] :?> string
let preferDateOnly = args.[16] :?> bool
let strictBooleans = args.[17] :?> bool

// This provider already has a schema mechanism, so let's disable inline schemas.
let inferenceMode = InferenceMode'.ValuesOnly
Expand Down Expand Up @@ -113,7 +114,8 @@ type public CsvProvider(cfg: TypeProviderConfig) as this =
schema,
assumeMissingValues,
preferOptionals,
unitsOfMeasureProvider
unitsOfMeasureProvider,
strictBooleans
)
#if NET6_0_OR_GREATER
if preferDateOnly && ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then
Expand Down Expand Up @@ -234,7 +236,8 @@ type public CsvProvider(cfg: TypeProviderConfig) as this =
ProvidedStaticParameter("Encoding", typeof<string>, parameterDefaultValue = "")
ProvidedStaticParameter("ResolutionFolder", typeof<string>, parameterDefaultValue = "")
ProvidedStaticParameter("EmbeddedResource", typeof<string>, parameterDefaultValue = "")
ProvidedStaticParameter("PreferDateOnly", typeof<bool>, parameterDefaultValue = false) ]
ProvidedStaticParameter("PreferDateOnly", typeof<bool>, parameterDefaultValue = false)
ProvidedStaticParameter("StrictBooleans", typeof<bool>, parameterDefaultValue = false) ]

let helpText =
"""<summary>Typed representation of a CSV file.</summary>
Expand All @@ -258,7 +261,8 @@ type public CsvProvider(cfg: TypeProviderConfig) as this =
<param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param>
<param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource
(e.g. 'MyCompany.MyAssembly, resource_name.csv'). This is useful when exposing types generated by the type provider.</param>
<param name='PreferDateOnly'>When true on .NET 6+, date-only strings are inferred as DateOnly and time-only strings as TimeOnly. Defaults to false for backward compatibility.</param>"""
<param name='PreferDateOnly'>When true on .NET 6+, date-only strings are inferred as DateOnly and time-only strings as TimeOnly. Defaults to false for backward compatibility.</param>
<param name='StrictBooleans'>When true, only <c>true</c> and <c>false</c> (case-insensitive) are inferred as boolean. Values such as <c>0</c>, <c>1</c>, <c>yes</c>, and <c>no</c> are treated as integers or strings respectively. Defaults to false.</param>"""

do csvProvTy.AddXmlDoc helpText
do csvProvTy.DefineStaticParameters(parameters, buildTypes)
Expand Down
1 change: 1 addition & 0 deletions src/FSharp.Data.Html.Core/HtmlInference.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let internal inferColumns parameters (headerNamesAndUnits: _[]) rows =
inferRows
parameters.MissingValues
parameters.InferenceMode
false
parameters.CultureInfo
assumeMissingValues
parameters.PreferOptionals
Expand Down
2 changes: 1 addition & 1 deletion tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ let internal unitsOfMeasureProvider = ProviderHelpers.unitsOfMeasureProvider

let internal inferType (csv:CsvFile) inferRows missingValues cultureInfo schema assumeMissingValues preferOptionals =
let headerNamesAndUnits, schema = parseHeaders csv.Headers csv.NumberOfColumns schema unitsOfMeasureProvider
inferType headerNamesAndUnits schema (csv.Rows |> Seq.map (fun x -> x.Columns)) inferRows missingValues inferenceMode cultureInfo assumeMissingValues preferOptionals unitsOfMeasureProvider
inferType headerNamesAndUnits schema (csv.Rows |> Seq.map (fun x -> x.Columns)) inferRows missingValues inferenceMode false cultureInfo assumeMissingValues preferOptionals unitsOfMeasureProvider

let internal toRecord fields = InferedType.Record(None, fields, false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type internal CsvProviderArgs =
Encoding : string
ResolutionFolder : string
EmbeddedResource : string
PreferDateOnly : bool }
PreferDateOnly : bool
StrictBooleans : bool }

type internal XmlProviderArgs =
{ Sample : string
Expand Down Expand Up @@ -98,7 +99,8 @@ type internal TypeProviderInstantiation =
box x.Encoding
box x.ResolutionFolder
box x.EmbeddedResource
box x.PreferDateOnly |]
box x.PreferDateOnly
box x.StrictBooleans |]
| Xml x ->
(fun cfg -> new XmlProvider(cfg) :> TypeProviderForNamespaces),
[| box x.Sample
Expand Down Expand Up @@ -232,7 +234,8 @@ type internal TypeProviderInstantiation =
CacheRows = false
ResolutionFolder = ""
EmbeddedResource = ""
PreferDateOnly = false }
PreferDateOnly = false
StrictBooleans = false }
| "Xml" ->
Xml { Sample = args.[1]
SampleIsList = args.[2] |> bool.Parse
Expand Down
32 changes: 32 additions & 0 deletions tests/FSharp.Data.Tests/CsvProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,45 @@ let [<Literal>] simpleCsv = """

type SimpleCsv = CsvProvider<simpleCsv>

let [<Literal>] csvWithBitValues = """
Flag,Status,Score
0,yes,42
1,no,7
1,yes,3 """

// With StrictBooleans=true, 0/1 infer as int and yes/no infer as string
type StrictBoolCsv = CsvProvider<csvWithBitValues, StrictBooleans=true>
// Without StrictBooleans, 0/1 infer as bool and yes/no infer as bool (default)
type NonStrictBoolCsv = CsvProvider<csvWithBitValues>

[<Test>]
let ``Bool column correctly inferred and accessed`` () =
let csv = SimpleCsv.GetSample()
let first = csv.Rows |> Seq.head
let actual:bool = first.Column1
actual |> should be True

[<Test>]
let ``StrictBooleans: 0 and 1 are inferred as int not bool`` () =
let csv = StrictBoolCsv.GetSample()
let first = csv.Rows |> Seq.head
let flagAsInt: int = first.Flag // Should compile: Flag is int, not bool
flagAsInt |> should equal 0

[<Test>]
let ``StrictBooleans: yes and no are inferred as string not bool`` () =
let csv = StrictBoolCsv.GetSample()
let first = csv.Rows |> Seq.head
let statusAsString: string = first.Status // Should compile: Status is string, not bool
statusAsString |> should equal "yes"

[<Test>]
let ``Without StrictBooleans: 0 and 1 are inferred as bool by default`` () =
let csv = NonStrictBoolCsv.GetSample()
let first = csv.Rows |> Seq.head
let flagAsBool: bool = first.Flag // Should compile: Flag is bool
flagAsBool |> should be False

[<Test>]
let ``Decimal column correctly inferred and accessed`` () =
let csv = SimpleCsv.GetSample()
Expand Down
Loading