Read json array and print the values using vba - excel

i have a json object that has a json array. I need to iterate array and print the values. I am using excel [vba].I am very new to VBA. Requesting anyone to help me out.
Set sr= CreateObject("MSScriptControl.ScriptControl")
sr.Language = "JScript"
Set Retval = MyScript.Eval("(" + newString + ")")
MsgBox Retval.Earth.Fruits(0).name
when i execute the above piece i am getting 'Object doesn't support this property or method'.
I need to iterate all the names under Fruit

I would use a json parser e.g. jsonconverter.bas as can use with 64bit and 32bit and doesn't represent the same security risk as scriptControl.
Jsonconverter.bas: Download raw code from here and add to standard module called jsonConverter . You then need to go VBE > Tools > References > Add reference to Microsoft Scripting Runtime.
Your json object is a dictionary with an inner dictionary Earth containing a collection Fruits (where Fruits is the key). The items in the collection are dictionaries with keys of "name" and values are the fruits. The [] denotes collection and {} dictionary.
Option Explicit
Public Sub test()
Dim s As String, json As Object, item As Object
s = "{""Earth"":{""Fruits"":[{""name"":""Mango""},{""name"":""Apple""},{""name"":""Banana""}]}}"
Set json = JsonConverter.ParseJson(s)
For Each item In json("Earth")("Fruits")
Debug.Print item("name")
Next
End Sub
Example with regex:
Public Sub test()
Dim s As String
s = "{""Earth"":{""Fruits"":[{""name"":""Mango""},{""name"":""Apple""},{""name"":""Banana""}]}}"
PrintMatches s
End Sub
Public Sub PrintMatches(ByVal s As String)
Dim i As Long, matches As Object, re As Object
Set re = CreateObject("VBScript.RegExp")
With re
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = """name"":""(.*?)"""
If .test(s) Then
Set matches = .Execute(s)
For i = 0 To matches.Count - 1
Debug.Print matches(i).SubMatches(0)
Next i
Else
Debug.Print "No matches"
End If
End With
End Sub

Related

VBA Append unique regular expressions to string variable

How can I grab matching regular expressions from a string, remove the duplicates, and append them to a string variable that separates each by a comma?
For example, in the string, "this is an example of the desired regular expressions: BPOI-G8J7R9, BPOI-G8J7R9 and BPOI-E5Q8D2" the desired output string would be "BPOI-G8J7R9,BPOI-E5Q8D2"
I have attempted to use a dictionary to remove the duplicates, but my function is spitting out the dreaded #Value error.
Can anyone see where I'm going wrong here? Or is there any suggestion for a better way of going about this task?
Code below:
Public Function extractexpressions(ByVal text As String) As String
Dim regex, expressions, expressions_dict As Object, result As String, found_expressions As Variant, i As Long
Set regex = CreateObject("VBScript.RegExp")
regex.Pattern = "[A-Z][A-Z][A-Z][A-Z][-]\w\w\w\w\w\w"
regex.Global = True
Set expressions_dict = CreateObject("Scripting.Dictionary")
If regex.Test(text) Then
expressions = regex.Execute(text)
End If
For Each item In expressions
If Not expressions_dict.exists(item) Then expressions_dict.Add item, 1
Next
found_expressions = expressions_dict.items
result = ""
For i = 1 To expressions_dict.Count - 1
result = result & found_expressions(i) & ","
Next i
extractexpressions = result
End Function
If you call your function from a Sub you will be able to debug it.
See the comment below about adding the matches as keys to the dictionary - if you add the match object itself, instead of explicitly specifying the match's value property, your dictionary won't de-duplicate your matches (because two or more match objects with the same value are still distinct objects).
Sub Tester()
Debug.Print extractexpressions("ABCD-999999 and DFRG-123456 also ABCD-999999 blah")
End Sub
Public Function extractexpressions(ByVal text As String) As String
Dim regex As Object, expressions As Object, expressions_dict As Object
Dim item
Set regex = CreateObject("VBScript.RegExp")
regex.Pattern = "[A-Z]{4}-\w{6}"
regex.Global = True
If regex.Test(text) Then
Set expressions = regex.Execute(text)
Set expressions_dict = CreateObject("Scripting.Dictionary")
For Each item In expressions
'A dictionary can have object-type keys, so make sure to add the match *value*
' and the not match object itself
If Not expressions_dict.Exists(item.Value) Then expressions_dict.Add item.Value, 1
Next
extractexpressions = Join(expressions_dict.Keys, ",")
End If
End Function
VBA's regex object actually supports the backreference to a previous capture group. Hence we can get all the unique items through the expression itself:
([A-Z]{4}-\w{6})(?!.*\1)
See an online demo
To put this in practice:
Sub Test()
Debug.Print extractexpressions("this is an example of the desired regular expressions: BPOI-G8J7R9, BPOI-G8J7R9 and BPOI-E5Q8D2")
End Sub
Public Function extractexpressions(ByVal text As String) As String
With CreateObject("VBScript.RegExp")
.Pattern = "([A-Z]{4}-\w{6})(?!.*\1)|."
.Global = True
extractexpressions = Replace(Application.Trim(.Replace(text, "$1 ")), " ", ",")
End With
End Function
Prints:

Extract value from URL and set it as variable

I want to double-click a cell in Excel to open a URL.
I've been using VBA for this aspect, but I am facing an issue.
I want to extract a value from URL and use it as variable in VBA.
Here is part of the script:
Dim ID As String
ID = ActiveSheet.Range("S" & Target.Cells.Row & "").Value
rptUrl = "http://...=" + ID
If (ID <> "") Then
ThisWorkbook.FollowHyperlink (rptUrl)
In such case, if the ID is at the end of the URL, it works.
What happens if the ID that I want to extract is somewhere in the middle of the URL, and not at the end?
For example:
rptUrl = "http://..**ID**..="
I tried the following:
rptUrl = "http://.. + **ID** + ..="
If you want to use a regular expression, here's an option that packages the regular expression into a function that you can call. If the URL contains "ID", it will return the corresponding value; otherwise, it will just return a blank string
Function GetId(sInput) As String
Dim oReg As Object
Dim m As Variant
Dim sOutput As String
sOutput = ""
Set oReg = CreateObject("VBScript.Regexp")
With oReg
.Global = False
.ignorecase = True
.MultiLine = False
.Pattern = "id=(\w+)[&|$]"
End With
If oReg.Test(sInput) Then
sOutput = oReg.Execute(sInput)(0).submatches(0)
End If
GetId = sOutput
End Function
Sub Test()
Debug.Print GetId("mysrv.com/form.jsp?id=12345&cn=0")
End Sub

In VBA, how to extract the string before a number from the text

From ActiveWorkbook.name, I would like to extract the strings that are before (left side of ) the numbers. Since I want to use the same code in multiple workbooks, the file names would be variable, but every file name has date info in the middle (yyyymmdd).
In case of excel file, I can use the below formula, but can I apply the same kind of method in VBA?
=LEFT(A1,MIN(FIND({0,1,2,3,4,5,6,7,8,9},ASC(A1)&1234567890))-1)
Example: MyExcelWorkbook_Management_20200602_MyName.xlsm
In above case, I want to extract "MyExcelWorkbook_Management_".
The most basic thing you could do is to replicate something that worked for you in Excel through Evaluate:
Sub Test()
Dim str As String: str = "MyExcelWorkbook_Management_20200602_MyName.xlsm"
Debug.Print Evaluate(Replace("=LEFT(""X"",MIN(FIND({0,1,2,3,4,5,6,7,8,9},ASC(""X"")&1234567890))-1)", "X", str))
End Sub
Pretty? Not really, but it does the job and got it's limitations.
You could use Regular Expressions to extract any letters / underscores before the number as well
Dim str As String
str = "MyExcelWorkbook_Management_20200602_MyName.xlsm"
With CreateObject("vbscript.regexp")
.Pattern = "^\D*"
.Global = True
MsgBox .Execute(str)(0)
End With
Gives:
MyExcelWorkbook_Management_
So basically you want to use the Midfunction to look for the first numerical character in your input string, and then cut your input string to that position.
That means we need to loop through the string from left to right, look at one character at a time and see if it is a digit or not.
This code does exactly that:
Option Explicit
Sub extratLeftText()
Dim someString As String
Dim result As String
someString = "Hello World1234"
Dim i As Long
Dim c As String 'one character of your string
For i = 1 To Len(someString)
c = Mid(someString, i, 1)
If IsNumeric(c) = True Then 'should write "If IsNumeric(c) = True AND i>1 Then" to avoid an "out of bounds" error
result = Left(someString, i - 1)
Exit For
End If
Next i
MsgBox result
End Sub
Last thing you need to do is to load in some workbook name into your VBA function. Generally this is done with the .Name method of the workbookobject:
Sub workbookName()
Dim wb As Workbook
Set wb = ActiveWorkbook
MsgBox wb.Name
End Sub
Of course you would need to find some way to replace the Set wb = ActiveWorkbook line with code that suits your purpose.

VBA Excel Need the inner text but not the tag the it also contains

I need to get the "4,499.00" in the span tag but not the SEK part.
<span id="big-value" class=big-value"> <sup> SEK </sup> 4,499.00 </span>
I use
Recovered = ie.document.getElementsByTagName("span")(22).innertext
But it do not seem to work when the tag contains another tag. It get back both the SEK and 4,499.00 (Which it should but I do not know how to work around it). Any idea of how to work round this?
You will need to use the tag information or parse the string to the extract the number. The code below uses a Regexp to do the later
Your text
Sub Test()
MsgBox CleanStr("<span id=""big-value"" class=big-value""""> <sup> SEK </sup> 4,499.00 </span>")
End Sub
UDF to extract number
Function CleanStr(StrIn As String) As String
Dim objRegex As Object
Dim objRegexMC As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "[0-9,\.]+"
If .Test(StrIn) Then
Set objRegexMC = .Execute(StrIn)
CleanStr = objRegexMC(0)
Else
CleanStr = "No Match"
End If
End With
End Function

VBA (Excel) Dictionary on Mac?

I have an Excel VBA project that makes heavy use of Windows Scripting Dictionary objects. I recently had a user attempt to use it on a Mac and received the following error:
Compile Error: Can't find project or library
Which is the result of using the Tools > References > Microsoft Scripting Runtime library.
My question is, is there a way to make this work on a Mac?
The following are the 3 cases I can think of as being possible solutions:
Use a Mac plugin that enables use of Dictionaries on Macs (my favorite option if one exists)
Do some kind of variable switch like the following:
isMac = CheckIfMac
If isMac Then
' Change dictionary variable to some other data type that is Mac friendly and provides the same functionality
End If
Write 2 completely separate routines to do the same thing (please let this not be what needs to happen):
isMac = CheckIfMac
If isMac Then
DoTheMacRoutine
Else
DoTheWindowsRoutine
End If
Pulling the Answer from the comments to prevent link rot.
Patrick O'Beirne # sysmod wrote a class set that addresses this issue.
Be sure to stop by Patirk's Blog to say thanks! Also there is a chance he has a newer version.
save this as a plain text file named KeyValuePair.cls and import into Excel
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "KeyValuePair"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
'Unrestricted class just to hold pairs of values together and permit Dictionary object updating
Public Key As String
Public value As Variant
save this as a plain text file named Dictionary.cls and import into excel
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Dictionary"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
'Collection methods: Add, Count, Item, Remove
'Dictionary : .Add(Key as string, Item as variant), .CompareMode, .Count, .Exists(Key); _
.Item(Key) - writeable, .Items, .Keys, .Remove(Key), .RemoveAll
'plus KeyValuePairs collection, KeyValuePair(Index as long), Tag as variant
' 25-11-2011 KeyValuePair helper object
Public KeyValuePairs As Collection ' open access but allows iteration
Public Tag As Variant ' read/write unrestricted
Private Sub Class_Initialize()
Set KeyValuePairs = New Collection
End Sub
Private Sub Class_Terminate()
Set KeyValuePairs = Nothing
End Sub
' in Scripting.Dictionary this is writeable, here we have only vbtextCompare because we are using a Collection
Public Property Get CompareMode() As VbCompareMethod
CompareMode = vbTextCompare '=1; vbBinaryCompare=0
End Property
Public Property Let Item(Key As String, Item As Variant) ' dic.Item(Key) = value ' update a scalar value for an existing key
Let KeyValuePairs.Item(Key).value = Item
End Property
Public Property Set Item(Key As String, Item As Variant) ' Set dic.Item(Key) = value ' update an object value for an existing key
Set KeyValuePairs.Item(Key).value = Item
End Property
Public Property Get Item(Key As String) As Variant
AssignVariable Item, KeyValuePairs.Item(Key).value
End Property
' Collection parameter order is Add(Item,Key); Dictionary is Add(Key,Item) so always used named arguments
Public Sub Add(Key As String, Item As Variant)
Dim oKVP As KeyValuePair
Set oKVP = New KeyValuePair
oKVP.Key = Key
If IsObject(Item) Then
Set oKVP.value = Item
Else
Let oKVP.value = Item
End If
KeyValuePairs.Add Item:=oKVP, Key:=Key
End Sub
Public Property Get Exists(Key As String) As Boolean
On Error Resume Next
Exists = TypeName(KeyValuePairs.Item(Key)) > "" ' we can have blank key, empty item
End Property
Public Sub Remove(Key As String)
'show error if not there rather than On Error Resume Next
KeyValuePairs.Remove Key
End Sub
Public Sub RemoveAll()
Set KeyValuePairs = Nothing
Set KeyValuePairs = New Collection
End Sub
Public Property Get Count() As Long
Count = KeyValuePairs.Count
End Property
Public Property Get Items() As Variant ' for compatibility with Scripting.Dictionary
Dim vlist As Variant, i As Long
If Me.Count > 0 Then
ReDim vlist(0 To Me.Count - 1) ' to get a 0-based array same as scripting.dictionary
For i = LBound(vlist) To UBound(vlist)
AssignVariable vlist(i), KeyValuePairs.Item(1 + i).value ' could be scalar or array or object
Next i
Items = vlist
End If
End Property
Public Property Get Keys() As String()
Dim vlist() As String, i As Long
If Me.Count > 0 Then
ReDim vlist(0 To Me.Count - 1)
For i = LBound(vlist) To UBound(vlist)
vlist(i) = KeyValuePairs.Item(1 + i).Key '
Next i
Keys = vlist
End If
End Property
Public Property Get KeyValuePair(Index As Long) As Variant ' returns KeyValuePair object
Set KeyValuePair = KeyValuePairs.Item(1 + Index) ' collections are 1-based
End Property
Private Sub AssignVariable(variable As Variant, value As Variant)
If IsObject(value) Then
Set variable = value
Else
Let variable = value
End If
End Sub
Public Sub DebugPrint()
Dim lItem As Long, lIndex As Long, vItem As Variant, oKVP As KeyValuePair
lItem = 0
For Each oKVP In KeyValuePairs
lItem = lItem + 1
Debug.Print lItem; oKVP.Key; " "; TypeName(oKVP.value);
If InStr(1, TypeName(oKVP.value), "()") > 0 Then
vItem = oKVP.value
Debug.Print "("; CStr(LBound(vItem)); " to "; CStr(UBound(vItem)); ")";
For lIndex = LBound(vItem) To UBound(vItem)
Debug.Print " (" & CStr(lIndex) & ")"; TypeName(vItem(lIndex)); "="; vItem(lIndex);
Next
Debug.Print
Else
Debug.Print "="; oKVP.value
End If
Next
End Sub
'NB VBA Collection object index is 1-based, scripting.dictionary items array is 0-based
'cf Scripting.Dictionary Methods s.Add(Key, Item), s.CompareMode, s.Count, s.Exists(Key); _
s.Item(Key) - updateable, s.Items, s.Key(Key), s.Keys, s.Remove(Key), s.RemoveAll
'Scripting.Dictionary has no index number; you can index the 0-based variant array of Items returned
' unlike Collections which can be indexed starting at 1
'Efficient iteration is For Each varPair in thisdic.KeyValuePairs
'Another difference I introduce is that in a scripting.dictionary, the doc says
' If key is not found when changing an item, a new key is created with the specified newitem.
' If key is not found when attempting to return an existing item, a new key is created and its corresponding item is left empty.
'but I want to raise an error when addressing a key that does not exist
'similarly, the scripting.dictionary will create separate integer and string keys for eg 2
Patirk's implementation doesn't work for MS Office 2016 on Mac. I made use of the implementation by Tim Hall.
Here is the link: https://github.com/VBA-tools/VBA-Dictionary
Also import of cls files into Excel doesn't work in MS Office 2016 on Mac as of September 2017. So I had to create a class module and to copy and paste the contents of Dictionary.cls manually in that module while removing meta info from Dictionary.cls such as VERSION 1.0 CLASS, BEGIN, END, Attribute.
I have at last updated the files for Excel 2016 for Mac.
http://www.sysmod.com/Dictionary.zip
(capital D in Dictionary)
Unzip this and import the class files (tested in Excel 2016 for Mac 16.13 Build 424, 27-Apr-2018)
My bug report to MS is at answers.microsoft.com
Excel 16.13 for Mac User Defined Class passed as parameter all properties are Null
Let me know if I've missed anything else!
Good luck,
Patrick O'Beirne

Resources