]> gitweb.michael.orlitzky.com - dead/htsn-import.git/commitdiff
Add a new module, TSN.XML.MLBEarlyLines supporting MLB_earlylinesXML.dtd.
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 25 Jul 2014 03:23:03 +0000 (23:23 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 25 Jul 2014 03:23:03 +0000 (23:23 -0400)
Add TSN.XML.MLBEarlyLines to the .ghci and cabal files.
Mention all TSN.XML.MLBEarlyLines-related weirdness in the man page.
Add test cases for TSN.XML.MLBEarlyLines to the test suite.
Enable import of MLB_earlyinesXML.dtd documents in Main.
Bump the file counts in import-duplicates.test.

.ghci
doc/TODO
doc/man1/htsn-import.1
htsn-import.cabal
src/Main.hs
src/TSN/XML/MLBEarlyLine.hs [new file with mode: 0644]
test/TestSuite.hs
test/shell/import-duplicates.test
test/xml/MLB_earlylineXML.dtd [new file with mode: 0644]
test/xml/MLB_earlylineXML.xml [new file with mode: 0644]

diff --git a/.ghci b/.ghci
index 0df75f144d36c1e34aa47b08d31619ce46b5cd9a..be999422548f27a2e8d75b5ed9a0de45409259b5 100644 (file)
--- a/.ghci
+++ b/.ghci
@@ -25,6 +25,7 @@
   src/TSN/XML/Injuries.hs
   src/TSN/XML/InjuriesDetail.hs
   src/TSN/XML/JFile.hs
   src/TSN/XML/Injuries.hs
   src/TSN/XML/InjuriesDetail.hs
   src/TSN/XML/JFile.hs
+  src/TSN/XML/MLBEarlyLine.hs
   src/TSN/XML/News.hs
   src/TSN/XML/Odds.hs
   src/TSN/XML/ScheduleChanges.hs
   src/TSN/XML/News.hs
   src/TSN/XML/Odds.hs
   src/TSN/XML/ScheduleChanges.hs
@@ -56,6 +57,7 @@ import TSN.XML.Heartbeat
 import TSN.XML.Injuries
 import TSN.XML.InjuriesDetail
 import TSN.XML.JFile
 import TSN.XML.Injuries
 import TSN.XML.InjuriesDetail
 import TSN.XML.JFile
+import TSN.XML.MLBEarlyLine
 import TSN.XML.News
 import TSN.XML.Odds
 import TSN.XML.ScheduleChanges
 import TSN.XML.News
 import TSN.XML.Odds
 import TSN.XML.ScheduleChanges
index 01caafd59abac4420be1af5390501d956cec58ca..9ab4e8ed1c21b3028a4849f0c32e0378a426365c 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -39,7 +39,6 @@
    * Minor_Baseball_TeamScheduleXML
    * MinorLeagueHockeyTeamScheduleXML
    * MLB_Boxscore_XML
    * Minor_Baseball_TeamScheduleXML
    * MinorLeagueHockeyTeamScheduleXML
    * MLB_Boxscore_XML
-   * MLB_earlylineXML
    * MLB_IndividualStats_XML
    * MLB_Probable_Pitchers_XML
    * MLB_Roster_XML
    * MLB_IndividualStats_XML
    * MLB_Probable_Pitchers_XML
    * MLB_Roster_XML
@@ -64,3 +63,6 @@
 
 5. Consolidate all of the make_game_time functions which take a
    date/time and produce a combined time.
 
 5. Consolidate all of the make_game_time functions which take a
    date/time and produce a combined time.
+
+6. Factor out test code where possible; a lot of them differ only in
+   the filename.
index 66f7ae5d1e769b23357f9fcdea9aaf1beb69cc83..34a8bbef7dd0cfbfb4ec412707ad93de709a987e 100644 (file)
@@ -106,6 +106,26 @@ type are provided with the \fBhtsn-import\fR documentation, in the
 should be considered a bug if they are incorrect. The diagrams are
 created using the pgModeler <http://www.pgmodeler.com.br/> tool.
 
 should be considered a bug if they are incorrect. The diagrams are
 created using the pgModeler <http://www.pgmodeler.com.br/> tool.
 
+.SH DATABASE SCHEMA COMPROMISES
+
+There are a few places that the database schema isn't exactly how we'd
+like it to be:
+
+.IP \[bu] 2
+\fIearlylineXML.dtd\fR
+
+The database representations for earlylineXML.dtd and
+MLB_earlylineXML.dtd are the same; that is, they share the same
+tables. The two document types represent team names in different
+ways. In order to accomodate both types with one parser, we had to
+make both ways optional, and then merge the two together before
+converting to the database representation.
+
+Unfortunately, when we merge two optional things together, we get
+another optional thing back. There's no way to say that \(dqat least
+one is not optional.\(dq So the team names in the database schema are
+optional as well, even though they should always be present.
+
 .SH NULL POLICY
 .P
 Normally in a database one makes a distinction between fields that
 .SH NULL POLICY
 .P
 Normally in a database one makes a distinction between fields that
@@ -204,8 +224,8 @@ Successfully imported schemagen/Odds_XML/19996433.xml.
 Processed 1 document(s) total.
 .fi
 .P
 Processed 1 document(s) total.
 .fi
 .P
-At this point, the database schema matches the old documents, i.e. the
-ones without \fIAStarter\fR and \fIHStarter\fR. If we use a new
+At this point, the database schema matches the old documents, that is,
+the ones without \fIAStarter\fR and \fIHStarter\fR. If we use a new
 version of \fBhtsn-import\fR, supporting the new fields, the migration
 is handled gracefully:
 .P
 version of \fBhtsn-import\fR, supporting the new fields, the migration
 is handled gracefully:
 .P
@@ -284,6 +304,33 @@ We don't parse this case at the moment, but we do recognize it and report
 it as unsupported so that offending documents can be removed. An example
 is provided as test/xml/newsxml-multiple-sms.xml.
 
 it as unsupported so that offending documents can be removed. An example
 is provided as test/xml/newsxml-multiple-sms.xml.
 
+.IP \[bu]
+\fIMLB_earlylineXML.dtd\fR
+
+Unlike earlylineXML.dtd, this document type has more than one <game>
+associated with each <date>. Moreover, each <date> has a bunch of
+<note> children that are supposed to be associated with the <game>s,
+but the document structure indicates no explicit relationship. For
+example,
+
+.nf
+<date>
+  <note>...</note>
+  <game>...</game>
+  <game>...</game>
+  <note>...</note>
+  <game>...</game>
+</date>
+.fi
+
+Here the first <note> is inferred to apply to the two <game>s that
+follow it, and the second <note> applies to the single <game> that
+follows it. But this is very fragile to parse. Instead, we use a hack
+to facilitate (un)pickling, and then drop the notes entirely during
+the database conversion.
+
+A similar workaround is implemented for Odds_XML.dtd.
+
 .IP \[bu]
 \fIOdds_XML.dtd\fR
 
 .IP \[bu]
 \fIOdds_XML.dtd\fR
 
@@ -333,10 +380,10 @@ in every message. A typical timestamp looks like,
 The \(dqtime zone\(dq is given as \(dqET\(dq, but unfortunately
 \(dqET\(dq is not a valid time zone. It stands for \(dqEastern
 Time\(dq, which can belong to either of two time zones, EST or EDT,
 The \(dqtime zone\(dq is given as \(dqET\(dq, but unfortunately
 \(dqET\(dq is not a valid time zone. It stands for \(dqEastern
 Time\(dq, which can belong to either of two time zones, EST or EDT,
-based on the time of the year (i.e. whether or not daylight savings
-time is in effect). Since we can't tell from the timestamp, we always
-parse these as EST which is UTC-5. When daylight savings is in effect,
-they will be off by an hour.
+based on the time of the year (that is, whether or not daylight
+savings time is in effect). Since we can't tell from the timestamp, we
+always parse these as EST which is UTC-5. When daylight savings is in
+effect, they will be off by an hour.
 
 Here's a list of the ones that may cause surprises:
 
 
 Here's a list of the ones that may cause surprises:
 
@@ -412,6 +459,11 @@ date:
 
 They are also stored as UTC.
 
 
 They are also stored as UTC.
 
+.IP \[bu]
+\fIMLB_earlylineXML.dtd\fR
+
+See earlylineXML.dtd.
+
 .IP \[bu]
 \fIOdds_XML.dtd\fR
 
 .IP \[bu]
 \fIOdds_XML.dtd\fR
 
@@ -589,6 +641,8 @@ injuriesxml.dtd
 .IP \[bu]
 jfilexml.dtd
 .IP \[bu]
 .IP \[bu]
 jfilexml.dtd
 .IP \[bu]
+MLB_earlylineXML.dtd
+.IP \[bu]
 newsxml.dtd
 .IP \[bu]
 Odds_XML.dtd
 newsxml.dtd
 .IP \[bu]
 Odds_XML.dtd
index e7d2940ba993393f90aa46f65d0e712e6bf07b00..0f04f6e66f1ce5f7314ef175bb5fad7816e542ed 100644 (file)
@@ -76,6 +76,7 @@ extra-source-files:
   schemagen/MLB_Matchup_XML/*.xml
   schemagen/mlbonbasepctxml/*.xml
   schemagen/MLBOPSXML/*.xml
   schemagen/MLB_Matchup_XML/*.xml
   schemagen/mlbonbasepctxml/*.xml
   schemagen/MLBOPSXML/*.xml
+  schemagen/MLB_earlylineXML/*.xml
   schemagen/MLB_Pitching_Appearances_Leaders/*.xml
   schemagen/MLB_Pitching_Balks_Leaders/*.xml
   schemagen/MLB_Pitching_CG_Leaders/*.xml
   schemagen/MLB_Pitching_Appearances_Leaders/*.xml
   schemagen/MLB_Pitching_Balks_Leaders/*.xml
   schemagen/MLB_Pitching_CG_Leaders/*.xml
@@ -280,6 +281,7 @@ executable htsn-import
     TSN.XML.Injuries
     TSN.XML.InjuriesDetail
     TSN.XML.JFile
     TSN.XML.Injuries
     TSN.XML.InjuriesDetail
     TSN.XML.JFile
+    TSN.XML.MLBEarlyLine
     TSN.XML.News
     TSN.XML.Odds
     TSN.XML.ScheduleChanges
     TSN.XML.News
     TSN.XML.Odds
     TSN.XML.ScheduleChanges
index 63bde65d78276e6c511f4168217a5f082457b3eb..59da41989696a0aabfe9aec446d5968c76d3f4d7 100644 (file)
@@ -60,6 +60,9 @@ import qualified TSN.XML.Injuries as Injuries ( dtd, pickle_message )
 import qualified TSN.XML.InjuriesDetail as InjuriesDetail (
   dtd,
   pickle_message )
 import qualified TSN.XML.InjuriesDetail as InjuriesDetail (
   dtd,
   pickle_message )
+import qualified TSN.XML.MLBEarlyLine as MLBEarlyLine (
+  dtd,
+  pickle_message )
 import qualified TSN.XML.JFile as JFile ( dtd, pickle_message )
 import qualified TSN.XML.News as News (
   dtd,
 import qualified TSN.XML.JFile as JFile ( dtd, pickle_message )
 import qualified TSN.XML.News as News (
   dtd,
@@ -207,6 +210,9 @@ import_file cfg path = do
 
             | dtd == JFile.dtd = go JFile.pickle_message
 
 
             | dtd == JFile.dtd = go JFile.pickle_message
 
+            | dtd == MLBEarlyLine.dtd =
+                go MLBEarlyLine.pickle_message
+
             | dtd == News.dtd =
                 -- Some of the newsxml docs are busted in predictable ways.
                 -- We want them to "succeed" so that they're deleted.
             | dtd == News.dtd =
                 -- Some of the newsxml docs are busted in predictable ways.
                 -- We want them to "succeed" so that they're deleted.
diff --git a/src/TSN/XML/MLBEarlyLine.hs b/src/TSN/XML/MLBEarlyLine.hs
new file mode 100644 (file)
index 0000000..fc90483
--- /dev/null
@@ -0,0 +1,136 @@
+-- | Parse TSN XML for the DTD \"MLB_earlylineXML.dtd\". This module
+--   is unique (so far) in that it is almost entirely a subclass of
+--   another module, "TSN.XML.EarlyLine". The database representations
+--   should be almost identical, and the XML schema /could/ be
+--   similar, but instead, welcome to the jungle baby. Here are the
+--   differences:
+--
+--   * In earlylineXML.dtd, each \<date\> element contains exactly one
+--     game. In MLB_earlylineXML.dtd, they contain multiple games.
+--
+--   * As a result of the previous difference, the \<note\>s are no
+--     longer in one-to-one correspondence with the games. The
+--     \<note\> elements are thrown in beside the \<game\>s, and we're
+--     supposed to figure out to which \<game\>s they correspond
+--     ourselves. This is the same sort of nonsense going on with
+--     'TSN.XML.Odds.OddsGameWithNotes'.
+--
+--    * The \<over_under\> element can be empty in
+--      MLB_earlylineXML.dtd (it can't in earlylineXML.dtd).
+--
+--   * Each home/away team in MLB_earlylineXML.dtd has a \<pitcher\>
+--     that isn't present in the regular earlylineXML.dtd.
+--
+--   * In earlylineXML.dtd, the home/away team lines are given as
+--     attributes on the \<teamH\> and \<teamA\> elements
+--     respectively. In MLB_earlylineXML.dtd, the lines can be found
+--     in \<line\> elements that are children of the \<teamH\> and
+--     \<teamA\> elements.
+--
+--   * In earlylineXML.dtd, the team names are given as text within
+--     the \<teamA\> and \<teamH\> elements. In MLB_earlylineXML.dtd,
+--     they are instead given as attributes on those respective
+--     elements.
+--
+--   Most of these difficulties have been worked around in
+--   "TSN.XML.EarlyLine", so this module could be kept somewhat boring.
+--
+module TSN.XML.MLBEarlyLine (
+  dtd,
+  mlb_early_line_tests,
+  module TSN.XML.EarlyLine -- This re-exports the EarlyLine and EarlyLineGame
+                           -- constructors unnecessarily. Whatever.
+  )
+where
+
+-- System imports (needed only for tests)
+import Database.Groundhog (
+  countAll,
+  deleteAll,
+  migrate,
+  runMigration,
+  silentMigrationLogger )
+import Database.Groundhog.Generic ( runDbConn )
+import Database.Groundhog.Sqlite ( withSqliteConn )
+import Test.Tasty ( TestTree, testGroup )
+import Test.Tasty.HUnit ( (@?=), testCase )
+
+
+-- Local imports.
+import TSN.DbImport ( DbImport( dbimport ) )
+import TSN.XML.EarlyLine ( EarlyLine, EarlyLineGame, pickle_message )
+import Xml (
+  pickle_unpickle,
+  unpickleable,
+  unsafe_unpickle )
+
+
+-- | The DTD to which this module corresponds. Used to invoke dbimport.
+--
+dtd :: String
+dtd = "MLB_earlylineXML.dtd"
+
+
+
+--
+-- * Tasty Tests
+--
+
+-- | A list of all tests for this module.
+--
+mlb_early_line_tests :: TestTree
+mlb_early_line_tests =
+  testGroup
+    "MLBEarlyLine 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/MLB_earlylineXML.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/MLB_earlylineXML.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 (MLB) early_lines deletes its children" $ do
+    let path = "test/xml/MLB_earlylineXML.xml"
+    results <- unsafe_unpickle path pickle_message
+    let a = undefined :: EarlyLine
+    let b = undefined :: EarlyLineGame
+
+    actual <- withSqliteConn ":memory:" $ runDbConn $ do
+                runMigration silentMigrationLogger $ do
+                  migrate a
+                  migrate b
+                _ <- dbimport results
+                deleteAll a
+                count_a <- countAll a
+                count_b <- countAll b
+                return $ sum [count_a, count_b]
+    let expected = 0
+    actual @?= expected
index ee3814867d718767d203bf2e399fd9ca3b5dd3f1..e82c2a0098f27cd70f2decd6956a3df5ac8e01fa 100644 (file)
@@ -9,6 +9,7 @@ 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.Injuries ( injuries_tests )
 import TSN.XML.InjuriesDetail ( injuries_detail_tests )
 import TSN.XML.JFile ( jfile_tests )
+import TSN.XML.MLBEarlyLine ( mlb_early_line_tests )
 import TSN.XML.News ( news_tests )
 import TSN.XML.Odds ( odds_tests )
 import TSN.XML.ScheduleChanges ( schedule_changes_tests )
 import TSN.XML.News ( news_tests )
 import TSN.XML.Odds ( odds_tests )
 import TSN.XML.ScheduleChanges ( schedule_changes_tests )
@@ -27,6 +28,7 @@ tests = testGroup
             injuries_tests,
             injuries_detail_tests,
             jfile_tests,
             injuries_tests,
             injuries_detail_tests,
             jfile_tests,
+            mlb_early_line_tests,
             news_tests,
             odds_tests,
             pickler_tests,
             news_tests,
             odds_tests,
             pickler_tests,
index 2e2e27cc86c8380f57b3220e0915618a4726bcda..94034c8f7482f72d5592604dd5b5d526d0201bd0 100644 (file)
@@ -16,15 +16,15 @@ rm -f shelltest.sqlite3
 # and a newsxml that aren't really supposed to import.
 find ./test/xml -maxdepth 1 -name '*.xml' | wc -l
 >>>
 # and a newsxml that aren't really supposed to import.
 find ./test/xml -maxdepth 1 -name '*.xml' | wc -l
 >>>
-28
+29
 >>>= 0
 
 # Run the imports again; we should get complaints about the duplicate
 >>>= 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*24
+# xml_file_ids. There are 2 errors for each violation, so we expect 2*25
 # occurrences of the string 'ERROR'.
 ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l
 >>>
 # occurrences of the string 'ERROR'.
 ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l
 >>>
-48
+50
 >>>= 0
 
 # Finally, clean up after ourselves.
 >>>= 0
 
 # Finally, clean up after ourselves.
diff --git a/test/xml/MLB_earlylineXML.dtd b/test/xml/MLB_earlylineXML.dtd
new file mode 100644 (file)
index 0000000..e3c242f
--- /dev/null
@@ -0,0 +1,22 @@
+<!ELEMENT XML_File_ID (#PCDATA)>
+<!ELEMENT heading (#PCDATA)>
+<!ELEMENT category (#PCDATA)>
+<!ELEMENT sport (#PCDATA)>
+<!ELEMENT title (#PCDATA)>
+<!ELEMENT note (#PCDATA)>
+<!ELEMENT time (#PCDATA)>
+<!ELEMENT pitcher (#PCDATA)>
+<!ELEMENT line (#PCDATA)>
+<!ELEMENT teamA ( ( pitcher, line ) )>
+<!ELEMENT teamH ( ( pitcher, line ) )>
+<!ELEMENT over_under (#PCDATA)>
+<!ELEMENT game ( ( time, teamA, teamH, over_under ) )>
+<!ELEMENT date ( ( note | game )+ )>
+<!ELEMENT time_stamp (#PCDATA)>
+<!ELEMENT message ( ( XML_File_ID, heading, category, sport, title, date, time_stamp ) )>
+
+<!ATTLIST teamA rotation CDATA #REQUIRED>
+<!ATTLIST teamA name CDATA #REQUIRED>
+<!ATTLIST teamH rotation CDATA #REQUIRED>
+<!ATTLIST teamH name CDATA #REQUIRED>
+<!ATTLIST date value CDATA #REQUIRED>
diff --git a/test/xml/MLB_earlylineXML.xml b/test/xml/MLB_earlylineXML.xml
new file mode 100644 (file)
index 0000000..0b2d120
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no" ?>\r<!DOCTYPE message PUBLIC "-//TSN//DTD Odds 1.0/EN" "MLB_earlylineXML.dtd">\r<message>\r<XML_File_ID>21161927</XML_File_ID>\r<heading>AAO;MLB-EARLY-LINE</heading>\r<category>Odds</category>\r<sport>MLB</sport>\r<title>Major League Baseball Overnight Line</title>\r<date value="SUNDAY, MAY 25TH (05/25/2014)">\r<note>National League</note>\r<game>\r<time>1:10</time>\r<teamA rotation="951" name="MIL">\r<pitcher>J.Nelson</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="952" name="MIA">\r<pitcher>R.Wolf</pitcher>\r<line>-105</line>\r</teamH>\r<over_under>8o</over_under>\r</game>\r<note>Game one of doubleheader</note>\r<game>\r<time>1:10</time>\r<teamA rotation="953" name="ARI">\r<!-- Manually removed pitcher -->\r<pitcher>B.Arroyo</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="954" name="NYM">\r<pitcher>R.Montero</pitcher>\r<line>-105</line>\r</teamH>\r<over_under>7.5u</over_under>\r</game>\r<game>\r<time>1:35</time>\r<teamA rotation="955" name="LOS">\r<pitcher>J.Beckett</pitcher>\r<line>-110</line>\r</teamA>\r<teamH rotation="956" name="PHI">\r<pitcher>AJ.Burnett</pitcher>\r<line></line>\r</teamH>\r<!-- Manually removed over_under -->\r<over_under></over_under>\r</game>\r<game>\r<time>1:35</time>\r<teamA rotation="957" name="WAS">\r<pitcher>D.Fister</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="958" name="PIT">\r<pitcher>F.Liriano</pitcher>\r<line>-115</line>\r</teamH>\r<over_under>7p</over_under>\r</game>\r<game>\r<time>4:10</time>\r<teamA rotation="959" name="CHC">\r<pitcher>J.Hammel</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="960" name="SDP">\r<pitcher>I.Kennedy</pitcher>\r<line>-125</line>\r</teamH>\r<over_under>6.5p</over_under>\r</game>\r<game>\r<time>5:10</time>\r<teamA rotation="961" name="COL">\r<pitcher>F.Morales</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="962" name="ATL">\r<pitcher>J.Teheran</pitcher>\r<line>-160</line>\r</teamH>\r<over_under>7.5p</over_under>\r</game>\r<game>\r<time>8:05</time>\r<teamA rotation="963" name="STL">\r<pitcher>A.Wainwright</pitcher>\r<line>-140</line>\r</teamA>\r<teamH rotation="964" name="CIN">\r<pitcher>M.Leake</pitcher>\r<line></line>\r</teamH>\r<over_under>6.5o</over_under>\r</game>\r<note>American League</note>\r<game>\r<time>1:05</time>\r<teamA rotation="965" name="TEX">\r<pitcher>C.Lewis</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="966" name="DET">\r<pitcher>J.Verlander</pitcher>\r<line>-180</line>\r</teamH>\r<over_under>8.5u</over_under>\r</game>\r<game>\r<time>1:05</time>\r<teamA rotation="967" name="OAK">\r<pitcher>D.Pomeranz</pitcher>\r<line>-125</line>\r</teamA>\r<teamH rotation="968" name="TOR">\r<pitcher>J.Happ</pitcher>\r<line></line>\r</teamH>\r<over_under>9o</over_under>\r</game>\r<game>\r<time>1:35</time>\r<teamA rotation="969" name="CLE">\r<pitcher>T.Bauer</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="970" name="BAL">\r<pitcher>M.Gonzalez</pitcher>\r<line>-120</line>\r</teamH>\r<over_under>9p</over_under>\r</game>\r<game>\r<time>1:40</time>\r<teamA rotation="971" name="BOS">\r<pitcher>B.Workman</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="972" name="TAM">\r<pitcher>J.Odorizzi</pitcher>\r<line>-120</line>\r</teamH>\r<over_under>8p</over_under>\r</game>\r<game>\r<time>2:10</time>\r<teamA rotation="973" name="NYY">\r<pitcher>M.Tanaka</pitcher>\r<line>-165</line>\r</teamA>\r<teamH rotation="974" name="CWS">\r<pitcher>A.Rienzo</pitcher>\r<line></line>\r</teamH>\r<over_under>7.5o</over_under>\r</game>\r<game>\r<time>3:35</time>\r<teamA rotation="975" name="KAN">\r<pitcher>J.Vargas</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="976" name="ANA">\r<pitcher>G.Richards</pitcher>\r<line>-155</line>\r</teamH>\r<over_under>8u</over_under>\r</game>\r<game>\r<time>4:10</time>\r<teamA rotation="977" name="HOU">\r<pitcher>D.Keuchel</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="978" name="SEA">\r<pitcher>H.Iwakuma</pitcher>\r<line>-165</line>\r</teamH>\r<over_under>6.5o</over_under>\r</game>\r<note>Inter League</note>\r<game>\r<time>4:05</time>\r<teamA rotation="979" name="MIN">\r<pitcher>R.Nolasco</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="980" name="SFG">\r<pitcher>M.Bumgarner</pitcher>\r<line>-175</line>\r</teamH>\r<over_under>7.5p</over_under>\r</game>\r<note>Write-in game - Game two of doubleheader</note>\r<game>\r<time>4:40</time>\r<teamA rotation="981" name="ARI">\r<pitcher>Z.Spruill</pitcher>\r<line></line>\r</teamA>\r<teamH rotation="982" name="NYM">\r<pitcher>D.Matsuzaka</pitcher>\r<line>off</line>\r</teamH>\r<over_under>off</over_under>\r</game>\r</date>\r<time_stamp> May 24, 2014, at 03:05 PM ET </time_stamp>\r</message>\r
\ No newline at end of file