java - library - pdfbox example
Análisis de archivos PDF(especialmente con tablas) con PDFBox (14)
¿Qué tal imprimir en la imagen y hacer OCR en eso?
Suena terriblemente ineficaz, pero es prácticamente el propósito de PDF hacer que el texto sea inaccesible, tienes que hacer lo que tienes que hacer.
Necesito analizar un archivo PDF que contiene datos tabulares. Estoy usando PDFBox para extraer el texto del archivo para analizar el resultado (cadena) más tarde. El problema es que la extracción de texto no funciona como esperaba para los datos tabulares. Por ejemplo, tengo un archivo que contiene una tabla como esta (7 columnas: las dos primeras siempre tienen datos, solo una columna de Complejidad tiene datos, solo una columna de Financiamiento tiene datos):
+----------------------------------------------------------------+
| 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|
+----------------------------------------------------------------+
Luego uso PDFBox:
PDDocument document = PDDocument.load(pathToFile);
PDFTextStripper s = new PDFTextStripper();
String content = s.getText(document);
Esas dos líneas de datos se extraerían así:
xyz 12.43 12.4312.43
abc 1.56 1.561.56
No hay espacios en blanco entre los dos últimos números, pero este no es el mayor problema. El problema es que no sé lo que significan los dos últimos números: Medio, Alto, ¿No corresponde? MAC / Otro, FAE? No tengo la relación entre los números y sus columnas.
No es necesario que use la biblioteca PDFBox, por lo que una solución que use otra biblioteca está bien. Lo que quiero es poder analizar el archivo y saber qué significa cada número analizado.
Deberá diseñar un algoritmo para extraer los datos en un formato utilizable. Independientemente de la biblioteca de PDF que use, tendrá que hacer esto. Los personajes y los gráficos se dibujan mediante una serie de operaciones de dibujo con estado, es decir, se mueven a esta posición en la pantalla y dibujan el glifo para el carácter ''c''.
Sugiero que extienda org.apache.pdfbox.pdfviewer.PDFPageDrawer
y anule el método strokePath
. Desde allí puede interceptar las operaciones de dibujo para segmentos de línea horizontales y verticales y usar esa información para determinar las posiciones de columna y fila para su tabla. Entonces es una simple cuestión de configurar regiones de texto y determinar qué números / letras / caracteres se dibujan en cada región. Como conoce el diseño de las regiones, podrá saber a qué columna pertenece el texto extraído.
Además, la razón por la que no puede haber espacios entre el texto que está visualmente separado es que, muy a menudo, el PDF no dibuja un carácter de espacio. En cambio, la matriz de texto se actualiza y se emite un comando de dibujo para "mover" para dibujar el siguiente carácter y un "ancho de espacio" aparte del último.
Buena suerte.
Esto funciona bien si el archivo PDF tiene "Only Rectangular table" usando pdfbox 2.0.6. No funcionará con ninguna otra tabla solo rectangular.
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;
}
}
Había usado muchas herramientas para extraer la tabla del archivo pdf, pero no funcionó para mí.
Así que he implementado mi propio algoritmo (su nombre es traprange
) para analizar datos tabulares en archivos pdf.
Los siguientes son algunos ejemplos de archivos pdf y resultados:
- Archivo de entrada: sample-1.pdf , result: sample-1.html
- Archivo de entrada: sample-4.pdf , resultado: sample-4.html
Visita mi página de proyecto en traprange .
Hay PDFLayoutTextStripper que fue diseñado para mantener el formato de los datos.
Del LÉAME:
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);
}
}
La extracción de datos de PDF está llena de problemas. ¿Los documentos se crean a través de algún tipo de proceso automático? De ser así, podría considerar convertir los archivos PDF a PostScript sin comprimir (intente con pdf2ps) y ver si el PostScript contiene algún tipo de patrón regular que puede explotar.
No estoy familiarizado con PDFBox, pero podrías intentar mirar itext . Aunque la página de inicio dice generación de PDF, también puede realizar la manipulación y extracción de PDF. Eche un vistazo y vea si se ajusta a su caso de uso.
Para leer el contenido de la tabla desde el archivo pdf, solo tiene que convertir el archivo pdf en un archivo de texto usando cualquier API (uso PdfTextExtracter.getTextFromPage () de iText) y luego lea ese archivo txt con su programa java. Ahora, después de leerlo, la tarea principal está lista. Debes filtrar los datos que necesites. puedes hacerlo usando continuamente el método split de la clase String hasta que encuentres un registro de tu interés ... aquí está mi código por el cual he extraído parte del registro de un archivo PDF y lo escribo en un archivo .CSV. URL de PDF el archivo es .. http://www.cea.nic.in/reports/monthly/generation_rep/actual/jan13/opm_02.pdf
Código:-
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();
}
}
Puede extraer texto por área en PDFBox. Consulte el archivo de ejemplo pdfbox-examples
, en el artefacto pdfbox-examples
si usa Maven. Un fragmento parece
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" );
El problema es obtener las coordenadas en primer lugar. Tuve éxito ampliando el TextStripper
normal, reemplazando processTextPosition(TextPosition text)
e imprimiendo las coordenadas de cada personaje y averiguando en qué parte del documento se encuentran.
Pero hay una manera mucho más simple, al menos si estás en una Mac. Abra el PDF en la Vista previa, ⌘I para mostrar el Inspector, elija la pestaña Recortar y asegúrese de que las unidades estén en Puntos, en el menú Herramientas, seleccione la selección Rectangular y seleccione el área de interés. Si selecciona un área, el inspector le mostrará las coordenadas, que puede redondear y alimentar en los argumentos del constructor Rectangle
. Solo necesita confirmar dónde está el origen, utilizando el primer método.
Puede que sea demasiado tarde para mi respuesta, pero creo que esto no es tan difícil. Puede extender la clase PDFTextStripper y anular los métodos writePage () y processTextPosition (...). En su caso, supongo que los encabezados de columna son siempre los mismos. Esto significa que conoce la coordenada x de cada encabezado de columna y puede comparar la coordenada x de los números con los encabezados de las columnas. Si están lo suficientemente cerca (tienes que probar para decidir qué tan cerca), entonces puedes decir que ese número pertenece a esa columna.
Otro enfoque sería interceptar el vector "charactersByArticle" después de escribir cada página:
@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
}
Conociendo sus columnas, puede hacer su comparación de las coordenadas x para decidir a qué columna pertenece cada número.
La razón por la que no tiene ningún espacio entre los números es porque tiene que establecer la palabra cadena de separación.
Espero que esto sea útil para ti o para otros que puedan estar intentando cosas similares.
Puede usar la clase PDFTextStripperByArea de PDFTextStripperByArea
para extraer texto de una región específica de un documento. Puede basarse en esto al identificar la región de cada celda de la tabla. Esto no se proporciona de DrawPrintTextLocations
, pero el ejemplo de la clase DrawPrintTextLocations
demuestra cómo se pueden analizar los cuadros delimitadores de caracteres individuales en un documento (sería genial analizar cuadros delimitadores de cadenas o párrafos, pero no he visto soporte). en PDFBox para esto - vea esta question ). Puede usar este enfoque para agrupar todas las casillas de delimitación que tocan para identificar celdas distintas de una tabla. Una forma de hacerlo es mantener un conjunto de boxes
de regiones Rectangle2D
y luego, para cada carácter analizado, buscar el cuadro delimitador del personaje como DrawPrintTextLocations.writeString(String string, List<TextPosition> textPositions)
y fusionarlo con los contenidos existentes.
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);
A continuación, puede pasar estas regiones a PDFTextStripperByArea
.
También puede ir más allá y separar los componentes horizontales y verticales de estas regiones, y así inferir las regiones de todas las celdas de la tabla, independientemente de si contienen contenido.
He tenido motivos para realizar estos pasos y finalmente escribí mi propia clase PDFBox usando PDFBox . He compartido mi código como una esencia en GitHub . El método main
proporciona un ejemplo de cómo se puede usar la clase:
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));
}
}
}
}
Tuve el mismo problema al leer el archivo pdf en el que los datos están en formato tabular. Después del análisis regular usando PDFBox cada fila se extraía con una coma como separador ... perdiendo la posición de la columna. Para resolver esto, utilicé PDFTextStripperByArea y, al usar las coordenadas, extraje la columna de datos por columna para cada fila. Esto se proporciona siempre que tengas un formato fijo en 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" ));
Luego fila 2 y así sucesivamente ...
Tuve un éxito decente al analizar archivos de texto generados por la utilidad 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);
}
http://swftools.org/ estos chicos tienen un componente pdf2swf. También pueden mostrar tablas. Ellos también están dando la fuente. Así que posiblemente puedas echarle un vistazo.