Get name and x-callback-url of all items of a specific type

Hey,

I’m looking for a way to index all my pdf-files by name and their respective x-callback-url ID to subsequently have them returned togehter in a single text-file. The name should be extracted, modified and linked to its ID, i.e. the x-callback-url without the prefix x-devonthink-item://. My aniticipated outcome is a JSON dictionary with the modified file-name as key and its ID as value.

Consider files like Mohanty, J.N. 1996 - Kant and Husserl.pdf or Höffe, Otfried 2004 - Kants Kritik der reinen Vernunft.pdf. My desired outcome would be:

{
  "MohantyJN1996": "72EDC0D0-8D8C-47DA-9251-B787992524A0",
  "HoeffeOtfried2004": "2D184D0C-DBE0-427E-981F-D8E3E5629104"
}

So the idea with the modification is to get rid of all white space, special characters and to replace umlauts while discarding everything after and including the -, given that I’ve named all my files in that manner.

How would I achieve that?

I’m curious why you want a JSON text file. Also, you realize this would be a static document, ie. it’s not going to update as you add or remove elements from your database, correct?

Thanks for your reply.

As to why I want it, I have “written” a siri shortcut that allows me to get the x-devonthink-item:// url for any pdf edited in a third-party editor like PDF Viewer or PDF Expert, including the page number. Thus circumventing the need for costly subscriptions for this single purpose or compensating for the continuous ignorance towards x-callback-url fucntionality altogether (PDF Expert).

This makes referencing far more efficient when I’m not on MacOS while being able to continue my work without searching for the manually transcribed references (like quotes) that I would have made otherwise – namely, due the extracted urls that also work with DTPO once I’m back on my computer. This is one of the aspects I highly appreciate about your app(s)!

Anyways, for this purpose I use a JSON file containing the information as I specified above. The method already works, I’m just looking for a way to automate the indexing procedure. If it’s not possible, I’d ad the entries manually as I pick up the documents I work with.

I do. But, what would be the problem with running the script again, thereby replacing the old dictionary and have the new elements included in or removed from the new one?

If there’s interest I can share the workflow – it’s really improved my DTTG experience.

You don’t need to manipulate reference URL, just use uuid. A snippet (without your JSON encoding, which you can add yourself):

tell application id "DNtp"
	set theIndex to ""
	set theSelection to the selection
	repeat with thisItem in theSelection
		set theIndex to theIndex & "\"" & (name of thisItem) & "\"; " & "\"" & (uuid of thisItem) & "\"" & return
	end repeat
end tell

If the documents you are “indexing” that reside inside a group, then you can attached the script to the group as a triggered script, so that whenever you select the parent, the “index” is updated.

(For other readers, the OP is using “index” in the “table of contents” sense, and not in the sense normally associated with a DEVONthink database.)

1 Like

Would you be willing to share your shortcut?

I am about to “publish”/share a iOS shortcut I wrote to create a markdown note in DT and then an OmniFocus task linking to the DT document and then updating the markdown in DT with the link to the OmniFocus task using the URL setting.

This helped me quite a bit to get started, thanks!

However, I’ve run into some problems. Building on your example, I assumed I could continue somehow like below. But first of all I want to say: wow, this is tear-jerkingly convoluted with applescript. I’ve never really concerned myself with it though. So please excuse my asking this possibly easy to solve question.

tell application id "DNtp"
	set theIndex to ""
	set theParsedName to ""
	set theSelection to the selection
	repeat with thisItem in theSelection
		
		-- here I wanted to intercept the returned name, modify it and only then pass it to theIndex
	set theParsedName to (name of thisItem)

	on splitName(originalName)
		set cutPosition to (offset of "-" in originalName) - 1
		return text 1 thru cutPosition of originalName
	end splitName

	set theParsedName to splitName(theParsedName)
		-- With this the name is cut off at the "-"

		-- The convoluted decoder
	on decodedName(TheString)
		set previousDelimiter to AppleScript's text item delimiters
		set potentialName to TheString
		set legalName to {}
		set legalCharacters to {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
	
		repeat with thisCharacter in the characters of potentialName
			set thisCharacter to thisCharacter as text
			if thisCharacter is in legalCharacters then
				set the end of legalName to thisCharacter
				log (legalName as string)
			else if thisCharacter = "ö" then
				set the end of legalName to "oe"
				log (legalName as string)
			else if thisCharacter = "ä" then
				set the end of legalName to "ae"
				log (legalName as string)
			else if thisCharacter = "ü" then
				set the end of legalName to "ue"
				log (legalName as string)
			else if thisCharacter = "Ö" then
				set the end of legalName to "Oe"
				log (legalName as string)
			else if thisCharacter = "Ä" then
				set the end of legalName to "Ae"
				log (legalName as string)
			else if thisCharacter = "Ü" then
				set the end of legalName to "Ue"
				log (legalName as string)
			end if
		end repeat
	
		set AppleScript's text item delimiters to ""
		if length of legalName is greater than 32 then
			set legalName to items 1 thru 32 of legalName as text
		else
			set legalName to legalName as text
		end if
		set AppleScript's text item delimiters to previousDelimiter
		return legalName
	end decodedName

return decodedName(theParsedName)
-- At this point, e.g. "Höffe, Otfried 2004 - Kant's Kritik der reinen Vernunft" is truncated to "HoeffeOtfried2004" and stored in theParsedName
-- This is the version I want to pass into theIndex
		set theIndex to theIndex & "\"" & theParsedName & "\": " & "\"" & (uuid of thisItem) & "\"" & return
		
	end repeat
	
	set the clipboard to {text:(theIndex as string), Unicode text:theIndex}
	
end tell

As a standalone it works fine, but in combination with your example it does not. I reckon the problem is that AS doesn’t allow for this kind of routine to play out WITHIN the tell application id "DNtp"segment. What I’m proped with is this:

expected “end”, but found “on”.
(“end” erwartet, aber “on” gefunden.)

This makes things even more complicated. Do you see my problem? I want to modify the name before it is passed into the collection theIndex (simple string, array, list?). To filter a text file for each individual line, modify just the name but leave everything else untouched – I wouldn’t even know how to start with that ad hoc… pandoc pipes maybe :joy::sweat_smile:

Anyway, this shouldn’t be so hard, right? Do you know of an easy fix or have a suggestion for me how to proceed?

Sure, once everything checks out nicely I’ll post it in the DTTG section :+1:

I do a lot of this sort of thing, but unfortunately I have absolutely no clue what you are trying to do with your script, sorry.

Rather than try to automate the parsing of the file name into some idiosyncratic naming convention, which is going to be prone to failure because it cannot anticipate all possible future file names you might encounter – why not just do that name-encoding manually and put the encoded document name (minus the UUID) into the Spotlight comment field, where a script can pick it up?

Sometimes it is not worth writing software to solve an intractable problem that you can do manually.

NVM, I figured it out – to anyone else who might be pondering, thank you but save your energy :smile:

Nah, that would, well, defeat the purpose. Maybe you can see what I’m trying to do once I upload the workflow and take a look at that. And yes, copying the uuid to the spotlight comment field was my first idea, too. But how would I access this field via Apple’s Shortcuts application? The options are extremely limited. But if there is a way, I’d be willing to scrap everything and go down that route. Sice I haven’t been able to figure this out yet, I’m working on this hackish fix to get what I want :smiley:
The whole thing is also based on the assumption that I don’t want to limit myself to the implemented DTTG pdf viewer.

On another note, what would the AS command be to tell DTPO to create a new file with the returned result, i.e. the contents of “theIndex”? Preferably directly with the extension .json or plain text otherwise.

Well, here it is. Works like a charm. Except for a stubborn “,” following the very last pair.

I’m sure there is a more elegant solution for exporting than copy to clipboard – paste clipboard to a folder in a database in rich text format. If you know of the better and proper way please share. Is there a DevonThink and Applescript Documentation I’m missing?

So, if you want to enrich your life with a little obscure sorrow become code, here you go:

tell application id "DNtp"
	set theIndex to "{"
	set theParsedName to ""
	set theSelection to the selection
	repeat with thisItem in theSelection
		
		set theParsedName to (name of thisItem)
		
		tell me
			activate
			set theParsedName to splitName(theParsedName)
			set theParsedName to decodedName(theParsedName)
		end tell
		
		set theIndex to theIndex & "\"" & theParsedName & "\": " & "\"" & (uuid of thisItem) & "\"," & return
		
	end repeat
	
	set theIndex to theIndex & "}"
	set the clipboard to {text:(theIndex as text), Unicode text:theIndex}
	paste clipboard to parent id 888888 of database id 2
	
end tell




on splitName(originalName)
	set cutPosition to (offset of "-" in originalName) - 1
	return text 1 thru cutPosition of originalName
end splitName

on decodedName(TheString)
	--Store the current TIDs. To be polite to other scripts.
	set previousDelimiter to AppleScript's text item delimiters
	set potentialName to TheString
	set legalName to {}
	set legalCharacters to {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
	
	repeat with thisCharacter in the characters of potentialName
		set thisCharacter to thisCharacter as text
		if thisCharacter is in legalCharacters then
			set the end of legalName to thisCharacter
			log (legalName as string)
		else if thisCharacter = "ö" then
			set the end of legalName to "oe"
			log (legalName as string)
		else if thisCharacter = "ä" then
			set the end of legalName to "ae"
			log (legalName as string)
		else if thisCharacter = "ü" then
			set the end of legalName to "ue"
			log (legalName as string)
		end if
	end repeat
	
	set AppleScript's text item delimiters to ""
	if length of legalName is greater than 32 then
		set legalName to items 1 thru 32 of legalName as text
	else
		set legalName to legalName as text
	end if
	set AppleScript's text item delimiters to previousDelimiter
	return legalName
end decodedName

Maybe the decoder or splitter, along with their application as functions within the tell-command will be of interest for future reference.

There is an AppleScript dictionary you can access via Shift-Command-O in Script Editor.


paste clipboard here creates an RTF file. This is definitely not ideal for plain text processing.

Instead of paste clipboard, you could set the content to a variable and use…

	set theIndex to theIndex & "}"
	--set myItems to {text:(theIndex as text), Unicode text:theIndex}
	create record with {name:"test", content:theIndex as text, type:text} in current group

… and yes, I left an unnecesary line commented out in the example.

Note: This is hardcoded.
paste clipboard to parent id 888888 of database id 2

In the future, you should comment that or give an explanatory note for people reading your post.

Beautiful! Ok, here’s the final version with some comments:


tell application id "DNtp"
	set theIndex to "{" & return
	set theParsedName to ""
	set theSelection to the selection
	repeat with thisItem in theSelection
		
		set theParsedName to (name of thisItem)
		
		-- splits the name of the current item at "-" and removes special characters or replaces umlauts
		tell me
			activate
			set theParsedName to splitName(theParsedName)
			set theParsedName to decodedName(theParsedName)
		end tell
		
		set theIndex to theIndex & "\"" & theParsedName & "\": " & "\"" & (uuid of thisItem) & "\"," & return
		
	end repeat
	
	-- gets rid of the last ","
	tell me
		activate
		set theIndex to reduceChar(theIndex)
	end tell
	
	set theIndex to theIndex & return & "}"

	create record with {name:"index.json", content:theIndex as text, type:text} in parent id 848882 of database id 2
	-- outputs the theIndex as plain text to a specific folder in a specific database: the id's are hardcoded in this example. To have the file created in the folder of the indexed items use:
	-- create record with {name:"test", content:theIndex as text, type:text} in current group	
	
end tell


on reduceChar(reducedString)
	set t to reducedString
	text 1 thru -3 of t
end reduceChar

on splitName(originalName)
	set cutPosition to (offset of "-" in originalName) - 1
	return text 1 thru cutPosition of originalName
end splitName

on decodedName(TheString)
	set previousDelimiter to AppleScript's text item delimiters
	set potentialName to TheString
	set legalName to {}
	set legalCharacters to {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
	
	repeat with thisCharacter in the characters of potentialName
		set thisCharacter to thisCharacter as text
		if thisCharacter is in legalCharacters then
			set the end of legalName to thisCharacter
			log (legalName as string)
		else if thisCharacter = "ö" then
			set the end of legalName to "oe"
			log (legalName as string)
		else if thisCharacter = "ä" then
			set the end of legalName to "ae"
			log (legalName as string)
		else if thisCharacter = "ü" then
			set the end of legalName to "ue"
			log (legalName as string)
		end if
	end repeat
	
	set AppleScript's text item delimiters to ""
	if length of legalName is greater than 32 then
		set legalName to items 1 thru 32 of legalName as text
	else
		set legalName to legalName as text
	end if
	set AppleScript's text item delimiters to previousDelimiter
	return legalName
end decodedName

Also thanks!

Come to think of it, this isn’t true, is it? Since all file names are treated the same way, the output is a unique identifier no matter what – given that I don’t start a file name with “-” or place it too early for the “compiler” to construct a unique identifier . But since this script is to be applied to pre-sorted documents, I still have control over the process. This naming convention is important because an identical identifier is being created from the same name(s) with the same method but from a different source. Having both identifiers align allows me to further process the data.

Beats me. I don’t know your data :smile: It was just a comment, not a directive.