Find and replace test edit

Hi,

I’ve been trying to find a way to create a simple script that will let me automate some find and replace of a string in text edit. I did look around in the different posts but nothing seemed to have worked for me so far.

What I’m trying to do is to replace this string "\ TC: " by this string "\ TC: " (there is some empty space that is causing me trouble)

thanks
Fred

AppleScript: 2.7
Browser: Safari 601.2.7
Operating System: Mac OS X (10.10)

The best solution, IMO, is to use the Satimage.osax scripting addition:
http://www.satimage.fr/software/en/dictionaries/dict_satimage.html#SatimageTextAdditions.change

Free download at:
http://www.satimage.fr/software/en/downloads/downloads_companion_osaxen.html

If you want a native AppleScript solution, then here is one at the bottom of the page:
http://www.macosxautomation.com/applescript/sbrt/sbrt-06.html

Thanks a lot Michael

To find and replace text in AppleScript strings is recommened to use AppleScript’s text item delimiters. The performance is faster than using an 3rd party tool (app or osax).

on stringReplace(haystack, needle, replace)
	tell AppleScript
		set oldTIDs to text item delimiters
		set text item delimiters to needle
		set lst to text items of haystack
		set text item delimiters to replace
		set str to lst as string
		set text item delimiters to oldTIDs
	end tell
	return str
end stringReplace

However the question was how to find and replace in text edit application, which can be done in two ways.

tell application "TextEdit"
	tell document 1
		set its text to my stringReplace(its text, "findstring", "replacestring")
	end tell
end tell

on stringReplace(haystack, needle, replace)
	tell AppleScript
		set oldTIDs to text item delimiters
		set text item delimiters to needle
		set lst to text items of haystack
		set text item delimiters to replace
		set str to lst as string
		set text item delimiters to oldTIDs
	end tell
	return str
end stringReplace

Using the example above, the entire markup of the document is ignored and removed. To keep the markup in the document you can remove and insert new characters in text edit to preserve it’s markup instead of replace the entire document’s text.

tell application "TextEdit"
	tell document 1
		set searchString to "searchString"
		set replaceString to "replaceString"
		set searchLen to count of searchString
		
		set matchPositions to reverse of my stringOffsets(its text, searchString)
		repeat with matchPosition in matchPositions
			if searchLen > 1 then
				delete (characters (matchPosition + 1) thru (matchPosition + searchLen - 1))
			end if
			set character matchPosition to replaceString
		end repeat
	end tell
end tell

on stringOffsets(haystack, needle)
	set offsets to {}
	set pos to 0
	tell AppleScript
		set oldTIDs to text item delimiters
		set text item delimiters to needle
		set lst to text items of haystack
		set text item delimiters to oldTIDs
	end tell
	repeat with x from 1 to (count lst) - 1
		set pos to pos + (length of item x of lst)
		set end of offsets to pos + 1
		set pos to pos + (length of needle)
	end repeat
	return offsets
end stringOffsets

The code above will be slower but will preserve the markup of the document. When you have AppleScript Toolbox installed you could also avoid the stringOffsets handler and use the AST find regex command instead:

tell application "TextEdit"
	tell document 1
		set searchString to "searchString"
		set replaceString to "replaceString"
		set searchLen to count of searchString
		set haystack to it's text
		
		tell current application
			set matchPositions to reverse of (AST find regex searchString in string haystack with returning offsets)
		end tell
		
		repeat with matchPosition in matchPositions
			if (rm_eo of matchPosition) - (rm_so of matchPosition) > 1 then
				delete (characters ((rm_so of matchPosition) + 1) thru ((rm_eo of matchPosition) - 1))
			end if
			set character ((rm_so of matchPosition) + 1) to replaceString
		end repeat
	end tell
end tell

Alternate version using the same scheme than DJ Bazzie Wazzie one but is case sensitive.
Honestly I don’t know which one is faster.
My proposal uses the library BridgePlus written by Shane STANLEY.

use scripting additions
use framework "Foundation"
use script "BridgePlus"
load framework

set searchString to "searchString"
set replaceString to "replaceString"
set searchLen to count of searchString
tell application "TextEdit"
	tell document 1
		set matchPositions to reverse of my stringOffsets(its text, searchString)
		repeat with matchPosition in matchPositions
			if searchLen > 1 then
				delete (characters (matchPosition + 1) thru (matchPosition + searchLen - 1))
			end if
			set character matchPosition to replaceString
		end repeat
	end tell
end tell

on stringOffsets(haystack, needle)
	set theResult to current application's SMSForder's findMatchRecords:needle inString:haystack options:""
	set fullList to ASify from theResult
	--set fullList to theResult as list -- only in 10.10 and later
	
	set theOffsets to {}
	repeat with aRecord in fullList
		set end of theOffsets to location of foundRange of aRecord
	end repeat
	return theOffsets
end stringOffsets

Here it uses ASify. If you are running 10.10 or 10.11, you may disable set fullList to ASify from theResult and enable set fullList to theResult as list.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) lundi 1 février 2016 21:08:35

This one :wink: No TextEdit involved:

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set posixPath to POSIX path of (choose file of type {"rtf"})
set searchString to "searchString"
set replaceString to "replaceString"
-- read in file as attributed string
set {theStyledString, docAttributes} to current application's NSAttributedString's alloc()'s initWithPath:posixPath documentAttributes:(reference)
-- make copy you can modify
set theStyledString to theStyledString's mutableCopy()
-- get it's text contents and length
set theText to theStyledString's |string|()
set fullLength to theText's |length|()
set theLength to fullLength
repeat
	-- search for the string starting at the end
	set theRange to theText's rangeOfString:searchString options:(current application's NSBackwardsSearch) range:{0, theLength}
	if |length| of theRange = 0 then exit repeat -- none found, so exit
	-- use the range to replace the characters
	theStyledString's replaceCharactersInRange:theRange withString:replaceString
	-- adjust the length you're searching in
	set theLength to location of theRange
end repeat
-- turn attributed string into RTF data
set theData to theStyledString's RTFFromRange:{0, theStyledString's |length|()} documentAttributes:docAttributes
-- build path for new file
set posixPath to current application's NSString's stringWithString:posixPath
set newPath to (posixPath's stringByDeletingPathExtension()'s stringByAppendingString:"-copy")'s stringByAppendingPathExtension:(posixPath's pathExtension())
-- write the data to the file
theData's writeToFile:newPath atomically:true

Have you actually tested your pure AppleScript solution against the Satimage.osax solution?
If so, I’d love to see the test and the results.

IAC, ease of use may outweigh performance benefits. Unless you are in a tight loop doing hundred’s, or maybe thousands, of find/replace, I submit the Satimage.osax solution will be just as good, and the difference in performance not noticeable/material.

Every osax command uses an AppleEvent. It has nothing to do with the osax command itself but the overhead of the AppleEvent manager. It became clear how large the overhead was when I started to write osaxen myself because it became also clear how slow getting and creating AppleEvent types was in an programming language that is known for it’s high performance. So it’s not something against Satimage.osax it’s something against all osaxen including my own.

In my very humble opinion I would use sed if it is to be part of a larger script

do shell script “sed -e ‘s/\ TC: /\ TC: /g’ /PathtoFile.txt > tmpfile; mv tmpfile /PathtoFile.txt”

However for very quick simple stand alone tasks such as yours I would use Text Wrangler

do shell script “open -n -a ‘TextWrangler.app’”
tell application “TextWrangler”
open “PathtoFile.txt”
replace "\ TC: " using "\ TC: " searching in text of text document 1 options {starting at top:true}
save documents
close documents
quit
end tell

Done!

Hi snaplash

You are assuming that the document is a text file but most of the time, the default format for TextEdit document is not .txt but .rtfd.
In this late format, it’s fine to keep the strings attributes unchanged.
Shane’s proposal take care of that. If I understand well, yours strip all attributes and return a plain text file.

Of course, only the Original Poster may tell us which kind of file he really use.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) mercredi 3 février 2016 17:48:15

Hi Yvan

I see your point on the rtf, but if it is indeed a txt the sed solution works very well.

Not completely unimportant is that the TS wanted it to work with TextEdit, Shane’s solution bypasses TextEdit completely uncertain if that is a good thing or not.

Honestly, I don’t know what was the real link with TextEdit.
Was it that the asker wanted a piece of code to complete a script working upon a TextEdit file ?
Was it just wanting to make changes in a .txt , a.rtf or a .rtfd file ?
In this late option, we just need to edit one instruction in Shane’s code :

set posixPath to POSIX path of (choose file of type {"rtf", "rtfd", "txt"})

and the script will be able to treat the three formats.

As I wrote in an other thread, I’m not a sooth sayer, so most of the time like other helpers I “try” to answer a question which is far from a precise one opening the door to many schemes.
I guess that I will be buried before you see a majority of questions describing completely and accurately the asker’s problem. It seems that we have to live with that.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) mercredi 3 février 2016 19:45:59

:lol: You’re totally right!