Question about scripting Pages and Keynote

I’m trying to loop through a Keynote presentation and move the presenter notes into a Pages document. I’m close, but I have two problems so far. The one I’ve been working on mostly is trying to add pages to the bottom of the Pages document. Right now, every time I add a page or a section, it adds a page after the selected page, and page 1 stays selected. Therefore, my notes are copying in reverse order.

The other problem that I haven’t really explored yet – not sure how to explore it – is how to copy the rich text from the presenter notes and not just the plain text.

Here’s the code that I have so far. I’ve had some tips from a user on another board for the Pages, but I think he’s stuck at the same place I am. There doesn’t seem to be a way to select the last page in the code. I found out that if I put a breakpoint in the code before the Make statement and select the last page manually, then the code would work. If I had a way to set the insertion point at the bottom of the document right before the Make command, then I think it would work.


property defaultTemplateName : "Blank"
property Slide_notes : ""


tell application "Keynote"
	set theNumberOfSlides to count of slides in document 1
end tell

tell application "Pages"
	activate
	set Number_of_pages to count of documents
	if Number_of_pages = 0 then
		set myDocument to make new document with properties {document template:template defaultTemplateName}
	else
		set myDocument to document Number_of_pages
	end if
end tell

tell application "Keynote"
	set myPresentation to document 1
end tell

repeat with Slide_count from 1 to theNumberOfSlides
	tell application "Keynote"
		tell myPresentation
			set mySlide to slide Slide_count
			set Slide_notes to (presenter notes of mySlide)
		end tell
	end tell
	
	tell application "Pages"
		tell myDocument
			if Slide_count is 1 then
				set body text to Slide_notes
			else
				set newPage to make new page at the end of sections
				set body text of newPage to Slide_notes
			end if
		end tell
	end tell
	
end repeat

Hi. Welcome to MacScripter.

It seems to be a bug in Pages rather than anything you’re doing wrong. The only way I can see so far to add pages and put text into the correct ones is to make all the pages first and then populate them. That is, replace your last repeat with these two:

-- Make the required number of additional pages.
repeat theNumberOfSlides - 1 times
	tell application "Pages" to tell myDocument to make new page
end repeat

-- Then add the required texts.
repeat with Slide_count from 1 to theNumberOfSlides
	tell application "Keynote"
		tell myPresentation
			set mySlide to slide Slide_count
			set Slide_notes to (presenter notes of mySlide)
		end tell
	end tell
	
	tell application "Pages" to set body text of page Slide_count of myDocument to Slide_notes
end repeat

Hello, Nigel.
That’s also the only workaround I had found.

How about the question with Keynote? Do you have any insights or guidance regarding rich text?
Chuck

Hi Chuck.

In broad terms, it’s fairly simple. Get the properties of the each slide’s presenter notes and set the properties of the corresponding page’s body text to the same values:

-- Make the required number of additional pages.
repeat theNumberOfSlides - 1 times
	tell application "Pages" to tell myDocument to make new page
end repeat

-- Then add the required texts.
repeat with Slide_count from 1 to theNumberOfSlides
	tell application "Keynote"
		tell myPresentation
			set mySlide to slide Slide_count
			set Slide_notes to ({it, font, color, size} of presenter notes of mySlide)
		end tell
	end tell

	tell application "Pages"
		tell page Slide_count of myDocument
			set {body text, font of body text, color of body text, size of body text} to Slide_notes
		end tell
	end tell
end repeat

If you want to match style changes within each text, you have to go through character-by-character searching for them. The iWork text suite doesn’t seem to support attribute runs. :frowning:

Thanks for the advice, Nigel.

I haven’t had to work much with characters and strings – only a couple of scripts a few years ago that I don’t remember. I’ll play with examining and copying characters one at a time. Do you have any samples to go by?

The following’s not very pleasant, but fairly effective. Effects like bold, italic, and underline can only be reproduced where Keynote uses separate fonts for them.

property defaultTemplateName : "Blank"

tell application "Keynote"
	set myPresentation to document 1
	set theNumberOfSlides to (count myPresentation's slides)
end tell

tell application "Pages"
	activate
	if ((count documents) is 0) then
		set myDocument to (make new document with properties {document template:template defaultTemplateName})
	else
		set myDocument to back document -- Equivalent to the original 'document Number_of_pages'. Change to 'front document' or 'document 1' if that's what's meant.
	end if
	
	-- Make the required number of additional pages. (This script assumes that the document starts with only one page.)
	repeat theNumberOfSlides - 1 times
		tell myDocument to make new page
	end repeat
end tell


-- Then add the required texts.
repeat with slideCount from 1 to theNumberOfSlides
	tell application "Keynote"
		tell myPresentation
			set mySlide to slide slideCount
			-- Get the text of the presenter notes and three lists containing respectively the fonts, colours, and sizes of all the text's characters.
			tell mySlide's presenter notes to set {noteText, fontList, colourList, sizeList} to {it, font of characters, color of characters, size of characters}
		end tell
	end tell
	
	-- Initialise an attribute details list to the index and font attributes of the first character.
	set previousAttributes to {1, {beginning of fontList, beginning of colourList, beginning of sizeList}}
	-- Initialise a collector for pseudo-"attribute runs".
	set attributeRuns to {}
	-- Work through the attributes for each of the remaining characters in turn.
	repeat with c from 2 to (count fontList)
		-- Get this character's font, colour, and size.
		set theseAttributes to {item c of fontList, item c of colourList, item c of sizeList}
		-- If any of them have changed since the previous character …
		if (previousAttributes does not end with {theseAttributes}) then
			-- … replace the start index in the previousAttributes list with the full text to which those attributes apply …
			set item 1 of previousAttributes to text (get beginning of previousAttributes) thru (c - 1) of noteText
			-- … store the list in attributeRuns …
			set end of attributeRuns to previousAttributes
			-- … and start a new previousAttributes list with the current character index and the character's attributes.
			set previousAttributes to {c, theseAttributes}
		end if
	end repeat
	-- Finish the attribute run in progress at the end of the repeat.
	set item 1 of previousAttributes to text (get beginning of previousAttributes) thru c of noteText
	set end of attributeRuns to previousAttributes
	
	tell application "Pages"
		tell page slideCount of myDocument
			-- The only way I can find to append text directly to Pages 7.1 body text is to set the body text's last character to itself and the additional text.
			-- Initialise this page's body text to a space so that it actually contains a last character.
			set body text to space
			-- Work through the collected "attribute runs" in turn.
			repeat with theseAttributes in attributeRuns
				-- Get the text, font, colour, and size of this run.
				set {txt, fontColourAndSize} to theseAttributes
				-- I've not been able to set the attributes of a _range_ of characters in a Pages 7.1 body text.
				-- Rather than append the additional text and then set the attributes of each of its characters in turn, this code just appends a space, changes its attributes, and then replaces it with (sets it to) the additional text.
				tell body text
					set character -1 to character -1 & space
					tell character -1 to set {its font, its color, its size} to fontColourAndSize
					set character -1 to txt
				end tell
			end repeat
			-- When done, delete the original initialisation space.
			set character 1 of body text to ""
		end tell
	end tell
end repeat

Thanks for the example – I think it would’ve been a long time before I reached this same code. And you’re right, it’s not very elegant.

I have to step back and reconsider how badly I want to move the formatting. I tend to make large-point speaker notes. I was running tests yesterday, and moving the formatted text conflicted with our method of pre-adding pages and then copying each slides’ value separately because the notes on some slides are longer than one page. That returned me to the problem that I would have to add pages to the bottom of the document within the loop that populates the text – which we found to not be possible.

You may create a document for each slide then merge them in a single one.
You will get the pages in the desired order.

An other way would be to use an index specific to the target page.
If the inserted ‘note’ use one page the index will be incremented by one. If it use say 3 pages, the index will be incremented by 3.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 13 septembre 2018 19:23:54

The solution may be to add ‘sections’ rather than ‘pages’. If a note text is longer than a page, Pages will create extra page(s) within the current, numbered section.

Texts as long as or longer than a page are going to have huge numbers of characters, so I’ve modified the script to reference the list variables and make them non-persistent. It may still hesitate over very long texts, but this is due to the time it takes to Keynote to return all the characters’ properties. The script’s analysis of them is actually pretty fast.

property defaultTemplateName : "Blank"

-- Code in a handler to make the variables local and therefore non-persistent.
on main()
	-- Script object to allow references to the list variables, for speed of access to the lists' items.
	script o
		property fontList : missing value
		property colourList : missing value
		property sizeList : missing value
		property attributeRuns : missing value
	end script
	
	tell application "Keynote"
		set myPresentation to document 1
		set theNumberOfSlides to (count myPresentation's slides)
	end tell
	
	tell application "Pages"
		activate
		if ((count documents) is 0) then
			set myDocument to (make new document with properties {document template:template defaultTemplateName})
		else
			set myDocument to back document -- Equivalent to the original 'document Number_of_pages'. Change to 'front document' or 'document 1' if that's what's meant.
		end if
		
		-- Make the required number of additional sections. (This script assumes that the document starts with only one section.)
		repeat theNumberOfSlides - 1 times
			tell myDocument to make new section
		end repeat
	end tell
	
	-- Repeat with every slide in the Keynote document.
	repeat with slideCount from 1 to theNumberOfSlides
		-- Get the presenter notes data from the Keynote slide.
		tell application "Keynote"
			tell myPresentation
				set mySlide to slide slideCount
				-- Get the text of the slide's presenter notes and three lists containing respectively the fonts, colours, and sizes of all the text's characters.
				tell mySlide's presenter notes
					set noteText to it
					set {o's fontList, o's colourList, o's sizeList} to {font, color, size} of characters
				end tell
			end tell
		end tell
		
		-- Analyse the data into pseudo-"attribute runs".
		set o's attributeRuns to {}
		-- Initialise an attribute details list to the index and font attributes of the first character.
		set previousAttributes to {1, {beginning of o's fontList, beginning of o's colourList, beginning of o's sizeList}}
		-- Work through the attributes for each of the remaining characters in turn.
		repeat with c from 2 to (count o's fontList)
			-- Get this character's font, colour, and size.
			set theseAttributes to {item c of o's fontList, item c of o's colourList, item c of o's sizeList}
			-- If any of them have changed since the previous character …
			if (previousAttributes does not end with {theseAttributes}) then
				-- … replace the start index in the previousAttributes list with the full text to which those attributes apply …
				set item 1 of previousAttributes to text (get beginning of previousAttributes) thru (c - 1) of noteText
				-- … store the list in attributeRuns …
				set end of o's attributeRuns to previousAttributes
				-- … and start a new previousAttributes list with the current character index and the character's attributes.
				set previousAttributes to {c, theseAttributes}
			end if
		end repeat
		-- Finish the attribute run in progress at the end of the repeat.
		set item 1 of previousAttributes to text (get beginning of previousAttributes) thru c of noteText
		set end of o's attributeRuns to previousAttributes
		
		-- Insert the data into the relevant section of the Pages document.
		tell application "Pages"
			tell myDocument
				tell section slideCount
					-- The only way I can find to append text directly to Pages 7.1 body text is to set the body text's last character to itself and the additional text.
					-- Initialise this section's body text to a space so that it actually contains a last character.
					set body text to space
					-- Work through the collected "attribute runs" in turn.
					repeat with theseAttributes in o's attributeRuns
						-- Get the text, font, colour, and size of this run.
						set {txt, fontColourAndSize} to theseAttributes
						-- I've not been able to set the attributes of a _range_ of characters in a Pages 7.1 body text.
						-- Rather than append the additional text and then set the attributes of each of its characters in turn, this code just appends a space, changes its attributes, and then replaces it with (sets it to) the additional text.
						tell body text
							set character -1 to character -1 & space
							tell character -1 to set {its font, its color, its size} to fontColourAndSize
							set character -1 to txt
						end tell
					end repeat
					-- When done, delete the original initialisation space.
					set character 1 of body text to ""
				end tell
			end tell
		end tell
	end repeat
end main

main()