Script Doesn’t Work When Target App is Referenced by Variable

Hello,

I’m trying to write a script it takes a bunch of either xlsx, docx or pptx files, and converts each to a PDF using the appropriate iWork app (Numbers, Pages and Keynote, respectively). Here’s how it goes:

  1. Files are dropped on to the script, or sent to it by Finder selection.
  2. Each file is put in a JSON-like record, with the designated target app as a property.
  3. Records are sorted (thanks, Nigel!) by the target app property, to make conversions more streamlined and to be able to close the target app once it’s done.
  4. Everything works well, except for this very specific segment:

		export doc_id ¬
		to file ((source_parent & source_name & ".pdf") as text) ¬
		as PDF

It doesn’t even let me compile the script. If I put this block in and if-else clause, and based on the extension, explicitly refer to the app to handle the export, the script works well, e.g.:


if source_ext = "xlsx" or source_ext = "xls" then
	 tell app  "Numbers" to export doc_id --...

It has been driving me crazy! Help would be extremely appreciated.

Thanks!

Here’s the full script:



tell application "Finder"
	set thePaths to selection as alias list
end tell

set docs_to_process to {}
repeat with thePath in thePaths
	tell application "Finder"
		set source_parent to folder of thePath as text
		set source_path to thePath
		set source_ext to name extension of thePath
		set source_name to (name of thePath as text)
	end tell
	if source_ext = "xlsx" or source_ext = "xls" then
		set target_app to "Numbers"
	else if source_ext = "ppt" or source_ext = "pptx" then
		set target_app to "Keynote"
	else if source_ext = "docx" or source_ext = "doc" then
		set target_app to "Pages"
	end if
	set docs_to_process to docs_to_process & [{source_path:source_path, source_parent:source_parent, target_app:target_app, source_name:source_name}]
end repeat

customBubbleSort(docs_to_process, byTargetApp)

repeat with the_doc in docs_to_process
	if application target_app is running then
		set quitApp to false
	else
		set quitApp to true
	end if
	
	tell application target_app
		set doc_id to open thePath -- gives the the opened doc a 'document id'
		tell application "Finder"
			if (exists file (source_parent & source_name & ".pdf")) then
				set n to 1
				repeat while (exists file (source_parent & source_name & ".pdf"))
					set source_name to source_name & space & n
					set n to n + 1
				end repeat
			end if
		end tell
		--export doc_id ¬
		--to file ((source_parent & source_name & ".pdf") as text) ¬
		--as PDF
		close doc_id
		if quitApp is true then
			quit
		end if
	end tell
end repeat

on customBubbleSort(thelist, compObj)
	script o
		property lst : thelist
	end script
	
	repeat with i from (count thelist) to 2 by -1
		set a to beginning of o's lst
		repeat with j from 2 to i
			set b to item j of o's lst
			if (compObj's isGreater(a, b)) then
				set item (j - 1) of o's lst to b
				set item j of o's lst to a
			else
				set a to b
			end if
		end repeat
	end repeat
end customBubbleSort

script byTargetApp
	on isGreater(a, b)
		(a's target_app > b's target_app)
	end isGreater
end script


Hi.

The compiler needs to be able to identify the application in order to read its dictionary and find out how to compile ‘export’. It can’t do this if it’s instead given a variable which won’t be set until the script’s run.

I think that Numbers, Keynote, and Pages use the same token for ‘export’, and their dictionary entries for it appear to be identical, so this may work for all three:

using terms from application "Numbers" -- also covers Keynote and Pages for 'export'.
	tell application target_app to export doc_id ¬
		to file ((source_parent & source_name & ".pdf") as text) ¬
		as PDF
end using terms from

‘running’, ‘open’, ‘close’, and ‘quit’ are common to all apps, which is why the compiler has no problem with them.

It’s a good idea not to nest ‘tell’ blocks to different applications if this can be avoided, so your second repeat may be better arranged like this:

repeat with the_doc in docs_to_process
	set quitApp to (application target_app is not running)
	
	tell application "Finder"
		if (exists file (source_parent & source_name & ".pdf")) then
			set n to 1
			repeat while (exists file (source_parent & source_name & ".pdf"))
				set source_name to source_name & space & n
				set n to n + 1
			end repeat
		end if
	end tell
	
	using terms from application "Numbers" -- also covers Keynote and Pages for 'export'.
		tell application target_app
			set doc_id to (open thePath) -- gives the opened doc a 'document id'
			export doc_id ¬
				to file ((source_parent & source_name & ".pdf") as text) ¬
				as PDF			
			close doc_id
			if (quitApp) then
				quit
			end if
		end tell
	end using terms from
end repeat

Thanks for the quick reply, Nigel, but sadly your suggestion doesn’t work. Even with the ‘using terms from’ statement, the target app still has to be specified explicitly. Thanks also for the other suggestion, I’ve implemented that as well.

P. S. — Apparently the script was defective on a whole other level, in the last tell block, I forgot to refer specifically to the document’s (the one in the current iteration) properties (i.e. “the_doc’s source_path” instead of just “source_path”).

see below

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 14 mars 2019 20:55:54

I’m not surprised that Using terms from doesn’t solve the problem.
In raw format, the export instruction is coded :
«event Knstexpo» doc_id given «class kfil»:pathToPDF, «class exft»:«constant KnefKpdf» in Keynote terms,
«event Nmstexpo» doc_id given «class pfil»:pathToPDF, «class exft»:«constant NmefNpdf» in Numbers terms
«event Pgstexpo» doc_id given «class pfil»:pathToPDF, «class exft»:«constant PgefPpdf» in Pages terms.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) vendredi 15 mars 2019 09:36:50

Ah. Thanks, Yvan. And now I look more closely at the applications’ dictionaries, ‘export’ is in the individual suite for each of them, not in their iWork Suites.

Sorry for the bad guess, ScriptLover. :frowning:

When I tested I missed some points.
(1) On each test I passed a single file so I missed that the script missed to treat different files.
This required the addition of four instructions:

set thePath to the_doc's source_path # ADDED
set source_parent to the_doc's source_parent # ADDED
set target_app to the_doc's target_app # ADDED
set source_name to the_doc's source_name # ADDED

(2) When it tried to open the 2nd file, the instructions:
tell application target_app
set doc_id to open thePath – gives the the opened doc a ‘document id’
end tell

were executed as :
tell application “Script Editor”
open alias “SSD 500:Users::desktop:Address List to Merge.xls"
open alias "SSD 500:Users:
:desktop:Address List to Merge.xls”
→ error “Erreur dans Numbers : L’application n’est pas ouverte.” number -600

So I moved the open instruction in the piece of code dedicated to each application.

(3) The use of quitApp failed.
It appeared that the instruction defining quitApp must be in the code dedicated to each application too.

The resulting code is :

(*

https://macscripter.net/viewtopic.php?id=46774

*)

property theList : {{"pptx", "Keynote"}, {"ppt", "Keynote"}, {"xlsx", "Numbers"}, {"xls", "Numbers"}, {"docx", "Pages"}, {"doc", "Pages"}}

tell application "Finder"
	set thePaths to selection as alias list
end tell

set docs_to_process to {}
repeat with thePath in thePaths
	set source_path to contents of thePath
	tell application "Finder"
		set source_parent to folder of source_path as text
		set source_ext to name extension of source_path
	end tell
	repeat with i from 1 to count theList
		if source_ext = item 1 of my theList's item i then
			set target_app to item 2 of my theList's item i
			exit repeat
		end if
	end repeat
	
	copy {source_path:source_path, source_parent:source_parent, target_app:target_app} to end of docs_to_process
end repeat

customBubbleSort(docs_to_process, byTargetApp)

repeat with the_doc in docs_to_process
	
	set source_path to the_doc's source_path
	set source_parent to the_doc's source_parent
	set target_app to the_doc's target_app
	
	tell application "Finder"
		# Extract the filename here so there is no need to insert it in the list of records!
		set source_name to name of source_path
		if (exists file (source_parent & source_name & ".pdf")) then
			set n to 2
			repeat while (exists file (source_parent & source_name & space & n & ".pdf"))
				set n to n + 1
			end repeat
			set source_name to source_name & space & n
		end if
	end tell -- Finder
	
	set pathToPDF to (source_parent & source_name & ".pdf") as «class furl»
	
	if target_app = "Keynote" then
		set quitApp to not (application "Keynote" is running)
		tell application "Keynote"
			set doc_id to open source_path -- gives the the opened doc a 'document id'
			export doc_id to pathToPDF as PDF
			close doc_id
			if quitApp then
				quit
				delay 0.1
			end if
		end tell # "Keynote"
	else if target_app = "Numbers" then
		set quitApp to not (application "Numbers" is running)
		tell application "Numbers"
			set doc_id to open source_path -- gives the the opened doc a 'document id'
			export doc_id to pathToPDF as PDF
			close doc_id
			if quitApp then
				quit
				delay 0.1
			end if
		end tell # "Numbers"
	else if target_app = "Pages" then
		set quitApp to not (application "Pages" is running)
		tell application "Pages"
		if quitApp then activate # ADDED
			set doc_id to open source_path -- gives the the opened doc a 'document id'
			export doc_id to pathToPDF as PDF
			close doc_id
			if quitApp then
				quit
				delay 0.1
			end if
		end tell # "Pages"
	end if
end repeat

#=====

on customBubbleSort(theList, compObj)
	script o
		property lst : theList
	end script
	
	repeat with i from (count theList) to 2 by -1
		set a to beginning of o's lst
		repeat with j from 2 to i
			set b to item j of o's lst
			if (compObj's isGreater(a, b)) then
				set item (j - 1) of o's lst to b
				set item j of o's lst to a
			else
				set a to b
			end if
		end repeat
	end repeat
	--return thelist # Matter of taste, for my own use, I would activate this instruction
end customBubbleSort

script byTargetApp
	on isGreater(a, b)
		(a's target_app > b's target_app)
	end isGreater
end script

I tested with a selection containing : a .doc, a .docx, a .xls, a xlsx files
I have no .ppt files on my machine.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) vendredi 15 mars 2019 12:31:11

Hello

I just discovered a curious behavior.
When the script quit once Pages, for the other documents using Pages, the open process is reported as :
tell application “Script Editor”
open alias “SSD 500:Users::desktop:Demande Mr KOENIG Yvan.docx"
end tell
tell application “Pages”
open alias "SSD 500:Users:
:desktop:Demande Mr KOENIG Yvan.docx”
→ document id “532A0C67-491B-43E7-A95C-508C5630C113”
export document id “532A0C67-491B-43E7-A95C-508C5630C113” to file “SSD 500:Users:**********:desktop:Demande Mr KOENIG Yvan.docx 3.pdf” as PDF
close document id “532A0C67-491B-43E7-A95C-508C5630C113”
quit
end tell

The only way to get rid of that was to activate Pages before opening the document.
I edited the script above accordingly.

No need to do that with Numbers.
I don’t know if it is needed for Keynote.
I will appreciate feedback about this point.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) vendredi 15 mars 2019 18:24:11

I was bored by the fact that the script had to quit - restart the used app repeatedly so I decided to design an alternate scheme which finally appears to be neater.

(*

https://macscripter.net/viewtopic.php?id=46774

*)

tell application "Finder"
	set thePaths to selection as alias list
end tell

set docsForKeynote to {}
set docsForNumbers to {}
set docsForPages to {}

repeat with thePath in thePaths
	set source_path to contents of thePath
	tell application "Finder"
		set source_ext to name extension of source_path
	end tell
	
	if source_ext is in {"pptx", "ppt"} then
		copy source_path to end of docsForKeynote # Build a list of doc to treat with Keynote
	else if source_ext is in {"xlsx", "xls"} then
		copy source_path to end of docsForNumbers # Build a list of doc to treat with Numbers
	else if source_ext is in {"docx", "doc"} then
		copy source_path to end of docsForPages # Build a list of doc to treat with Pages
	end if
end repeat

set quitKeynote to not (application "Keynote" is running)
repeat with the_doc in docsForKeynote # Treat every pptx or ppt documents
	set pathToPDF to my buildPathToPDF(the_doc)
	tell application "Keynote"
		set doc_id to open the_doc -- gives the the opened doc a 'document id'
		export doc_id to pathToPDF as PDF
		close doc_id
	end tell # "Keynote"
end repeat
if quitKeynote then quit application "Keynote" # Quit only once if it's needed.
# If Keynote wasn't running on entry, now it's not running.

set quitNumbers to not (application "Numbers" is running)
repeat with the_doc in docsForNumbers # Treat every xlsx or xls documents
	set pathToPDF to my buildPathToPDF(the_doc)
	tell application "Numbers"
		set doc_id to open the_doc -- gives the the opened doc a 'document id'
		export doc_id to pathToPDF as PDF
		close doc_id
	end tell # "Numbers"
end repeat
if quitNumbers then quit application "Numbers" # Quit only once if it's needed.
# If Numbers wasn't running on entry, now it's not running.

set quitPages to not (application "Pages" is running)
repeat with the_doc in docsForPages # Treat every docx or doc documents
	set pathToPDF to my buildPathToPDF(the_doc)
	tell application "Pages"
		set doc_id to open the_doc -- gives the the opened doc a 'document id'
		export doc_id to pathToPDF as PDF
		close doc_id
	end tell # "Pages"
end repeat
if quitPages then quit application "Pages" # Quit only once if it's needed.
# If Pages wasn't running on entry, now it's not running.

#=====

on buildPathToPDF(source_path)
	local source_parent, source_name, n
	tell application "Finder"
		set source_parent to folder of source_path as text
		set source_name to name of source_path
		
		if exists file (source_parent & source_name & ".pdf") then
			set n to 2
			repeat while (exists file (source_parent & source_name & space & n & ".pdf"))
				set n to n + 1
			end repeat
			set source_name to source_name & space & n
		end if
	end tell -- Finder
	
	return (source_parent & source_name & ".pdf") as «class furl»
end buildPathToPDF

#=====

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 16 mars 2019 11:48:53

Thanks Ivan, this indeed works and a similar version to what you’ve offered is what I’ve been using so far, but I’ve opened this discussion in hopes of finding a shorter, more elegant script. Guess there’s no way to avoid the final if-else clause.

Thanks!