diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Michael B. Gale
+
+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/c14n.cabal b/c14n.cabal
new file mode 100644
--- /dev/null
+++ b/c14n.cabal
@@ -0,0 +1,48 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 27cf335f30aa9127c857b841734dd2c389fc6f511adb198dfcf2052f5d9150fb
+
+name:           c14n
+version:        0.1.0.0
+synopsis:       Bindings to the c14n implementation in libxml.
+description:    Please see the README on GitHub at <https://github.com/mbg/c14n#readme>
+category:       XML
+homepage:       https://github.com/mbg/c14n#readme
+bug-reports:    https://github.com/mbg/c14n/issues
+author:         Michael B. Gale
+maintainer:     m.gale@warwick.ac.uk
+copyright:      Copyright (c) Michael B. Gale
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+
+source-repository head
+  type: git
+  location: https://github.com/mbg/c14n
+
+library
+  exposed-modules:
+      Text.XML.C14N
+      Text.XML.C14N.LibXML
+  other-modules:
+      Paths_c14n
+  hs-source-dirs:
+      src
+  ghc-options: -W
+  c-sources:
+      cbits/libxml.c
+  extra-libraries:
+      xml2
+  build-depends:
+      base >=4.8 && <5
+    , bytestring
+  if os(darwin)
+    include-dirs:
+        /usr/local/opt/libxml2/include/libxml2
+    extra-lib-dirs:
+        /usr/local/opt/libxml2/lib
+  default-language: Haskell2010
diff --git a/cbits/libxml.c b/cbits/libxml.c
new file mode 100644
--- /dev/null
+++ b/cbits/libxml.c
@@ -0,0 +1,5 @@
+#include <libxml/globals.h>
+
+void freeXml(void* p) {
+    xmlFree(p);
+}
diff --git a/src/Text/XML/C14N.hs b/src/Text/XML/C14N.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/XML/C14N.hs
@@ -0,0 +1,146 @@
+--------------------------------------------------------------------------------
+-- Haskell bindings for c14n implementation in libxml                         --
+--------------------------------------------------------------------------------
+-- This source code is licensed under the MIT license found in the LICENSE    --
+-- file in the root directory of this source tree.                            --
+--------------------------------------------------------------------------------
+
+-- | Provides a mid-level interface to libxml's implementation of c14n, i.e. 
+-- XML canonicalisation. 
+module Text.XML.C14N ( 
+    -- * Canonicalisation
+    c14n_1_0,
+    c14n_exclusive_1_0,
+    c14n_1_1,
+    c14n,
+
+    -- * Parsing
+    xml_opt_recover, 
+    xml_opt_noent,
+    xml_opt_dtdload,
+    xml_opt_dtdattr,
+    xml_opt_dtdvalid,
+    xml_opt_noerror,
+    xml_opt_nowarning,
+    xml_opt_pedantic,
+    xml_opt_noblanks,
+    xml_opt_sax1,
+    xml_opt_xinclude,
+    xml_opt_nonet,
+    xml_opt_nodict,
+    xml_opt_nsclean,
+    xml_opt_nocdata,
+    xml_opt_noxincnode,
+    xml_opt_compact,
+    xml_opt_old10,
+    xml_opt_nobasefix,
+    xml_opt_huge,
+    xml_opt_oldsax,
+    xml_opt_ignore_env,
+    xml_opt_big_lines,
+    parseXml
+) where 
+
+--------------------------------------------------------------------------------
+
+import Control.Exception
+
+import Data.Bits
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Unsafe as BS
+
+import Text.XML.C14N.LibXML 
+
+import Foreign.Ptr
+import Foreign.ForeignPtr
+import Foreign.Marshal
+import Foreign.Storable
+import Foreign.C.Error
+import Foreign.C.Types
+
+--------------------------------------------------------------------------------
+
+-- | 'parseXml' @parseOpts text@ parses @text@ into an XML document using 
+-- libxml according to options given by @parseOpts@.
+parseXml :: [CInt] -> BS.ByteString -> IO (ForeignPtr LibXMLDoc) 
+parseXml opts bin = newForeignPtr xmlFreeDoc =<< 
+    (BS.unsafeUseAsCStringLen bin $ \(ptr, len) ->  
+        throwErrnoIfNull "xmlReadMemory" $ xmlReadMemory 
+            ptr (fromIntegral len) nullPtr nullPtr (foldl (.|.) 0 opts))
+
+-- | 'withXmlXPathNodeList' @docPtr xPathLocation continuation@ evaluates the
+-- XPath location path given by @xPathLocation@ in the document context 
+-- pointed at by @docPtr@ and calls @continuation@ with the result.
+withXmlXPathNodeList :: Ptr LibXMLDoc 
+                     -> BS.ByteString 
+                     -> (Ptr LibXMLNodeSet -> IO a) 
+                     -> IO a
+withXmlXPathNodeList docPtr expr cont = 
+    -- initialise a new XPath context, run the continuation with the context
+    -- as argument, and then free up the context again afterwards
+    bracket (xmlXPathNewContext docPtr) xmlXPathFreeContext $ \ctx -> 
+    -- get a C string pointer for the XPath location path
+    BS.unsafeUseAsCString expr $ \strPtr ->
+    -- evaluate the XPath location path and free up the resulting object
+    -- after the continuation is finished; see 
+    -- http://xmlsoft.org/html/libxml-xpath.html#xmlXPathEval
+    bracket
+        ( throwErrnoIfNull "xmlXPathEval" $ 
+            xmlXPathEval (castPtr strPtr) ctx
+        )
+        xmlXPathFreeObject 
+        -- the XPath object structure contains the node set pointer
+        -- at offset 8; see 
+        -- http://xmlsoft.org/html/libxml-xpath.html#xmlXPathObject
+        $ \a -> peekByteOff a 8 >>= cont
+
+-- | 'c14n' @parseOpts mode nsPrefixes keepComments xPathLocation input@ 
+-- canonicalises the document given by @input@, which is parsed using options
+-- specified by @parseOpts@. The @mode@ argument deteremines the 
+-- canonicalisation mode to use. @nsPrefixes@ gives a (potentially empty)
+-- list of namespace prefixes which is used when @mode@ is 
+-- 'c14n_exclusive_1_0'. If @keepComments@ is 'True', all comments are kept
+-- in the output. @xPathLocation@ is used to select a set of nodes that should
+-- be included in the canonicalised result.
+c14n :: [CInt]
+     -> CInt 
+     -> [BS.ByteString] 
+     -> Bool 
+     -> Maybe BS.ByteString 
+     -> BS.ByteString 
+     -> IO BS.ByteString
+c14n opts mode nsPrefixes keepComments xpath bin = 
+    -- parse the input xml
+    parseXml opts bin >>= \docPtr ->
+    -- wrap the pointer we got in a foreign pointer
+    withForeignPtr docPtr $ \ptr -> 
+    -- convert the namespace prefixes into C strings
+    withMany BS.unsafeUseAsCString nsPrefixes $ \inclPtr ->
+    -- turn the Haskell list of C strings into a C array,
+    -- terminated by NULL
+    withArray0 nullPtr inclPtr $ \arrayPtr -> 
+    -- get a pointer to the node set 
+    maybeWith (withXmlXPathNodeList ptr) xpath $ \nsPtr ->
+    -- allocate some memory for a pointer to the results
+    alloca $ \outPtr -> do
+        -- convert the option determining whether to keep comments from a 
+        -- Haskell boolean to a CInt
+        let commentsOpt = fromIntegral (fromEnum keepComments)
+        -- cast from CChar pointers to whatever LibXMLChar is (e.g. Word8)
+        let prefixesPtr :: Ptr (Ptr LibXMLChar)
+            prefixesPtr = castPtr arrayPtr
+
+        -- run the canonicalisation function on the document;
+        -- this function returns the number of bytes that were written
+        -- to outPtr or a negative value if this fails
+        numBytes <- throwErrnoIf (<0) "xmlC14NDocDumpMemory" $
+            xmlC14NDocDumpMemory ptr nsPtr mode prefixesPtr commentsOpt outPtr 
+
+        -- dereference the results pointer 
+        ptrPtr <- peek outPtr
+
+        -- construct a ByteString from the C string and return it
+        BS.unsafePackCStringFinalizer 
+            ptrPtr (fromIntegral numBytes) (freeXml ptrPtr)
+
+--------------------------------------------------------------------------------
diff --git a/src/Text/XML/C14N/LibXML.hsc b/src/Text/XML/C14N/LibXML.hsc
new file mode 100644
--- /dev/null
+++ b/src/Text/XML/C14N/LibXML.hsc
@@ -0,0 +1,251 @@
+--------------------------------------------------------------------------------
+-- Haskell bindings for c14n implementation in libxml                         --
+--------------------------------------------------------------------------------
+-- This source code is licensed under the MIT license found in the LICENSE    --
+-- file in the root directory of this source tree.                            --
+--------------------------------------------------------------------------------
+
+-- | Bindings to libxml types and functions required for the c14n 
+-- implementation. See http://xmlsoft.org/html/libxml-c14n.html
+module Text.XML.C14N.LibXML (
+    -- * libxml2 types
+    LibXMLDoc,
+    LibXMLNodeSet,
+    LibXMLChar,
+    LibXMLXPathCtx,
+    LibXMLXPathObj,
+
+    -- * Memory-related functions
+    freeXml,
+
+    -- * Parsing
+    -- See http://xmlsoft.org/html/libxml-parser.html
+    xml_opt_recover, 
+    xml_opt_noent,
+    xml_opt_dtdload,
+    xml_opt_dtdattr,
+    xml_opt_dtdvalid,
+    xml_opt_noerror,
+    xml_opt_nowarning,
+    xml_opt_pedantic,
+    xml_opt_noblanks,
+    xml_opt_sax1,
+    xml_opt_xinclude,
+    xml_opt_nonet,
+    xml_opt_nodict,
+    xml_opt_nsclean,
+    xml_opt_nocdata,
+    xml_opt_noxincnode,
+    xml_opt_compact,
+    xml_opt_old10,
+    xml_opt_nobasefix,
+    xml_opt_huge,
+    xml_opt_oldsax,
+    xml_opt_ignore_env,
+    xml_opt_big_lines,
+    xmlReadMemory,
+    xmlFreeDoc,
+
+    -- * XML canonicalisation
+    -- See http://xmlsoft.org/html/libxml-c14n.html
+    c14n_1_0,
+    c14n_exclusive_1_0,
+    c14n_1_1,
+    xmlC14NDocDumpMemory,
+
+    -- * XPath
+    -- See http://xmlsoft.org/html/libxml-xpath.html
+    xmlXPathNewContext,
+    xmlXPathFreeContext,
+    xmlXPathEval,
+    xmlXPathFreeObject
+) where 
+
+--------------------------------------------------------------------------------
+
+#include <libxml/parser.h>
+#include <libxml/c14n.h>
+
+import Data.Word
+
+import Foreign.Ptr
+import Foreign.C.String
+import Foreign.C.Types
+
+--------------------------------------------------------------------------------
+
+-- | Original C14N 1.0 specification.
+c14n_1_0 :: CInt 
+c14n_1_0 = #{const XML_C14N_1_0}
+
+-- | Exclusive C14N 1.0 specification.
+c14n_exclusive_1_0 :: CInt 
+c14n_exclusive_1_0 = #{const XML_C14N_EXCLUSIVE_1_0}
+
+-- | C14N 1.1 specification.
+c14n_1_1 :: CInt 
+c14n_1_1 = #{const XML_C14N_1_1}
+
+--------------------------------------------------------------------------------
+
+-- | Recover on errors.
+xml_opt_recover :: CInt 
+xml_opt_recover = #{const XML_PARSE_RECOVER}
+
+-- | Substitute entities.
+xml_opt_noent :: CInt
+xml_opt_noent = #{const XML_PARSE_NOENT}
+
+-- | Load the external subset.
+xml_opt_dtdload :: CInt 
+xml_opt_dtdload = #{const XML_PARSE_DTDLOAD}
+
+-- | Default DTD attributes.
+xml_opt_dtdattr :: CInt 
+xml_opt_dtdattr = #{const XML_PARSE_DTDATTR}
+
+-- | Validate with the DTD.
+xml_opt_dtdvalid :: CInt 
+xml_opt_dtdvalid = #{const XML_PARSE_DTDVALID}
+
+-- | Suppress error reports.
+xml_opt_noerror :: CInt 
+xml_opt_noerror = #{const XML_PARSE_NOERROR}
+
+-- | Suppress warning reports.
+xml_opt_nowarning :: CInt 
+xml_opt_nowarning = #{const XML_PARSE_NOWARNING}
+
+-- | Pedantic error reporting.
+xml_opt_pedantic :: CInt 
+xml_opt_pedantic = #{const XML_PARSE_PEDANTIC}
+
+-- | Remove blank nodes.
+xml_opt_noblanks :: CInt 
+xml_opt_noblanks = #{const XML_PARSE_NOBLANKS}
+
+-- | Use the SAX1 interface internally.
+xml_opt_sax1 :: CInt 
+xml_opt_sax1 = #{const XML_PARSE_SAX1}
+
+-- | Implement XInclude substitution.
+xml_opt_xinclude :: CInt 
+xml_opt_xinclude = #{const XML_PARSE_XINCLUDE}
+
+-- | Forbid network access.
+xml_opt_nonet :: CInt
+xml_opt_nonet = #{const XML_PARSE_NONET}
+
+-- | Do not reuse the context dictionary.
+xml_opt_nodict :: CInt
+xml_opt_nodict = #{const XML_PARSE_NODICT}
+
+-- | Remove redundant namespaces declarations.
+xml_opt_nsclean :: CInt
+xml_opt_nsclean = #{const XML_PARSE_NSCLEAN}
+
+-- | Merge CDATA as text nodes.
+xml_opt_nocdata :: CInt
+xml_opt_nocdata = #{const XML_PARSE_NOCDATA}
+
+-- | Do not generate XINCLUDE START/END nodes.
+xml_opt_noxincnode :: CInt
+xml_opt_noxincnode = #{const XML_PARSE_NOXINCNODE}
+
+-- | Compact small text nodes; no modification of the tree allowed afterwards
+-- (will probably crash if you try to modify the tree)
+xml_opt_compact :: CInt
+xml_opt_compact = #{const XML_PARSE_COMPACT}
+
+-- | Parse using XML-1.0 before update 5.
+xml_opt_old10 :: CInt
+xml_opt_old10 = #{const XML_PARSE_OLD10}
+
+-- | Do not fixup XINCLUDE xml:base uris.
+xml_opt_nobasefix :: CInt
+xml_opt_nobasefix = #{const XML_PARSE_NOBASEFIX}
+
+-- | Relax any hardcoded limit from the parser.
+xml_opt_huge :: CInt
+xml_opt_huge = #{const XML_PARSE_HUGE}
+
+-- | Parse using SAX2 interface before 2.7.0.
+xml_opt_oldsax :: CInt
+xml_opt_oldsax = #{const XML_PARSE_OLDSAX}
+
+-- | Ignore internal document encoding hint.
+xml_opt_ignore_env :: CInt
+xml_opt_ignore_env = #{const XML_PARSE_IGNORE_ENC}
+
+-- | Store big lines numbers in text PSVI field.
+xml_opt_big_lines :: CInt
+xml_opt_big_lines = #{const XML_PARSE_BIG_LINES}
+
+--------------------------------------------------------------------------------
+
+-- | XML documents
+data LibXMLDoc 
+
+-- | XML node sets
+data LibXMLNodeSet
+
+-- | XML strings
+type LibXMLChar = #type xmlChar
+
+-- | XML XPath contexts
+data LibXMLXPathCtx
+
+-- | XML XPath objects
+data LibXMLXPathObj
+
+-- | Free an XML object.
+foreign import ccall unsafe "freeXml"
+    freeXml :: Ptr a -> IO ()
+
+-- | Free an XML document.
+foreign import ccall unsafe "libxml/tree.h &xmlFreeDoc"
+    xmlFreeDoc :: FunPtr ((Ptr LibXMLDoc) -> IO ())
+
+-- | Parses an XML document from a textual representation held in memory.
+foreign import ccall unsafe "libxml/parser.h xmlReadMemory"
+    xmlReadMemory :: CString 
+                  -> CInt 
+                  -> CString 
+                  -> CString 
+                  -> CInt 
+                  -> IO (Ptr LibXMLDoc)
+
+-- | Writes the canonicalised representation of an XML document to memory.
+foreign import ccall unsafe "libxml/c14n.h xmlC14NDocDumpMemory"
+  xmlC14NDocDumpMemory :: Ptr LibXMLDoc -- ^ The XML document to canonicalise.
+                       -> Ptr LibXMLNodeSet -- ^ The nodes set to be included
+                                            -- in the canonicalised output.
+                       -> CInt -- ^ The canonicalisation mode.
+                       -> Ptr (Ptr LibXMLChar) -- ^ A list of inclusive 
+                                               -- namespace prefixes
+                       -> CInt -- ^ A boolean value indicating whether comments
+                               -- should be included in the result or not.
+                       -> Ptr (Ptr LibXMLChar) -- ^ A memory address to which
+                                               -- the output should be written.
+                       -> IO CInt
+
+-- | Creates a new XPath context for the given document.
+foreign import ccall unsafe "libxml/xpath.h xmlXPathNewContext"
+    xmlXPathNewContext :: Ptr LibXMLDoc -> IO (Ptr LibXMLXPathCtx)
+
+-- | Frees up an 'LibXMLXPathCtx' context.
+foreign import ccall unsafe "libxml/xpath.h xmlXPathFreeContext"
+    xmlXPathFreeContext :: Ptr LibXMLXPathCtx -> IO ()
+
+-- | 'xmlXPathEval' @pathPtr ctxPtr@ evaluates the XPath location path
+-- pointed at by @pathPtr@ in the XPath context pointed at by @ctxPtr@. 
+foreign import ccall unsafe "libxml/xpath.h xmlXPathEval"
+    xmlXPathEval :: Ptr LibXMLChar 
+                 -> Ptr LibXMLXPathCtx 
+                 -> IO (Ptr LibXMLXPathObj)
+
+-- | Free up an 'LibXMLXPathObj' object.
+foreign import ccall unsafe "libxml/xpath.h xmlXPathFreeObject"
+    xmlXPathFreeObject :: Ptr LibXMLXPathObj -> IO ()
+
+--------------------------------------------------------------------------------
