Разбор XML в Haskell - PullRequest
       12

Разбор XML в Haskell

14 голосов
/ 06 января 2011

Я пытаюсь получить данные с веб-страницы, которая периодически предоставляет файл 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"

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
[]

Что-то не так?

Ответы [ 5 ]

19 голосов
/ 06 января 2011

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

5 голосов
/ 06 января 2011

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

4 голосов
/ 07 января 2011

Я использовал 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
4 голосов
/ 07 января 2011

В следующем фрагменте кода используется 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)
4 голосов
/ 06 января 2011

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

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

...