Batch convert font family in TextEdit documents

Hi there,

how can I batch convert thousands of TextEdit .rtf documents in a folder and different subfolders with text formatted in Helvetica to Courier without opening the documents (El Capitan)?

Thanks for your help.

Here is a draft script treating a single file.
The loop treating several file is left as an exercise :wink:

set original to choose file of type {"public.rtf"} # returns an alias
set pathAsList to my decoupe(original as text, ":")
set commonFolder to my recolle(items 1 thru -2 of pathAsList, ":") & ":"
set origName to item -1 of pathAsList
set newName to text 1 thru -5 of origName & " modified.rtf"
set newDoc to commonFolder & newName

set origText to read original # original is an alias
set newText to my remplace(origText, "charset0 Helvetica;", "charset0 Courier;")
my writeto(newDoc, newText, text, false)


#=====

on decoupe(t, d)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set l to text items of t
	set AppleScript's text item delimiters to oTIDs
	return l
end decoupe

#=====

on recolle(l, d)
	local oTIDs, t
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end recolle

#=====
(*
replaces every occurences of d1 by d2 in the text t
*)
on remplace(t, d1, d2)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d1}
	set l to text items of t
	set AppleScript's text item delimiters to d2
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end remplace

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		set targetFile to targetFile as «class furl»
		set openFile to open for access targetFile with write permission
		if not apendData then set eof of openFile to 0
		write theData to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access targetFile
		end try
		return false
	end try
end writeto

#=====

Yvan KOENIG running El Capitan 10.11.5 in French (VALLAURIS, France) mercredi 13 juillet 2016 16:46:26

Thank you very much Yvan. I will try that exercise :slight_smile:

Here are two proposed ways to do the job.

Version 1 :

--[SCRIPT batch_change Helvetica into Courier.app]
(*
Based upon an old recursive skeleton using only old fashioned Applescript features.

Yvan KOENIG (VALLAURIS, France)
2016/07/13
*)

property changeName : true
(* true = wxyz.rtf will be saved as wxyz modified.rtf
false = save with the original name
*)

property theExt : "rtf"

script o
	property msg1 : "" -- globale
	property msg3 : "" -- globale
	property msg90 : "" -- globale
	property msg91 : "" -- globale
	property msg92 : "" -- globale
	property msg98 : "" -- globale
	property msg99 : "" -- globale
	
	property report : "" -- globale
end script

--=====

on run (* lignes exécutées si on double clique sur l'icône du script application
¢ lines executed if one double click the application script's icon *)
	tell application (path to frontmost application as string) to set aFolder to choose folder
	open {aFolder}
end run

--=====

on open (sel) (* sel contient une liste d'alias des éléments qu'on a déposés sur l'icône du script (la sélection)
¢ sel contains a list of aliases of the items dropped on the script's icon (the selection) *)
	local elem, reportName, p2d, p2r, MsgErr, NroErr
	
	my buildMessages()
	my displayMsg(o's msg1) (*
Éviter de cliquer.
¢ Don't click. *)
	
	try
		repeat with elem in sel
			set elem to elem as text
			try
				my scanAndTreat(elem, "")
			end try
		end repeat
		
		if o's report = "" then set o's report to o's msg90 (*
crée un fichier texte sur le Bureau *)
		set p2r to (path to desktop as text) & "report_Helvetica2Courier.txt"
		my writeto(p2r, (o's report), text, false)
		
		my displayMsg(o's msg3) (*
Traitement terminé
¢ Export done. *)
		
	on error MsgErr number NroErr
		if NroErr is not -128 then
			beep 2
			tell application (path to frontmost application as string) to ¬
				display dialog "" & NroErr & " : " & MsgErr with icon 0 buttons {o's msg99} giving up after 20
		end if -- NroErr is.
	end try
end open

--=====  

on displayMsg(m)
	beep 1
	tell application (path to frontmost application as string)
		display dialog m buttons {o's msg98} default button 1 giving up after 10
	end tell
end displayMsg

--=====

on scanAndTreat(elem, ptree) (*
elem est un Unicode text
¢ elem is an Unicode text *)
	local cl_, nameExt_
	tell application "System Events" to tell disk item elem
		set cl_ to (get its class) as text
		try
			set nameExt_ to name extension
		on error
			set nameExt_ to ""
		end try
	end tell -- System Events
	
	if nameExt_ is theExt then
		my treatADocument(elem) (*
C'est un fichier Rtf.
¢ It's a Rtf document *)
	else if cl_ is in {"file package", "«class cpkg»"} then
		set o's report to o's report & elem & o's msg91 & return (*
"Package", Attention, un package EST un dossier "spécial".
¢ Caution, a package IS a "special" folder. *)
	else if cl_ is in {"folder", "«class cfol»"} then
		my scanAfolder(elem, ptree)
	else
		set o's report to o's report & elem & o's msg92 & return (*  
"Pas un document Pages".
¢ "Not a Pages document" *)
	end if -- cl_ is .
end scanAndTreat

--=====

on scanAfolder(aFolder, ptree)
	local theElems, anElem, elemName, C
	if aFolder does not end with ":" then set aFolder to aFolder & ":"
	tell application "System Events"
		set theElems to disk items of folder aFolder whose visible is true
		--repeat with anElem in (disk items of folder aFolder whose visible is true)
		repeat with anElem in theElems
			set elemName to name of anElem
			set C to name of folder aFolder
			my scanAndTreat((aFolder & elemName), ptree & C & ":")
		end repeat
	end tell
end scanAfolder

--=====

on treatADocument(sourcePath) (*
¢ sourcePath is of class Unicode text *)
	local newDoc, origText, newText
	if changeName then
		set newDoc to text 1 thru -5 of sourcePath & " modified.rtf"
	else
		set newDoc to sourcePath # uses the original pathname as destination one
	end if
	set origText to read file sourcePath # sourcePath is an Unicode text
	if origText contains "charset0 Helvetica;" then
		set newText to my remplace(origText, "charset0 Helvetica;", "charset0 Courier;")
		my writeto(newDoc, newText, text, false)
	end if
end treatADocument

--=====

on buildMessages()
	if (do shell script "defaults read 'Apple Global Domain' AppleLocale") starts with "fr_" then
		set o's msg1 to "Éviter de cliquer durant l'exécution du script" & return & "sauf s'il le demande."
		set o's msg3 to "Traitement terminé."
		set o's msg90 to "Modifications réussies sans incident."
		set msg91 to "est un Package"
		set msg92 to "n'est pas un document " & theExt
		set o's msg98 to " Vu "
		set o's msg99 to " Vu "
	else
		set o's msg1 to "Don't click when the script is running." & return & "Except, of course, if it ask for."
		set o's msg3 to "Changes done."
		set o's msg90 to "No problem during the modification process."
		set msg91 to "is a Package"
		set msg92 to "is not a " & theExt & " document"
		set o's msg98 to " OK "
		set o's msg99 to "Oops"
	end if
	set o's msg91 to " ### " & msg91 & " ###"
	set o's msg92 to " ### " & msg92 & " ###"
end buildMessages

#=====

on decoupe(t, d)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set l to text items of t
	set AppleScript's text item delimiters to oTIDs
	return l
end decoupe

#=====

on recolle(l, d)
	local oTIDs, t
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end recolle

#=====
(*
replaces every occurences of d1 by d2 in the text t
*)
on remplace(t, d1, d2)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d1}
	set l to text items of t
	set AppleScript's text item delimiters to d2
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end remplace

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		set targetFile to targetFile as «class furl»
		set openFile to open for access targetFile with write permission
		if not apendData then set eof of openFile to 0
		write theData to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access targetFile
		end try
		return false
	end try
end writeto

#=====
--[/SCRIPT]

Version 2 : use Shane STANLEY’s BridgePlus

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

property changeName : true

script o
	property theFiles : {}
end script

on run
	load framework
	my germaine()
end run

#=====

on getItemsIn:posixPathOfFolder searchedExtensions:theExtensions
	set theResults to current application's SMSForder's resourceValuesForKeys:{current application's NSURLPathKey} forFilesIn:posixPathOfFolder recursive:true skipHidden:true skipInsidePackages:true
	if theExtensions is not {} then
		set thePred to (current application's NSPredicate's predicateWithFormat:"%K.pathExtension IN[c] %@" argumentArray:{current application's NSURLPathKey, theExtensions})
		set theResults to (theResults's filteredArrayUsingPredicate:thePred)
	end if
	set thePaths to (theResults's valueForKey:(current application's NSURLPathKey))
	set o's theFiles to thePaths as list # Use the well known tip fasting use of lists as the asker wrote upon 4000 files
end getItemsIn:searchedExtensions:

#=====

on decoupe(t, d)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set l to text items of t
	set AppleScript's text item delimiters to oTIDs
	return l
end decoupe

#=====

on recolle(l, d)
	local oTIDs, t
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end recolle

#=====
(*
replaces every occurences of d1 by d2 in the text t
*)
on remplace(t, d1, d2)
	local oTIDs, l
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d1}
	set l to text items of t
	set AppleScript's text item delimiters to d2
	set t to l as text
	set AppleScript's text item delimiters to oTIDs
	return t
end remplace

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		set targetFile to targetFile as «class furl»
		set openFile to open for access targetFile with write permission
		if not apendData then set eof of openFile to 0
		write theData to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access targetFile
		end try
		return false
	end try
end writeto

#=====

on germaine()
	set posixPathOfFolder to POSIX path of (choose folder)
	# Builds the list of RTF files stored in the folder and its subfolders
	my getItemsIn:posixPathOfFolder searchedExtensions:{"rtf"} # Returns a list of posix paths
	repeat with aFile in o's theFiles
		if changeName then
			set newDoc to text 1 thru -5 of aFile & " modified.rtf"
		else
			set newDoc to aFile # uses the original pathname as destination one
		end if
		# Why is the syntax below failing in Script Editor ?
		-- set origText to read (POSIX file aFile) #  "Impossible de convertir current application en type file." number -1700 from current application to file
		# This alternate syntax works in Script Editor
		set origText to read (get POSIX file aFile) # CAUTION, get is required here
		if origText contains "charset0 Helvetica;" then
			set newText to my remplace(origText, "charset0 Helvetica;", "charset0 Courier;")
			my writeto(newDoc, newText, text, false)
		end if
	end repeat
	
end germaine

#=====

Main difference : the first one treat files when it reach them in there original location.
The second one builds a list of the files in s single call then treat the listed files.
As the list may be long it uses the good old tip storing the list in a script object.
This late one is also a recursive one but this time the recursive part is treated by the system itself which is more efficient than AppleScript to do that.

Yvan KOENIG running El Capitan 10.11.5 in French (VALLAURIS, France) jeudi 14 juillet 2016 16:02:05

Fantastic Yvan. Thank you very much. I see I have a lot to learn :slight_smile:
Is there a chance to keep the tags and comments of the files?

Thanks.

If you choose the option : property changeName : false, the tags and the comments are kept.

If you choose the option : property changeName : true, the tags and comments are dropped.

Yvan KOENIG running El Capitan 10.11.5 in French (VALLAURIS, France) jeudi 14 juillet 2016 19:36:35

Awesome! Converted the font family in more than 7000 files in about 1:23 min.

Thank you!

Thanks for the feedback.

Yvan KOENIG running El Capitan 10.11.5 in French (VALLAURIS, France) jeudi 14 juillet 2016 20:28:22