diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
+
+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.
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/HTML/DOM.hs b/Text/HTML/DOM.hs
new file mode 100644
--- /dev/null
+++ b/Text/HTML/DOM.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Text.HTML.DOM
+    ( eventConduit
+    , sinkDoc
+    , readFile
+    , parseLBS
+    ) where
+
+import Prelude hiding (readFile)
+import qualified Data.ByteString as S
+import qualified Text.HTML.TagStream as TS
+import qualified Data.XML.Types as XT
+import Data.Conduit
+import qualified Data.Conduit.List as CL
+import Control.Arrow ((***))
+import Data.Text.Encoding (decodeUtf8With)
+import Data.Text.Encoding.Error (lenientDecode)
+import qualified Data.Set as Set
+import qualified Text.XML as X
+import qualified Filesystem.Path.CurrentOS as F
+import Data.Conduit.Filesystem (sourceFile)
+import qualified Data.ByteString.Lazy as L
+import Control.Monad.Trans.Resource (runExceptionT_)
+import Data.Functor.Identity (runIdentity)
+
+-- | Converts a stream of bytes to a stream of properly balanced @Event@s.
+eventConduit :: Monad m => Conduit S.ByteString m XT.Event
+eventConduit =
+    TS.tokenStream =$= go []
+  where
+    go stack = do
+        mx <- await
+        case fmap (fmap' $ decodeUtf8With lenientDecode) mx of
+            Nothing -> closeStack stack
+            Just (TS.TagOpen local attrs isClosed) -> do
+                let name = toName local
+                    attrs' = map (toName *** return . XT.ContentText) attrs
+                yield $ XT.EventBeginElement name attrs'
+                if isClosed || isVoid local
+                    then yield (XT.EventEndElement name) >> go stack
+                    else go $ name : stack
+            Just (TS.TagClose name)
+                | toName name `elem` stack ->
+                    let loop [] = go []
+                        loop (n:ns) = do
+                            yield $ XT.EventEndElement n
+                            if n == toName name
+                                then go ns
+                                else loop ns
+                     in loop stack
+                | otherwise -> go stack
+            Just (TS.Text t) -> do
+                yield $ XT.EventContent $ XT.ContentText t
+                go stack
+            Just (TS.Comment t) -> do
+                yield $ XT.EventComment t
+                go stack
+            Just TS.Special{} -> go stack
+            Just TS.Incomplete{} -> go stack
+    toName l = XT.Name l Nothing Nothing
+    closeStack = mapM_ (yield . XT.EventEndElement)
+
+    fmap' :: (a -> b) -> TS.Token' a -> TS.Token' b
+    fmap' f (TS.TagOpen x pairs b) = TS.TagOpen (f x) (map (f *** f) pairs) b
+    fmap' f (TS.TagClose x) = TS.TagClose (f x)
+    fmap' f (TS.Text x) = TS.Text (f x)
+    fmap' f (TS.Comment x) = TS.Comment (f x)
+    fmap' f (TS.Special x y) = TS.Special (f x) (f y)
+    fmap' f (TS.Incomplete x) = TS.Incomplete (f x)
+
+    isVoid = flip Set.member $ Set.fromList
+        [ "area"
+        , "base"
+        , "br"
+        , "col"
+        , "command"
+        , "embed"
+        , "hr"
+        , "img"
+        , "input"
+        , "keygen"
+        , "link"
+        , "meta"
+        , "param"
+        , "source"
+        , "track"
+        , "wbr"
+        ]
+
+sinkDoc :: MonadThrow m => Sink S.ByteString m X.Document
+sinkDoc = eventConduit =$ X.fromEvents
+
+readFile :: F.FilePath -> IO X.Document
+readFile fp = runResourceT $ sourceFile fp $$ sinkDoc
+
+parseLBS :: L.ByteString -> X.Document
+parseLBS lbs = runIdentity $ runExceptionT_ $ CL.sourceList (L.toChunks lbs) $$ sinkDoc
diff --git a/html-conduit.cabal b/html-conduit.cabal
new file mode 100644
--- /dev/null
+++ b/html-conduit.cabal
@@ -0,0 +1,43 @@
+Name:                html-conduit
+Version:             0.0.0
+Synopsis:            Parse HTML documents using xml-conduit datatypes.
+Description:         This package uses tagstream-conduit for its parser. It automatically balances mismatched tags, so that there shouldn't be any parse failures. It does not handle a full HTML document rendering, such as adding missing html and head tags.
+Homepage:            https://github.com/snoyberg/xml
+License:             MIT
+License-file:        LICENSE
+Author:              Michael Snoyman
+Maintainer:          michael@snoyman.com
+Category:            Web, Text, Conduit
+Build-type:          Simple
+Extra-source-files:  test/main.hs
+Cabal-version:       >=1.8
+
+Library
+  Exposed-modules:     Text.HTML.DOM
+  Build-depends:       base                             >= 4              && < 5
+                     , transformers
+                     , bytestring
+                     , containers
+                     , text
+                     , resourcet                        >= 0.3            && < 0.4
+                     , conduit                          >= 0.4            && < 0.5
+                     , filesystem-conduit               >= 0.4            && < 0.5
+                     , system-filepath                  >= 0.4            && < 0.5
+                     , xml-conduit                      >= 0.7            && < 0.8
+                     , tagstream-conduit                >= 0.3            && < 0.4
+                     , xml-types                        >= 0.3            && < 0.4
+
+test-suite test
+    type: exitcode-stdio-1.0
+    main-is: main.hs
+    hs-source-dirs: test
+    build-depends:          base
+                          , hspec
+                          , HUnit
+                          , xml-conduit
+                          , html-conduit
+                          , bytestring
+
+source-repository head
+  type: git
+  location: git://github.com/snoyberg/xml.conduit
diff --git a/test/main.hs b/test/main.hs
new file mode 100644
--- /dev/null
+++ b/test/main.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE OverloadedStrings #-}
+import Test.HUnit hiding (Test)
+import Test.Hspec.Monadic
+import Test.Hspec.HUnit ()
+import Data.ByteString.Lazy.Char8 ()
+import qualified Text.HTML.DOM as H
+import qualified Text.XML as X
+
+main :: IO ()
+main = hspecX $ do
+    describe "parses" $ do
+        it "well-formed document" $
+            X.parseLBS_ X.def "<foo><bar>baz</bar></foo>" @=?
+            H.parseLBS        "<foo><bar>baz</bar></foo>"
+        it "adds missing close tags" $
+            X.parseLBS_ X.def "<foo><bar>baz</bar></foo>" @=?
+            H.parseLBS        "<foo><bar>baz</foo>"
+            {- Need to fix in tagstream-conduit
+        it "entities" $
+            X.parseLBS_ X.def "<foo><bar>baz&#160;</bar></foo>" @=?
+            H.parseLBS        "<foo><bar>baz&nbsp;</foo>"
+            -}
+        it "void tags" $
+            X.parseLBS_ X.def "<foo><bar><img/>foo</bar></foo>" @=?
+            H.parseLBS        "<foo><bar><img>foo</foo>"
