2011-01-06 20 views
14

Ich versuche, Daten von einer Webseite zu erhalten, die eine XML-Datei regelmäßig mit Börsenkursnotierungen (sample data) liefert. Die Struktur des XML ist sehr einfach, und ist so etwas wie diese:Parse XML in Haskell

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

(es ist mehr als das, aber das genügt als Beispiel).

Ich mag es auf eine Datenstruktur analysieren:

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

Ich verstehe, mehr oder weniger, wie Parsec Werke (auf der Ebene des Real World Haskell Buch), und ich versuchte, ein wenig die Text.XML Bibliothek, aber alles, was ich entwickeln konnte, war ein Code, der funktionierte, aber für eine so einfache Aufgabe zu groß ist und wie ein halbgebackener Hack aussieht und nicht der Beste, den man tun könnte.

Ich weiß nicht viel über Parser und XML (Ich weiß im Grunde, was ich im RWH Buch gelesen habe, ich habe nie zuvor Parser verwendet) (Ich mache nur statistische und numerische Programmierung, ich bin kein Informatiker) . Gibt es eine XML-Parsing-Bibliothek, in der ich einfach sagen könnte, was das Modell ist und die Informationen sofort extrahiere, ohne jedes Element von Hand analysieren zu müssen und ohne reine Zeichenfolge analysieren zu müssen?

Ich denke an so etwas wie:

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 

, wo ich würde nicht mit dem reinen Zeichenfolge zu tun haben, und erstellen Sie die combinators mich (ich es saugen).

EDIT: Ich muss wahrscheinlich ein wenig (gerade genug, um dies richtig gemacht) über Parser im Allgemeinen (nicht nur Parsec) und das Minimum über XML zu lesen. Kannst du etwas empfehlen?

Die eigentliche Zeichenfolge ich zu analysieren haben, ist dies:

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:

habe ich versucht, die folgende (readfloat, readQuoteTime, etc ... sind nur Funktionen Dinge von Strings zu lesen).

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 

Als ich es in GHCI nennen:

*Main> docParser stringTest >>= print 
[] 

etwas falsch?

+0

Wenn Sie in Parser Kombinatoren interessiert, S. Doaitse Swierstra Tutorial, http://www.cs.uu.nl/research/techreps/repo/CS-2008/2008- 044.pdf, ist eine ziemlich gute Einführung. Es verwendet den Anwendungsstil, aber es setzt keine Kenntnisse der Applicative (oder Parser-Theorie) voraus. Ich denke, die meisten Parser-Kombinator-Bibliotheken auf Hackage (Polyparse, Attoparsec, UU-Parsinglib) sind eine bessere Wahl als Parsec. –

Antwort

4

ich verwendet habe Haskell XML Toolbox in der Vergangenheit. Etwas nach dem Vorbild von

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

Das ist schön. Ich mag Pfeile. Aber ich kann sowieso keinen String finden und einen XmlTree zurückgeben, um den Parser zu füttern. Ich finde nur Funktionen zum Lesen von Dokumenten. Gibt es eine '(ArrowXml a) => eine String XmlTree' Funktion? –

+0

ha! Gefunden 'hread' und' xread'. Vielen Dank. –

+0

Ich habe ein Problem mit der ersten Zeile ''. Wenn es vorhanden ist, kann der Parser nichts erreichen. Ich löste das, indem ich einfach 23 Zeichen aus der Zeichenfolge löschte. Gibt es eine weniger hacky Lösung? –

5

Für einfaches XML-Parsing können Sie mit Tagsoup nichts falsch machen. http://hackage.haskell.org/package/tagsoup

+1

Solange Sie Welligkeit nicht validieren müssen oder sicherstellen, dass Tags gut ausbalanciert sind. So sehr ich Tags für HTML-Scraping mag, ich denke, es ist schlecht geeignet für das Parsen von gut strukturierten XML-Dateien. –

+3

@Michael - wenn ich das irritierende Format eines anderen analysiere, ist es mir im Allgemeinen egal, ob sie die Details richtig verstanden haben, oder ich vertraue darauf, dass sie das getan haben oder nicht, abhängig von der Kompetenz des Anbieters. Ich sorge mich darum, meine Informationen herauszubekommen, und zwar robust, wenn sie Dinge an mir ändern. – sclv

19

Es gibt viele für Haskell geschriebene XML-Bibliotheken, die das Parsing für Sie durchführen können. Ich empfehle die Bibliothek namens xml (siehe http://hackage.haskell.org/package/xml). Mit ihm können Sie einfach schreiben z.B .:

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 

Dieser Code-Schnipsel druckt [Just "PETR3"] als Ergebnis für Ihr Beispiel XML, und es ist einfach für das Sammeln aller Daten, die Sie erweitern müssen. Um das Programm in dem von Ihnen beschriebenen Stil zu schreiben, sollten Sie die Maybe-Monade verwenden, da die XML-Lookup-Funktionen oft einen Maybe-String zurückgeben, der anzeigt, ob das Tag, das Element oder das Attribut gefunden werden kann. Siehe auch eine verwandte Frage: Which Haskell XML library to use?

4

Es gibt andere Möglichkeiten, diese Bibliothek zu verwenden, aber für etwas Einfaches wie dies warf ich einen Sax Parser zusammen.

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 

Von dort können Sie herausfinden, wohin Sie gehen. Sie könnten auch Text.XML.Expat.Annotated verwenden, um sie in eine Struktur zu analysieren, die mehr ist wie das, was Sie oben suchen:

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

Und dann Text.XML.Expat.Proc verwenden, um die Struktur zu surfen.

4

Das folgende Snippet verwendet den XML-Enumerator. Es läßt Datum und Uhrzeit als Text (die blieb als Übung für den Leser Parsen):

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