Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does anyone know how to handle PDF downloads/uploads? #456

Closed
nhorton79 opened this issue May 20, 2021 · 7 comments
Closed

Does anyone know how to handle PDF downloads/uploads? #456

nhorton79 opened this issue May 20, 2021 · 7 comments

Comments

@nhorton79
Copy link

I'm using VBA-Web to access the Xero API and the API reference states I can download an invoice PDF by specifying "application/pdf" in the Accept header.

There doesn't appear to be anything as a standard WebFormat, so would need to be Custom.

I imagine that I would set the values like:

auth_Request.Method = WebMethod.HttpGet
auth_Request.RequestFormat = WebFormat.FormUrlEncoded
auth_Request.Accept = "application/pdf"
WebHelpers.RegisterConverter "pdf", "application/pdf", "MyPDFConverter", "MyPDFParser", , "Binary"
auth_Request.CustomResponseFormat = "pdf"

I imagine that this is correct?

However, I suppose my real question is how to handle the downloading of the file. How to code the two methods to parse and convert the PDF file.

Has anyone done into this before?

@RichardWein
Copy link

Hi nhorton79. I haven't used the Xero API. But most likely the response body will just be the binary contents of the PDF file. It shouldn't need any parsing. You basically just need to write response.Body to a file, treating response,Body as a VBA Byte array.

Here's some code I wrote a while ago, for downloading a file from Google Drive API:

Public Sub DownloadFileFromGoogleDrive(ByVal fileId As String, ByVal exportToFullFileName As String)

    ' Downloads a file from Google Drive to the given file path/name
    ' NOTE: For Google Workspace documents (including Google Sheets), use Export instead.

    ' Set up request
    Dim request As New WebRequest
    request.method = WebMethod.HttpGet
    request.Resource = "https://www.googleapis.com/drive/v3/files/{fileId}"
    request.AddUrlSegment "fileId", fileId
    request.AddQuerystringParam "alt", "media" ' alt = media indicates that we want to download content, not metadata
    request.ResponseFormat = WebFormat.PlainText
    ' The response body could be binary, but WebFormat.PlainText just tells VBA-Web not to interpret it as JSON etc

    ' Make request
    Dim response As WebResponse
    Set response = ExecuteRequest(request, GetGoogleClient)

    ' Handle response
    If Not RequestWasSuccessful(response) Then
        Err.Raise CUSTOM_ERROR, , WebResponseErrorDescription(response)
    Else
        Dim bodyBytes() As Byte
        bodyBytes = response.body
        Call WriteBinaryFile(exportToFullFileName, bodyBytes)
    End If

End Sub

Note that I've set the ResponseFormat to WebFormat.PlainText, which just stops VBA-Web from parsing the response. It doesn't matter whether the file contents are actually text or binary.

FYI "ExecuteRequest" is my own wrapper for WebClient.Execute

Try something like this. If the resulting file is not a valid PDF file, then take a closer look at it (e.g. with a hex editor) to see what you've got.

@RichardWein
Copy link

P.S. Taking a fresh look at my code, I noticed a couple of things...

  1. There was no need to use the "bodyBytes" variable. I could have just written:
    Call WriteBinaryFile(exportToFullFileName, response.body)

  2. A problem with using request.ResponseFormat = WebFormat.PlainText is that, if you get an error response with JSON-encoded error information, the error information won't be parsed. In the case of the Google Drive API, this can happen. I don't know how the Xero API handles errors. The trouble is that (at least in the case of Google Drive) the response could be either JSON-encoded or not, depending on whether you get an error response or not. As far as I can see, VBA-Web doesn't have a built-in way to handle this. I'll have to write my own code to handle it.

@nhorton79
Copy link
Author

Hi Richard,
Thanks for your response.
I'll have to have a play around with the code you've provided and see what I can get to work.

I originally tried setting this a WebFormat.PlainText however, the Xero API didn't like this as it wouldn't accept an Accept header of "text/plain", so I may have to register a custom converter/parser that just passes the response body unaltered.

One thing I can see that I don't have in the current Webhelpers or VBA-Web is "WriteBinaryFile".
Would you be able to supply the code you have for this function/sub?

@RichardWein
Copy link

Instead of writing your own custom format, perhaps you could use request.SetHeader to override the the Accept header that VBA-Web sets. I've never tried that though.

Here's my WriteBinaryFile sub...

Public Sub WriteBinaryFile(ByVal fullFileName As String, ByRef output() As Byte)

    Dim fileNum As Long
    
    ' Delete any existing file with this name
    On Error Resume Next
    Kill fullFileName
    
    On Error GoTo FileError
    fileNum = FreeFile
    Open fullFileName For Binary Access Write As #fileNum
    Put #fileNum, 1, output
    Close #fileNum
    
    Exit Sub

FileError:
    Err.Raise CUSTOM_ERROR, , "Error writing file '" & RemovePathFromFileName(fullFileName) & "'" & vbNewLine & vbNewLine & Err.description
    
End Sub

@nhorton79
Copy link
Author

Hi Richard,

I tried using PlainText and then using request.SetHeader = "Accept", "application/pdf", however this didn't work.

So I went with:

Public Function GetInvoicePDF _
                    (Client As WebClient, _
                     XeroInvoiceID As String, _
                     SaveLocation As String) _
                     As String

    On Error GoTo GetInvoicePDF_Cleanup
    
    Dim InvoiceNumber As Long
    InvoiceNumber = XeroAuth.GetInvoiceNo("ef52bb9f-6a2d-4a0b-82eb-f9126c402694")
    
    Dim auth_TokenClient As WebClient
    Dim auth_Request As New WebRequest
    Dim auth_Response As WebResponse
    
    ' Clone client (to avoid accidental interactions)
    Set auth_TokenClient = Client.Clone
    Set auth_TokenClient.Authenticator = Nothing
    auth_TokenClient.BaseUrl = "https://api.xero.com/api.xro/2.0"
    
    ' Prepare token request
    auth_Request.Resource = "Invoices/{Id}"
    auth_Request.AddUrlSegment "Id", XeroInvoiceID
    
    auth_Request.Method = WebMethod.HttpGet
    auth_Request.RequestFormat = WebFormat.FormUrlEncoded
    
    auth_Request.AddHeader "Authorization", "Bearer " & Me.AccessToken
    auth_Request.AddHeader "Xero-tenant-id", Me.TenantID
    
    'Response Formatting
    auth_Request.Accept = "application/pdf"
    auth_Request.CustomResponseFormat = "pdf"
    WebHelpers.RegisterConverter "pdf", "application/pdf", "ConvertToPDF", "ParsePDF", , "Binary"
    
    ' Execute request
    Set auth_Response = auth_TokenClient.Execute(auth_Request)
    
    If auth_Response.StatusCode = WebStatusCode.Ok Then
    
        Dim strFileName As String
        CheckTrailingChars SaveLocation, "\"
        strFileName = SaveLocation & "Invoice " & InvoiceNumber & ".pdf"
        
        WriteBinaryFile strFileName, auth_Response.Body
        
        GetInvoicePDF = strFileName
        
    Else
        Err.Raise 11043 + vbObjectError, "XeroAuth.GetInvoicePDF", _
            auth_Response.StatusCode & ": " & auth_Response.content
    End If
    
GetInvoicePDF_Cleanup:

This download the invoice PDF file to the location specified.

Regarding the ConvertToPDF and ParsePDF functions, all I did was copy what Tim had suggested in #61
Which are essentially just empty. Its hacky but works like a charm.
It would be good to get this working like freiwerk did in #432 and #433 however that may be something for another day.

Thank you for your assistance. I'll close this off shortly.

@TK-99
Copy link

TK-99 commented Jan 11, 2023

If anyone is still interested, I have managed to get pdfs uploading to Sharepoint via the REST API and now the Graph API using an ADODB stream

@nhorton79
Copy link
Author

If anyone is still interested, I have managed to get pdfs uploading to Sharepoint via the REST API and now the Graph API using an ADODB stream

I would always be interested in checking something like that out TK-99. Do you want to open this as another "issue" and include the issue number here for those who follow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants