]> gitweb.michael.orlitzky.com - dead/htsn-import.git/commitdiff
Write the database code for TSN.XML.JFile.
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 27 Jun 2014 19:54:48 +0000 (15:54 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 27 Jun 2014 19:54:48 +0000 (15:54 -0400)
Add tests for TSN.XML.JFile.
Enable JFile import in Main.
Fixup the shell test expected output.
Simplify the big case statement (guard) in Main.

src/Main.hs
src/TSN/XML/JFile.hs
test/TestSuite.hs
test/shell/import-duplicates.test
test/xml/jfilexml.dtd [new file with mode: 0644]
test/xml/jfilexml.xml [new file with mode: 0644]

index 0b6dce631166ef83c06d8be345810e0277893d57..38f393080f9d7f128f1833b32c18199b1888163c 100644 (file)
@@ -57,6 +57,7 @@ import qualified TSN.XML.Injuries as Injuries ( dtd, pickle_message )
 import qualified TSN.XML.InjuriesDetail as InjuriesDetail (
   dtd,
   pickle_message )
+import qualified TSN.XML.JFile as JFile ( dtd, pickle_message )
 import qualified TSN.XML.News as News ( dtd, pickle_message )
 import qualified TSN.XML.Odds as Odds ( dtd, pickle_message )
 import qualified TSN.XML.Scores as Scores ( dtd, pickle_message )
@@ -163,41 +164,41 @@ import_file cfg path = do
           --
           migrate_and_import m = dbmigrate m >> dbimport m
 
+          -- | The error message we return if unpickling fails.
+          --
+          errmsg = "Could not unpickle " ++ dtd ++ "."
+
+          -- | Try to migrate and import using the given pickler @f@;
+          --   if it works, return the result. Otherwise, return an
+          --   'ImportFailed' along with our error message.
+          --
+          go f = maybe
+                   (return $ ImportFailed errmsg)
+                   migrate_and_import
+                   (unpickleDoc f xml)
+
           importer
-            | dtd == AutoRacingResults.dtd = do
-               let m = unpickleDoc AutoRacingResults.pickle_message xml
-               maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == AutoRacingResults.dtd =
+                go AutoRacingResults.pickle_message
 
-            | dtd == AutoRacingSchedule.dtd = do
-               let m = unpickleDoc AutoRacingSchedule.pickle_message xml
-               maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == AutoRacingSchedule.dtd =
+                go AutoRacingSchedule.pickle_message
 
-            -- GameInfo and SportInfo appear least in the guards
-            | dtd == Injuries.dtd = do
-               let m = unpickleDoc Injuries.pickle_message xml
-               maybe (return $ ImportFailed errmsg) migrate_and_import m
+            -- GameInfo and SportInfo appear last in the guards
+            | dtd == Injuries.dtd = go Injuries.pickle_message
 
-            | dtd == InjuriesDetail.dtd = do
-                let m = unpickleDoc InjuriesDetail.pickle_message xml
-                maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == InjuriesDetail.dtd = go InjuriesDetail.pickle_message
 
+            | dtd == JFile.dtd = go JFile.pickle_message
 
-            | dtd == News.dtd = do
-                let m = unpickleDoc News.pickle_message xml
-                maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == News.dtd = go News.pickle_message
 
-            | dtd == Odds.dtd = do
-                let m = unpickleDoc Odds.pickle_message xml
-                maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == Odds.dtd = go Odds.pickle_message
 
-            | dtd == Scores.dtd = do
-                let m = unpickleDoc Scores.pickle_message xml
-                maybe (return $ ImportFailed errmsg) migrate_and_import m
+            | dtd == Scores.dtd = go Scores.pickle_message
 
-            -- SportInfo and GameInfo appear least in the guards
-            | dtd == Weather.dtd = do
-                let m = unpickleDoc Weather.pickle_message xml
-                maybe (return $ ImportFailed errmsg) migrate_and_import m
+            -- SportInfo and GameInfo appear last in the guards
+            | dtd == Weather.dtd = go Weather.pickle_message
 
             | dtd `elem` GameInfo.dtds = do
                 let either_m = GameInfo.parse_xml dtd xml
@@ -220,8 +221,6 @@ import_file cfg path = do
                     "Unrecognized DTD in " ++ path ++ ": " ++ dtd ++ "."
               return $ ImportUnsupported infomsg
 
-            where
-              errmsg = "Could not unpickle " ++ dtd ++ "."
 
 
 -- | Entry point of the program. It twiddles some knobs for
index 2eed37fb7e2f8eb555ce7ed510c1e0df8d55159b..5642c8957dc8b566902c15b09b3875d3f8959e1f 100644 (file)
 --   a message contains a bunch of games.
 --
 module TSN.XML.JFile (
-  dtd )
+  dtd,
+  pickle_message,
+  -- * Tests
+  jfile_tests,
+  -- * WARNING: these are private but exported to silence warnings
+  JFileConstructor(..),
+  JFileGameConstructor(..),
+  JFileGame_TeamConstructor(..) )
 where
 
 -- System imports
+import Control.Monad ( forM_ )
+import Data.List ( intercalate )
+import Data.String.Utils ( split )
 import Data.Time ( UTCTime(..) )
 import Data.Tuple.Curry ( uncurryN )
-import Database.Groundhog ( migrate )
+import Database.Groundhog (
+  countAll,
+  deleteAll,
+  insert_,
+  migrate,
+  runMigration,
+  silentMigrationLogger )
 import Database.Groundhog.Core ( DefaultKey )
+import Database.Groundhog.Generic ( runDbConn )
+import Database.Groundhog.Sqlite ( withSqliteConn )
 import Database.Groundhog.TH (
   groundhog,
   mkPersist )
+import Test.Tasty ( TestTree, testGroup )
+import Test.Tasty.HUnit ( (@?=), testCase )
 import Text.XML.HXT.Core (
   PU,
   xpTriple,
   xp6Tuple,
-  xp7Tuple,
-  xp8Tuple,
-  xp10Tuple,
   xp14Tuple,
+  xp19Tuple,
   xpAttr,
   xpElem,
   xpInt,
   xpList,
   xpOption,
   xpPair,
+  xpPrim,
   xpText,
+  xpText0,
   xpWrap )
 
 
 -- Local imports
 import TSN.Codegen ( tsn_codegen_config )
 import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
-import TSN.Picklers ( xp_date_padded, xp_time, xp_time_stamp )
+import TSN.Picklers (
+  xp_date,
+  xp_date_padded,
+  xp_datetime,
+  xp_time,
+  xp_time_dots,
+  xp_time_stamp )
 import TSN.Team ( Team(..) )
 import TSN.XmlImport (
   XmlImport(..),
   XmlImportFk(..) )
-
 import Xml (
   FromXml(..),
   FromXmlFk(..),
-  ToDb(..) )
+  ToDb(..),
+  pickle_unpickle,
+  unpickleable,
+  unsafe_unpickle )
 
 
 
@@ -192,22 +220,25 @@ instance XmlImport JFileGameHomeTeamXml
 --   measure, but in the conversion to the database type, we can drop
 --   all of the redundant information.
 --
+--   All of these are optional because TSN does actually leave the
+--   whole thing empty from time to time.
+--
 data JFileGameOddsInfo =
   JFileGameOddsInfo {
-    db_list_date :: UTCTime,
-    db_home_team_id :: String, -- redundant (Team)
-    db_away_team_id :: String, -- redundant (Team)
-    db_home_abbr :: String, -- redundant (Team)
-    db_away_abbr :: String, -- redundant (Team)
-    db_home_team_name :: String, -- redundant (Team)
-    db_away_team_name :: String, -- redundant (Team)
-    db_home_starter :: String,
-    db_away_starter :: String,
-    db_game_date :: UTCTime, -- redundant (JFileGame)
-    db_home_game_key :: Int,
-    db_away_game_key :: Int,
-    db_current_timestamp :: UTCTime,
-    db_live :: Bool,
+    db_list_date :: Maybe UTCTime,
+    db_home_team_id :: Maybe String, -- redundant (Team)
+    db_away_team_id :: Maybe String, -- redundant (Team)
+    db_home_abbr :: Maybe String, -- redundant (Team)
+    db_away_abbr :: Maybe String, -- redundant (Team)
+    db_home_team_name :: Maybe String, -- redundant (Team)
+    db_away_team_name :: Maybe String, -- redundant (Team)
+    db_home_starter :: Maybe String,
+    db_away_starter :: Maybe String,
+    db_game_date :: Maybe UTCTime, -- redundant (JFileGame)
+    db_home_game_key :: Maybe Int,
+    db_away_game_key :: Maybe Int,
+    db_current_timestamp :: Maybe UTCTime,
+    db_live :: Maybe Bool,
     db_notes :: String }
   deriving (Eq, Show)
 
@@ -219,7 +250,7 @@ data JFileGameOddsInfo =
 data JFileGameStatus =
   JFileGameStatus {
     db_status_numeral :: Int,
-    db_status  :: String }
+    db_status  :: Maybe String }
   deriving (Eq, Show)
 
 
@@ -235,7 +266,7 @@ data JFileGame =
     db_game_id :: Int,
     db_schedule_id :: Int,
     db_odds_info :: JFileGameOddsInfo,
-    db_season_type :: String,
+    db_season_type :: Maybe String,
     db_game_time :: UTCTime,
     db_vleague :: Maybe String,
     db_hleague :: Maybe String,
@@ -256,7 +287,7 @@ data JFileGameXml =
     xml_game_id :: Int,
     xml_schedule_id :: Int,
     xml_odds_info :: JFileGameOddsInfo,
-    xml_season_type :: String,
+    xml_season_type :: Maybe String,
     xml_game_date :: UTCTime,
     xml_game_time :: UTCTime,
     xml_vteam :: JFileGameAwayTeamXml,
@@ -306,7 +337,7 @@ instance FromXmlFk JFileGameXml where
       db_schedule_id = xml_schedule_id,
       db_odds_info = xml_odds_info,
       db_season_type = xml_season_type,
-      db_game_time = xml_game_time,
+      db_game_time = make_game_time xml_game_date xml_game_time,
       db_vleague = xml_vleague,
       db_hleague = xml_hleague,
       db_vscore = xml_vscore,
@@ -318,8 +349,7 @@ instance FromXmlFk JFileGameXml where
       --   date/time. Simply take the day part from one and the time
       --   from the other.
       --
-      make_game_time d Nothing = d
-      make_game_time d (Just t) = UTCTime (utctDay d) (utctDayTime t)
+      make_game_time d t = UTCTime (utctDay d) (utctDayTime t)
 
 
 -- | This allows us to insert the XML representation
@@ -351,7 +381,31 @@ instance DbImport Message where
       migrate (undefined :: JFileGame)
       migrate (undefined :: JFileGame_Team)
 
-  dbimport m = return ImportSucceeded
+  dbimport m = do
+    -- Insert the top-level message
+    msg_id <- insert_xml m
+
+    -- Now loop through the message's games
+    forM_ (xml_games $ xml_gamelist m) $ \game -> do
+
+      -- Next, we insert the home and away teams. We do this before
+      -- inserting the game itself because the game has two foreign keys
+      -- pointing to "teams".
+      away_team_id <- insert_xml_or_select (xml_vteam game)
+      home_team_id <- insert_xml_or_select (xml_hteam game)
+
+      game_id <- insert_xml_fk msg_id game
+
+      -- Insert a record into jfile_games__teams mapping the
+      -- home/away teams to this game. Use the full record syntax
+      -- because the types would let us mix up the home/away teams.
+      insert_ JFileGame_Team {
+                jgt_jfile_games_id = game_id,
+                jgt_away_team_id = away_team_id,
+                jgt_home_team_id = home_team_id }
+
+
+    return ImportSucceeded
 
 
 mkPersist tsn_codegen_config [groundhog|
@@ -405,7 +459,7 @@ mkPersist tsn_codegen_config [groundhog|
             - {name: home_starter, dbName: home_starter}
             - {name: away_starter, dbName: away_starter}
             - {name: home_game_key, dbName: home_game_key}
-            - {name: away_game_key, dbName: home_game_key}
+            - {name: away_game_key, dbName: away_game_key}
             - {name: current_timestamp, dbName: current_timestamp}
             - {name: live, dbName: live}
             - {name: notes, dbName: notes}
@@ -475,7 +529,7 @@ pickle_game =
     xp14Tuple (xpElem "game_id" xpInt)
               (xpElem "schedule_id" xpInt)
               pickle_odds_info
-              (xpElem "seasontype" xpText)
+              (xpElem "seasontype" (xpOption xpText))
               (xpElem "Game_Date" xp_date_padded)
               (xpElem "Game_Time" xp_time)
               pickle_away_team
@@ -503,8 +557,67 @@ pickle_game =
                   xml_time_remaining m,
                   xml_game_status m)
 
-pickle_odds_info = undefined
-
+pickle_odds_info :: PU JFileGameOddsInfo
+pickle_odds_info =
+  xpElem "Odds_Info" $
+    xpWrap (from_tuple, to_tuple) $
+    xp19Tuple (xpElem "ListDate" (xpOption xp_date))
+              (xpElem "HomeTeamID" (xpOption xpText))
+              (xpElem "AwayTeamID" (xpOption xpText))
+              (xpElem "HomeAbbr" (xpOption xpText))
+              (xpElem "AwayAbbr" (xpOption xpText))
+              (xpElem "HomeTeamName" (xpOption xpText))
+              (xpElem "AwayTeamName" (xpOption xpText))
+              (xpElem "HStarter" (xpOption xpText))
+              (xpElem "AStarter" (xpOption xpText))
+              (xpElem "GameDate" (xpOption xp_datetime))
+              (xpElem "HGameKey" (xpOption xpInt))
+              (xpElem "AGameKey" (xpOption xpInt))
+              (xpElem "CurrentTimeStamp" (xpOption xp_time_dots))
+              (xpElem "Live" (xpOption xpPrim))
+              (xpElem "Notes1" xpText0)
+              (xpElem "Notes2" xpText0)
+              (xpElem "Notes3" xpText0)
+              (xpElem "Notes4" xpText0)
+              (xpElem "Notes5" xpText0)
+  where
+    from_tuple (x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,n1,n2,n3,n4,n5) =
+      JFileGameOddsInfo x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 notes
+      where
+        notes = intercalate "\n" [n1,n2,n3,n4,n5]
+
+    to_tuple o = (db_list_date o,
+                  db_home_team_id o,
+                  db_away_team_id o,
+                  db_home_abbr o,
+                  db_away_abbr o,
+                  db_home_team_name o,
+                  db_away_team_name o,
+                  db_home_starter o,
+                  db_away_starter o,
+                  db_game_date o,
+                  db_home_game_key o,
+                  db_away_game_key o,
+                  db_current_timestamp o,
+                  db_live o,
+                  n1,n2,n3,n4,n5)
+      where
+        note_lines = split "\n" (db_notes o)
+        n1 = case note_lines of
+               (notes1:_) -> notes1
+               _          -> ""
+        n2 = case note_lines of
+               (_:notes2:_) -> notes2
+               _            -> ""
+        n3 = case note_lines of
+               (_:_:notes3:_) -> notes3
+               _              -> ""
+        n4 = case note_lines of
+               (_:_:_:notes4:_) -> notes4
+               _                -> ""
+        n5 = case note_lines of
+               (_:_:_:_:notes5:_) -> notes5
+               _                  -> ""
 
 pickle_home_team :: PU JFileGameHomeTeamXml
 pickle_home_team =
@@ -539,8 +652,80 @@ pickle_status =
   xpElem "status" $
     xpWrap (from_tuple, to_tuple) $
     xpPair (xpAttr "numeral" xpInt)
-           xpText
+           (xpOption xpText)
   where
     from_tuple = uncurry JFileGameStatus
     to_tuple s = (db_status_numeral s,
                   db_status s)
+
+
+
+--
+-- Tasty Tests
+--
+
+-- | A list of all tests for this module.
+--
+jfile_tests :: TestTree
+jfile_tests =
+  testGroup
+    "JFile tests"
+    [ test_on_delete_cascade,
+      test_pickle_of_unpickle_is_identity,
+      test_unpickle_succeeds ]
+
+
+-- | If we unpickle something and then pickle it, we should wind up
+--   with the same thing we started with. WARNING: success of this
+--   test does not mean that unpickling succeeded.
+--
+test_pickle_of_unpickle_is_identity :: TestTree
+test_pickle_of_unpickle_is_identity =
+  testCase "pickle composed with unpickle is the identity" $ do
+    let path = "test/xml/jfilexml.xml"
+    (expected, actual) <- pickle_unpickle pickle_message path
+    actual @?= expected
+
+
+
+-- | Make sure we can actually unpickle these things.
+--
+test_unpickle_succeeds :: TestTree
+test_unpickle_succeeds =
+  testCase "unpickling succeeds" $ do
+    let path = "test/xml/jfilexml.xml"
+    actual <- unpickleable path pickle_message
+
+    let expected = True
+    actual @?= expected
+
+
+
+-- | Make sure everything gets deleted when we delete the top-level
+--   record.
+--
+test_on_delete_cascade :: TestTree
+test_on_delete_cascade =
+  testCase "deleting auto_racing_results deletes its children" $ do
+    let path = "test/xml/jfilexml.xml"
+    results <- unsafe_unpickle path pickle_message
+    let a = undefined :: Team
+    let b = undefined :: JFile
+    let c = undefined :: JFileGame
+    let d = undefined :: JFileGame_Team
+
+    actual <- withSqliteConn ":memory:" $ runDbConn $ do
+                runMigration silentMigrationLogger $ do
+                  migrate a
+                  migrate b
+                  migrate c
+                  migrate d
+                _ <- dbimport results
+                deleteAll b
+                count_a <- countAll a
+                count_b <- countAll b
+                count_c <- countAll c
+                count_d <- countAll d
+                return $ sum [count_a, count_b, count_c, count_d]
+    let expected = 20 -- Twenty teams should be left over
+    actual @?= expected
index e0e86a9750826ea5f78d0ae8a97e34feaccefc85..a67ee2a682b878db2aa99559002a8710e814ece8 100644 (file)
@@ -6,6 +6,7 @@ import TSN.XML.GameInfo ( game_info_tests )
 import TSN.XML.Heartbeat ( heartbeat_tests )
 import TSN.XML.Injuries ( injuries_tests )
 import TSN.XML.InjuriesDetail ( injuries_detail_tests )
+import TSN.XML.JFile ( jfile_tests )
 import TSN.XML.News ( news_tests )
 import TSN.XML.Odds ( odds_tests )
 import TSN.XML.Scores ( scores_tests )
@@ -21,6 +22,7 @@ tests = testGroup
             heartbeat_tests,
             injuries_tests,
             injuries_detail_tests,
+            jfile_tests,
             news_tests,
             odds_tests,
             scores_tests,
index b262b82e9eafd63edc1fec876516de62aee6977e..7dac44f6a5ad503d688168a6fbee87ed31d43d02 100644 (file)
@@ -15,15 +15,15 @@ rm -f shelltest.sqlite3
 # Heartbeat.xml that doesn't really count.
 find ./test/xml -maxdepth 1 -name '*.xml' | wc -l
 >>>
-17
+18
 >>>= 0
 
 # Run the imports again; we should get complaints about the duplicate
-# xml_file_ids. There are 2 errors for each violation, so we expect 2*16
+# xml_file_ids. There are 2 errors for each violation, so we expect 2*17
 # occurrences of the string 'ERROR'.
 ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l
 >>>
-32
+34
 >>>= 0
 
 # Finally, clean up after ourselves.
diff --git a/test/xml/jfilexml.dtd b/test/xml/jfilexml.dtd
new file mode 100644 (file)
index 0000000..8b841fd
--- /dev/null
@@ -0,0 +1,47 @@
+<!ELEMENT XML_File_ID (#PCDATA)>
+<!ELEMENT heading (#PCDATA)>
+<!ELEMENT category (#PCDATA)>
+<!ELEMENT sport (#PCDATA)>
+<!ELEMENT game_id (#PCDATA)>
+<!ELEMENT schedule_id (#PCDATA)>
+<!ELEMENT ListDate (#PCDATA)>
+<!ELEMENT HomeTeamID (#PCDATA)>
+<!ELEMENT AwayTeamID (#PCDATA)>
+<!ELEMENT HomeAbbr (#PCDATA)>
+<!ELEMENT AwayAbbr (#PCDATA)>
+<!ELEMENT HomeTeamName (#PCDATA)>
+<!ELEMENT AwayTeamName (#PCDATA)>
+<!ELEMENT HStarter (#PCDATA)>
+<!ELEMENT AStarter (#PCDATA)>
+<!ELEMENT GameDate (#PCDATA)>
+<!ELEMENT HGameKey (#PCDATA)>
+<!ELEMENT AGameKey (#PCDATA)>
+<!ELEMENT CurrentTimeStamp (#PCDATA)>
+<!ELEMENT Live (#PCDATA)>
+<!ELEMENT Notes1 (#PCDATA)>
+<!ELEMENT Notes2 (#PCDATA)>
+<!ELEMENT Notes3 EMPTY>
+<!ELEMENT Notes4 EMPTY>
+<!ELEMENT Notes5 EMPTY>
+<!ELEMENT Odds_Info ( ( ListDate, HomeTeamID, AwayTeamID, HomeAbbr, AwayAbbr, HomeTeamName, AwayTeamName, HStarter, AStarter, GameDate, HGameKey, AGameKey, CurrentTimeStamp, Live, Notes1, Notes2, Notes3, Notes4, Notes5 ) )>
+<!ELEMENT seasontype (#PCDATA)>
+<!ELEMENT Game_Date (#PCDATA)>
+<!ELEMENT Game_Time (#PCDATA)>
+<!ELEMENT vteam (#PCDATA)>
+<!ELEMENT hteam (#PCDATA)>
+<!ELEMENT vscore (#PCDATA)>
+<!ELEMENT hscore (#PCDATA)>
+<!ELEMENT status (#PCDATA)>
+<!ELEMENT game ( ( game_id, schedule_id, Odds_Info, seasontype, Game_Date, Game_Time, vteam, vleague?, hteam, hleague?, vscore, hscore, time_r?, status ) )>
+<!ELEMENT gamelist ( game* )>
+<!ELEMENT time_stamp (#PCDATA)>
+<!ELEMENT message ( ( XML_File_ID, heading, category, sport, gamelist, time_stamp ) )>
+<!ELEMENT time_r (#PCDATA)>
+<!ELEMENT vleague (#PCDATA)>
+<!ELEMENT hleague (#PCDATA)>
+
+<!ATTLIST vteam teamid CDATA #REQUIRED>
+<!ATTLIST vteam abbr CDATA #REQUIRED>
+<!ATTLIST hteam teamid CDATA #REQUIRED>
+<!ATTLIST hteam abbr CDATA #REQUIRED>
+<!ATTLIST status numeral CDATA #REQUIRED>
diff --git a/test/xml/jfilexml.xml b/test/xml/jfilexml.xml
new file mode 100644 (file)
index 0000000..87c8514
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no" ?>\r<!DOCTYPE message PUBLIC "-//TSN//DTD Scores 1.0/EN" "jfilexml.dtd">\r<message>\r<XML_File_ID>21321128</XML_File_ID>\r<heading>BC-AAJ</heading>\r<category>JFILE</category>\r<sport>MLB</sport>\r<gamelist>\r<game>\r<game_id>40293</game_id>\r<schedule_id>40293</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>008</HomeTeamID>\r<AwayTeamID>014</AwayTeamID>\r<HomeAbbr>PHI</HomeAbbr>\r<AwayAbbr>MIA</AwayAbbr>\r<HomeTeamName>Philadelphia</HomeTeamName>\r<AwayTeamName>Miami</AwayTeamName>\r<HStarter>R.Hernandez</HStarter>\r<AStarter>N.Eovaldi</AStarter>\r<GameDate>6/23/2014 7:05:00 PM</GameDate>\r<HGameKey>902</HGameKey>\r<AGameKey>901</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>07:05 PM</Game_Time>\r<vteam teamid="014" abbr="MIA">Miami</vteam>\r<vleague>NL</vleague>\r<hteam teamid="008" abbr="PHI">Philadelphia</hteam>\r<hleague>NL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">7:05 PM</status>\r</game>\r<game>\r<game_id>40294</game_id>\r<schedule_id>40294</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>002</HomeTeamID>\r<AwayTeamID>003</AwayTeamID>\r<HomeAbbr>CHC</HomeAbbr>\r<AwayAbbr>CIN</AwayAbbr>\r<HomeTeamName>Chicago Cubs</HomeTeamName>\r<AwayTeamName>Cincinnati</AwayTeamName>\r<HStarter>J.Samardzija</HStarter>\r<AStarter>A.Simon</AStarter>\r<GameDate>6/23/2014 8:05:00 PM</GameDate>\r<HGameKey>904</HGameKey>\r<AGameKey>903</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>08:05 PM</Game_Time>\r<vteam teamid="003" abbr="CIN">Cincinnati</vteam>\r<vleague>NL</vleague>\r<hteam teamid="002" abbr="CHC">Chicago Cubs</hteam>\r<hleague>NL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">8:05 PM</status>\r</game>\r<game>\r<game_id>40295</game_id>\r<schedule_id>40295</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>034</HomeTeamID>\r<AwayTeamID>006</AwayTeamID>\r<HomeAbbr>MIL</HomeAbbr>\r<AwayAbbr>WAS</AwayAbbr>\r<HomeTeamName>Milwaukee</HomeTeamName>\r<AwayTeamName>Washington</AwayTeamName>\r<HStarter>M.Garza</HStarter>\r<AStarter>G.Gonzalez</AStarter>\r<GameDate>6/23/2014 8:10:00 PM</GameDate>\r<HGameKey>906</HGameKey>\r<AGameKey>905</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>08:10 PM</Game_Time>\r<vteam teamid="006" abbr="WAS">Washington</vteam>\r<vleague>NL</vleague>\r<hteam teamid="034" abbr="MIL">Milwaukee</hteam>\r<hleague>NL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">8:10 PM</status>\r</game>\r<game>\r<game_id>40297</game_id>\r<schedule_id>40297</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>013</HomeTeamID>\r<AwayTeamID>012</AwayTeamID>\r<HomeAbbr>COL</HomeAbbr>\r<AwayAbbr>STL</AwayAbbr>\r<HomeTeamName>Colorado</HomeTeamName>\r<AwayTeamName>St. Louis</AwayTeamName>\r<HStarter>J.Chacin</HStarter>\r<AStarter>L.Lynn</AStarter>\r<GameDate>6/23/2014 8:40:00 PM</GameDate>\r<HGameKey>908</HGameKey>\r<AGameKey>907</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>08:40 PM</Game_Time>\r<vteam teamid="012" abbr="STL">St. Louis</vteam>\r<vleague>NL</vleague>\r<hteam teamid="013" abbr="COL">Colorado</hteam>\r<hleague>NL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">8:40 PM</status>\r</game>\r<game>\r<game_id>40296</game_id>\r<schedule_id>40296</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>011</HomeTeamID>\r<AwayTeamID>010</AwayTeamID>\r<HomeAbbr>SFG</HomeAbbr>\r<AwayAbbr>SDP</AwayAbbr>\r<HomeTeamName>San Francisco</HomeTeamName>\r<AwayTeamName>San Diego</AwayTeamName>\r<HStarter>M.Cain</HStarter>\r<AStarter>O.Despaigne</AStarter>\r<GameDate>6/23/2014 10:15:00 PM</GameDate>\r<HGameKey>910</HGameKey>\r<AGameKey>909</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>10:15 PM</Game_Time>\r<vteam teamid="010" abbr="SDP">San Diego</vteam>\r<vleague>NL</vleague>\r<hteam teamid="011" abbr="SFG">San Francisco</hteam>\r<hleague>NL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">10:15 PM</status>\r</game>\r<game>\r<game_id>40287</game_id>\r<schedule_id>40287</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>027</HomeTeamID>\r<AwayTeamID>030</AwayTeamID>\r<HomeAbbr>BAL</HomeAbbr>\r<AwayAbbr>CWS</AwayAbbr>\r<HomeTeamName>Baltimore</HomeTeamName>\r<AwayTeamName>Chicago WSox</AwayTeamName>\r<HStarter>W.Chen</HStarter>\r<AStarter>C.Sale</AStarter>\r<GameDate>6/23/2014 7:05:00 PM</GameDate>\r<HGameKey>914</HGameKey>\r<AGameKey>913</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>07:05 PM</Game_Time>\r<vteam teamid="030" abbr="CWS">Chicago WSox</vteam>\r<vleague>AL</vleague>\r<hteam teamid="027" abbr="BAL">Baltimore</hteam>\r<hleague>AL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">7:05 PM</status>\r</game>\r<game>\r<game_id>40289</game_id>\r<schedule_id>40289</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>040</HomeTeamID>\r<AwayTeamID>036</AwayTeamID>\r<HomeAbbr>TOR</HomeAbbr>\r<AwayAbbr>NYY</AwayAbbr>\r<HomeTeamName>Toronto</HomeTeamName>\r<AwayTeamName>NY Yankees</AwayTeamName>\r<HStarter>M.Stroman</HStarter>\r<AStarter>C.Whitley</AStarter>\r<GameDate>6/23/2014 7:07:00 PM</GameDate>\r<HGameKey>912</HGameKey>\r<AGameKey>911</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>07:07 PM</Game_Time>\r<vteam teamid="036" abbr="NYY">NY Yankees</vteam>\r<vleague>AL</vleague>\r<hteam teamid="040" abbr="TOR">Toronto</hteam>\r<hleague>AL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">7:07 PM</status>\r</game>\r<game>\r<game_id>40291</game_id>\r<schedule_id>40291</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>038</HomeTeamID>\r<AwayTeamID>028</AwayTeamID>\r<HomeAbbr>SEA</HomeAbbr>\r<AwayAbbr>BOS</AwayAbbr>\r<HomeTeamName>Seattle</HomeTeamName>\r<AwayTeamName>Boston</AwayTeamName>\r<HStarter>F.Hernandez</HStarter>\r<AStarter>J.Lackey</AStarter>\r<GameDate>6/23/2014 10:10:00 PM</GameDate>\r<HGameKey>916</HGameKey>\r<AGameKey>915</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>10:10 PM</Game_Time>\r<vteam teamid="028" abbr="BOS">Boston</vteam>\r<vleague>AL</vleague>\r<hteam teamid="038" abbr="SEA">Seattle</hteam>\r<hleague>AL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">10:10 PM</status>\r</game>\r<game>\r<game_id>40288</game_id>\r<schedule_id>40288</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>056</HomeTeamID>\r<AwayTeamID>009</AwayTeamID>\r<HomeAbbr>TAM</HomeAbbr>\r<AwayAbbr>PIT</AwayAbbr>\r<HomeTeamName>Tampa Bay</HomeTeamName>\r<AwayTeamName>Pittsburgh</AwayTeamName>\r<HStarter>A.Cobb</HStarter>\r<AStarter>E.Volquez</AStarter>\r<GameDate>6/23/2014 7:10:00 PM</GameDate>\r<HGameKey>918</HGameKey>\r<AGameKey>917</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>07:10 PM</Game_Time>\r<vteam teamid="009" abbr="PIT">Pittsburgh</vteam>\r<vleague>NL</vleague>\r<hteam teamid="056" abbr="TAM">Tampa Bay</hteam>\r<hleague>AL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">7:10 PM</status>\r</game>\r<game>\r<game_id>40292</game_id>\r<schedule_id>40292</schedule_id>\r<Odds_Info>\r<ListDate>6/23/2014</ListDate>\r<HomeTeamID>033</HomeTeamID>\r<AwayTeamID>005</AwayTeamID>\r<HomeAbbr>KAN</HomeAbbr>\r<AwayAbbr>LOS</AwayAbbr>\r<HomeTeamName>Kansas City</HomeTeamName>\r<AwayTeamName>Los Angeles</AwayTeamName>\r<HStarter>J.Guthrie</HStarter>\r<AStarter>Z.Greinke</AStarter>\r<GameDate>6/23/2014 8:10:00 PM</GameDate>\r<HGameKey>920</HGameKey>\r<AGameKey>919</AGameKey>\r<CurrentTimeStamp>11:30 A.M.</CurrentTimeStamp>\r<Live>True</Live>\r<Notes1></Notes1>\r<Notes2></Notes2>\r<Notes3></Notes3>\r<Notes4></Notes4>\r<Notes5></Notes5>\r</Odds_Info>\r<seasontype>Regular</seasontype>\r<Game_Date>06/23/2014</Game_Date>\r<Game_Time>08:10 PM</Game_Time>\r<vteam teamid="005" abbr="LOS">Los Angeles</vteam>\r<vleague>NL</vleague>\r<hteam teamid="033" abbr="KAN">Kansas City</hteam>\r<hleague>AL</hleague>\r<vscore>0</vscore>\r<hscore>0</hscore>\r<status numeral="0">8:10 PM</status>\r</game>\r</gamelist>\r<time_stamp>June 23, 2014, at 12:10 PM ET </time_stamp>\r</message>\r
\ No newline at end of file