PDFBox:提取文本时保持 PDF 结构
PDFBox : Maintaining PDF structure when extracting text
我正在尝试从充满表格的 PDF 中提取文本。
在某些情况下,一列是空的。
当我从 PDF 中提取文本时,空列被跳过并替换为空格,因此,我的正则表达式无法确定此处有一个没有信息的列。
为了更好的理解图片:
我们可以看到提取的文本中没有考虑列
我从 PDF 中提取文本的代码示例:
PDFTextStripper reader = new PDFTextStripper();
reader.setSortByPosition(true);
reader.setStartPage(page);
reader.setEndPage(page);
String st = reader.getText(document);
List<String> lines = Arrays.asList(st.split(System.getProperty("line.separator")));
从 PDF 中提取文本时如何保持原始 PDF 的完整结构?
非常感谢。
(这最初是 the answer (dated Feb 6 '15) to another question,OP 删除了所有答案。由于年代久远,答案中的代码仍然基于 PDFBox 1。8.x,因此可能需要进行一些更改才能使它 运行 与 PDFBox 2.0.x.)
在评论中,OP 表现出对 将 PDFBox PDFTextStripper
扩展到 return 试图反映 PDF 文件布局的文本行 的解决方案的兴趣可能会对手头的问题有所帮助。
对此的概念验证是这样 class:
public class LayoutTextStripper extends PDFTextStripper
{
public LayoutTextStripper() throws IOException
{
super();
}
@Override
protected void startPage(PDPage page) throws IOException
{
super.startPage(page);
cropBox = page.findCropBox();
pageLeft = cropBox.getLowerLeftX();
beginLine();
}
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
float recentEnd = 0;
for (TextPosition textPosition: textPositions)
{
String textHere = textPosition.getCharacter();
if (textHere.trim().length() == 0)
continue;
float start = textPosition.getTextPos().getXPosition();
boolean spacePresent = endsWithWS | textHere.startsWith(" ");
if (needsWS | spacePresent | Math.abs(start - recentEnd) > 1)
{
int spacesToInsert = insertSpaces(chars, start, needsWS & !spacePresent);
for (; spacesToInsert > 0; spacesToInsert--)
{
writeString(" ");
chars++;
}
}
writeString(textHere);
chars += textHere.length();
needsWS = false;
endsWithWS = textHere.endsWith(" ");
try
{
recentEnd = getEndX(textPosition);
}
catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e)
{
throw new IOException("Failure retrieving endX of TextPosition", e);
}
}
}
@Override
protected void writeLineSeparator() throws IOException
{
super.writeLineSeparator();
beginLine();
}
@Override
protected void writeWordSeparator() throws IOException
{
needsWS = true;
}
void beginLine()
{
endsWithWS = true;
needsWS = false;
chars = 0;
}
int insertSpaces(int charsInLineAlready, float chunkStart, boolean spaceRequired)
{
int indexNow = charsInLineAlready;
int indexToBe = (int)((chunkStart - pageLeft) / fixedCharWidth);
int spacesToInsert = indexToBe - indexNow;
if (spacesToInsert < 1 && spaceRequired)
spacesToInsert = 1;
return spacesToInsert;
}
float getEndX(TextPosition textPosition) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
Field field = textPosition.getClass().getDeclaredField("endX");
field.setAccessible(true);
return field.getFloat(textPosition);
}
public float fixedCharWidth = 3;
boolean endsWithWS = true;
boolean needsWS = false;
int chars = 0;
PDRectangle cropBox = null;
float pageLeft = 0;
}
它是这样使用的:
PDDocument document = PDDocument.load(PDF);
LayoutTextStripper stripper = new LayoutTextStripper();
stripper.setSortByPosition(true);
stripper.fixedCharWidth = charWidth; // e.g. 5
String text = stripper.getText(document);
fixedCharWidth
是假定的字符宽度。根据相关 PDF 中的文字,不同的值可能更合适。在我的示例文档中,3..6 的值很有趣。
它基本上模拟了 this answer 中 iText 的类似解决方案。但是,结果略有不同,因为 iText 文本提取转发文本块,而 PDFBox 文本提取转发单个字符。
请注意,这只是一个概念验证。它特别没有考虑任何旋转
我正在尝试从充满表格的 PDF 中提取文本。 在某些情况下,一列是空的。 当我从 PDF 中提取文本时,空列被跳过并替换为空格,因此,我的正则表达式无法确定此处有一个没有信息的列。
为了更好的理解图片:
我们可以看到提取的文本中没有考虑列
我从 PDF 中提取文本的代码示例:
PDFTextStripper reader = new PDFTextStripper();
reader.setSortByPosition(true);
reader.setStartPage(page);
reader.setEndPage(page);
String st = reader.getText(document);
List<String> lines = Arrays.asList(st.split(System.getProperty("line.separator")));
从 PDF 中提取文本时如何保持原始 PDF 的完整结构?
非常感谢。
(这最初是 the answer (dated Feb 6 '15) to another question,OP 删除了所有答案。由于年代久远,答案中的代码仍然基于 PDFBox 1。8.x,因此可能需要进行一些更改才能使它 运行 与 PDFBox 2.0.x.)
在评论中,OP 表现出对 将 PDFBox PDFTextStripper
扩展到 return 试图反映 PDF 文件布局的文本行 的解决方案的兴趣可能会对手头的问题有所帮助。
对此的概念验证是这样 class:
public class LayoutTextStripper extends PDFTextStripper
{
public LayoutTextStripper() throws IOException
{
super();
}
@Override
protected void startPage(PDPage page) throws IOException
{
super.startPage(page);
cropBox = page.findCropBox();
pageLeft = cropBox.getLowerLeftX();
beginLine();
}
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
float recentEnd = 0;
for (TextPosition textPosition: textPositions)
{
String textHere = textPosition.getCharacter();
if (textHere.trim().length() == 0)
continue;
float start = textPosition.getTextPos().getXPosition();
boolean spacePresent = endsWithWS | textHere.startsWith(" ");
if (needsWS | spacePresent | Math.abs(start - recentEnd) > 1)
{
int spacesToInsert = insertSpaces(chars, start, needsWS & !spacePresent);
for (; spacesToInsert > 0; spacesToInsert--)
{
writeString(" ");
chars++;
}
}
writeString(textHere);
chars += textHere.length();
needsWS = false;
endsWithWS = textHere.endsWith(" ");
try
{
recentEnd = getEndX(textPosition);
}
catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e)
{
throw new IOException("Failure retrieving endX of TextPosition", e);
}
}
}
@Override
protected void writeLineSeparator() throws IOException
{
super.writeLineSeparator();
beginLine();
}
@Override
protected void writeWordSeparator() throws IOException
{
needsWS = true;
}
void beginLine()
{
endsWithWS = true;
needsWS = false;
chars = 0;
}
int insertSpaces(int charsInLineAlready, float chunkStart, boolean spaceRequired)
{
int indexNow = charsInLineAlready;
int indexToBe = (int)((chunkStart - pageLeft) / fixedCharWidth);
int spacesToInsert = indexToBe - indexNow;
if (spacesToInsert < 1 && spaceRequired)
spacesToInsert = 1;
return spacesToInsert;
}
float getEndX(TextPosition textPosition) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
Field field = textPosition.getClass().getDeclaredField("endX");
field.setAccessible(true);
return field.getFloat(textPosition);
}
public float fixedCharWidth = 3;
boolean endsWithWS = true;
boolean needsWS = false;
int chars = 0;
PDRectangle cropBox = null;
float pageLeft = 0;
}
它是这样使用的:
PDDocument document = PDDocument.load(PDF);
LayoutTextStripper stripper = new LayoutTextStripper();
stripper.setSortByPosition(true);
stripper.fixedCharWidth = charWidth; // e.g. 5
String text = stripper.getText(document);
fixedCharWidth
是假定的字符宽度。根据相关 PDF 中的文字,不同的值可能更合适。在我的示例文档中,3..6 的值很有趣。
它基本上模拟了 this answer 中 iText 的类似解决方案。但是,结果略有不同,因为 iText 文本提取转发文本块,而 PDFBox 文本提取转发单个字符。
请注意,这只是一个概念验证。它特别没有考虑任何旋转