7

我正在尝试使用 vba 从 pdf 文件中提取表格并将它们导出到 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 年的列(2010 2011 2012),有些只有两个(或 1 个),有些表在列之间有更多的空间,有些不包括某些行(例如 Capital Asset,net)。

我正在考虑做这样的事情,但不知道如何在 VBA 中进行。

  1. 查找用户定义的字符串。例如。“表一:年复一年。”
  2. 一个。下一行查找年份;如果有两个,我们将需要输出三列(标题 +,2x 年),如果有三个,我们将需要四个(标题 +,3x 年).. 等等
    b。为每年创建标题列+列。
  3. 到达行尾时,转到下一行
  4. 一个。读取文本 -> 输出到第 1 列
    。将空格(空格 > 3?)识别为第 2 列的开始。读取数字 -> 输出到第 2 列。
    c。(如果 column = 3)将空格识别为第 3 列的开头。读取数字 -> 输出到第 3 列
    。(如果 column = 4)将空格识别为第 4 列的开头。读取数字 -> 输出到第 4 列。
  5. 每行,循环 4。
  6. 下一行不包含任何数字 - 结束表。(可能最简单的只是用户定义的数字,15 个字符后没有数字?结束表)

我的第一个版本基于Pdf to excel,但是在线阅读的人不推荐OpenFile而是推荐FileSystemObject(尽管它似乎要慢很多)。

任何让我开始的指针,主要是在第 2 步?

4

3 回答 3

1

您有多种方法来剖析文本文件,具体取决于它的复杂程度可能会导致您以一种或另一种方式倾斜。我开始了这个,它有点失控......享受。

根据您提供的示例和其他评论,我注意到以下内容。其中一些可能适用于简单文件,但对于更大更复杂的文件可能会变得笨拙。此外,我在这里使用的方法或技巧可能会稍微更有效,但这肯定会让你达到预期的结果。希望这与提供的代码一起有意义:

  • 您可以使用布尔值来帮助您确定您所在的文本文件的“部分”。即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'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
于 2013-02-24T08:03:39.537 回答
0

由于 PasteBin 已被删除,我无法检查示例数据。根据我从问题描述中收集到的信息,在我看来,使用正则表达式会使解析数据变得更加容易。

为 FileSystemObject 添加对脚本运行时 scrrun.dll 的引用。
添加对 Microsoft VBScript 正则表达式 5.5 的引用。RegExp 对象的库。

使用 Dim objRE As New RegExp 实例化 RegEx 对象

将 Pattern 属性设置为 "(\bd{4}\b){1,3}" 上面的模式应该匹配包含以下字符串的行:2010 2010 2011 2010 2011 2012

年份字符串之间的空格数无关紧要,只要至少有一个(因为我们不希望遇到像 201020112012 这样的字符串)

将 Global 属性设置为 True

捕获的组将在 RegEx 对象 objRE 的 Execute 方法返回的 MatchCollection 中的各个 Match 对象中找到。所以声明适当的对象:

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

这只是我将如何应对这一挑战的粗略概述。我希望此代码大纲中的某些内容对您有所帮助。

于 2015-03-31T12:08:22.163 回答
0

我取得了一些成功的另一种方法是使用 VBA 转换为 .doc 或 .docx 文件,然后从 Word 文件中搜索并提取表格。它们可以很容易地提取到 Excel 工作表中。转换似乎很好地处理了表格。但是请注意,它是逐页工作的,因此延伸到一页上的表格最终会在 word doc 中作为单独的表格。

于 2019-04-26T17:52:16.213 回答