Where are recent documents for Pages 09?

I’ve got a couple of scripts that check data for recent Pages '09 app documents that are now broken in El Capitan (MacOSX10.11.6) +

The scripts check the “com.apple.iWork.Pages.LSSharedFileList.plist” file in ~/Library/Preferences. But this is now an empty file ??? Pages still updates recent docs in the menu, so it’s getting the data from somewhere !!! I’ve tried looking in ~/Library/Containers… there’s no “com.apple.iWork” folder as expected.

It’s the same for other scripts that employ similar strategies for other apps like Preview etc. I get an error when it tries to read the associated LSSharedFileList file. It worked fine in Yosemite/Mavericks, so maybe there’s a change in the system.

Any ideas where the data for recent docs for apps is now stored?

One of the scripts below. Suggestions please!


--"clear recent docs" v7 by Alnoor Feb 2014 for Mavericks

--Selectively clear listed documents in Pages "Open Recent" Menu & trash "version" files
--NB: Clears ALL recent menu items in Finder!!
-----------------------

--PROPERTIES:

global prefsFile

property token : ASCII character 165 -- "¢"
property prefsName : "com.apple.iWork.Pages.LSSharedFileList"
property defaultPrefsRecord : {|RecentDocuments|:{|Controller|:"CustomListItems", |CustomListItems|:{}, |MaxAmount|:10}}
set path2prefs to (path to preferences)
set prefsFolder to path2prefs as alias


--HANDLERS:

--SEARCHANDREPLACE
on searchAndReplace(txt, srch, rpl)
	set AppleScript's text item delimiters to {srch}
	set temp to every text item of txt
	set AppleScript's text item delimiters to {rpl}
	set temp to (temp as string)
	set AppleScript's text item delimiters to {""}
	return temp
end searchAndReplace

--REWRITE PLIST 
on rewriteplist(myRecord)
	tell application "System Events"
		prefsFile as text
		set value of property list file the result to myRecord
	end tell
end rewriteplist

--MENU CLICK
on menu_click(mList)
	local appName, topMenu, r
	-- Validate our input
	if mList's length < 3 then error "Menu list is not long enough"
	-- Set these variables for clarity and brevity later on
	set {appName, topMenu} to (items 1 through 2 of mList)
	set r to (items 3 through (mList's length) of mList)
	-- This overly-long line calls the menu_recurse function with
	-- two arguments: r, and a reference to the top-level menu
	tell application "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
		(menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click
--...
on menu_click_recurse(mList, parentObject)
	local f, r
	-- `f` = first item, `r` = rest of items
	set f to item 1 of mList
	if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
	-- either actually click the menu item, or recurse again
	tell application "System Events"
		if mList's length is 1 then
			click parentObject's menu item f
		else
			my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
		end if
	end tell
end menu_click_recurse


--SCRIPT:

--welcome
activate
display dialog "Remove selected documents from \"Open Recent\" Menu?" with icon 1

--initialize
set iquitpages to false
tell application "System Events" to get name of processes
set application_list to result
if application_list contains "Pages" is true then
	display dialog "\"Pages\" will quit!" buttons {"Cancel", "Continue"} default button 2 with icon 0
	tell application "Pages" to quit
	set iquitpages to true
	delay 2
end if

tell application "Finder"
	set prefsFile to (file (prefsName & ".plist") of prefsFolder) as alias --keep in line with other apps!!
end tell

--read pList
tell application "System Events"
	tell property list file (prefsFile as text)
		if (it exists) then
			tell property list item "RecentDocuments"
				tell property list item "CustomListItems"
					-- Get a list of the menu item names and a parallel list of plists for the associated data.
					set {RecentNames, RecentBookmarks, RecentPlists} to {value of property list item "Name", value of property list item "Bookmark", text} of every property list item --<data> of (Bookmarks & Icon) cannot be read into a record, they must be read explicitly as <text>
					
					--check for uniqueness...
					--add token(s) to duplicate names only
					repeat with i from 2 to (count RecentNames) --i.e skip first, and compare with proceeding items only
						repeat with j from 1 to i
							if i is not j then --skip self
								if (item i of RecentNames) is (item j of RecentNames) then
									set (item i of RecentNames) to (item i of RecentNames) & token
								end if
							end if
						end repeat
					end repeat
					
				end tell
			end tell
		else
			--error --No pList!!
			activate
			display dialog "No recent documents for \"Pages\" found!" buttons {"Cancel"} default button 1 with icon 0
		end if
	end tell
end tell

--select the names of the items to remove from the menu.
activate
try
	set namesToCut to (choose from list RecentNames with prompt "Delete document(s):" OK button name "Delete" with multiple selections allowed)
on error
	--no recent items!!
	display dialog "No recent documents for \"Pages\" found!" buttons {"Cancel"} default button 1 with icon 0
end try
if (namesToCut is false) then
	error number -128 --User cancelled
else
	
	--trash version files?
	set trashversionfiles to false
	display dialog "Trash version file(s) too?" buttons {"No", "Yes"} default button 2 with icon 1
	set button_returned to button returned of the result
	if button_returned is "Yes" then
		--Yes
		set trashversionfiles to true
		
		--get recent file paths
		if (RecentBookmarks is not {}) then
			set accessRef to (open for access file ((path to temporary items as text) & "Bookmark.dat") with write permission)
			repeat with i from 1 to (count RecentBookmarks)
				try
					set eof accessRef to 0
					write item i of RecentBookmarks to accessRef
					set item i of RecentBookmarks to (read accessRef from 1 as «class bmrk») --aliases!!
				on error the error_message number the error_number
					display dialog "Error: " & the error_number & ". " & the error_message buttons {"OK"} default button 1 with icon 0
				end try
			end repeat
			close access accessRef
		end if
	end if
	
	--delete from plist
	my rewriteplist(defaultPrefsRecord) --replace plist with empty default, because "set value to {}" is FAILING in mavericks!!***
	tell application "System Events"
		tell property list file (prefsFile as text)
			tell property list item "RecentDocuments"
				tell property list item "CustomListItems"
					
					-- Recreate the items to keep from their plist texts.
					repeat with i from 1 to (count RecentNames)
						try
							if (item i of RecentNames is not in namesToCut) then
								make new property list item at end of property list items with properties {text:item i of RecentPlists}
							else
								if trashversionfiles is true then
									
									--trash version file
									set thisitem_version to (item i of RecentBookmarks) as text
									set thisitem_version to my searchAndReplace(thisitem_version, ".pages", "~.pages")
									tell application "Finder"
										if exists alias thisitem_version then
											delete alias thisitem_version
										end if
									end tell
								end if
							end if
						on error the error_message number the error_number
							display dialog "Error: " & the error_number & ". " & the error_message buttons {"OK"} default button 1 with icon 0
						end try
					end repeat
				end tell
			end tell
		end tell
	end tell
	
	--clear Finder recent items menu
	my menu_click({"Finder", "Apple", "Recent Items", "Clear Menu"})
	
	--done
	if iquitpages is true then
		set btn1 to "Done"
		set btn2 to "Open Pages"
	else
		set btn1 to "Open Pages"
		set btn2 to "Done"
	end if
	
	display dialog "Selected document(s) removed." buttons {btn1, btn2} default button 2 with icon 1
	set button_returned3 to button returned of the result
	
	--open Pages
	if button_returned3 is "Open Pages" then
		tell application "Pages" to activate
	end if
end if

--END

There are no longer application dedicated files. There is a global single one.
Look at :

set theRecents to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.RecentDocuments.sfl"

OOPS, I missed them.

set theRecentsNumbers to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple.iwork.numbers.sfl"

set theRecentsPagess to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple.iwork.pages.sfl"

It seems that they are also stored elsewhere because, after deleting these files, we may display a list of recent items of these applications from the dedicated Dock’s icons.

I would be glad to know which is this alternate location because I want to get rid of these lists.

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) samedi 11 février 2017 15:45:14

@Yann

Thanks for this!!

I’ve got this routine below, which lists paths to recent docs found in the .sfl file for Pages or Numbers. But it returns them in the format “file://localhost/Users/myusername/Documents/title.pages/” for docs in my Documents folder and “file://localhost/Volumes/externaldrive/title.pages/” for files on an external hard drive.

Any tips on how Finder can understand the reference?
I guess I could do a simple text substitution for the “file://localhost/” part of the string… But wondered if there was a better way.

By the way, I’m using this for a script that clears up the clutter of version files (the copies prefixed with “~”)!! So it determines the parent folder of recent docs and trashes those copies.


on readRecentDocs(someapp)
	
	set plistPath to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple.iwork." & someapp & ".sfl" --plist for iWork app
	
	--get plist items
	tell application "System Events"
		tell property list file plistPath
			if (it exists) then
				tell property list item "$objects"
					set n to count of property list items
					set myitems to {}
					
					--filter paths ONLY...
					repeat with i from 1 to n
						if (value of property list item i) contains "file://localhost/" then
							set myitems to myitems & value of property list item i
						end if
					end repeat
					
				end tell
			else
				set myitems to {}
			end if
		end tell
	end tell
	return myitems
end readRecentDocs


my readRecentDocs("Pages")

This script used to clear recent items from the Dock for Preview… but afraid it no longer works. I don’t know where Dock gets data.


--"Clear Recent Pictures" v1 by Alnoor July 2015
----------------------

--PROPERTIES:

global defaultPrefsRecord

property defaultPrefsRecord : {|RecentDocuments|:{|Controller|:"CustomListItems", |CustomListItems|:{}, |MaxAmount|:10}}

set path2home to (path to home folder)
set prefsFolder to ((path2home as text) & "Library:Preferences:") --as alias NB: NOT IN LINE WITH OTHER APPS***
set path2home_POSIX to POSIX path of (path to home folder from user domain)
set path2prefs_POSIX to POSIX path of (path to preferences)
set recentitems_defaultsName to "com.apple.recentitems"
set recentitems_plist_POSIX to path2prefs_POSIX & recentitems_defaultsName & ".plist"
set preview_defaultsName to "com.apple.Preview.LSSharedFileList"
set preview_plist to (prefsFolder & preview_defaultsName & ".plist")


--HANDLERS:

--REWRITE PLIST 2
on rewriteplist2(prefsFile, myRecord) --takes prefs file as alias (not posix) NB: NOT IN LINE WITH OTHER APPS!!***
	tell application "System Events"
		prefsFile as text
		set value of property list file the result to myRecord
	end tell
end rewriteplist2

--SCRIPT:

--welcome
display dialog "Clear Preview recent items?" with icon note

tell application "System Events" to get name of processes
set application_list to result
if (application_list contains "Preview" is true) then
	display dialog "\"Preview\" will quit!" buttons {"Continue"} default button 1 with icon 0
	--BRUTE FORCE QUIT***...
	tell application "Preview" to quit
	delay 2
end if

--remove plists... NB: try, incase doesn't exist
try
	do shell script "rm " & recentitems_plist_POSIX --recent items --WORKS ON LOG OUT???***
end try
try
	my rewriteplist2(preview_plist, defaultPrefsRecord) --rewrite plist to default
end try

--???
try
	do shell script "sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV* 'delete from LSQuarantineEvent'"
end try

try
	do shell script "killall Dock" --refresh dock --ALSO HAPPENS ON LOG OUT!***
end try


--done
display dialog "Full changes take effect on next log in." buttons {" Cancel", "Log Out", "Shut Down"} default button 3 with icon note
set button_returned to button returned of the result
if button_returned is "Log Out" then
	
	--Log Out
	tell application "System Events"
		log out
	end tell
else
	if button_returned is "Shut Down" then
		
		--Shut Down
		tell application "System Events"
			shut down
		end tell
	else
		
		--cancel
		display dialog "Recent menu items in the Finder may NOT be removed unless you log out now!" buttons {"OK"} default button 1 with icon 0
	end if
end if


--END

Bingo, I found the explanation.

The datas describing the recent items are cached - maybe in Ram - so, when I deleted them, the system recreated them.
I booted from an other HD, deleted the files from my standard boot volume and rebooted from this one.
This time, the recent items lists are empty everywhere, in the Dock dedicated menus as well as in applications dedicated ones.

I guess that they are cached in Ram because a search in files contents din’t found them. But maybe they aren’t stored in string format but in bookmark one (hexadecimal representation of the path).

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) samedi 11 février 2017 19:15:42

Oh OK! So RAM cache would override any modification you do to the file on restart/log out? Then no script would work to selectively nuke recent docs. You can only clear all manually via the app’s recent items menu.

Yes, data is definitely stored in Bookmark format, as per first script I posted.

What is really boring is the fact that when we clear the recent items list thru the dedicated menu item, it doesn’t clear the list displayed in the Dock.
Looking in the reverse order.
At this time, when I click on the Pages icon in the Dock, there is no recent item available but if I really open the app, there is one file listed in the recent menu.
I assumed that updating the list used by the Dock was not immediate but after five minutes, it’s always empty.

Yvan KOENIG running SIerra 10.12.3 in French (VALLAURIS, France) samedi 11 février 2017 20:45:00

Try Open Recent/Clear Menu in the menu bar.
Then Terminal command “killall Dock”

That removed both immediately for me.

Thanks.
It works most of the time but not always.
It fails with TextWrangler.

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) dimanche 12 février 2017 12:14:04

This is my solution for iWork apps:


(* ==== PROPERTIES ==== *)

global startupdisk


(* ==== HANDLERS ==== *)

--GET RECENT DOCS
on getRecentDocs(someapp)
	set plistPath to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple.iwork." & someapp & ".sfl"
	tell application "System Events"
		tell property list file plistPath
			if (it exists) then
				tell property list item "$objects"
					set n to count of property list items
					set these_RecentDocuments to {}
					repeat with i from 1 to n
						set this_item to value of property list item i
						--filter paths ONLY...
						if this_item contains "file://localhost/" then
							if this_item contains "file://localhost/Volumes" then
								set this_item to my replaceText("file://localhost/Volumes/", "", this_item) --for  external drives
							else
								set this_item to my replaceText("file://localhost", startupdisk, this_item)
							end if
							set this_item to my replaceText("%20", " ", this_item)
							set this_item to my replaceText("/", ":", this_item)
							set these_RecentDocuments to these_RecentDocuments & this_item
						end if
					end repeat
				end tell
			else
				set these_RecentDocuments to {}
			end if
		end tell
	end tell
	return these_RecentDocuments
end getRecentDocs

--REPLACE TEXT
on replaceText(find, replace, someText)
	set OldDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to find
	set someText to text items of someText
	set AppleScript's text item delimiters to replace
	set someText to "" & someText
	set AppleScript's text item delimiters to OldDelimiters
	return someText
end replaceText


(* ====  SCRIPT ==== *)

tell application "System Events" to set startupdisk to name of startup disk

my getRecentDocs("Numbers")

(* ====  END ==== *)


With which system are you running your script ?

Here, the menu of Numbers dedicated to recent items display one document but your script returns an empty list.

If I understand well, it’s because the entry storing the document path is :
→ “file:///Users/yvankoenig/Desktop/4lookup.numbers”

when you assume that it contains “file://localhost”.

This edited version return the existing files.


(* ==== PROPERTIES ==== *)

-- global startupdisk


(* ==== HANDLERS ==== *)

--GET RECENT DOCS
on getRecentDocs(someapp)
	
	set plistPath to (path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple.iwork." & someapp & ".sfl"
	tell application "System Events"
		tell property list file plistPath
			if (it exists) then
				tell property list item "$objects"
					set n to count of property list items
					set these_RecentDocuments to {}
					repeat with i from 1 to n
						set this_item to value of property list item i
						--filter paths ONLY...
						if this_item contains "file://" then
							set this_item to my replaceText({"file://localhost", "file://"}, "", this_item)
							# Now decipher some Unicode characters. I know that the list is far from complete.
							set this_item to my replaceText("%20", " ", this_item)
							considering case
								set this_item to my replaceText("a%CC%80", "Ã ", this_item) # a + COMBINING ACUTE ACCENT
								set this_item to my replaceText("A%CC%80", "À", this_item) # A + COMBINING ACUTE ACCENT
								set this_item to my replaceText("c%CC%A7", "ç", this_item) # c + COMBINING CEDILLA
								set this_item to my replaceText("C%CC%A7", "Ç", this_item) # C + COMBINING CEDILLA
								set this_item to my replaceText("e%CC%81", "é", this_item) # e + COMBINING ACUTE ACCENT
								set this_item to my replaceText("E%CC%81", "É", this_item) # E + COMBINING ACUTE ACCENT
								set this_item to my replaceText("e%CC%80", "è", this_item) # e + COMBINING GRAVE ACCENT
								set this_item to my replaceText("E%CC%80", "È", this_item) # E + COMBINING GRAVE ACCENT
								set this_item to my replaceText("u%CC%80", "ù", this_item) # u + COMBINING GRAVE ACCENT
								set this_item to my replaceText("U%CC%80", "Ù", this_item) # U + COMBINING GRAVE ACCENT
								set this_item to my replaceText("%C6%92", "Æ’", this_item) # UTF-8 $C692
								set this_item to my replaceText("%E2%80%A2", "¢", this_item) # UTF-8 $E280A2
								set this_item to my replaceText("%EF%A3%BF", "", this_item) # UTF-8 $EFA3BF
							end considering
							tell me to set this_item to POSIX file this_item
							set this_item to this_item as text # to store strings as you did
							set these_RecentDocuments to these_RecentDocuments & this_item
						end if
					end repeat
				end tell
			else
				set these_RecentDocuments to {}
			end if
		end tell
	end tell
	return these_RecentDocuments
end getRecentDocs

--REPLACE TEXT
on replaceText(find, replace, someText)
	set OldDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to find
	set someText to text items of someText
	set AppleScript's text item delimiters to replace
	set someText to someText as text
	set AppleScript's text item delimiters to OldDelimiters
	return someText
end replaceText


(* ====  SCRIPT ==== *)

-- tell application "System Events" to set startupdisk to name of startup disk

my getRecentDocs("Numbers")

(* ====  END ==== *)


Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) dimanche 12 février 2017 13:52:32

I’m running El Capitan 10.11.6

Hmmm… I always get “localhost” in my paths, even to Desktop.
But I’ll go with your suggestion so it’s more compatible. Thanks.

By the way, here’s the additional routine that will clean up version files from the list generated…


--CLEAN VERSION FILES
on autocleanversionfiles(someapp)
	set VersionFiles to {}
	set RecentDocuments to {}
	set RecentDocuments to RecentDocuments & my getRecentDocs(someapp)
	
	if (RecentDocuments is not {}) then
		try
			tell application "Finder"
				set RecentFolders to {}
				repeat with j from 1 to (count RecentDocuments)
					if exists item j of RecentDocuments then
						set this_folder to (container of alias (item j of RecentDocuments)) as text
						if this_folder is not in RecentFolders then
							set RecentFolders to RecentFolders & {this_folder}
						end if
					end if
				end repeat
				repeat with m from 1 to count of RecentFolders
					set these_files to every file of folder (item m of RecentFolders)
					repeat with k from 1 to count of these_files
						if ((name of item k of these_files) as text) contains "~.pages" or ((name of item k of these_files) as text) contains "~.numbers" or ((name of item k of these_files) as text) contains "~.key" then
							set VersionFiles to VersionFiles & {quoted form of POSIX path of ((item k of these_files) as text)}
						end if
					end repeat
				end repeat
			end tell
		end try
		repeat with p from 1 to count of VersionFiles
			try
				do shell script "rm -r " & item p of VersionFiles
			end try
		end repeat
	end if
end autocleanversionfiles

(1) If I asked it’s because, if my memory is right, the syntax changed with Sierra.
(2) I edited the script because I forgot to treat the case “file://Volumes/volName/.”

I corrected some other oddities.
You convert POSIX paths into Hfs ones by replacing slashes by colons.
(a)If the original name of the file contained a slash (which is perfectly legal) you will get a wrong result.
(b) if the path contained unicode characters, they aren’t correctly deciphered. Exemple:
→ “file:///Volumes/Macintosh%20HD/Users/Important/%C6%92%20banque/Che%CC%80ques%20YK%202016.numbers”
“%C6%92” is the character “Æ’”
“e%CC%80” is the character “è”

I apply the conversion with :

considering case
	set this_item to my replaceText("a%CC%80", "Ã ", this_item) # a + COMBINING ACUTE ACCENT
	set this_item to my replaceText("A%CC%80", "À", this_item) # A + COMBINING ACUTE ACCENT
	set this_item to my replaceText("c%CC%A7", "ç", this_item) # c + COMBINING CEDILLA
	set this_item to my replaceText("C%CC%A7", "Ç", this_item) # C + COMBINING CEDILLA
	set this_item to my replaceText("e%CC%81", "é", this_item) # e + COMBINING ACUTE ACCENT
	set this_item to my replaceText("E%CC%81", "É", this_item) # E + COMBINING ACUTE ACCENT
	set this_item to my replaceText("e%CC%80", "è", this_item) # e + COMBINING GRAVE ACCENT
	set this_item to my replaceText("E%CC%80", "È", this_item) # E + COMBINING GRAVE ACCENT
	set this_item to my replaceText("u%CC%80", "ù", this_item) # u + COMBINING GRAVE ACCENT
	set this_item to my replaceText("U%CC%80", "Ù", this_item) # U + COMBINING GRAVE ACCENT
	set this_item to my replaceText("%C6%92", "Æ’", this_item) # UTF-8 $C692
	set this_item to my replaceText("%E2%80%A2", "¢", this_item) # UTF-8 $E280A2
	set this_item to my replaceText("%EF%A3%BF", "", this_item) # UTF-8 $EFA3BF
end considering

I know that the list of characters treated is far from complete.
I hope that somebody, maybe Shane STANLEY, is aware of an ASObjC feature allowing a more complete conversion because on my side I really don’t know if the used encoding is a documented one.

EDIT. My memory was right.
I looked the file storing the list of recent items on a dish which I used under 10.11.6
The strings defining the paths really start with “file://localhost/”
Same thing under 10.10.
Under 10.10, 10.11 and 10.12 the descriptors are strings stored in a .sfl file.
Under 10.9.5, they are bookmarks stored in a .plist file.

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) dimanche 12 février 2017 15:52:40

I’m always a bit wary of messing with files like these because (a) the formats can change at any time (that’s one reason they’re private), and (b) because once people start trying to modify them there’s a risk of all sorts of problems.

But to answer Yvan’s question about percent encoding:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set aString to current application's NSString's stringWithString:"file:///Users/yvankoenig/Desktop/ae%CC%81%20bc%CC%A7%20da%CC%80%20eu%CC%80%20f%E2%80%A2%20g%EF%A3%BF%20hE%CC%81%20IE%CC%80%20JC%CC%A7%20KA%CC%80%20LU%CC%80.numbers"
set aString to aString's stringByRemovingPercentEncoding() as text

And if you’re bothering with ASObjC and you want a list of the folders used by recent documents, you could try something like this:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set theData to the current application's NSData's dataWithContentsOfFile:"/Users/shane/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.apple.scripteditor2.sfl" -- change to suit
set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:theData options:(current application's NSPropertyListMutableContainersAndLeaves) |format|:(missing value) |error|:(reference)
if theDict = missing value then error (theError's localizedDescription() as text)
set theValues to (theDict's valueForKey:"$objects")
set thePred to current application's NSPredicate's predicateWithFormat:"(self isKindOfClass: %@) AND (self BEGINSWITH %@)" argumentArray:{current application's NSString's |class|(), "file:///"}
set theFiles to (theValues's filteredArrayUsingPredicate:thePred)
set theSet to current application's NSMutableSet's |set|()
repeat with aFile in theFiles
	(theSet's addObject:((current application's |NSURL|'s URLWithString:aFile)'s |path|()'s stringByDeletingLastPathComponent()))
end repeat
-- remove duplicate entries
set theFolders to theSet's allObjects() as list -- returns POSIX paths

Edited to improve predicate, fix key path and remove duplicate folder paths

Edited to remove leading file:/

Thank you Shane.
Once more I learnt something.
I’m always astonished by your knowledge of these “wonderful” function’s names.

Yvan KOENIG running Sierra 10.12.3 in French (VALLAURIS, France) lundi 13 février 2017 11:01:41