2013-11-17 2 views
0

У меня есть pdf. PDF содержит таблицу. Таблица содержит много ячеек (> 100). Я знаю точное положение (x, y) и размерность (w, h) каждой ячейки таблицы.
Мне нужно извлечь текст из ячеек, используя itextsharp. Используя PdfReaderContentParser + FilteredTextRenderListener (используя такой код http://itextpdf.com/examples/iia.php?id=279), я могу извлечь текст, но мне нужно запустить всю процедуру для каждой ячейки. В моем PDF-документе много ячеек, и программе требуется слишком много времени для запуска. Есть ли способ извлечь текст из списка «прямоугольник»? Мне нужно знать текст каждого прямоугольника. Я ищу что-то вроде PDFTextStripperByArea от PdfBox (вы можете определить столько областей, сколько вам нужно, и получить текст, используя .getTextForRegion («region-name»)).Извлечение текста из ячеек таблицы

ответ

2

Этот параметр не включается сразу в дистрибутив iTextSharp, но его легко реализовать. В следующем я использую имена класса, интерфейса и метода iText (Java), потому что я больше дома с Java. Они должны быть легко переведены в имена iTextSharp (C#).

Если вы используете LocationTextExtractionStrategy, вы можете использовать его механизм posteriori TextChunkFilter вместо механизма априорного FilteredRenderListener, используемого в образце, к которому вы подключались. Этот механизм был введен в версии 5.3.3.

Для этого сначала проанализируйте содержимое всей страницы, используя LocationTextExtractionStrategy без применения фильтра FilteredRenderListener. Это заставляет объект стратегии собирать объекты TextChunk для всех текстовых объектов PDF на странице, содержащей соответствующий сегмент базовой линии.

Тогда вы называете 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); 
} 

Так принять метод фильтра для данной ячейки необходимо проверить, является ли текст фрагмента в вопросе внутри клетки.

(вместо отдельных экземпляров для каждой ячейки вы можете, конечно, также создать один экземпляр, параметры, т.е. координаты ячейки, могут быть изменены между getResultantText вызовов.)

PS: Как уже упоминалось в ОП, это 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.)

+0

C# версия LocationTextExtractionStrategy не получаетResultantText (TextChunkFilter chunkFilter). :(Но ваша идея интересная. Я буду искать немного другой подход. –

+0

К сожалению, вы правы. Интересно, поскольку эта функция была общедоступной в версии 5.3.4 (не 5.3.3, как утверждается в комментариях JavaDoc) iText/Java, которому больше года. Но я думаю, что очень просто перевести на C#, только один интерфейс и один метод ... – mkl

+0

Вы все еще вызываете 'PdfTextExtractor.getTextFromPage()' при использовании 'getResultantText()', или он каким-то образом заменяет этот вызов? Если да, то как вам тогда указать страницу для извлечения на? – sschuberth

0

Вот мой вопрос о том, как извлечь текст из табличной структуры в PDF, используя itextsharp. Он возвращает коллекцию строк, и каждая строка содержит набор интерпретируемых столбцов. Это может работать для вас, исходя из предпосылки, что между одним столбцом и рядом существует разрыв, который больше средней ширины одного символа. Я также добавил возможность проверки для обернутого текста в виртуальном столбце. Ваш пробег может отличаться.

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

Эта стратегия делает число (некоторые из которых уже упоминал Шон), поэтому он не может использоваться для всех типов PDF-файлов с таблицами. Но для PDF-файлов, для которых предположения верны, содержимое таблицы извлекается без необходимости знать coor dinates. – mkl

+0

Просто любопытно - есть ли ссылка для более универсального разбора с использованием iText? Я искал SE и не мог найти его - очень интересно узнать, есть ли что-то лучше с источником выборки. – Shaun

+0

Ну, в связи с извлечением текста iText предлагает вам структуру, в которой вы можете разработать стратегии извлечения по вашему выбору сложности. Но многие пользователи предполагают, что две тестовые стратегии, связанные с iText, есть все. Вы найдете некоторые специализированные стратегии извлечения и POC для более продвинутых материалов, например. здесь в переполнении стека ответы, но не большое бесплатное решение, такое как tabula. Там могут быть некоторые закрытые исходные текстовые экстракторы, основанные на iText где-то там. – mkl

Смежные вопросы