I am looking to adapt the code presented by Santosh so that I can return the top ten links, not just the top one result, of a particular Google search. I need to upload > 1000 search queries and map the results against expected results but I care about more than just the #1 result, I'm looking to see if it returns within the top ten. I looked into the html and VBA and I can't figure it out.
Using VBA in Excel to Google Search in IE and return the hyperlink of the first result
Without testing, it looks like this line here is pulling one element in a collection:
Set link = objH3.getelementsbytagname("a")(0)
So, you're setting the "Link" object to the first object (object 0) in the objH3 collection that has the "a" tag.
What you want to do is loop through that collection. Ex:
Set links = objH3.getelementsbytagname("a")
For i = 0 To 9
set link = links(i)
'do stuff
next
Edit:
The loop should be on a higher level of objects:
For i = 0 To 5
Set objH3 = objResultDiv.getelementsbytagname("H3")(i)
Set link = objH3.getelementsbytagname("a")(0)
' Do Stuff
Next I
Thank you, Richard, for the update.
Related
I have a very large word file. In fact it has more than 500 pages. Now I want to set different left margins for every third page. Like page one, four, seven... have left margin 30
the rest have left margin 1. I try to do that by using vba like the following:
Sub SetMargin()
Set MyDoc = ActiveDocument
PageNum = MyDoc.Range.Information(wdNumberOfPagesInDocument)
For i = PageNum To 2 Step -1
Set Pos = MyDoc.GoTo(wdGoToPage, wdGoToAbsolute, i)
Pos.insertBreak wdSectionBreakContinuous
Next
For Each i In MyDoc.Sections
If (i.Index + 2) Mod 3 = 0 Then
i.PageSetup.LeftMargin = 30
End If
Next
End Sub
But the problem is the article contains some long sentences, so after running the program, those sentences break into two lines and some pages just cannot contain the whole section and it floats to the next page. Thus some pages have two different left margins, which really confuses me since I am new at Word and VBA programming. There must be some way to fix that. But I don't know how. Your help would be greatly appreciated.
Thanks for your help.
Suppose you have a microsoft word file (.DOCX), you open it and start to view it, using the mouse scrolling, from page 1 towards the last page.
Then, suppose you see a table, for instance, on page 4.
Now I ask: is it possible for Word-VBA to say to you what is the index or name of that table that is on the screen at the moment, no matter where is cursor located?
I want word-VBA fullfil a table that I am seeing at that moment, no matter where the cursor is located.
I hope I was clear enough...
Word doesn't have a direct way to get what's visible on-screen. It can be calculated, sort of, but not with 100% accuracy.
The following code sample does the trick for me, on my machine. It may need some tweaking to work on a different set up.
The object model does return the co-ordinates of the application window (ActiveWindow, here), the height of that Window and the UsableHeight - the height of the actual document working space. That can be used to get an estimated position.
There's also a Windows API function equivalent - RangeFromPoint - for a Window object that returns the a Range in the document for the given screen co-ordinates.
This code calculates a left and top position for the start of the visible part of the document, as well as for the end of the visible document. (In my test, it was a bit more, but not much). It then checks whether there is one or more tables within that scope. If it is, it takes the first one ( Set tbl = rngTargetStart.Tables(1)) - this returns the object your code needs to work with. As a "bonus", the code prints the index number of the table in the document and the page it's on to the Immediate Window.
Sub CheckForTableOnPage()
Dim WordWindowTop As Long 'in points
Dim WordWindowLeft As Long 'in points
Dim windowUsableHeight As Long 'in points
Dim rngTargetStart As Range
Dim rngTargetEnd As Range
Dim pageNumberTarget As Long
Dim tbl As Table
WordWindowTop = ActiveWindow.height
WordWindowLeft = ActiveWindow.left
windowUsableHeight = ActiveWindow.UsableHeight
RibbonFactor = 200
Set rngTargetStart = ActiveWindow.RangeFromPoint(WordWindowLeft, WordWindowTop - windowUsableHeight)
Set rngTargetEnd = ActiveWindow.RangeFromPoint(WordWindowLeft, WordWindowTop + windowUsableHeight)
rngTargetStart.End = rngTargetEnd.End
If rngTargetStart.Tables.Count >= 1 Then
pageNumberTarget = rngTargetStart.Information(wdActiveEndPageNumber)
Set tbl = rngTargetStart.Tables(1)
rngTargetStart.Start = ActiveDocument.Content.Start
Debug.Print "The table on page " & pageNumberTarget & " is number: " & rngTargetStart.Tables.Count
End If
End Sub
For the next part of this project that I've been working on to speed up follow up emails to clients for the office, I'm looking to grab a specific attachment from a specified filepath based on the items that the user selected on the userform. These emails will always be sending the exact same files so the less time the user has to spend manually attaching files, the better it will be. My first assumption right off the bat was that I'd need a loop to do this, so I began to do my groundwork, but now I'm generally stuck.
The first loop grabs what the user selected from the userform:
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then
Counter = Counter + 1
msg = msg & "<font style = 'background: yellow'>" & List1.List(i) & "<br />"
Else: If Counter = 0 Then End
End If
Next
And the second loop attaches the files based on the selections above:
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then
Counter = Counter + 1
.Attachments.Add List1.List(i)
Else: If Counter = 0 Then End
End If
Next
The attachments process just fine.However, the program ends up displaying the highlighted body of the message as the filepath I tried to associate with the list item:
' test files
List1.List(0) = "C:\Users\jmarkman\Dropbox\Python Practice\ex1.py"
List1.List(1) = "C:\Users\jmarkman\Dropbox\Python Practice\ex2.py"
List1.List(2) = "C:\Users\jmarkman\Dropbox\Python Practice\ex3.py"
List1.List(3) = "C:\Users\jmarkman\Dropbox\Python Practice\ex4.py"
List1.List(4) = "C:\Users\jmarkman\Dropbox\Python Practice\ex5.py"
List1.List(5) = "C:\Users\jmarkman\Dropbox\Python Practice\ex6.py"
So my question is, how do I associate these file paths with corresponding items in the listbox within the userform? I'm pretty sure that although it worked, the process changed the list items since I assigned them a different value.
I'm not sure how complex or simple this might end up being, so your time and patience are well appreciated.
People have said that one can learn best just by doing and making mistakes, but let me tell you that not having someone you can at least talk to about where to start making mistakes is incredibly frustrating. I hope anyone else who, like me, was on the cusp of solving this problem is able to look here and have their own "a-ha!" moment.
When I first asked this question, I was on the right path with choosing to put all of my filepath locations within an array and I was further along the right path by using a loop to go through it. I declared the myAttach array with a range as I did at first, but unlike whatever example I saw during my google research, I kept my second attachment loop the same but simply changed out List1.List(i) for myAttach(i). That made the script's wheels turn and performed all the functions I needed it to.
The long and short of it was that I made an array that matched the quantity and order of the items in the userform and re-used the loop I made for picking the subjectivities, I was able to associate the choices in the userform to those in the array by having the second loop go through the array. Each listing in the array had a filepath associated with it.
' example
myAttach(0) = "[filepath]"
myAttach(1) = "[filepath]"
myAttach(2) = "[filepath]"
myAttach(3) = "[filepath]"
' ... and so on ...
It helped to visualize the array and the list items as two side-by-side columns; the first item in the list is parallel to the first item in the array, and can therefore be recognized by the second loop. If a mental image isn't forthcoming, try inputting some data into two or more Excel columns to witness the relationship or review Matricies.
A caveat of this line-up approach, I found out shortly after while continuing research to figure out exactly what I did right this time would be if I had to have the items in the list in a specific order (i.e., alphabetical), but fortunately the list items are phrases and complete sentences and don't require sorting.
Happy learning and programming!
I want to make a function that extracts data from a part of a site.
The following is the HTML site. HTML code.
Code for the function
Function GetElementById(url As String, id As String, Optional isVolatile As Boolean)
Application.Volatile (isVolatile)
On Error Resume Next
Dim html As Object, objResult As Object
ret = GetPageContent(url)
Set html = CreateObject("htmlfile")
html.Body.innerHtml = ret
Set objResult = html.GetElementById(id)
GetElementById = objResult.innerHtml
End Function
I need that extracts only the class "panel-body"
directly into the function. I think it would be .children (3). Is that correct?
And so that it is practical and fast, because I need to extract more than 50 sites.
I see at least two options.
Once you have the HTMLDivElement with id=Result you could simply get the children. Please test this by first doing objResult.Children(2) and checking what the element is that is returned.
objResult.Children(2).Children(0).Children(0)
The second is that in later versions of MSHTML I think with IE8 or later installed you have the method "GetElementsByClassName" This will return a collection of IHTMLElements. If the HTMLDocument only has 1 "panel-body" then you are in luck. If not you would need to loop through each one and check some other unique feature to know you have the right one.
Another way to generate the code for this job is to record a macro, then add a loop around the recorded macro that loops through your 50 pages and gets the results.
On the data tab in the ribbon there is an option get data from external sources. If you use this it's gives you a point and click interface that let's you chose the table your looking for. Record a macro while your doing this and it generates the code for you.
I am making lots of changes to a Word document using automation, and then running a VBA macro which - among other things - checks that the document is no more than a certain number of pages.
I'm using ActiveDocument.Information(wdNumberOfPagesInDocument) to get the number of pages, but this method is returning an incorrect result. I think this is because Word has not yet updated the pagination of the document to reflect the changes that I've made.
ActiveDocument.ComputeStatistics(wdStatisticPages) also suffers from the same issue.
I've tried sticking in a call to ActiveDocument.Repaginate, but that makes no difference.
I did have some luck with adding a paragraph to the end of the document and then deleting it again - but that hack seems to no longer work (I've recently moved from Word 2003 to Word 2010).
Is there any way I can force Word to actually repaginate, and/or wait until the repagination is complete?
I just spent a good 2 hours trying to solve this, and I have yet to see this answer on any forum so I thought I would share it.
https://msdn.microsoft.com/en-us/vba/word-vba/articles/pages-object-word?f=255&MSPPError=-2147217396
That gave me my solution combined with combing through the articles to find that most of the solutions people reference are not supported in the newest versions of Word. I don't know what version it changed in, but my assumption is that 2013 and newer can use this code to count pages:
ActiveDocument.ActiveWindow.Panes(1).Pages.Count.
I believe the way this works is ActiveDocument selects the file, ActiveWindow confirms that the file to be used is in the current window (in case the file is open in multiple windows from the view tab), Panes determines that if there is multiple windows/split panes/any other nonsense you want the "first" one to be evaluated, pages.count designates the pages object to be evaluated by counting the number of items in the collection.
Anyone more knowledgeable feel free to correct me, but this is the first method that gave me the correct page count on any document I tried!
Also I apologize but I cant figure out how to format that line into a code block. If the mods want to edit my comment to do that be my guest.
Try (maybe after ActiveDocument.Repaginate)
ActiveDocument.BuiltinDocumentProperties(wdPropertyPages)
It is causing my Word 2010 to spend half-second with "Counting words" status in status bar, while ActiveDocument.ComputeStatistics(wdStatisticPages) returns the result immediately.
Source: https://support.microsoft.com/en-us/kb/185509
After you've made all your changes, you can use OnTime to force a slight delay before reading the page statistics.
Application.OnTime When:=Now + TimeValue("00:00:02"), _
Name:="UpdateStats"
I would also update all the fields before this OnTime statement:
ActiveDocument.Range.Fields.Update
I found a possible workaround below, if not a real answer to the topic question.
Yesterday, the first ComputeStatistics line below was returning the correct total of 31 pages, but today it returns only 1.
The solution is to get rid of the Content object and the correct number of pages is returned.
Dim docMultiple As Document
Set docMultiple = ActiveDocument
lPageCount = docMultiple.Content.ComputeStatistics(wdStatisticPages) ' Returns 1
lPageCount = docMultiple.ComputeStatistics(wdStatisticPages) ' Returns correct count, 31
ActiveDocument.Range.Information(wdNumberOfPagesInDocument)
This works every time for me. It returns total physical pages in the word.
I used this from within Excel
it worked reliably on about 20 documents
none were longer than 20 pages but some were quite complex
with images and page breaks etc.
Sub GetlastPageFromInsideExcel()
Set wD = CreateObject("Word.Application")
Set myDoc = wD.Documents.Open("C:\Temp\mydocument.docx")
myDoc.Characters.Last.Select ' move to end of document
wD.Selection.Collapse ' collapse selection at end
lastPage = wD.Selection.Information(wdActiveEndPageNumber)
mydoc.close
wd.quit
Set wD = Nothing
End Sub
One problem I had in getting "ComputeStatistics" to return a correct page count was that I often work in "Web Layout" view in Word. Whenever you start Word it reverts to the last view mode used. If Word was left in "Web Layout" mode "ComputeStatistics" returned a page count of "1" page for all files processed by the script. Once I specifically set "Print Layout" view I got the correct page counts.
For example:
$MSWord.activewindow.view.type = 3 # 3 is 'wdPrintView', 6 is 'wdWebView'
$Pages = $mydoc.ComputeStatistics(2) # 2 is 'wdStatisticPages'
You can use Pages-Object and its properties such as Count. It works perfect;)
Dim objPages As Pages
Set objPage = ActiveDocument.ActiveWindow.Panes(1).Pages
QuantityOfPages = ActiveDocument.ActiveWindow.Panes(1).Pages.Count
Dim wordapp As Object
Set wordapp = CreateObject("Word.Application")
Dim doc As Object
Set doc = wordapp.Documents.Open(oFile.Path)
Dim pagesCount As Integer
pagesCount = doc.Content.Information(4) 'wdNumberOfPagesInDocument
doc.Close False
Set doc = Nothing