Get Text from Range - VBA (Excel) - list

I want to make a data validation list from text only in a Range of cells.
I searched for formula but I found nothing so I decided to make my own function but its not working.
I tried those codes:
Code 1:
Function ListFromRange(rng As Range) As Range
Dim cl As Range
Dim entry As Range
For Each cl In rng
If Not IsNumeric(cl.Value) Or cl.Value = "" Then
If entry Is Nothing Then
Set entry = cl
Else
Set entry = Union(entry, cl)
End If
End If
Next
Set ListFromRange = entry
End Function
Code 2:
Function ListFromRange2(rng As Range) As Variant
Dim cl As Range
Dim i As Integer
Dim entry() As String
ReDim entry(rng.Count)
For Each cl In rng
If Not IsNumeric(cl.Value) Or cl.Value = "" Then
entry(i) = cl.Value
i = i + 1
End If
Next
ListFromRange2 = entry
End Function
The second code is working but when I use with a defined name and use that defined name for data validation list its tells me that there is an error in the validation list source but when I use this function with index its returning the desired result.
Also some images to explain more:
I want to make a list from cells that contains a text and apply it here:
But without the number values.

The problem is that the resultant range is multiple columns and cannot be used as the source for a Data Validation List. If you cannot change the design of the table of options so that it is just one column, you need to find another way to set up the Validation List.
Here is a solution using VBA. I put this in a sub that can be run on demand as a macro, but you might drop it into a worksheet event that triggers when data on the sheet changes or some other event.
This will only create the validation list as far down as there is data in Column A. You'll probably want to carry it down further than this, or as mentioned, put this into a worksheet event so that it updates the Validation list as new rows are added.
I set up my sheets as follows, but you can also download my example here.
Option Explicit
Sub Create_Validation_List()
Dim rngList As Range, cl As Range
Dim rngValidationList As Range
Dim strList As String
Set rngList = Worksheets("BasicPrice").Range("A2:F3")
strList = ""
For Each cl In rngList
If Not IsNumeric(cl.Value) And Not cl.Value = "" Then strList = strList & "," & cl.Value 'Add to our Validation List
Next cl
strList = Mid(strList, 2) 'Chop off leading comma
'Apply Data Validation to this Range (starting at cell C2 and ending at the last row with data in column A)
Set rngValidationList = Range("C2:C" & Cells(Rows.Count, "A").End(xlUp).Row)
Application.EnableEvents = False
With rngValidationList.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=strList 'Pass in Validation List created above
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Application.EnableEvents = True
End Sub
Let me know if you have any questions.

Related

VBA webscraper - Return InnerHTML with regex

Using Excel VBA, i have to scrape some data from this website.
Since the relevant website objects dont contain an id, I cannot use HTML.Document.GetElementById.
However, I noticed that the relevant information is always stored in a <div>-section like the following:
<div style="padding:7px 12px">Basler Versicherung AG Özmen</div>
Question:
Is it possible to construct a RegExp that, probably in a Loop, returns the contents inside <div style="padding:7px 12px"> and the next </div>?
What I have so far is the complete InnerHtml of the container, obviously I need to add some code to loop over the yet-to-be-constructed RegExp.
Private Function GetInnerHTML(url As String) As String
Dim i As Long
Dim Doc As Object
Dim objElement As Object
Dim objCollection As Object
On Error GoTo catch
'Internet Explorer Object is already assigned
With ie
.Navigate url
While .Busy
DoEvents
Wend
GetInnerHTML = .document.getelementbyId("cphContent_sectionCoreProperties").innerHTML
End With
Exit Function
catch:
GetInnerHTML = Err.Number & " " & Err.Description
End Function
Another way you can achieve the same using XMLHTTP request method. Give it a go:
Sub Fetch_Data()
Dim S$, I&
With New XMLHTTP60
.Open "GET", "https://www.uid.admin.ch/Detail.aspx?uid_id=CHE-105.805.649", False
.send
S = .responseText
End With
With New HTMLDocument
.body.innerHTML = S
With .querySelectorAll("#cphContent_sectionCoreProperties label[id^='cphContent_ct']")
For I = 0 To .Length - 1
Cells(I + 1, 1) = .Item(I).innerText
Cells(I + 1, 2) = .Item(I).NextSibling.FirstChild.innerText
Next I
End With
End With
End Sub
Reference to add to the library before executing the above script:
Microsoft HTML Object Library
Microsoft XML, V6.0
I don't think you need Regular expressions to find the content on the page. You can use the relative positions of the elements to find the content I believe you are after.
Code
Option Explicit
Public Sub GetContent()
Dim URL As String: URL = "https://www.uid.admin.ch/Detail.aspx?uid_id=CHE-105.805.649"
Dim IE As Object: Set IE = CreateObject("InternetExplorer.Application")
Dim Labels As Object
Dim Label As Variant
Dim Values As Variant: ReDim Values(0 To 1, 0 To 5000)
Dim i As Long
With IE
.Navigate URL
.Visible = False
'Load the page
Do Until IE.busy = False And IE.readystate = 4
DoEvents
Loop
'Find all labels in the table
Set Labels = IE.document.getElementByID("cphContent_pnlDetails").getElementsByTagName("label")
'Iterate the labels, then find the divs relative to these
For Each Label In Labels
Values(0, i) = Label.InnerText
Values(1, i) = Label.NextSibling.Children(0).InnerText
i = i + 1
Next
End With
'Dump the values to Excel
ReDim Preserve Values(0 To 1, 0 To i - 1)
ThisWorkbook.Sheets(1).Range("A1:B" & i) = WorksheetFunction.Transpose(Values)
'Close IE
IE.Quit
End Sub

Need to remove numbers in parentheses and parentheses

I have a spreadsheet in which a column has string data (names) followed by numbers in parentheses; i.e., (9815536). These numbers are not constant in length. I need to get rid of the numbers in the parentheses and the parentheses. I've tried using Columns().Cells.Replace funtion to no avail. Is there a way to use a regular expression to do this? A cell example would look like:
Column A
John Doe (9815536)
Sam Smith (12906)
...
Code I've tried looks like:
Columns("A:A").Select
Selecton.Replace What:="\([0-9]*\), _
Replacement:="", LookAt:=xlPart, SearchOrder:=xlByRows, _
MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
A quick way to implement this is to use
a regexp
and a variant array in VBA.
Using code based on my Article Using Variant Arrays in Excel VBA for Large Scale Data Manipulation
Sub KillNumParen()
Dim rng1 As Range
Dim rngArea As Range
Dim lngRow As Long
Dim lngCol As Long
Dim lngCalc As Long
Dim objReg As Object
Dim X()
On Error Resume Next
Set rng1 = Application.InputBox("Select range for the replacement of non-number", "User select", Selection.Address, , , , , 8)
If rng1 Is Nothing Then Exit Sub
On Error GoTo 0
'See Patrick Matthews excellent article on using Regular Expressions with VBA
Set objReg = CreateObject("vbscript.regexp")
objReg.Pattern = "\(\d+\)"
objReg.Global = True
'Speed up the code by turning off screenupdating and setting calculation to manual
'Disable any code events that may occur when writing to cells
With Application
lngCalc = .Calculation
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
'Test each area in the user selected range
'Non contiguous range areas are common when using SpecialCells to define specific cell types to work on
For Each rngArea In rng1.Areas
'The most common outcome is used for the True outcome to optimise code speed
If rngArea.Cells.Count > 1 Then
'If there is more than once cell then set the variant array to the dimensions of the range area
'Using Value2 provides a useful speed improvement over Value. On my testing it was 2% on blank cells, up to 10% on non-blanks
X = rngArea.Value2
For lngRow = 1 To rngArea.Rows.Count
For lngCol = 1 To rngArea.Columns.Count
'replace the leading zeroes
X(lngRow, lngCol) = objReg.Replace(X(lngRow, lngCol), vbNullString)
Next lngCol
Next lngRow
'Dump the updated array sans leading zeroes back over the initial range
rngArea.Value2 = X
Else
'caters for a single cell range area. No variant array required
rngArea.Value = objReg.Replace(rngArea.Value, vbNullString)
End If
Next rngArea
'cleanup the Application settings
With Application
.ScreenUpdating = True
.Calculation = lngCalc
.EnableEvents = True
End With
Set objReg = Nothing
End Sub

outlook vba regex on each mail item in array

I am using the code below to create output showing how many emails were in a defined folder per day. This all works fine... My question is in the section with XXXXX, how do I reference each mail item so that I can do a regex for a word pattern? The end goal is to find out how many emails contained a keyword on a given day. The desired output is something like this:
,,
2015-01-01,15,2,5
2015-01-01,23,22,0
...
...
I'm ok to figure out the code on determining the number of emails based on the keyword, just not certain how to reference the email messages based on the code as is today...
Thanks for your advice.
Sub HowManyEmails()
Dim objOutlook As Object, objnSpace As Object, objFolder As MAPIFolder
Dim EmailCount As Integer
Set objOutlook = CreateObject("Outlook.Application")
Set objnSpace = objOutlook.GetNamespace("MAPI")
On Error Resume Next
Set objFolder = objnSpace.Folders("Personal Folders").Folders("Inbox").Folders("jobs.keep")
If Err.Number <> 0 Then
Err.Clear
MsgBox "No such folder."
Exit Sub
End If
EmailCount = objFolder.Items.Count
MsgBox "Number of emails in the folder: " & EmailCount, , "email count"
Dim dateStr As String
Dim myItems As Outlook.Items
Dim dict As Object
Dim msg As String
Set dict = CreateObject("Scripting.Dictionary")
Set myItems = objFolder.Items
myItems.SetColumns ("SentOn")
' Determine date of each message:
For Each myItem In myItems
xxxxxxx
xxxxxxx
xxxxxxx
dateStr = GetDate(myItem.SentOn)
If Not dict.Exists(dateStr) Then
dict(dateStr) = 0
End If
dict(dateStr) = CLng(dict(dateStr)) + 1
Next myItem
'Write output to file
Dim enviro As String
enviro = CStr(Environ("USERPROFILE"))
FILEPATH = enviro & "\Desktop\emails.csv"
Open FILEPATH For Output As 1
msg = ""
For Each o In dict.Keys
msg = msg & o & "," & dict(o) & vbCrLf
'MsgBox msg
Next
Print #1, msg
Close #1
'Write output to file
Set objFolder = Nothing
Set objnSpace = Nothing
Set objOutlook = Nothing
End Sub
Function GetDate(dt As Date) As String
GetDate = Year(dt) & "-" & Month(dt) & "-" & Day(dt)
End Function
You need to check the type of item in your code:
Dim myMailItem As Outlook.mailItem
....
For each myItem in myItems
If TypeOf myItem Is MailItem Then
Set myMailItem = myItem
XXXXXXXXXXX and rest of code here use myMailItem instead of myItem to get info
End If
Next myItem
First of all, I'd recommend using the Find/FindNext or Restrict methods of the Items class to find the subset of items that match to the specified condition. Iterating through all items in the folder may take a lot of time.
objnSpace.Folders("Personal Folders").Folders("Inbox")
Use the GetDefaultFolder method of the Namespace class to get a folder that represents the default folder of the requested type for the current profile.
Outlook uses EntryID values for identifying Outlook items uniquely. See Working with EntryIDs and StoreIDs for more information. If you know the IDs of an item and the folder it's stored in, you can directly reference the item using the NameSpace.GetItemFromID method.

Linq with HashTable Matching

I need another pair of eyes. I've been playing around with this LINQ syntax for scanning a Hashtable with a regular express. Can't seem to get it quite right. The goal is to match all keys to a regular expression, then using those results match the remaining values to an separate regular expression. In the test case below, I should end up with the first three entries.
Private ReadOnly Property Testhash As Hashtable
Get
Testhash = New Hashtable
Testhash.Add("a1a", "abc")
Testhash.Add("a2a", "aac")
Testhash.Add("a3a", "acc")
Testhash.Add("a4a", "ade")
Testhash.Add("a1b", "abc")
Testhash.Add("a2b", "aac")
Testhash.Add("a3b", "acc")
Testhash.Add("a4b", "ade")
End Get
End Property
Public Sub TestHashSearch()
Dim KeyPattern As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex("a.a")
Dim ValuePattern As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex("a.c")
Try
Dim queryMatchingPairs = (From item In Testhash
Let MatchedKeys = KeyPattern.Matches(item.key)
From key In MatchedKeys
Let MatchedValues = ValuePattern.Matches(key.value)
From val In MatchedValues
Select item).ToList.Distinct
Dim info = queryMatchingPairs
Catch ex As Exception
End Try
End Sub
Can't you match both the key and value at the same time?
Dim queryMatchingPairs = (From item In Testhash
Where KeyPattern.IsMatch(item.Key) And ValuePattern.IsMatch(item.Value)
Select item).ToList
I should have taken a break sooner, then worked a little more. The correct solution uses the original "from item" and not the lower "from key" in the second regular expression. Also, "distinct" is unnecessary for a hashtable.
Dim queryMatchingPairs = (From item In Testhash
Let MatchedKeys = KeyPattern.Matches(item.key)
From key In MatchedKeys
Let MatchedValues = ValuePattern.Matches(item.value)
From val In MatchedValues
Select item).ToList

Dynamic Depending Lists in Separated WorkSheets in VBA

I'm not really expert in VBA and I have a problem with my code and I don't know how to solve it. (The code is from: http://siddharthrout.wordpress.com/2011/07/29/excel-data-validationcreate-dynamic-dependent-lists-vba/)
I'm working with 8 dynamic dependent lists, and I thought the best way to automate the process and avoid to modify the macro in a future if I modify the lists was a VBA code.
Trying to find the correct code, I'm just working with to lists. For after, apply it for all lists.
I've checked the code and I discovered that there's an error (method 'intersect' of object '_global' failed) because I'm comparing two ranges from a different worksheets.
My code is:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Long, LastRow As Long, n As Long
Dim MyCol As Collection
Dim SearchString As String, Templist As String
Application.EnableEvents = False
On Error GoTo Whoa
' Find LastRow in Col A
LastRow = Sheet2.Range("A" & Rows.Count).End(xlUp).Row
If Not Intersect(Target, Sheet2.Columns(1)) Is Nothing Then
Set MyCol = New Collection
' Get the data from Col A into a collection
For i = 2 To LastRow
If Len(Trim(Sheet2.Range("A" & i).Value)) <> 0 Then
On Error Resume Next
MyCol.Add CStr(Sheet2.Range("A" & i).Value), CStr(Sheet2.Range("A" & i).Value)
On Error GoTo 0
End If
Next i
' Create a list for the Data Validation List
For n = 1 To MyCol.Count
Templist = Templist & "," & MyCol(n)
Next
Templist = Mid(Templist, 2)
Range("A2").ClearContents: Range("A2").Validation.Delete
' Create the Data Validation List
If Len(Trim(Templist)) <> 0 Then
With Range("A2").Validation
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Templist
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End If
' Capturing change in cell A2
ElseIf Not Intersect(Target, Range("A2")) Is Nothing Then
SearchString = Range("A2").Value
Templist = FindRange(Sheet2.Range("A2:A" & LastRow), SearchString)
Range("B2").ClearContents: Range("B2").Validation.Delete
If Len(Trim(Templist)) <> 0 Then
' Create the DV List
With Range("B2").Validation
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Templist
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End If
End If
LetsContinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
' Function required to find the list from Col B
Function FindRange(FirstRange As Range, StrSearch As String) As String
Dim aCell As Range, bCell As Range, oRange As Range
Dim ExitLoop As Boolean
Dim strTemp As String
Set aCell = FirstRange.Find(what:=StrSearch, LookIn:=xlValues, lookat:=xlWhole, SearchOrder:= _
xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
ExitLoop = False
If Not aCell Is Nothing Then
Set bCell = aCell
strTemp = strTemp & "," & aCell.Offset(, 1).Value
Do While ExitLoop = False
Set aCell = FirstRange.FindNext(After:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
strTemp = strTemp & "," & aCell.Offset(, 1).Value
Else
ExitLoop = True
End If
Loop
FindRange = Mid(strTemp, 2)
End If
End Function
Into the Sheet1, I just want the cells to select the list options and into the Sheet2, I want the all dynamic and dependent lists.
Is there any possibility to compare two ranges from a different worksheets using these algorithm? Or an alternative code to create a pick list for 8 depending and dynamic lists?
I am going to turn you to this page that describes dynamic dependent list usage very well.
Dynamic Dependent Lists
Perhaps you don't need VBA at all, unless you have to alter these on the fly, or based on some other variable. It's always best to use Excel's built-in functionality first, and code 2nd.
In case you are wandering, you can get around having lists on two different sheets by setting the named range scope to the entire workbook.
Edit: Adding answer to direct VBA error.
Since you didn't say, not sure if your Intersect is breaking here:
If Not Intersect(Target, Sheet2.Columns(1)) Is Nothing Then
but I think it is. Try this:
If Not Intersect(Target, Columns(1).EntireColumn) Is Nothing Then