16

我正在尝试从定期为 XML 文件提供股票市场报价(示例数据)的网页获取数据。XML 的结构非常简单,大致如下:

<?xml version="1.0"?>
<Contents>
  <StockQuote Symbol="PETR3" Date="21-12-2010" Time="13:20" Price="23.02" />
</Contents>

(不仅如此,但这足以作为示例)。

我想将其解析为数据结构:

 data Quote = Quote { symbol :: String, 
                      date   :: Data.Time.Calendar.Day, 
                      time   :: Data.Time.LocalTime.TimeOfDay,
                      price  :: Float}

我或多或少了解 Parsec 的工作原理(在 Real World Haskell 书的水平上),我尝试了一些Text.XML库,但我所能开发的只是一个有效的代码,但对于这样一个简单的任务来说太大了,看起来像一个半生不熟的黑客,而不是最好的。

我不太了解解析器和 XML(我基本上知道我在 RWH 书中读到的内容,我以前从未使用过解析器)(我只是做统计和数值编程,我不是计算机科学家)。是否有一个 XML 解析库,我可以在其中知道什么是模型并立即提取信息,而不必手动解析每个元素,也不必解析纯字符串?

我正在考虑类似的事情:

  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

我不必处理纯字符串并自己创建组合器(我很烂)。

编辑:我可能需要阅读一些关于解析器(不仅仅是 Parsec)和 XML 的最低限度的内容(足以以正确的方式完成这项工作)。大家有什么推荐的吗?

我必须解析的真正字符串是这样的:

 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"

编辑2:

我尝试了以下方法(readFloat、readQuoteTime 等......只是从字符串中读取内容的函数)。

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

当我在 ghci 中调用它时:

*Main> docParser stringTest >>= print
[]

有什么问题吗?

4

5 回答 5

20

有很多为 Haskell 编写的 XML 库可以为您进行解析。我推荐名为 xml 的库(参见http://hackage.haskell.org/package/xml)。有了它,您可以简单地编写例如:

let contents = parseXML source
    quotes   = concatMap (findElements $ simpleName "StockQuote") (onlyElems contents)
    symbols  = map (findAttr $ simpleName "Symbol") quotes
    simpleName s = QName s Nothing Nothing
print symbols

此代码段作为您的示例 XML 的结果打印[Just "PETR3"],并且很容易扩展以收集您需要的所有数据。要以您描述的风格编写程序,您应该使用 Maybe monad,因为 xml 查找函数通常返回一个 Maybe String,表示是否可以找到标签、元素或属性。另请参阅相关问题:使用哪个 Haskell XML 库?

于 2011-01-06T20:42:28.510 回答
5

对于简单的 xml 解析,tagsoup 不会出错。http://hackage.haskell.org/package/tagsoup

于 2011-01-06T20:26:45.057 回答
5

以下代码段使用 xml-enumerator。它将日期和时间保留为文本(解析这些作为练习留给读者):

{-# LANGUAGE OverloadedStrings #-}
import Text.XML.Enumerator.Parse
import Data.Text.Lazy (Text, unpack)

data Quote = Quote { symbol :: Text
                   , date   :: Text
                   , time   :: Text
                   , price  :: Float}
  deriving Show

main = parseFile_ "test.xml" (const Nothing) $ parseContents

parseContents = force "Missing Contents" $ tag'' "Contents" parseStockQuote
parseStockQuote = force "Missing StockQuote" $ flip (tag' "StockQuote") return $ do
    s <- requireAttr "Symbol"
    d <- requireAttr "Date"
    t <- requireAttr "Time"
    p <- requireAttr "Price"
    return $ Quote s d t (read $ unpack p)
于 2011-01-06T21:01:23.170 回答
4

还有其他方法可以使用这个库,但是对于像这样简单的事情,我拼凑了一个 sax 解析器。

import Prelude as P
import Text.XML.Expat.SAX
import Data.ByteString.Lazy as L

parsexml txt = parse defaultParseOptions txt :: [SAXEvent String String]

main = do
  xml <- L.readFile "stockinfo.xml"
  return  $ P.filter stockquoteelement (parsexml xml)

  where
    stockquoteelement (StartElement "StockQuote" attrs) = True
    stockquoteelement _ = False

从那里你可以弄清楚去哪里。您还可以使用 Text.XML.Expat.Annotated 将其解析为更像您在上面寻找的结构:

parsexml txt = parse defaultParseOptions txt :: (LNode String String, Maybe XMLParseError)

然后使用Text.XML.Expat.Proc 浏览该结构。

于 2011-01-06T20:44:56.427 回答
4

我过去使用过Haskell XML Toolbox。类似的东西

{-# 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
于 2011-01-06T21:42:30.953 回答