如何使用itextsharp从表结构化PDF中读取数据?

时间:2015-08-14 16:23:46

标签: c# itextsharp

我在从pdf文件中读取一些数据时遇到问题 我的文件是结构化的,它包含表格和纯文本。标准解析器从同一行的不同列读取数据。例如:

Some Table Header  
Data Col1a     Data Col2a      Data Col3a
Data Col1b     Data Col2b      Data Col3b
               Data Col2c

使用此代码

        PdfReader reader = new PdfReader(pdfName);

        List<String> text = new List<String>();
        String page;
        List<String> pageStrings;
        string[] separators = { "\n", "\r\n" };

        for (int i = 1; i <= reader.NumberOfPages; i++)
        {
            page = PdfTextExtractor.GetTextFromPage(reader, i);
            pageStrings = new List<string>(page.Split(separators, StringSplitOptions.RemoveEmptyEntries));
            text.AddRange(pageStrings);

        }

        reader.Close();

        return text;

将连接成字符串:

Some Table Header
Data Col1a Data Col2a Data Col3a  
Data Col1b Data Col2b Data Col3b  
Data Col2c  

我想获得反映来自块数据的串联字符串。我想为上面的例子获得这样的字符串:

Some Table Header
Data Col1a Data Col1b   
Data Col2a Data Col2b Data Col2c  
Data Col3a Data Col3b

有没有人知道如何调整itextsharp以获得pdf解析器的这种行为? 也许某人有适当的代码示例? 示例PDF文件为here

1 个答案:

答案 0 :(得分:6)

OP的示例文件包含多个部分,如下所示:

sampleFile.pdf, top of page 1

评论中提到的OP:

  

另一个工具完全像我想要的那样解析我的PDF。 [...]

     

PS:这个工具是pdfbox

在此方法中使用PDFBox(v1.8.10,当前版本):

String extract(PDDocument document) throws IOException
{
    PDFTextStripper stripper = new PDFTextStripper();
    return stripper.getText(document);
}

返回上面显示的部分

Driver Book for 8/5/2015
Company IS MEDICAL; AND Date of Service IS BETWEEN 08/05/2015 AND 08/05/2015; AND Status IS Assigned; AND Vehicles IS  MEDICAL: 
CATY
 MEDICAL
Trip #: 314-A
Comments: ----LIVERY---
Destination:Pick-up:
Call Type: Livery
<Doctor Office>
REGO PARK,  (631) 
000-0000
(718) 896-5953
74- AVE 204E  HEIGHTS, NY 
11372 (718) 639-4154
11:00:00 PAT, MIKHAIL
Trip #: 314-B
Comments:  ----LIVERY---
Destination:Pick-up:
Call Type: Livery
74- AVE 204E  HEIGHTS, NY 
11372 (718) 639-4154
<Doctor Office>
63-6 REGO PARK, NY 
11374 (631) 000-0000
11:01:00 PAT, MIKHAIL

这不是一个整齐的列式提取,但某些信息块(如地址块)保持在一起。

使用iText(Sharp)获得相同的输出实际上非常简单:只需要​​明确使用SimpleTextExtractionStrategy而不是默认使用的LocationTextExtractionStrategy,即必须替换此行

page = PdfTextExtractor.GetTextFromPage(reader, i);

通过

page = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy());

除了每个数据集一个空格字符(iText(夏普)提取Destination: Pick-up:而不是Destination:Pick-up:),结果都是相同的。

关于PDFBox提取文本的结论:

  

所以我认为PDF实际上是表格式的。

实际上,这种提取顺序仅仅意味着用于在PDF页面内容流中绘制字符串片段的操作以这种顺序发生。由于这些操作的顺序是任意的,根据PDF规范,生成这些PDF的软件的任何更新都可能导致PDFBox PDFTextStripper和iText SimpleTextExtractionStrategy提取的文件只是一个难以理解的字符汤

PS:如果将PDFBox PDFTextStripper属性SortByPosition设置为true,就像这样

    PDFTextStripper stripper = new PDFTextStripper();
    stripper.setSortByPosition(true);
    return stripper.getText(document);

然后PDFBox就像iText(夏普)一样提取文本(默认)LocationTextExtractionStrategy

OP表示对内容流中固有的块结构感兴趣。像通用PDF中那样最明显的结构是文本对象(可以绘制多个字符串)。

在手头的情况下使用SimpleTextExtractionStrategy。它可以很容易地扩展为还包括与其输出中的文本对象的开始和结束相对应的标记。在Java中,这可以通过使用这样的匿名类来完成:

return PdfTextExtractor.getTextFromPage(reader, pageNo, new SimpleTextExtractionStrategy()
{
    boolean empty = true;

    @Override
    public void beginTextBlock()
    {
        if (!empty)
            appendTextChunk("<BLOCK>");
        super.beginTextBlock();
    }

    @Override
    public void endTextBlock()
    {
        if (!empty)
            appendTextChunk("</BLOCK>\n");
        super.endTextBlock();
    }

    @Override
    public String getResultantText()
    {
        if (empty)
            return super.getResultantText();
        else
            return "<BLOCK>" + super.getResultantText();
    }

    @Override
    public void renderText(TextRenderInfo renderInfo)
    {
        empty = false;
        super.renderText(renderInfo);
    }
});

TextExtraction.java方法extractSimple

(这个Java代码应该很容易转换为C#。使用empty布尔值可能看起来很有趣;但是,这是必要的,因为基类假设某些附加属性可以立即设置块已附加到提取的内容中。)

使用此扩展策略可获得上面显示的部分:

<BLOCK>Driver Book for 8/5/2015
Company IS MEDICAL; AND Date of Service IS BETWEEN 08/05/2015 AND 08/05/2015; AND Status IS Assigned; AND Vehicles IS  MEDICAL: 
CATY</BLOCK>
<BLOCK>
 MEDICAL</BLOCK>
<BLOCK>
Trip #: 314-A</BLOCK>
<BLOCK>
Comments: ----LIVERY---</BLOCK>
<BLOCK>
Destination: Pick-up:</BLOCK>
<BLOCK>
Call Type: Livery
<Doctor Office>
REGO PARK,  (631) 
000-0000
(718) 896-5953</BLOCK>
<BLOCK>
74- AVE 204E  HEIGHTS, NY 
11372 (718) 639-4154</BLOCK>
<BLOCK>
11:00:00</BLOCK>
<BLOCK> PAT, MIKHAIL</BLOCK>
<BLOCK>
Trip #: 314-B</BLOCK>
<BLOCK>
Comments:  ----LIVERY---</BLOCK>
<BLOCK>
Destination: Pick-up:</BLOCK>
<BLOCK>
Call Type: Livery
74- AVE 204E  HEIGHTS, NY 
11372 (718) 639-4154</BLOCK>
<BLOCK>
<Doctor Office>
63-6 REGO PARK, NY 
11374 (631) 000-0000</BLOCK>
<BLOCK>
11:01:00</BLOCK>
<BLOCK> PAT, MIKHAIL</BLOCK>

由于这会将地址保留在同一个块中,因此在提取过程中可能会有所帮助。