Создать таблицу html из отфильтрованной таблицы Excel

Мне нужна небольшая помощь, поскольку я пытаюсь вставить отфильтрованную таблицу Excel в открытое сообщение электронной почты Outlook. Мне удалось сделать это с помощью функции, которую я нашел в Интернете (ExcelRangeToOutlookEmailBody), но она заменяет сообщение, которое у меня было перед вставкой таблицы, и я также не могу добавить какой-либо текст над или под таблицей, поэтому теперь я пытаюсь написать подпрограмму, которая динамически создает таблицу html, поскольку кажется, что с ней легче манипулировать, чтобы вставлять текст до и после таблицы.

У меня есть таблица с количеством проектов, приписываемых нескольким людям, и я хочу отфильтровать таблицу по каждому человеку, скопировать видимые строки и вставить ее в открытое сообщение электронной почты. Я хочу просто нажать кнопку, чтобы Excel создал несколько (обычно менее 10) сообщений электронной почты с отфильтрованной таблицей.

Код, который у меня есть, представляет собой смесь того, что я нашел в Интернете, плюс некоторые мои изменения, и он динамически строит таблицу html. Однако он работает только для первого человека, и с этого момента ему не удается построить таблицу html — он просто повторяет первую отфильтрованную таблицу. Чтобы упростить ситуацию, код ниже — это просто построитель html-таблиц, с остальным я разобрался (по крайней мере, я так думаю).

Sub HtmlTableBuilder()

Dim Wb As Workbook
Dim ws As Worksheet
Dim wsBD As Worksheet
Dim Table As ListObject
Dim TableMail As ListObject
Dim Col As Range
Dim count As Integer
Dim finalTable As Range
Dim result As Variant
Dim values As Variant
Dim dic As Scripting.Dictionary
Dim valCounter As Long
Dim rngHdr As Range
Dim rngDat As Range

Set Wb = Workbooks("MyWorkbook.xlsb")
Set ws = Wb.Worksheets("FL")
Set wsBD = Wb.Worksheets("BD")                                                  'Person database
Set Table = ws.ListObjects("TabMail")                                           'Table to be filtered, copied and pasted to email
Set TableMail = wsBD.ListObjects("People")                                      'Table with names, ID and emails of each person
Set Col = Range("TabMail[PersonID]")                                            'Column with the IDs
Set dic = New Scripting.Dictionary                                              ' Add reference to MS Scripting Runtime

'Extract all person names from an array

 values = ws.Range("G5:G1000").Value2                                           'Value2 is faster than Value
 dic.CompareMode = BinaryCompare                                                'Set the comparison mode to case-sensitive

 For valCounter = LBound(values) To UBound(values)                              'Loop to extract name of persons
    If Not dic.Exists(values(valCounter, 1)) Then                               'Check if the name is already in the dictionary
        dic.Add values(valCounter, 1), 0                                        'Add the new name as key, with a dummy value of 0
    End If
 Next valCounter

 result = dic.Keys                                                              'Extract the dictionary's keys as a 1D array
 count = UBound(result)                                                         'number of persons

'Filter a table by a person name a build a html table with the visible data to send via email
i = 0
 Do While i <= count - 1
    With Range("A4")                                                            'table first cell is A4; count = number of persons
        Col.AutoFilter Field:=7, Criteria1:=result(i)                           'filters table by person i
        Set rng = Table.HeaderRowRange                                          'gets the table header
        Set rngHdr = rng.Resize(, 6)                                            'discards last column
        Set rng = Table.DataBodyRange.SpecialCells(xlCellTypeVisible)           'gets table visible celss
        Set rngDat = rng.Resize(, 6)                                            'discards last column
        Set finalTable = Union(rngHdr, rngDat)                                  'joins header and body
        
        'loop to build html tables
        
        R = 0                                                                   'initializes row counter
            If finalTable.Rows.count > 1 Then                                   'condition to check if filtered table isn't empty
                htmlstr = "<table border=1 style='border-collapse: collapse'>"  'html string start
                For Each rngrow In finalTable.Rows                              'loop rows
                    c = 0: R = R + 1                                            'Initializes row & column counter
                    htmlstr = htmlstr & "<tr>"                                  'html string row beginning
                    For Each rngcol In finalTable.Columns                       'loop columns to each row
                        c = c + 1
                        rngvalue = finalTable(R, c).Value
                         If R = 1 Then                                          'checks if is first row to format as header
                            htmlstr = htmlstr & "<th>" & rngvalue & "</th>"
                        Else                                                    'formats as body row
                            htmlstr = htmlstr & "<td>" & rngvalue & "</td>"
                        End If
                    Next rngcol
                 htmlstr = htmlstr & "</tr>"                                    'html string row ending
                Next rngrow
                htmlstr = htmlstr & "</table>"                                  'html string table ending
            End If
        Debug.Print htmlstr                                                     'Debug to output results to immediate window
    End With
    
    i = i + 1
 
 Loop

End Sub

Ниже приведена таблица (TableMail), которую мне нужно отфильтровать по идентификатору человека. Обратите внимание, что эта таблица связана с другой таблицей на другом листе, где она получает все свои значения, кроме крайнего срока, который необходимо ввести пользователю. Пустые ячейки в последних трех строках на самом деле не пусты, они содержат те же формулы, что и другие ячейки, просто у меня есть условное форматирование пустых ячеек. Формулы аналогичны этой: =IF(MyWorkbook.xlsb!TabMail[@[Project_ID]]="";"";"All")

| Table     | Project_ID    | Task Assigned     | Qtty 1    | Qyy2  | Deadline          | PersonID  |
|---------- |------------   |---------------    |--------   |------ |-----------------  |---------- |
| Project2  | 790403        | All               | 20        | 30    | 06/01/24 13:00    | 104       |
| Project2  | 790536        | All               | 40        | 50    | 06/01/24 13:00    | 104       |
| Project1  | 790539        | All               | 2         | 0     | 06/01/24 13:00    | 104       |
| Project2  | 790661        | All               | 224       | 1,2   | 09/02/24 13:00    | 104       |
| Project1  | 790685        | All               | 1         | 0     | 09/02/24 13:00    | 103       |
| Project1  | 790977        | All               | 0         | 19,8  | 09/02/24 13:00    | 103       |
| Project2  | 799103        | All               | 299       | 4,8   | 09/02/24 13:00    | 103       |
| Project1  | 799372        | All               | 35        | 0,6   | 06/01/24 13:00    | 102       |
| Project1  | 799420        | All               | 0         | 87    | 06/01/24 13:00    | 102       |
| Project1  | 790691        | All               | 56        | 40,2  | 06/01/24 13:00    | 101       |
| Project1  | 790864        | All               | 15        | 0,6   | 09/02/24 13:00    | 101       |
| Project1  | 790907        | All               | 267       | 3,6   | 09/02/24 13:00    | 101       |
|           |               |                   |           |       | xx/xx/24  13:00   |           |
|           |               |                   |           |       | xx/xx/24  13:00   |           |
|           |               |                   |           |       | xx/xx/24  13:00   |           |

В кодексе также упоминается вторая таблица (Люди):

| PersonID  | Name      | MAIL                                  | Step1     | Step2     |
|---------- |---------- |-------------------------------------  |-------    |-------    |
| 95        | Bart      | [email protected]                | ide3      | idv2      |
| 96        | Maggie    | [email protected]                 | ide4      | idv3      |
| 97        | Lisa      | [email protected]                   | ide8      | idv1      |
| 98        | Homer     | [email protected]                 | ide3      | idv5      |
| 99        | Marge     | [email protected]              | ide5      | idv4      |
| 100       | Flanders  | [email protected]             | ide2      |           |
| 101       | Peter     | nomorefunnynames@gmail                | ide1      |           |
| 102       | Lois      | [email protected]             | ide11     |           |
| 103       | Meg       | [email protected]   | ide9      |           |
| 104       | Chris     | [email protected]          | ide6      |           |
| 105       | Brian     | [email protected]                 | ide7      |           |

Вот вывод приведенного выше кода

У меня есть идентификаторы 4 человек, но этот цикл всегда выводит одну и ту же таблицу, а не одну таблицу для каждого человека:

| Project   | Project_ID    | Task Assigned     | Qtty 1    | Qtty 2    | Deadline              |
|---------- |------------   |---------------    |--------   |--------   |-------------------    |
| Project2  | 790403        | All               | 20        | 30        | 06/01/24 13:00:00     |
| Project2  | 790536        | All               | 40        | 50        | 06/01/24 13:00:00     |
| Project1  | 790539        | All               | 2         | 0         | 06/01/24 13:00:00     |
| Project2  | 790661        | All               | 224       | 1,2       | 09/02/24 13:00:00     |

Кажется, проблема заключается в том, что я собираю заголовок и тело таблицы:

Set finalTable = Union(rngHdr, rngDat) 

Я проверил, и rngHdr, и rngDat кажутся в порядке, но FinalTable, кажется, содержит только заголовок, что странно, потому что он выводит проекты предыдущего человека.

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

Остальная часть кода, похоже, работает нормально, он последовательно фильтрует TableMail по каждому идентификатору человека, и с помощью моего полного кода я получаю необходимую информацию из второй таблицы «Люди» и могу открыть 4 новых сообщения электронной почты, каждое с правильный получатель, тема и приветствие, и я также могу объединить таблицу с остальным текстом электронного письма. Я просто не понимаю, почему он продолжает вставлять одну и ту же первую отфильтрованную таблицу.

If finalTable.Rows.count > 1 Then этот тест возвращает true, только если первый Area из finalTable имеет > 1 строки. Поскольку вы не очищаете htmlstr между итерациями, вы всегда получаете одну таблицу, для которой тест проходит.
Tim Williams 26.02.2024 19:38

Попробуйте вместо этого использовать If finalTable.Cells.Count > finalTable.Rows(1).Cells.Count Then

Tim Williams 26.02.2024 20:59
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
0
3
209
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Использование одной таблицы для иллюстрации и разбиение функциональности на отдельные методы, где, по моему мнению, это имеет смысл:

Sub HtmlTableBuilder()

    Dim Wb As Workbook, ws As Worksheet
    Dim Table As ListObject, Col As ListColumn, html As String
    Dim dic As Object, rngDat As Range, rngVis As Range, k
    
    Set Wb = ThisWorkbook 'Workbooks("MyWorkbook.xlsb")
    Set ws = Wb.Worksheets("FL")
    Set Table = ws.ListObjects("TabMail")     'Table to be filtered
    Set Col = Table.ListColumns("PersonID")   'Column with the IDs
    
    Set dic = UniquesFromRange(Col.DataBodyRange) 'get unique values

    For Each k In dic.Keys    'loop over dictionary keys
        Table.Range.AutoFilter Field:=Col.Index, Criteria1:=k
        Set rngVis = Table.Range.SpecialCells(xlCellTypeVisible)
        html = AsHtmlTable(rngVis)
        If Len(html) > 0 Then
            Debug.Print html
            Debug.Print "---------------------"
        Else
            Debug.Print "No rows for " & k 'should never happen....
        End If
    Next k
    Table.AutoFilter.ShowAllData
    
End Sub

'Convert a range `rng` to html
'  `rng` should include headers on row#1: if only one row then no html is created
Function AsHtmlTable(rng As Range) As String
    Dim html As String, rw As Range, c As Range, tag As String
    If rng.Cells.count = rng.Rows(1).Cells.count Then Exit Function 'nothing to build...
    html = "<table border=1 style='border-collapse: collapse'>"
    tag = "th" 'headers to start....
    For Each rw In rng.Rows
        html = html & "    <tr>"
        For Each c In rw.Cells
            html = html & "<" & tag & ">" & c.Value & "</" & tag & ">"
        Next c
        html = html & "</tr>" & vbLf
        tag = "td" 'regular td for rest of rows
    Next rw
    AsHtmlTable = html & "</table>"
End Function

'return a dictionary object with all unique values from `rng`
Function UniquesFromRange(rng As Range) As Object
    Dim c As Range, tmp
    Set UniquesFromRange = CreateObject("scripting.dictionary")
    UniquesFromRange.CompareMode = 0 'vbBinaryCompare: case-insensitive
    For Each c In rng.Cells
       tmp = Trim(c.Value)
       If Len(tmp) > 0 Then
            If Not UniquesFromRange.Exists(tmp) Then UniquesFromRange.Add tmp, 1
       End If
    Next c
End Function

Это прекрасно работает, хотя мне не нужен последний столбец PersonID в таблице html, поэтому для достижения этой цели я добавил две строки. Columns("G").Hidden = True и Columns("G").Hidden = False Есть возможно, более элегантные и оптимальные решения для достижения этой цели, но это работает

smAC 27.02.2024 13:49

Другие вопросы по теме