PDFBox로 PDF 파일 (특히 표 사용) 구문 분석
표 형식 데이터가 포함 된 PDF 파일을 구문 분석해야합니다. 내가 사용하고 PDFBox를 결과 (문자열) 이상을 구문 분석 할 파일의 텍스트를 추출 할 수 있습니다. 문제는 텍스트 추출이 테이블 형식 데이터에 대해 예상대로 작동하지 않는다는 것입니다. 예를 들어, 다음과 같은 테이블이 포함 된 파일이 있습니다 (7 개 열 : 처음 두 개에는 항상 데이터가 있고 하나의 복잡성 열에 만 데이터가 있고 하나의 Financing 열에 만 데이터가 있음).
+----------------------------------------------------------------+
| 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 라이브러리를 사용할 필요가 없으므로 다른 라이브러리를 사용하는 솔루션이 좋습니다. 내가 원하는 것은 파일을 구문 분석하고 각 구문 분석 된 숫자가 의미하는 바를 알 수있는 것입니다.
사용 가능한 형식으로 데이터를 추출하려면 알고리즘을 고안해야합니다. 사용하는 PDF 라이브러리에 관계없이이 작업을 수행해야합니다. 문자와 그래픽은 일련의 상태 저장 그리기 작업에 의해 그려집니다. 즉, 화면에서이 위치로 이동하여 문자 'c'에 대한 글리프를 그립니다.
메서드 를 확장 org.apache.pdfbox.pdfviewer.PDFPageDrawer
하고 재정의하는 것이 좋습니다 strokePath
. 여기에서 가로 및 세로 선 세그먼트에 대한 그리기 작업을 가로 채고 해당 정보를 사용하여 테이블의 열 및 행 위치를 결정할 수 있습니다. 그런 다음 텍스트 영역을 설정하고 어떤 영역에 그려지는 숫자 / 문자 / 문자를 결정하는 간단한 문제입니다. 영역의 레이아웃을 알고 있으므로 추출 된 텍스트가 속한 열을 알 수 있습니다.
또한 시각적으로 구분 된 텍스트 사이에 공백이 없을 수있는 이유는 PDF에서 공백 문자가 그려지지 않는 경우가 매우 많기 때문입니다. 대신 텍스트 매트릭스가 업데이트되고 '이동'에 대한 그리기 명령이 실행되어 마지막 문자와 별도로 다음 문자와 "공간 너비"를 그립니다.
행운을 빕니다.
pdf 파일에서 표를 추출하기 위해 많은 도구를 사용했지만 저에게 효과가 없었습니다.
그래서 traprange
pdf 파일의 표 형식 데이터를 구문 분석하기 위해 자체 알고리즘 (이름은 )을 구현했습니다 .
다음은 몇 가지 샘플 pdf 파일 및 결과입니다.
- 입력 파일 : sample-1.pdf , 결과 : sample-1.html
- 입력 파일 : sample-4.pdf , 결과 : sample-4.html
traprange 에서 내 프로젝트 페이지를 방문하십시오 .
대답하기에는 너무 늦었을지 모르지만 그렇게 어렵지 않다고 생각합니다. PDFTextStripper 클래스를 확장하고 writePage () 및 processTextPosition (...) 메서드를 재정의 할 수 있습니다. 귀하의 경우에는 열 헤더가 항상 동일하다고 가정합니다. 즉, 각 열 머리글의 x 좌표를 알고 있으며 숫자의 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 좌표를 비교하여 모든 숫자가 속한 열을 결정할 수 있습니다.
숫자 사이에 공백이없는 이유는 단어 구분 문자열을 설정해야하기 때문입니다.
나는 이것이 당신이나 비슷한 일을 시도하는 다른 사람들에게 유용하기를 바랍니다.
PDFBox에서 영역별로 텍스트를 추출 할 수 있습니다. Maven을 사용하는 경우 아티팩트 ExtractByArea.java
에서 예제 파일을 참조하십시오 pdfbox-examples
. 스 니펫은 다음과 같습니다.
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를 사용하여 Inspector를 표시하고 자르기 탭을 선택한 다음 단위가 포인트에 있는지 확인하고 도구 메뉴에서 직사각형 선택을 선택한 다음 관심 영역을 선택합니다. 영역을 선택하면 검사자가 좌표를 표시하여 Rectangle
생성자 인수에 반올림하여 입력 할 수 있습니다 . 첫 번째 방법을 사용하여 출처가 어디에 있는지 확인하기 만하면됩니다.
데이터 형식을 유지하도록 설계된 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);
}
}
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);
}
PDF에서 데이터를 추출하는 것은 문제가 될 수 있습니다. 문서는 일종의 자동 프로세스를 통해 생성됩니까? 그렇다면 PDF를 압축되지 않은 PostScript로 변환하고 (pdf2ps 시도) PostScript에 악용 할 수있는 일종의 일반 패턴이 포함되어 있는지 확인할 수 있습니다.
데이터가 표 형식 인 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 행 등 ...
PDFBox의 PDFTextStripperByArea
클래스를 사용하여 문서의 특정 영역에서 텍스트를 추출 할 수 있습니다 . 테이블의 각 셀 영역을 식별하여이를 기반으로 구축 할 수 있습니다. 이것은 기본적으로 제공되지 않지만 예제 DrawPrintTextLocations
클래스는 문서에서 개별 문자의 경계 상자를 구문 분석하는 방법을 보여줍니다 (문자열 또는 단락의 경계 상자를 구문 분석하는 것이 좋지만 이에 대한 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
.
또한 한 단계 더 나아가 이러한 영역의 수평 및 수직 구성 요소를 분리하여 내용을 보유하는지 여부에 관계없이 모든 테이블 셀의 영역을 추론 할 수 있습니다.
이 단계를 수행해야 할 이유가 있었고 결국 PDFBox를PDFTableStripper
사용하여 내 자신의 클래스를 작성했습니다 . 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));
}
}
}
}
TabulaPDF ( https://github.com/tabulapdf/tabula )를 사용해보세요 . 이것은 PDF 파일에서 테이블 내용을 추출하는 데 매우 좋은 라이브러리입니다. 예상대로입니다.
행운을 빕니다. :)
http://swftools.org/ 이 사람들은 pdf2swf 구성 요소를 가지고 있습니다. 테이블을 표시 할 수도 있습니다. 그들은 또한 소스를 제공하고 있습니다. 그래서 당신은 그것을 확인할 수 있습니다.
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;
}
}
PDFBox에 익숙하지 않지만 itext 를 살펴볼 수 있습니다. 홈페이지에 PDF 생성이라고 나와 있지만 PDF 조작 및 추출도 할 수 있습니다. 사용 사례에 맞는지 살펴보십시오.
이미지로 인쇄하고 OCR을 수행하는 것은 어떻습니까?
끔찍하게 비효율적으로 들리지만 텍스트에 액세스 할 수 없게 만드는 것이 사실상 PDF의 목적입니다.해야 할 일을해야합니다.
pdf 파일에서 테이블 내용을 읽으려면 API (iText의 PdfTextExtracter.getTextFromPage ()를 사용했습니다)를 사용하여 pdf 파일을 텍스트 파일로 변환 한 다음 Java 프로그램으로 해당 txt 파일을 읽어야합니다. .. 이제 그것을 읽은 후 주요 작업이 완료되었습니다 .. 필요한 데이터를 필터링해야합니다. 당신은 당신의 intrest 기록을 찾을 때까지 String 클래스의 split 메소드를 계속 사용하여 할 수 있습니다 .. 여기에 내가 PDF 파일로 레코드의 일부를 추출하여 .CSV 파일에 쓰는 내 코드가 있습니다 .. Url of PDF 파일은 .. http://www.cea.nic.in/reports/monthly/generation_rep/actual/jan13/opm_02.pdf
암호:-
public static void genrateCsvMonth_Region(String pdfpath, String csvpath) {
try {
String 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 String..
String page = PdfTextExtractor.getTextFromPage(reader, 1);
// Extracting month and Year from String..
String period1[] = page.split("PEROID");
String period2[] = period1[0].split(":");
String month[] = period2[1].split("-");
String period3[] = month[1].split("ENERGY");
String year[] = period3[0].split("VIS");
// Extracting Northen region
String northen[] = page.split("NORTHEN REGION");
String nthermal1[] = northen[0].split("THERMAL");
String nthermal2[] = nthermal1[1].split(" ");
String nnuclear1[] = northen[0].split("NUCLEAR");
String nnuclear2[] = nnuclear1[1].split(" ");
String nhydro1[] = northen[0].split("HYDRO");
String nhydro2[] = nhydro1[1].split(" ");
String ntotal1[] = northen[0].split("TOTAL");
String 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
String western[] = page.split("WESTERN");
String wthermal1[] = western[1].split("THERMAL");
String wthermal2[] = wthermal1[1].split(" ");
String wnuclear1[] = western[1].split("NUCLEAR");
String wnuclear2[] = wnuclear1[1].split(" ");
String whydro1[] = western[1].split("HYDRO");
String whydro2[] = whydro1[1].split(" ");
String wtotal1[] = western[1].split("TOTAL");
String 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
String southern[] = page.split("SOUTHERN");
String sthermal1[] = southern[1].split("THERMAL");
String sthermal2[] = sthermal1[1].split(" ");
String snuclear1[] = southern[1].split("NUCLEAR");
String snuclear2[] = snuclear1[1].split(" ");
String shydro1[] = southern[1].split("HYDRO");
String shydro2[] = shydro1[1].split(" ");
String stotal1[] = southern[1].split("TOTAL");
String 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
String eastern[] = page.split("EASTERN");
String ethermal1[] = eastern[1].split("THERMAL");
String ethermal2[] = ethermal1[1].split(" ");
String ehydro1[] = eastern[1].split("HYDRO");
String ehydro2[] = ehydro1[1].split(" ");
String etotal1[] = eastern[1].split("TOTAL");
String 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
String neestern[] = page.split("NORTH");
String nethermal1[] = neestern[2].split("THERMAL");
String nethermal2[] = nethermal1[1].split(" ");
String nehydro1[] = neestern[2].split("HYDRO");
String nehydro2[] = nehydro1[1].split(" ");
String netotal1[] = neestern[2].split("TOTAL");
String 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();
}
}
참고 URL : https://stackoverflow.com/questions/3203790/parsing-pdf-files-especially-with-tables-with-pdfbox
'development' 카테고리의 다른 글
활동 레이아웃 : 조각 클래스 : vs android : name 속성 (0) | 2020.11.25 |
---|---|
pip의`--no-cache-dir`은 무엇에 좋은가요? (0) | 2020.11.25 |
Angular CLI-서비스시 자동 새로 고침을 비활성화하는 방법 (0) | 2020.11.24 |
순환 복잡성을 최소화하는 조건부 로깅 (0) | 2020.11.24 |
솔로 개발자에게 지속적인 통합이 중요한가요? (0) | 2020.11.24 |