Help Polish This Script

Hi all; I’m inexperienced with AppleScript but I’ve been working on something important: Mail Merge for Pages (since Apple has decided to remove this feature… :/). I’ve got the basics working but I’m guessing there are much better ways to do most of this. I’d love some help fixing this script up and doing things the “right” way. In particular, I despise the need for delays to wait for Pages to finish each step (it feels like it might break at any moment).

on waitIfNeeded()
	delay 0.05
	repeat while (do shell script "/bin/ps -xco %cpu,command | /usr/bin/awk '/Pages$/ {print $1}'") > 1
		delay 0.1
	end repeat
end waitIfNeeded

on run
	set fieldDelimiter to "%"
	
	tell application "Numbers"
		set entries to table 1 of sheet 1 of document 1
		set fields to value of cells of row 1 of entries
		set countOfEntries to count of rows of entries
	end tell
	
	tell application "Pages" to set pagesDocument to name of document 1
	tell application "Numbers" to set numbersDocument to name of document 1
	
	display dialog "Merging data from \"" & numbersDocument & "\" into \"" & pagesDocument & "\". Estimated merge time: " & (round (3 * countOfEntries / 60)) & " minutes."
	
	tell application "Pages" to activate
	tell application "System Events"
		tell process "Pages"
			-- Copy template and make new document
			keystroke "a" using command down
			my waitIfNeeded()
			keystroke "c" using command down
			my waitIfNeeded()
			keystroke "s" using command down & shift down
			my waitIfNeeded()
			keystroke return
			my waitIfNeeded()
			key code 51 -- delete
			my waitIfNeeded()
			
			repeat with currentEntry from 2 to countOfEntries
				-- Paste template and replace fields
				keystroke "v" using command down
				keystroke "f" using command down
				repeat with currentField in fields
					tell application "Numbers" to set currentValue to value of cell currentEntry of column currentField of entries
					
					keystroke fieldDelimiter & currentField & fieldDelimiter
					keystroke tab
					keystroke currentValue
					click button 4 of window 1
					my waitIfNeeded()
					keystroke tab
				end repeat
				keystroke "w" using command down
				my waitIfNeeded()
				
				-- Move insertion point to end of document	
				keystroke "a" using command down
				key code 124 -- right arrow
				
				-- Also scroll to end of document
				key code 119 -- end
				my waitIfNeeded()
			end repeat
		end tell
	end tell
end run

Hello

I’m not sure that I understand everything in your script.
It would be more clear if you described what is available in the original Pages document used as template.
At this time I’m assuming that it contains something like :

someText1 %field1% someText2 %field2% someText3 %field3% .

I would also wish to know why you are seemingly using a standard Pages document as starting point in lieu of a true template.

This written, here are some suggestions.

At the beginning I would use :

tell application "Numbers"
	set numbersDocument to name of document 1
	set entries to table 1 of sheet 1 of document 1
	set countOfEntries to count of rows of entries
	set theEntries to value of cells of every row of entries
	--> {{"field1", "field2", "field3"}, {123.0, 567.0, 89.0}, {45.0, missing value, "az"}, {1.2345E+4, "a", "z"}, {400.0, "b", "c"}, {500.0, "d", "e"}}
end tell

This way I will no longer have to speak to Number in the script.

The main loop would start with :

repeat with currentEntry from 2 to countOfEntries
				-- Paste template and replace fields
				keystroke "v" using command down
				keystroke "f" using command down
				repeat with currentField in item 1 of theEntries
					 set currentValue to item currentField of item currentEntry of theEntries

As you see, I no longer use the variable theEntries.
It’s just a suggestion because I don’t know if my scheme is faster than yours.

I’m a bit puzzled by the waitIfNeeded handler because, according to the offset linked to every call to do shell script, I’m afraid that it will insert too long pauses.

Yvan KOENIG (VALLAURIS, France) jeudi 21 août 2014 10:46:56

Thanks for the input, Yvan! I do like your only-speaking-to-Numbers-once method.

I’m not using a template because I want this script to work as much as possible like the original feature that Apple removed, and that worked on a normal document.

I hate waitIfNeeded, too! But how else can I know, other than monitoring Pages’s processor use, if Pages is done processing and is ready to move on? I wish there were a way to block on every event, and know for sure when Pages is done with Select All, Copy, Replace All, etc. But from what I gather on the internet, that’s not possible, is it? That’s the downside of UI scripting?

Here is a handler which I use in some scripts when I know that treated datas may be large.


#=====

on safeCopy(theApp)
	local tt
	(*
Fill the clipboard with a fake string *)
	set tt to "All The Things You Could Be By Now If Sigmund Freud's Wife Was Your Mother, © Charles Mingus"
	set the clipboard to tt
	(*
Copy the selected item *)
	my raccourci(theApp, "c", "c")
	(*
Loop waiting the achievement of the Copy task. *)
	repeat 10 times
		try
			if (the clipboard as text) is not tt then exit repeat
		on error
			delay 0.1
		end try
	end repeat
end safeCopy

==== Uses GUIscripting ==== 
*)
(*
This handler may be used to 'type' text, invisible characters if the third parameter is an empty string. 
It may be used to 'type' keyboard raccourcis if the third parameter describe the required modifier keys. 

I changed its name « shortcut » to « raccourci » to get rid of a name conflict in Smile. 
*)
on raccourci(a, t, d)
	local k
	activate application a
	tell application "System Events" to tell process a
		set frontmost to true
		try
			t * 1
			if d is "" then
				key code t
			else if d is "c" then
				key code t using {command down}
			else if d is "a" then
				key code t using {option down}
			else if d is "k" then
				key code t using {control down}
			else if d is "s" then
				key code t using {shift down}
			else if d is in {"ac", "ca"} then
				key code t using {command down, option down}
			else if d is in {"as", "sa"} then
				key code t using {shift down, option down}
			else if d is in {"sc", "cs"} then
				key code t using {command down, shift down}
			else if d is in {"kc", "ck"} then
				key code t using {command down, control down}
			else if d is in {"ks", "sk"} then
				key code t using {shift down, control down}
			else if (d contains "c") and (d contains "s") and d contains "k" then
				key code t using {command down, shift down, control down}
			else if (d contains "c") and (d contains "s") and d contains "a" then
				key code t using {command down, shift down, option down}
			end if
		on error
			repeat with k in t
				if d is "" then
					keystroke (k as text)
				else if d is "c" then
					keystroke (k as text) using {command down}
				else if d is "a" then
					keystroke k using {option down}
				else if d is "k" then
					keystroke (k as text) using {control down}
				else if d is "s" then
					keystroke k using {shift down}
				else if d is in {"ac", "ca"} then
					keystroke (k as text) using {command down, option down}
				else if d is in {"as", "sa"} then
					keystroke (k as text) using {shift down, option down}
				else if d is in {"sc", "cs"} then
					keystroke (k as text) using {command down, shift down}
				else if d is in {"kc", "ck"} then
					keystroke (k as text) using {command down, control down}
				else if d is in {"ks", "sk"} then
					keystroke (k as text) using {shift down, control down}
				else if (d contains "c") and (d contains "s") and d contains "k" then
					keystroke (k as text) using {command down, shift down, control down}
				else if (d contains "c") and (d contains "s") and d contains "a" then
					keystroke (k as text) using {command down, shift down, option down}
				end if
			end repeat
		end try
	end tell
end raccourci

#=====

Of course, the handler issuing the keystroke may be dropped if you insert the useful code in the first handler.
Often I’m too lazy to do that and just call one of my pre-written handlers.

Yvan KOENIG (VALLAURIS, France) samedi 23 août 2014 10:44:47