Analyse de fichiers PDF (en particulier avec des tables) avec PDFBox

Je dois parsingr un fichier PDF contenant des données tabulaires. J’utilise PDFBox pour extraire le texte du fichier pour parsingr le résultat (Ssortingng) plus tard. Le problème est que l’extraction de texte ne fonctionne pas comme prévu pour les données tabulaires. Par exemple, j’ai un fichier qui contient une table comme celle-ci (7 colonnes: les deux premières ont toujours des données, une seule colonne Complexité contient des données, une seule colonne Financement contient des données):

+----------------------------------------------------------------+ | AIH | Value | Complexity | Financing | | | | Medium | High | Not applicable | MAC/Other | FAE | +----------------------------------------------------------------+ | xyz | 12.43 | 12.34 | | | 12.34 | | +----------------------------------------------------------------+ | abc | 1.56 | | 1.56 | | | 1.56| +----------------------------------------------------------------+ 

Ensuite, j’utilise PDFBox:

 PDDocument document = PDDocument.load(pathToFile); PDFTextSsortingpper s = new PDFTextSsortingpper(); Ssortingng content = s.getText(document); 

Ces deux lignes de données seraient extraites comme ceci:

 xyz 12.43 12.4312.43 abc 1.56 1.561.56 

Il n’y a pas d’espaces blancs entre les deux derniers chiffres, mais ce n’est pas le plus gros problème. Le problème est que je ne sais pas ce que signifient les deux derniers nombres: Moyen, Élevé, Non applicable? MAC / Autre, FAE? Je n’ai pas la relation entre les nombres et leurs colonnes.

Il n’est pas obligatoire pour moi d’utiliser la bibliothèque PDFBox, donc une solution utilisant une autre bibliothèque est correcte. Ce que je veux, c’est pouvoir parsingr le fichier et savoir ce que signifie chaque numéro analysé.

Vous devrez concevoir un algorithme pour extraire les données dans un format utilisable. Quelle que soit la bibliothèque PDF que vous utilisez, vous devrez le faire. Les caractères et les graphiques sont dessinés par une série d’opérations de dessin avec état, c’est-à-dire que vous passez à cette position sur l’écran et tracez le glyphe pour le caractère ‘c’.

Je vous suggère d’étendre org.apache.pdfbox.pdfviewer.PDFPageDrawer et de remplacer la méthode strokePath . De là, vous pouvez intercepter les opérations de dessin pour les segments de ligne horizontaux et verticaux et utiliser ces informations pour déterminer les positions des colonnes et des lignes de votre table. Ensuite, il suffit de configurer des régions de texte et de déterminer quels nombres / lettres / caractères sont dessinés dans quelle région. Comme vous connaissez la disposition des régions, vous pourrez déterminer à quelle colonne appartient le texte extrait.

De plus, la raison pour laquelle vous n’avez pas d’espace entre les textes séparés visuellement est que très souvent, le PDF ne dessine pas un espace. Au lieu de cela, la masortingce de texte est mise à jour et une commande de dessin pour «déplacer» est émise pour dessiner le caractère suivant et une «largeur d’espace» à part le dernier.

Bonne chance.

J’avais utilisé de nombreux outils pour extraire la table du fichier pdf mais cela ne fonctionnait pas pour moi.

J’ai donc implémenté mon propre algorithme (son nom est traprange ) pour parsingr les données tabulaires dans les fichiers pdf.

Voici quelques exemples de fichiers pdf et résultats:

  1. Fichier d’entrée: sample-1.pdf , résultat: sample-1.html
  2. Fichier d’entrée: sample-4.pdf , résultat: sample-4.html

Visitez ma page de projet chez traprange .

Il est peut-être trop tard pour ma réponse, mais je pense que ce n’est pas si difficile. Vous pouvez étendre la classe PDFTextSsortingpper et remplacer les méthodes writePage () et processTextPosition (…). Dans votre cas, je suppose que les en-têtes de colonne sont toujours les mêmes. Cela signifie que vous connaissez la coordonnée x de chaque en-tête de colonne et que vous pouvez comparer la coordonnée x des nombres à celles des en-têtes de colonne. Si elles sont suffisamment proches (vous devez tester pour déterminer la distance), vous pouvez alors dire que ce nombre appartient à cette colonne.

Une autre approche serait d’intercepter le vecteur “charactersByArticle” après chaque écriture de page:

 @Override public void writePage() throws IOException { super.writePage(); final Vector> pageText = getCharactersByArticle(); //now you have all the characters on that page //to do what you want with them } 

Connaissant vos colonnes, vous pouvez faire votre comparaison des coordonnées x pour décider à quelle colonne chaque nombre appartient.

La raison pour laquelle vous n’avez pas d’espace entre les chiffres est que vous devez définir la chaîne de séparation des mots.

J’espère que cela est utile pour vous ou pour d’autres qui pourraient essayer des choses similaires.

Vous pouvez extraire du texte par zone dans PDFBox. Voir le fichier d’exemple ExtractByArea.java , dans l’artefact pdfbox-examples si vous utilisez Maven. Un extrait ressemble à

  PDFTextSsortingpperByArea ssortingpper = new PDFTextSsortingpperByArea(); ssortingpper.setSortByPosition( true ); Rectangle rect = new Rectangle( 464, 59, 55, 5); ssortingpper.addRegion( "class1", rect ); ssortingpper.extractRegions( page ); Ssortingng ssortingng = ssortingpper.getTextForRegion( "class1" ); 

Le problème est d’obtenir les coordonnées en premier lieu. J’ai réussi à étendre le TextSsortingpper normal, à TextSsortingpper processTextPosition(TextPosition text) et à imprimer les coordonnées de chaque caractère et à déterminer où elles se trouvent dans le document.

Mais il existe un moyen beaucoup plus simple, du moins si vous utilisez un Mac. Ouvrez le PDF en mode Aperçu, ⌘Il affiche l’Inspecteur, choisissez l’onglet Recadrage et assurez-vous que les unités sont en Points. Dans le menu Outils, sélectionnez Sélection rectangular et sélectionnez la zone d’intérêt. Si vous sélectionnez une zone, l’inspecteur vous montrera les coordonnées que vous pouvez arrondir et insérer dans les arguments du constructeur Rectangle . Il vous suffit de confirmer où l’origine est, en utilisant la première méthode.

J’ai eu un succès décent en analysant des fichiers texte générés par l’utilitaire pdftotext (sudo apt-get install poppler-utils).

 File convertPdf() throws Exception { File pdf = new File("mypdf.pdf"); Ssortingng outfile = "mytxt.txt"; Ssortingng proc = "/usr/bin/pdftotext"; ProcessBuilder pb = new ProcessBuilder(proc,"-layout",pdf.getAbsolutePath(),outfile); Process p = pb.start(); p.waitFor(); return new File(outfile); } 

Il y a PDFLayoutTextSsortingpper qui a été conçu pour conserver le format des données.

Du README:

 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import org.apache.pdfbox.pdfparser.PDFParser; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.util.PDFTextSsortingpper; public class Test { public static void main(Ssortingng[] args) { Ssortingng ssortingng = null; try { PDFParser pdfParser = new PDFParser(new FileInputStream("sample.pdf")); pdfParser.parse(); PDDocument pdDocument = new PDDocument(pdfParser.getDocument()); PDFTextSsortingpper pdfTextSsortingpper = new PDFLayoutTextSsortingpper(); ssortingng = pdfTextSsortingpper.getText(pdDocument); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }; System.out.println(ssortingng); } } 

L’extraction de données à partir de fichiers PDF est inévitablement lourde de problèmes. Les documents sont-ils créés par une sorte de processus automatique? Si c’est le cas, vous pourriez envisager de convertir les fichiers PDF en PostScript non compressé (essayez pdf2ps) et voir si le PostScript contient une sorte de motif régulier que vous pouvez exploiter.

J’ai eu le même problème en lisant le fichier pdf dans lequel les données sont sous forme de tableau. Après une parsing régulière en utilisant PDFBox, chaque ligne a été extraite avec une virgule comme séparateur … perdant la position en colonne. Pour résoudre ce problème, j’ai utilisé PDFTextSsortingpperByArea et en utilisant les coordonnées, j’ai extrait les données colonne par colonne pour chaque ligne. Ceci est fourni à condition que vous ayez un format PDF fixe.

  File file = new File("fileName.pdf"); PDDocument document = PDDocument.load(file); PDFTextSsortingpperByArea ssortingpper = new PDFTextSsortingpperByArea(); ssortingpper.setSortByPosition( true ); Rectangle rect1 = new Rectangle( 50, 140, 60, 20 ); Rectangle rect2 = new Rectangle( 110, 140, 20, 20 ); ssortingpper.addRegion( "row1column1", rect1 ); ssortingpper.addRegion( "row1column2", rect2 ); List allPages = document.getDocumentCatalog().getAllPages(); PDPage firstPage = (PDPage)allPages.get( 2 ); ssortingpper.extractRegions( firstPage ); System.out.println(ssortingpper.getTextForRegion( "row1column1" )); System.out.println(ssortingpper.getTextForRegion( "row1column2" )); 

Puis rang 2 et ainsi de suite …

http://swftools.org/ Ces gars ont un composant pdf2swf. Ils peuvent également afficher des tableaux. Ils donnent aussi la source. Donc, vous pouvez éventuellement vérifier.

Cela fonctionne très bien si le fichier PDF a “table uniquement rectangular” en utilisant pdfbox 2.0.6. Ne fonctionnera pas avec une autre table uniquement Table rectangular.

 import java.io.File; import java.io.IOException; import java.util.ArrayList; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextSsortingpper; import org.apache.pdfbox.text.PDFTextSsortingpperByArea; public class PDFTableExtractor { public static void main(Ssortingng[] args) throws IOException { ArrayList objTableList = readParaFromPDF("C:\\sample1.pdf", 1,1,6); //Enter Filepath, startPage, EndPage, Number of columns in Rectangular table } public static ArrayList readParaFromPDF(Ssortingng pdfPath, int pageNoStart, int pageNoEnd, int noOfColumnsInTable) { ArrayList objArrayList = new ArrayList<>(); try { PDDocument document = PDDocument.load(new File(pdfPath)); document.getClass(); if (!document.isEncrypted()) { PDFTextStripperByArea stripper = new PDFTextStripperByArea(); stripper.setSortByPosition(true); PDFTextStripper tStripper = new PDFTextStripper(); tStripper.setStartPage(pageNoStart); tStripper.setEndPage(pageNoEnd); String pdfFileInText = tStripper.getText(document); // split by whitespace String Documentlines[] = pdfFileInText.split("\\r?\\n"); for (String line : Documentlines) { String lineArr[] = line.split("\\s+"); if (lineArr.length == noOfColumnsInTable) { for (String linedata : lineArr) { System.out.print(linedata + " "); } System.out.println(""); objArrayList.add(lineArr); } } } } catch (Exception e) { System.out.println("Exception " +e); } return objArrayList; } } 

Vous pouvez utiliser la classe PDFTextSsortingpperByArea de PDFTextSsortingpperByArea pour extraire du texte d’une région spécifique d’un document. Vous pouvez tirer parti de cela en identifiant la région de chaque cellule de la table. Cela n’est pas fourni par DrawPrintTextLocations , mais l’exemple de la classe DrawPrintTextLocations montre comment vous pouvez parsingr les boîtes englobantes des caractères individuels dans un document (il serait intéressant d’parsingr les boîtes englobantes de chaînes ou de paragraphes, mais je n’ai pas vu le support dans PDFBox pour cela – voir cette question ). Vous pouvez utiliser cette approche pour regrouper tous les frameworks de sélection afin d’identifier des cellules distinctes d’une table. Pour ce faire, vous pouvez gérer un ensemble de boxes de régions Rectangle2D , puis, pour chaque caractère analysé, rechercher le cadre de sélection du caractère comme dans DrawPrintTextLocations.writeSsortingng(Ssortingng ssortingng, List textPositions) et le fusionner avec le contenu existant.

 Rectangle2D bounds = s.getBounds2D(); // Pad sides to detect almost touching boxes Rectangle2D hitbox = bounds.getBounds2D(); final double dx = 1.0; // This value works for me, feel free to tweak (or add setter) final double dy = 0.000; // Rows of text tend to overlap, so no need to extend hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy); hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy); // Find all overlapping boxes List intersectList = new ArrayList(); for(Rectangle2D box: boxes) { if(box.intersects(hitbox)) { intersectList.add(box); } } // Combine all touching boxes and update for(Rectangle2D box: intersectList) { bounds.add(box); boxes.remove(box); } boxes.add(bounds); 

Vous pouvez ensuite transmettre ces régions à PDFTextSsortingpperByArea .

Vous pouvez également aller plus loin et séparer les composants horizontaux et verticaux de ces régions, et ainsi déduire des régions de toutes les cellules de la table, indépendamment du fait de savoir si elles contiennent ou non du contenu.

J’ai eu la raison d’effectuer ces étapes et j’ai finalement écrit ma propre classe PDFTableSsortingpper en utilisant PDFBox . J’ai partagé mon code sous forme de liste sur GitHub . La méthode main donne un exemple d’utilisation de la classe:

 try (PDDocument document = PDDocument.load(new File(args[0]))) { final double res = 72; // PDF units are at 72 DPI PDFTableSsortingpper ssortingpper = new PDFTableSsortingpper(); ssortingpper.setSortByPosition(true); // Choose a region in which to extract a table (here a 6"wide, 9" high rectangle offset 1" from top left of page) ssortingpper.setRegion(new Rectangle( (int) Math.round(1.0*res), (int) Math.round(1*res), (int) Math.round(6*res), (int) Math.round(9.0*res))); // Repeat for each page of PDF for (int page = 0; page < document.getNumberOfPages(); ++page) { System.out.println("Page " + page); PDPage pdPage = document.getPage(page); stripper.extractTable(pdPage); for(int c=0; c 

Essayez d’utiliser TabulaPDF ( https://github.com/tabulapdf/tabula ). C’est une très bonne bibliothèque pour extraire le contenu de la table du fichier PDF. C’est très comme prévu.

Bonne chance. 🙂

Je ne suis pas familier avec PDFBox, mais vous pouvez essayer de regarder itext . Même si la page d’accueil indique la génération de fichiers PDF, vous pouvez également effectuer des manipulations et des extractions de fichiers PDF. Jetez un coup d’œil et voyez si cela correspond à votre cas d’utilisation.

Qu’en est-il de l’impression sur l’image et de la reconnaissance optique de caractères?

Cela semble terriblement inefficace, mais c’est pratiquement le but même de PDF de rendre le texte inaccessible, vous devez faire ce que vous devez faire.

Pour lire le contenu de la table à partir d’un fichier pdf, il suffit de convertir le fichier pdf en fichier texte en utilisant n’importe quelle API (j’utilise PdfTextExtracter.getTextFromPage () de iText) et lis le fichier txt par votre programme Java .. maintenant après l’avoir lu la tâche principale est terminée .. vous devez filtrer les données de votre besoin. vous pouvez le faire en utilisant continuellement la méthode split de la classe Ssortingng jusqu’à ce que vous trouviez un enregistrement de votre intrest .. voici mon code par lequel j’ai extrait une partie de l’enregistrement par un fichier PDF et l’écrivez dans un fichier .CSV. le fichier est .. http://www.cea.nic.in/reports/monthly/generation_rep/actual/jan13/opm_02.pdf

Code:-

 public static void genrateCsvMonth_Region(Ssortingng pdfpath, Ssortingng csvpath) { try { Ssortingng line = null; // Appending Header in CSV file... BufferedWriter writer1 = new BufferedWriter(new FileWriter(csvpath, true)); writer1.close(); // Checking whether file is empty or not.. BufferedReader br = new BufferedReader(new FileReader(csvpath)); if ((line = br.readLine()) == null) { BufferedWriter writer = new BufferedWriter(new FileWriter( csvpath, true)); writer.append("REGION,"); writer.append("YEAR,"); writer.append("MONTH,"); writer.append("THERMAL,"); writer.append("NUCLEAR,"); writer.append("HYDRO,"); writer.append("TOTAL\n"); writer.close(); } // Reading the pdf file.. PdfReader reader = new PdfReader(pdfpath); BufferedWriter writer = new BufferedWriter(new FileWriter(csvpath, true)); // Extracting records from page into Ssortingng.. Ssortingng page = PdfTextExtractor.getTextFromPage(reader, 1); // Extracting month and Year from Ssortingng.. Ssortingng period1[] = page.split("PEROID"); Ssortingng period2[] = period1[0].split(":"); Ssortingng month[] = period2[1].split("-"); Ssortingng period3[] = month[1].split("ENERGY"); Ssortingng year[] = period3[0].split("VIS"); // Extracting Northen region Ssortingng northen[] = page.split("NORTHEN REGION"); Ssortingng nthermal1[] = northen[0].split("THERMAL"); Ssortingng nthermal2[] = nthermal1[1].split(" "); Ssortingng nnuclear1[] = northen[0].split("NUCLEAR"); Ssortingng nnuclear2[] = nnuclear1[1].split(" "); Ssortingng nhydro1[] = northen[0].split("HYDRO"); Ssortingng nhydro2[] = nhydro1[1].split(" "); Ssortingng ntotal1[] = northen[0].split("TOTAL"); Ssortingng ntotal2[] = ntotal1[1].split(" "); // Appending filtered data into CSV file.. writer.append("NORTHEN" + ","); writer.append(year[0] + ","); writer.append(month[0] + ","); writer.append(nthermal2[4] + ","); writer.append(nnuclear2[4] + ","); writer.append(nhydro2[4] + ","); writer.append(ntotal2[4] + "\n"); // Extracting Western region Ssortingng western[] = page.split("WESTERN"); Ssortingng wthermal1[] = western[1].split("THERMAL"); Ssortingng wthermal2[] = wthermal1[1].split(" "); Ssortingng wnuclear1[] = western[1].split("NUCLEAR"); Ssortingng wnuclear2[] = wnuclear1[1].split(" "); Ssortingng whydro1[] = western[1].split("HYDRO"); Ssortingng whydro2[] = whydro1[1].split(" "); Ssortingng wtotal1[] = western[1].split("TOTAL"); Ssortingng wtotal2[] = wtotal1[1].split(" "); // Appending filtered data into CSV file.. writer.append("WESTERN" + ","); writer.append(year[0] + ","); writer.append(month[0] + ","); writer.append(wthermal2[4] + ","); writer.append(wnuclear2[4] + ","); writer.append(whydro2[4] + ","); writer.append(wtotal2[4] + "\n"); // Extracting Southern Region Ssortingng southern[] = page.split("SOUTHERN"); Ssortingng sthermal1[] = southern[1].split("THERMAL"); Ssortingng sthermal2[] = sthermal1[1].split(" "); Ssortingng snuclear1[] = southern[1].split("NUCLEAR"); Ssortingng snuclear2[] = snuclear1[1].split(" "); Ssortingng shydro1[] = southern[1].split("HYDRO"); Ssortingng shydro2[] = shydro1[1].split(" "); Ssortingng stotal1[] = southern[1].split("TOTAL"); Ssortingng stotal2[] = stotal1[1].split(" "); // Appending filtered data into CSV file.. writer.append("SOUTHERN" + ","); writer.append(year[0] + ","); writer.append(month[0] + ","); writer.append(sthermal2[4] + ","); writer.append(snuclear2[4] + ","); writer.append(shydro2[4] + ","); writer.append(stotal2[4] + "\n"); // Extracting eastern region Ssortingng eastern[] = page.split("EASTERN"); Ssortingng ethermal1[] = eastern[1].split("THERMAL"); Ssortingng ethermal2[] = ethermal1[1].split(" "); Ssortingng ehydro1[] = eastern[1].split("HYDRO"); Ssortingng ehydro2[] = ehydro1[1].split(" "); Ssortingng etotal1[] = eastern[1].split("TOTAL"); Ssortingng etotal2[] = etotal1[1].split(" "); // Appending filtered data into CSV file.. writer.append("EASTERN" + ","); writer.append(year[0] + ","); writer.append(month[0] + ","); writer.append(ethermal2[4] + ","); writer.append(" " + ","); writer.append(ehydro2[4] + ","); writer.append(etotal2[4] + "\n"); // Extracting northernEastern region Ssortingng neestern[] = page.split("NORTH"); Ssortingng nethermal1[] = neestern[2].split("THERMAL"); Ssortingng nethermal2[] = nethermal1[1].split(" "); Ssortingng nehydro1[] = neestern[2].split("HYDRO"); Ssortingng nehydro2[] = nehydro1[1].split(" "); Ssortingng netotal1[] = neestern[2].split("TOTAL"); Ssortingng netotal2[] = netotal1[1].split(" "); writer.append("NORTH EASTERN" + ","); writer.append(year[0] + ","); writer.append(month[0] + ","); writer.append(nethermal2[4] + ","); writer.append(" " + ","); writer.append(nehydro2[4] + ","); writer.append(netotal2[4] + "\n"); writer.close(); } catch (IOException ioe) { ioe.printStackTrace(); } }