2011-01-06 18 views
14

Estoy tratando de obtener datos de una página web que sirve un archivo XML periódicamente con cotizaciones bursátiles (sample data). La estructura del XML es muy simple, y es algo como esto:Parse XML en Haskell

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

(que es más que eso, pero esto basta como ejemplo).

me gustaría analizar a una estructura de datos:

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

entiendo más o menos cómo funciona (en el nivel del libro Real World Haskell) Parsec, y he intentado un poco la Text.XML biblioteca, pero todo lo que pude desarrollar fue un código que funcionó, pero es demasiado grande para una tarea tan simple y parece un truco medio cocido y no lo mejor que se podía hacer.

No sé mucho sobre analizadores sintácticos y XML (sé básicamente lo que leo en el libro de RWH, nunca usé analizadores sintácticos antes) (solo hago programación estadística y numérica, no soy científico informático) . ¿Hay una biblioteca de análisis XML donde podría simplemente decir cuál es el modelo y extraer la información de inmediato, sin tener que analizar cada elemento a mano y sin tener que analizar la cadena pura?

Estoy pensando en algo como:

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 

donde no tendría que lidiar con la cadena pura y crear los combinadores yo (soy muy malo en ello).

EDITAR: Probablemente necesite leer un poco (lo suficiente para hacer esto correctamente) sobre analizadores en general (no solo Parsec) y el mínimo sobre XML. ¿Ustedes recomiendan algo?

La cadena real que tengo que analizar es la siguiente:

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:

He intentado lo siguiente (readFloat, readQuoteTime, etc ... son sólo funciones para leer cosas de cuerdas).

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 

Cuando llamo en ghci:

*Main> docParser stringTest >>= print 
[] 

¿Es algo malo?

+0

Si usted está interesado en los combinadores de analizadores sintácticos, el tutorial de S. Doaitse Swierstra, http://www.cs.uu.nl/research/techreps/repo/CS-2008/2008- 044.pdf, es una introducción bastante buena. Utiliza el estilo aplicativo, pero no asume conocimiento de Aplicativo (o teoría del analizador sintáctico). Creo que la mayoría de las bibliotecas de combinador de analizadores en Hackage (Polyparse, Attoparsec, UU-parsinglib) son mejores opciones que Parsec. –

Respuesta

4

He usado Haskell XML Toolbox en el pasado. Algo a lo largo de las líneas de

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

Esto es bueno. Me gustan las flechas Pero no puedo encontrar de todos modos para obtener una cadena y devolver un XmlTree para alimentar el analizador. Solo encuentro funciones para leer documentos. ¿Hay alguna función '(ArrowXml a) => una Cadena XmlTree'? –

+0

¡ja! Encontrado 'hread' y' xread'. Gracias. –

+0

Tengo un problema con la primera línea ''. Cuando está presente, el analizador no puede obtener nada. Lo resolví simplemente descartando 23 caracteres de la cadena. ¿Hay una solución menos hacky? –

5

Para un análisis xml simple, no se puede equivocar con la etiqueta de cambio. http://hackage.haskell.org/package/tagsoup

+1

Siempre que no necesite validar la buena formación o asegurarse de que las etiquetas estén bien equilibradas. Por mucho que me guste la marca de etiquetas para raspar HTML, creo que no es adecuada para analizar archivos XML bien estructurados. –

+3

@Michael: si estoy analizando el formato irritante de otra persona, generalmente no me importa si tienen los detalles correctos, o confío en que lo hayan hecho o no, según la competencia del proveedor. Me importa difundir mi información, y de manera contundente si me cambian las cosas. – sclv

19

Hay muchas bibliotecas XML escritas para Haskell que pueden hacer el análisis sintáctico para usted. Recomiendo la biblioteca llamada xml (vea http://hackage.haskell.org/package/xml). Con él, usted puede simplemente escribir ej .:

impresiones
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 

este fragmento [Just "PETR3"] como resultado de su ejemplo XML, y es fácil de extender para la recogida de todos los datos que necesita. Para escribir el programa en el estilo que describe, debe usar la mónada Maybe, ya que las funciones de búsqueda xml a menudo devuelven una Cadena Maybe, que indica si la etiqueta, el elemento o el atributo se pueden encontrar. También vea una pregunta relacionada: Which Haskell XML library to use?

4

Hay otras maneras de usar esta biblioteca, pero para algo tan simple como esto, diseñé un analizador de saxofón.

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 

Desde allí se puede averiguar a dónde ir. También es posible usar Text.XML.Expat.Annotated con el fin de analizarlo en una estructura que se parece más a lo que busca por encima de:

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

y luego usar Text.XML.Expat.Proc para navegar por la estructura.

4

El siguiente fragmento usa xml-enumerator. Deja fecha y hora como texto (análisis sintáctico aquellos se deja como ejercicio para el lector):

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