Pdf 从表格单元格中提取文本

Pdf 从表格单元格中提取文本,pdf,itextsharp,Pdf,Itextsharp,我有一个pdf文件。pdf包含一个表。该表包含许多单元格(>100)。我知道表格中每个单元格的确切位置(x,y)和尺寸(w,h)。 我需要使用itextsharp从单元格中提取文本。使用PdfReadContentParser+FilteredTextRenderListener(使用类似的代码),我可以提取文本,但我需要为每个单元格运行整个过程。我的pdf有很多单元格,程序运行时间太长。有没有办法从“矩形”列表中提取文本?我需要知道每个矩形的文本。我正在寻找类似PDFTextStripperB

我有一个pdf文件。pdf包含一个表。该表包含许多单元格(>100)。我知道表格中每个单元格的确切位置(x,y)和尺寸(w,h)。

我需要使用itextsharp从单元格中提取文本。使用PdfReadContentParser+FilteredTextRenderListener(使用类似的代码),我可以提取文本,但我需要为每个单元格运行整个过程。我的pdf有很多单元格,程序运行时间太长。有没有办法从“矩形”列表中提取文本?我需要知道每个矩形的文本。我正在寻找类似PDFTextStripperByArea by PdfBox(您可以根据需要定义任意多个区域,并使用.getTextForRegion(“区域名称”)获取文本)的内容。

此选项不会立即包含在iTextSharp发行版中,但很容易实现。在下文中,我使用iText(Java)类、接口和方法名,因为我更熟悉Java。它们应该很容易翻译成iTextSharp(C#)名称

如果使用
LocationTextExtractionStrategy
,则可以使用其后验
TextChunkFilter
机制,而不是链接到的示例中使用的先验
FilteredRenderListener
机制。该机制已在5.3.3版中引入

为此,首先使用
LocationTextExtractionStrategy
解析整个页面内容,而不应用任何
FilteredRenderListener
过滤。这使得策略对象为包含相关基线段的页面上的所有PDF文本对象收集
textcunk
对象

然后使用
TextChunkFilter
参数调用策略的
getResultantText
重载(而不是常规的无参数重载):

对于每个表单元格,使用不同的
TextChunkFilter
实例调用它。您必须实现这个过滤器接口,这并不太困难,因为它只定义了一个方法:

public static interface TextChunkFilter
{
    /**
     * @param textChunk the chunk to check
     * @return true if the chunk should be allowed
     */
    public boolean accept(TextChunk textChunk);
}
因此,给定单元格的过滤器的accept方法必须测试所涉及的文本块是否在单元格内

(当然,您也可以创建一个实例,而不是为每个单元格创建单独的实例,该实例的参数(即单元格坐标)可以在
getResultantText
调用之间更改。)

PS:如OP所述,此
textcunkfilter
尚未移植到iTextSharp。不过,要做到这一点并不难,因为只有一个小界面和一种方法可以添加到策略中

PPS:在被询问的评论中


然后,当使用
getResultantText()
时,您是否仍然调用
pdftextractor.getTextFromPage()
,或者它以某种方式替换了该调用?如果是,那么如何指定要提取到的页面

实际上,
PdfTextExtractor.getTextFromPage()
内部已经使用了无参数
getResultantText()
重载:

public String getResultantText(TextChunkFilter chunkFilter)
public static String getTextFromPage(PdfReader reader, int pageNumber, TextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators) throws IOException
{
    PdfReaderContentParser parser = new PdfReaderContentParser(reader);
    return parser.processContent(pageNumber, strategy, additionalContentOperators).getResultantText();
}
但是,在当前的上下文中,我们只想分析一次页面内容并应用多个过滤器,每个单元格一个过滤器,我们可以将其概括为:

public static List<String> getTextFromPage(PdfReader reader, int pageNumber, LocationTextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators, Iterable<TextChunkFilter> chunkFilters) throws IOException
{
    PdfReaderContentParser parser = new PdfReaderContentParser(reader);
    parser.processContent(pageNumber, strategy, additionalContentOperators)

    List<String> result = new ArrayList<>();
    for (TextChunkFilter chunkFilter : chunkFilters)
    {
        result.add(strategy).getResultantText(chunkFilter);
    }
    return result;
}
public static List getTextFromPage(PdfReader阅读器、int pageNumber、LocationTextExtractionStrategy策略、Map additionalContentOperators、Iterable chunkFilters)引发IOException
{
PdfReaderContentParser=新的PdfReaderContentParser(读取器);
processContent(页码、策略、additionalContentOperators)
列表结果=新建ArrayList();
for(TextChunkFilter chunkFilter:chunkFilters)
{
result.add(策略).getResultantText(chunkFilter);
}
返回结果;
}

(您可以通过使用Java 8 collection streaming(而不是老式的
for
循环)使其看起来更华丽。)

此选项不会立即包含在iTextSharp发行版中,但很容易实现。在下文中,我使用iText(Java)类、接口和方法名,因为我更熟悉Java。它们应该很容易翻译成iTextSharp(C#)名称

如果使用
LocationTextExtractionStrategy
,则可以使用其后验
TextChunkFilter
机制,而不是链接到的示例中使用的先验
FilteredRenderListener
机制。该机制已在5.3.3版中引入

为此,首先使用
LocationTextExtractionStrategy
解析整个页面内容,而不应用任何
FilteredRenderListener
过滤。这使得策略对象为包含相关基线段的页面上的所有PDF文本对象收集
textcunk
对象

然后使用
TextChunkFilter
参数调用策略的
getResultantText
重载(而不是常规的无参数重载):

对于每个表单元格,使用不同的
TextChunkFilter
实例调用它。您必须实现这个过滤器接口,这并不太困难,因为它只定义了一个方法:

public static interface TextChunkFilter
{
    /**
     * @param textChunk the chunk to check
     * @return true if the chunk should be allowed
     */
    public boolean accept(TextChunk textChunk);
}
因此,给定单元格的过滤器的accept方法必须测试所涉及的文本块是否在单元格内

(当然,您也可以创建一个实例,而不是为每个单元格创建单独的实例,该实例的参数(即单元格坐标)可以在
getResultantText
调用之间更改。)

PS:如OP所述,此
textcunkfilter
尚未移植到iTextSharp。不过,要做到这一点并不难,因为只有一个小界面和一种方法可以添加到策略中

PPS:在被询问的评论中


然后,当使用
getResultantText()
时,您是否仍然调用
pdftextractor.getTextFromPage()
,或者它以某种方式替换了该调用?如果是,那么如何指定要提取到的页面

实际上
pdftextractor.getTextFromPage()
内部已经使用了no参数
getResu
   using (PdfReader pdfReader = new PdfReader(stream))
        {
            for (int page = 1; page <= pdfReader.NumberOfPages; page++)
            {

                TableExtractionStrategy tableExtractionStrategy = new TableExtractionStrategy();
                string pageText = PdfTextExtractor.GetTextFromPage(pdfReader, page, tableExtractionStrategy);
                var table = tableExtractionStrategy.GetTable();

            }
        }



        public class TableExtractionStrategy : LocationTextExtractionStrategy
        {
            public float NextCharacterThreshold { get; set; } = 1;
            public int NextLineLookAheadDepth { get; set; } = 500;
            public bool AccomodateWordWrapping { get; set; } = true;

            private List<TableTextChunk> Chunks { get; set; } = new List<TableTextChunk>();

            public override void RenderText(TextRenderInfo renderInfo)
            {
                base.RenderText(renderInfo);
                string text = renderInfo.GetText();
                Vector bottomLeft = renderInfo.GetDescentLine().GetStartPoint();
                Vector topRight = renderInfo.GetAscentLine().GetEndPoint();
                Rectangle rectangle = new Rectangle(bottomLeft[Vector.I1], bottomLeft[Vector.I2], topRight[Vector.I1], topRight[Vector.I2]);
                Chunks.Add(new TableTextChunk(rectangle, text));
            }

            public List<List<string>> GetTable()
            {
                List<List<string>> lines = new List<List<string>>();
                List<string> currentLine = new List<string>();

                float? previousBottom = null;
                float? previousRight = null;

                StringBuilder currentString = new StringBuilder();

                // iterate through all chunks and evaluate 
                for (int i = 0; i < Chunks.Count; i++)
                {
                    TableTextChunk chunk = Chunks[i];

                    // determine if we are processing the same row based on defined space between subsequent chunks
                    if (previousBottom.HasValue && previousBottom == chunk.Rectangle.Bottom)
                    {
                        if (chunk.Rectangle.Left - previousRight > 1)
                        {
                            currentLine.Add(currentString.ToString());
                            currentString.Clear();
                        }
                        currentString.Append(chunk.Text);
                        previousRight = chunk.Rectangle.Right;
                    }
                    else
                    {
                        // if we are processing a new line let's check to see if this could be word wrapping behavior
                        bool isNewLine = true;
                        if (AccomodateWordWrapping)
                        {
                            int readAheadDepth = Math.Min(i + NextLineLookAheadDepth, Chunks.Count);
                            if (previousBottom.HasValue)
                                for (int j = i; j < readAheadDepth; j++)
                                {
                                    if (previousBottom == Chunks[j].Rectangle.Bottom)
                                    {
                                        isNewLine = false;
                                        break;
                                    }
                                }
                        }

                        // if the text was not word wrapped let's treat this as a new table row
                        if (isNewLine)
                        {
                            if (currentString.Length > 0)
                                currentLine.Add(currentString.ToString());
                            currentString.Clear();

                            previousBottom = chunk.Rectangle.Bottom;
                            previousRight = chunk.Rectangle.Right;
                            currentString.Append(chunk.Text);

                            if (currentLine.Count > 0)
                                lines.Add(currentLine);

                            currentLine = new List<string>();
                        }
                        else
                        {
                            if (chunk.Rectangle.Left - previousRight > 1)
                            {
                                currentLine.Add(currentString.ToString());
                                currentString.Clear();
                            }
                            currentString.Append(chunk.Text);
                            previousRight = chunk.Rectangle.Right;

                        }
                    }
                }

                return lines;
            }

            private struct TableTextChunk
            {
                public Rectangle Rectangle;
                public string Text;

                public TableTextChunk(Rectangle rect, string text)
                {
                    Rectangle = rect;
                    Text = text;
                }

                public override string ToString()
                {
                    return Text + " (" + Rectangle.Left + ", " + Rectangle.Bottom + ")";
                }
            }
        }