Cannot get the text of nth span element using vba - excel

I have the following html part
<div class="description">
<span>Brand:</span>
Nikon<br/>
<span>Product Code:</span> 130342 <br/>
<span>Barcode</span> 18208948581 <br/>
<span>Availability:</span>Available</div>
I am trying to get the last span and the word Available using the following
Set availability = ie.Document.getElementsByClassName(".description").getElementsByTagName("span")(2)
wks.Cells(i, "D").Value = availability.innerText
But it shows all span texts
What I am doing wrong here?

Use last-child css pseudo class in descendant combination with parent element class selector.
.description span:last-child
The :last-child CSS pseudo-class represents the last element among a
group of sibling elements.
Applying:
single match
Set availability = ie.document.querySelector(".description span:last-child")
Cells(1,1) = availability.innerText
all matches
Set availability = ie.document.querySelectorAll(".description span:last-child")
Cells(1,1) = availability.item(0).innerText
Otherwise, you can return the span collection from that parent class and index into it
Set availability = ie.document.querySelectorAll(".description span")
Cells(1,1) = availability.item(2).innerText '<==choose your index here
Or even chain:
Set availability = ie.document.querySelector(".description span + span + span") '<==expand as required. This uses [adjacent sibling combinator][4].
Sadly, pseudo classes nth-of-type / nth-child are not supported in VBA implementation though you can in many other languages e.g. python.
—-
If after just the Available you should be able to use .description as your selector to return all the text in the div. Then use Split on the .innerText using Chr$(32) to split by and extract the UBound (I.e. the last element of the generated array)
Set availability = ie.document.querySelector(".description")
Dim arr() As String
arr = split( availability.innerText, ":")
Cells(1,1) = arr(UBound(arr))

As Zac pointed out in the comments, you shouldn't use a period . with the getElementsByClassName method.
ie.Document.getElementsByClassName is returning a DispHTMLElementCollection of elements. You need to specify which element you want to reference
Set availability = ie.Document.getElementsByClassName(".description")(0).getElementsByTagName("span")(2)
A better way to write the write the code would be to reference the Microsoft HTML Object Library and create a variable to test each element returned. Unfortunately, there is a bug in the DispHTMLElementCollection implementation, so you will need to use Object instead of DispHTMLElementCollection.
Dim doc As HTMLDocument
Dim availability As Object
Set doc = ie.Document
Set availability = doc.getElementsByClassName("description")
Dim div As HTMLDivElement
Dim span As HTMLSpanElement
Dim spans As Object
For Each div In availability
Set spans = div.getElementsByTagName("span")
For Each span In spans
Debug.Print span.innerText
Next
Next
Output

Related

Excel VBA extract InnerText after <a> tag

I am trying to extract some InnerText after a tag.
This is the HTML:
'<pre>../
view_10496.html 06-Feb-2021 01:54 60K
view_10498.html 06-Feb-2021 01:54 53K
view_10499.html 06-Feb-2021 01:54 26K
view_10500.html 06-Feb-2021 01:54 15K
view_10501.html 06-Feb-2021 01:54 128K
My code can pick up the content of the a tag but I also want to extract the text behind the a tag.
The counter makes sure that I discard the first a tag.
Set alle_a_tags = ie.document.getElementsByTagName("a")
For Each a_tag In alle_a_tags
If teller = 0 Then
GoTo Volgende_a_tag
End If
InnerHTML = a_tag.InnerHTML
InnerText = a_tag.InnerText
Href = a_tag.Href
Date = ...
Next
Based only on HTML provided:
You can match the substring of the href attribute value with starts with operator to get right preceding nodes. You then need to move to the NextSibling to get desired text. You can use Select Case to determine which property to access depending on nodeType of that sibling
Dim i As Long, nodes As Object, nextSibling As Object
Set nodes = ie.document.querySelectorAll("[href^='view_']")
For i = 0 To nodes.Length - 1
Set nextSibling = nodes.Item(i).nextSibling
'https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Select Case nextSibling.NodeType
Case 1
Debug.Print nextSibling.innerText
Case 3
Debug.Print nextSibling.NodeValue
End Select
Next
You can extract the text behind closing tags using a regular expression.
example: (?<=</a>)(.|\r).*?$
RegEx-Sandbox: https://regex101.com/

VBA getelementsbytagname issue

Good morning,
I'm attempting to extract HTML table information and collate results on en excel spreadsheet.
I'm using the getelementsbytagname("table")(0) function to extract the HTML table info, which has worked well. Can someone please tell me what is the significance of the (0) after the table?
Also, I have an instance where an opened webpage does not have any table information to process (I don't know this until the page is opened), this leads to an error in my code as I try to initialize my data array to the table dimensions. Is there a way of extracting a result from getelementsbytagname("table")(0), I've tried:-
If (iDom.getelementsbytagname("table")(0) = 0) Then
but this returns a run time error.
Many thanks in advance for your help.
First add reference to Microsoft Internet Controls (SHDocVw) and to Microsoft HTML Object Library:
Then the Object Explorer is your friend:
So getElementsByTagName returns IHTMLElementCollection which has property length. When on the page some elements with specific tag name are found then length is greater then zero. HTH
Dim tables As IHTMLElementCollection
Set tables = doc.getElementsByTagName("table")
If tables.Length > 0 Then
Dim table As HTMLTable
Set table = tables.item(0)
' Because item is the default property of IHTMLElementCollection we can simplyfy
Set table = tables(0) ' this is the same as tables.item(0)
End If
In VBA the appended (0) refers to the first element of an array (assuming an Option Base 0). Here is a short example:
vArr = Array("element 1", "element 2", "element 3")
Debug.Print v(1)
The above code should return element 2 as the second element of a zero-based array.
So, getelementsbytagname("table")(0) refers to the first element of that table. Yet, if the "table" is not found then there is no array to get from that table and getting the first element from that array (by appending (0)) yields an error.
Instead you should test if there is actually a table by that name (before trying to access the array of elements within that table) like so:
If (iDom.getelementsbytagname("table") = 0) Then

Excel-VBA get filtered collection from Outlook AddressList

Problem:
As it seems to me that AddressList does not have a built-in filter functionality such as, say a C# DataTable (DatTableObject.Select(filter criteria), i am looking for a way to do this.
The Global Address List I am accessing has around a million entries, and I need to search through it up to 1000 times.
I am using the exchange-user name to find the e-mails of people, using the following code:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntry = aList.AddressEntries("" + ExchangeName + "")
Set exUser = aEntry.GetExchangeUser
But it only retrieves me a single AddressEntry, which is a problem when I have several people of the same Exchange name - happens often enough.
Question: When I search Global Address List in Outlook, I have everything sorted alphabetically and with good speed, I am presented with all matches starting with the string I type in. How can I get a similar collection in VBA?
The AddressEntries object is a collection of AddressEntry objects.
When you index directly into the AddressEntries collection as you are, you return a single AddressEntry object based on the Index parameter provided. The Index parameter can either by an index number or it can be the default property of the item.
Since the default property of an AddressEntry item is the .Name property, the return is whatever the first item in the collection is which matches on the .Name property.
If you want to return all AddressEntry objects in the collection which match on the .Name property, you will need to loop through the collection.
Now, in .Net you can iterate over a collection using For...Next, and I believe you can do this in VBA as well, but I just can't recall off hand.
e.g.:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntries = aList.AddressEntries
For each aEntry in aEntries
if aEntry.Name="" + ExchangeName + "" Then
'Do something with aEntry object
End If
Next
If for some reason that doesn't work, you can iterate over the collection using the GetFirst and GetNext methods.
e.g.:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntries = aList.AddressEntries
Set aEntry = aEntries.GetFirst
Do While Not aEntry is Nothing
if aEntry.Name="" + ExchangeName + "" Then
'Do something with aEntry object
End If
Set aEntry = aEntries.GetNext
Loop
As for sorting alphabetically, have a look at the Sort Method.
And as far as building functionality where
...I am presented with all matches starting with the string I type in.
I'm not sure how you want to "present" the matches, or where you want to type in a string. But the general idea would be to a create function that takes an input parameter (e.g "MatchName" As String) and perform a loop like above to find all matches on that string for whatever property you want to look at, and then you would return an array, or something which you could use to "present" the information.
If you wanted to make it dynamic, so that the list updated "as you typed" you could run the updating procedure from the KeyPress event. In order to not run through the whole collection as you typed in a word, you would probably want to store the array and then with each additional letter typed you could just iterate through that array and remove non-matches (narrow the results). Before that happened you would probably then need some sort of check to see if a letter was removed, (e.g. checking the length of the string in the textbox) which would tell your program to re-run the check on the AddressEntries collection (widen the results).
Anyways, that's kind of a general idea on one way you could do it.

How to change the alterrowcolor and Header Style using Lotus script?

My requirement is, I am having a hundreds of views. I want to make them as standard colors and UI. Simple I am using for changing the font color for column header and column values by NotesViewColumn class. But I do not know that which class is having the property for action bar and View alternate color and Heaer style and etc.,
In javascript is also welcome., But it should change its property as a designer level.
Thanks in advance
You have 3 options:
The easiest one: Go and buy ezView from Ytria. Should take you less than an hour to sort your views out
Create one view that looks the way you want your views to look and then go through all the views in a script, rename them, create a new view based on your view template and copy the view columns from the old views and adjust the view selection formulas (all in LotusScript)
Export your views in DXL and run some XSLT or search/replace to adjust the properties
Hope that helps
I just ran this agent, to change all the views in my (small) test database to having alternate row colours, and it worked.
Sub Initialize
Dim session As New NotesSession
Dim db As NotesDatabase
Dim exporter As NotesDXLExporter
Dim importer As NotesDXLImporter
Dim out As String
Dim infile As string
Dim pointer As long
Dim filenum As Integer
Dim altrow As integer
Dim unid As String
Dim doc As notesdocument
Set db = session.currentdatabase
Set exporter = session.Createdxlexporter
Set importer = session.Createdxlimporter
Dim count As Integer
count = 1
ForAll v In db.views
unid = v.UniversalID
Set doc = db.getdocumentbyunid(unid)
out = exporter.Export(doc)
altrow = instr(out, "altrowcolor")
If altrow > 0 Then
pointer = InStr(altrow, out, "=")
out = Left(out,pointer) & "'#f7f7f7'" & Mid(out, pointer+10)
else
pointer = InStr(out, "bgcolor=")
pointer = InStr(pointer, out, " ")
out = Left(out,pointer) & "altrowcolor='#f7f7f7' " & Mid(out, pointer+1)
End if
Call importer.setinput(out)
Call importer.setoutput(db)
importer.Designimportoption = 5
importer.Documentimportoption = 5
Call importer.Process()
out = ""
infile = ""
count = count + 1
End ForAll
Print count & " views processed"
End Sub
If your view designs are much bigger, you might want to use a NotesStream instead of String for "out". In that case, from the Help Files, I believe that the stream has to be closed and re-opened before you can use it for import.
For further research, I suggest writing "out" to a file, and examining the xml to find other "hidden" parameters.
Have fun, Phil
I can also recommend ezView. Makes it a piece of cake to modify views. I also use actionBarEZ to modify action bars across applications.
I blogged about a few different development tools I use in Domino Designer, you can find the entry here: http://www.bleedyellow.com/blogs/texasswede/entry/mydevelopmenttools

Using multiple values field in Lotus Notes

I am trying to write a logging system for a form in Lotus Notes but I am at the part where I am not sure how I can append the information about the fields that are changed in the log fields. There are 3 fields that I use Log_Date (date), Log_User and Log_Actions (Text, allow multiple values).
I thought if I add comma to the log field it will create a new line when displaying the form but I am still getting a type mismatch on the case 2 line.
How can I append the new values to the log fields?
Sub Querysave(Source As Notesuidocument, Continue As Variant)
' Compare the values in the form after it is saved with its original values when the document is not a new document.
Dim doc As NotesDocument
Set doc = Source.Document
Dim session As New NotesSession
Dim user As String
user = session.CommonUserName
If newDoc Then
doc.Log_Date = Now()
doc.Log_User = user
doc.Log_Actions = "New document created."
Else
' Load fields value to the array
lastValues(0) = doc.QCR_No(0)
lastValues(1) = doc.QCR_Mobile_Item_No(0)
lastValues(2) = doc.QCR_Qty(0)
' Compared each value in the array to see if there is any difference
Dim i As Integer
For i = 0 To 2
If lastValues(i) <> originalValues(i) Then
Select Case i
Case 2 : doc.Log_Actions = doc.Log_Actions & "," & "Field QCR_Qty is changed"
End Select
End If
Next
End If
End Sub
doc.Log_Actions returns the notesitem. To access the value you need to use doc.Log_Actions(0)
In LotusScript back-end classes (e.g. NotesDocument, NotesItem), a multi-valued field is represented by an array with one value per element of the array. For setting the value of a field, doc.Log_Actions is shorthand (they call it 'extended syntax' in the Domino Designer help) for assigning the first (i.e., zero subscript) element of the array, but that does not work for getting the value. To get the first value, you have to use doc.Log_Actions(0). To get or set the second value, you have to use doc.Log_Actions(1).
So, your Case 2 code could look like this:
doc.Log_Actions(1) = "Field QCR_Qty is changed"
My guess, however, is that you really want to be able to continually append to the end of the list of values each time this code runs. You are also going to want your code to be robust and not blow up on you if (for any reason!) the Log_Actions item does not exist in the document. For that, you are going to want to do this:
dim actionsItem as NotesItem
if doc.hasItem("Log_Actions") then
set actionsItem = doc.getFirstItem("Log_Actions")
call actionsItem.AppendToTextList("Field QCR_Qty is changed")
end if
Or,
If (Not doc.HasItem("LogActions")) Then
doc.LogActions = "Field QCR_Qty is changed"
Else
doc.LogActions = ArrayAppend(doc.LogActions,"Field QCR_Qty is changed")
End If
This is equivalent to the NotesItem method by rhsatrhs, which you use is matter of preference.

Resources