Reputation: 29199
I have the following code to deal several different xml files (different scheme) which all have node of <parameters>
. Now I found the match
part will be used in many place so I want to create a function for it.
let xml1 = XmlProvider<"./file1.xml">.Parse(resp)
match xml1.Parameters |> Seq.tryFind (fun x -> x.Name = "token") with
| Some value -> value.Value |> Some
| None -> None
let xml2 = XmlProvider<"./file2.xml">.Parse(resp) // different scheme but has <parameters>
match xml2.Parameters |> Seq.tryFind (fun x -> x.Name = "token") with
| Some value -> value.Value |> Some
| None -> None
// more very different files but has <parameters> ......
In the above example you can see the match
part is repeated. How to define the function?
let getToken (parameters : <what the type?>) =
match parameters |> Seq.tryFind (fun x -> x.Name = "token") with
| Some value -> value.Value |> Some
| None -> None
What the type of parameters
should be?
Update: I've updated the question. BTW, is this a case to show the usefulness of Structural typing?
Here is the code (not workable yet) for testing. xml1
and xml2
have totally different xml scheme except they both have the <parameters>
part.
let input1 = """<r1><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf1>..sample....</othersOf1></r1>"""
let xml1 = XmlProvider<"""<r1><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf1>...</othersOf1></r1>""">.Parse(input1)
let input2 = """<r2><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf2>...sample...</othersOf2></r2>"""
let xml2 = XmlProvider<"""<r2><parameters><parameter name="token">1</parameter><parameter name="other">xxx</parameter></parameters><othersOf2>...</othersOf2></r2>""">.Parse(input2)
let getToken (parameters: ????) =
match parameters |> Seq.tryFind (fun x -> x.Name = "token") with
| Some value -> value.Value |> Some
| None -> None
let token1 = getToken xml1.Parameters
let token2 = getToken xml2.Parameters
Upvotes: 3
Views: 69
Reputation: 8551
The idea is to have a function that takes a seq
of some type ^P
which has Name
and Value
. For educational purposes Name
can be of any type 'a
(supporting equality) and Value
is of generic type 'b
:
let inline get name parameters =
parameters |> Seq.tryFind (fun x -> (^P : (member Name : 'a) x) = name)
|> Option.map (fun v -> (^P : (member Value : 'b) v))
Adding some values <parameter name="token" value="123">
to XML1 and changing name to an int
as well having a DateTime
value in XML2 <parameter name="12" value="2016-12-31">
allows to get:
let token1 = get "token" xml1.Parameters // : int option
let token2 = get 12 xml2.Parameters // : DateTime option
So, yes, you are right, this can be done leveraging structural typing.
The actual type of parameters
depends on name
as well. The full type of get
is:
name:'a -> parameters:seq< ^P> -> 'b option
when 'a : equality and ^P : (member get_Name : ^P -> 'a) and
^P : (member get_Value : ^P -> 'b)
Upvotes: 3
Reputation: 243106
Generally speaking, the types of nested nodes become nested types of the type provided by the XML type provider.
To access the types, you'll first need to use a type alias and name your provided type:
type DbToken = XmlProvider<"""<parameters>
<parameter name="token" value="123" />
<parameter name="token" value="123" />
</parameters>""">
I'm using an inline XML example, so that I can test it, but it should work the same with a file.
The type of single <parameter />
node in the file is now DbToken.Parameter
(name is auto-generated to be unique and may vary), so we can now write a function:
let getToken (parameters:DbToken.Parameter[]) =
match parameters |> Seq.tryFind (fun x -> x.Name = "token") with
| Some value -> value.Value |> Some
| None -> None
And the following call works:
let xml = DbToken.GetSample()
getToken xml.Parameters
Upvotes: 2