Non si dice cosa sia lo processEvent
. In linea di principio, non dovrebbe essere problematico utilizzare il pigro ByteString
per una rigida piega a sinistra sull'input generato in modo pigramente, quindi non sono sicuro di cosa stia andando male nel tuo caso. Ma si dovrebbe usare tipi appropriati per lo streaming quando si tratta di file giganteschi!
Infatti, hexpat
ha un'interfaccia 'streaming' (proprio come xml-conduit
). Utilizza la libreria non troppo conosciuta List
e the rather ugly List
class it defines. In linea di principio il ListT
type dal pacchetto List dovrebbe funzionare bene. Mi sono arreso rapidamente a causa di una mancanza di combinatori e ho scritto un'istanza appropriata della brutta classe List
per una versione compressa di Pipes.ListT
che ho poi utilizzato per esportare le normali funzioni Pipes.Producer
come parseProduce
. Le manipolazioni banali necessari per questo vengono aggiunti sotto come PipesSax.hs
Una volta che abbiamo parseProducer
possiamo convertire un ByteString o Producer testo in un produttore di SaxEvents
con componenti di testo o ByteString. Ecco alcune semplici operazioni. Stavo usando un 238M "input.xml"; i programmi non richiedono mai più di 6 mb di memoria, a giudicare dall'aspetto di top
.
-Sax.hs
La maggior parte delle azioni IO utilizzare un tubo registerIds
definita in fondo che è su misura per un po 'gigante di XML, di cui questa è una valida 1000 frammento http://sprunge.us/WaQK
{-#LANGUAGE OverloadedStrings #-}
import PipesSax (parseProducer)
import Data.ByteString (ByteString)
import Text.XML.Expat.SAX
import Pipes -- cabal install pipes pipes-bytestring
import Pipes.ByteString (toHandle, fromHandle, stdin, stdout)
import qualified Pipes.Prelude as P
import qualified System.IO as IO
import qualified Data.ByteString.Char8 as Char8
sax :: MonadIO m => Producer ByteString m()
-> Producer (SAXEvent ByteString ByteString) m()
sax = parseProducer defaultParseOptions
-- stream xml from stdin, yielding hexpat tagstream to stdout;
main0 :: IO()
main0 = runEffect $ sax stdin >-> P.print
-- stream the extracted 'IDs' from stdin to stdout
main1 :: IO()
main1 = runEffect $ sax stdin >-> registryIds >-> stdout
-- write all IDs to a file
main2 =
IO.withFile "input.xml" IO.ReadMode $ \inp ->
IO.withFile "output.txt" IO.WriteMode $ \out ->
runEffect $ sax (fromHandle inp) >-> registryIds >-> toHandle out
-- folds:
-- print number of IDs
main3 = IO.withFile "input.xml" IO.ReadMode $ \inp ->
do n <- P.length $ sax (fromHandle inp) >-> registryIds
print n
-- sum the meaningful part of the IDs - a dumb fold for illustration
main4 = IO.withFile "input.xml" IO.ReadMode $ \inp ->
do let pipeline = sax (fromHandle inp) >-> registryIds >-> P.map readIntId
n <- P.fold (+) 0 id pipeline
print n
where
readIntId :: ByteString -> Integer
readIntId = maybe 0 (fromIntegral.fst) . Char8.readInt . Char8.drop 2
-- my xml has tags with attributes that appear via hexpat thus:
-- StartElement "FacilitySite" [("registryId","110007915364")]
-- and the like. This is just an arbitrary demo stream manipulation.
registryIds :: Monad m => Pipe (SAXEvent ByteString ByteString) ByteString m()
registryIds = do
e <- await -- we look for a 'SAXEvent'
case e of -- if it matches, we yield, else we go to the next event
StartElement "FacilitySite" [("registryId",a)] -> do yield a
yield "\n"
registryIds
_ -> registryIds
- 'biblioteca' : PipesSax.hs
Questo appena newtypes Pipes.ListT per ottenere le istanze appropriate. Non esportiamo nulla a che fare con List
o ListT
ma usiamo solo il concetto di Pipes.Producer standard.
{-#LANGUAGE TypeFamilies, GeneralizedNewtypeDeriving #-}
module PipesSax (parseProducerLocations, parseProducer) where
import Data.ByteString (ByteString)
import Text.XML.Expat.SAX
import Data.List.Class
import Control.Monad
import Control.Applicative
import Pipes
import qualified Pipes.Internal as I
parseProducer
:: (Monad m, GenericXMLString tag, GenericXMLString text)
=> ParseOptions tag text
-> Producer ByteString m()
-> Producer (SAXEvent tag text) m()
parseProducer opt = enumerate . enumerate_
. parseG opt
. Select_ . Select
parseProducerLocations
:: (Monad m, GenericXMLString tag, GenericXMLString text)
=> ParseOptions tag text
-> Producer ByteString m()
-> Producer (SAXEvent tag text, XMLParseLocation) m()
parseProducerLocations opt =
enumerate . enumerate_ . parseLocationsG opt . Select_ . Select
newtype ListT_ m a = Select_ { enumerate_ :: ListT m a }
deriving (Functor, Monad, MonadPlus, MonadIO
, Applicative, Alternative, Monoid, MonadTrans)
instance Monad m => List (ListT_ m) where
type ItemM (ListT_ m) = m
joinL = Select_ . Select . I.M . liftM (enumerate . enumerate_)
runList = liftM emend . next . enumerate . enumerate_
where
emend (Right (a,q)) = Cons a (Select_ (Select q))
emend _ = Nil
Si consiglia di guardare in una soluzione di streaming: https://hackage.haskell.org/package/xml-conduit – Sibi
Cosa 'processEvent' fare? Usa '++' sulla stringa dell'accumulatore? Inoltre, si esaurisce solo lo spazio di stack o anche la RAM? Si prega di postare alcune statistiche sull'utilizzo della RAM (per esempio l'output del flag '+ RTS -s'). Inoltre, qual è la tua versione di GHC? Per impostazione predefinita, dopo lo stack GHC 7.8, lo stack può raggiungere l'80% della RAM. –
@Sibi hexpat ha già un'interfaccia di streaming, proprio come xml-conduit. – Michael