diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Text/XML/LibXML/Enumerator.hs b/Text/XML/LibXML/Enumerator.hs
new file mode 100644
--- /dev/null
+++ b/Text/XML/LibXML/Enumerator.hs
@@ -0,0 +1,103 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module: Text.XML.LibXML.Enumerator
+-- Copyright: 2010 John Millikin
+-- License: MIT
+--
+-- Maintainer: jmillikin@gmail.com
+-- Portability: portable
+--
+-----------------------------------------------------------------------------
+module Text.XML.LibXML.Enumerator
+	( Event (..)
+	, parseBytes
+	, parseText
+	) where
+import qualified Data.ByteString as B
+import qualified Data.Enumerator as E
+import Data.Enumerator ((>>==))
+import qualified Data.Text as T
+import qualified Data.XML.Types as X
+import qualified Text.XML.LibXML.SAX as SAX
+
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.ST (ST, unsafeSTToIO)
+import qualified Data.STRef as ST
+import System.IO.Unsafe (unsafePerformIO)
+
+data Event
+	= EventBeginDocument
+	| EventEndDocument
+	| EventBeginElement X.Name [X.Attribute]
+	| EventEndElement X.Name
+	| EventCharacters T.Text
+	| EventComment T.Text
+	| EventInstruction X.Instruction
+	deriving (Show, Eq)
+
+newParser :: Maybe T.Text -> ST s (SAX.Parser (ST s), ST.STRef s [Event], ST.STRef s (Maybe T.Text))
+newParser name = do
+	errRef <- ST.newSTRef Nothing
+	p <- SAX.newParserST (\msg -> ST.writeSTRef errRef (Just msg)) name
+	eventRef <- ST.newSTRef []
+	
+	let addEvent e = ST.modifySTRef eventRef (e:) >> return True
+	let setCallback cb st = SAX.setCallback p cb st
+	
+	setCallback SAX.parsedBeginDocument (addEvent EventBeginDocument)
+	setCallback SAX.parsedEndDocument (addEvent EventEndDocument)
+	setCallback SAX.parsedBeginElement ((addEvent .) . EventBeginElement)
+	setCallback SAX.parsedEndElement (addEvent . EventEndElement)
+	setCallback SAX.parsedCharacters (addEvent . EventCharacters)
+	setCallback SAX.parsedComment (addEvent . EventComment)
+	setCallback SAX.parsedInstruction (addEvent . EventInstruction)
+	
+	return (p, eventRef, errRef)
+
+unsafePerformST_M :: Monad m => ST s a -> m a
+unsafePerformST_M st = unsafePerformIO (unsafeSTToIO (fmap return st))
+
+parseBytes :: Monad m => Maybe T.Text -> E.Enumeratee T.Text B.ByteString Event m b
+parseBytes = parseSomething SAX.parseBytes
+
+parseText :: Monad m => Maybe T.Text -> E.Enumeratee T.Text T.Text Event m b
+parseText = parseSomething SAX.parseText
+
+parseSomething :: Monad m
+               => (SAX.Parser (ST s) -> a -> ST s ())
+               -> Maybe T.Text
+               -> E.Enumeratee T.Text a Event m b
+parseSomething something name s = E.Iteratee $ do
+	(p, eventRef, errRef) <- unsafePerformST_M $ newParser name
+	
+	let withEvents st = unsafePerformST_M $ do
+		ST.writeSTRef eventRef []
+		ST.writeSTRef errRef Nothing
+		st
+		events <- ST.readSTRef eventRef
+		err <- ST.readSTRef errRef
+		return (reverse events, err)
+	
+	let parseChunk bytes = withEvents (something p bytes)
+	let complete = withEvents (SAX.parseComplete p)
+	E.runIteratee $ eneeParser parseChunk complete s
+
+eneeParser :: Monad m
+           => (a -> m ([Event], Maybe T.Text))
+           -> m ([Event], Maybe T.Text)
+           -> E.Enumeratee T.Text a Event m b
+eneeParser parseChunk parseComplete = E.checkDone (E.continue . step) where
+	step k E.EOF = checkEvents k parseComplete (\k' -> E.yield (E.Continue k') E.EOF)
+	step k (E.Chunks xs) = parseLoop k xs
+	
+	parseLoop k [] = E.continue (step k)
+	parseLoop k (x:xs) = checkEvents k (parseChunk x) (\k' -> parseLoop k' xs)
+	
+	checkEvents k getEvents next = do
+		(events, maybeErr) <- lift getEvents
+		let checkError k' = case maybeErr of
+			Nothing -> next k'
+			Just err -> E.throwError err
+		if null events
+			then checkError k
+			else k (E.Chunks events) >>== E.checkDone checkError
diff --git a/libxml-enumerator.cabal b/libxml-enumerator.cabal
new file mode 100644
--- /dev/null
+++ b/libxml-enumerator.cabal
@@ -0,0 +1,29 @@
+name: libxml-enumerator
+version: 0.1
+synopsis: Enumerator-based API for libXML's SAX interface
+license: MIT
+license-file: license.txt
+author: John Millikin <jmillikin@gmail.com>
+maintainer: jmillikin@gmail.com
+copyright: Copyright (c) John Millikin 2010
+build-type: Simple
+cabal-version: >=1.6
+category: Enumerator, Text, XML, Parsing
+stability: experimental
+bug-reports: mailto:jmillikin@gmail.com
+tested-with: GHC==6.12.1
+
+library
+  ghc-options: -Wall -fno-warn-unused-do-bind
+
+  build-depends:
+      base >= 4 && < 5
+    , bytestring >= 0.9 && < 0.10
+    , text >= 0.7 && < 0.8
+    , enumerator >= 0.1 && < 0.4
+    , transformers >= 0.2 && < 0.3
+    , libxml-sax >= 0.6 && < 0.7
+    , xml-types >= 0.1 && < 0.2
+
+  exposed-modules:
+    Text.XML.LibXML.Enumerator
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2010 John Millikin
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
