VBA – Creating an HTML Document

I made a couple recent posts regrading mapping, encoding, decoding, all web related stuff

so I thought I’d demonstrate how one can use VBA to dynamically create, or manipulate, an HTML document.

Creating a MSHTML HTML Document via VBA

Public Function HF_CreateHTMLDoc() As String
    On Error GoTo Error_Handler
    #Const HF_EarlyBind = False     'True => Early Binding / False => Late Binding
                                    'Typically a Global Module Level Variable
    #If HF_EarlyBind = True Then
        Dim oHTMLFile         As MSHTML.HTMLDocument
        Dim oElem             As MSHTML.HTMLGenericElement
        Dim oLink             As MSHTML.HTMLLinkElement
        Dim oTable            As MSHTML.HTMLTable
        Dim oTr               As MSHTML.HTMLTableRow
        Dim oTd               As MSHTML.HTMLTableCell

        Set oHTMLFile = New MSHTML.HTMLDocument
    #Else
        Dim oHTMLFile         As Object
        Dim oElem             As Object
        Dim oLink             As Object
        Dim oTable            As Object
        Dim oTr               As Object
        Dim oTd               As Object

        Set oHTMLFile = CreateObject("HTMLFile")
    #End If

    oHTMLFile.body.innerHTML = ""    'otherwise the body defaults to <p>&nbsp;</p>

    'Add to the header
    '**************************************************
    'Add meta charset
    Set oElem = oHTMLFile.createElement("meta")
    Call oElem.setAttribute("charset", "UTF-8")
    Call oHTMLFile.head.appendChild(oElem)
    'Add title
    Set oElem = oHTMLFile.createElement("title")
    oElem.innerText = "Testing Title"    'Should Encode!
    Call oHTMLFile.head.appendChild(oElem)
    'Add a script
    Set oElem = oHTMLFile.createElement("script")
    Call oElem.setAttribute("id", "jquery-easing-js")
    Call oElem.setAttribute("type", "text/javascript")
    Call oElem.setAttribute("src", "//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js?ver=6.1.1")
    Call oHTMLFile.head.appendChild(oElem)

    'Add to the body
    '**************************************************
    'Add a paragraph
    Set oElem = oHTMLFile.createElement("p")
    oElem.innerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."    'Should Encode!
    Call oHTMLFile.body.appendChild(oElem)

    'Add a link
    Set oLink = oHTMLFile.createElement("a")
    'Properties are all messed up!
    '    oLink.href = "https://www.google.com" '=> rel
    '    oLink.rel = "https://www.google.com" '=> rev
    '    oLink.rev = "https://www.google.com" '=>urn
    '    oLink.Target = "_blank" 'Appends itself to the href?!
    Call oLink.setAttribute("href", "https://www.google.com")
    Call oLink.setAttribute("target", "_blank")
    oLink.innerText = "Open Google Search"
    Set oElem = oHTMLFile.createElement("p")
    Call oElem.appendChild(oLink)
    Call oHTMLFile.body.appendChild(oElem)

    'Add table
    Set oTable = oHTMLFile.createElement("table")
    Call oTable.setAttribute("id", "myTable")
    Call oTable.setAttribute("border", "1 | 0")
    'Build the header
    oTable.createTHead
    Set oTr = oHTMLFile.createElement("tr")
    'Create cell
    Set oTd = oHTMLFile.createElement("th")
    oTd.innerText = "th1 td1"
    'add cell to row
    Call oTr.appendChild(oTd)
    'create another cell
    Set oTd = oHTMLFile.createElement("th")
    oTd.innerText = "th1 td2"
    'add cell to row
    Call oTr.appendChild(oTd)
    'add row to table
    Call oTable.tHead.appendChild(oTr)
    'Create row 1 *
    Set oTr = oHTMLFile.createElement("tr")
    'Create cell
    Set oTd = oHTMLFile.createElement("td")
    oTd.innerText = "tr1 td1"
    'add cell to row
    Call oTr.appendChild(oTd)
    'create another cell
    Set oTd = oHTMLFile.createElement("td")
    oTd.innerText = "tr1 td2"
    'add cell to row
    Call oTr.appendChild(oTd)
    'add row to table
    Call oTable.appendChild(oTr)
    'Create row 2 *
    Set oTr = oHTMLFile.createElement("tr")
    'Create cell
    Set oTd = oHTMLFile.createElement("td")
    Call oTd.setAttribute("colspan", "2")
    oTd.innerText = "tr2 td1"
    'add cell to row
    Call oTr.appendChild(oTd)
    'add row to table
    Call oTable.appendChild(oTr)
    'add table to body
    Call oHTMLFile.body.appendChild(oTable)

Error_Handler_Exit:
    On Error Resume Next
    Set oTd = Nothing
    Set oTr = Nothing
    Set oTable = Nothing
    Set oElem = Nothing
    Set oHTMLFile = Nothing
    Exit Function

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Source: HF_CreateHTMLDoc" & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Function

Now this is just a sample, but ultimately it creates a proper HTML document like

<html>
    <head>
        <meta charset="UTF-8" />
        <title>Testing Title</title>
        <script id="jquery-easing-js" src="//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js?ver=6.1.1" type="text/javascript"></script>
    </head>
    <body>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
        <p><a href="https://www.google.com" target="_blank">Open Google Search</a></p>
        <table id="myTable" border="1">
            <thead>
                <tr>
                    <th>th1 td1</th>
                    <th>th1 td2</th>
                </tr>
            </thead>
            <tr>
                <td>tr1 td1</td>
                <td>tr1 td2</td>
            </tr>
            <tr>
                <td colspan="2">tr2 td1</td>
            </tr>
        </table>
    </body>
</html>

Removing Elements

In my example I demonstrate the creation of various elements using createElement and appendChild, but what about deleting/removing elements? Well is it equally simple.

If you have the object variable for the element in question you can simply use removeChild. For instance, to remove the table from the example above we could do:

call oHTMLFile.body.removeChild(oTable)

If on the other hand we don’t have the variable, then we can do something along the lines of:

Dim oTableToDelete as Object
set oTableToDelete = oHTMLFile.getElementById("MyTable")
If Not oTableToDelete Is Nothing Then call oHTMLFile.body.removeChild(oTableToDelete )

So we first find it, in this instance by it’s id using the getElementById() and then we delete it.

Checking the generated HTML

To get the full document HTML you can do

oHTMLFile.documentElement.outerHTML

To get the head’s HTML you can do

oHTMLFile.head.innerHTML

To get the body’s HTML you can do

oHTMLFile.body.innerHTML

Displaying the MSHTML.HTMLDocument in a Web Browser Control

If you want you can push the MSHTML.HTMLDocument and display it in a Web Browser control by doing something like:

If your function returns a MSHTML.HTMLDocument

    With Me.WebBrowser0.Object.Document
        .Open
        .Write HF_CreateHTMLDoc.documentElement.outerHTML
        .Close
    End With

or, if your function return the MSHTML.HTMLDocument as a string

    With Me.WebBrowser0.Object.Document
        .Open
        .Write HF_CreateMSHTMLDocument
        .Close
    End With

So this would allow you to create and display HTML without ever reading/writing anything to the HD.

Word of Caution

If you review my code for creating a link, you’ll notice several lines commented out of things I tried and did not work as expected amongst other oLink.href! So be forewarned that there appear to be bugs with some of the properties/methods. So if you encounter issue, try to find workarounds as I did.

Also note, I already reported some of these issue to Microsoft, for whatever that is worth.

Documentation

Now this was just a generic example to illustrate how you can create a few different elements in the header or body to show how it is done as support documentation is very scarce and even when you do find some it is extremely basic to the point of being pretty much useless. I highly recommend using Early Binding for development and only switching to Late Binding when deploying. The Early Binding intellisense is a life saver in this instance!

Furthermore, Help from within the VBE is useless as whenever I tried using F1 to get some help on any of the properties/methods it always redirected me to a ‘Keyword not found‘ page. Not once did I actually get to a help page relating to MSHTML or the property/method that was actively selected! Once again, this has been reported to Microsoft.
 

Other Resources on the Subject

2 responses on “VBA – Creating an HTML Document

  1. Emanuele

    With Option Explicit, I got an error: It’s necessary “Dim oLink As Object” in the declarations section above.

    1. Daniel Pineault Post author

      Sorry about that, I don’t know how I overlooked that declaration in the Late Binding version of the code. Thank you for pointing it out, I have updated the code.