pdftotext 0.0.2.0 → 0.1.0.0
raw patch · 7 files changed
+263/−40 lines, 7 filesdep +aesondep +ansi-wl-pprintdep +optparse-applicativedep −xml-conduitnew-component:exe:pdftotext.hsPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, ansi-wl-pprint, optparse-applicative, range
Dependencies removed: xml-conduit
API changes (from Hackage documentation)
+ Pdftotext.Internal: instance GHC.Generics.Generic Pdftotext.Internal.Properties
Files
- CHANGELOG.md +5/−0
- README.md +30/−3
- cbits/poppler.cc +30/−0
- cli/Cli.hs +178/−0
- pdftotext.cabal +14/−8
- src/Pdftotext.hs +0/−4
- src/Pdftotext/Internal.hs +6/−25
CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for pdftotext +## 0.1.0.0 -- 2020-06-18++* Added executable `pdftotext.hs`+* Removed `xml-conduit` flag, it was bad idea, sorry for that+ ## 0.0.2.0 -- 2020-06-11 * Added PDF document properties (author, title etc.)
README.md view
@@ -14,11 +14,38 @@ T.putStrLn $ pdftotext Physical pdf ``` -## Flags+## Executable -### `xml-conduit`+`pdftotext` comes with executable program `pdftotext.hs` which can print text extracted from PDF and basic information from the document. -`pdftotext` can extract properties from PDF document. One of them is metadata which is in form of XML document. If `xml-conduit` flag is set then the metadata is parsed using `xml-conduit` package, otherwise provided as text.+```shell+$> pdftotext.hs info test/simple.pdf+File : test/simple.pdf+Pages : 4+Properties+ Title : Simple document for testing+ Author : G. Eyaeb+ Subject : Testing+ Creator : pdflatex+ Producer: LaTeX with hyperref+ Keywords: haskell,pdf+```++```shell+$> pdftotext.hs text --pages 1,4 test/simple.pdf+Simple document for testing++ deserve neither+liberty nor safety.+```++See help for more information:++```shell+$> pdftotext.hs --help+$> pdftotext.hs text --help+$> pdftotext.hs info --help+``` ## Internals
cbits/poppler.cc view
@@ -3,6 +3,36 @@ #include <iostream> #include <string.h> +/*++Failed attempt to make poppler quiet+====================================++++#include "Error.h"+#include "GlobalParams.h"++static void errorcb(ErrorCategory, Goffset pos, const char *msg) {+ ...+}++setErrorCallback(errorcb);+globalParams = std::make_unique<GlobalParams>();+globalParams->setErrQuiet(true);++++Do not forget to add poppler to pkgconfig-depends.++Neither setting error callback nor setting global param makes+poppler quiet. It seems that the errQuiet flag is set+and the changes are visible inside poppler.cc, however+they seem not to be visible by poppler library. I probably+do something wrong.++*/+ extern "C" { std::string* to_stdstring(poppler::ustring ust) {
+ cli/Cli.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Main (main) where++import Data.Aeson ((.=), ToJSON (..), defaultOptions, genericToJSON, object)+import Data.Aeson.Text (encodeToLazyText)+import Data.Bifunctor (first)+import Data.List (sort)+import Data.Maybe (catMaybes)+import Data.Range (Range, fromRanges, intersection, lbi)+import Data.Range.Parser (parseRanges)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Data.Text.Lazy.IO as TL+import Options.Applicative+import Pdftotext.Internal+import qualified Text.PrettyPrint.ANSI.Leijen as P++data Command+ = PrintText PrintTextOptions+ | Info InfoOptions++data PrintTextOptions = PrintTextOptions+ { prtPages :: [Range Int],+ prtOutfile :: Maybe FilePath,+ prtSeparate :: Bool,+ prtColor :: Bool,+ prtViewer :: Bool,+ prtFile :: FilePath+ }+ deriving (Show)++data InfoFormat = JsonFormat | PlainFormat deriving (Eq, Show)++data InfoOptions = InfoOptions+ { infFormat :: InfoFormat,+ infFile :: FilePath+ }+ deriving (Show)++data Information = Information+ { iProperties :: Properties,+ iFile :: FilePath,+ iPages :: Int+ }+ deriving (Show)++instance ToJSON Information where+ toJSON Information {..} =+ object+ [ "file" .= iFile,+ "pages" .= iPages,+ "properties" .= genericToJSON defaultOptions iProperties+ ]++main :: IO ()+main =+ customExecParser+ (prefs $ showHelpOnEmpty <> showHelpOnError)+ ( info+ (commandParser <**> helper)+ (fullDesc <> progDesc "Extract text from PDF")+ )+ >>= \case+ PrintText opts -> printText opts+ Info opts -> printInfo opts++commandParser :: Parser Command+commandParser =+ hsubparser+ ( command "text" (info printOptions (progDesc "Print extracted text" <> footer "RANGE: -3,5,7-12,15,20-"))+ <> command "info" (info infoOptions (progDesc "Show information about document"))+ )++infoOptions :: Parser Command+infoOptions =+ fmap Info $+ InfoOptions+ <$> option+ format+ ( long "format"+ <> short 'f'+ <> help "Output format (plain, json)"+ <> value PlainFormat+ <> completeWith ["plain", "json"]+ )+ <*> strArgument+ ( metavar "FILE"+ <> help "PDF file"+ <> completer (bashCompleter "file")+ )+ where+ format =+ eitherReader \case+ "json" -> Right JsonFormat+ "plain" -> Right PlainFormat+ f -> Left $ f ++ " is not a valid output format, use one of: plain, json"++printOptions :: Parser Command+printOptions =+ fmap PrintText $+ PrintTextOptions+ <$> option+ range+ ( long "pages"+ <> short 'p'+ <> help "Range of pages to process"+ <> metavar "RANGE"+ <> value []+ )+ <*> pure Nothing -- switch (metavar "FILE" <> long "output" <> short 'o' <> help "Write output to file")+ <*> pure False -- switch (long "separate" <> help "Separate pages")+ <*> pure False -- switch (long "color" <> short "c" <> help "Use colors")+ <*> pure False -- switch (long "viewer" <> short "v" <> help "Use internal viewer")+ <*> strArgument+ ( metavar "FILE"+ <> help "PDF file"+ <> completer (bashCompleter "file")+ )+ where+ range = eitherReader (first show . parseRanges)++printText :: PrintTextOptions -> IO ()+printText PrintTextOptions {..} = do+ f <- openFile prtFile+ case f of+ Just d -> do+ pageNo <- pagesTotalIO d+ pages <- mapM (flip pageIO d) (pageList pageNo prtPages)+ txt <- mapM (pageTextIO Physical) (catMaybes pages)+ T.putStrLn (T.concat txt)+ _ -> putStrLn $ prtFile ++ " is not a valid PDF document"++pageList :: Int -> [Range Int] -> [Int]+pageList total [] = [1 .. total]+pageList total ranges =+ sort+ $ filter (<= total)+ $ take total+ $ fromRanges+ $ intersection [lbi 1] ranges++printInfo :: InfoOptions -> IO ()+printInfo InfoOptions {..} = do+ f <- openFile infFile+ case f of+ Just d -> do+ p <- propertiesIO d+ pageno <- pagesTotalIO d+ let i = Information p infFile pageno+ case infFormat of+ JsonFormat -> printInfoJson i+ PlainFormat -> printInfoPlain i+ _ -> putStrLn $ infFile ++ " is not a valid PDF document"++printInfoJson :: Information -> IO ()+printInfoJson p = TL.putStrLn (encodeToLazyText $ toJSON p)++{- ORMOLU_DISABLE -}+printInfoPlain :: Information -> IO ()+printInfoPlain Information{..} =+ P.putDoc $+ P.text "File :" P.<+> P.text iFile P.<$>+ P.text "Pages :" P.<+> P.text (show iPages) P.<$>+ P.text "Properties" P.<$>+ P.indent 2 (+ P.text "Title :" P.<+> P.text (maybe "" T.unpack title) P.<$>+ P.text "Author :" P.<+> P.text (maybe "" T.unpack author) P.<$>+ P.text "Subject :" P.<+> P.text (maybe "" T.unpack subject) P.<$>+ P.text "Creator :" P.<+> P.text (maybe "" T.unpack creator) P.<$>+ P.text "Producer:" P.<+> P.text (maybe "" T.unpack producer) P.<$>+ P.text "Keywords:" P.<+> P.text (maybe "" T.unpack keywords)+ ) P.<> P.hardline+ where Properties{..} = iProperties+{- ORMOLU_ENABLE -}
pdftotext.cabal view
@@ -1,7 +1,7 @@ cabal-version: >=1.10 name: pdftotext-version: 0.0.2.0+version: 0.1.0.0 synopsis: Extracts text from PDF using poppler description: The @pdftotext@ package provides functions for extraction of plain text from PDF documents. It uses C++ library [Poppler](https://poppler.freedesktop.org/), which is required to be installed in the system. Output of Haskell @pdftotext@ library is identical to output of Poppler's tool @pdftotext@. homepage: https://sr.ht/~geyaeb/haskell-pdftotext/@@ -18,10 +18,6 @@ source-repository head type: mercurial location: https://hg.sr.ht/~geyaeb/haskell-pdftotext--flag xml-conduit- description: Parse metadata of PDF document properties using xml-conduit- default: False library exposed-modules: Pdftotext@@ -38,9 +34,19 @@ , cbits/stdstring.cc extra-libraries: stdc++ pkgconfig-depends: poppler-cpp- if flag(xml-conduit)- build-depends: xml-conduit == 1.8.*- cpp-options: -DXMLC++executable pdftotext.hs+ build-depends: base >= 4.11 && < 5+ , aeson >= 1.4 && < 1.6+ , ansi-wl-pprint == 0.6.*+ , optparse-applicative == 0.15.*+ , pdftotext+ , range == 0.3.*+ , text == 1.2.*+ hs-source-dirs: cli+ main-is: Cli.hs+ ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -fhide-source-paths -Wmissing-export-lists -Wpartial-fields+ default-language: Haskell2010 test-suite pdftotext-test default-language: Haskell2010
src/Pdftotext.hs view
@@ -20,10 +20,6 @@ > Just pdf <- openFile "path/to/file.pdf" > T.putStrLn $ pdftotext Physical pdf -=== Flags--* @xml-conduit@ – 'metadata' of PDF document properties is parsed as XML, otherwise remains as text- -} {- ORMOLU_ENABLE -} module Pdftotext
src/Pdftotext/Internal.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE BlockArguments #-}-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-} {- ORMOLU_DISABLE -} {-|@@ -42,34 +42,24 @@ import qualified Data.Text as T import Foreign (ForeignPtr, newForeignPtr, nullPtr, withForeignPtr) import Foreign.C (withCString)+import GHC.Generics import Pdftotext.Foreign -#ifdef XMLC-import qualified Text.XML as X-import qualified Data.Text.Lazy as TL-#endif- newtype Document = Document (ForeignPtr Poppler_Document) -- | Document properties. ----- If flag @xml-conduit@ is set, 'metadata' is of type @Maybe Text.XML.Document@.--- -- @since 0.0.2.0 data Properties = Properties { author :: Maybe T.Text, creator :: Maybe T.Text, keywords :: Maybe T.Text,-#ifdef XMLC- metadata :: Maybe X.Document,-#else metadata :: Maybe T.Text,-#endif producer :: Maybe T.Text, subject :: Maybe T.Text, title :: Maybe T.Text }- deriving (Show)+ deriving (Show, Generic) data Page = Page { -- | Number of this page in original document.@@ -148,6 +138,7 @@ None -> 2 -- | Extract properties from the document.+-- -- @since 0.0.2.0 propertiesIO :: Document -> IO Properties propertiesIO (Document docptr) = withForeignPtr docptr \doc -> do@@ -158,22 +149,12 @@ p <- asText $ ffiDocumentProducer doc s <- asText $ ffiDocumentSubject doc t <- asText $ ffiDocumentTitle doc--#ifdef XMLC- return $ Properties (f a) (f c) (f k) (xml m) (f p) (f s) (f t)- where- xml x =- if T.null x- then Nothing- else either (const Nothing) Just $ X.parseText X.def (TL.fromStrict x)-#else return $ Properties (f a) (f c) (f k) (f m) (f p) (f s) (f t) where-#endif f x = if T.null x- then Nothing- else Just x+ then Nothing+ else Just x -- | Extract text from PDF document with given 'Layout'. pdftotextIO :: Layout -> Document -> IO T.Text