I'm trying to get data from a webpage that serves a XML file periodically with stock market quotes (sample data). The structure of the XML is very simple, and is something like this:
<?xml version="1.0"?>
<Contents>
<StockQuote Symbol="PETR3" Date="21-12-2010" Time="13:20" Price="23.02" />
</Contents>
(it's more than that but this suffices as an example).
I'd like to parse it to a data structure:
data Quote = Quote { symbol :: String,
date :: Data.Time.Calendar.Day,
time :: Data.Time.LocalTime.TimeOfDay,
price :: Float}
I understand more or less how Parsec works (on the level of the Real World Haskell book), and I tried a bit the Text.XML
library but all I could develop was a code that worked but is too big for such a simple task and looks like a half baked hack and not the best one could do.
I don't know a lot about parsers and XML (I know basically what I read in the RWH book, I never used parsers before) (I just do statistical and numerical programming, I'm not a computer scientist). Is there a XML parsing library where I could just tell what is the model and extract the information right away, without having to parse each element by hand and without having to parse pure string?
I'm thinking about something like:
myParser = do cont <- openXMLElem "Contents"
quote <- openXMLElem "StockQuote"
symb <- getXMLElemField "Symbol"
date <- getXMLElemField "Date"
(...)
closequote <- closeXMLElem "StockQuote"
closecont <- closeXMLElem "Contents"
return (symb, date)
results = parse myParser "" myXMLString
where I wouldn't have to deal with the pure string and create the combinators myself (I suck at it).
EDIT: I probably need to read a bit (just enough to get this done the right way) about parsers in general (not only Parsec) and the minimum about XML. Do you guys recomend something?
The real string I have to parse is this:
stringTest = "<?xml version=\"1.0\"?>\r\n<ComportamentoPapeis><Papel Codigo=\"PETR3\"
Nome=\"PETROBRAS ON\" Ibovespa=\"#\" Data=\"05/01/201100:00:00\"
Abertura=\"29,80\" Minimo=\"30,31\" Maximo=\"30,67\" Medio=\"30,36\"
Ultimo=\"30,45\" Oscilacao=\"1,89\" Minino=\"29,71\"/></ComportamentoPapeis>\r\n"
EDIT2:
I tried the following (readFloat, readQuoteTime, etc... are just functions to read things from strings).
bvspaParser :: (ArrowXml a) => a XmlTree Quote
bvspaParser = hasName "ComportamentoPapeis" /> hasName "Papel" >>> proc x -> do
(hour,date) <- readQuoteTime ^<< getAttrValue "Data" -< x
quoteCode <- getAttrValue "Codigo" -< x
openPrice <- readFloat ^<< getAttrValue "Abertura" -< x
minim <- readFloat ^<< getAttrValue "Minimo" -< x
maxim <- readFloat ^<< getAttrValue "Maximo" -< x
ultimo <- readFloat ^<< getAttrValue "Ultimo" -< x
returnA -< Quote quoteCode (LocalTime date hour) openPrice minim maxim ultimo
docParser :: String -> IO [Quote]
docParser str = runX $ readString [] str >>> (parseXmlDocument False) >>> bvspaParser
When I call it in ghci:
*Main> docParser stringTest >>= print
[]
Is anything wrong?
I've used Haskell XML Toolbox in the past. Something along the lines of
{-# LANGUAGE Arrows #-}
quoteParser :: (ArrowXml a) => a XmlTree Quote
quoteParser =
hasName "Contents" /> hasName "StockQuote" >>> proc x -> do
symbol <- getAttrValue "Symbol" -< x
date <- readTime defaultTimeLocale "%d-%m-%Y" ^<< getAttrValue "Date" -< x
time <- readTime defaultTimeLocale "%H:%M" ^<< getAttrValue "Time" -< x
price <- read ^<< getAttrValue "Price" -< x
returnA -< Quote symbol date time price
parseQuoteDocument :: String -> IO (Maybe Quote)
parseQuoteDocument xml =
liftM listToMaybe . runX . single $
readString [] xml >>> getChildren >>> quoteParser