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