F# XmlProvider and parsing collection - f#

I have a question. I have a simple configuation in a file that looks:
<configuration>
<Protocol Version="1" />
<RequestParameters>
<ModuleIdentyfication Interval="500" />
<SectionIdentification Interval="500" />
</RequestParameters>
</configuration>
Config file is in the same folder as app so my reading module looks like:
[<Literal>]
let private cabinetConfigFilename : string = "cabinet.config";
type Communication = XmlProvider<cabinetConfigFilename>
let GetConnectionConfiguration : Async<Option<ServiceConfiguration>> =
async {
let assemblyPathInfo = FileInfo(Assembly.GetEntryAssembly().Location);
let configurationFilePath = Path.Combine(assemblyPathInfo.DirectoryName, cabinetConfigFilename);
let root = Communication.Parse((File.ReadAllTextAsync(configurationFilePath) |> Async.AwaitTask |> Async.RunSynchronously))
try
let protocolVersion : uint8 = uint8 root.Protocol.Version
let requestParameters = Communication.Parse (string root.RequestParameters)
for param in requestParameters do printf " - "
return Some (ServiceConfiguration(protocolVersion))
with
| :? ArgumentNullException -> logger.Error("No IP address was given"); return None;
| :? FormatException -> logger.Error("IP address incorrect format was given"); return None
}
So I have no problem, with getting Protocol.Version, but problem is with parsing that RequestParameters, I am getting a whole XProvider object but I can not use it as seq. I tried to do it as is written in https://fsharp.github.io/FSharp.Data/library/XmlProvider.html in Types for multiple simple elements section. Is there a way to cast it to sequence.

The easiest (read: laziest) way if you just have these two types, is to simply double up the sample:
<RequestParameters>
<ModuleIdentification Interval="500" />
<ModuleIdentification Interval="500" />
<SectionIdentification Interval="500" />
<SectionIdentification Interval="500" />
</RequestParameters>
Now you can access the array of elements using:
RequestParameters.ModuleIdentifications and RequestParameters.SectionIdentifications.
A better way to go about this is to generate an XSD for your XML,
and use the type provider with
FSharp.Data.XmlProvider<Schema = ...>

Related

How to set the ViewModel type in F# Elmish.WPF for use by a WPF DataTemplate?

I am learning to use Elmish.WPF. Below is typical XAML code for a tab control with a simple example of the UserControl ContactDetailsView:
XMAL:
<Window x:Class="FrontOfficeV.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FrontOfficeV"
xmlns:vm="clr-namespace:Models;assembly=Models"
xmlns:vi="clr-namespace:Views;assembly=Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TabControl Grid.Row="1" ItemsSource="{Binding Details}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:ContactDetail}">
<vi:ContactDetailsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Internet}">
<vi:InternetView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PhoneNumber}">
<vi:PhoneNumbersView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Address}">
<vi:AddressesView/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
</Window>
<UserControl x:Class="Views.ContactDetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="{Binding Content}" />
</Grid>
</UserControl>
Here is the F# code I am using to supply the views to the xaml:
namespace Models
open Elmish.WPF
open Elmish
open System
open System.Windows
type ContactDetail = { Name: string; Content: string; Text: string }
type Internet = { Name: string; Content: string; Text: string }
type PhoneNumber = { Name: string; Content: string; Text: string }
type Address = { Name: string; Content: string; Text: string }
module FrontOffice =
type Details =
| ContactDetail of ContactDetail * Id: Guid
| Internet of Internet * Id: Guid
| PhoneNumber of PhoneNumber * Id: Guid
| Address of Address * Id: Guid
member this.id =
match this with
| ContactDetail(_, id)
| Internet(_, id)
| PhoneNumber(_, id)
| Address(_, id) -> id
member this.name =
match this with
| ContactDetail(cd,_) -> cd.Name
| Internet(i,_) -> i.Name
| PhoneNumber(pn,_) -> pn.Name
| Address(ad,_) -> ad.Name
member this.content =
match this with
| ContactDetail(cd,_) -> cd.Content
| Internet(i,_) -> i.Content
| PhoneNumber(pn,_) -> pn.Content
| Address(ad,_) -> ad.Content
let contactDetail : ContactDetail = { Name="Contact Detail"; Content="Content for Contact Detail"; Text="here is the contact detail text" }
let internet : Internet = { Name="Internet"; Content="Content for Internet"; Text="here is the internet text" }
let phoneNumber : PhoneNumber = {Name="Phone Number"; Content="Content for phone number"; Text="here is the phone number text" }
let address : Address = { Name="Address"; Content="Content for Address"; Text="here is the Address text" }
let details = [ContactDetail (contactDetail,Guid.NewGuid())
Internet (internet,Guid.NewGuid())
PhoneNumber (phoneNumber,Guid.NewGuid())
Address (address,Guid.NewGuid())
]
/// This is the main data model for our application
type Model = {
ClickCount: int
Message: string
Details: Details list
}
/// This is used to define the initial state of our application. It can take any arguments, but we'll just use unit. We'll need the Cmd type.
/// Notice that we return a tuple. The first field of the tuple tells the program the initial state. The second field holds the command to issue.
/// This is the standard Elmish init() (not special to Elmish.WPF).
let init() =
{
ClickCount = 0
Message = "Hello Elmish.WPF"
Details = details
}
/// This is a discriminated union of the available messages from the user interface
type Msg =
| ButtonClicked
| Reset
/// This is the Reducer Elmish.WPF calls to generate a new model based on a message and an old model.
/// The update function will receive the change required by Msg, and the current state. It will produce a new state and potentially new command(s).
let update (msg: Msg) (model: Model) =
match msg with
| ButtonClicked -> {model with ClickCount = model.ClickCount + 1}
| Reset -> init()
/// Elmish.WPF uses this to provide the data context for your view based on a model.
/// The bindings is the view for Elmish.WPF
/// Define the “view” function using the Bindings module. This is the central public API of Elmish.WPF. Normally in Elm/Elmish this
/// function is called view and would take a model and a dispatch function (to dispatch new messages to the update loop) and return
/// the UI (e.g. a HTML DOM to be rendered), but in Elmish.WPF this function is in general only run once and simply sets up bindings
/// that XAML-defined views can use. Therefore, it is called bindings instead of view.
let bindings(): Binding<Model, Msg> list =
[
// One-Way Bindings
"ClickCount" |> Binding.oneWay (fun m -> m.ClickCount)
"Message" |> Binding.oneWay (fun m -> m.Message)
"Details" |> Binding.subModelSeq((fun m -> m.Details), (fun detail -> detail.id), fun () ->
[
"Id" |> Binding.oneWay (fun (_, detail) -> detail.id)
"Name" |> Binding.oneWay (fun (_, detail) -> detail.name)
"Content" |> Binding.oneWay (fun (_,detail) -> detail.content)
])
// Commands
"ClickCommand" |> Binding.cmd ButtonClicked
"ResetCommand" |> Binding.cmd Reset
]
/// This is the application's entry point. It hands things off to Elmish.WPF
let entryPoint (mainWindow: Window) =
Program.mkSimpleWpf init update bindings
|> Program.runWindowWithConfig
{ ElmConfig.Default with LogTrace = true; Measure = true; MeasureLimitMs = 1 }
mainWindow
Everything compiles and runs!
However, when examining the tabs content, WPF is reporting:
Elmish.WPF.ViewModel'2[System.Object.System.Object]
So I believe the DataTemplate in the XAML is unable to match the DataType coming from Elmish.WPF. How can this be fixed? What am I missing? (I'm guessing that I need someway to set Elmish.WPF.ViewModel'2[System.Object.System.Object] to the appropriate DataType???)
TIA

XML Type Provider Create New XML Object Fails

I'm trying to combine two XmlDocument.xmls for WebAPI 2.0 documentation. But when I do the below code it returns < /doc> with no errors. It looks like I'm doing everything right, so I'm a bit confused. Where the printf statement is, that is where it is failing with the result value. Before and after the code seems to work flawlessly.
#r "../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"
#r "System.Xml.Linq.dll"
open FSharp.Data
open System.IO
// Define your library scripting code here
let seedPath = __SOURCE_DIRECTORY__ + "../../FieldOps/bin"
let outFile = __SOURCE_DIRECTORY__ + "../../FieldOps/App_Data/XmlDocument.xml"
type XmlDocument = XmlProvider<"""<?xml version="1.0"?>
<doc>
<assembly><name>lala</name></assembly>
<members>
<member name="">
<summary>lala</summary>
<param name="">lala</param>
<param name="">lala</param>
<returns>lala</returns>
</member>
<member name=""></member>
</members>
</doc>
""">
let xmlPaths =
[
"../bin/Release"
]
let seed = XmlDocument.Load(Path.Combine(seedPath, "XmlDocument.xml"))
let addFile path = Path.Combine(path, "XmlDocument.XML")
let loadXmlDoc (path: string) = XmlDocument.Load(path)
let joinXmlDocuments (acc: XmlDocument.Doc) path =
let doc = (addFile >> loadXmlDoc) path
let members = Array.append acc.Members doc.Members
let result = new XmlDocument.Doc(acc.Assembly, members)
printf "result ------------------- %A --------------------" result
result
let joinedDocs =
xmlPaths
|> List.fold (joinXmlDocuments) seed
let xml = joinedDocs.XElement.ToString()
File.WriteAllText(outFile, xml)
Update → I think I know why it has the problem. Probably because they are immutable so you need to create a new one. But, how to easily do that, I don't know.
It appears that the XmlProvider doesn't recognize it's own types. Not sure what is going on exactly. But when I recreate the types from scratch it doesn't come back with the empty < /doc> value that I was seeing.
Here's what I ended up doing:
let assembly = new XmlDocument.Assembly(acc.Assembly.Name)
let members =
Array.append acc.Members doc.Members
|> Array.map
(
fun x ->
let parameters =
x.Params
|> Array.map (fun x -> new XmlDocument.Param(x.Name, x.Value))
new XmlDocument.Member(x.Name, x.Summary, parameters, x.Returns)
)
let result = XmlDocument.Doc(assembly, members)
This is fixed in F# Data 2.3.0-beta1

How to create record in match pattern

I want to write an application that read ip address from xml file. The file looks like
<range>
<start>192.168.40.1</start>
<end>192.168.50.255</end>
<subnet>255.255.255.0</subnet>
<gateway>192.168.50.1</gateway>
</range>
I create an records type to save the ip address
type Scope = { Start: IPAddress; End: IPAddress; Subnetmask: IPAddress; Gateway: IPAddress }
I wrote a unit function, that output the ip's.
loc
|> Seq.iter (fun e -> match e.Name.LocalName with
|"start" -> printfn "Start %s" e.Value
|"end" -> printfn "End %s" e.Value
|"subnet" -> printfn "Subnet %s" e.Value
|"gateway" -> printfn "Gateway %s" e.Value
| _ -> ())
How can I return the scope records type instead of unit?
As mentioned in the comments, the XML type provider makes this a lot easier. You can just point it at a sample file, it will infer the structur and let you read the file easily:
type RangeFile = XmlProvider<"sample.xml">
let range = RangeFile.Load("file-you-want-to-read.xml")
let scope =
{ Start = IPAddress.Parse(range.Start)
End = IPAddress.Parse(range.End)
Subnetmask = IPAddress.Parse(range.Subnet)
Gateway = IPAddress.Parse(range.Gateway) }
That said, you can certainly implement this yourself too. The code you wrote is a good start - there is a number of ways to do this, but in any case, you'll need to do some lookup based on the local name of the element (to find start, end, etc.).
One option is to load all the properties into a dictionary:
let lookup =
loc
|> Seq.map (fun e -> e.Name.LocalName, IPAddress.Parse(e.Value)
|> dict
Now you have a lookup table that contains IPAddress for each of the keys, so you can create Scope value using just:
let scope =
{ Start = lookup.["start"]; End = lookup.["end"];
Subnetmask = lookup.["subnet"]; Gateway = lookup.["gateway"] }
That said, the nice thing about the XML type provider is that it removes the need to do lookup based on string values and so you are less likely to make mistakes caused by typos.

Active patterns over provided types in F#

I have a scenario where I am using the XML type provider from FSharp.Data to read in a stream containing various key/value pairs. The values in this case are sometimes decimals, sometimes dates, and sometimes strings:
<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>
Because there seems to be no way to refer to the provided types by name, in order to pattern match on the values I have to move the values into a tuple and then provide active patterns over that:
open System
open System.IO
type XmlFoo=FSharp.Data.XmlProvider<"""<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>""">
[<EntryPoint>]
let main argv =
let (|Date|_|) ((v,_,_):Option<DateTime> * Option<Decimal> * Option<String>) = v
let (|Number|_|) ((_,v,_):Option<DateTime> * Option<Decimal> * Option<String>) = v
let (|String|_|) ((_,_,v):Option<DateTime> * Option<Decimal> * Option<String>) = v
use stream = File.OpenRead("sample.xml")
let data = XmlFoo.Load(stream)
for field in data.Fields do
let value = field.Value.DateTimeValue, field.Value.NumberValue, field.Value.StringValue
match value with
| Date x -> printfn "Found a date: %s" (x.ToShortDateString())
| Number x -> printfn "Found a number: %M" x
| String x -> printfn "Found a string: %s" x
| _ -> printfn "Found nothing"
0 // return an integer exit code
Is there a way either to
a) refer to the provided types directly (so I don't need the intermediate tuple and the active patterns can be generalised to XML files that return any number of possible data types)
or
b) modify the type provider so that it provides the active patterns itself - not sure if this is possible looking at the type provider API.
EDIT: Nevermind, I missed the obvious - the tooltips in VS2013 are misleading. By displaying <...> it gives the impression there isn't a way to refer to the type by name. Thanks to #ovatsus for the tip. You can do this:
open System
open System.IO
type XmlFoo=FSharp.Data.XmlProvider<"""<records>
<fields>
<key>foo</key>
<value>123.456</value>
</fields>
<fields>
<key>bar</key>
<value>2013-07-23</value>
</fields>
<fields>
<key>fizz</key>
<value>hello world</value>
</fields>
</records>""">
[<EntryPoint>]
let main argv =
let (|Date|_|) (v:XmlFoo.Value) = v.DateTimeValue
let (|Number|_|) (v:XmlFoo.Value) = v.NumberValue
let (|String|_|) (v:XmlFoo.Value) = v.StringValue
use stream = File.OpenRead("sample.xml")
let data = XmlFoo.Load(stream)
for field in data.Fields do
match field.Value with
| Date x -> printfn "Found a date: %s" (x.ToShortDateString())
| Number x -> printfn "Found a number: %M" x
| String x -> printfn "Found a string: %s" x
| _ -> printfn "Found nothing"
0 // return an integer exit code
You can create type aliases to reference the types directly:
type F = XmlFoo.Field
For the question about having active patterns directly in the type provider, it seems that active patterns must be let bindings, not class members (even static), so there is no way to provide them unfortunately.

How can I extract specific tags using LINQ to XML in F#?

I have an XML file, which I open in F# like this:
let Bookmarks(xmlFile:string) =
let xml = XDocument.Load(xmlFile)
Once I have the XDocument I need to navigate it using LINQ to XML and extract all specific tags. Part of my solution is:
let xname (tag:string) = XName.Get(tag)
let tagUrl (tag:XElement) = let attribute = tag.Attribute(xname "href")
attribute.Value
let Bookmarks(xmlFile:string) =
let xml = XDocument.Load(xmlFile)
xml.Elements <| xname "A" |> Seq.map(tagUrl)
How can I extract the specific tags from the XML file?
#light
open System
open System.Xml.Linq
let xname s = XName.Get(s)
let bookmarks (xmlFile : string) =
let xd = XDocument.Load xmlFile
xd.Descendants <| xname "bookmark"
This will find all the descendant elements of "bookmark". If you only want direct descendants, use the Elements method (xd.Root.Elements <| xname "whatever").
Caveat: I've never done linq-to-xml before, but looking through other posts on the topic, this snippet has some F# code that compiles and does something, and thus it may help you get started:
open System.IO
open System.Xml
open System.Xml.Linq
let xmlStr = #"<?xml version='1.0' encoding='UTF-8'?>
<doc>
<blah>Blah</blah>
<a href='urn:foo' />
<yadda>
<blah>Blah</blah>
<a href='urn:bar' />
</yadda>
</doc>"
let xns = XNamespace.op_Implicit ""
let a = xns + "a"
let reader = new StringReader(xmlStr)
let xdoc = XDocument.Load(reader)
let aElements = [for x in xdoc.Root.Elements() do
if x.Name = a then
yield x]
let href = xns + "href"
aElements |> List.iter (fun e -> printfn "%A" (e.Attribute(href)))

Resources