代码之家  ›  专栏  ›  技术社区  ›  Leor

PDFBox:提取文本时保持PDF结构

  •  3
  • Leor  · 技术社区  · 9 年前

    我试图从一个充满表格的PDF中提取文本。 在某些情况下,列为空。 当我从PDF中提取文本时,空白列被跳过并替换为空白,因此,我的常规表达式无法判断出在这个位置有一个没有信息的列。

    Image of PDF source and extracted text

    我们可以看到,在提取的文本中不尊重列

    我从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")));
    

    非常感谢。

    1 回复  |  直到 9 年前
        1
  •  2
  •   mkl    9 年前

    (这原本是 the answer (dated Feb 6 '15) to another question

    在评论中,OP对解决方案表现出兴趣 扩展PDFBox PDFTextStripper 返回试图反映PDF文件布局的文本行 这可能有助于解决眼前的问题。

    这一类可以证明这一点:

    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

    它基本上模拟了iText中的类似解决方案 this answer

    请注意,这只是概念证明。它特别不考虑任何旋转