diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,35 @@
+Copyright (c) 2004-2006, David Himmelstrup
+All rights reserved.
+
+Redistribution and use in source and binary forms,
+with or without modification, are permitted provided
+that the following conditions are met:
+
+    * Redistributions of source code must retain
+      the above copyright notice, this list of
+      conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce
+      the above copyright notice, this list of
+      conditions and the following disclaimer in the
+      documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of David Himmelstrup nor the
+      names of other contributors may be used to
+      endorse or promote products derived from this
+      software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
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 = defaultMainWithHooks defaultUserHooks
diff --git a/hxweb.cabal b/hxweb.cabal
new file mode 100644
--- /dev/null
+++ b/hxweb.cabal
@@ -0,0 +1,23 @@
+Name: hxweb
+Version: 0.1
+Maintainer: Lemmih (lemmih@gmail.com)
+Author: Lemmih (lemmih@gmail.com)
+Copyright: 2006, Lemmih
+License-File: LICENSE
+License: BSD3
+Category: Web
+Synopsis: Minimal webframework using fastcgi, libxml2 and libxslt.
+Description:
+  Fast template based web-framework using libxslt and fastcgi.
+Build-Depends: base, cgi, fastcgi, mtl, libxml, xslt
+Extensions: NoMonomorphismRestriction OverlappingInstances
+GHC-Options: -fglasgow-exts -fwarn-duplicate-exports -Werror
+             -fwarn-unused-binds -fwarn-unused-imports -fwarn-unused-matches
+Hs-Source-Dirs: src
+GHC-Prof-options: -auto-all
+Exposed-modules:
+  Network.HxWeb
+  Network.HxWeb.Monad
+  Network.HxWeb.Utils
+  Network.HxWeb.XSLT
+  Network.HxWeb.Cache
diff --git a/src/Network/HxWeb.hs b/src/Network/HxWeb.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HxWeb.hs
@@ -0,0 +1,20 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.HxWeb
+-- Copyright   :  (c) David Himmelstrup 2006
+-- License     :  BSD-like
+--
+-- Maintainer  :  lemmih@gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-----------------------------------------------------------------------------
+module Network.HxWeb
+    ( module Network.HxWeb.Monad
+    , module Network.HxWeb.Utils
+    , module Network.HxWeb.XSLT
+    ) where
+
+import Network.HxWeb.Monad
+import Network.HxWeb.Utils
+import Network.HxWeb.XSLT
diff --git a/src/Network/HxWeb/Cache.hs b/src/Network/HxWeb/Cache.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HxWeb/Cache.hs
@@ -0,0 +1,102 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.HxWeb.Catch
+-- Copyright   :  (c) David Himmelstrup 2006
+-- License     :  BSD-like
+--
+-- Maintainer  :  lemmih@gmail.com
+-- Stability   :  provisional
+-- Portability :  non-portable (requires System.Mem.StableName)
+--
+-----------------------------------------------------------------------------
+module Network.HxWeb.Cache
+    ( memoFn'
+    , memoFn
+    , memo
+    , static
+    ) where
+
+import Network.CGI
+
+import Data.IORef            ( IORef, newIORef, readIORef, writeIORef )
+import System.Time           ( ClockTime, getClockTime, addToClockTime, TimeDiff(..) )
+import System.IO.Unsafe      ( unsafePerformIO )
+
+import qualified Data.HashTable as HT
+import System.Mem.StableName
+import Control.Monad
+import Data.Maybe
+
+import Network.HxWeb.Monad   ( WebPage )
+
+{-# NOINLINE memoFn' #-}
+memoFn' :: Eq a => Int -> (a -> WebPage st b) -> (a -> WebPage st b, a -> WebPage st ())
+memoFn' mins webpage
+    = unsafePerformIO $
+      do now <- getClockTime
+         let delay = TimeDiff 0 0 0 0 mins 0 0
+             expire = addToClockTime delay now
+         ht <- HT.new (==) (\a -> unsafePerformIO $
+                                  do stableA <- makeStableName a
+                                     return (HT.hashInt (hashStableName stableA)))
+         ref <- newIORef expire
+         return (worker delay ref ht webpage
+                ,\key -> liftIO $ HT.delete ht key)
+
+{-# INLINE memoFn #-}
+memoFn :: Eq a => Int -> (a -> WebPage st b) -> (a -> WebPage st b)
+memoFn mins page = fst (memoFn' mins page)
+
+worker :: Eq a => TimeDiff -> IORef ClockTime -> HT.HashTable a b -> (a -> WebPage st b)
+       -> (a -> WebPage st b)
+worker delay ref ht webFunc a
+    = do now <- liftIO $ getClockTime
+         expire <- liftIO $ readIORef ref
+         mbB <- liftIO $ HT.lookup ht a
+         case mbB of
+           Just b | now < expire -> return b
+           _otherwise -> do b <- webFunc a
+                            when (isJust mbB) $ liftIO $ writeIORef ref (addToClockTime delay now)
+                            liftIO $ HT.insert ht a b
+                            return b
+
+{-
+  Cache a WebPage for @n@ minutes.
+-}
+{-# NOINLINE memo #-}
+memo :: Int -> WebPage st a -> WebPage st a
+memo mins
+    = unsafePerformIO $
+           do now <- getClockTime
+              let delay = TimeDiff 0 0 0 0 mins 0 0
+                  expire = addToClockTime delay now
+              ref <- newIORef (Nothing,expire)
+              return (applyFn delay ref)
+
+applyFn :: TimeDiff -> IORef (Maybe a,ClockTime) -> WebPage st a -> WebPage st a
+applyFn delay ref webPage
+    = do now <- liftIO $ getClockTime
+         (mbA, expire) <- liftIO $ readIORef ref
+         case mbA of
+           Just a | now < expire -> return a
+           _otherwise -> do a <- webPage
+                            liftIO (writeIORef ref (Just a,addToClockTime delay now))
+                            return a
+
+{-
+  Mark page as static. The WebPage will only
+  be run once.
+-}
+{-# NOINLINE static #-}
+static :: WebPage st a -> WebPage st a
+static
+    = unsafePerformIO $
+         do ref <- newIORef Nothing
+            let loop webPage
+                    = do mbA <- liftIO $ readIORef ref
+                         case mbA of
+                           Just a -> return a
+                           Nothing -> do a <- webPage
+                                         liftIO (writeIORef ref (Just a))
+                                         return a
+            return loop
diff --git a/src/Network/HxWeb/Monad.hs b/src/Network/HxWeb/Monad.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HxWeb/Monad.hs
@@ -0,0 +1,62 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.HxWeb.Monad
+-- Copyright   :  (c) David Himmelstrup 2006
+-- License     :  BSD-like
+--
+-- Maintainer  :  lemmih@gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-----------------------------------------------------------------------------
+module Network.HxWeb.Monad where
+
+import Control.Monad.State
+import Network.CGI
+import Network.CGI.Monad
+
+newtype WebPage st a
+    = WebPage { unWebPage :: StateT st (CGIT IO) (WebResult a) }
+
+data WebResult ret
+    = WebResult ret
+    | Redirect String
+    | Fail String
+      deriving Show
+
+instance Monad (WebPage st) where
+    WebPage g >>= f
+        = WebPage $
+          do g' <- g
+             case g' of
+               Redirect url -> return (Redirect url)
+               Fail msg -> return (Fail msg)
+               WebResult ret -> unWebPage (f ret)
+    fail = WebPage . return . Fail
+    return = WebPage . return . WebResult
+
+instance MonadPlus (WebPage st) where
+    mzero = WebPage $ return (Fail "mzero")
+    mplus a b = WebPage $
+                do a' <- unWebPage a
+                   case a' of
+                     Fail _ -> unWebPage b
+                     _ -> return a'
+
+instance MonadIO (WebPage st) where
+    liftIO io = WebPage (do a <- liftIO io
+                            return (WebResult a))
+
+
+instance MonadState st (WebPage st) where
+    get = WebPage (liftM WebResult get)
+    put s = WebPage (liftM WebResult $ put s)
+
+instance Functor (WebPage st) where
+    fmap fn a = do a' <- a
+                   return (fn a')
+
+instance MonadCGI (WebPage st) where
+    cgiAddHeader h v = WebPage (liftM WebResult $ lift (cgiAddHeader h v))
+    cgiGet fn = WebPage (liftM WebResult $ lift (cgiGet fn))
+
diff --git a/src/Network/HxWeb/Utils.hs b/src/Network/HxWeb/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HxWeb/Utils.hs
@@ -0,0 +1,53 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.HxWeb.Utils
+-- Copyright   :  (c) David Himmelstrup 2006
+-- License     :  BSD-like
+--
+-- Maintainer  :  lemmih@gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-----------------------------------------------------------------------------
+module Network.HxWeb.Utils
+    ( readM       -- (Monad m, Read a) => String -> m a
+    , getInput'   -- String -> WebPage st String
+    , readInput'  -- Read a => String -> WebPage st a
+    , escape      -- String -> WebPage st a
+    , runWebPage  -- st -> WebPage st CGIResult -> CGI CGIResult
+    ) where
+
+import Network.CGI           ( CGI, CGIResult, output, redirect
+                             , getInput )
+import Control.Monad.State   ( evalStateT )
+
+import Network.HxWeb.Monad   ( WebPage(..), WebResult(..) )
+
+readM :: (Monad m,Read a) => String -> m a
+readM str = case reads str of
+              [] -> fail "No parse"
+              [(val,_)] -> return val
+              _ -> fail "Ambiguous parse"
+
+getInput' :: String -> WebPage st String
+getInput' key = do mbVal <- getInput key
+                   case mbVal of
+                     Nothing -> fail $ "Failed to lookup key: " ++ key
+                     Just val -> return val
+
+readInput' :: Read a => String -> WebPage st a
+readInput' key
+    = do val <- getInput' key
+         readM val
+
+escape :: String -> WebPage st a
+escape redirect
+    = WebPage (return (Redirect redirect))
+
+runWebPage :: st -> WebPage st CGIResult -> CGI CGIResult
+runWebPage st (WebPage r)
+    = do ret <- evalStateT r st
+         case ret of
+           WebResult a  -> return a
+           Fail str     -> output $ "Uncaught error: " ++ str
+           Redirect url -> redirect url
diff --git a/src/Network/HxWeb/XSLT.hs b/src/Network/HxWeb/XSLT.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/HxWeb/XSLT.hs
@@ -0,0 +1,52 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.HxWeb.XSLT
+-- Copyright   :  (c) David Himmelstrup 2006
+-- License     :  BSD-like
+--
+-- Maintainer  :  lemmih@gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-----------------------------------------------------------------------------
+module Network.HxWeb.XSLT
+    ( displayXml
+    ) where
+
+import qualified Text.XML.LibXML as XML
+import qualified Text.XML.XSLT as XSLT
+import Text.XML.XSLT (Stylesheet)
+import Text.XML.LibXML (Node)
+
+import qualified Data.ByteString.Lazy as Lazy
+import Data.ByteString (ByteString)
+
+import Data.Maybe
+
+import Network.CGI
+
+import Network.HxWeb.Monad
+
+renderXML :: FilePath -> Node -> IO ByteString
+renderXML path node
+    = do doc <- XML.newDocument "1.0"
+         piNode <- XML.newDocumentPI doc "xml-stylesheet" ("type=\"text/xsl\" href=\""++path++"\"")
+         XML.setDocumentRootElement doc piNode
+         XML.addSibling piNode node
+         XML.documentDumpMemory doc
+
+displayXml :: (FilePath -> WebPage st Stylesheet)
+           -> Bool  -- ^ usexsl. False => transform xml on the server.
+                    -- True => send xml + stylesheet to the client.
+           -> (FilePath, Node) -> WebPage st CGIResult
+displayXml fetchStylesheet usexsl (path, node)
+    = do xml <- liftIO $ renderXML path node
+         if usexsl
+            then do setHeader "Content-type" "text/xml; charset=ISO-8859-1"
+                    outputFPS (Lazy.LPS [xml])
+            else do setHeader "Content-type" "text/html; charset=ISO-8859-1"
+                    sheet <- fetchStylesheet path
+                    doc <- liftIO $ XML.parseMemory xml
+                    res <- liftIO $ XSLT.applyStylesheet sheet (Just doc) []
+                    resBS <- liftIO $ XSLT.saveResultToString res sheet
+                    outputFPS (Lazy.LPS [resBS])
