X-devonthink-item link from PDF Expert (with page number)

I frequently find myself copying links to specific pages of documents to include them in my notes. This way I can easily access the relevant passages on later review.

Though, I prefer to annotate with PDF Expert. This often entails finding the pdf in dt3, going to the corresponding page and copying the item link in order to get the referencing URL. I’d like to evade those steps.

While working on a solution I came across this thread: Script: Open DEVONthink record for PDF Expert tab. Thanks @pete31 for this! Makes for a feasible result. Here’s my derivative script:

    -- Get the x-devonthink-item URL for the active document's page in PDF Expert
    --
    -- Via "Open DEVONthink record for current PDF Expert tab"
    -- https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
    -- solution for umlauts ("iconv -t UTF8-MAC") found here: https://stackoverflow.com/questions/23219482/bash-ps-grep-for-process-with-umlaut-os-x/23226449#23226449

    tell application "System Events"
    	activate application "PDF Expert"
    	delay 1
    	tell process "PDF Expert"
    		click menu item "Gehe zu Seite..." of menu 1 of menu bar item "Gehe zu" of menu bar 1
    	end tell
    	tell application "System Events" to keystroke "c" using {command down}
    	delay 1
    	set pageNum to the clipboard
    	tell application "System Events"
    		key code 53
    		-- esc
    	end tell
    	delay 1
    	try
    		tell application process "PDF Expert"
    			tell window 1
    				set theFileName to value of attribute "AXTitle" & ".pdf"
    				if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
    			end tell
    			set PID to unix id
    		end tell
    	on error error_message number error_number
    		if the error_number is not -128 then
    			display alert "System Events" & space & error_number message error_message as warning
    			return
    		end if
    	end try
    end tell

    set theOpenFiles to (do shell script "lsof -p " & PID)
    set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
    set theLines to paragraphs of convertedOutput

    repeat with thisLine in theLines
    	if thisLine contains theFileName then
    		set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
    		exit repeat
    	end if
    end repeat

    tell application id "DNtp"
    	try
    		set theDatabases to databases
    		set theResults to {}
    		repeat with thisDatabase in theDatabases
    			set thisDatabasesResults to lookup records with path thePath in thisDatabase
    			set theResults to theResults & thisDatabasesResults
    		end repeat
    		if theResults = {} then
    			display notification "Dokumentpfad nicht in geöffneten Datenbanken enthalten!" with title "Lookup DEVONthink"
    			return
    		else
    			set theUUID to uuid of (item 1 of theResults)
    			set callback to "x-devonthink-item://" & theUUID & "?page=" & pageNum
    			set the clipboard to callback
    			display notification callback with title ("x-devonthink-item URL")
    		end if
    		
    	on error error_message number error_number
    		if the error_number is not -128 then
    			display alert "DEVONthink Pro" & space & error_number message error_message as warning
    			return
    		end if
    	end try
    end tell
3 Likes

Alfred Workflow:

2 Likes

I like the idea, but when you get the page with this method via PDF Expert, sometimes you’ll get the printed page number, but not the actual page DT3 needs for the link.

Just now I tested it on a PDF. The printed page number (and the number displayed by PDF Expert and DT3) was 47. But when I use the copy page URL command in DT3, the page number was 57. This because the pdf is not computing the pages numbered i to x.

This here will work with Adobe Acrobat and with Skim.


set thePage to ""
set thePath to ""
set front_app to (path to frontmost application as Unicode text)


if front_app contains "Acrobat" then
	tell application "Adobe Acrobat"
		--    activate
		set theDoc to item 1 of PDF Windows
		set thePage to get page number of theDoc
		set thePage to thePage - 1
		set thePath to file alias of front document
		set thePath to POSIX path of thePath
		
	end tell
end if
if front_app contains "Skim" then
	tell application "Skim"
		--    activate
		set theDoc to front document
		set thePage to get index for current page of theDoc
		set thePage to thePage - 1
		set thePath to the path of theDoc
	end tell
end if
if thePage is not "" then
	tell application id "DNtp"
		try
			set theDatabases to databases
			set theResults to {}
			repeat with thisDatabase in theDatabases
				set thisDatabasesResults to lookup records with path thePath in thisDatabase
				set theResults to theResults & thisDatabasesResults
			end repeat
			if theResults = {} then
				display notification "Document could not be found in open databases" with title "Lookup DEVONthink"
				return
			else
				set theUUID to uuid of (item 1 of theResults)
				set callback to "x-devonthink-item://" & theUUID & "?page=" & thePage
				set the clipboard to callback
				display notification callback with title ("x-devonthink-item URL")
			end if
			
		on error error_message number error_number
			if the error_number is not -128 then
				display alert "DEVONthink Pro" & space & error_number message error_message as warning
				return
			end if
		end try
	end tell
end if

1 Like

Hey Bernardo,

thanks for pointing that out! While for most cases that won’t cause a problem for me, it would be nice to have a workaround. Just in case.

I observed something: Assume you have a document with pages i-v followed by pages 1-10: A pair of two ranges <i-v,1-10>, say the curated page count, at 15 pages total, say the actual page count.

If I run the script on the document viewed in PDF Expert on page 6 (of the curated page count), the reference URL will bring me to page 1 (curated) in dt3 or page 6 (actual) respectively, just as you pointed out (the desired position in the actual page count’s range is 11). Now the observation: If I click on the current page/ page count indicator[1] in dt3 and enter ‘6’, it brings me to the correct (curated) page. Dt3 handles the alphanumerical mix just fine. If I’m in the range <i-v> the indicator also displays, e.g. “iv”.

So, the record must have the relevant information as one of its properties, right? If we had access to it, then there was a workaround. What we can access however, as far as I can tell, is just this:

page count (integer, r/o) : The page count of a record. Currently only supported by PDF documents.

With information about both sides of the pair we could simply add the left range to the current page position (if >= 1) and had the correct reference URL. Could this be done, developers?


  1. Bildschirmfoto 2020-05-14 um 21.05.33 ↩︎

I found a different workaround:

-- Get the x-devonthink-item URL for the active document's page in PDF Expert v.2
--
-- Via "Open DEVONthink record for current PDF Expert tab"
-- https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
-- solution for umlauts ("iconv -t UTF8-MAC") found here: https://stackoverflow.com/questions/23219482/bash-ps-grep-for-process-with-umlaut-os-x/23226449#23226449

tell application "System Events"
	activate application "PDF Expert"
	delay 1
	tell process "PDF Expert"
		click menu item "Gehe zu Seite..." of menu 1 of menu bar item "Gehe zu" of menu bar 1
	end tell
	tell application "System Events" to keystroke "c" using {command down}
	delay 1
	set pageNum to the clipboard
	tell application "System Events"
		key code 53
		-- esc
	end tell
	delay 1
	
	tell process "PDF Expert"
		click menu item "Letzte Seite" of menu 1 of menu bar item "Gehe zu" of menu bar 1
		delay 1
		click menu item "Gehe zu Seite..." of menu 1 of menu bar item "Gehe zu" of menu bar 1
	end tell
	tell application "System Events" to keystroke "c" using {command down}
	delay 1
	set lastPageCurated to the clipboard
	set the clipboard to pageNum
	tell application "System Events" to keystroke "v" using {command down}
	tell application "System Events"
		key code 36
		-- esc
	end tell
	delay 1
	
	try
		tell application process "PDF Expert"
			tell window 1
				set theFileName to value of attribute "AXTitle" & ".pdf"
				if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
			end tell
			set PID to unix id
		end tell
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "System Events" & space & error_number message error_message as warning
			return
		end if
	end try
end tell

set theOpenFiles to (do shell script "lsof -p " & PID)
set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
set theLines to paragraphs of convertedOutput

repeat with thisLine in theLines
	if thisLine contains theFileName then
		set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
		exit repeat
	end if
end repeat

tell application id "DNtp"
	try
		set theDatabases to databases
		set theResults to {}
		repeat with thisDatabase in theDatabases
			set thisDatabasesResults to lookup records with path thePath in thisDatabase
			set theResults to theResults & thisDatabasesResults
		end repeat
		if theResults = {} then
			display notification "Dokumentpfad nicht in geöffneten Datenbanken enthalten!" with title "Lookup DEVONthink"
			return
		else
			set theUUID to uuid of (item 1 of theResults)
			set totalPageCount to page count of (item 1 of theResults)
			
			if lastPageCurated is not equal to totalPageCount then
				set alphaNum to (totalPageCount - lastPageCurated)
				set pageNum to (pageNum + alphaNum)
			end if
			
			set callback to "x-devonthink-item://" & theUUID & "?page=" & (pageNum - 1)
			set the clipboard to callback
			display notification callback with title ("URL copied to clipboard:")
		end if
		
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "DEVONthink Pro" & space & error_number message error_message as warning
			return
		end if
	end try
end tell

Btw. the x-devonthink-item URL is zero-based on purpose, right (and will always stay that way)?

1 Like

Yes, the page parameter in an item link is zero-based.

Updated language agnostic version:

-- Get the x-devonthink-item:// URL for the active document's page viewed in PDF Expert
-- Credit: https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
-- Solution for umlauts ("iconv -t UTF8-MAC"): https://stackoverflow.com/a/23226449/7625403

tell application "System Events"
	activate application "PDF Expert"
	delay 0.5
	tell process "PDF Expert" to keystroke "g" using {command down, option down} -- open go to page modal
	tell application "System Events" to keystroke "c" using command down
	delay 0.5
	set pageNum to the clipboard
	tell application "System Events" to key code 53 -- esc
	tell application "System Events" to key code 125 using command down -- ⌘↓
	tell process "PDF Expert" to keystroke "g" using {command down, option down} -- open go to page modal
	tell application "System Events" to keystroke "c" using command down -- copy the page number		
	delay 0.5
	set lastPageCurated to the clipboard
	set the clipboard to pageNum
	tell application "System Events" to keystroke "v" using command down -- go back to the original page
	tell application "System Events" to key code 36 -- return / enter
	delay 0.5
	
	try
		tell application process "PDF Expert"
			tell window 1
				set theFileName to value of attribute "AXTitle" & ".pdf"
				if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
			end tell
			set PID to unix id
		end tell
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "System Events" & space & error_number message error_message as warning
			return
		end if
	end try
end tell

set theOpenFiles to (do shell script "lsof -p " & PID)
set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
set theLines to paragraphs of convertedOutput

repeat with thisLine in theLines
	if thisLine contains theFileName then
		set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
		exit repeat
	end if
end repeat

tell application id "DNtp"
	try
		set theDatabases to databases
		set theResults to {}
		repeat with thisDatabase in theDatabases
			set thisDatabasesResults to lookup records with path thePath in thisDatabase
			set theResults to theResults & thisDatabasesResults
		end repeat
		if theResults = {} then
			display notification "Document path not found in open databases!" with title "Lookup DEVONthink"
			return
		else
			set theUUID to uuid of (item 1 of theResults)
			set totalPageCount to page count of (item 1 of theResults)
			
			if lastPageCurated is not equal to totalPageCount then
				set alphaNum to (totalPageCount - lastPageCurated)
				set pageNum to (pageNum + alphaNum)
			end if
			
			set callback to "x-devonthink-item://" & theUUID & "?page=" & (pageNum - 1)
			set the clipboard to callback
		end if
		
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "DEVONthink 3" & space & error_number message error_message as warning
			return
		end if
	end try
end tell
2 Likes

Thanks for your work! @zeitlings

Sometimes the page number is the roman number like iv, instead of the arabic number, DEVONthink would prompt the warning message:

DEVONthink 3 -1700

Can’t make "iv" into type number.

How to resolve the issue? Thanks!

Hey @TomBen!

Indeed, this is a known problem for which I have no workaround. Even if I convert the roman number to decimal, the end of the preface enumerated with roman numbers remains unknown (and the difference would have to be subtracted from the total page count).

Btw., the Alfred Workflow now has a proper home with additional info, including the known issues:

Thanks for your reply! @zeitlings

I’ve tried to create a Keyboard Maestro macro to make it as a workaround.

Where the AppleScript used was derived from Script: Open DEVONthink record for PDF Expert tab:

-- Copy the page number in PDF Expert
tell application "System Events"
	tell process "PDF Expert" to keystroke "g" using {command down, option down} -- open go to page modal
	tell application "System Events" to keystroke "c" using command down
	delay 0.5
	tell application "System Events" to key code 53 -- esc
end tell

-- Open DEVONthink record for current PDF Expert tab
-- Source: https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
tell application "System Events"
	try
		tell application process "PDF Expert"
			tell window 1
				set theFileName to value of attribute "AXTitle" & ".pdf"
				if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
			end tell
			set PID to unix id
		end tell
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "System Events" & space & error_number message error_message as warning
			return
		end if
	end try
end tell

set theOpenFiles to (do shell script "lsof -p " & PID)

set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)

set theLines to paragraphs of convertedOutput

repeat with thisLine in theLines
	if thisLine contains theFileName then
		set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
		exit repeat
	end if
end repeat

tell application id "DNtp"
	try
		set theDatabases to databases
		set theResults to {}
		repeat with thisDatabase in theDatabases
			set thisDatabasesResults to lookup records with path thePath in thisDatabase
			set theResults to theResults & thisDatabasesResults
		end repeat
		if theResults = {} then
			display notification "Pfad nicht in offenen Datenbanken enthalten!" with title "Lookup DEVONthink"
			return
		else
			open window for record (item 1 of theResults)
			activate
		end if
		
	on error error_message number error_number
		if the error_number is not -128 then
			display alert "DEVONthink Pro" & space & error_number message error_message as warning
			return
		end if
	end try
end tell

The issue of this way is that the DEVONthink window would be open, but it doesn’t matter in most cases.

Hope it helps!

Turns out there is a solution to the problem after all :hugs: Thanks @chrillek for the tip!

@TomBen, there is an updated version of the Alfred workflow on Github if you want to check it out.

And here is the updated script:

-- Part of the DEVONthink ↔ PDF Expert workflow for Alfred
-- https://github.com/zeitlings/alfred-workflows#14-devonthink--pdf-expert
--
-- Get the DEVONthink reference URL for the page viewed in PDF Expert
-- ===================================================================
-- Credits
--  - pete31: https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
--  - Umlaut fix: https://stackoverflow.com/a/23226449/7625403

use AppleScript version "2.4"
use framework "PDFKit"
use scripting additions

property NSURL : a reference to current application's NSURL
property NSString : a reference to current application's NSString
property PDFDocument : a reference to current application's PDFDocument

on run argv
	tell application "System Events"
		activate application "PDF Expert"
		delay 0.5
		tell process "PDF Expert" to keystroke "g" using {command down, option down} -- go to 
		tell application "System Events" to keystroke "c" using command down
		delay 0.5
		tell application "System Events" to key code 53 -- esc
		set theActiveLabel to the clipboard
		try
			tell application process "PDF Expert"
				tell window 1
					set theFileName to value of attribute "AXTitle" & ".pdf"
					if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
				end tell
				set PID to unix id
			end tell
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
	
	set thePath to my recoveredPath(theFileName, PID)
	
	set theURL to NSURL's fileURLWithPath:(NSString's stringWithString:thePath)
	set pdf to PDFDocument's alloc()'s initWithURL:theURL
	set theCount to pdf's pageCount()
	set thePageIndex to -1
	set cur to 0
	
	repeat while cur < theCount
		set thePage to pdf's pageAtIndex:cur
		set theLabel to thePage's label()
		if theLabel ≠ missing value then
			if (theLabel as text) is theActiveLabel then
				set thePageIndex to cur
				exit repeat
			end if
		end if
		set cur to cur + 1
	end repeat
	
	if thePageIndex is equal to -1 then
		return "Unable to recover Page Index"
	end if
	
	set pageLink to my referenceURL(thePath, thePageIndex)
	try
		set the clipboard to pageLink
		return pageLink
	on error number -2753 -- not defined
		return "Document not found in any open Database"
	end try
	
end run

on recoveredPath(theFileName, PID)
	set theOpenFiles to (do shell script "lsof -p " & PID)
	set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
	set theLines to paragraphs of convertedOutput
	repeat with thisLine in theLines
		if thisLine contains theFileName then
			set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
			return thePath
		end if
	end repeat
end recoveredPath

on referenceURL(thePath, thePageIndex)
	tell application id "DNtp"
		try
			set theDatabases to databases
			set theResults to {}
			repeat with thisDatabase in theDatabases
				set dbResults to lookup records with path thePath in thisDatabase
				set theResults to theResults & dbResults
			end repeat
			if theResults = {} then
				return
			else
				set theUUID to uuid of (item 1 of theResults)
				set referenceURL to "x-devonthink-item://" & theUUID & "?page=" & (thePageIndex as text)
				return referenceURL
			end if
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
end referenceURL

Great! Thanks @zeitlings

1 Like

DEVONthink 3.9 introduced deep links to select text and PDF annotations. So is it possible to copy DEVONthink’s annotation link in PDF Expert directly? Although it seems quite cumbersome.

Only DEVONthink (or scripts) can create & copy these links.

:sunglasses:

The updated workflow: DEVONthink ↔ PDF Expert

(*
  Part of the DEVONthink ↔ PDF Expert workflow for Alfred v2.1.0
  https://github.com/zeitlings/alfred-workflows#14-devonthink--pdf-expert

   Get the DEVONthink reference URL for the page viewed in PDF Expert
  ====================================================================
  Credits
   - pete31: https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
   - Umlaut fix: https://stackoverflow.com/a/23226449/7625403
*)

use AppleScript version "2.4"
use framework "PDFKit"
use scripting additions

property NSURL : a reference to current application's NSURL
property NSString : a reference to current application's NSString
property PDFDocument : a reference to current application's PDFDocument

on run argv
	tell application "System Events"
		set bufferedClipboard to the clipboard
		activate application "PDF Expert"
		delay 0.5
		tell process "PDF Expert" to keystroke "c" using {command down}
		delay 0.2
		set perhapsSelection to the clipboard as text
		tell process "PDF Expert" to keystroke "g" using {command down, option down} -- go to 
		tell application "System Events" to keystroke "c" using command down
		delay 0.5
		tell application "System Events" to key code 53 -- esc
		set theActiveLabel to the the clipboard
		try
			tell application process "PDF Expert"
				tell window 1
					set theFileName to value of attribute "AXTitle" & ".pdf"
					if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
				end tell
				set PID to unix id
			end tell
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
	
	set thePath to my recoveredPath(theFileName, PID)
	
	set theURL to NSURL's fileURLWithPath:(NSString's stringWithString:thePath)
	set pdf to PDFDocument's alloc()'s initWithURL:theURL
	set theCount to pdf's pageCount()
	set thePageIndex to -1
	set cur to 0
	
	repeat while cur < theCount
		set thePage to pdf's pageAtIndex:cur
		set theLabel to thePage's label()
		if theLabel ≠ missing value then
			if (theLabel as text) is theActiveLabel then
				set thePageIndex to cur
				exit repeat
			end if
		end if
		set cur to cur + 1
	end repeat
	
	if thePageIndex is equal to -1 then
		set the cliboard to bufferedClipboard
		return "Unable to recover Page Index"
	end if
	
	set pageLink to my referenceURL(thePath, thePageIndex)
	try
		-- Build page selection
		set thePage to pdf's pageAtIndex:thePageIndex
		set theText to thePage's |string|()
		set theRange to theText's rangeOfString:perhapsSelection
		if theRange's |length|() ≠ 0 then
			set theStart to theRange's location() as text
			set theLength to theRange's |length|() as text
			set theSelection to NSString's stringWithString:perhapsSelection
			set theSearchString to theSelection's stringByAddingPercentEncodingWithAllowedCharacters:(current application's NSCharacterSet's URLQueryAllowedCharacterSet())
			set pageSelectionLink to pageLink & "&start=" & theStart & "&length=" & theLength & "&search=" & theSearchString
			set the clipboard to pageSelectionLink
			return pageSelectionLink
		end if
		
		set the clipboard to pageLink
		return pageLink
	on error number -2753 -- not defined
		return "Document not found in any open Database"
	end try
	
end run

on recoveredPath(theFileName, PID)
	set theOpenFiles to (do shell script "lsof -p " & PID)
	set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
	set theLines to paragraphs of convertedOutput
	repeat with thisLine in theLines
		if thisLine contains theFileName then
			set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
			return thePath
		end if
	end repeat
end recoveredPath

on referenceURL(thePath, thePageIndex)
	tell application id "DNtp"
		try
			set theDatabases to databases
			set theResults to {}
			repeat with thisDatabase in theDatabases
				set dbResults to lookup records with path thePath in thisDatabase
				set theResults to theResults & dbResults
			end repeat
			if theResults = {} then
				return
			else
				set theUUID to uuid of (item 1 of theResults)
				set referenceURL to "x-devonthink-item://" & theUUID & "?page=" & (thePageIndex as text)
				return referenceURL
			end if
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
end referenceURL
1 Like

The script now also supports annotation links.

To get the full annotation link, your text selection just needs to overlap it. I anticipate poor performance for very large PDFs and in cases where the selected text occurs quite often in the document. (Because of the pdf's findString:textSelection withOptions:3). For these cases, the annotation check can be disabled in the (alfred) configuration.

p.s. There really is no way to call CGRectIntersectsRect from AppleScript, or any other ObjC method for that matter, right? :sob:

(*
  Part of the DEVONthink ↔ PDF Expert workflow for Alfred v2.1.1
  https://github.com/zeitlings/alfred-workflows#14-devonthink--pdf-expert

  Get the DEVONthink page link, selection link, or annotation link for the page viewed in PDF Expert
  ====================================================================
  Credits
   - pete31: https://discourse.devontechnologies.com/t/script-open-devonthink-record-for-pdf-expert-tab/47243
   - Umlaut fix: https://stackoverflow.com/a/23226449/7625403
*)

use AppleScript version "2.4"
use framework "PDFKit"
use scripting additions

property NSURL : a reference to current application's NSURL
property NSString : a reference to current application's NSString
property PDFDocument : a reference to current application's PDFDocument

on run argv
	set checkForAnnotations to (item 1 of argv as integer) as boolean
	tell application "System Events"
		set bufferedClipboard to the clipboard
		activate application "PDF Expert"
		delay 0.5
		tell process "PDF Expert" to keystroke "c" using {command down}
		delay 0.2
		set perhapsSelection to the clipboard as text
		tell process "PDF Expert" to keystroke "g" using {command down, option down} -- go to 
		tell application "System Events" to keystroke "c" using command down
		delay 0.5
		tell application "System Events" to key code 53 -- esc
		set theActiveLabel to the clipboard
		try
			tell application process "PDF Expert"
				tell window 1
					set theFileName to value of attribute "AXTitle" & ".pdf"
					if theFileName starts with "* " then set theFileName to characters 3 thru -1 in theFileName as string
				end tell
				set PID to unix id
			end tell
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
	
	set thePath to my recoveredPath(theFileName, PID)
	set theURL to NSURL's fileURLWithPath:(NSString's stringWithString:thePath)
	set pdf to PDFDocument's alloc()'s initWithURL:theURL
	set theCount to pdf's pageCount()
	set thePageIndex to -1
	set cur to 0
	
	repeat while cur < theCount
		set thePage to pdf's pageAtIndex:cur
		set theLabel to thePage's label()
		if theLabel ≠ missing value then
			if (theLabel as text) is theActiveLabel then
				set thePageIndex to cur
				exit repeat
			end if
		end if
		set cur to cur + 1
	end repeat
	
	if thePageIndex is equal to -1 then
		set the cliboard to bufferedClipboard
		return "Unable to recover Page Index"
	end if
	
	set pageLink to my referenceURL(thePath, thePageIndex)
	try
		set thePage to pdf's pageAtIndex:thePageIndex
		-- ------------------------------------- --
		-- Check for annotation intersection --
		-- ------------------------------------- --
		if checkForAnnotations then
			set theSelections to pdf's findString:perhapsSelection withOptions:3
			repeat with thisSelection in theSelections
				if (thisSelection's pages() as list) contains thePage then
					set theBounds to (thisSelection's boundsForPage:thePage)
					set theOrigin to item 1 of theBounds
					set theSize to item 2 of theBounds
					set coorX to item 1 of theOrigin
					set coorY to item 2 of theOrigin
					set width to item 1 of theSize
					set height to item 2 of theSize
					
					repeat with thisAnnotation in thePage's annotations()
						set annotationBounds to thisAnnotation's |bounds|()
						set annotationOrigin to item 1 of annotationBounds
						set annotationSize to item 2 of annotationBounds
						set annoX to item 1 of annotationOrigin
						set annoY to item 2 of annotationOrigin
						set annoWidth to item 1 of annotationSize
						set annoHeight to item 2 of annotationSize
						
						set a to (coorX < (annoX + annoWidth))
						set b to ((coorX + width) > annoX)
						set c to (coorY < (annoY + annoHeight))
						set d to ((coorY + height) > annoY)
						set isIntersecting to a and b and c and d
						
						if isIntersecting then
							set theType to thisAnnotation's type() as text -- possibly nil
							if theType is missing value then
								set theType to "Highlight"
							end if
							set x to (round annoX rounding as taught in school) as text -- ....
							set y to (round annoY rounding as taught in school) as text
							set deepAnnotationLink to (pageLink as text) & "&annotation=" & theType & "&x=" & x & "&y=" & y
							
							set the clipboard to deepAnnotationLink
							return deepAnnotationLink
						end if
					end repeat
				end if
			end repeat
		end if
		-- ------------------------------ --
		-- Reconstruct page selection --
		-- ------------------------------ --
		set theText to thePage's |string|()
		set theRange to theText's rangeOfString:perhapsSelection
		if theRange's |length|() ≠ 0 then
			set theStart to theRange's location() as text
			set theLength to theRange's |length|() as text
			set theSelection to NSString's stringWithString:perhapsSelection
			set theSearchString to theSelection's stringByAddingPercentEncodingWithAllowedCharacters:(current application's NSCharacterSet's URLQueryAllowedCharacterSet())
			set deepSelectionLink to pageLink & "&start=" & theStart & "&length=" & theLength & "&search=" & theSearchString
			set the clipboard to deepSelectionLink
			return deepSelectionLink
		end if
		
		set the clipboard to pageLink
		return pageLink
	on error number -2753 -- not defined
		set the cliboard to bufferedClipboard
		return "Document not found in any open Database"
	end try
end run

on recoveredPath(theFileName, PID)
	set theOpenFiles to (do shell script "lsof -p " & PID)
	set convertedOutput to (do shell script "iconv -t UTF8-MAC <<<$" & quoted form of theOpenFiles)
	set theLines to paragraphs of convertedOutput
	repeat with thisLine in theLines
		if thisLine contains theFileName then
			set thePath to characters (offset of "/" in thisLine) thru -1 in thisLine as string
			return thePath
		end if
	end repeat
end recoveredPath

on referenceURL(thePath, thePageIndex)
	tell application id "DNtp"
		try
			set theDatabases to databases
			set theResults to {}
			repeat with thisDatabase in theDatabases
				set dbResults to lookup records with path thePath in thisDatabase
				set theResults to theResults & dbResults
			end repeat
			if theResults = {} then
				return
			else
				set theUUID to uuid of (item 1 of theResults)
				set referenceURL to "x-devonthink-item://" & theUUID & "?page=" & (thePageIndex as text)
				return referenceURL
			end if
		on error error_message number error_number
			return "Error with code " & (error_number as text) & "	" & (error_message as text)
		end try
	end tell
end referenceURL

Why did it report error: Execute an AppleScript failed with script error: text-script:837:844: execution error: Can’t make item 1 of {} into type integer. (-1700) when executing the AppleScript in Keyboard Maestro? However, the Alfred workflow works as expected.

1 Like

Keyboard Maestro uses a pristine vanilla shell, rather than importing the ENV of Terminal.app, which has custom search paths defined in it.

(Alfred, in contrast, chooses to use the Terminal.app environment)

For use in KM, you would need to expand iconv to the full path on your system, which might, for example, depending on macOS version, be something like:

/usr/bin/iconv

but in any case, you should check first, using the Terminal command line:

which iconv
1 Like

Thank you. I followed your instruction to specify the full path of iconv, but it still encountered errors:

2023-04-05 21:11:03 Execute macro “Copy DEVONthink Annotation Link in PDF Expert” from trigger The Hot Key ⌥⇧L is pressed
2023-04-05 21:11:03 Action 12390747 failed: Execute an AppleScript failed with script error: text-script:837:844: execution error: Can’t make item 1 of {} into type integer. (-1700)
2023-04-05 21:11:03 Execute an AppleScript failed with script error: text-script:837:844: execution error: Can’t make item 1 of {} into type integer. (-1700). Macro “Copy DEVONthink Annotation Link in PDF Expert” cancelled (while executing Execute AppleScript).

Could you help to diagnose it?