Извлечение таблиц из pdf (в excel), прив. w/vba

Я пытаюсь извлечь таблицы из PDF файлов с помощью VBA и экспортировать их в Excel. Если все работает так, как должно, все должно происходить автоматически. Проблема в том, что таблица не стандартизирована.

Это то, что я до сих пор.

  1. VBA (Excel) запускает XPDF и преобразует все файлы .pdf, найденные в текущей папке, в текстовый файл.
  2. VBA (Excel) читает каждый текстовый файл построчно.

И код:

With New Scripting.FileSystemObject
With .OpenTextFile(strFileName, 1, False, 0)

    If Not .AtEndOfStream Then .SkipLine
    Do Until .AtEndOfStream
        //do something
    Loop
End With
End With

Это все прекрасно работает. Но теперь я перехожу к вопросу извлечения таблиц из текстовых файлов. Я пытаюсь найти в VBA строку, например "Year Income", а затем вывести данные после нее в столбцы. (До конца стола.)

Первая часть не очень сложная (найти определенную строку), но как бы я поступил по поводу второй части. Текстовый файл будет выглядеть следующим образом: Pastebin. Проблема в том, что текст не стандартизирован. Так, например, некоторые таблицы имеют трехлетние столбцы (2010 2011 2012), а некоторые - только два (или 1), некоторые таблицы имеют больше пробелов между столбцами n, а некоторые не содержат определенные строки (например, Капитальный актив, нетто).

Я думал о том, чтобы сделать что-то подобное, но не знал, как это сделать в VBA.

  1. Найти пользовательскую строку. например. "Таблица 1: Возвращение лет".
  2. а. Следующая строка найти годы; если их два, нам понадобятся три столбца (заголовки +, 2x год), если их три, нам понадобится четыре (заголовки +, 3x год) и т.д.
    б. Создать заголовок столбца + столбец для каждого года.
  3. Достигнув конца строки, переходите к следующей строке
  4. а. Читать текст → вывод в столбец 1.
    б. Распознать пробелы (пробелы> 3?) Как начало столбца 2. Считать числа → вывод в столбец 2.
    с. (если столбец = 3) Распознать пробелы как начало столбца 3. Считать числа → вывод в столбец 3.
    д. (если столбец = 4) Распознать пробелы как начало столбца 4. Считать числа → вывод в столбец 4.
  5. Каждая строка, петля 4.
  6. Следующая строка не содержит никаких чисел - Конец таблицы. (вероятно, просто, что пользователь просто определил число, после 15 символов нет числа? конец таблицы)

Я основал свою первую версию на Pdf, чтобы преуспеть, но читающие онлайн люди не рекомендуют OpenFile а скорее FileSystemObject (даже если кажется, что он намного медленнее).

Какие-нибудь указатели, чтобы начать меня, главным образом на шаге 2?

+7
источник поделиться
3 ответа

У вас есть несколько способов проанализировать текстовый файл и в зависимости от того, насколько сложно это может привести к тому, что вы опираетесь так или иначе. Я начал это, и он немного вышел из рук... наслаждайтесь.

На основе предоставленного вами примера и дополнительных комментариев я отметил следующее. Некоторые из них могут хорошо работать для простых файлов, но могут стать неудобными с более сложными файлами. Кроме того, могут быть несколько более эффективные методы или трюки к тому, что я использовал здесь, но это, безусловно, поможет вам достичь желаемого результата. Надеюсь, это имеет смысл в сочетании с предоставленным кодом:

  • Вы можете использовать логические значения, чтобы помочь вам определить, какой "раздел" текстового файла вы находитесь. Т.е. используйте InStr в текущей строке, чтобы определите, что вы находитесь в таблице, ища текст "Таблица", а затем как только вы знаете, что находитесь в разделе "Таблица" начала файла ищет раздел "Активы" и т.д.
  • Вы можете использовать несколько методов для определения количества лет (или столбцов), которое у вас есть. Функция Split вместе с циклом будет работа.
  • Если ваши файлы всегда имеют постоянное форматирование, даже в определенных частях, вы можете воспользоваться этим. Например, если вы знаете строка файла всегда будет иметь знак доллара перед ними, затем вы знаете, что это определит ширину столбцов, и вы можете использовать это на последующие строки текста.

Следующий код будет извлекать детали активов из текстового файла, вы можете модифицировать его для извлечения других разделов. Он должен обрабатывать несколько строк. Надеюсь, я прокомментировал это достаточно. Посмотрите, и я отредактирую, если вам нужно помочь.

 Sub ReadInTextFile()
    Dim fs As Scripting.FileSystemObject, fsFile As Scripting.TextStream
    Dim sFileName As String, sLine As String, vYears As Variant
    Dim iNoColumns As Integer, ii As Integer, iCount As Integer
    Dim bIsTable As Boolean, bIsAssets As Boolean, bIsLiabilities As Boolean, bIsNetAssets As Boolean

    Set fs = CreateObject("Scripting.FileSystemObject")
    sFileName = "G:\Sample.txt"
    Set fsFile = fs.OpenTextFile(sFileName, 1, False)

    'Loop through the file as you've already done
    Do While fsFile.AtEndOfStream <> True
        'Determine flag positions in text file
        sLine = fsFile.Readline

        Debug.Print VBA.Len(sLine)

        'Always skip empty lines (including single spaceS)
        If VBA.Len(sLine) > 1 Then

            'We've found a new table so we can reset the booleans
            If VBA.InStr(1, sLine, "Table") > 0 Then
                bIsTable = True
                bIsAssets = False
                bIsNetAssets = False
                bIsLiabilities = False
                iNoColumns = 0
            End If

            'Perhaps you want to also have some sort of way to designate that a table has finished.  Like so
            If VBA.Instr(1, sLine, "Some text that designates the end of the table") Then
                bIsTable = False
            End If 

            'If we're in the table section then we want to read in the data
            If bIsTable Then
                'Check for your different sections.  You could make this constant if your text file allowed it.
                If VBA.InStr(1, sLine, "Assets") > 0 And VBA.InStr(1, sLine, "Net") = 0 Then bIsAssets = True: bIsLiabilities = False: bIsNetAssets = False
                If VBA.InStr(1, sLine, "Liabilities") > 0 Then bIsAssets = False: bIsLiabilities = True: bIsNetAssets = False
                If VBA.InStr(1, sLine, "Net Assests") > 0 Then bIsAssets = True: bIsLiabilities = False: bIsNetAssets = True

                'If we haven't triggered any of these booleans then we're at the column headings
                If Not bIsAssets And Not bIsLiabilities And Not bIsNetAssets And VBA.InStr(1, sLine, "Table") = 0 Then
                    'Trim the current line to remove leading and trailing spaces then use the split function to determine the number of years
                    vYears = VBA.Split(VBA.Trim$(sLine), " ")
                    For ii = LBound(vYears) To UBound(vYears)
                        If VBA.Len(vYears(ii)) > 0 Then iNoColumns = iNoColumns + 1
                    Next ii

                    'Now we can redefine some variables to hold the information (you'll want to redim after you've collected the info)
                    ReDim sAssets(1 To iNoColumns + 1, 1 To 100) As String
                    ReDim iColumns(1 To iNoColumns) As Integer
                Else
                    If bIsAssets Then
                        'Skip the heading line
                        If Not VBA.Trim$(sLine) = "Assets" Then
                            'Increment the counter
                            iCount = iCount + 1

                            'If iCount reaches it limit you'll have to redim preseve you sAssets array (I'll leave this to you)
                            If iCount > 99 Then
                                'You'll find other posts on stackoverflow to do this
                            End If

                            'This will happen on the first row, it'll happen everytime you
                            'hit a $ sign but you could code to only do so the first time
                            If VBA.InStr(1, sLine, "$") > 0 Then
                                iColumns(1) = VBA.InStr(1, sLine, "$")
                                For ii = 2 To iNoColumns
                                    'We need to start at the next character across
                                    iColumns(ii) = VBA.InStr(iColumns(ii - 1) + 1, sLine, "$")
                                Next ii
                            End If

                            'The first part (the name) is simply up to the $ sign (trimmed of spaces)
                            sAssets(1, iCount) = VBA.Trim$(VBA.Mid$(sLine, 1, iColumns(1) - 1))
                            For ii = 2 To iNoColumns
                                'Then we can loop around for the rest
                                sAssets(ii, iCount) = VBA.Trim$(VBA.Mid$(sLine, iColumns(ii) + 1, iColumns(ii) - iColumns(ii - 1)))
                            Next ii

                            'Now do the last column
                            If VBA.Len(sLine) > iColumns(iNoColumns) Then
                                sAssets(iNoColumns + 1, iCount) = VBA.Trim$(VBA.Right$(sLine, VBA.Len(sLine) - iColumns(iNoColumns)))
                            End If
                        Else
                            'Reset the counter
                            iCount = 0
                        End If
                    End If
                End If

            End If
        End If
    Loop

    'Clean up
    fsFile.Close
    Set fsFile = Nothing
    Set fs = Nothing
End Sub
+2
источник

Я не могу проверить образцы данных, поскольку PasteBin был удален. Основываясь на том, что я могу почерпнуть из описания проблемы, мне кажется, что использование регулярных выражений упростит анализ данных.

Добавьте ссылку на скрипт Runtime scrrun.dll для FileSystemObject.
Добавьте ссылку на регулярные выражения Microsoft VBScript 5.5. библиотека для объекта RegExp.

Создайте экземпляр объекта RegEx с помощью   Dim objRE As New RegExp

Задайте для свойства Pattern значение "(\ bd {4}\b) {1,3}" Вышеупомянутый шаблон должен соответствовать строкам, содержащим строки, такие как: 2010 2010 2011 2010 2011 2012

Число пробелов между строками года не имеет значения, если существует хотя бы один (поскольку мы не ожидаем, что будем сталкиваться с такими строками, как 201020112012)

Установите для свойства Global значение True

Захваченные группы будут найдены в отдельных объектах Match из MatchCollection, возвращенных методом Execute объекта OBRERE объекта RegEx. Поэтому объявите соответствующие объекты:

Dim objMatches as MatchCollection
Dim objMatch as Match
Dim intMatchCount 'tells you how many year strings were found, if any

Предполагая, что вы создали объект FileSystemObject и просматриваете текстовый файл, считывая каждую строку в переменной strLine

Первый тест, чтобы увидеть, содержит ли текущая строка искомый шаблон:

If objRE.Test(strLine) Then
  'do something
Else
  'skip over this line
End If

Set objMatches = objRe.Execute(strLine)
intMatchCount = objMatches.Count

For i = 0 To intMatchCount - 1
   'processing code such as writing the years as column headings in Excel
    Set objMatch = objMatches(i)
    e.g. ActiveCell.Value = objMatch.Value
   'subsequent lines beneath the line containing the year strings should
   'have the amounts, which may be captured in a similar fashion using an
   'additional RegExp object and a Pattern such as "(\b\d+\b){1,3}" for
   'whole numbers or "(\b\d+\.\d+\b){1,3}" for floats. For currency, you
   'can use "(\b\$\d+\.\d{2}\b){1,3}"
Next i

Это всего лишь приблизительная схема того, как я подхожу к этой проблеме. Я надеюсь, что в этом коде есть что-то, что поможет вам.

0
источник

Еще один способ сделать это, у меня есть успех, - использовать VBA для преобразования в файл .doc или .docx, а затем искать и извлекать таблицы из файла Word. Их можно легко извлечь в листы Excel. Преобразование, кажется, хорошо обрабатывает таблицы. Тем не менее, обратите внимание, что он работает на постраничной основе, поэтому таблицы, простирающиеся на страницу, заканчиваются как отдельные таблицы в слове doc.

0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос