rakhana-0.1.0.0: Data/Rakhana/Internal/Parsers.hs
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- |
-- Module : Data.Rakhana.Internal.Parsers
-- Copyright : (C) 2014 Yorick Laupa
-- License : (see the file LICENSE)
--
-- Maintainer : Yorick Laupa <yo.eight@gmail.com>
-- Stability : provisional
-- Portability : non-portable
--
-- Base on pdf-toolbox Parser code
--------------------------------------------------------------------------------
module Data.Rakhana.Internal.Parsers where
--------------------------------------------------------------------------------
import Prelude hiding (take)
import Control.Applicative ((<$), (<|>), many)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import Data.Char (digitToInt, isDigit, isHexDigit)
import Data.Map (Map, fromList)
import qualified Data.Vector as V
--------------------------------------------------------------------------------
import Data.Attoparsec.ByteString.Char8 hiding (isDigit)
import Data.Scientific (floatingOrInteger)
--------------------------------------------------------------------------------
import Data.Rakhana.Internal.Types
--------------------------------------------------------------------------------
parseHeader :: Parser Header
parseHeader
= do _ <- string "%PDF-"
majVer <- decimal
_ <- char '.'
minVer <- decimal
return $ makeHeader majVer minVer
--------------------------------------------------------------------------------
startXRef :: Parser Int
startXRef
= do res <- many $
do _ <- manyTill anyChar $ string "startxref"
skipSpace
offset <- decimal
skipSpace
_ <- string "%%EOF"
return offset
case res of
[] -> fail "Trailer not found"
xs -> return $ last xs
--------------------------------------------------------------------------------
tableXRef :: Parser ()
tableXRef
= do _ <- string "xref"
pdfEndOfLine
--------------------------------------------------------------------------------
parseXRef :: Parser XRef
parseXRef
= do skipSpace
tableXRef
h@(s,_) <- parseSubsectionHeader
es <- parseTableEntries s
t <- parseTrailerAfterTable
return $ makeXRef h (fromList es) t
--------------------------------------------------------------------------------
parseSubsectionHeader :: Parser (Int, Int)
parseSubsectionHeader
= do start <- decimal
skipSpace
len <- decimal
pdfEndOfLine
return (start, len)
--------------------------------------------------------------------------------
parseTrailerAfterTable :: Parser Dictionary
parseTrailerAfterTable
= do skipSpace
_ <- string "trailer"
pdfEndOfLine
skipSpace
Dict d <- parseDict
return d
--------------------------------------------------------------------------------
parseTableEntries :: Int -> Parser [(Reference, TableEntry)]
parseTableEntries start
= loop start
where
loop i
= do mT <- optional parseTableEntry
case mT of
Nothing -> return []
Just t
-> let gen = tableEntryGeneration t
off = tableEntryOffset t
ref = (i,gen) in
if (off /= 0)
then fmap ((ref,t):) $ loop (i+1)
else loop (i+1)
--------------------------------------------------------------------------------
parseTableEntry :: Parser TableEntry
parseTableEntry
= do skipSpace
offset <- decimal
skipSpace
gen <- decimal
skipSpace
c <- anyChar
case c of
'n' -> return $ makeTableEntry offset gen False
'f' -> return $ makeTableEntry offset gen True
_ ->
let msg = "error parsing XRef table entry: unknown char: " ++
[c] in
fail msg
--------------------------------------------------------------------------------
parseDict :: Parser Object
parseDict
= do _ <- string "<<"
dict <- many parseKey
skipSpace
_ <- string ">>"
return $ Dict $ fromList dict
--------------------------------------------------------------------------------
parseKey :: Parser (ByteString, Object)
parseKey
= do skipSpace
key <- parseNameString
obj <- parseObject
return (key, obj)
--------------------------------------------------------------------------------
parseName :: Parser Object
parseName = fmap Name parseNameString
--------------------------------------------------------------------------------
parseNumber :: Parser Object
parseNumber = regularNumber <|> irregularNumber
where
toNumber sc
= case floatingOrInteger sc of
Left d -> Number $ Real d
Right n -> Number $ Natural n
regularNumber = fmap toNumber scientific
irregularNumber
= fmap (Number . Real) $ signed $
do _ <- char '.'
s <- takeWhile1 isDigit
let d = "0." ++ B8.unpack s
return $ read d
--------------------------------------------------------------------------------
parseNameString :: Parser ByteString
parseNameString
= do _ <- char '/'
takeWhile1 isRegularChar
where
isRegularChar c = c `notElem` "[]()/<>{}% \n\r"
--------------------------------------------------------------------------------
parseStringBytes :: Parser Object
parseStringBytes
= do _ <- char '('
s <- takeStr (0 :: Int) []
return $ Bytes $ B8.pack s
where
takeStr lvl res
= do c <- anyChar
case c of
'(' -> takeStr (lvl+1) (c:res)
')' | lvl == 0 -> return $ reverse res
| otherwise -> takeStr (lvl-1) (c:res)
'\\'
-> do c' <- anyChar
if c' `elem` "()\\"
then takeStr lvl (c':res)
else
case c' of
'r' -> takeStr lvl ('\r':res)
'n' -> takeStr lvl ('\n':res)
'f' -> takeStr lvl ('\f':res)
'b' -> takeStr lvl ('\b':res)
't' -> takeStr lvl ('\t':res)
'\r' -> takeStr lvl res
_ -> do
c'' <- anyChar
c''' <- anyChar
let i' = charToInt c' * 64
i'' = charToInt c'' * 8
i''' = charToInt c'''
h = toEnum $ i' + i'' + i'''
takeStr lvl (h:res)
_ -> takeStr lvl (c:res)
--------------------------------------------------------------------------------
parseHexBytes :: Parser Object
parseHexBytes
= do _ <- char '<'
s <- many hex
_ <- char '>'
return $ Bytes $ B.pack s
where
hex = do h <- satisfy isHexDigit
l <- satisfy isHexDigit
return $ fromIntegral $ digitToInt h * 16 + digitToInt l
--------------------------------------------------------------------------------
parseBoolean :: Parser Object
parseBoolean = fmap Boolean go
where
go = True <$ string "true" <|>
False <$ string "false"
--------------------------------------------------------------------------------
parseArray :: Parser Object
parseArray
= do _ <- char '['
a <- many parseObject
skipSpace
_ <- char ']'
return $ Array $ V.fromList a
--------------------------------------------------------------------------------
parseRef :: Parser Object
parseRef
= do o <- decimal
skipSpace
g <- decimal
skipSpace
_ <- char 'R'
return $ Ref o g
--------------------------------------------------------------------------------
parseObject :: Parser Object
parseObject = skipSpace >> go
where
go = parseName <|>
parseDict <|>
parseBoolean <|>
parseArray <|>
parseRef <|>
parseHexBytes <|>
parseStringBytes <|>
parseNumber
--------------------------------------------------------------------------------
parseStreamHeader :: Parser ()
parseStreamHeader
= do skipSpace
_ <- string "stream"
skipSpace
return ()
--------------------------------------------------------------------------------
parseIndirectObject :: Parser (Int, Int, Object)
parseIndirectObject
= do skipSpace
idx <- decimal
skipSpace
gen <- decimal
skipSpace
_ <- string "obj"
skipSpace
skipComment
obj <- parseObject
return (idx, gen, obj)
--------------------------------------------------------------------------------
makeXRef :: (Int, Int)
-> Map Reference TableEntry
-> Dictionary
-> XRef
makeXRef header entries dict
= XRef
{ xrefHeader = header
, xrefEntries = entries
, xrefTrailer = dict
}
--------------------------------------------------------------------------------
makeTableEntry :: Integer -> Int -> Bool -> TableEntry
makeTableEntry offset gen free
= TableEntry
{ tableEntryOffset = offset
, tableEntryGeneration = gen
, tableEntryFree = free
}
--------------------------------------------------------------------------------
makeHeader :: Int -> Int -> Header
makeHeader mj mi
= Header
{ headerMaj = mj
, headerMin = mi
}
--------------------------------------------------------------------------------
parseTillStreamData :: Parser ()
parseTillStreamData
= do skipSpace
_ <- string "stream"
pdfEndOfLine
--------------------------------------------------------------------------------
charToInt :: Char -> Int
charToInt c = fromEnum c - 48
--------------------------------------------------------------------------------
pdfEndOfLine :: Parser ()
pdfEndOfLine
= do _ <- many $ char ' '
endOfLine <|> () <$ char '\r'
--------------------------------------------------------------------------------
skipComment :: Parser ()
skipComment
= option () $
do _ <- char '%'
skipWhile (not . isSpace)
--------------------------------------------------------------------------------
parseEndOfObject :: Parser ()
parseEndOfObject
= do skipSpace
_ <- string "endobj"
return ()
--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------
natural :: Monad m => Object -> m Integer
natural (Number (Natural i)) = return i
natural _ = fail "natural"
--------------------------------------------------------------------------------
optional :: Parser a -> Parser (Maybe a)
optional p = fmap Just p <|> return Nothing