data-pdf-fieldreader 0.1.0.0 → 0.1.1.0
raw patch · 4 files changed
+66/−22 lines, 4 filesdep +megaparsecPVP ok
version bump matches the API change (PVP)
Dependencies added: megaparsec
API changes (from Hackage documentation)
+ Data.Pdf.FieldReader: instance GHC.Classes.Eq Data.Pdf.FieldReader.FieldPart
+ Data.Pdf.FieldReader: instance GHC.Show.Show Data.Pdf.FieldReader.FieldPart
Files
- CHANGELOG.md +4/−0
- README.md +3/−1
- data-pdf-fieldreader.cabal +3/−1
- src/Data/Pdf/FieldReader.hs +56/−20
CHANGELOG.md view
@@ -6,4 +6,8 @@ * Initially created. +## 0.1.1.0++* Updated to read hex-encoded and multiline field values. This required changing the parser to megaparsec.+ [1]: https://pvp.haskell.org
README.md view
@@ -1,8 +1,10 @@ # Data.Pdf.FieldReader -[](https://hackage.haskell.org/package/pdfreader)+[](https://hackage.haskell.org/package/data-pdf-fieldreader-0.1.1.0) [](LICENSE) This is a minimal library for reading the data in form fields out of a PDF document.++Updated to be able to read hex-encoded field values, and multiline field values. A trivial example of how to use this library can be found in `app/Main.hs` in the source repository.
data-pdf-fieldreader.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: data-pdf-fieldreader-version: 0.1.0.0+version: 0.1.1.0 description: Simple function to extract PDF form field values from a PDF file. synopsis: Read PDF form fields category: Data@@ -39,6 +39,7 @@ default-language: Haskell2010 default-extensions: ConstraintKinds DeriveGeneric+ DerivingStrategies GeneralizedNewtypeDeriving InstanceSigs KindSignatures@@ -55,6 +56,7 @@ import: common-options hs-source-dirs: src exposed-modules: Data.Pdf.FieldReader+ build-depends: megaparsec >= 9.1.0 && < 9.2 executable pdfreader import: common-options
src/Data/Pdf/FieldReader.hs view
@@ -15,12 +15,16 @@ readPdfFields ) where -import Prelude hiding (drop, init, lines) import Data.ByteString (ByteString)-import Data.ByteString.Char8 (unpack)+import Data.Char (chr, digitToInt)+import Data.Functor ((<&>)) import Data.List (foldl')-import Data.Map (Map, fromList)-import Data.Text (Text, drop, init, isPrefixOf, lines, pack, replace, strip)+import qualified Data.Map as M+import qualified Data.Text as T+import Data.Text (Text)+import Data.Text.Encoding (decodeLatin1)+import Data.Void (Void)+import Text.Megaparsec (Parsec, (<|>), anySingle, chunk, many, parseMaybe, takeWhileP) -- $fileDataParser -- @@ -36,20 +40,52 @@ -- > let ys = readPdfFields xs -- > print ys --- | Read fields from file data-readPdfFields :: ByteString -> Map Text Text-readPdfFields = fromList . snd . foldl' f (Nothing, []) . lines . pack . unpack+-- | Read fields from PDF file data++readPdfFields :: ByteString -> M.Map Text Text+readPdfFields = maybe M.empty collate . parseMaybe pdfFieldParser . decodeLatin1++type Parser = Parsec Void Text+data FieldPart + = FieldName Text+ | FieldValue Text+ | FieldNothing+ deriving stock (Show, Eq)++collate :: [FieldPart] -> M.Map Text Text+collate = snd . foldl' f (Nothing, M.empty) where- f (Nothing, b) x | isFldName x = (Just (fmtFldName x), b) - | otherwise = (Nothing, b)- f ((Just n), b) x | isFldName x = (Just (fmtFldName x), b)- | isFldValue x = (Nothing, (n, (fmtFldValue x)) : b)- | otherwise = ((Just n), b)- isFldName = isPrefixOf "/T("- fmtFldName = stripBrackets- isFldValue = isPrefixOf "/V("- fmtFldValue = unescape . stripBrackets- stripBrackets = init . drop 3 . strip- unescape xs = foldr (\(a, b) c -> replace a b c) xs escPairs- escPairs = [("\\n", "\n"), ("\\(", "("), ("\\)", ")")]- + f (Nothing, m) (FieldName n) = (Just n, m)+ f (Nothing, m) _ = (Nothing, m)+ f (Just n, m) (FieldValue v) = (Nothing, M.insert n v m) + f (Just n, m) _ = (Just n, m) ++pdfFieldParser :: Parser [FieldPart]+pdfFieldParser = many (pFieldName <|> pFieldValue <|> pFieldHexValue <|> anythingElse)++anythingElse :: Parser FieldPart+anythingElse = anySingle >> pure FieldNothing++pFieldName :: Parser FieldPart+pFieldName = getBetween "T(" ")" <&> FieldName++pFieldValue :: Parser FieldPart+pFieldValue = getBetween "V(" ")" <&> FieldValue++pFieldHexValue :: Parser FieldPart+pFieldHexValue = getBetween "V<" ">" <&> FieldValue . decodeHexField++getBetween :: Text -> Text -> Parser Text+getBetween x y = chunk x >> takeWhileP Nothing (/= T.head y) >>= \z -> chunk y >> pure z++decodeHexField :: Text -> Text+decodeHexField xs = if head ys /= "FEFF" then "" else T.pack (map (chr . decodeHex) (tail ys))+ where+ ys = splitBy 4 $ T.concat $ T.lines xs++splitBy :: Int -> Text -> [Text]+splitBy _ "" = []+splitBy x s = T.take x s : splitBy x (T.drop x s)++decodeHex :: Text -> Int+decodeHex = foldl' (\b a -> b * 16 + digitToInt a) 0 . T.unpack