Just a quick question: What's wrong with the following AppleScript code? What it's supposed to do is get the position of a text item (separated by a user-provided delimiter) within a string. But thus far, it doesn't work. Script Debugger simply says, "Can't continue return_string_position" without any specific errors. Any ideas as to what's wrong?
tell application "System Events"
set the_text to "The quick brown fox jumps over the lazy dog"
set word_index to return_string_position("jumps", the_text, " ")
end tell
on return_string_position(this_item, this_str, delims)
set old_delims to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set this_list to this_str as list
repeat with i from 1 to the count of this_list
if item i of this_list is equal to this_item then return i
end repeat
set AppleScript's text item delimiters to old_delims
end return_string_position
Your issue is that System Events thinks the function return_string_position is one of its own (if you'll look at the dictionary, you'll find that it isn't). This is very easily resolved; just add my before the call to return_string_position.
Your new code:
tell application "System Events"
set the_text to "The quick brown fox jumps over the lazy dog"
set word_index to my return_string_position("jumps", the_text, " ")
end tell
...
Or you can use adayzdone's solution. In this case, his/her solution is perfect for the job because there really isn't any need to target system events when working with simple textual things.
The tell system events command is incorrect and should be excluded. Also, you don't need to use text item delimiters of " " to make a list of words, simply use "every word of". Lastly, your code will only return the last match of the passed parameter, this will return EACH match.
on return_string_position(this_item, this_str)
set theWords to every word of this_str
set matchedWords to {}
repeat with i from 1 to count of theWords
set aWord to item i of theWords
if item i of theWords = this_item then set end of matchedWords to i
end repeat
return matchedWords
end return_string_position
return_string_position("very", "The coffee was very very very very very ... very hot.")
Related
I want to create a Mac app similar to Textexpander or Atext. Both these applications allow the user to define snippets along with their respective trigger words. Typing the trigger words in any app, replaces that trigger word with the actual snippet defined.
I presume that the app listens to all strings being typed in any app and when it detects a string matching one of the trigger words defined, it replaces it with the snippet.
Is that how it actually works, or is there some other way?
Make two fields. In field 2 put something like:
time xyz
come ABC
In the script of field 1:
on textChanged
if the last char of me = space then
put the last word of me into temp
if temp is in fld 2 then
repeat for each word tWord in fld 2
put the last word of line lineOffset(temp,fld 2) of fld 2 into the last word of me
exit repeat
end repeat
end if
select after text of me
end if
end textChanged
Now type into fld 1, you know, "Now is the time for all good men to come to the aid of their country". This can be better done with an array, but the concept may be more accessible here.
This is a better handler, since it will not react to the trigger word:
on textChanged
if the last char of me = space then
put the last word of me into stringOfInterest
put fld 2 into dataToSearch
if stringOfInterest is in dataToSearch then
repeat for each line tLine in dataToSearch
if word 1 of tLine = stringOfInterest then
put word 2 of tLine into the last word of me
exit repeat
end if
end repeat
end if
select after text of me
end if
end textChanged
So, I have a very specific problem. I have a random word which I have already generated. I also have a separate string that uses the length of that character, 'n' to repeat "_ " n times to field wordDisplay. These elements of the program work. So, if the random word was "meme", the wordDisplay, on openCard would show "_ _ _ _ ".
Now, I have an input button "a". If this button is clicked, it will check for "a" in the randomword variable and if it is found, it will replace the "_ " in the wordDisplay with "a". So, if the randomword was "name", then before you click "a", the wordDisplay shows "_ _ _ _ ". After you click "a", wordDisplay will show "_ a _ _ ". However, if there is more than one "a", it will reveal all of them.
It is also worth knowing that I plan to have the program do something if there is no letter found.
Being a beginner to LiveCode, this all seems very confusing to me and I would really appreciate someone to share their knowledge on how I would do something like this. Thanks!
There are several ways to do this, but I wouldn't try to do everything in a single field. Unless you use a monospaced font, the text's width will shift each time you swap characters and underscores.
I would suggest using a separate field (or button or graphic) to display each character of the word (named something like letter1, letter2, letter3...), and place a line graphic under each field (named line1, line2, line3...). Create a number of controls that matches the length of your worst-case word, and hide the unneeded controls each time a new word is selected.
By laying the controls out this way, you're guaranteed a relatively consistent alignment regardless of what font is used, or how the card is scaled.
Now you can simply toggle the visibility of each "letter" field and the visibility of each "underscore" line as needed, by looping through the set of controls. Something sort of like:
[ the secret word is 'aardvark' ]
command checkForCharacterMatch pUserChar
local noMatch = true
repeat with N = 1 to length(theSecretWord)
put long id of field ("letter" & N) into theLetter
put long id of graphic ("line" & N) into theLine
if (the text of theLetter = pUserChar) then
show theLetter
hide theLine
put false into noMatch
end if
end repeat
if noMatch then
-- DO PENALTY STUFF HERE
end if
end checkForCharacterMatch
In reality, you don't even need to hide the lines, but this follows your original approach.
I am trying to build an AppleScript that reads each line of a “.txt” file (with a linefeed) and stores the contents of each line into AppleScript variables.
Here is what I mean:
Let’s say there was a “Test.txt” file with the contents:
Apples
Oranges
Pears
As you can see, the “Test.txt” file’s contents have a String on each line, a linefeed introducing the new String, and so on.
I would really like to know how an AppleScript could be made so that each line’s Strings are copied into individual AppleScript variables.
(This way, “Apples” in the first line, would be stored in variableA, “Oranges” in the next would be stored in variableB, “Pears” … variableC, etc.)
Please let me know, from your experience, how best to accomplish this. I know it’s slightly more involved, here is where I am:
(*
This portion of the AppleScript accesses the contents of the ".txt" file named "Test," though takes all of the content and places it into a single variable.
*)
set newFile to ("Macintosh HD:Users:Username:Desktop:Test.txt")
set theFileContents to (read file newFile)
{ AppleScript code to read each line to individual variables }
I know there must be others trying to accomplish this.
This example is for a situation where you know the anticipated paragraphs you'll be looking for to assign to each of a known set of variables.
set newFile to ("Macintosh HD:Users:Username:Desktop:Test.txt")
set theFileContents to paragraphs of (read file newFile)
set recipientEmail to paragraph 1 of theFileContents as text
set senderEmail to paragraph 2 of theFileContents as text
set theSubject to paragraph 3 of theFileContents as text
set theBody to (paragraphs 4 thru -1 of theFileContents) as text
Another option would be to dynamically search for a string in the paragraph, and if it matches, then assign it to that variable. Something like:
set newFile to ("Macintosh HD:Users:jweaks:Desktop:horses.txt")
set theFileContents to paragraphs of (read file newFile)
set recipientEmail to ""
set senderEmail to ""
set theSubject to ""
set theBody to ""
repeat with p in theFileContents
if p contains "To:" then
set recipientEmail to p
else if p contains "From:" then
set senderEmail to p
else if p contains "Subject:" then
set theSubject to p
else
set theBody to theBody & p & return
end if
end repeat
Thank you so much for all of your effort to answer this question, jweaks. As I am still catching on to AppleScript best practices, I was thinking more about your recommendation of bringing the contents of the “.txt” file into a list, assigning the items to AppleScript variables (if needed), and began brainstorming how to accomplish it. I agree that it seems like the simplest and most efficient approach:
Reads the “.txt” file’s into a list of items: Apples, Oranges, and Pears
set paragraph_list to read file "Macintosh HD:Users:tombettinger:Desktop:Test.txt" using delimiter linefeed
Apples
set variableA to item 1 of paragraph_list
Oranges
set variableB to item 2 of paragraph_list
Pears
set variableC to item 3 of paragraph_list
Displays the contents of each variable (optional comment)
display dialog variableA & " " & variableB & " " & variableC
End of AppleScript
As long as the contents of the ".txt" file are stacked in a table, this approach will support the accessibility of information I was searching for. Thanks again!
I am currently using this Applescript I found that searches for a file name and returns the file path in a text doc. This works fine for finding 1 or 2 files, but I would like to find 500 files that are spread over hundreds of folders. My ideal script would use data from an excel spreadsheet or csv, perform a search, find the file and make a copy of it in a designated folder on my desktop. Any help is appreciated.
Here is the script I found:
tell application "System Events"
activate
set thePattern to text returned of (display dialog "Search for" default answer "")
end tell
if thePattern = "" then return
try
set foundFiles to do shell script "mdfind -name " & quoted form of thePattern & " | /usr/bin/egrep -i " & quoted form of thePattern & "[^/]*/?$ | /usr/bin/grep -vi " & quoted form of thePattern & ".*" & quoted form of thePattern
on error
set foundFiles to "Nothing Returned"
end try
if foundFiles = "" then set foundFiles to "Nothing Returned"
tell application "TextEdit"
activate
delay 0.5
try
set theDoc to document 1
get text of theDoc
if result is not "" then
make new document
set theDoc to result
end if
on error
make new document
set theDoc to result
end try
set text of theDoc to foundFiles
end tell
You need to read the data from the text file, then turn it into a return or linefeed delimited list and do a repeat over the items of this list. Then turn each item (which is actually a line) into e.g. a tab delimited list and again do a (nested) repeat loop over the items of this list. If you know that e.g. item 3 is the file path, you can set a variable to item 3 of the line as text and use this variable in your shell script.
I think you need to show that you understand the concept of repeat loops by posting your own attempt of implementing this. If you do, I'll be happy to come back and help you with the next step.
Kind regards,
Mark
The command below for Windows Speech Recognition forces the speech recognition engine to use literal text (""all seventy six people paid five dollars") instead of the default ("all 76 people paid $5").
I'm trying to adapt this command to remove the spaces between words so that I could program using speech by saying things like:
"Security Manager"
-> "Security Manager"
"Compound That"
-> "SecurityManager"
The way I think this could work is by using a regex to remove the spaces between the selected text. So, in the following code:
<command priority="5">
<listenFor>literal that</listenFor>
<emulateRecognition>select that</emulateRecognition>
<sendKeys>{250 WAIT}{{CTRL}}c{250 WAIT}</sendKeys>
<!-- Works by first inserting the word " literal " before each word
example "all 76 people paid $5"
is emulated as "literal all literal 76 literal people literal paid literal $5"
which produces the result "all seventy six people paid five dollars"
EmulateRecognition can fail if the text contains improper formatting or nonsense words
(which can easily happen if only part of a word in the document is included in the text selection).
When the failure can be handled by "On Error Resume Next", such as EmulateRecognition("literal s")
or EmulateRecognition("multiple spaces"), we restore the original text and show feedback message.
Unfortunately, some failures, such as EmulateRecognition("nonsens"), cause macros to immediately
halt. This is why "replace that with " is used. Either it succeeds, or it fails non-destructively
(before any text is deleted). Unfortunately, this can be confusing for the user because the macro
can fail without doing anything visible to the user (no changes to the text, and no SetTextFeedback.)
-->
<script language="VBScript">
<![CDATA[
that = Application.clipboardData.GetData("text")
Set regEx = New RegExp
regEx.Pattern = "[^\s\w,;:]"
If regEx.Test(that) Then
Application.SetTextFeedback("Try again without any punctuation selected")
Else
regEx.Pattern = "(\s) *(\S)"
regEx.Global = True
that = regEx.Replace(" " & that, "$1literal $2")
On Error Resume Next
Application.EmulateRecognition("replace that with" & that)
If 0 <> Err.Number Then
Application.SetTextFeedback("Try again with only the digits selected")
End If
End If
]]>
</script>
</command>
instead of writing this:
regEx.Pattern = "(\s) *(\S)"
regEx.Global = True
that = regEx.Replace(" " & that, "$1literal $2")
I think it would need to use some other regex pattern that extracts whitespace between words and pushes the words together in the new output.
I'm not sure how to do that. Would appreciate if anyone has a suggestion.
Since you're already using the 'literal' command, you could also use the 'nospace' command to suppress spaces between words. See the examples on my blog.