packages feed

HsHyperEstraier-0.4: Text/HyperEstraier/Document.hsc

{-# LANGUAGE
    EmptyDataDecls
  , ForeignFunctionInterface
  , OverloadedStrings
  , UnicodeSyntax
  #-}

{-# OPTIONS_HADDOCK prune #-}

-- |An interface to manipulate documents of the HyperEstraier.

module Text.HyperEstraier.Document
    ( -- * Types
      Document
    , DocumentID

    , ESTDOC -- private
    , wrapDoc -- private
    , withDocPtr -- private

      -- * Creating and parsing document
    , newDocument
    , parseDraft

      -- * Setting contents and attributes of document
    , addText
    , addHiddenText
    , setAttribute
    , setURI
    , setKeywords
    , setScore

      -- * Getting contents and attributes of document
    , getId
    , getAttrNames
    , getAttribute
    , getText
    , getURI
    , getKeywords
    , getScore

      -- * Dumping document
    , dumpDraft

      -- * Making snippet of document
    , makeSnippet
    )
    where
import Control.Monad
import Control.Monad.Unicode
import Data.Maybe
import qualified Data.ByteString.Char8 as C8
import qualified Data.Text as T
import Data.Text.Encoding
import qualified Database.QDBM.Cabin.List as CL
import qualified Database.QDBM.Cabin.Map  as CM
import Foreign.C.String
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import Text.HyperEstraier.Utils
import Network.URI
import Prelude hiding (words)
import Prelude.Unicode

-- |'Document' is an opaque object representing a document of
-- HyperEstraier.
newtype Document = Document (ForeignPtr ESTDOC)
data ESTDOC

-- |'DocumentID' is just an alias to 'Prelude.Int'. It represents a
-- document ID.
type DocumentID = Int


foreign import ccall unsafe "estraier.h est_doc_new"
        _new ∷ IO (Ptr ESTDOC)

foreign import ccall unsafe "estraier.h est_doc_new_from_draft"
        _new_from_draft ∷ CString → IO (Ptr ESTDOC)

foreign import ccall unsafe "estraier.h &est_doc_delete"
        _delete ∷ FunPtr (Ptr ESTDOC → IO ())

foreign import ccall unsafe "estraier.h est_doc_add_attr"
        _add_attr ∷ Ptr ESTDOC → CString → CString → IO ()

foreign import ccall unsafe "estraier.h est_doc_add_text"
        _add_text ∷ Ptr ESTDOC → CString → IO ()

foreign import ccall unsafe "estraier.h est_doc_add_hidden_text"
        _add_hidden_text ∷ Ptr ESTDOC → CString → IO ()

foreign import ccall unsafe "estraier.h est_doc_set_keywords"
        _set_keywords ∷ Ptr ESTDOC → Ptr CM.CBMAP → IO ()

foreign import ccall unsafe "estraier.h est_doc_set_score"
        _set_score ∷ Ptr ESTDOC → CInt → IO ()

foreign import ccall unsafe "estraier.h est_doc_id"
        _id ∷ Ptr ESTDOC → IO CInt

foreign import ccall unsafe "estraier.h est_doc_attr_names"
        _attr_names ∷ Ptr ESTDOC → IO (Ptr CL.CBLIST)

foreign import ccall unsafe "estraier.h est_doc_attr"
        _attr ∷ Ptr ESTDOC → CString → IO CString

foreign import ccall unsafe "estraier.h est_doc_cat_texts"
        _cat_texts ∷ Ptr ESTDOC → IO CString

foreign import ccall unsafe "estraier.h est_doc_keywords"
        _keywords ∷ Ptr ESTDOC → IO (Ptr CM.CBMAP)

foreign import ccall unsafe "estraier.h est_doc_score"
        _score ∷ Ptr ESTDOC → IO CInt

foreign import ccall unsafe "estraier.h est_doc_dump_draft"
        _dump_draft ∷ Ptr ESTDOC → IO CString

foreign import ccall unsafe "estraier.h est_doc_make_snippet"
        _make_snippet ∷ Ptr ESTDOC → Ptr CL.CBLIST → CInt → CInt → CInt → IO CString


wrapDoc ∷ Ptr ESTDOC → IO Document
wrapDoc = fmap Document ∘ newForeignPtr _delete


withDocPtr ∷ Document → (Ptr ESTDOC → IO a) → IO a
withDocPtr (Document doc) = withForeignPtr doc

-- |'newDocument' creates an empty document.
newDocument ∷ IO Document
newDocument = _new ≫= wrapDoc

-- |'parseDraft' parses a document in the \"draft\" format.
parseDraft ∷ T.Text → IO Document
parseDraft draft
    = withUTF8CString draft $ \ draftPtr →
      _new_from_draft draftPtr ≫= wrapDoc

-- |Set an attribute value of a document.
setAttribute ∷ Document     -- ^ The document.
             → T.Text       -- ^ An attribute name.
             → Maybe T.Text -- ^ An attribute value. If this is
                            -- 'Prelude.Nothing', the attribute will
                            -- be deleted.
             → IO ()
setAttribute doc name value
    = withDocPtr       doc   $ \ docPtr   →
      withUTF8CString  name  $ \ namePtr  →
      withUTF8CString' value $
      _add_attr docPtr namePtr

-- |Add a block of text to a document.
addText ∷ Document → T.Text → IO ()
addText doc text
    = withDocPtr      doc  $ \ docPtr  →
      withUTF8CString text $
      _add_text docPtr

-- |Add a block of hidden text to a document.
addHiddenText ∷ Document → T.Text → IO ()
addHiddenText doc text
    = withDocPtr      doc  $ \ docPtr  →
      withUTF8CString text $
      _add_hidden_text docPtr

-- |Set an URI of a document. This is a special case of
-- 'setAttribute'.
setURI ∷ Document → Maybe URI → IO ()
setURI doc uri = setAttribute doc "@uri" (fmap uri2str uri)
    where
      uri2str = T.pack ∘ flip (uriToString id) ""

-- |Set keywords of a document.
setKeywords ∷ Document            -- ^ The document.
            → [(T.Text, Integer)] -- ^ A list of @(keyword, score)@.
            → IO ()
setKeywords doc keywords
    = withDocPtr doc    $ \ docPtr →
      withKeywordMapPtr $
      _set_keywords docPtr
    where
      withKeywordMapPtr ∷ (Ptr CM.CBMAP → IO a) → IO a
      withKeywordMapPtr f
          = do m <- CM.fromList $ map encodeKeyword keywords
               CM.withMapPtr m f

      encodeKeyword (word, score)
          = (encodeUtf8 word, C8.pack $ show score)

-- |Set an alternative score of a document.
setScore ∷ Document → Maybe Int → IO ()
setScore doc
    = withDocPtr doc ∘ flip _set_score ∘ fromIntegral ∘ fromMaybe (-1)

-- |Get the ID of document.
getId ∷ Document → IO DocumentID
getId = liftM fromIntegral ∘ flip withDocPtr _id

-- |Get a list of all attribute names in a document.
getAttrNames ∷ Document → IO [T.Text]
getAttrNames doc
    = withDocPtr doc $ \ docPtr →
      _attr_names docPtr
           ≫= CL.wrapList
           ≫= CL.toList
           ≫= return ∘ map decodeUtf8

-- |Get an attribute value of a document.
getAttribute ∷ Document → T.Text → IO (Maybe T.Text)
getAttribute doc name
    = withDocPtr doc $ \ docPtr →
      withUTF8CString name $ \ namePtr →
      do valuePtr <- _attr docPtr namePtr
         if valuePtr == nullPtr then
             return Nothing
           else
             fmap Just (peekUTF8CString valuePtr)

-- |Get the text in a document.
getText ∷ Document → IO T.Text
getText doc
    = withDocPtr doc $ \ docPtr →
      _cat_texts docPtr
           ≫= packMallocUTF8CString

-- |Get the URI of a document.
getURI ∷ Document → IO (Maybe URI)
getURI doc = fmap (fmap parse) (getAttribute doc "@uri")
    where
      parse ∷ T.Text → URI
      parse = fromJust ∘ parseURI ∘ T.unpack

-- |Get the keywords of a document.
getKeywords ∷ Document → IO [(T.Text, Integer)]
getKeywords doc
    = withDocPtr doc $ \ docPtr →
      _keywords docPtr
           ≫= CM.unsafePeekMap
           ≫= CM.toList
           ≫= return ∘ map decodeKeyword
    where
      decodeKeyword (word, score)
          = (decodeUtf8 word, read $ C8.unpack score)

-- |Get an alternative score of a document.
getScore ∷ Document → IO (Maybe Int)
getScore doc
    = withDocPtr doc $ \ docPtr →
      _score docPtr ≫= \ n →
          case n of
            -1 → return Nothing
            _  → return $ Just $ fromIntegral n
            
-- |Dump a document in the \"draft\" format.
dumpDraft ∷ Document → IO T.Text
dumpDraft doc
    = withDocPtr doc $ \ docPtr →
      _dump_draft docPtr ≫= packMallocUTF8CString

-- |Make a snippet from a document.
makeSnippet
    ∷ Document -- ^ The document.
    → [T.Text] -- ^ Words to be highlighted.
    → Int      -- ^ Maximum width of the whole result.
    → Int      -- ^ Width of the heading text to be shown.
    → Int      -- ^ Width of the text surrounding each highlighted words.
    → IO [Either T.Text (T.Text, T.Text)] -- ^ A list of either
                                           -- @('Prelude.Left'
                                           -- non-highlighted text)@
                                           -- or @('Prelude.Right'
                                           -- (highlighted word, its
                                           -- normalized form))@.
makeSnippet doc words wwidth hwidth awidth
    = do wordsList <- CL.fromList $ map encodeUtf8 words
         withDocPtr doc $ \ docPtr →
             CL.withListPtr wordsList $ \ wordsPtr →
             _make_snippet docPtr wordsPtr (fromIntegral wwidth)
                                           (fromIntegral hwidth)
                                           (fromIntegral awidth)
                  ≫= packMallocUTF8CString
                  ≫= return ∘ parseSnippet
    where
      parseSnippet ∷ T.Text → [Either T.Text (T.Text, T.Text)]
      parseSnippet = map parseLine ∘ T.lines

      parseLine ∷ T.Text → Either T.Text (T.Text, T.Text)
      parseLine line
          = case T.break (== '\t') line of
              (x, y) | T.null y  → Left x
                     | otherwise → Right (x, T.tail y)