hexpat 0.1 → 0.2
raw patch · 5 files changed
+122/−199 lines, 5 filesdep +bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring
API changes (from Hackage documentation)
- C2HS: cFloatConv :: (RealFloat a, RealFloat b) => a -> b
- C2HS: cFromBool :: (Num a) => Bool -> a
- C2HS: cFromEnum :: (Enum e, Integral i) => e -> i
- C2HS: cIntConv :: (Integral a, Integral b) => a -> b
- C2HS: cToBool :: (Num a) => a -> Bool
- C2HS: cToEnum :: (Integral i, Enum e) => i -> e
- C2HS: combineBitMasks :: (Enum a, Bits b) => [a] -> b
- C2HS: containsBitMask :: (Bits a, Enum b) => a -> b -> Bool
- C2HS: extractBitMasks :: (Bits a, Enum b, Bounded b) => a -> [b]
- C2HS: instance (Storable a) => Storable (Maybe a)
- C2HS: nothingIf :: (a -> Bool) -> (a -> b) -> a -> Maybe b
- C2HS: nothingIfNull :: (Ptr a -> b) -> Ptr a -> Maybe b
- C2HS: peekBool :: (Integral a, Storable a) => Ptr a -> IO Bool
- C2HS: peekCStringLenIntConv :: (Integral t) => (Ptr CChar, t) -> IO String
- C2HS: peekEnum :: (Enum a, Integral b, Storable b) => Ptr b -> IO a
- C2HS: peekFloatConv :: (Storable a, RealFloat a, RealFloat b) => Ptr a -> IO b
- C2HS: peekIntConv :: (Storable a, Integral a, Integral b) => Ptr a -> IO b
- C2HS: withBool :: (Integral a, Storable a) => Bool -> (Ptr a -> IO b) -> IO b
- C2HS: withCStringLenIntConv :: (Integral b) => String -> ((Ptr CChar, b) -> IO a) -> IO a
- C2HS: withEnum :: (Enum a, Integral b, Storable b) => a -> (Ptr b -> IO c) -> IO c
- C2HS: withFloatConv :: (Storable b, RealFloat a, RealFloat b) => a -> (Ptr b -> IO c) -> IO c
- C2HS: withIntConv :: (Storable b, Integral a, Integral b) => a -> (Ptr b -> IO c) -> IO c
+ Text.XML.Expat.IO: ASCII :: Encoding
+ Text.XML.Expat.IO: ISO88591 :: Encoding
+ Text.XML.Expat.IO: UTF16 :: Encoding
+ Text.XML.Expat.IO: UTF8 :: Encoding
+ Text.XML.Expat.IO: data Encoding
+ Text.XML.Expat.IO: parseChunk :: Parser -> ByteString -> Bool -> IO (Bool)
+ Text.XML.Expat.Tree: ASCII :: Encoding
+ Text.XML.Expat.Tree: ISO88591 :: Encoding
+ Text.XML.Expat.Tree: UTF16 :: Encoding
+ Text.XML.Expat.Tree: UTF8 :: Encoding
+ Text.XML.Expat.Tree: data Encoding
- Text.XML.Expat.IO: newParser :: Maybe String -> IO Parser
+ Text.XML.Expat.IO: newParser :: Maybe Encoding -> IO Parser
- Text.XML.Expat.IO: parse :: Parser -> String -> Bool -> IO (Bool)
+ Text.XML.Expat.IO: parse :: Parser -> ByteString -> IO Bool
- Text.XML.Expat.Tree: parse :: Maybe String -> String -> Maybe Node
+ Text.XML.Expat.Tree: parse :: Maybe Encoding -> ByteString -> Maybe Node
Files
- Text/XML/Expat/IO.chs +52/−23
- Text/XML/Expat/IO.hs +0/−164
- Text/XML/Expat/Tree.hs +13/−9
- hexpat.cabal +15/−3
- perf.hs +42/−0
Text/XML/Expat/IO.chs view
@@ -1,21 +1,33 @@ -- hexpat, a Haskell wrapper for expat -- Copyright (C) 2008 Evan Martin <martine@danga.com> --- |This module wraps the Expat API directly, with IO.+-- |This module wraps the Expat API directly with IO operations+-- everywhere. Basic usage is:+--+-- (1) Make a new parser: 'newParser'.+--+-- (2) Set up callbacks on the parser: 'setStartElementHandler', etc.+--+-- (3) Feed data into the parser: 'parse' or 'parseChunk'. module Text.XML.Expat.IO ( -- ** Parser Setup Parser, newParser, -- ** Parsing- parse,+ parse, Encoding(..), -- ** Parser Callbacks StartElementHandler, EndElementHandler, CharacterDataHandler,- setStartElementHandler, setEndElementHandler, setCharacterDataHandler+ setStartElementHandler, setEndElementHandler, setCharacterDataHandler,++ -- ** Lower-level Parsing Interface+ parseChunk ) where import C2HS+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL #include <expat.h> @@ -31,38 +43,55 @@ withParser :: Parser -> (ParserPtr -> IO a) -> IO a withParser (Parser fp) = withForeignPtr fp -withOptCString :: Maybe String -> (CString -> IO a) -> IO a-withOptCString Nothing f = f nullPtr-withOptCString (Just str) f = withCString str f+-- |Encoding types available for the document encoding.+data Encoding = ASCII | UTF8 | UTF16 | ISO88591+encodingToString :: Encoding -> String+encodingToString ASCII = "US-ASCII"+encodingToString UTF8 = "UTF-8"+encodingToString UTF16 = "UTF-16"+encodingToString ISO88591 = "ISO-8859-1" +withOptEncoding :: Maybe Encoding -> (CString -> IO a) -> IO a+withOptEncoding Nothing f = f nullPtr+withOptEncoding (Just enc) f = withCString (encodingToString enc) f+ {#fun unsafe XML_ParserCreate as parserCreate- {withOptCString* `Maybe String'} -> `ParserPtr' id#}+ {withOptEncoding* `Maybe Encoding'} -> `ParserPtr' id#} foreign import ccall "&XML_ParserFree" parserFree :: FunPtr (ParserPtr -> IO ()) --- |Create a 'Parser'. The optional parameter is the default character--- encoding, and can be one of------ - \"US-ASCII\"------ - \"UTF-8\"------ - \"UTF-16\"------ - \"ISO-8859-1\"-newParser :: Maybe String -> IO Parser+-- |Create a 'Parser'. The encoding parameter, if provided, overrides the+-- document's encoding declaration.+newParser :: Maybe Encoding -> IO Parser newParser enc = do ptr <- parserCreate enc fptr <- newForeignPtr parserFree ptr return $ Parser fptr +-- ByteString.useAsCStringLen is almost what we need, but C2HS wants a CInt+-- instead of an Int.+withBStringLen :: BS.ByteString -> ((CString, CInt) -> IO a) -> IO a+withBStringLen bs f = do+ BS.useAsCStringLen bs $ \(str, len) -> f (str, fromIntegral len)+ unStatus :: CInt -> Bool unStatus 0 = False unStatus 1 = True--- |@parse data False@ feeds mode data into a 'Parser'. The end of the data--- is indicated by passing True for the final parameter. @parse@ returns--- False on a parse error.-{#fun XML_Parse as parse- {withParser* `Parser', `String' &, `Bool'} -> `Bool' unStatus#}+-- |@parseChunk data False@ feeds /strict/ ByteString data into a+-- 'Parser'. The end of the data is indicated by passing @True@ for the+-- final parameter. @parse@ returns @False@ on a parse error.+{#fun XML_Parse as parseChunk+ {withParser* `Parser', withBStringLen* `BS.ByteString' &, `Bool'}+ -> `Bool' unStatus#}++-- |@parse data@ feeds /lazy/ bytestring data into a parser and returns+-- @True@ if there was no parse error.+parse :: Parser -> BSL.ByteString -> IO Bool+parse parser bs = feedChunk (BSL.toChunks bs) where+ feedChunk [] = return True+ feedChunk [chunk] = parseChunk parser chunk True+ feedChunk (c:cs) = do ok <- parseChunk parser c False+ if ok then feedChunk cs+ else return False -- |The type of the \"element started\" callback. The first parameter is -- the element name; the second are the (attribute, value) pairs.
− Text/XML/Expat/IO.hs
@@ -1,164 +0,0 @@--- GENERATED by C->Haskell Compiler, version 0.15.1 Rainy Days, 31 Aug 2007 (Haskell)--- Edit the ORIGNAL .chs file instead!---{-# LINE 1 "IO.chs" #-}-- hexpat, a Haskell wrapper for expat--- Copyright (C) 2008 Evan Martin <martine@danga.com>---- |This module wraps the Expat API directly, with IO.--module Text.XML.Expat.IO (- -- ** Parser Setup- Parser, newParser,-- -- ** Parsing- parse,-- -- ** Parser Callbacks- StartElementHandler, EndElementHandler, CharacterDataHandler,- setStartElementHandler, setEndElementHandler, setCharacterDataHandler-) where--import C2HS----- Expat functions start with "XML", but C2HS appears to ignore our "as"--- definitions if they only differ from the symbol in case. So we write out--- XML_* in most cases anyway... :(--{-# LINE 25 "IO.chs" #-}---- |Opaque parser type.-type ParserPtr = Ptr ()-newtype Parser = Parser (ForeignPtr ())--withParser :: Parser -> (ParserPtr -> IO a) -> IO a-withParser (Parser fp) = withForeignPtr fp--withOptCString :: Maybe String -> (CString -> IO a) -> IO a-withOptCString Nothing f = f nullPtr-withOptCString (Just str) f = withCString str f--parserCreate :: Maybe String -> IO (ParserPtr)-parserCreate a1 =- withOptCString a1 $ \a1' -> - parserCreate'_ a1' >>= \res ->- let {res' = id res} in- return (res')-{-# LINE 39 "IO.chs" #-}-foreign import ccall "&XML_ParserFree" parserFree :: FunPtr (ParserPtr -> IO ())---- |Create a 'Parser'. The optional parameter is the default character--- encoding, and can be one of------ - \"US-ASCII\"------ - \"UTF-8\"------ - \"UTF-16\"------ - \"ISO-8859-1\"-newParser :: Maybe String -> IO Parser-newParser enc = do- ptr <- parserCreate enc- fptr <- newForeignPtr parserFree ptr- return $ Parser fptr--unStatus :: CInt -> Bool-unStatus 0 = False-unStatus 1 = True--- |@parse data False@ feeds mode data into a 'Parser'. The end of the data--- is indicated by passing True for the final parameter. @parse@ returns--- False on a parse error.-parse :: Parser -> String -> Bool -> IO (Bool)-parse a1 a2 a3 =- withParser a1 $ \a1' -> - withCStringLenIntConv a2 $ \(a2'1, a2'2) -> - let {a3' = cFromBool a3} in - parse'_ a1' a2'1 a2'2 a3' >>= \res ->- let {res' = unStatus res} in- return (res')-{-# LINE 65 "IO.chs" #-}---- |The type of the \"element started\" callback. The first parameter is--- the element name; the second are the (attribute, value) pairs.-type StartElementHandler = String -> [(String,String)] -> IO ()--- |The type of the \"element ended\" callback. The parameter is--- the element name.-type EndElementHandler = String -> IO ()--- |The type of the \"character data\" callback. The parameter is--- the character data processed. This callback may be called more than once--- while processing a single conceptual block of text.-type CharacterDataHandler = String -> IO ()--type CStartElementHandler = Ptr () -> CString -> Ptr CString -> IO ()-foreign import ccall "wrapper"- mkCStartElementHandler :: CStartElementHandler- -> IO (FunPtr CStartElementHandler)-wrapStartElementHandler :: StartElementHandler- -> IO (FunPtr CStartElementHandler)-wrapStartElementHandler handler = mkCStartElementHandler h where- h ptr cname cattrs = do- name <- peekCString cname- cattrlist <- peekArray0 nullPtr cattrs- attrlist <- mapM peekCString cattrlist- handler name (pairwise attrlist)--- |Attach a StartElementHandler to a Parser.-setStartElementHandler :: Parser -> StartElementHandler -> IO ()-setStartElementHandler parser handler = do- withParser parser $ \p -> do- handler' <- wrapStartElementHandler handler- xmlSetstartelementhandler p handler'---type CEndElementHandler = Ptr () -> CString -> IO ()-foreign import ccall "wrapper"- mkCEndElementHandler :: CEndElementHandler- -> IO (FunPtr CEndElementHandler)-wrapEndElementHandler :: EndElementHandler- -> IO (FunPtr CEndElementHandler)-wrapEndElementHandler handler = mkCEndElementHandler h where- h ptr cname = do- name <- peekCString cname- handler name--- |Attach an EndElementHandler to a Parser.-setEndElementHandler :: Parser -> EndElementHandler -> IO ()-setEndElementHandler parser handler = do- withParser parser $ \p -> do- handler' <- wrapEndElementHandler handler- xmlSetendelementhandler p handler'--type CCharacterDataHandler = Ptr () -> CString -> CInt -> IO ()-foreign import ccall "wrapper"- mkCCharacterDataHandler :: CCharacterDataHandler- -> IO (FunPtr CCharacterDataHandler)-wrapCharacterDataHandler :: CharacterDataHandler- -> IO (FunPtr CCharacterDataHandler)-wrapCharacterDataHandler handler = mkCCharacterDataHandler h where- h ptr cdata len = do- data_ <- peekCStringLen (cdata, fromIntegral len)- handler data_--- |Attach an CharacterDataHandler to a Parser.-setCharacterDataHandler :: Parser -> CharacterDataHandler -> IO ()-setCharacterDataHandler parser handler = do- withParser parser $ \p -> do- handler' <- wrapCharacterDataHandler handler- xmlSetcharacterdatahandler p handler'--pairwise (x1:x2:xs) = (x1,x2) : pairwise xs-pairwise [] = []--foreign import ccall unsafe "IO.chs.h XML_ParserCreate"- parserCreate'_ :: ((Ptr CChar) -> (IO (Ptr ())))--foreign import ccall safe "IO.chs.h XML_Parse"- parse'_ :: ((Ptr ()) -> ((Ptr CChar) -> (CInt -> (CInt -> (IO CInt)))))--foreign import ccall unsafe "IO.chs.h XML_SetStartElementHandler"- xmlSetstartelementhandler :: ((Ptr ()) -> ((FunPtr ((Ptr ()) -> ((Ptr CChar) -> ((Ptr (Ptr CChar)) -> (IO ()))))) -> (IO ())))--foreign import ccall unsafe "IO.chs.h XML_SetEndElementHandler"- xmlSetendelementhandler :: ((Ptr ()) -> ((FunPtr ((Ptr ()) -> ((Ptr CChar) -> (IO ())))) -> (IO ())))--foreign import ccall unsafe "IO.chs.h XML_SetCharacterDataHandler"- xmlSetcharacterdatahandler :: ((Ptr ()) -> ((FunPtr ((Ptr ()) -> ((Ptr CChar) -> (CInt -> (IO ()))))) -> (IO ())))
Text/XML/Expat/Tree.hs view
@@ -2,15 +2,18 @@ -- Copyright (C) 2008 Evan Martin <martine@danga.com> -- |The Expat.Tree module provides a simplified interface to parsing, that--- returns a tree of the XML structure. (Note that this is not a lazy parse--- of the document: as soon as the root node is accessed, the entire document--- is parsed.)+-- returns a tree of the XML structure. It is written using the lower-level+-- bindings in the "Text.XML.Expat.IO" module. (Note that this is not a lazy+-- parse of the document: as soon as the root node is accessed, the entire+-- document is parsed.) module Text.XML.Expat.Tree (- Text.XML.Expat.Tree.parse, Node(..)+ Text.XML.Expat.Tree.parse, Node(..),+ EIO.Encoding(..) ) where -import Text.XML.Expat.IO as EIO+import qualified Text.XML.Expat.IO as EIO+import qualified Data.ByteString.Lazy as B import Data.IORef import System.IO.Unsafe (unsafePerformIO) @@ -23,9 +26,10 @@ modifyChildren :: ([Node] -> [Node]) -> Node -> Node modifyChildren f node = node { eChildren = f (eChildren node) } --- |@parse enc doc@ parses XML content @doc@ with optional encoding @enc@,--- and returns the root 'Node' of the document if there were no parsing errors.-parse :: Maybe String -> String -> Maybe Node+-- |@parse enc doc@ parses /lazy/ bytestring XML content @doc@ with optional+-- encoding override @enc@ and returns the root 'Node' of the document if there+-- were no parsing errors.+parse :: Maybe EIO.Encoding -> B.ByteString -> Maybe Node parse enc doc = unsafePerformIO $ runParse where runParse = do parser <- EIO.newParser enc@@ -35,7 +39,7 @@ EIO.setStartElementHandler parser (\n a -> modifyIORef stack (start n a)) EIO.setEndElementHandler parser (\n -> modifyIORef stack (end n)) EIO.setCharacterDataHandler parser (\s -> modifyIORef stack (text s))- ok <- EIO.parse parser doc True+ ok <- EIO.parse parser doc if ok then do [Element _ _ [root]] <- readIORef stack
hexpat.cabal view
@@ -1,7 +1,18 @@ Cabal-Version: >= 1.2 Name: hexpat-Version: 0.1+Version: 0.2 Synopsis: wrapper for expat, the fast XML parser+Description:+ Expat (<http://expat.sourceforge.net/>) is a stream-oriented XML parser+ written in C. It is known for being simple and fast.+ .+ There are already nice XML libraries in the Text.XML hierarchy. The+ reason to use Expat is when speed is of concern. From a benchmark+ that compares getting the length of the root node of a HaXml tree+ (presumably forcing parsing the entire file) against running an Expat+ parser with a registered start node handler, Expat is about 12 times+ faster. This is not a fair benchmark; HaXml does a lot more than this+ Expat library. But if Expat suffices, it is good for what it does. Category: XML License: BSD3 License-File: LICENSE@@ -9,12 +20,13 @@ Maintainer: martine@danga.com Copyright: (c) 2008 Evan Martin <martine@danga.com> Homepage: http://neugierig.org/software/darcs/browse/?r=hexpat;a=summary-Extra-Source-Files: C2HS.hs test.hs+Extra-Source-Files: test.hs, perf.hs Build-Type: Simple Library- Build-Depends: base, haskell98+ Build-Depends: base, haskell98, bytestring Exposed-Modules: Text.XML.Expat.IO, Text.XML.Expat.Tree+ Other-Modules: C2HS Extensions: ForeignFunctionInterface Extra-Libraries: expat
+ perf.hs view
@@ -0,0 +1,42 @@+-- hexpat, a Haskell wrapper for expat+-- Copyright (C) 2008 Evan Martin <martine@danga.com>++-- This program microbenchmarks HaXml versus Expat parsing.+-- The comparision is pretty unfair; HaXml does a whole lot more.+-- However, if you just want to read all the XML tags in a file,+-- this program demonstrates why Expat may be preferable.++import Control.Exception+import qualified Data.ByteString.Lazy as BS+import Data.Char (ord)+import Data.IORef+import Microbench+import Text.XML.Expat.IO as Expat+import Text.XML.HaXml as HaXml++parse_haxml :: String -> IO ()+parse_haxml input = do+ let Document _ _ root _ = HaXml.xmlParse "input" input+ let Elem _ _ content = root+ evaluate $ length content+ return ()++parse_expat :: BS.ByteString -> IO ()+parse_expat input = do+ parser <- Expat.newParser Nothing+ counter <- newIORef 0+ Expat.setStartElementHandler parser (elementHandler counter)+ Expat.parse parser input+ readIORef counter+ return ()+ where+ elementHandler counter tag attrs = modifyIORef counter (+1)++main = do+ xml <- readFile "test.xml"+ let xmlbs = BS.pack (map (fromIntegral.ord) xml)+ -- Force reading the entire file first.+ putStrLn $ "input is " ++ show (BS.length xmlbs) ++ " bytes."+ -- Start the races.+ microbench "HaXml" (parse_haxml xml)+ microbench "hexpat" (parse_expat xmlbs)