packages feed

htsn-0.0.5: src/Xml.hs

-- | Minimal XML functionality needed to parse each document's
--   XML_File_ID.
--
module Xml (
  parse_xmlfid,
  -- * Tests
  xml_tests )
where

import Data.Either.Utils ( maybeToEither )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), Assertion, testCase )
import Text.Read ( readMaybe )
import Text.XML.HXT.Core (
  (>>>),
  (/>),
  getChildren,
  getText,
  hasName,
  runLA,
  xreadDoc )


-- | A tiny parser written in HXT to extract the \"XML_File_ID\"
--   element from a document. If we fail to parse an XML_File_ID, we
--   return the reason wrapped in a 'Left' constructor. The reason
--   should be one of two things:
--
--     1. No XML_File_ID elements were found.
--
--     2. An XML_File_ID element was found, but it could not be read
--        into an Integer.
--
--   We use an Either rather than a Maybe because we do expect some
--   non-integer XML_File_IDs. In the examples, you will see
--   NHL_DepthChart_XML.XML with an XML_File_ID of \"49618.61\" and
--   CFL_Boxscore_XML1.xml with an XML_File_ID of
--   \"R28916\". According to Brijesh Patel of TSN, these are special
--   category files and not part of the usual feed.
--
--   We want to report them differently, \"just in case.\"
--
parse_xmlfid :: String -- ^ The XML Document
             -> Either String Integer
parse_xmlfid doc =
  case parse_results of
    []    -> Left "No XML_File_ID elements found."
    (x:_) -> x
  where
    parse :: String -> [String]
    parse =
      runLA (xreadDoc
               >>> hasName "message"
               />  hasName "XML_File_ID"
               >>> getChildren
               >>> getText)

    read_either_integer :: String -> Either String Integer
    read_either_integer s =
      let msg = "Could not parse XML_File_ID " ++ s ++ " as an integer."
      in
        maybeToEither msg (readMaybe s)

    elements = parse doc
    parse_results = map read_either_integer elements


--
-- Tasty Tests
--

-- | A list of all tests for this module.
--
xml_tests :: TestTree
xml_tests =
  testGroup
    "XML tests"
    [ xml_file_id_tests ]


-- | Ensure that we parse the correct XML_File_ID out of some known
--   examples.
--
xml_file_id_tests :: TestTree
xml_file_id_tests =
  testCase "XML_File_ID is parsed correctly" $ do
    let xmlfids = ["19908216", "19908216", "19908245", "19908246", "19908247"]
    mapM_ check xmlfids
  where
    check :: String -> Assertion
    check xmlfid = do
      xml <- readFile ("test/xml/" ++ xmlfid ++ ".xml")
      let actual = parse_xmlfid xml
      -- The maybeToEither should always succeed here, so the error
      -- message goes unused.
      let expected = maybeToEither "derp" (readMaybe xmlfid)
      actual @?= expected