Извлечение таблиц из pdf (в excel), pref. 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 найти строку, например "годовой доход", а затем вывести данные после нее в столбцы. (До конца стола.)

первая часть не очень сложно (найти определенную строку), но как бы я идти о второй части. Текстовый файл будет выглядеть как этот Сайт Pastebin. Проблема в том, что текст не нормируется. Так, например, некоторые таблицы имеют столбцы 3-year (2010 2011 2012), а некоторые только два (или 1), некоторые таблицы имеют больше пробелов между столбцами, а некоторые не включают определенные строки (например, Capital Asset, net).

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

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

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

любые указатели, чтобы я начал, в основном на Шаге 2?

2 ответов


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

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

  • вы можете использовать логические значения, чтобы помочь вам определить, какой "раздел" текстового файла вы находитесь. Ie использовать InStr в текущей строке определите, что вы находитесь в таблице, ища текст "таблица", а затем как только вы узнаете, что находитесь в разделе "таблица" начала файла ищу раздел "активы" и т. д.
  • вы можете использовать несколько методов чтобы определить количество лет (или столбцов), которые у вас есть. The 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's 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

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

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

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

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

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

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

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

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

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