Follow Up to my earlier question.
This is an RSS Parser intended only to parse the feed Wordpress provides (and even then, only what I actually want to display on my website). System.Linq.Xml
and System.ServiceModel.Syndication
are unavailable because I'm targeting .Net Core instead of the full framework.
I think I've achieved a more idiomatic implementation this time. Where can I improve? Are there opportunities to compose higher order functions that I'm missing?
Rss.fs
module Rss
open System
type Item = {
Title: string
Link: Uri
Comments: Uri
PublishDate: DateTimeOffset
Description: string
}
type Channel = {
Title: string
Link: Uri
Description: string
LastBuildDate: DateTimeOffset
Items: seq<Item>
}
type RssFeed = {
Version: double
Channel: Channel
}
open System.Xml
let parse input =
// Helper Functions
let innerText (elementName: string) (node: XmlNode) =
let element = node.Item elementName
element.InnerText
let titleText node =
node |> innerText "title"
let descriptionText node =
node |> innerText "description"
let elementAsDateTimeOffset elementName node =
node |> innerText elementName |> DateTimeOffset.Parse
let elementAsUri elementName node =
node |> innerText elementName |> Uri
let linkUri node =
node |> elementAsUri "link"
let itemNodeToItem node = {
Title = node |> titleText
Link = node |> linkUri
Comments = node |> elementAsUri "comments"
PublishDate = node |> elementAsDateTimeOffset "pubDate"
Description = node |> descriptionText
}
// Load the document and begin parsing
let document = XmlDocument()
document.LoadXml input
let rss = document.DocumentElement
let channelNode = rss.FirstChild
let items =
channelNode.ChildNodes
|> Seq.cast<XmlNode>
|> Seq.filter (fun node -> node.Name.Equals "item")
|> Seq.map itemNodeToItem
let channel = {
Title = channelNode |> titleText
Link = channelNode |> linkUri
Description = channelNode |> descriptionText
LastBuildDate = channelNode |> elementAsDateTimeOffset "lastBuildDate"
Items = items
}
let version =
(rss.Attributes.ItemOf "version").Value
|> Double.Parse
let rssFeed = {Version = version; Channel = channel}
rssFeed
Controllers/BlogController.fs
namespace theupsyde.Controllers
open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Rss
open System.Net
type BlogController () =
inherit Controller()
let fetchUrl url = async {
let req = WebRequest.Create(Uri(url))
use! resp = req.GetResponseAsync() |> Async.AwaitTask
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
return reader.ReadToEnd()
}
let rssFeed =
fetchUrl "https://christopherjmcclellan.wordpress.com/feed/"
|> Async.RunSynchronously
|> Rss.parse
let recent =
rssFeed.Channel.Items
member this.Index () =
this.View(rssFeed.Channel)
member this.Recent() =
this.PartialView("BlogPosts", recent)
member this.Latest () =
let latest =
recent
|> Seq.head
this.PartialView("BlogPost", latest)