Search code examples
vbapdfbase64decode

Out of string space when decoding base 64 to PDF in VBA


I'm getting the aforementioned run-time error 'Out of string space' when decoding a base 64 string into a PDF document. This obviously only happens when decoding larger documents (164 page PDF in this case) but is there any way to adjust the method to cope with larger base 64 strings?

The code used is below -

Private Sub Apply_Click()

Dim strPath As String
Dim b64test As String

User = Split(Application.username, " ")

   
b64 = FinalResult
strPath = "C:\Users\" & User(1) & "\Downloads" & "\" & ConsignmentNumber & " Invoice.pdf"
 
 
Open strPath For Binary As #1
Put #1, 1, DecodeBase64(b64)
Close #1

 
 
End Sub

Private Function DecodeBase64(ByVal strData As String) As Byte()

    Dim objXML As Object
    Dim objNode As Object

    
    Set objXML = CreateObject("MSXML2.DOMDocument")

    
    Set objNode = objXML.createElement("b64")
    objNode.DataType = "bin.base64"
    objNode.Text = strData
    DecodeBase64 = objNode.nodeTypedValue

   
    Set objNode = Nothing
    Set objXML = Nothing

End Function

Solution

  • This seems to work for me. You can base64 encode/decode in chunks, as long as you use a multiple of 3 bytes going in, and 4 characters the other way.

    I was able to repeatedly encode/decode a >200MB file with no errors, but it's not very fast (decode is slower than encode).

    Option Explicit
    
    'Show a round-trip encode/decode for a binary file to base64 .dat file and back...
    'Add a VB Project reference to the ADODB object library.
    Sub Base64RoundTrip()
        
        Const BYTESCHUNK As Long = 1800000
        Const B64CHUNK As Long = 2048000
        
        Dim bytes() As Byte, inBytes, b64 As String, diff As Long, inFile As String, outFile As String
        Dim folder As String, streamIn As ADODB.stream, streamOut As ADODB.stream, datFile As String
        
        folder = "C:\Temp\B64\"           'all files are here
        datFile = folder & "b64.dat"      'for the Base64-encoded content
        inFile = folder & "test.pdf"      'the file being encoded
        outFile = folder & "test 2.pdf"   'round-trip output file
        
        '### Encode process ###
        KillIfExists datFile
        
        Set streamIn = New ADODB.stream
        With streamIn
            .Type = adTypeBinary
            .Open
            .LoadFromFile inFile
        End With
        
        Set streamOut = New ADODB.stream
        With streamOut
            .Open
            .Type = adTypeText
            .Charset = "utf-8"
        End With
        
        Do
            'Read the file to be encoded in chunks, and append the
            '   base 64-encoded text to the .dat file
            inBytes = streamIn.Read(BYTESCHUNK)
            If IsNull(inBytes) Then Exit Do
            streamOut.WriteText EncodeBase64(inBytes)
        Loop
        streamIn.Close
        streamOut.SaveToFile datFile
        streamOut.Close
        Debug.Print "encoded"
        'done creating B64 file
        
        
        '### Decode process - read in the B64 file and decode in chunks ###
        Set streamIn = New ADODB.stream
        With streamIn
            .Open
            .Type = adTypeText
            .Charset = "utf-8"
            .LoadFromFile datFile
        End With
        
        Set streamOut = New ADODB.stream
        With streamOut
            .Open
            .Type = adTypeBinary
        End With
        
        KillIfExists outFile
        
        Do
            If Not streamIn.EOS Then
                b64 = b64 & streamIn.ReadText(B64CHUNK * 1.05) 'padding for vbLf replacement
                b64 = Replace(b64, vbLf, "") 'remove any newlines
            Else
                Debug.Print "EOS" 'no more file content
            End If
            
            bytes = DecodeBase64(Left(b64, B64CHUNK)) 'convert first B64CHUNK characters
            streamOut.Write bytes                     'write to output stream
            diff = Len(b64) - B64CHUNK
            b64 = Right(b64, IIf(diff < 0, 0, diff))  'remove the part we just encoded
            If Len(b64) = 0 Then Exit Do              'out of text to encode...
        Loop
    
        streamIn.Close
        streamOut.SaveToFile outFile
        streamOut.Close
        Debug.Print "decoded"
        
    End Sub
    
    Function DecodeBase64(ByVal strData As String) As Byte()
        With CreateObject("MSXML2.DOMDocument").createElement("b64")
            .DataType = "bin.base64"
            .Text = strData
            DecodeBase64 = .nodeTypedValue
        End With
    End Function
    
    Function EncodeBase64(bytes) As String
        With CreateObject("MSXML2.DOMDocument").createElement("b64")
            .DataType = "bin.base64"
            .nodeTypedValue = bytes
            EncodeBase64 = .Text
        End With
    End Function
    
    'kill a file: ignore error if it doesn't exist
    Sub KillIfExists(fPath As String)
        On Error Resume Next
        Kill fPath
        On Error GoTo 0
    End Sub