Skip to content

Commit 355c7b0

Browse files
committed
2 parents 920457f + e7251a0 commit 355c7b0

206 files changed

Lines changed: 10213 additions & 666 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/FSharp.Data.DesignTime/Csv/CsvGenerator.fs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,35 @@ module internal CsvTypeBuilder =
9999
for field in fields do
100100
rowType.AddMember field.ProvidedProperty
101101

102+
// Add With* methods so users can create a modified copy of a row
103+
// e.g. myRow.WithAmount(42.0) returns a new Row identical to myRow except Amount = 42.0
104+
for targetIdx, targetField in List.indexed fields do
105+
let methodName = "With" + targetField.ProvidedProperty.Name
106+
107+
let withMethod =
108+
ProvidedMethod(
109+
methodName,
110+
[ ProvidedParameter(targetField.ProvidedParameter.Name, targetField.ProvidedProperty.PropertyType) ],
111+
rowType,
112+
invokeCode =
113+
fun args ->
114+
let row = args.[0]
115+
let newVal = args.[1]
116+
117+
match fields with
118+
| [ _ ] ->
119+
// Single-column CSV: Row erases to the value itself
120+
newVal
121+
| _ ->
122+
let tupleArgs =
123+
fields
124+
|> List.mapi (fun i _ -> if i = targetIdx then newVal else Expr.TupleGet(row, i))
125+
126+
Expr.NewTuple tupleArgs
127+
)
128+
129+
rowType.AddMember withMethod
130+
102131
// The erased csv type will be parameterised by the tuple type
103132
let csvErasedTypeWithRowErasedType =
104133
typedefof<CsvFile<_>>.MakeGenericType(rowErasedType)

src/FSharp.Data.DesignTime/Json/JsonGenerator.fs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ module JsonTypeBuilder =
651651

652652
let name = makeUnique prop.Name
653653

654-
prop.Name,
654+
(prop.Name, name),
655655
[ ProvidedProperty(name, convertedType, getterCode = getter) ],
656656
ProvidedParameter(
657657
(if ctx.UseOriginalNames then
@@ -661,7 +661,8 @@ module JsonTypeBuilder =
661661
replaceJDocWithJValue ctx convertedType
662662
) ]
663663

664-
let names, properties, parameters = List.unzip3 members
664+
let namePairs, properties, parameters = List.unzip3 members
665+
let names = namePairs |> List.map fst
665666
let properties = properties |> List.concat
666667
objectTy.AddMembers properties
667668

@@ -685,6 +686,34 @@ module JsonTypeBuilder =
685686
let ctor = ProvidedConstructor(parameters, invokeCode = ctorCode)
686687
objectTy.AddMember ctor
687688

689+
// Add With* methods: WithPropName(newValue) returns a new record with one field updated
690+
for i, (rawName, displayName) in List.indexed namePairs do
691+
let param = parameters.[i]
692+
let cultureStr = ctx.CultureStr
693+
694+
let withMethod =
695+
ProvidedMethod(
696+
"With" + displayName,
697+
[ ProvidedParameter(param.Name, param.ParameterType) ],
698+
objectTy,
699+
invokeCode =
700+
fun args ->
701+
let jDoc = args.[0]
702+
let newVal = args.[1]
703+
let newValObj = Expr.Coerce(newVal, typeof<obj>)
704+
705+
<@@
706+
JsonRuntime.WithRecordProperty(
707+
(%%jDoc: IJsonDocument),
708+
rawName,
709+
%%newValObj,
710+
cultureStr
711+
)
712+
@@>
713+
)
714+
715+
objectTy.AddMember withMethod
716+
688717
()
689718

690719
if ctx.GenerateConstructors then

src/FSharp.Data.DesignTime/Xml/XmlProvider.fs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
5151
let schema = args.[8] :?> string
5252
let inferenceMode = args.[9] :?> InferenceMode
5353
let preferDateOnly = args.[10] :?> bool
54-
let useOriginalNames = args.[11] :?> bool
54+
let dtdProcessing = args.[11] :?> string
55+
let useOriginalNames = args.[12] :?> bool
5556

5657
let inferenceMode =
5758
InferenceMode'.FromPublicApi(inferenceMode, inferTypesFromValues)
@@ -103,7 +104,8 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
103104

104105
{ GeneratedType = tpType
105106
RepresentationType = result.ConvertedType
106-
CreateFromTextReader = fun reader -> result.Converter <@@ XmlElement.Create(%reader) @@>
107+
CreateFromTextReader =
108+
fun reader -> result.Converter <@@ XmlElement.Create(%reader, dtdProcessing) @@>
107109
CreateListFromTextReader = None
108110
CreateFromTextReaderForSampleList =
109111
fun reader -> // hack: this will actually parse the schema
@@ -117,10 +119,10 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
117119
use _holder = IO.logTime "Parsing" sample
118120

119121
if sampleIsList then
120-
XmlElement.CreateList(new StringReader(value))
122+
XmlElement.CreateList(new StringReader(value), dtdProcessing)
121123
|> Array.map (fun doc -> doc.XElement)
122124
else
123-
[| XDocument.Parse(value).Root |]
125+
[| XmlElement.Create(new StringReader(value), dtdProcessing).XElement |]
124126

125127
let inferedType =
126128
use _holder = IO.logTime "Inference" sample
@@ -159,10 +161,11 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
159161

160162
{ GeneratedType = tpType
161163
RepresentationType = result.ConvertedType
162-
CreateFromTextReader = fun reader -> result.Converter <@@ XmlElement.Create(%reader) @@>
164+
CreateFromTextReader =
165+
fun reader -> result.Converter <@@ XmlElement.Create(%reader, dtdProcessing) @@>
163166
CreateListFromTextReader = None
164167
CreateFromTextReaderForSampleList =
165-
fun reader -> result.Converter <@@ XmlElement.CreateList(%reader) @@>
168+
fun reader -> result.Converter <@@ XmlElement.CreateList(%reader, dtdProcessing) @@>
166169
CreateFromValue = None }
167170

168171
let source =
@@ -199,6 +202,7 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
199202
parameterDefaultValue = InferenceMode.BackwardCompatible
200203
)
201204
ProvidedStaticParameter("PreferDateOnly", typeof<bool>, parameterDefaultValue = false)
205+
ProvidedStaticParameter("DtdProcessing", typeof<string>, parameterDefaultValue = "Ignore")
202206
ProvidedStaticParameter("UseOriginalNames", typeof<bool>, parameterDefaultValue = false) ]
203207

204208
let helpText =
@@ -224,6 +228,7 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
224228
Note inline schemas are not used from Xsd documents.
225229
</param>
226230
<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>
231+
<param name='DtdProcessing'>Controls how DTD declarations in the XML are handled. Accepted values: "Ignore" (default, silently skips DTD processing, safe for most cases), "Prohibit" (throws on any DTD declaration), "Parse" (enables full DTD processing including entity expansion, use with caution).</param>
227232
<param name='UseOriginalNames'>When true, XML element and attribute names are used as-is for generated property names instead of being normalized to PascalCase. Defaults to false.</param>"""
228233

229234

src/FSharp.Data.Json.Core/JsonRuntime.fs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,31 @@ type JsonRuntime =
351351

352352
JsonDocument.Create(json, "")
353353

354+
// Returns a new JSON record document with one property replaced (or added if absent).
355+
// Used by generated With* methods.
356+
static member WithRecordProperty(doc: IJsonDocument, name: string, value: obj, cultureStr: string) =
357+
let cultureInfo = TextRuntime.GetCulture cultureStr
358+
let newPropValue = JsonRuntime.ToJsonValue cultureInfo value
359+
let existingProps = JsonRuntime.GetRecordProperties(doc)
360+
let mutable found = false
361+
362+
let updatedProps =
363+
existingProps
364+
|> Array.map (fun (k, v) ->
365+
if k = name then
366+
found <- true
367+
k, newPropValue
368+
else
369+
k, v)
370+
371+
let finalProps =
372+
if found then
373+
updatedProps
374+
else
375+
Array.append updatedProps [| name, newPropValue |]
376+
377+
JsonDocument.Create(JsonValue.Record finalProps, "")
378+
354379
// Creates a JsonValue.Record, omitting null fields, and wraps it in a json document
355380
static member CreateRecordOmitNulls(properties, cultureStr) =
356381
let cultureInfo = TextRuntime.GetCulture cultureStr

src/FSharp.Data.WorldBank.Core/WorldBankRuntime.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ module Implementation =
247247
/// At runtime, download the data
248248
member internal __.GetDataAsync(countryOrRegionCode, indicatorCode) =
249249
async {
250-
let! data =
251-
getData [ "countries"; countryOrRegionCode; "indicators"; indicatorCode ] [ "date", "" ] "date"
250+
let! data = getData [ "countries"; countryOrRegionCode; "indicators"; indicatorCode ] [] "date"
252251

253252
return
254253
seq {

src/FSharp.Data.Xml.Core/XmlRuntime.fs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace FSharp.Data.Runtime.BaseTypes
66

77
open System.ComponentModel
88
open System.IO
9+
open System.Xml
910
open System.Xml.Linq
1011

1112
#nowarn "10001"
@@ -54,9 +55,37 @@ type XmlElement =
5455
10001,
5556
IsHidden = true,
5657
IsError = false)>]
57-
static member Create(reader: TextReader) =
58+
static member Create(reader: TextReader) = XmlElement.Create(reader, "Prohibit")
59+
60+
/// <exclude />
61+
[<EditorBrowsableAttribute(EditorBrowsableState.Never)>]
62+
[<CompilerMessageAttribute("This method is intended for use in generated code only.",
63+
10001,
64+
IsHidden = true,
65+
IsError = false)>]
66+
static member CreateList(reader: TextReader) =
67+
XmlElement.CreateList(reader, "Prohibit")
68+
69+
/// <exclude />
70+
[<EditorBrowsableAttribute(EditorBrowsableState.Never)>]
71+
[<CompilerMessageAttribute("This method is intended for use in generated code only.",
72+
10001,
73+
IsHidden = true,
74+
IsError = false)>]
75+
static member Create(reader: TextReader, dtdProcessing: string) =
5876
use reader = reader
59-
let element = XDocument.Load(reader, LoadOptions.PreserveWhitespace).Root
77+
78+
let dtd =
79+
match dtdProcessing with
80+
| "Ignore" -> DtdProcessing.Ignore
81+
| "Parse" -> DtdProcessing.Parse
82+
| _ -> DtdProcessing.Prohibit
83+
84+
let xmlReaderSettings =
85+
new XmlReaderSettings(DtdProcessing = dtd, XmlResolver = null, MaxCharactersFromEntities = 1024L * 1024L)
86+
87+
use xmlReader = XmlReader.Create(reader, xmlReaderSettings)
88+
let element = XDocument.Load(xmlReader, LoadOptions.PreserveWhitespace).Root
6089
{ XElement = element }
6190

6291
/// <exclude />
@@ -65,16 +94,30 @@ type XmlElement =
6594
10001,
6695
IsHidden = true,
6796
IsError = false)>]
68-
static member CreateList(reader: TextReader) =
97+
static member CreateList(reader: TextReader, dtdProcessing: string) =
6998
use reader = reader
7099
let text = reader.ReadToEnd()
71100

101+
let dtd =
102+
match dtdProcessing with
103+
| "Ignore" -> DtdProcessing.Ignore
104+
| "Parse" -> DtdProcessing.Parse
105+
| _ -> DtdProcessing.Prohibit
106+
107+
let xmlReaderSettings =
108+
new XmlReaderSettings(DtdProcessing = dtd, XmlResolver = null, MaxCharactersFromEntities = 1024L * 1024L)
109+
110+
let parseWithReader xmlText =
111+
use stringReader = new StringReader(xmlText)
112+
use xmlReader = XmlReader.Create(stringReader, xmlReaderSettings)
113+
XDocument.Load(xmlReader, LoadOptions.PreserveWhitespace)
114+
72115
try
73-
XDocument.Parse(text, LoadOptions.PreserveWhitespace).Root.Elements()
116+
(parseWithReader text).Root.Elements()
74117
|> Seq.map (fun value -> { XElement = value })
75118
|> Seq.toArray
76119
with _ when text.TrimStart().StartsWith "<" ->
77-
XDocument.Parse("<root>" + text + "</root>", LoadOptions.PreserveWhitespace).Root.Elements()
120+
(parseWithReader ("<root>" + text + "</root>")).Root.Elements()
78121
|> Seq.map (fun value -> { XElement = value })
79122
|> Seq.toArray
80123

tests/FSharp.Data.DesignTime.Tests/TypeProviderInstantiation.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type internal XmlProviderArgs =
4242
Schema : string
4343
InferenceMode: InferenceMode
4444
PreferDateOnly : bool
45+
DtdProcessing : string
4546
UseOriginalNames : bool }
4647

4748
type internal JsonProviderArgs =
@@ -119,6 +120,7 @@ type internal TypeProviderInstantiation =
119120
box x.Schema
120121
box x.InferenceMode
121122
box x.PreferDateOnly
123+
box x.DtdProcessing
122124
box x.UseOriginalNames |]
123125
| Json x ->
124126
(fun cfg -> new JsonProvider(cfg) :> TypeProviderForNamespaces),
@@ -257,6 +259,7 @@ type internal TypeProviderInstantiation =
257259
Schema = args.[6]
258260
InferenceMode = args.[7] |> InferenceMode.Parse
259261
PreferDateOnly = false
262+
DtdProcessing = "Ignore"
260263
UseOriginalNames = false }
261264
| "Json" ->
262265
// Handle special case for Schema.json tests where some fields might be empty

tests/FSharp.Data.DesignTime.Tests/expected/Csv,AirQuality.csv,;,,True,False,False,,,.expected

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,52 @@ class CsvProvider+Row : float * float * decimal * int * int * int
246246
member Wind: decimal with get
247247
(let _,_,t3,_,_,_ = this in t3)
248248

249+
member WithDay: day:int -> CsvProvider+Row
250+
(let t1,_,_,_,_,_ = this in t1),
251+
(let _,t2,_,_,_,_ = this in t2),
252+
(let _,_,t3,_,_,_ = this in t3),
253+
(let _,_,_,t4,_,_ = this in t4),
254+
(let _,_,_,_,t5,_ = this in t5),
255+
day
256+
257+
member WithMonth: month:int -> CsvProvider+Row
258+
(let t1,_,_,_,_,_ = this in t1),
259+
(let _,t2,_,_,_,_ = this in t2),
260+
(let _,_,t3,_,_,_ = this in t3),
261+
(let _,_,_,t4,_,_ = this in t4),
262+
month,
263+
(let _,_,_,_,_,t6 = this in t6)
264+
265+
member WithOzone: ozone:float -> CsvProvider+Row
266+
ozone,
267+
(let _,t2,_,_,_,_ = this in t2),
268+
(let _,_,t3,_,_,_ = this in t3),
269+
(let _,_,_,t4,_,_ = this in t4),
270+
(let _,_,_,_,t5,_ = this in t5),
271+
(let _,_,_,_,_,t6 = this in t6)
272+
273+
member WithSolar.R: solarR:float -> CsvProvider+Row
274+
(let t1,_,_,_,_,_ = this in t1),
275+
solarR,
276+
(let _,_,t3,_,_,_ = this in t3),
277+
(let _,_,_,t4,_,_ = this in t4),
278+
(let _,_,_,_,t5,_ = this in t5),
279+
(let _,_,_,_,_,t6 = this in t6)
280+
281+
member WithTemp: temp:int -> CsvProvider+Row
282+
(let t1,_,_,_,_,_ = this in t1),
283+
(let _,t2,_,_,_,_ = this in t2),
284+
(let _,_,t3,_,_,_ = this in t3),
285+
temp,
286+
(let _,_,_,_,t5,_ = this in t5),
287+
(let _,_,_,_,_,t6 = this in t6)
288+
289+
member WithWind: wind:decimal -> CsvProvider+Row
290+
(let t1,_,_,_,_,_ = this in t1),
291+
(let _,t2,_,_,_,_ = this in t2),
292+
wind,
293+
(let _,_,_,t4,_,_ = this in t4),
294+
(let _,_,_,_,t5,_ = this in t5),
295+
(let _,_,_,_,_,t6 = this in t6)
296+
249297

0 commit comments

Comments
 (0)