Ce projet de parser de fichier PDF permet le découpage de contenu textuel d'un document PDF en paragraphes et tableaux. Le résultat du traitement est un fichier JSON contenant l'ensemble du contenu du document selon l'arborescence :
- document
- pages
- page
- paragraphes
- tables
Ce projet est développé en Scala 2.12.8 et s'appuie sur les librairies :
Ce projet est découpé en parties distinctes correspondant à des préoccupations particulières :
src/main/scala/json
- Les classes "wrapper" des objets json à écrire
src/main/scala/model
- Les classes modélisant les différents éléments de texte :
- caractère
- ligne
- bloc de texte
- paragraphe
- table
src/main/scala/parser
- L'implémentation des algorithmes nécessaires au parsing du PDF :
- Une classe "TextStripper" d'extraction du texte à partir d'un stream PDF (contenu encrypté)
- Une classe d'écriture permettant la reconnaissance des différents éléments textuels et de les convertir au format JSON
La compilation du projet se fait avec l'outil SBT et le plugin sbt-assembly
$ git clone https://github.com/pluzeaux/pdf-parser
$ cd ./pdf-parser
$ sbt assembly
Le fichier jar exécutable se trouve dans le répertoire target du projet.
$ java -jar target/scala-2.12/pdf-parser-assembly-0.1.0-SNAPSHOT.jar <path-to-pdf-directories>
Idéalement l'organisation des fichiers pdf doit se faire selon l'organisation suivante :
/
/Danone
DDR_2018.pdf
DDR_2017.pdf
/Thales
DDR_2018.pdf
DDR_2017.pdf
...
La classe la plus importante est JsonDocumentWriter
, elle porte le coeur de l'algorithme.
En entrée de cette classe nous avons une liste de caractères issue du Stripper
,
chaque caractère est défini par :
ses coordonnées (place dans le document)
x
ety
la police du caractère
sa dimension
height
etwidth
son code unicode
taille de l'espace entre 2 lettres
Nous devons nous limiter uniquement à ces données et ne pas prendre en compte des métadonnées du PDF, car les PDF traités sont de qualité très variable. Ce traitement se voulant générique nous devons prendre en compte le sous-ensemble commun des informations disponibles à tous les PDF.
Le traitement va réaliser un découpage de cette liste de caractères en :
lignes
blocs
paragraphes
tables
suivant certains critères définis par le traitement.
Le caractère est encapsulé par la classe TextPosition
La rupture de ligne est détectée si l'intersection des alignements horizontaux des caractères de la ligne et du caratère suivant est vide.
voir les fonctions within
et overlap
Une ligne tabulée est détectée si l'espace entre deux mots est supérieur à une certaine valeur (taille de l'espace blanc moyen + delta)
voir la classe Tabular
Un paragraphe est détecté si l'espace entre deux lignes est supérieur à une certaine valeur (taille normale de l'espace entre deux lignes + delta)
voir la classe Paragraph
Une table est une liste de lignes tabulées ayant le même nombre de colonnes. Une colonne est le nombre d'espaces de type tabulation (voir Ligne tabulée
ci-dessus)
Attention : Un paragraphe peut être inclus dans une table dans le cas d'entête de ligne d'une table sur plusieurs lignes et ne contenant pas de valeurs dans ses cellules.
voir la fonction filterBlocks
Un bloc encapsule aussi bien un paragraphe qu'une table
Si vous détectez un bug, une piste d'amélioration ou une question sur ce projet vous pouvez me contacter via une « Pull request » GitHub, ou directement par mail Philippe LUZEAUX.
Thanks and enjoy !