ca9163d9
ca9163d9

Reputation: 29199

The type of the common piece of Xml in different files with different scheme?

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

Answers (2)

CaringDev
CaringDev

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

Tomas Petricek
Tomas Petricek

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

Related Questions