VBA Access parsing JSON nested array - arrays

I am using VBA Access to get data from Google Books for a library database. The code is based on that given in this stackoverflow question.
I am struggling for the right code to allow for a varying number of authors as the information is in a nested array. I would like all of the author names to appear in one TextBox.
I tried:
Form_AddAmendItems.AuthorTextBox.Value = Join(subitem("authors"), ",")
from the link above but that fails to find any result.
I think I need to use UBound and LBound to count the number of authors and then loop through and add each one. But I haven't been able to find an example of how to do that.
Currently as a workaround I can populate the AuthorTextBox with the names of up to 3 authors, which is enough for my needs. But if there are less than 3 authors the error handler message pops up because it hasn't been able to find the requested data.
I am using the VBA-JSON Converter from here.
This is the JSON I would like to parse (from here)
{
"kind": "books#volumes",
"totalItems": 1,
"items": [
{
"kind": "books#volume",
"id": "BT2CAz-EjvcC",
"etag": "6Z7JqyUtyJU",
"selfLink": "https://www.googleapis.com/books/v1/volumes/BT2CAz-EjvcC",
"volumeInfo": {
"title": "Collins Gem German Dictionary",
"subtitle": "German-English, English-German",
"authors": [
"Veronika Calderwood-Schnorr",
"Ute Nicol",
"Peter Terrell"
]
And this is my VBA code:
Private Sub FindBookDetailsButton_Click()
'Error handle for Null Strings
If IsNull(Me.ISBNTextBox) = True Then
MsgBox "Item ID not specified.", vbExclamation + vbOKOnly, "Error"
Exit Sub
End If
'Error message if there is no match
On Error GoTo ErrMsg
Dim http As Object, JSON As Object, i As Integer, subitem As Object
Dim ISBN As String
ISBN = CStr(Me.ISBNTextBox.Value)
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", "https://www.googleapis.com/books/v1/volumes?q=isbn:" & ISBN, False
http.send
Set JSON = ParseJSON(http.responseText)
For Each item In JSON("items")
Set subitem = item("volumeInfo")
Form_AddAmendItems.TitleTextBox.Value = subitem("title")
Form_AddAmendItems.AuthorTextBox.Value = subitem("authors")(1)
Form_AddAmendItems.PublisherTextBox.Value = subitem("publisher")
'For multiple authors
Set subitem = item("volumeInfo")
If subitem.Exists("authors") Then
For Each item2 In subitem("authors")
Form_AddAmendItems.AuthorTextBox.Value = subitem("authors")(1) & ", " & subitem("authors")(2)
Next
For Each item3 In subitem("authors")
Form_AddAmendItems.AuthorTextBox.Value = subitem("authors")(1) & ", " & subitem("authors")(2) & ", " & subitem("authors")(3)
Next
End If
Next
'To end with success
MsgBox ("Process complete"), vbInformation
Exit Sub
'To end with an error message
ErrMsg:
MsgBox ("No match obtained"), vbCritical
End Sub

An array or collection or dictionary can be looped without knowing that object's limits. So if subitem("authors") is one of those object types, your code could be something like (essentially the code shown in accepted answer for the SO link in your question):
Set subitem = item("volumeInfo")
Form_AddAmendItems.TitleTextBox.Value = subitem("title")
Form_AddAmendItems.PublisherTextBox.Value = subitem("publisher")
If subitem.Exists("authors") Then
For Each item2 In subitem("authors")
sA = sA & item2 & ","
Next
sA = Left(sA, Len(sA) - 1) 'remove trailing comma
If sA <> "" Then Form_AddAmendItems.AuthorTextBox.Value = sA
End If
I discovered that elements in the ISBN download aren't always the same. As an example, with ISBN 0-575-05577-4 the publisher is not provided even though publishedDate is. Might use an If Then condition for each element you want to explicitly address. As a side note, found it interesting that the co-author for that book was not included.

Related

Word VBA Array not behaving as expected

I'm iterating through a list of keywords to define terms in a document but only certain keywords will get picked up.
For instance, with Array("Agreement", "deed", "AGREEMENT", "letter agreement", "letter", "Undertaking"), "Agreement" and "letter" get picked up just fine, but letter agreement and Undertaking do not.
I've tried rearranging the order of the array but that does nothing.
I'm guessing there's something fundamental about arrays I'm misunderstanding. I'm more familiar with python and am going for list functionality.
Full code is below. Any pointers would be very much appreciated.
Function getagree() As String
Dim aggrlist As Variant
aggrlist = Array("Agreement", "NDA", "deed", "AGREEMENT", "letter
agreement", "letter", "Undertaking", "Confidentiality Undertaking",
"agreement")
Set myRange = ActiveDocument.Content
With myRange.Find
For Each aggr In aggrlist
.ClearFormatting
.Text = aggr
.MatchWholeWord = True
.MatchCase = True
.Execute Forward:=True
If .Found = True Then
getagree = aggr
End If
Next
End With
End Function
Try using an underscore (_) to break your string into multiple lines...
aggrlist = Array("Agreement", "NDA", "deed", "AGREEMENT", _
"letter agreement", "letter", "Undertaking", _
"Confidentiality Undertaking", "agreement")

How do I check if an object collides with every object in an array?(Picture boxes)

High school student here and I'm pretty rusty on my code. Okay, I have to have an image scroll along, and if it hits an object(in this case both are picture boxes), it resets.
The problem is when it gets to the If statement below, it won't work saying " 'bounds' is not a member of 'system.array' "
If PtbIcon.Bounds.IntersectsWith(objects.Bounds) Then
The error is the Objects.bounds
If PtbIcon.Bounds.IntersectsWith(objects.Bounds) Then
t = t + 1
PtbIcon.Location = New Point(29, 236)
'resets when you die, sets the score
End If
lblScore.Text = "Your Score Equals" & t
End
Why doesn't this work? Why? Is there a simpler way of checking all of this, such as calling a function which checks the bounds individually?
Use Linq.
Dim t As Integer = 0
PtbIcon.All(Function(pb As PictureBox) As Boolean
' Checking goes here with pb
' Return True if you want to go through all of them
End Function)
lblScore.Text = "Your Score Equals" & t

rails 3.2 searching action. how to pass results to another action

I should create a search page in which i have to save in an Array all the results of the searching. I had two problems:
1) I used the following statement:
Company.joins(:references).where(sql_string)
that returns an ActiveRecord:Relation and it's not good for me cause i have to display these results in the index action , in which i use an each statement. So , to overcame this problem i used the to_a.
I checked the .class of my variable and with the to_a it passed from ActiveRecord:Relation to Array. So , it seems that this solve the problem.
Company.joins(:references).where(sql_string).to_a
2) Now, i have to pass this variable (Array) into my index action.
I executed the search in the action called search:
def search
...
#companies = Company.joins(:references).where(sql_string).to_a
end
Now, i want to pass this to index:
def index
#companies ||= Company.all
end
I used #companies ||= Company.all cause i think that the #companies is and istance variable and it should be available in all the actions of the class. Isn't it? By the way, it doesn't workl. I mean , the results are not shared through the two methods.
Also , in the search action i don't know how to call index action. I used the redirect_to but this bring me to another problem.
def search
...
#companies = Company.joins(:references).where(sql_string).to_a
redirect_to companies_index_path
end
The second time i call the search action it brings me into the index action.As i insered the searching value. At really he still had the past searching in memory, and i don't want this behavior.
So , in other words, i want to:
passing #companies searching result to index action.
avoid the loop between search-index. So in every new request resets
the old searching.
i want to know if it's correct the casting with the to_a to bring
an ActiveRecord:Relation to Array.
Thank You.
EDIT:
def search
stringa_sql = ""
ragione_sociale = ""
riferimento = ""
note = ""
min_date = ""
max_date = ""
company_type = ""
sector = ""
country = ""
certification = ""
contact = ""
state = ""
manage = ""
consultation = ""
formation = ""
software = ""
if params[:ragione_sociale]
ragione_sociale = params[:ragione_sociale]
stringa_sql = "ragione_sociale like "+"'%"+ragione_sociale+"%'"
end
if params[:riferimento]
riferimento = params[:riferimento]
stringa_sql += " AND nome like "+"'%"+riferimento+"%'"
end
if params[:note]
note = params[:note]
stringa_sql += " AND note like "+"'%"+note+"%'"
end
if params[:min_date] && params[:min_date]!= ""
if params[:max_date] && params[:max_date]!= ""
min_date = params[:min_date]
max_date = params[:max_date]
stringa_sql += " AND richiamare >="+min_date+" AND richiamare <="+max_date
end
end
if params[:company_type] #se inviamo la richesta senza scrivere params[:category] viene passato vuoto
if params[:company_type][:id] != ""
company_type = params[:company_type][:id]
stringa_sql += " AND id_forma_giuridica = "+company_type
end
end
if params[:sector]
if params[:sector][:id] != ""
sector = params[:sector][:id]
stringa_sql += " AND id_settore = "+sector
end
end
if params[:country]
if params[:country][:id] != ""
country = params[:country][:id]
stringa_sql += " AND id_provincia = "+country
end
end
if params[:certification]
if params[:certification][:id] != ""
certification = params[:certification][:id]
stringa_sql += " AND id_certificazione = "+certification
end
end
if params[:contact]
if params[:contact][:id] != ""
contact = params[:contact][:id]
stringa_sql += " AND id_contattato = "+contact
end
end
if params[:state]
if params[:state][:id] != ""
state = params[:state][:id]
stringa_sql += " AND id_stato = "+state
end
end
if params[:manage]
if params[:manage][:id] != ""
manage = params[:manage][:id]
stringa_sql += " AND id_gestito = "+manage
end
end
if params[:consultation]
if params[:consultation][:id] != ""
consultation = params[:consultation][:id]
stringa_sql += " AND id_consulenza = "+consultation
end
end
if params[:formation]
if params[:formation][:id] != ""
formation = params[:formation][:id]
#formazione DA METTERE
end
end
if params[:software]
if params[:software][:id] != ""
software = params[:software][:id]
stringa_sql += " AND id_software = "+software
end
end
#companies = Company.search(stringa_sql).to_a
if not #companies.empty?
redirect_to companies_index_path
end
end
index:
def index
#companies ||= Company.all
end
I used #companies ||= Company.all cause i think that the #companies
is and istance variable and it should be available in all the actions
of the class. Isn't it?
Not really, it depends on from where you want to access the #companies instance variable. e.g. from which view, you need #companies instance variable in the corresponding action method of the controller.
The second time i call the search action it brings me into the index
action
You are using redirect_to companies_index_path in your search method which brings you to the index action.
To implement search in your application, you can follow this somewhat standard process:
In your application_controller.rb which will have the #search_query.
# Returns string
def search_query
#search_query ||= params[:query] || params[:search]
end
Then, in your searches_controller.rb, you can have:
def search
# in the method build your search results based on
# search_query param
#search_results = Company.joins(:references).where(sql_string(search_query)).to_a
end
In your routes.rb, you can have:
get '/search(/:query)' => 'searches#search', query: /.+/, as: 'search'
Which will take you to the searches_controller's search action where you are building the search results #search_results.
Finally, you need to have a app/views/searches/search.html.erb view file where you have access to your #search_results instance variable and you can just loop through them and display them in this view.
Answers to your last 3 questions:
passing #companies searching result to index action.
avoid the loop between search-index. So in every new request resets
the old searching.
You can overcome these problems by following the request/response flow that I have mentioned above. You should not share your index view with your search and you should not have any loop between search and index. Both of them are separate actions of the controller and can be handled separately as I showed above.
i want to know if it's correct the casting with the to_a to bring an
ActiveRecord:Relation to Array.
You can do that if you want. But, you don't really need it in this use case. You can store the ActiveRecord:Relation in your search_results and when you access this instance variable from inside your search.html.erb view, you can easily loop through using a .each do block. So, you don't have to worry about ActiveRecord:Relation and Array.

Convert.ChangeType() Returns incorrect value

I've got a class that parses a CNC file, but I'm having difficulties with trailing "words" on each line of the file.
My code parses all leading "words" until it reaches the final word. It's most noticeable when parsing "Z" values or other Double type values. I've debugged it enough to notice that it successfully parses the numerical value just as it does with "X" and "Y" values, but it doesn't seem to successfully convert it to double. Is there an issue with a character I'm missing or something?
Here's my code:
If IO.File.Exists("Some GCode File.eia") Then
Dim sr As New IO.StreamReader("Some GCode File.eia")
Dim i As Integer = 0
'Read text file
Do While Not sr.EndOfStream
'Get the words in the line
Dim words() As String = sr.ReadLine.Split(" ")
'iterate through each word
For i = 0 To words.Length - 1 Step 1
'iterate through each "registered" keyword. Handled earlier in program
For Each cmd As String In _registeredCmds.Keys
'if current word resembles keyword then process
If words(i) Like cmd & "*" Then
_commands.Add(i, _registeredCmds(cmd))
'Double check availability of a Type to convert to
If Not IsNothing(_commands(i).DataType) Then
'Verify enum ScopeType exists
If Not IsNothing(_commands(i).Scope) Then
'If ScopeType is modal then just set it to True. I'll fix later
If _commands(i).Scope = ScopeType.Modal Then
_commands(i).DataValue = True
Else
'Catch errors in conversion
Try
'Get the value of the gcode command by removing the "registered" keyword from the string
Dim strTemp As String = words(i).Remove(0, words(i).IndexOf(_commands(i).Key) + _commands(i).Key.Length)
'Save the parsed value into an Object type in another class
_commands(i).DataValue = Convert.ChangeType(strTemp, _commands(i).DataType)
Catch ex As Exception
'Log(vbTab & "Error:" & ex.Message)
End Try
End If
Else
'Log(vbTab & "Command scope is null")
End If
Else
'Log(vbTab & "Command datatype is null")
End If
Continue For
End If
Next
Next
i += 1
Loop
Else
Throw New ApplicationException("FilePath provided does not exist! FilePath Provided:'Some GCode File.eia'")
End If
Here's an example of the GCode:
N2930 X-.2187 Y-1.2378 Z-.0135
N2940 X-.2195 Y-1.2434 Z-.0121
N2950 X-.2187 Y-1.249 Z-.0108
N2960 X-.2164 Y-1.2542 Z-.0096
N2970 X-.2125 Y-1.2585 Z-.0086
N2980 X-.207 Y-1.2613 Z-.0079
N2990 X-.2 Y-1.2624 Z-.0076
N3000 X0.
N3010 X12.
N3020 X24.
N3030 X24.2
N3040 X24.2072 Y-1.2635 Z-.0075
N3050 X24.2127 Y-1.2665 Z-.0071
N3060 X24.2167 Y-1.2709 Z-.0064
N3070 X24.2191 Y-1.2763 Z-.0057
N3080 X24.2199 Y-1.2821 Z-.0048
N3090 X24.2191 Y-1.2879 Z-.004
N3100 X24.2167 Y-1.2933 Z-.0032
N3110 X24.2127 Y-1.2977 Z-.0026
N3120 X24.2072 Y-1.3007 Z-.0021
N3130 X24.2 Y-1.3018 Z-.002
N3140 X24.
N3150 X12.
N3160 X0.
N3170 X-.2
N3180 X-.2074 Y-1.3029 Z-.0019
N3190 X-.2131 Y-1.306 Z-.0018
N3200 X-.2172 Y-1.3106 Z-.0016
N3210 X-.2196 Y-1.3161 Z-.0013
N3220 X-.2204 Y-1.3222 Z-.001
N3230 X-.2196 Y-1.3282 Z-.0007
N3240 X-.2172 Y-1.3338 Z-.0004
N3250 X-.2131 Y-1.3384 Z-.0002
N3260 X-.2074 Y-1.3415 Z-.0001
N3270 X-.2 Y-1.3426 Z0.
N3280 X0.
N3290 X12.
N3300 X24.
N3310 X24.2
N3320 G0 Z.1
N3330 Z1.0
N3340 G91 G28 Z0.0
N3350 G90
With regard to the sample CNC code above, you'll notice that X and Y commands with a trailing Z command parse correctly.
EDIT
Per comment, here is a breakdown of _commands()
_commands = SortedList(Of Integer, Command)
Command is a class with the following properties:
Scope as Enum ScopeType
Name as String
Key as String
DataType as Type
DataValue as Object
EDIT: Solution!
Figured out what was wrong. The arrays that make up the construction of the classes were essentially being passed a reference to the "registered" array of objects from the Command class. Therefore every time I parsed the value out of the "word" each line, I was overwriting the DataValue in the Command object.
The solution was to declare a new 'Command' object with every parse and append it to the proper array.
Here's my short hand:
...
For I = 0 To words.Length - 1 Step 1
'iterate through each "registered" keyword. Handled earlier in program
For Each cmd as String in _registeredCmds.Keys
'if current word resembles keyword then process
If words(I) Like cmd & "*" Then
'NEW!!! Declare unassigned Command object
Dim com As Command
' ****** New elongated logic double checking existence of values.....
If _registeredCmds.Keys.Scope = ScopeType.Modal Then
'assign Command object to previously declared variable com
com = New Command()'There's technically passing arguments now to ensure items are transferred
Else
'Parse and pass DataValue from this word
com = New Command()'There's technically passing arguments now to ensure items are transferred
End If
'New sub to add Command object to local array
Add(com)
Continue For
End If
Next
Next
...

Recursively search files for information contained in excel cell and return path

Okedoke... I have an Excel spreadsheet with a filename in column A. The filenames listed in column A appear in one or more text files in one or more source directories.
I need Excel to search the text files recursively and return the path(s) of the file(s) that contain the filename specified in column A into column B. If more than one file go to column C etc.
The Excel sheet would be
__________________________________
__|______A___________|______B_____|
1 | filename.avi | |
2 | another_file.flv | |
The text files to search would be in multiple directories under C:\WebDocs\ and are DokuWiki pages some are quite short, such as this page that would need to be returned
===== Problem Description =====
Reopen a closed bank reconciliation.
===== Solution =====
Demonstration of the tool box routine that allows reposting of the bank rec.
{{videos:bank_rec_reopen1006031511.flv|}}
===== Additional Information -cm =====
You may have noticed that in the video there is a number to the right of the bank account number. In this case it was a 0. That indicates department 0 which is all departments. You get the department 0 if you have all departments combined using the option in the bank set up called "One Bank for All Departments". If this setting is not checked then when you create your starting bank rec for each department you will get a 1 to the right of the bank rec for department 1 and so on. You should normally only have a 0, or have numbers 1 or greater. If you have both, then the method was changed after the initial bank rec was made. You just have to be aware of this as you move forward. As always backup before you make any changes.
There are some other pages though that are quite long that do not contain videos but would be in the directories being searched. Format is the same, plain text, ==== are place holders for headings may contain links to other pages/sites.
I did find an existing VBA script that sort of does what I need it to. It does not recurse and returns too much information, date/time stamp for instance, where all I need is the path.
Private Sub CommandButton1_Click()
Dim sh As Worksheet, rng As Range, lr As Long, fPath As String
Set sh = Sheets(1) 'Change to actual
lstRw = sh.Cells.Find(What:="*", After:=sh.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious, MatchCase:=False).Row
Set rng = sh.Range("A2:A" & lstRw)
With Application.FileDialog(msoFileDialogFolderPicker)
.Show
fPath = .SelectedItems(1)
End With
If Right(fPath, 1) <> "\" Then
fPath = fPath & "\"
End If
fwb = Dir(fPath & "*.*")
x = 2
Do While fwb <> ""
For Each c In rng
If InStr(LCase(fwb), LCase(c.Value)) > 0 Then
Worksheets("Sheet2").Range("C" & x) = fwb
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(fwb)
Worksheets("Sheet2").Range("D" & x) = f.DateLastModified
Worksheets("Sheet2").Range("B" & x) = f.Path
Worksheets("sheet2").Range("A" & x) = c.Value
Columns("A:D").AutoFit
Set fs = Nothing
Set f = Nothing
x = x + 1
End If
Next
fwb = Dir
Loop
Set sh = Nothing
Set rng = Nothing
Sheets(2).Activate
End Sub
My attempts at moification so far have generally resulted in a broken script and have thus led me here asking for help.
Thanks,
Simon
Downlaoded the win32 port of the GNU tool grep from http://gnuwin32.sourceforge.net/
Saved the list of video files into a plain text file instead of using a spreadsheet.
grep --file=C:\file_containing video_file_names.txt -R --include=*.txt C:\Path\To\Files >grep_output.txt
The information written to the grep_output.txt file looked like
C:\wiki_files\wiki\pages/my_bank_rec_page.txt:{{videos:bank_rec_reopen1006031511.flv|}}
So there was the path to the file containing the video name and the video name on one line.
Imported the grep_output.txt file into a new Excel workbook.
Used regular formulae to do the following
Split Column A at the "/" to give the path in Column A and the page and video information in Column B
Split the data in in Column B at the ":{{" characters leaving page name in Column B and video information in Column C
Stripped the :{{ and |}} from the front and rear of the string in Column C
From my limited experience, it seems you'd want to perform 4 tasks.
1) Loop through Directories
2) Loop through files per directory (Good idea to keep the filename in a variable)
3) Test the text file for values. Would suggest clear a "scribble sheet", import the file, run a check. e.g.
Sheets("YourScratchPatch").Select
Application.CutCopyMode = False
With ActiveSheet.QueryTables.Add(Connection:="TEXT;" & yourpath & yourfile.txt, Destination:=Range("A1"))
.FieldNames = True
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.RefreshStyle = xlInsertDeleteCells
.SavePassword = False
.SaveData = True
.RefreshPeriod = 0
.TextFilePromptOnRefresh = False
.TextFilePlatform = 850
.TextFileStartRow = 2
.TextFileParseType = xlDelimited
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileConsecutiveDelimiter = False
.TextFileTabDelimiter = True
.TextFileSemicolonDelimiter = False
.TextFileCommaDelimiter = True
.TextFileSpaceDelimiter = False
.TextFileColumnDataTypes = Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
.TextFileTrailingMinusNumbers = True
.Refresh BackgroundQuery:=False
End With
4) if values are found, write the file name variable to the index sheet.
I'm sure there should be better (arrays?) ways to do the comparison check as well, but it depends on what's inside the text file (i.e. just one file name?)
More info on the text file structure would be useful. Hope this helps.

Resources