]> gitweb.michael.orlitzky.com - dead/htsn-import.git/commitdiff
Add a new pickler for Double values that have no leading integer.
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 25 Jul 2014 19:15:12 +0000 (15:15 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 25 Jul 2014 19:15:12 +0000 (15:15 -0400)
Use the new pickler to fix an unhandled AutoRacingResultsXML document.
Add a test case for the aforementioned fix.

src/TSN/Picklers.hs
src/TSN/XML/AutoRacingResults.hs
test/shell/import-duplicates.test
test/xml/AutoRacingResultsXML-fractional-kph.xml [new file with mode: 0644]

index 3d7215a07744436055a7eff73a8169201d394301..6f131a75b921f4aac3490a39214a5106db0f6160 100644 (file)
@@ -9,6 +9,7 @@ module TSN.Picklers (
   xp_datetime,
   xp_early_line_date,
   xp_earnings,
+  xp_fracpart_only_double,
   xp_gamedate,
   xp_tba_time,
   xp_time,
@@ -28,6 +29,7 @@ import Data.Tree.NTree.TypeDefs ( NTree(..) )
 import System.Locale ( TimeLocale( wDays, months ), defaultTimeLocale )
 import Test.Tasty ( TestTree, testGroup )
 import Test.Tasty.HUnit ( (@?=), testCase )
+import Text.Read ( readMaybe )
 import Text.XML.HXT.Arrow.Pickle (
   xpText,
   xpWrap,
@@ -148,6 +150,7 @@ format_commas x =
   reverse (intercalate "," $ chunksOf 3 $ reverse $ show x)
 
 
+
 -- | Parse \<Earnings\> from an 'AutoRaceResultsListing'. These are
 --   essentially 'Int's, but they look like,
 --
@@ -185,6 +188,45 @@ xp_earnings =
 
 
 
+-- | Pickle a 'Double' that can be missing its leading zero (for
+--   values less than one). For example, we've seen,
+--
+--   <TrackLength KPH=".805">0.5</TrackLength>
+--
+--   Which 'xpPrim' can't handle without the leading
+--   zero. Unfortunately there's no way pickle/unpickle can be
+--   inverses of each other here, since \"0.5\" and \".5\" should
+--   unpickle to the same 'Double'.
+--
+--   Examples:
+--
+--   >>> let tn = text_node "0.5"
+--   >>> unpickleDoc xp_fracpart_only_double tn
+--   Just 0.5
+--
+--   >>> let tn = text_node ".5"
+--   >>> unpickleDoc xp_fracpart_only_double tn
+--   Just 0.5
+--
+--   >>> let tn = text_node "foo"
+--   >>> unpickleDoc xp_fracpart_only_double tn
+--   Nothing
+--
+xp_fracpart_only_double :: PU Double
+xp_fracpart_only_double =
+  (to_double, from_double) `xpWrapMaybe` xpText
+  where
+    -- | Convert a 'String' to a 'Double', maybe. We always prepend a
+    -- zero, since it will fix the fraction-only values, and not hurt
+    -- the ones that already have a leading integer.
+    to_double :: String -> Maybe Double
+    to_double s = readMaybe ("0" ++ s)
+
+    from_double :: Double -> String
+    from_double = show
+
+
+
 -- | (Un)pickle an unpadded 'UTCTime'. Used for example on the
 --   \<RaceDate\> elements in an 'AutoRaceResults' message.
 --
index cd5ebec36010eeefc7f9132d02bc1d3f7ae58892..0e80b11b4702a18756328f2021d0d123a174e5ac 100644 (file)
@@ -59,7 +59,11 @@ import Text.XML.HXT.Core (
 -- Local imports.
 import TSN.Codegen ( tsn_codegen_config )
 import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
-import TSN.Picklers ( xp_earnings, xp_datetime, xp_time_stamp )
+import TSN.Picklers (
+  xp_earnings,
+  xp_fracpart_only_double,
+  xp_datetime,
+  xp_time_stamp )
 import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) )
 import Xml (
   Child(..),
@@ -528,7 +532,9 @@ pickle_race_information =
     xp11Tuple (-- I can't think of another way to get both the
                -- TrackLength and its KPH attribute. So we shove them
                -- both in a 2-tuple. This should probably be an embedded type!
-                 xpElem "TrackLength" $ xpPair xpText (xpAttr "KPH" xpPrim) )
+                 xpElem "TrackLength" $
+                   xpPair xpText
+                          (xpAttr "KPH" xp_fracpart_only_double) )
               (xpElem "Laps" xpInt)
               (xpOption $ xpElem "AverageSpeedMPH" xpPrim)
               (xpOption $ xpElem "AverageSpeedKPH" xpPrim)
@@ -578,24 +584,33 @@ auto_racing_results_tests =
 --   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/AutoRacingResultsXML.xml"
-    (expected, actual) <- pickle_unpickle pickle_message path
-    actual @?= expected
+test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" $
+  [ check "pickle composed with unpickle is the identity"
+          "test/xml/AutoRacingResultsXML.xml",
+
+    check "pickle composed with unpickle is the identity (fractional KPH)"
+          "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+  where
+    check desc path = testCase desc $ do
+      (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/AutoRacingResultsXML.xml"
-    actual <- unpickleable path pickle_message
+test_unpickle_succeeds = testGroup "unpickle tests" $
+  [ check "unpickling succeeds"
+          "test/xml/AutoRacingResultsXML.xml",
 
-    let expected = True
-    actual @?= expected
+    check "unpickling succeeds (fractional KPH)"
+          "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+  where
+    check desc path = testCase desc $ do
+      actual <- unpickleable path pickle_message
+      let expected = True
+      actual @?= expected
 
 
 
@@ -603,24 +618,29 @@ test_unpickle_succeeds =
 --   record.
 --
 test_on_delete_cascade :: TestTree
-test_on_delete_cascade =
-  testCase "deleting auto_racing_results deletes its children" $ do
-    let path = "test/xml/AutoRacingResultsXML.xml"
-    results <- unsafe_unpickle path pickle_message
-    let a = undefined :: AutoRacingResults
-    let b = undefined :: AutoRacingResultsListing
-    let c = undefined :: AutoRacingResultsRaceInformation
-
-    actual <- withSqliteConn ":memory:" $ runDbConn $ do
-                runMigration silentMigrationLogger $ do
-                  migrate a
-                  migrate b
-                  migrate c
-                _ <- dbimport results
-                deleteAll a
-                count_a <- countAll a
-                count_b <- countAll b
-                count_c <- countAll c
-                return $ sum [count_a, count_b, count_c]
-    let expected = 0
-    actual @?= expected
+test_on_delete_cascade = testGroup "cascading delete tests" $
+  [ check "deleting auto_racing_results deletes its children"
+          "test/xml/AutoRacingResultsXML.xml",
+
+    check "deleting auto_racing_results deletes its children (fractional KPH)"
+          "test/xml/AutoRacingResultsXML-fractional-kph.xml" ]
+  where
+    check desc path = testCase desc $ do
+      results <- unsafe_unpickle path pickle_message
+      let a = undefined :: AutoRacingResults
+      let b = undefined :: AutoRacingResultsListing
+      let c = undefined :: AutoRacingResultsRaceInformation
+
+      actual <- withSqliteConn ":memory:" $ runDbConn $ do
+                  runMigration silentMigrationLogger $ do
+                    migrate a
+                    migrate b
+                    migrate c
+                  _ <- dbimport results
+                  deleteAll a
+                  count_a <- countAll a
+                  count_b <- countAll b
+                  count_c <- countAll c
+                  return $ sum [count_a, count_b, count_c]
+      let expected = 0
+      actual @?= expected
index 94034c8f7482f72d5592604dd5b5d526d0201bd0..33b1e78339b90b67a2f52e10520097d5f5405e60 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
 >>>
-29
+30
 >>>= 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*25
+# xml_file_ids. There are 2 errors for each violation, so we expect 2*26
 # occurrences of the string 'ERROR'.
 ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l
 >>>
-50
+52
 >>>= 0
 
 # Finally, clean up after ourselves.
diff --git a/test/xml/AutoRacingResultsXML-fractional-kph.xml b/test/xml/AutoRacingResultsXML-fractional-kph.xml
new file mode 100644 (file)
index 0000000..5576ab8
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no" ?>\r<!DOCTYPE message PUBLIC "-//TSN//DTD Leader 1.0/EN" "AutoRacingResultsXML.dtd">\r<message>\r<XML_File_ID>21475733</XML_File_ID>\r<heading>ATX%CRAFTSMAN-FINAL-RESULTS</heading>\r<category>Statistics</category>\r<sport>NASCAR-T</sport>\r<RaceID>1747</RaceID>\r<RaceDate>7/23/2014 9:00:00 PM</RaceDate>\r<Title>NASCAR - Camping World - 1-800 CarCash Mudsummer Classic - Final Results</Title>\r<Track_Location>Eldora Speedway - New Weston, OH</Track_Location>\r<Laps_Remaining>0</Laps_Remaining>\r<Checkered_Flag>True</Checkered_Flag>\r<Listing>\r<FinishPosition>1</FinishPosition>\r<StartingPosition>6</StartingPosition>\r<CarNumber>54</CarNumber>\r<DriverID>1484</DriverID>\r<Driver>Darrell Wallace Jr.</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>2</FinishPosition>\r<StartingPosition>3</StartingPosition>\r<CarNumber>30</CarNumber>\r<DriverID>108</DriverID>\r<Driver>Ron Hornaday Jr.</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>3</FinishPosition>\r<StartingPosition>4</StartingPosition>\r<CarNumber>29</CarNumber>\r<DriverID>1482</DriverID>\r<Driver>Ryan Blaney</Driver>\r<CarMake>Ford</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>4</FinishPosition>\r<StartingPosition>10</StartingPosition>\r<CarNumber>52</CarNumber>\r<DriverID>40</DriverID>\r<Driver>Ken Schrader</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>5</FinishPosition>\r<StartingPosition>13</StartingPosition>\r<CarNumber>3</CarNumber>\r<DriverID>1061</DriverID>\r<Driver>Ty Dillon</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>6</FinishPosition>\r<StartingPosition>18</StartingPosition>\r<CarNumber>8</CarNumber>\r<DriverID>1106</DriverID>\r<Driver>John H. Nemechek</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>7</FinishPosition>\r<StartingPosition>2</StartingPosition>\r<CarNumber>13</CarNumber>\r<DriverID>1483</DriverID>\r<Driver>Jeb Burton</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>8</FinishPosition>\r<StartingPosition>5</StartingPosition>\r<CarNumber>98</CarNumber>\r<DriverID>117</DriverID>\r<Driver>Johnny Sauter</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>9</FinishPosition>\r<StartingPosition>9</StartingPosition>\r<CarNumber>88</CarNumber>\r<DriverID>123</DriverID>\r<Driver>Matt Crafton</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>10</FinishPosition>\r<StartingPosition>19</StartingPosition>\r<CarNumber>2</CarNumber>\r<DriverID>1321</DriverID>\r<Driver>Austin Dillon</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>11</FinishPosition>\r<StartingPosition>8</StartingPosition>\r<CarNumber>19</CarNumber>\r<DriverID>1105</DriverID>\r<Driver>Tyler Reddick</Driver>\r<CarMake>Ford</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>12</FinishPosition>\r<StartingPosition>24</StartingPosition>\r<CarNumber>77</CarNumber>\r<DriverID>1221</DriverID>\r<Driver>German Quiroga</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>13</FinishPosition>\r<StartingPosition>21</StartingPosition>\r<CarNumber>31</CarNumber>\r<DriverID>1085</DriverID>\r<Driver>Ben Kennedy</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>14</FinishPosition>\r<StartingPosition>7</StartingPosition>\r<CarNumber>21</CarNumber>\r<DriverID>1477</DriverID>\r<Driver>Joey Coulter</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>15</FinishPosition>\r<StartingPosition>27</StartingPosition>\r<CarNumber>02</CarNumber>\r<DriverID>1486</DriverID>\r<Driver>Tyler Young</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>16</FinishPosition>\r<StartingPosition>14</StartingPosition>\r<CarNumber>17</CarNumber>\r<DriverID>550</DriverID>\r<Driver>Timothy Peters</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>17</FinishPosition>\r<StartingPosition>23</StartingPosition>\r<CarNumber>9</CarNumber>\r<DriverID>1509</DriverID>\r<Driver>Chase Pistone</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>18</FinishPosition>\r<StartingPosition>16</StartingPosition>\r<CarNumber>63</CarNumber>\r<DriverID>999</DriverID>\r<Driver>J.R. Heffner</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>19</FinishPosition>\r<StartingPosition>26</StartingPosition>\r<CarNumber>05</CarNumber>\r<DriverID>1300</DriverID>\r<Driver>John Wes Townley</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>20</FinishPosition>\r<StartingPosition>17</StartingPosition>\r<CarNumber>20</CarNumber>\r<DriverID>1504</DriverID>\r<Driver>Gray Gaulding</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>21</FinishPosition>\r<StartingPosition>15</StartingPosition>\r<CarNumber>50</CarNumber>\r<DriverID>241</DriverID>\r<Driver>T.J. Bell</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>22</FinishPosition>\r<StartingPosition>12</StartingPosition>\r<CarNumber>35</CarNumber>\r<DriverID>1115</DriverID>\r<Driver>Mason Mingus</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>23</FinishPosition>\r<StartingPosition>20</StartingPosition>\r<CarNumber>99</CarNumber>\r<DriverID>1238</DriverID>\r<Driver>Bryan Silas</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>24</FinishPosition>\r<StartingPosition>25</StartingPosition>\r<CarNumber>08</CarNumber>\r<DriverID>1503</DriverID>\r<Driver>Korbin Forrister</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>150</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>25</FinishPosition>\r<StartingPosition>30</StartingPosition>\r<CarNumber>14</CarNumber>\r<DriverID>1307</DriverID>\r<Driver>Michael Annett</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>149</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>26</FinishPosition>\r<StartingPosition>11</StartingPosition>\r<CarNumber>32</CarNumber>\r<DriverID>965</DriverID>\r<Driver>Kyle Larson</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>148</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Accident</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>27</FinishPosition>\r<StartingPosition>29</StartingPosition>\r<CarNumber>6</CarNumber>\r<DriverID>171</DriverID>\r<Driver>Norm Benning</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>148</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>28</FinishPosition>\r<StartingPosition>28</StartingPosition>\r<CarNumber>80</CarNumber>\r<DriverID>1520</DriverID>\r<Driver>Jody Knowles</Driver>\r<CarMake>Ford</CarMake>\r<Points>0</Points>\r<Laps_Completed>148</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>29</FinishPosition>\r<StartingPosition>1</StartingPosition>\r<CarNumber>51</CarNumber>\r<DriverID>639</DriverID>\r<Driver>Erik Jones</Driver>\r<CarMake>Toyota</CarMake>\r<Points>0</Points>\r<Laps_Completed>144</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Running</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Listing>\r<FinishPosition>30</FinishPosition>\r<StartingPosition>22</StartingPosition>\r<CarNumber>03</CarNumber>\r<DriverID>1518</DriverID>\r<Driver>Michael Affarano</Driver>\r<CarMake>Chevrolet</CarMake>\r<Points>0</Points>\r<Laps_Completed>93</Laps_Completed>\r<Laps_Leading>0</Laps_Leading>\r<Status>Overheating</Status>\r<DNF>False</DNF>\r<Earnings>TBA</Earnings>\r</Listing>\r<Race_Information>\r<TrackLength KPH=".805">0.5</TrackLength>\r<Laps>150</Laps>\r<AverageSpeedMPH>50.195</AverageSpeedMPH>\r<AverageSpeedKPH>80.764</AverageSpeedKPH>\r<AverageSpeed>50.195</AverageSpeed>\r<TimeOfRace>1 Hr., 29 Mins., 39 Secs.</TimeOfRace>\r<MarginOfVictory>5.489 Seconds</MarginOfVictory>\r<Cautions>7 for 33 laps</Cautions>\r<LeadChanges>5 among 5 drivers</LeadChanges>\r<Most_Laps_Leading>\r<DriverID>1484</DriverID>\r<Driver>Darrell Wallace Jr.</Driver>\r<NumberOfLaps>0</NumberOfLaps>\r</Most_Laps_Leading>\r</Race_Information>\r<time_stamp> July 23, 2014, at 11:32 PM ET </time_stamp>\r</message>\r
\ No newline at end of file