I have set up a keyboard shortcut (ctrl+alt+c) That calculates the users selection pretty basic I grabbed off of here
Private Sub Workbook_Open()
Application.OnKey "^%c", "CalculateSelection"
End Sub
Sub CalculateSelection()
On Error GoTo NoSelection
If Not Selection Is Nothing Then
If TypeName(Application.Selection) = "Range" Then
Dim Rng As Range
Set Rng = Application.Selection
Rng.Calculate
End If
End If
Exit Sub
NoSelection:
MsgBox ("No Range Selected")
End Sub
This works perfectly fine on all other cell and i can save my work when its all completed.
If I use this function on a cell that calls an API the function works fine until you save, Microsoft stops working entirely and I have no idea why.
Does anyone know why this might be happening? would this be cause by some object not being destroyed or is my excel corrupt?
All the API function work fine I've had no errors with them until I used this sub on them.
This is the function that sends the data to the API. It has different functions that are called from the sheet to format the data to XML and check whether the data being passed is correct and fully calculated.
Private Function PostToApiXmlToXml(Data As String, Route As String)
'Initialise Document Object Model to hold the XML
'And the xmlHttp to handle Sending Data
Dim xmlhttp As MSXML2.ServerXMLHTTP60
Dim XMLDOM As MSXML2.DOMDocument60
Set xmlhttp = CreateObject("MSXML2.ServerXMLHTTP")
Set XMLDOM = New MSXML2.DOMDocument60
'Load entire Document before moving on
'Data is a string but must conatin opening and closing tags.
XMLDOM.async = False
XMLDOM.LoadXML (Data)
'Using the Http object
With xmlhttp
Call .setOption(2, SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS)
'Set up a POST to the Base Address + Route
.Open "POST", BaseAddress + Route, False
'Content-Type Header is requred to let the API Know What type of Data you are Sending
.setRequestHeader "Content-Type", "application/xml"
'Accept Header Lets the Api know we want to recieve JSON Data
.setRequestHeader "Accept", "application/json"
'Send the Dom as XML in the http object
.send (XMLDOM.Xml)
'Take in the Response as text
PostToApiXmlToXml = .responseText
End With
Set xmlhttp = Nothing
End Function
I can use the keyboard shortcut anywhere on the sheet and save with no problems. and i can use it on single cells with API calls and press ctrl + s and it saves fine. But if i select multiple and press ctrl + alt + c then save with ctrl + s it corrupts the save and restores to previous save doesn't let me open or view any of the problems with the corrupted version.
Related
I have created userform and try to input parameters and invoke the api url to “create incident”
api url:https://***.com/incident/create
to create an incident http post a json payload to the url above,example as below
{
“state”:“**”
“caller_id”:“**”
.
.
.
“urgency”=“**”
}
outlook userfrom has been created for inputting parameters just to match the json,so how could user invoke the api url in the userform to “create incident” in the userform ui?
Here is an example which shows how you can use web calls from VBA (not only from user forms):
Sub listPokemons()
Dim json As String
Dim jsonObject As Object, item As Object
Dim i As Long
Dim ws As Worksheet
Dim objHTTP As Object
'We selected our results sheet
Set ws = Worksheets("results")
'We create our request object and send
Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
URL = "https://pokeapi.co/api/v2/pokemon"
objHTTP.Open "GET", URL, False
objHTTP.Send
strResult = objHTTP.responseText
json = strResult
Set objectJson = JsonConverter.ParseJson(json)
'We create the header cells
ws.Cells(1, 1) = "name"
ws.Cells(1, 2) = "link"
'We loop the results property of the API response
i = 2 'We will start the counter on line 2
For Each pokemon InJsonObject("results")
ws.Cells(i, 1) = pokemon("name")
ws.Cells(i, 2) = pokemon("url")
i = i + 1
next
End Sub
It is possible to perform all types of requests - GET, POST, UPDATE.
Read more about that in the How to use Excel VBA to query REST JSON APIs article.
I have an excel-sheet which contains many links.
How do I open them all at once in a new tab with my default browser?
Like this? Included checking url is valid (basic check). The advantage here is you adapt to log information about the response from the URL.
Option Explicit
Sub TEST()
Dim h As Hyperlink
For Each h In ActiveSheet.Hyperlinks
If UrlOK(h.Address) Then h.Follow
Next h
End Sub
Public Function UrlOK(ByVal url As String) As Boolean
Dim request As Object
Dim respCode As Long
On Error Resume Next
Set request = CreateObject("WinHttp.WinHttpRequest.5.1")
With request
.Open "GET", url, False
.Send
respCode = .Status
End With
If respCode = 200 Then UrlOK = True
On Error GoTo 0
End Function
Edit: Thanks to #Omegastripes for noting
1) If you use MSXML2.XMLHTTP over WinHttp.WinHttpRequest.5.1 you get a more reliable result
Benefits include (amongst others):
A) Simplified code to open a URL.
B) Separate sessions do not impact each other.
C) Protected Mode IE Support
D) Credential Cache
2) Use HEAD over GET, in the request, to reduce network traffic
With a HEAD request, a server will only return the headers of a resource, rather than the resource itself.
So you could use a revised, more efficient function, as follows:
Public Function UrlOK(ByVal url As String) As Boolean
Dim request As Object
Dim respCode As Long
On Error Resume Next
Set request = CreateObject("MSXML2.XMLHTTP")
With request
.Open "HEAD", url, False
.Send
respCode = .Status
End With
If respCode = 200 Then UrlOK = True
On Error GoTo 0
End Function
Image of code in a standard module and where to place cursor to execute Test sub.
That's pretty easy in VBA
Sub OpenAll()
Dim H As Hyperlink
For Each H In ActiveWorkbook.ActiveSheet.UsedRange.Hyperlinks
H.Follow
Next
End Sub
If there are invalid URLs you can stop the code from erroring like this:
Sub OpenAll()
Dim H As Hyperlink
For Each H In ActiveWorkbook.ActiveSheet.Hyperlinks
On Error Resume Next
H.Follow
On Error GoTo 0
Next
End Sub
I've written a script in vba using IE to parse some links from a webpage. The thing is the links are within an iframe. I've twitched my code in such a way so that the script will first find a link within that iframe and navigate to that new page and parse the required content from there. If i do this way then I can get all the links.
Webpage URL: weblink
Successful approach (working one):
Sub Get_Links()
Dim IE As New InternetExplorer, HTML As HTMLDocument
Dim elem As Object, post As Object
With IE
.Visible = True
.navigate "put here the above link"
While .Busy = True Or .readyState < 4: DoEvents: Wend
Set elem = .document.getElementById("compInfo") #it is within iframe
.navigate elem.src
While .Busy = True Or .readyState < 4: DoEvents: Wend
Set HTML = .document
End With
For Each post In HTML.getElementsByClassName("news")
With post.getElementsByTagName("a")
If .Length Then R = R + 1: Cells(R, 1) = .Item(0).href
End With
Next post
IE.Quit
End Sub
I've seen few sites where no such links exist within iframe so, I will have no option to use any link to track down the content.
If you take a look at the below approach by tracking the link then you can notice that I've parsed the content from a webpage which are within Iframe. There is no such link within Iframe to navigate to a new webpage to locate the content. So, I used contentWindow.document instead and found it working flawlessly.
Link to the working code of parsing Iframe content from another site:
contentWindow approach
However, my question is: why should i navigate to a new webpage to collect the links as I can see the content in the landing page? I tried using contentWindow.document but it is giving me access denied error. How can I make my below code work using contentWindow.document like I did above?
I tried like this but it throws access denied error:
Sub Get_Links()
Dim IE As New InternetExplorer, HTML As HTMLDocument
Dim frm As Object, post As Object
With IE
.Visible = True
.Navigate "put here the above link"
While .Busy = True Or .readyState < 4: DoEvents: Wend
Set HTML = .document
End With
''the code breaks when it hits the following line "access denied error"
Set frm = HTML.getElementById("compInfo").contentWindow.document
For Each post In frm.getElementsByClassName("news")
With post.getElementsByTagName("a")
If .Length Then R = R + 1: Cells(R, 1) = .Item(0).href
End With
Next post
IE.Quit
End Sub
I've attached an image to let you know which links (they are marked with pencil) I'm after.
These are the elements within which one such link (i would like to grab) is found:
<div class="news">
<span class="news-date_time"><img src="images/arrow.png" alt="">19 Jan 2018 00:01</span>
<a style="color:#5b5b5b;" href="/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17019039003&opt=9">ABB India Limited - Press Release</a>
</div>
Image of the links of that page I would like to grab:
From the very first day while creating this thread I strictly requested not to use this url http://hindubusiness.cmlinks.com/Companydetails.aspx?cocode=INE117A01022 to locate the data. I requested any solution from this main_page_link without touching the link within iframe. However, everyone is trying to provide solutions that I've already shown in my post. What did I put a bounty for then?
You can see the links within <iframe> in browser but can't access them programmatically due to Same-origin policy.
There is the example showing how to retrieve the links using XHR and RegEx:
Option Explicit
Sub Test()
Dim sContent As String
Dim sUrl As String
Dim aLinks() As String
Dim i As Long
' Retrieve initial webpage HTML content via XHR
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://www.thehindubusinessline.com/stocks/abb-india-ltd/overview/", False
.Send
sContent = .ResponseText
End With
'WriteTextFile sContent, CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\tmp\tmp.htm", -1
' Extract target iframe URL via RegEx
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = True
.IgnoreCase = True
' Process all a within div.news
.Pattern = "<iframe[\s\S]*?src=""([^""]*?Companydetails[^""]*)""[^>]*>"
sUrl = .Execute(sContent).Item(i).SubMatches(0)
End With
' Retrieve iframe HTML content via XHR
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", sUrl, False
.Send
sContent = .ResponseText
End With
'WriteTextFile sContent, CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\tmp\tmp.htm", -1
' Parse links via XHR
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = True
.IgnoreCase = True
' Process all anchors within div.news
.Pattern = "<div class=""news"">[\s\S]*?href=""([^""]*)"
With .Execute(sContent)
ReDim aLinks(0 To .Count - 1)
For i = 0 To .Count - 1
aLinks(i) = .Item(i).SubMatches(0)
Next
End With
End With
Debug.Print Join(aLinks, vbCrLf)
End Sub
Generally RegEx's aren't recommended for HTML parsing, so there is disclaimer. Data being processed in this case is quite simple that is why it is parsed with RegEx.
The output for me as follows:
/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17047038016&opt=9
/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17046039003&opt=9
/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17045039006&opt=9
/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17043039002&opt=9
/HomeFinancial.aspx?&cocode=INE117A01022&Cname=ABB-India-Ltd&srno=17043010019&opt=9
I also tried to copy the content of the <iframe> from IE to clipboard (for further pasting to the worksheet) using commands:
IE.ExecWB OLECMDID_SELECTALL, OLECMDEXECOPT_DODEFAULT
IE.ExecWB OLECMDID_COPY, OLECMDEXECOPT_DODEFAULT
But actually that commands select and copy the main document, excluding the frame, unless I click on the frame manually. So that might be applied if click on the frame could be reproduced from VBA (frame node methods like .focus and .click didn't help).
Something like this should work. They key is to realize the iFrame is technically another Document. Reviewing the iFrame on the page you listed, you can easily use a web request to get at the data you need. As already mentioned, the reason you get an error is due to the Same-Origin policy. You could write something to get the src of the iFrame then do the web request as I've shown below, or, use IE to scrape the page, get the src, then load that page which looks like what you have done.
I would recommend using a web request approach, Internet Explorer can get annoying, fast.
Code
Public Sub SOExample()
Dim html As Object 'To store the HTML content
Dim Elements As Object 'To store the anchor collection
Dim Element As Object 'To iterate the anchor collection
Set html = CreateObject("htmlFile")
With CreateObject("MSXML2.XMLHTTP")
'Navigate to the source of the iFrame, it's another page
'View the source for the iframe. Alternatively -
'you could navigate to this page and use IE to scrape it
.Open "GET", "https://stocks.thehindubusinessline.com/Companydetails.aspx?&cocode=INE117A01022"
.send ""
'See if the request was ok, exit it there was an error
If Not .Status = 200 Then Exit Sub
'Assign the page's HTML to an HTML object
html.body.InnerHTML = .responseText
Set Elements = html.body.document.getElementByID("hmstockchart_CompanyNews1_updateGLVV")
Set Elements = Elements.getElementsByTagName("a")
For Each Element In Elements
'Print out the data to the Immediate window
Debug.Print Element.InnerText
Next
End With
End Sub
Results
ABB India Limited - AGM/Book Closure
Board of ABB India recommends final dividend
ABB India to convene AGM
ABB India to pay dividend
ABB India Limited - Outcome of Board Meeting
More ?
The simple of solution like everyone suggested is to directly go the link. This would take the IFRAME out of picture and it would be easier for you loop through links. But in case you still don't like the approach then you need to get a bit deeper into the hole.
Below is a function from a library I wrote long back in VB.NET
https://github.com/tarunlalwani/ScreenCaptureAPI/blob/2646c627b4bb70e36fe2c6603acde4cee3354b39/Source%20Code/ScreenCaptureAPI/ScreenCaptureAPI/ScreenCapture.vb#L803
Private Function _EnumIEFramesDocument(ByVal wb As HTMLDocumentClass) As Collection
Dim pContainer As olelib.IOleContainer = Nothing
Dim pEnumerator As olelib.IEnumUnknown = Nothing
Dim pUnk As olelib.IUnknown = Nothing
Dim pBrowser As SHDocVW.IWebBrowser2 = Nothing
Dim pFramesDoc As Collection = New Collection
_EnumIEFramesDocument = Nothing
pContainer = wb
Dim i As Integer = 0
' Get an enumerator for the frames
If pContainer.EnumObjects(olelib.OLECONTF.OLECONTF_EMBEDDINGS, pEnumerator) = 0 Then
pContainer = Nothing
' Enumerate and refresh all the frames
Do While pEnumerator.Next(1, pUnk) = 0
On Error Resume Next
' Clear errors
Err.Clear()
' Get the IWebBrowser2 interface
pBrowser = pUnk
If Err.Number = 0 Then
pFramesDoc.Add(pBrowser.Document)
i = i + 1
End If
Loop
pEnumerator = Nothing
End If
_EnumIEFramesDocument = pFramesDoc
End Function
So basically this is a VB.NET version of below C++ version
Accessing body (at least some data) in a iframe with IE plugin Browser Helper Object (BHO)
Now you just need to port it to VBA. The only problem you may have is finding the olelib rerefernce. Rest most of it is VBA compatible
So once you get the array of object, you will find the one which belongs to your frame and then you can just that one
frames = _EnumIEFramesDocument(IE)
frames.Item(1).document.getElementsByTagName("A").length
How do I output the result of a WinHTTPRequest in Excel?
For example, the following code queries the stock quote of Apple from a webpage but it doesn't output anything:
Sub GetQuotes()
Dim XMLHTTP As Object, html As Object, pontod As Object
On Error Resume Next
Set oHtml = New HTMLDocument
With CreateObject("WINHTTP.WinHTTPRequest.5.1")
.Open "GET", "http://www.reuters.com/finance/stocks/overview?symbol=AAPL.O", False
.send
oHtml.body.innerHTML = .responseText
End With
'Price
Set pontod = oHtml.getElementsByClassName("sectionQuote nasdaqChange")(0).getElementsByTagName("span")(1)
MsgBox pontod.innerText
End Sub
While this runs perfectly for the name:
Sub GetQuotes2()
Dim XMLHTTP As Object, html As Object, pontod As Object
On Error Resume Next
Set oHtml = New HTMLDocument
With CreateObject("WINHTTP.WinHTTPRequest.5.1")
.Open "GET", "http://www.reuters.com/finance/stocks/overview?symbol=AAPL.O", False
.send
oHtml.body.innerHTML = .responseText
End With
'Name
Set pontod = oHtml.getElementById("sectionTitle").getElementsByTagName("h1")(0)
MsgBox pontod.innerText
End Sub
I'd like to be able to fetch the whole page and look for specific HTML elements in it, but how do I manage to see the whole response from the query?
As Jeeped said above, the method getElementsByClassName doesn't work on an XML request.
However, by looking at the webpage you're trying to scrape, you can work-around the issue by using this line:
Set pontod = oHtml.getElementById("headerQuoteContainer").getElementsByTagName("span")(1)
instead of this one:
Set pontod = oHtml.getElementsByClassName("sectionQuote nasdaqChange")(0).getElementsByTagName("span")(1)
As you can observe from the HTML structure of the webpage:
... your price is not only the second span element of the first div with class names sectionQuote and nasdaqChange, but also the second span element of the unique object with id headerQuoteContainer.
Hence, scrape it from there will avoid you to use the invalid method getElementsByClassName (which is a valid HTML method, but not when the HTML is an XML response) for the classic getElementById().
The VBA code below is supposed to call a REST API to grant user access to the file or not. If the REST API returns allow then the code should close normally and grant access, if the REST API retuns disallow then the program should notify user and close workbook. If there is no Internet access, then the user should be notified and the work book should be closed.
My question is how do I code so the REST API response is handled by the macro properly so that it will either end normally or close due to the disallow response from url?
Here is the VBA code so far:
Private Sub Workbook_activate()
Application.EnableCancelKey = xlDisabled
' Run the Error handler "ErrHandler" when an error occurs.
On Error GoTo Errhandler
ActiveWorkbook.FollowHyperlink Address:="https://mysite.com/licensing/getstatus.php?", NewWindow:=True
' If response is allow
' Exit the macro so that the error handler is not executed.
****what goes here??****
Exit Sub
' If response is disallow
****what goes here??****
MsgBox "Your license key is not valid. Please check your key or contact customer service."
ActiveWorkbook.Close SaveChanges:=False
Errhandler:
' If no Internet Access, display a message and end the macro.
MsgBox "An error has occurred. You need Internet access to open the software."
ActiveWorkbook.Close SaveChanges:=False
End Sub
here an example for a simple HttpWebRequest:
Dim oRequest As Object
Set oRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
oRequest.Open "GET", "https://mysite.com/licensing/getstatus.php?"
oRequest.Send
MsgBox oRequest.ResponseText
If you are behind a proxy you can use something like this:
Const HTTPREQUEST_PROXYSETTING_PROXY = 2
Dim oRequest As Object
Set oRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
oRequest.setProxy HTTPREQUEST_PROXYSETTING_PROXY, "http://proxy.intern:8080"
oRequest.Open "GET", "https://mysite.com/licensing/getstatus.php?"
oRequest.Send
MsgBox oRequest.ResponseText
and if you want to use POST (instead of the GET method) to pass some values to the webserver, you can try this:
Dim oRequest As Object
Set oRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
oRequest.Open "POST", "https://mysite.com/licensing/getstatus.php"
oRequest.SetRequestHeader "Content-Typ", "application/x-www-form-urlencoded"
oRequest.Send "var1=123&anothervar=test"
MsgBox oRequest.ResponseText