AutoHotKey: Loop thru Associative Array - autohotkey

Happy to be back in the forums!
So after some extensive searching both in the AHK documentation, stackexchange and the ahk_forum, I have decided to seek help from the experts
I will explain the code I am working on and then post some examples.
I have written a script that pulls the value of untranslated and unverified words of 6 different word files and does this for 23 different languages via WebTranslateIt (site). I have managed to get it to work by coping the block of code and setting the "fileid" variable to one of the 6 files before each new query, however when I arrived at the language iteration part, I quickly noticed it would be a lot more efficient (and significantly less code) to simply have one loop within a loop iterate through two arrays until end of array.
This script accesses a website using Google Chrome, then proceeds to inject several jQueries into the developer tools and extract the results into independently named variables (which I would like to assign as part of an associate array).
This is the part that I am having issues with... I can loop through an array succesfully via:
langid := ["bg", "cs", "da", "el", "eu", "fi", "hr" , "ja", "ko", "lt", "nb",
"nl", "pl", "pt", "ro" , "ru", "sk", "sl", "sv", "th" , "tr", "zh", "zh-Hant"]
For Key, Value in langid
; MsgBox, %A_Index% = %Value% ; the MsgBox displays index values correctly but when putting my script in a loop start after the For Key part, it does not provide a value when sending the var inside a url. This works fine when I set the var manually before sending the URL.
But when I tried running this right before Looping the script that does the main querying, the 'langid' variable in the URL I send to change the language is blank.
I would like to loop the script 23 times through all the languages. When the script enters the loop there is a part that enters page URL with a %langid% and I cannot get this part to work with the above code.
Further to this, I would also like to assign further values to each key in the index as follows:
langid := ["bg", "cs", "da", "el", "eu", "fi", "hr" , "ja", "ko", "lt", "nb",
"nl", "pl", "pt", "ro" , "ru", "sk", "sl", "sv", "th" , "tr", "zh", "zh-Hant"]
lang := ["Bulgarian", "Czech", "Danish", "Greek", "Basque", "Finnish",
"Hungarian", "Japanese", "Korean", "Lithuanian", "Norwegian", "Dutch",
"Polish", "Portuguese", "Romanian", "Russian"]
The 'lang' array should reference the index position in 'langid', as I use it as a descriptive variable in some MsgBoxes that show word count progress and a function at the end that writes the total results of the 6 files per language. (This is purely aesthetic but I would love to get it working in the loop)
I also need something very similar with the wordfiles:
filenum := ["342553", "342582", "342411", "342367", "342467", "342502"]
wrdfile := ["OHFrontend", "OHFrontendListing", "HouseTypeAndSubType", "GuestType", "RoomTypeFeatureName", "RoomTypeFaturePrefixAndBracketOption"]
The 'wrdfile' array is also purely aesthetic, as I rather display the filename and not filenum in the totals confirmations. The 'filenum' however IS important and needs to iterate through each value in the array once until arriving at the end, at which point it needs to restart but with the next language in the 'lang' array.
I am thinking the following structure would work:
langid := [val1, val2, etc..]
lang := [val1, val2, etc..]
For Key, Value in langid ; iterate through Languages
Loop
{
filenum := [val1, val2, etc...]
wrdfile := [val1, val2, etc...]
Run Chrome
Open Site
For Key, Value in filenum ;iterate through Wordfiles
Loop
{
Send URL containing 'langid' var & 'filenum' var
Open DevTools and send jQuery and store totals
Confirm totals in MsgBox and save in txt file
}
Return
It is worth noting that all languages need to iterate through the same 6 wordfiles, so perhaps the file array can also be part of the first associative array instead of two different ones...? Not sure what is the best approach here.
Please help me me find the correct structure and syntax for the loops and if possible point me in the right direction as far as the arrays go. (I know I am not associating them correctly)
I need assistance with forming associative arrays... the documentation and examples I´ve looked up is not thorough enough. Please help atleast with a push in the right direction :oops:
I need assistance with the For Key command. It should go through the loop underneath it serving up the next 'langid' value on each iteration until the last value ("zh-Hant").
Once I have point 2 working then I will tweak the script to also loop through the 6 wordfiles and then reiterate language. My intention is to loop through the 6 wordsfile for each language. Once that is working I will add a small GUI prompt to enable going through the entire loop or just through a specific language.
Link to AHK_Forum post containing full code.
THANKS AGAIN!

You could use an associative array for your language and files variables. The correct syntax for associative arrays is as follows.
object := { "key1" : "value", "key2" : "value 2" }
You can iterate over the array with a for loop like so.
for key, value in object
MsgBox key: %key% value: %value%
The for loop already iterates through your object so you don't need the loop beneath it. If I'm understanding you correctly you could try something like this.
; Associative arrays
Langs := { "bg" : "Bulgarian", "cs" : "Czech" }
Files := { "342553" : "OHFrontend", "342582" : "OHFrontendListing"}
; Open Chrome
For langId, langName in Langs
{
For fileId, fileName in Files
{
url := "example.com/" . langId . "/" . fileId
; Send url with langId & fileId
; Open DevTools and send jQuery and store totals
; Confirm totals in MsgBox and save in txt file
msgbox %url%
}
}
Return

Related

Modify vscode snippet by regex: TitleCase and SNAKE_CASE

I have two questions for vscode snippets+regex;
I have a pathname like some-component and I need to generate an output like SomeComponent using vscode snippet.
I need to input sendData and return an string like const sendData = createMessage(SEND_DATA);
How can I do this using regex on vscode snippet?
"${TM_DIRECTORY/(.*)/${1:/pascalcase}/g}" you didn't really provide enough info on how you are getting your pathName, so this is just one possibility, perhaps RELATIVE_FILEPATH` works for you.
"$1 = createMessage(${1/(([^A-Z]+)(\\w*))/${2:/upcase}_${3:/upcase}/});"
split the input sendData into 2 capture groups $2 and $3. Upcase them both in the transform.
"sendData": {
"prefix": "cm",
"body": [
"${TM_DIRECTORY/(.*)/${1:/pascalcase}/}",
// simpler form if ONLY two "words" like "sendData"
"$1 = createMessage(${1/(([^A-Z]+)(\\w*))/${2:/upcase}_${3:/upcase}/});",
// for any number of words, like "sendDataTwoThreeFour" use this:
"$1 = createMessage(${1/([a-z]*)([A-Z][a-z]*)/${1:/upcase}${2:+_}${2:/upcase}/g});"
]
}
${1/([a-z]*)([A-Z][a-z]*)/${1:/upcase}${2:+_}${2:/upcase}/g} get the first word "send" into capture group 1 and the other words like "Data" or "Two", etc. into subsequent matches' capture group 2. [So the g flag at the end is very important.]
Upcase group1. Then if there is a group 2 ${2:+_} add _. Then upcase group2.
The only case this will not work on is send with nothing else. It still prints out the all the text just doesn't upcase send if it is by itself. There is probably a way to include that...
Edit: And here it is:
"$1 = createMessage(${1/([a-z]*)([A-Z][a-z]*)|([a-z]+)/${1:/upcase}${3:/upcase}${2:+_}${2:/upcase}/g});"
now a bare send will be put into group 3 and upcased. For the rest of the matches there will not be a group 3 so ${3:/upcase} returns nothing.

Converting numbers into timestamps (inserting colons at specific places)

I'm using AutoHotkey for this as the code is the most understandable to me. So I have a document with numbers and text, for example like this
120344 text text text
234000 text text
and the desired output is
12:03:44 text text text
23:40:00 text text
I'm sure StrReplace can be used to insert the colons in, but I'm not sure how to specify the position of the colons or ask AHK to 'find' specific strings of 6 digit numbers. Before, I would have highlighted the text I want to apply StrReplace to and then press a hotkey, but I was wondering if there is a more efficient way to do this that doesn't need my interaction. Even just pointing to the relevant functions I would need to look into to do this would be helpful! Thanks so much, I'm still very new to programming.
hfontanez's answer was very helpful in figuring out that for this problem, I had to use a loop and substring function. I'm sure there are much less messy ways to write this code, but this is the final version of what worked for my purposes:
Loop, read, C:\[location of input file]
{
{ If A_LoopReadLine = ;
Continue ; this part is to ignore the blank lines in the file
}
{
one := A_LoopReadLine
x := SubStr(one, 1, 2)
y := SubStr(one, 3, 2)
z := SubStr(one, 5)
two := x . ":" . y . ":" . z
FileAppend, %two%`r`n, C:\[location of output file]
}
}
return
Assuming that the "timestamp" component is always 6 characters long and always at the beginning of the string, this solution should work just fine.
String test = "012345 test test test";
test = test.substring(0, 2) + ":" + test.substring(2, 4) + ":" + test.substring(4, test.length());
This outputs 01:23:45 test test test
Why? Because you are temporarily creating a String object that it's two characters long and then you insert the colon before taking the next pair. Lastly, you append the rest of the String and assign it to whichever String variable you want. Remember, the substring method doesn't modify the String object you are calling the method on. This method returns a "new" String object. Therefore, the variable test is unmodified until the assignment operation kicks in at the end.
Alternatively, you can use a StringBuilder and append each component like this:
StringBuilder sbuff = new StringBuilder();
sbuff.append(test.substring(0,2));
sbuff.append(":");
sbuff.append(test.substring(2,4));
sbuff.append(":");
sbuff.append(test.substring(4,test.length()));
test = sbuff.toString();
You could also use a "fancy" loop to do this, but I think for something this simple, looping is just overkill. Oh, I almost forgot, this should work with both of your test strings because after the last colon insert, the code takes the substring from index position 4 all the way to the end of the string indiscriminately.

Get First Character of A_LoopFileName?

I'm trying to parse a filename and get the first character from it as a string to compare it to a previously inputted variable. My code looks like:
FileSelectFolder, WhichFolder ; Ask the user to pick a folder.
; Ask what letter you want to start the loop from
InputBox, UserInput, Start At What Letter?, Please enter a letter to start at within the folder (CAPITALIZE IT!)., , 450, 150
if ErrorLevel {
MsgBox, CANCEL was pressed.
ExitApp
} else {
inputted_letter = %UserInput%
tooltip %inputted_letter% ; Show the inputted letter
sleep, 2000
tooltip
}
Loop, %WhichFolder%\*.*
{
current_filename_full = %A_LoopFileName%
files_first_letter := SubStr(current_filename_full, 1, 1)
tooltip %files_first_letter% ; Show the file's first letter
sleep, 2000
tooltip
if files_first_letter != inputted_letter
continue
...
Right now, it clearly shows in the tooltips the user-entered capital letter, and then the first letter of each file name from within the selected folder, but for some reason when the two look alike, it doesn't recognize them as a match. I'm thinking maybe because technically A_LoopFileName is not of a string type? Or maybe the inputted letter doesn't match the type of the first filename's letter?
I want it to continue if the inputted letter and the first letter of the filename don't match, but if they do, to carry on with the rest of the script. Any ideas on how I can get these two to successfully match? Thanks!
Firstly, AHK doesn't really have types. At least not how you've experienced types in other languages.
So your assumption about "not being correct type" will pretty much always be wrong.
So the actual cause is because in a legacy if statement, the syntax is
if <name of variable> <operator> <legacy way of representing a value>
So you'd do it like this:
if files_first_letter != %inputted_letter%
You we're comparing if the variable files_first_letter is equal to the literal text inputted_letter.
However, I highly recommend you stop using legacy syntax. It's really just that old.
It'll differ horribly much from any other programming language and you run into confusing behavior like this. Expression syntax is what you want to use in AHK nowadays.
Here's your code snippet converted over to expression syntax in case you're interested:
FileSelectFolder, WhichFolder
;Forcing an expression like this with % in every parameter
;is really not needed of course, and could be considered
;excessive, but I'm doing it for demonstrational
;purposes here. Putting everything in expression syntax.
;also, not gonna lie, I always do it myself haha
InputBox, UserInput, % "Start At What Letter?", % "Please enter a letter to start at within the folder (CAPITALIZE IT!).", , 450, 150
if (ErrorLevel)
;braces indicate an expression and the non-legacy if statement
;more about this, as an expression, ErrorLevel here holds the value
;1, which gets evaluated to true, so we're doing
;if (true), which is true
{
MsgBox, % "CANCEL was pressed."
ExitApp
}
else
inputted_letter := UserInput ; = is never used, always :=
Loop, Files, % WhichFolder "\*.*"
;non-legacy file loop
;note that here forcing the expression statement
;with % is actually very much needed
{
current_filename_full := A_LoopFileName
files_first_letter := SubStr(current_filename_full, 1, 1)
if (files_first_letter != inputted_letter)
continue
}
Also you don't have to be concerned about case with !=, it'll always compare case insensitively.

Setting a control name with concatenated text+variable for ControlSetText usage

TL;DR I created a new variable (destinationControl) by concatenating a string, a separate string variable, and then another string. I tried using the variable destinationControl with ControlSetText, but its not working. Can anyone tell me why?
Long Explanation:
I'm attempting to send some data from an excel spreadsheet into another application using AHK ControlSetText. My issue comes in when I need the script to detect which one of two possible programs is the active one (the detection part is working) and then based on the name of the program, set the destination control name is slightly different.
prog_A_segment := "abc"
prog_B_segment := "def"
;determine which program is open
IfInString, OpenProgram, ProgA
{
ctrlSegment := prog_A_segment
}
else
ctrlSegment := prog_B_segment
;set control variable
destinationControl := "WindowsForms10.EDIT.app.0." . ctrlSegment . "_r13_ad11"
;activate program
WinActivate, % OpenProgram
WinWaitActive, % OpenProgram,,3
;open vendor form
Sleep 300
Send ^o
Sleep 200
Send Vendors
sleep 200
Send {ENTER}
Sleep 2000
;This does not work:
;pass information to vendor form control
ControlSetText, %destinationControl%, %myNumber%, %OpenProgram%
I know I could just slightly more manually set them based on the open program but i have about 25 controls in total and the only difference is that center segment so I thought this would be a little more elegant and cleaner.
When I use the above method it doesn't appear AHK can find the control. I'm assuming it has something to do with how I combined a string and a variable. Is there some way to make this approach work without doing this instead:
IfInString, OpenProgram, ProgA
{
destinationControl1 := "WindowsForms10.EDIT.app.0.abc_r13_ad11"
....
destinationControl25 := "WindowsForms10.EDIT.app.0.abc_d52_ad11"
}
else
destinationControl1 := "WindowsForms10.EDIT.app.0.def_r13_ad11"
....
destinationControl25 := "WindowsForms10.EDIT.app.0.def_d52_ad11"
I agree with Josh Brobst that your first piece of code would work with the missing quote added.
Well, here's what you want to try anyways:
ctrlSegment := InStr(OpenProgram, ProgA) ? "abc" : "def"
Loop Parse, % "r13, ... ,d52", CSV
ControlSetText % "WindowsForms10.EDIT.app.0." ctrlSegment "_" A_LoopField "_ad11"
, % myNumber, % OpenProgram

Saving specific areas of a filename

I have a list of pdf files in this format "123 - Test - English.pdf". I want to be able to set "111", "Test" and "English.pdf" in their own individual variables. I tried running the code below but I don't think it accounts for multiple dashes "-". How can I do this? Please help Thanks in advance.
Loop,C:\My Documents\Notes\*.pdf, 0, 0
{
NewVariable = Trim(Substr(A_LoopFileName,1, Instr(A_LoopFileName, "-")-1))
I would recommend using a parse loop to get your variables. The following loops through values between the dashes and removes the whitespace.
FileName = Test - file - name.pdf
Loop, parse, FileName, `-
MyVar%A_Index% := RegExReplace(A_LoopField, A_Space, "")
msgbox % Myvar1 "`n" Myvar2 "`n" MyVar3
First, I don't know if it was a typo, but if you use a { under your loop statement, you also need to close it. If your next statement is just one line, you don't need any brackets at all.
Second, if you just use = then your code will output as just that very code text. You need to use a :=
Third, your present code, if coded correctly would result in this:
somepdffile.pd
if it found any pdf files without a dash. Instr() will return the position of a dash. If there is no dash, it returns 0 - in which case, your substr() statement will add 0 and your -1 which adds up to -1 and if you use a negative number with substr(), it will search from the end of the string instead of the beginning - which is why your string would get cut off.
Loop, C:\My Documents\Notes\*.pdf, 0, 0
{
;look at the docs (http://www.autohotkey.com/docs/) for `substr`
}
So there is an explanation of why your code doesn't work. To get it to do what you want to do, can you explain a bit more as to how you want NewVariable to look like?
; here is another way (via RegExMatch)
src:="123 - Test - English.pdf", pat:="[^\s|-]+"
While, mPos:=RegExMatch(src, pat, match, mPos ? mPos+StrLen(match):1)
match%A_Index%:=match
MsgBox, 262144, % "result", % match1 ", "match2 ", "match3