Ошибка использования VBA для извлечения данных из текстового файла - PullRequest
1 голос
/ 10 октября 2019

Я пытаюсь извлечь данные из текстового файла, который является результатом экспорта комментариев из PDF. Экспорт комментариев в текстовый файл оказался наилучшим вариантом, так как форматы XML и Excel работают не очень хорошо. Тем не менее, я получаю некоторые ошибки с кодом при попытке его запустить.

Я хочу, чтобы код взял данные из текстового файла, который выглядит следующим образом:

enter image description here

И поместите их в таблицу следующим образом:

enter image description here

Вместо этого таблица получается вот так,с комментариями, номерами комментариев и страницами, которые не упорядочены правильно, а некоторая информация не указана. Я не уверен, почему комментарии, кажется, составляются, но тогда комментарий от автора D опущен.

enter image description here

Любая помощь очень ценится!

Вот мой код:

Sub Format()

' This code determines the users username.  Useful if there is a standard location each person would have the file on their computer.

Set scripting_object = CreateObject("Scripting.FileSystemObject")
strUser = CreateObject("WScript.Network").UserName
localfileName = "C:\Users\" + strUser + "\Downloads\ForumPostExample.txt"


' URL of the file
file_url = "C:\ForumPostExample.txt"

' This creates a scripting object
Set local_file = CreateObject("Scripting.FileSystemObject")
' Opens the text file based on the url for the file.  1, and 2 are options, like read only, can't remember exactly
Set local_file_read = local_file.OpenTextFile(file_url, 1, 2)
'Initialize worksheet
Set xlSheet = ActiveWorkbook.Worksheets("Sheet1")
'Write out the first row
xlSheet.Range("A1") = "Comment No."
xlSheet.Range("B1") = "Reviewer Name"
xlSheet.Range("C1") = "Type"
xlSheet.Range("D1") = "Page Number"
xlSheet.Range("E1") = "Comment"
xlSheet.Range("F1") = "Date Submitted"
'Set row count
row_count = 2

'Variable tells if comments needs to be written out
write_comments = "No"
'Initialize comments variable and comments count as it will need to be added to multiple times
Comments = ""
comment_count = 0

'Read each line of the file
Do Until local_file_read.AtEndOfStream
    ' Set variable textline to be the line from the text file
    textline = local_file_read.ReadLine
    'Look for Page number by checking for string "Page: " in the textline variable string
    If InStr(textline, "Page: ") > 0 Then
        'Determine if comments need to be written out
        If write_comments = "Yes" Then
            xlSheet.Range("A" & row_count) = comment_count
            xlSheet.Range("B" & row_count) = author_name
            xlSheet.Range("C" & row_count) = comment_type
            xlSheet.Range("D" & row_count) = page_number
            xlSheet.Range("E" & row_count) = Comments
            xlSheet.Range("F" & row_count) = date_variable
            row_count = row_count + 1
        End If
        'split the textline at the string "Page: " and grab the second part of the split
        page_number = Split(textline, "Page: ")(1)
        'Change write_comments variable so next time the comments get written out
        write_comments = "Yes"
    'Look for author by checkign for string "Author: "
    ElseIf InStr(textline, "Author: ") > 0 Then
        'Determine if comments need to be written out
        If write_comments = "Yes" Then
            xlSheet.Range("A" & row_count) = comment_count
            xlSheet.Range("B" & row_count) = author_name
            xlSheet.Range("C" & row_count) = comment_type
            xlSheet.Range("D" & row_count) = page_number
            xlSheet.Range("E" & row_count) = Comments
            xlSheet.Range("F" & row_count) = date_variable
            row_count = row_count + 1
        End If
        'First split the line using "Author: " as the delimiter, grab the second string
        'Then split the second string by "Subject; ", grab the first string, this isolate the author's name
        author_name = Split(Split(textline, "Author: ")(1), "Subject: ")(0)
        'Do a double split to get the type using "Subject: " and "Date: " as the delimiters
        comment_type = Split(Split(textline, "Subject: ")(1), "Date: ")(0)
        'Single split is needed to get the date
        date_variable = Split(textline, "Date: ")(1)
        comment_count = comment_count + 1
        'Change write_comments variable so next time the comments get written out
        write_comments = "Yes"
    'Determine if first line is being read and then continue to next line
    ElseIf InStr(textline, "Summary of Comments on ") > 0 Then
        'Nothin needs to happen if its the first line
    'Read in comments
    Else
        Comments = Comments + " " + textline
    End If
Loop
End Sub
enter code here

Ответы [ 2 ]

0 голосов
/ 13 октября 2019

Вот еще один метод.

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

Если ваши данные не соответствуют представленным, кодирование необходимо будет изменить.

Базовый алгоритм

  • Создать объект (класс)свойства которого являются элементами, которые мы хотим отобразить
  • Сохранять каждый объект (представляющий строку таблицы) в словаре.
  • Считать весь файл в массив строк, разбитый на Page:
  • Для каждого элемента в приведенном выше массиве разбить на Author
  • Разделить столбец Автор на автора, тему, дату и комментарии
    • Создать комментарии и обработать dashed комментарии с тире в отдельной строке
  • Сохранение каждого набора элементов отдельно в виде объекта Dictionary
  • Вывод и форматирование результатов в таблице.

Обязательно прочитайте комментарии вверху модулей для получения информации о переименовании и настройке ссылок

Class Module

'RENAME TO cComment
Option Explicit
Private pPage As Long
Private pAuthor As String
Private pSubject As String
Private pDT As Date
Private pComment As String

Public Property Get Page() As Long
    Page = pPage
End Property
Public Property Let Page(Value As Long)
    pPage = Value
End Property

Public Property Get Author() As String
    Author = pAuthor
End Property
Public Property Let Author(Value As String)
    pAuthor = Value
End Property

Public Property Get Subject() As String
    Subject = pSubject
End Property
Public Property Let Subject(Value As String)
    pSubject = Value
End Property

Public Property Get DT() As Date
    DT = pDT
End Property
Public Property Let DT(Value As Date)
    pDT = Value
End Property

Public Property Get Comment() As String
    Comment = pComment
End Property
Public Property Let Comment(Value As String)
    pComment = Value
End Property

Обычный модуль

'Set Reference to Microsoft Scripting Runtime

Option Explicit
Sub orgComments()
    Dim fn As Variant
    Dim dC As Dictionary, cC As cComment
    Dim FSO As FileSystemObject, TS As TextStream

    Dim wsRes As Worksheet, vRes As Variant, rRes As Range

    Dim str() As String, V As Variant
    Dim sAuthComm() As String
    Dim lComNum As Long
    Dim sPage As String
    Dim sComments() As String

    Dim I As Long, J As Long

fn = Application.GetOpenFilename("Text Files (*.txt),*.txt")
If fn = False Then Exit Sub

Set FSO = New FileSystemObject
Set TS = FSO.OpenTextFile(fn, ForReading, False, TristateFalse)
str = Split(TS.ReadAll, "Page:")

Set dC = New Dictionary

'organize the data
For Each V In str()
    If Val(V) > 0 Then 'make sure we start with a page number that is 1 or greater
        sAuthComm = Split(V, "Author: ")
        sPage = Trim(sAuthComm(0))
        For I = 1 To UBound(sAuthComm)
            Set cC = New cComment
                With cC
                    .Page = sPage
                    .Author = Trim(Split(sAuthComm(I), "Subject:")(0))
                    .Subject = Trim(Split(Split(sAuthComm(I), "Subject:")(1), "Date:")(0))
                    .DT = Trim(Split(Split(sAuthComm(I), "Date:")(1), vbNewLine)(0))

                    sComments = Split(sAuthComm(I), vbNewLine)
                    For J = 1 To UBound(sComments)
                        If sComments(J) <> "" Then .Comment = .Comment & vbLf & sComments(J)
                    Next J
                    .Comment = Mid(.Comment, 2)

                    'Process dashed comments
                    .Comment = Replace(.Comment, "-" & vbLf, " - ")

                    lComNum = lComNum + 1
                    dC.Add Key:=lComNum, Item:=cC
                End With
        Next I
    End If
Next V

'organize the output
ReDim vRes(0 To dC.Count, 1 To 6)

'Headers
    vRes(0, 1) = "Comment No."
    vRes(0, 2) = "Reviewer Name"
    vRes(0, 3) = "Type"
    vRes(0, 4) = "Page Number"
    vRes(0, 5) = "Comment"
    vRes(0, 6) = "Date Submitted"

For Each V In dC.Keys
    With dC(V)
        vRes(V, 1) = V
        vRes(V, 2) = .Author
        vRes(V, 3) = .Subject
        vRes(V, 4) = .Page
        vRes(V, 5) = .Comment
        vRes(V, 6) = .DT
    End With
Next V

On Error Resume Next
    Set wsRes = ThisWorkbook.Worksheets("Results")
    If Err.Number = 9 Then
        ThisWorkbook.Worksheets.Add
        ActiveSheet.Name = "Results"
    Else
        If Err.Number <> 0 Then _
        MsgBox "Error " & Error & vbLf & "Error Num: " & Err.Number
    End If
On Error GoTo 0
Set wsRes = ThisWorkbook.Worksheets("Results")

'write to the worsheet and format the table
Set rRes = wsRes.Cells(1, 1).Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
    .EntireColumn.Clear
    .Value = vRes
    .Rows(1).HorizontalAlignment = xlCenter
    .Columns(6).NumberFormat = "mm/dd/yyyy hh:mm"
    .EntireColumn.AutoFit
    .EntireRow.AutoFit
    .VerticalAlignment = xlCenter

    'Format like a table, without making it a table
    With wsRes.ListObjects
        .Add xlSrcRange, rRes, , xlYes, , "TableStyleMedium9"
        .Item(.Count).Unlist 'omit this line if you want a "real table"
    End With
End With
End Sub

Результаты

enter image description here

0 голосов
/ 10 октября 2019

Итак, вам нужен «конечный автомат», который отслеживает то, что анализируется в текстовом файле в любой момент времени для любой данной строки. По большей части ваш текстовый файл выглядит так, как будто у него есть два основных ключевых слова: Page и Author. Кроме того, вы либо ожидаете обнаружения следующего ключевого слова, либо собираете (многострочный) комментарий. В простейшей форме конечный автомат часто выражается с помощью оператора Select Case:

Select Case
    Case "Page"
        '--- do something with the page number
    Case "Author"
        '--- do something with the author line
    Case Else
        '--- either wait for a keyword or collect the comment
End Select

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

Option Explicit

Sub main()
    Dim forumPostFile As String
    forumPostFile = "C:\Temp\ForumPostExample.txt"

    ExtractComments forumPostFile, Sheet1
End Sub

Sub ExtractComments(ByVal fullPathFilename As String, _
                    ByRef destWS As Worksheet)

    InitializeOutput destWS

    Dim commentNumber As Long
    commentNumber = 1

    Dim fso As FileSystemObject
    Set fso = CreateObject("Scripting.FileSystemObject")

    Dim forumFile As Object
    Set forumFile = fso.OpenTextFile(fullPathFilename, ForReading)

    Dim oneLine As String
    Dim state As String
    state = "LookForPage"

    Dim keywords() As String
    Dim page As Long
    Dim author As String
    Dim subject As String
    Dim timestamp As Date
    Dim comment As String
    Do Until forumFile.AtEndOfStream
        oneLine = forumFile.ReadLine
        If Len(oneLine) > 0 Then
            keywords = Split(oneLine, ":")
            Select Case keywords(0)
                Case "Page"
                    If state = "BuildComment" Then
                        CommentToSheet destWS, (commentNumber + 1), _
                                       commentNumber, author, subject, page, comment, timestamp
                        commentNumber = commentNumber + 1
                        comment = vbNullString
                    End If
                    page = keywords(1)
                    state = "LookForAuthor"

                Case "Author"
                    If state = "BuildComment" Then
                        CommentToSheet destWS, (commentNumber + 1), _
                                       commentNumber, author, subject, page, comment, timestamp
                        commentNumber = commentNumber + 1
                        comment = vbNullString
                    End If
                    author = Trim$(Left(keywords(1), Len(keywords(1)) - Len("Subject")))
                    subject = Trim$(Left(keywords(2), Len(keywords(2)) - Len("Date")))
                    timestamp = CDate(Right$(oneLine, Len(oneLine) - InStr(1, oneLine, "Date:") - Len("Date:")))
                    state = "BuildComment"

                Case Else
                    If state = "BuildComment" Then
                        comment = comment & oneLine
                    End If
            End Select
        End If
    Loop
    forumFile.Close
End Sub

Private Sub InitializeOutput(ByRef destWS As Worksheet)
    Dim header As Range
    Set header = destWS.Range("A1:F1")
    destWS.Cells.Clear
    With header
        .Cells(1, 1) = "Comment No."
        .Cells(1, 2) = "Reviewer Name"
        .Cells(1, 3) = "Type"
        .Cells(1, 4) = "Page Number"
        .Cells(1, 5) = "Comment"
        .Cells(1, 6) = "Date Submitted"
        .WrapText = True
        .Interior.Color = RGB(191, 191, 191)
        .Columns(1).EntireColumn.HorizontalAlignment = xlHAlignCenter
        .Columns(2).EntireColumn.HorizontalAlignment = xlHAlignCenter
        .Columns(3).EntireColumn.HorizontalAlignment = xlHAlignCenter
        .Columns(4).EntireColumn.HorizontalAlignment = xlHAlignCenter
        .Columns(5).EntireColumn.HorizontalAlignment = xlHAlignLeft
        .Columns(5).EntireColumn.WrapText = True
        .Columns(6).EntireColumn.HorizontalAlignment = xlHAlignLeft
    End With
End Sub

Private Sub CommentToSheet(ByRef destWS As Worksheet, _
                           ByVal row As Long, _
                           ByVal number As Long, _
                           ByVal author As String, _
                           ByVal subject As String, _
                           ByVal pageNumber As Long, _
                           ByVal comment As String, _
                           ByVal timestamp As Date)
    With destWS.Rows(row)
        .Cells(1, 1) = number
        .Cells(1, 2) = author
        .Cells(1, 3) = subject
        .Cells(1, 4) = pageNumber
        .Cells(1, 5) = comment
        .Cells(1, 6) = timestamp
    End With
End Sub
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...