2

I'm trying to touch some F# language by developing a small "web crawler". I've got a functions declared like this:

let results = HtmlDocument.Load("http://joemonster.org//")

let images = 
results.Descendants ["img"]
|> Seq.map (fun x -> 
    x.TryGetAttribute("src").Value.Value(),
    x.TryGetAttribute("alt").Value.Value()
)

which of course should return for me a map of "src" and "alt" attributes for "img" tag. But when I'm encountering a situation when one of those are missing in the tag I'm getting an exception that TryGetAttribute is returning null. I want to change that function to return the attribute value or empty string in case of null. I've tried out answers from this ticket but with no success.

Kamil Stadryniak
  • 600
  • 9
  • 25
  • Where is `TryGetAttribute` coming from? XMLLinq extension method? Your problem is that you're doing a `.Value` on it and that will look inside. First return whatever `TryGetAttribute` gives you back, than pattern match on that result to give your desired output. – s952163 Mar 11 '18 at 13:02
  • 1
    TryGetAttribute is comming from FSharp.Data library, it docs can be found here: http://fsharp.github.io/FSharp.Data/reference/fsharp-data-htmlnodeextensions.html – Kamil Stadryniak Mar 11 '18 at 13:35

1 Answers1

5

TryGetAttribute returns an option type, and when it is None you can't get its value—you get an exception instead. You can pattern match against the returned option value and return an empty string for the None case:

let getAttrOrEmptyStr (elem: HtmlNode) attr =
  match elem.TryGetAttribute(attr) with
  | Some v -> v.Value()
  | None -> ""
let images = 
  results.Descendants ["img"]
  |> Seq.map (fun x -> getAttrOrEmptyStr x "src", getAttrOrEmptyStr x "alt")

Or a version using defaultArg and Option.map:

let getAttrOrEmptyStr (elem: HtmlNode) attr =
  defaultArg (elem.TryGetAttribute(attr) |> Option.map (fun a -> a.Value())) ""

Or another option now that Option.defaultValue exists, and using HtmlAttribute.value function for a terser Option.map call:

let getAttrOrEmptyStr (elem: HtmlNode) attr =
  elem.TryGetAttribute(attr)
  |> Option.map HtmlAttribute.value
  |> Option.defaultValue ""
Taylor Wood
  • 15,886
  • 1
  • 20
  • 37
  • 2
    @TaylorWood Regarding the second approach, you don't need to do this anymore. Option finally has `defaultValue`. So you can just do, `Option.defaultValue "" (elem.TryGetAttribute(attr))`. – s952163 Mar 11 '18 at 14:51