2011-01-06 5 views
14

Я пытаюсь получить данные с веб-страницы, которая периодически служит XML-файлу с котировками на фондовом рынке (sample data). Структура XML является очень простой, и что-то вроде этого:Parse XML в Haskell

<?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} 

Я понимаю, более или менее, как Парсек работы (на уровне книги 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 

, где я бы не иметь дело с чистой строки и создавать комбинаторы себя (я сосу у него).

EDIT: Мне, вероятно, нужно немного прочитать (как раз для того, чтобы это сделать правильно) о парсерах вообще (не только 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" 

EDIT2:

Я попробовал следующее (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 
[] 

Что-то случилось?

+0

Если вы заинтересованы в парсеров комбинаторов, учебник С. Doaitse Swierstra, в http://www.cs.uu.nl/research/techreps/repo/CS-2008/2008- 044.pdf, это довольно хорошее введение. Он использует аппликативный стиль, но он не предполагает знания прикладной (или теории парсеров). Я думаю, что большинство библиотек-комбайнов парсеров в Hackage (Polyparse, Attoparsec, UU-parsinglib) - лучший выбор, чем Parsec. –

ответ

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 
+1

Это приятно. Мне нравятся стрелы. Но я все равно не могу найти String и вернуть XmlTree для подачи парсера. Я только нахожу функции для чтения документов. Есть ли функция '(ArrowXml a) => a String XmlTree'? –

+0

ha! Найдено 'hread' и' xread'. Благодарю. –

+0

У меня возникла проблема с первой строкой ''. Когда он присутствует, синтаксический анализатор не может получить anythig. Я решил это, просто удалив 23 символа из строки. Существует ли менее хакерское решение? –

5

Для простого анализа XML, вы не можете ошибиться с tagoup. http://hackage.haskell.org/package/tagsoup

+1

До тех пор, пока вам не нужно проверять правильную форму или обеспечить хорошую сбалансированность тегов. Насколько мне нравится tagoup для выскакивания HTML, я думаю, что он плохо подходит для синтаксического анализа хорошо структурированных XML-файлов. –

+3

@ Майкл - если я разбираюсь в чьем-то раздражающем формате, мне вообще все равно, есть ли у них правильные сведения, или я доверяю им, чтобы они сделали это или нет, в зависимости от компетенции поставщика. Я забочусь о том, чтобы получить мою информацию, и надежно, чтобы они меняли вещи на меня. – sclv

19

Существует множество библиотек XML, написанных для Haskell, которые могут выполнять синтаксический анализ для вас. Я рекомендую библиотеку с именем 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 

Этот фрагмент кода печатает [Just "PETR3"] в результате для вашего примера XML, и это легко расширить для сбора всех необходимых данных. Чтобы написать программу в описываемом вами стиле, вы должны использовать монаду Maybe, поскольку функции поиска xml часто возвращают Maybe String, сигнализируя, можно ли найти тег, элемент или атрибут. Также см. Связанный с этим вопрос: Which Haskell XML library to use?

4

Есть и другие способы использования этой библиотеки, но для чего-то простого, как это, я собрал саксовый парсер.

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 для серфинга структуры.

4

В следующем фрагменте используется xml-перечислитель. Он оставляет дату и время в виде текста (разбор тех оставляется в качестве упражнения для читателя):

{-# 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)