it-swarm.com.ru

Анализ PDF файлы (особенно с таблицами) с PDFBox

Мне нужно проанализировать файл PDF, который содержит табличные данные. Я использую PDFBox , чтобы извлечь текст файла для последующего анализа результата (String). Проблема в том, что извлечение текста не работает так, как я ожидал для табличных данных. Например, у меня есть файл, который содержит такую ​​таблицу (7 столбцов: первые два всегда имеют данные, только один столбец сложности содержит данные, только один столбец финансирования содержит данные):

+----------------------------------------------------------------+
| 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|
+----------------------------------------------------------------+

Тогда я использую PDFBox:

PDDocument document = PDDocument.load(pathToFile);
PDFTextStripper s = new PDFTextStripper();
String content = s.getText(document);

Эти две строки данных будут извлечены следующим образом:

xyz 12.43 12.4312.43
abc 1.56 1.561.56

Между двумя последними числами нет пробелов, но это не самая большая проблема. Проблема в том, что я не знаю, что означают последние два числа: Среднее, Высокое, Не применимо? MAC/Другое, FAE? У меня нет связи между числами и их столбцами.

Мне не нужно использовать библиотеку PDFBox, поэтому решение, которое использует другую библиотеку, подойдет. Я хочу, чтобы у меня была возможность проанализировать файл и узнать, что означает каждый проанализированный номер.

61
matheus.emm

Вам нужно будет разработать алгоритм для извлечения данных в пригодном для использования формате. Независимо от того, какую библиотеку PDF вы используете, вам придется это сделать. Символы и графика рисуются с помощью ряда операций рисования с сохранением состояния, то есть перемещаются в эту позицию на экране и рисуют глиф для символа 'c'.

Я предлагаю вам расширить org.Apache.pdfbox.pdfviewer.PDFPageDrawer и переопределить метод strokePath. Оттуда вы можете перехватывать операции рисования для горизонтальных и вертикальных отрезков и использовать эту информацию для определения позиций столбцов и строк для вашей таблицы. Тогда просто установить текстовые области и определить, какие цифры/буквы/символы нарисованы в какой области. Так как вы знаете расположение регионов, вы сможете определить, к какому столбцу относится извлеченный текст.

Кроме того, причина, по которой у вас может не быть пробелов между текстом, который визуально разделен, заключается в том, что очень часто пробел не рисуется в PDF. Вместо этого текстовая матрица обновляется и выдается команда рисования для перемещения, чтобы нарисовать следующий символ и «ширину пробела» отдельно от последнего.

Удачи.

18
purecharger

Я использовал много инструментов для извлечения таблицы из PDF-файла, но у меня это не сработало.

Поэтому я реализовал свой собственный алгоритм (его имя traprange) для анализа табличных данных в PDF-файлах. 

Ниже приведены примеры файлов PDF и результаты: 

  1. Входной файл: sample-1.pdf , результат: sample-1.html
  2. Входной файл: sample-4.pdf , результат: sample-4.html

Посетите страницу моего проекта в traprange .

12
Tho

Вы можете извлечь текст по областям в PDFBox. См. Пример файла ExtractByArea.Java, в артефакте pdfbox-examples, если вы используете Maven. Фрагмент выглядит как

   PDFTextStripperByArea stripper = new PDFTextStripperByArea();
   stripper.setSortByPosition( true );
   Rectangle rect = new Rectangle( 464, 59, 55, 5);
   stripper.addRegion( "class1", rect );
   stripper.extractRegions( page );
   String string = stripper.getTextForRegion( "class1" );

Проблема в том, чтобы получить координаты в первую очередь. Мне удалось расширить обычную TextStripper, переопределить processTextPosition(TextPosition text) и распечатать координаты для каждого символа и выяснить, где они находятся в документе.

Но есть гораздо более простой способ, по крайней мере, если вы на Mac. Откройте PDF в окне предварительного просмотра, ⌘I, чтобы показать Инспектора, выберите вкладку «Обрезать» и убедитесь, что единицы измерения находятся в точках, в меню «Инструменты» выберите «Прямоугольный выбор» и выберите интересующую область. Если вы выберете область, инспектор покажет вам координаты, которые вы можете округлить и передать в аргументы конструктора Rectangle. Вам просто нужно подтвердить, где находится Origin, используя первый метод.

10
Emerson Farrugia

Может быть, уже слишком поздно для моего ответа, но я думаю, что это не так сложно. Вы можете расширить класс PDFTextStripper и переопределить методы writePage () и processTextPosition (...). В вашем случае я предполагаю, что заголовки столбцов всегда одинаковы. Это означает, что вы знаете x-координату каждого заголовка столбца и можете сравнить x-координаты чисел с заголовками столбцов. Если они достаточно близки (вам нужно проверить, насколько близко), то вы можете сказать, что это число принадлежит этому столбцу.

Другой подход заключается в перехвате вектора "charactersByArticle" после написания каждой страницы:

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

Зная ваши столбцы, вы можете сравнить x-координаты, чтобы определить, к какому столбцу относится каждое число.

Причина, по которой у вас нет пробелов между числами, заключается в том, что вы должны установить строку разделителя Word.

Я надеюсь, что это полезно для вас или для других, которые могут попробовать похожие вещи.

10
impeto

Есть PDFLayoutTextStripper , который был разработан, чтобы сохранить формат данных.

Из 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.PDFTextStripper;

public class Test {

    public static void main(String[] args) {
        String string = null;
        try {
            PDFParser pdfParser = new PDFParser(new FileInputStream("sample.pdf"));
            pdfParser.parse();
            PDDocument pdDocument = new PDDocument(pdfParser.getDocument());
            PDFTextStripper pdfTextStripper = new PDFLayoutTextStripper();
            string = pdfTextStripper.getText(pdDocument);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        };
        System.out.println(string);
    }
}
7
Matthias Braun

Я добился приличного успеха в разборе текстовых файлов, созданных утилитой pdftotext (Sudo apt-get install poppler-utils).

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

    p.waitFor();

    return new File(outfile);
}
4
scott

У меня была такая же проблема при чтении PDF-файла, в котором данные представлены в табличном формате. После регулярного анализа с использованием PDFBox каждая строка извлекалась с запятой в качестве разделителя ... теряя положение столбца .... Для решения этой проблемы я использовал PDFTextStripperByArea и, используя координаты, извлекал данные столбец за столбцом для каждой строки. Это при условии, что у вас есть фиксированный формат PDF.

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

Затем строка 2 и так далее ...

2
manu

Извлечение данных из PDF неизбежно сопряжено с проблемами. Документы создаются с помощью какого-то автоматического процесса? Если это так, вы можете рассмотреть преобразование PDF-файлов в несжатый PostScript (попробуйте pdf2ps) и посмотреть, содержит ли PostScript какой-то регулярный шаблон, который вы можете использовать.

2
Todd Owen

Попробуйте использовать TabulaPDF ( https://github.com/tabulapdf/tabula ). Это очень хорошая библиотека для извлечения содержимого таблицы из файла PDF. Это очень, как ожидалось.

Удачи. :)

1
SURESH KUMAR S

Вы можете использовать PDFBox's PDFTextStripperByArea class для извлечения текста из определенной области документа. Вы можете опираться на это, указав регион каждой ячейки таблицы. Это не предусмотрено "из коробки", но пример DrawPrintTextLocations class демонстрирует, как вы можете анализировать ограничивающие блоки отдельных символов в документе (было бы здорово проанализировать ограничивающие блоки строк или абзацев, но у меня нет не видел поддержки в PDFBox для этого - см. этот вопрос ). Вы можете использовать этот подход, чтобы сгруппировать все соприкасающиеся ограничивающие рамки, чтобы идентифицировать отдельные ячейки таблицы. Один из способов сделать это - сохранить набор boxes областей Rectangle2D, а затем для каждого анализируемого символа найти ограничивающую рамку символа, как в DrawPrintTextLocations.writeString(String string, List<TextPosition> textPositions), и объединить ее с существующим содержимым.

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<Rectangle2D> intersectList = new ArrayList<Rectangle2D>();
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);

Затем вы можете передать эти регионы PDFTextStripperByArea.

Вы также можете пойти еще дальше и отделить горизонтальные и вертикальные компоненты этих областей, и таким образом вывести области всех ячеек таблицы, независимо от того, удерживаете ли они какое-либо содержимое.

У меня была причина выполнить эти шаги, и в конце концов я написал свой собственный класс PDFTableStripper, используя PDFBox . Я поделился своим кодом как Gist на GitHub . Метод main приводит пример использования класса:

try (PDDocument document = PDDocument.load(new File(args[0])))
{
    final double res = 72; // PDF units are at 72 DPI
    PDFTableStripper stripper = new PDFTableStripper();
    stripper.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)
    stripper.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<stripper.getColumns(); ++c) {
            System.out.println("Column " + c);
            for(int r=0; r<stripper.getRows(); ++r) {
                System.out.println("Row " + r);
                System.out.println(stripper.getText(r, c));
            }
        }
    }
}
1
beldaz

http://swftools.org/ у этих ребят есть компонент pdf2swf. Они также могут показывать таблицы .... Они также дают источник. Так что вы могли бы проверить это. 

0
kaushalc

Это прекрасно работает, если файл PDF содержит «Только прямоугольную таблицу» с использованием pdfbox 2.0.6. Не будет работать с любым другим столом только прямоугольный стол.

import Java.io.File;
import Java.io.IOException;
import Java.util.ArrayList;

import org.Apache.pdfbox.pdmodel.PDDocument;
import org.Apache.pdfbox.text.PDFTextStripper;
import org.Apache.pdfbox.text.PDFTextStripperByArea;
public class PDFTableExtractor {
    public static void main(String[] args) throws IOException {
        ArrayList<String[]> objTableList = readParaFromPDF("C:\\sample1.pdf", 1,1,6);
        //Enter Filepath, startPage, EndPage, Number of columns in Rectangular table
    }
    public static ArrayList<String[]> readParaFromPDF(String pdfPath, int pageNoStart, int pageNoEnd, int noOfColumnsInTable) {
        ArrayList<String[]> 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;
    }
}
0
Sunil K Chaudhary