2011-01-06 29 views
14

Borsa fiyat teklifleriyle (sample data) periyodik olarak bir XML dosyasına hizmet veren bir web sayfasından veri almaya çalışıyorum. XML yapısı çok basittir ve bunun gibi bir şeydir:Haskell'deki XML Ayrıştırma

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

(bundan fazlası var ama bu bir örnek olarak yeterli).

Bir veri yapısı bunu ayrıştırmak istiyorum: Anlıyorum

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

fazla veya daha az ne kadar Parsec (Real World Haskell kitabın düzeyinde) çalışır ve biraz çalıştı Text.XML kütüphane ama geliştirebildiğim bir kod çalıştı ama bu kadar basit bir görev için çok büyük ve en iyi olanın yapamayacağı bir yarım fırında kesilmiş hack gibi görünüyor.

Ben ayrıştırıcıları ve XML hakkında çok şey (Ben önce ayrıştırıcıları hiç kullanılmamış ne RWH kitapta okumak temelde biliyorum) bilmiyorum (Ben sadece bir bilgisayar bilimcisi değilim, istatistik ve sayısal programlama yapmak) . Her öğeyi elle ayrıştırmak zorunda kalmadan ve saf dizeyi ayrıştırmak zorunda kalmadan, yalnızca modelin ne olduğunu ve bilgiyi hemen ayıklayabildiğim bir XML ayrıştırma kütüphanesi var mı?

Ben böyle bir şey düşünüyorum: Ben saf dize ile anlaşma ve (Ben ona emmek) combinators kendim yaratmak zorunda olmazdı

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 

.

DÜZENLEME: Herhalde genel (sadece Parsec) içinde ayrıştırıcıları hakkında (bu doğru yolu halletmek için yeterli) biraz ve XML hakkında asgari okumak gerekir. Bir şey tavsiye eder misiniz?

ben ayrıştırmak zorunda gerçek dize şudur:

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:

Aşağıdaki çalıştı (vb readFloat, readQuoteTime ... dizeleri şeyleri okumak için sadece fonksiyonlardır).

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 

ben GHCi içinde diyoruz:

*Main> docParser stringTest >>= print 
[] 

yanlış bir şey var mı?

+0

çizgisinde şey S. Doaitse Swierstra en öğretici, http://www.cs.uu.nl/research/techreps/repo/CS-2008/2008- 044.pdf, oldukça iyi bir tanıtımdır. Uygulama stilini kullanır, ancak Uygulamanın (veya ayrıştırıcı teorisinin) bilgisini varsaymaz. Hackage (Polyparse, Attoparsec, UU-parsinglib) üzerindeki ayrıştırıcı kitaplık kütüphanelerinin çoğunun Parsec'ten daha iyi seçimler olduğunu düşünüyorum. –

cevap

4

: Bu metin olarak tarih ve saati (bu okuyucuya bir alıştırma olarak bırakılmıştır ayrıştırma) bırakır. Eğer ayrıştırıcı, bağdaştırıcılarla ilgileniyorsanız

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

Bu güzel. Okları severim. Ancak bir String almak ve ayrıştırıcıyı beslemek için bir XmlTree döndürmek için bulamadım. Sadece belgeleri okumak için fonksiyonlar buluyorum. Herhangi bir (ArrowX a a) => String XmlTree işlevi var mı? –

+0

ha! 'Hread' ve' xread' bulundu. Teşekkürler. –

+0

İlk satır '' Ile ilgili bir sorun yaşıyorum. Mevcut olduğunda, ayrıştırıcı hiçbir şey alamaz. Bunu, dizeden 23 karakter çıkartarak çözdüm. Daha az karmaşık bir çözüm var mı? –

5

Basit xml ayrıştırma için, tagsoup ile yanlış gidemezsiniz. http://hackage.haskell.org/package/tagsoup

+1

İyi oluşumu doğrulamanız veya etiketlerin iyi dengelendiğinden emin olmanız gerekmediği sürece. HTML kazıma işlemi için etiketleri beğendiğim kadarıyla, iyi yapılandırılmış XML dosyalarını ayrıştırma konusunda yanıltıcı olduğunu düşünüyorum. –

+3

@Michael - başkasının rahatsız edici biçimini ayrıştırıyorsam, ayrıntıların doğru olup olmadığını umursamıyorum ya da satıcının yetkinliğine bağlı olmak için onlara güveniyorum. Bilgimi almamı umursuyorum ve onlar da benim üzerimde bir şeyler değiştirmeye devam etseler. – sclv

19

sizin için ayrıştırmayı yapabilirsiniz Haskell için yazılmış XML kütüphanelerinin bol vardır. Xml adlı kitaplığı öneririm (bkz. http://hackage.haskell.org/package/xml). Bununla beraber, sadece .: senin örneğin XML için bir sonucu olarak

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 

Bu pasajı baskılar [Just "PETR3"] örn yazabilir ve ihtiyacınız olan tüm veri toplamak için uzatmak kolaydır. Programı tanımladığınız stilde yazmak için, xml arama işlevleri genellikle bir "String" i döndürdüğünden, belki de etiketin, öğenin veya özniteliğin bulunup bulunamadığını işaret ederek, Belki monad'ını kullanmalısınız. Ayrıca ilgili soruya bakın: Which Haskell XML library to use?

4

bu kütüphaneyi kullanmaya başka yolları da vardır, ancak bu gibi basit bir şey için ben birlikte bir saksofon ayrıştırıcı attı.

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 

Buradan nereye gideceğinizi öğrenebilirsiniz. yapıyı sörf Text.XML.Expat.Proc kullanmak sonra

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

Ve: Ayrıca daha yukarıda aradıklarını gibi bir yapıya ayrıştırmak amacıyla Text.XML.Expat.Annotated kullanabilirsiniz.

4

Aşağıdaki parçacık, xml sayımı kullanır. Geçmişte Haskell XML Toolbox kullandım

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