I want to be able to set a KM variable to a text array, and then access individual text elements of that array for later use.
I am assuming that I can set the array using comma separated values, but I can’t seem to access the items in that array with the %Calculate% token; this seems to work on numeric values only.
So, Set Variable “ortho” to Text “pa21,pa22,pa23” gives me an array of 3 test values stored in an array. How do I pull out just the second value (pa22) from that array?
Hey Bob,
Agh. That looks like a bug, but Peter's away - so we'll have to wait for a definitive answer.
In the meantime you can use AppleScript to manage a real one-dimensional array (list):
set arrayVar to "one,two,three,four,five"
set AppleScript's text item delimiters to ","
set arrayVar to text items of arrayVar
set item1 to item 1 of arrayVar
set item2 to item 2 of arrayVar
set item3 to item 3 of arrayVar
set item4 to item 4 of arrayVar
set item5 to item 5 of arrayVar
Run this in the Applescript Editor and comment-out lines to see how it works.
Legal comment characters:
--
OR
#
Here's how to make it work in KM:
tell application "Keyboard Maestro Engine"
# Simulate setting a KM variable to something in your macro
set value of variable "temp" to "one,two,three,four,five"
# Bring the value of you KM variable into AppleScript.
set arrayVar to value of variable "temp"
end tell
set AppleScript's text item delimiters to ","
set arrayVar to text items of arrayVar
set item1 to item 1 of arrayVar
set item2 to item 2 of arrayVar
set item3 to item 3 of arrayVar
set item4 to item 4 of arrayVar
set item5 to item 5 of arrayVar
You can save the result to a KM variable if desired and continue.
Another way to do this is with a regular expression:
Split Array with RegEx.kmmacros (1.9 KB)
You can also search/replace a variable with a regular expression:
Find:
^([^,]+)(?:, *)?([^,]+)(?:, *)?([^,]+)(?:, *)?([^,]+)(?:, *)?([^,]+)(?:, *)?
Replace:
$3
There are more possibilities of course, but I'll stop here.
-Chris
Hey Bob,
Oh, well. One more method using AppleScript.
set _array to "one, two, three, four, five, six, seven, eight, nine, ten"
set _array to change " *, *" into linefeed in _array with regexp # Satimage.osax Dependency
set firstItem to first paragraph of _array
set item8 to paragraph 8 of _array
set lastItem to last paragraph of _array
(The Satimage.osax is an AppleScript extension that adds regular expressions and other goodies.)
You can do the find/replace with KM instead, and then use an Execute-AppleScript-Action to get a number paragraph (or first/last).
I nearly always use regular expressions for some part of this sort of job, because I can easily allow for a poorly formatted array structure. For instance:
a, b ,c,d , e
-Chris
Thanks for your response (and obviously the time you took to compose it). The number of items in the array is variable (sorry I didn’t make that clear in my original post), and I have been using Applescript to read in all of the values to an array, and then System Events to type them out (I’m not really a programmer and I don’t know how to use regular expressions). I only recently started using KM a couple of months ago, and it looks like it would be less prone to errors than the System Events step that I was using (like if another app unexpectedly comes to the front, System Events is no longer typing in the correct app). Applescript counts the items in the array, and loops through all of them. I guess I’ll wait until Peter is back to find out if there is a solution using the %Calculate% token.
Hey Bob,
When asking for help on the list it’s usually a good idea to provide an overview of what you’re doing. It gives us a better idea of how to help, and there’s always the possibility that a better mousetrap may be forthcoming.
You’re right System Events is an awkward means of simulating standard user input, and it’s better to use KM. (Although there are some things that SEV can do better than KM of course.)
Remember that AppleScript can drive KM. You can for instance drop a value into a KM-variable in your script and then call a KM-Macro to type or paste or otherwise process the contents of that variable.
Such an action can be in an AppleScript loop.
Yada, yada.
Again. Multiple methods are available.
* Note: when you’re driving the Mac UI artificially with simulated mouse movements/clicks and/or keyboard entry the possibility an unexpected event might interfere with the process is always there. However there are some techniques available to mitigate that risk.
-Chris
Keyboard Maestro supports arrays in calculations only, specifically they are supported for rectangles, points, and sizes. Keyboard Maestro has no direct support for arrays of strings.
You can create an anything separated string of entries, and then operate on that as if it is an array. For example, if no entry will ever include a comma, then you can use comma separated, but you can just as easily use something else, such as a bullet (•) or even a sequence of characters like “,KMSEP,”.
After that, you can extract the 5th element using a regular expression, something like a Search Variable action with regular expression:
^(?[^,]*,){4}([^,]*)
If you want to iterate through the entries, you can use a For Each action with a Substrings matching [^,]*.
And so on. It depends on exactly what you are trying to do.
Hopefully I will add some form of hash and/or array access at some point in the future.
OK.
Thanks for your response.
-Bob
Can anyone help me figure out why my regular expression isn't capturing each line of my variable?
The same query works in TextWrangler (without the multiline flag, which isn't needed there).
Hey Jack,
If I understand correctly you want to capture EACH line rather than EVERY line.
Your regex doesn't work in TextWrangler per se, because you have to hit Cmd-G to find each line in succession. KM doesn't do that for you. You have to explicitly tell KM to capture each line.
Each of these will capture EACH line in your KM variable to a capture variable:
(.*)\n(.*)\n(.*)
(?m)(^.*$)\n(^.*$)\n(^.*$)
Think of it this way. If you want to have more than one capture you have to explicitly define more than one capture group — e.g. you have to have more than one set of parentheses.
In my opinion when testing something like this it's a bad idea to depend on what might be in the variable, because it's all too easy to inadvertently alter its contents with your captures — especially if you don't change the default variable names.
It's a better idea to explicitly set your text in the test macro. Something like this:
Generic-Test 01.kmmacros (3.0 KB)
Also. The capture variable status line does not indicate when there is more than one line of text, so it's all too easy to be misled about the complete contents of the variable. Because of that I will display the result text in a way that's easy to visualize — my macro will show capture 1, capture 2, capture 3, and the whole capture. The whole capture is delimited with a couple of hash marks to alert me to unexpected line breaks.
One last tip. I think it's a bad idea to use 'Variable' as a variable name. It's all too easy to read it as a label or other information instead of as a VARIABLE-NAME, so I always change the default 'Variable' to something that reads more clearly to me.
HTH.
--
Best Regards,
Chris
Thanks, Chris! This is great.
A footnote, for reference, to a much-viewed topic:
Another approach, using JavaScript for Application in OS X 9+, would be String.Split(), which accepts a regex as an argument.
Word (splitting by regex on one or more spaces) at index 10 (zero-based):
"The world would be happier if men had the same capacity to be silent that they have to speak.".split(/\s+/)[10]
--> "capacity"
( With apologies to Spinoza.
https://en.wikipedia.org/wiki/Baruch_Spinoza )
Hi Peter,
i know this post is rather old but i need your help! I've tried the regular expression ^(?[^,],){4}([^,]) to try and draw the 5th element in a variable that contains the below, but i cant seem to get it to work. Ideally i need an expression to get element n from the variable below, where n is set by another variable. Is this possible?
C-1,C#-1,D-1,D#-1,E-1,F-1,F#-1,G-1,G#-1,A-1,A#-1,B-1,C-1,C#0,D0,D#0,E0,F0,F#0,G0,G#0,A0,A#0,B0,C1,C#1,D1,D#1,E1,F1,F#1,G1,G#1,A1,A#1,B1,C2,C#2,D2,D#2,E2,F2,F#2,G2,G#2,A2,A#2,B2,C3,C#3,D3,D#3,E3,F3,F#3,G3,G#3,A3,A#3,B3,C4,C#4,D4,D#4,E4,F4,F#4,G4,G#4,A4,A#4,B4,C5,C#5,D5,D#5,E5,F5,F#5,G5,G#5,A5,A#5,B5,C6,C#6,D6,D#6,E6,F6,F#6,G6,G#6,A6,A#6,B6,C7,C#7,D7,D#7,E7,F7,F#7,G7,G#7,A7,A#7,B7,C8,C#8,D8,D#8,E8,F8,F#8,G8,G#8,A8,A#8,B8,C9,C#9,D9,D#9,E9,F9,F#9,G9,
Thanks in advance for your help!
-Stanley
Hey Stanley,
This works for me (lightly tested).
-Chris
And an Execute action using JavaScript might, FWIW, look something like this:
(JS array indexes are zero-based)
Nth element.kmmacros (19.4 KB)
osascript -l JavaScript <<JXA_END 2>/dev/null
(function(strText, strDelim, intIndex) {
return strText.split(strDelim)[intIndex-1];
})("$KMVAR_strText", "$KMVAR_strDelim", "$KMVAR_Index");
JXA_END
Amazing! This has completely solved so many issues i’ve been having. Thanks SO MUCH!