从表格单元格中提取文本

时间:2013-11-17 20:36:22

标签: pdf itextsharp

我有一个pdf。 pdf包含一个表格。该表包含许多单元格(> 100)。我知道表格中每个单元格的确切位置(x,y)和尺寸(w,h) 我需要使用itextsharp从单元格中提取文本。使用PdfReaderContentParser + FilteredTextRenderListener(使用像http://itextpdf.com/examples/iia.php?id=279这样的代码)我可以提取文本,但我需要为每个单元格运行整个过程。我的pdf有很多单元格,程序需要太多时间才能运行。有没有办法从“矩形”列表中提取文本?我需要知道每个矩形的文字。我正在寻找像PdfBox这样的PDFTextStripperByArea(你可以根据需要定义多个区域,使用.getTextForRegion(“region-name”)来获取文本。)

2 个答案:

答案 0 :(得分:2)

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

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

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

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

public String getResultantText(TextChunkFilter chunkFilter)

您为每个表格单元格使用不同的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所提到的,此TextChunkFilter尚未移植到iTextSharp。不过,要做到这一点并不困难,只需要一个小的界面和一个方法来添加到战略中。

PPS:在评论中sschuberth询问

  

在使用PdfTextExtractor.getTextFromPage()时,您是否仍然会调用getResultantText(),还是以某种方式取代该呼叫?如果是这样,那么如何指定要提取的页面?

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

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();
}

要使用TextChunkFilter,您可以简单地构建类似的便捷方法,例如

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

在目前的上下文中,我们只想解析页面内容一次并应用多个过滤器,每个单元格一个,我们可以将其概括为:

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;
}

(通过使用Java 8集合流而不是旧的for循环,可以使这看起来更漂亮。)

答案 1 :(得分:0)

以下是我如何使用itextsharp从PDF中的表格式结构中提取文本的方法。它返回一组行,每行包含一组已解释的列。这可能对您有用,前提是一列与下一列之间存在间隙,该间隙大于单个字符的平均宽度。我还添加了一个选项来检查虚拟列中的包装文本。您的里程可能会有所不同。

   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 + ")";
                }
            }
        }