Анализ XML в Haskell
Я пытаюсь получить данные с веб-страницы, периодически использующей 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
где мне не нужно было бы разбираться с чистой строкой и сами создавать комбинаторы (я сосать ее).
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
[]
Что-то не так?
Ответы
Ответ 1
Я использовал 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
Ответ 2
Существует множество библиотек 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 использовать?
Ответ 3
Для простого синтаксического анализа xml вы не можете ошибиться с tagoup. http://hackage.haskell.org/package/tagsoup
Ответ 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 для просмотра структуры.
Ответ 5
В следующем фрагменте используется 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)