I'm new to MacOS but I could not find any solution so here it is what I need: I work with a lot of pdf-s while writing and I need to use a lot of quotations. I need an automator service for macos sierra with the following function: When I copy a text from PDF I need to paste it to a word processor (mainly scrivener but preferably to anywhere else too). The trick: I need to paste it as plain text to match formatting of the receiving document. I also need to get rid of all line breaks. And I need quotation marks at the beginning and at the end of the pasted text.
Thanks for any suggestions!
Here's the documentation on how to make a systemwide service using Automator. You'll want to read and follow its instructions, which goes through a step-by-step example of how to take some selected text and change it's case to uppercase, or what not.
So the method won't be too far from what you'll need to employ to achieve your outcome.
You've mentioned using Scrivener, but implicitly assumed that we all use it too. I, personally, don't, so I have no idea what Automator actions are supplied with it (if any), nor whether it is AppleScriptable or not. Therefore, I'm already limited by what I can assist you with, so I'm going to focus on using another application in this example.
When I copy a text from PDF I need to paste it to a word processor
So, your Automator service will take selected text, which can be from any application or limited to a specific application of your choice, such as Preview or some other PDF document viewer.
The bulk of the Automator workflow will then be scripted, which I predominantly do with AppleScript. So I would insert a Run AppleScript action as the first action, which will receive the selected text.
This will be sent through and stored in the variable input
of the run
handler:
on run {input, parameters}
I need to paste it as plain text
So the first line of the script is probably going to be:
set input to input as text
It's probably premature and almost definitely redundant, as there'll be manipulations of the variable's contents in a moment. But, it's also useful to have it explicitly declared just for when one reads the script back to themselves.
I need the line breaks to be replaced with a space
i.e. No paragraphs, just one, single block of prose, like your question.
set the text item delimiters to {space, linefeed, return}
set input to the text items of input as text
I need quotation marks at the beginning and at the end of the pasted text.
set input to [quote, input, quote] as text
Here's the workflow so far:
From here, there are a couple of options with regards to what you can do:
① Put the reformatted text onto the clipboard, as you see I've done there, and then allow the user to simply move into whichever application they wish and paste the text themselves. If this is what you elect for, then you're finised, and the above screenshot is your completed service, which—once saved—will be accessible from the services menus in whichever application you pick (or any application if you leave it).
But, if you want to go on...
② Get the workflow to open a scriptable application and set the contents of the document to the contents of our input
variable. I'll demonstrate this with Apple's TextEdit.
③ Get the workflow to open any regular (non-scriptable) application and use a rather inelegant technique of imposing control over certain elements of the application in a limited fashion. This is slightly less robust than ②, but if it's tested well on a single application that you'll always be using, there's no reason it won't be reliable.
It turned out to be slightly more involved that I had hoped. Whilst being scriptable does make it a lot easier, there were specific AppleScript properties missing from the likes of TextEdit and even Apple's Pages, which meant that regular scripting got me about half-way to where I was aiming for, so had to use some of that inelegance I mentioned in order to get the rest of the way there. Therefore, I won't bore you with going through how it works. But here's the portion of the script responsible for getting the highlighted input text into TextEdit:
script TextEdit
use application "System Events"
use T : application "TextEdit"
property parent : this
to receiveTheText:(plainText as string)
activate T
repeat until name of processes ¬
contains "TextEdit"
delay 1
end repeat
if not (exists document 1 of T) then
tell T to set D to make new document
else
set D to T's first document
end if
set [a, b] to the value of ¬
attribute "AXSelectedTextRange" of ¬
text area 1 of ¬
scroll area 1 of ¬
window 1 of ¬
process "TextEdit"
set character a of D's text to plainText & linefeed
end receiveTheText:
end script
However, it works surprisingly well, preserving the local formatting of the TextEdit document, and matching the incoming text to the local styling:
So, I went ahead and downloaded a trial version of Scrivener. Sadly, it isn't AppleScript-able, so this limits us in some respects. Namely, whilst TextEdit doesn't have to be open because the Automator service can open the app and create a new document if need be, Scrivener does need to an open project ready to receive the text. I imagine this isn't too much of an inconvenience, but from a scripting point-of-view, it was a much tougher script to write and it has the potential to stop working properly if Scrivener decide to change their program.
Here's the script for Scrivener:
script Scrivener
use scripting additions
property parent : this
to receiveTheText:(plainText as string)
tell application "System Events" to ¬
if not (exists process "Scrivener") then ¬
return display alert ¬
"Application not running." & ¬
linefeed & linefeed & ¬
"When using this service with Scrivener, an open " & ¬
"project is required into which the text can be " & ¬
"inserted. AppleScript is unable to create a project " & ¬
"for you." as critical
tell application "Scrivener" to activate
tell application "System Events"
tell process "Scrivener" to set _P to ¬
a reference to (first window whose ¬
name of attributes contains "AXDocument" and ¬
value of attribute "AXDocument" contains ".scriv")
if not (exists _P) then ¬
return display alert ¬
"No open documents." & ¬
linefeed & linefeed & ¬
"Please create a project and try again." as critical
repeat until (count _P) = 0
set _T to a reference to (UI elements of _P ¬
whose role is "AXTextArea")
if (count _T) ≠ 0 then exit repeat
set _P to a reference to UI elements of _P
end repeat
set [_T] to _T --> The text area
tell _T to set value of attribute "AXSelectedText" to plainText
end tell
end receiveTheText:
end script
To finish up, you'll want the entire script as a whole. The individual component scriptlets I've pasted above won't do much my themselves. You will need to copy and paste this entire script into the Automator workflow using the Run AppleScript action I showed you earlier.
There's only one optional variable you might want to change, which is at the very bottom of the script: you'll see two almost-identical lines, one of which is commented out with a preceding #
. These dictate whether the service passes the text to TextEdit or to Scrivener. Currently, it's set to go to Scrivener. Just switch the #
symbol to the other line should you want to give it a try with TextEdit.
property parent : AppleScript
property this : me
--------------------------------------------------------------------------------
script TextEdit
use application "System Events"
use T : application "TextEdit"
property parent : this
to receiveTheText:(plainText as string)
activate T
repeat until name of processes ¬
contains "TextEdit"
delay 1
end repeat
if not (exists document 1 of T) then
tell T to set D to make new document
else
set D to T's first document
end if
set [a, b] to the value of ¬
attribute "AXSelectedTextRange" of ¬
text area 1 of ¬
scroll area 1 of ¬
window 1 of ¬
process "TextEdit"
set character a of D's text to plainText & linefeed
end receiveTheText:
end script
script Scrivener
use scripting additions
property parent : this
to receiveTheText:(plainText as string)
tell application "System Events" to ¬
if not (exists process "Scrivener") then ¬
return display alert ¬
"Application not running." & ¬
linefeed & linefeed & ¬
"When using this service with Scrivener, an open " & ¬
"project is required into which the text can be " & ¬
"inserted. AppleScript is unable to create a project " & ¬
"for you." as critical
tell application "Scrivener" to activate
tell application "System Events"
tell process "Scrivener" to set _P to ¬
a reference to (first window whose ¬
name of attributes contains "AXDocument" and ¬
value of attribute "AXDocument" contains ".scriv")
if not (exists _P) then ¬
return display alert ¬
"No open documents." & ¬
linefeed & linefeed & ¬
"Please create a project and try again." as critical
repeat until (count _P) = 0
set _T to a reference to (UI elements of _P ¬
whose role is "AXTextArea")
if (count _T) ≠ 0 then exit repeat
set _P to a reference to UI elements of _P
end repeat
set [_T] to _T --> The text area
tell _T to set value of attribute "AXSelectedText" to plainText
end tell
end receiveTheText:
end script
--------------------------------------------------------------------------------
on run {input, parameters}
set input to input as text
set the text item delimiters to {space, linefeed, return}
set input to the text items of input as text
set input to [quote, input, quote] as text
# tell TextEdit to receiveTheText:input
tell Scrivener to receiveTheText:input
end run
-----------------------------------------------------------------------------END