“Open in Script Debugger” links gone (bis)

foundation
safari
asobjc

(Jonas Whale) #1

Continuing the discussion from "Open in Script Debugger" links gone:

The links are gone for the second time for a couple of months here.
@ccstone shared a script on this forum, but it does not seem to work anymore.
And the one provided by @JMichaelTX and @NigelGarvey does not select the entire script.

Can anyone give a hand on that?


(Nigel Garvey) #2

You have to select the script text yourself, manually, then run the script.


(Jonas Whale) #3

Yes, I’m aware of that.
That’s exactly what I’m trying to avoid.
I know that it’s possible with javascript to select the the entire code block.
But, as I’m not an expert in Javascript, I’m not able to find how to do this.


(Jonas Whale) #4

I found this to get the entire code block:

set JSscript to "document.getElementsByTagName('code')[0].outerHTML;"
tell application "Safari" to do JavaScript JSscript in front document

But it returns the script in a form (html or xml?) I don’t know what to do with.
How can I convert it?
Here is a sample:

<code class=“lang-applescript hljs”>use framework <span class=“hljs-string”>“Foundation”</span>


(Christopher Stone) #5

Hey @ionah,

You’re only getting the very first (zero-indexed) item, and you’re getting html — because you’re explicitly asking for it:

…[0].outerHTML

The way to extract the text is to use .textContent

Unfortunately I don’t know how to find the click or hover location in JavaScript (just yet). The code I posted for that was written by a friend who has (so far) not fixed it for Safari 11.

Here’s a script that will scrape the text from all code blocks on the front page in Safari and send it to BBEdit.

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/11/24 18:25
# dMod: 2017/11/24 18:36
# Appl: Safari, BBEdit
# Task: Extract text from code blocks in Safari's front document and send to BBEdit.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @BBEdit, @Safari, @Extract, @Text, @Code, @Blocks, @Front, @Document, @Send, @JavaScript
----------------------------------------------------------------

set jsCmdStr to "

var elementsListObject = document.getElementsByTagName('code');
var textObjects = new Array();

for (var i=0; i < elementsListObject.length; i++){
   textObjects.push(elementsListObject[i].textContent);
}

textObjects.join(\"\\n\\n••••••••••••••••••••\\n\\n\");

"

tell application "Safari" to set codeBlocks to do JavaScript jsCmdStr in front document

tell application "BBEdit"
   make new document with properties {text:codeBlocks}
   set bounds of front window to {303, 45, 1617, 1196}
   tell front text window to select insertion point before its text
   activate
end tell

----------------------------------------------------------------

Long topics in Discourse (over 20 or so posts) may NOT be completely scraped, because of the way it handles paging.

This is not a solution, but it will get me by until we come up with something better.

I’ll nag my friend and see if he’ll fix his script.

@JMichaelTX is gaining proficiency with JavaScript, so he might be able to help us out as well.

-Chris


(Shane Stanley) #6

You can get all the scripts, subject to the limitation with long topics Chris mentioned, using this:

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

tell application id "com.apple.Safari" -- Safari.app
	set theSource to source of document 1
end tell
set {xmlDoc, theError} to current application's NSXMLDocument's alloc()'s initWithXMLString:theSource options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
set theScripts to (xmlDoc's nodesForXPath:".//pre/code" |error|:(missing value))'s valueForKey:"stringValue"
repeat with aScript in theScripts
	tell application id "com.latenightsw.ScriptDebugger6" -- Script Debugger.app
		make new document
		set source text of document 1 to aScript as text
		try
			compile document 1
		end try
	end tell
end repeat

You can also limit the results to scripts containing the selection like this:

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

tell application id "com.apple.Safari" -- Safari.app
	set theSource to source of document 1
	set JSscript to "document.getSelection().toString()"
	set selText to do JavaScript JSscript in front document
end tell
set {xmlDoc, theError} to current application's NSXMLDocument's alloc()'s initWithXMLString:theSource options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
set theScripts to (xmlDoc's nodesForXPath:".//pre/code" |error|:(missing value))'s valueForKey:"stringValue"
repeat with aScript in theScripts
	if (aScript's containsString:selText) then
		tell application id "com.latenightsw.ScriptDebugger6" -- Script Debugger.app
			make new document
			set source text of document 1 to aScript as text
			try
				compile document 1
			end try
		end tell
	end if
end repeat

(Nigel Garvey) #7

The caveats here are that you have to select text that’s unique to the script you want and that if you select text containing tabbed indents, the script won’t recognise it because the tabs will have been turned into sequences of spaces in the node strings. (They are on my machine anyway.) The latter problem’s easy to solve, of course, by doing the same in the string from the selection:

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

tell application id "com.apple.Safari" -- Safari.app
	set theSource to source of document 1
	set JSscript to "document.getSelection().toString()"
	set selText to do JavaScript JSscript in front document
end tell
set {xmlDoc, theError} to current application's NSXMLDocument's alloc()'s initWithXMLString:theSource options:(current application's NSXMLDocumentTidyHTML) |error|:(reference)
set theScripts to (xmlDoc's nodesForXPath:".//pre/code" |error|:(missing value))'s valueForKey:"stringValue"
-- Added. Replace each tab in selText with eight spaces:
set selText to (current application's NSString's stringWithString:selText)'s stringByReplacingOccurrencesOfString:tab withString:"        "
----------
repeat with aScript in theScripts
	if (aScript's containsString:selText) then
		tell application id "com.latenightsw.ScriptDebugger6" -- Script Debugger.app
			make new document
			set source text of document 1 to aScript as text
			try
				compile document 1
			end try
		end tell
	end if
end repeat

Edit: This still leaves a problem that the source of pages on this site doesn’t always include the text of the posts! The pages have to be reloaded to get that, which destroys the selection.


(Jonas Whale) #8

Here is my proposition, after reading all the contributions:
(If you need translations for the alerts, let me know)
Thanks to everyone!

 -- get the selection
tell application "Safari" to set theSelection to do JavaScript "document.getSelection().toString() " in front document
if theSelection = "" then display alert "Safari n'a renvoyé aucun texte actif." message "Veuillez sélectionner une partie du script assez longue pour le différencier des autres présents sur la page." buttons {"Annuler"} cancel button 1

-- make a list of indexes of all scripts containing the selection  
set theIndexes to {}
repeat with i from 0 to 99
	try
		tell application "Safari" to set theScript to do JavaScript "document.getElementsByTagName('code')[" & i & "].textContent;" in front document
		if theScript contains theSelection then set end of theIndexes to i
	on error
		exit repeat
	end try
end repeat

-- manage cases where there's more than 1 index or no one
if (count of theIndexes) > 1 then
	display alert "Safari a renvoyé plusieurs scripts." message "Veuillez étendre votre sélection de façon à différencier le script actif des autres présents sur cette page." buttons {"Annuler"} cancel button 1
else if theIndexes = {} then
	display alert "La sélection n'est pas du bon type." message "Le texte actif ne fait pas partie d'un bloc de code." buttons {"Annuler"} cancel button 1
end if

-- get the index of the selected script
set theIndex to item 1 of theIndexes

-- get the entire selected script
tell application "Safari" to set theScript to do JavaScript "document.getElementsByTagName('code')[" & theIndexes & "].textContent;" in front document

-- make a new document script
tell application "Script Debugger"
	activate
	make new document with properties {source text:theScript}
	if not (compile) then return
end tell

(Christopher Stone) #9

Hey @ionah,

You don’t need to guess at the number of code blocks.

----------------------------------------------------------------
-- Get the number of code blocks and the selection.
----------------------------------------------------------------
tell application "Safari"
   set jsOutput to do JavaScript "

var elementsListObject = document.getElementsByTagName('code'),
numberOfCodeBlocks = elementsListObject.length,
theSelection = document.getSelection().toString(),
theOutput = '';

theOutput.concat(numberOfCodeBlocks,\"\",theSelection);

" in front document
   
end tell

set AppleScript's text item delimiters to ""

set codeBlockNumber to text item 1 of jsOutput
set selectedText to text item 2 of jsOutput
----------------------------------------------------------------

-Chris


(Jim Underwood) #10

Please try this script. It works for me in both Safari and Chrome.
It will select ONLY the code block that you click in.

I will have an update later that also returns the post author, date, and URL.

If you have any issues or suggestions, please let me know.

How To Use

  1. Click in the code block you want
  2. Trigger the script by hot key

Occasionally the script will not find the code block. Just repeat the above process.

Script Revised Ver 2.1 2017-11-25 23:56 GMT-0600

  • ADD error checking to ensure frontmost app is a browser.
  • ADD comments
property ptyScriptName : "Get Web Code Block in FrontMost Browser & Open in SD"
property ptyScriptVer : "2.1"
property ptyScriptDate : "2017-11-25"
property ptyScriptAuthor : "JMichaelTX"

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

----------------------------------------------------
--  JavaScript to Find Code Block & Extract Text ---
----------------------------------------------------

set jsStr to "
(function selectCodeBlock() {      // this will auto-run when script is executed

var scriptResults = \"TBD\";
var codeBlockTag  = \"CODE\";

//--- Get Range of where User has Clicked Mouse ---
var rngSel   = window.getSelection().getRangeAt(0);
var parElem   = rngSel.startContainer.parentNode;
var parTag   = parElem.tagName;

//--- IF selected tag is NOT codeBlockTag, Move UP Until it if found ---

while (parTag !== codeBlockTag) {
 parElem = parElem.parentElement;
 parTag = parElem.tagName;
 
 if (parTag === \"BODY\") {
   break; 
 }
}

if (parTag === codeBlockTag) {
 scriptResults = parElem.textContent;
} else {
 scriptResults = \"[ERROR] Could NOT find Code Block with tagName of \" + codeBlockTag;
}

return scriptResults;
}  // END of function selectCodeBlock()
)();
"

--------------------------------------------
--- DO JavaScript in FrontMost Browser ---
--------------------------------------------
-- the Browser MUST be FrontMost when this script is run

set sourceStr to my doJavaScript("", jsStr)

--- Open Script Debugger & Set Document to Code Block Text ---

if (sourceStr does not start with "[ERROR]") then
 
 tell application id "com.latenightsw.ScriptDebugger6" -- Script Debugger.app
   activate
   make new document
   set source text of document 1 to sourceStr as text
   try
     compile document 1
   end try
 end tell
 
else
 display dialog sourceStr with title (name of me) with icon stop
end if

--~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~~~~~~

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on doJavaScript(pBrowserName, pScriptStr)
 --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
 (*  VER: 1.1    2017-11-25
     PURPOSE:  Run JavaScript in the Designated Browser (Safari OR Chrome)
     PARAMETERS:
       • pBrowserName    |  text  | Name of brower in which to run JavaScript
           • IF this parameter is empty "", then the FrontMost App will 
               be determined and used, IF it is a browser (else an error)
       • pScriptStr      | text  | JavaScript text to be run
       
     RETURNS:  Results of the JavaScript
     
     AUTHOR:  JMichaelTX
   ## REQUIRES:  Either Google Chrome or Safari
 ----------------------------------------------------------------
 *)
 
 local jsScriptResults, frontAppName
 
 if (pBrowserName = "") then -- get FrontMost App Name
   tell application "System Events"
     set pBrowserName to item 1 of (get name of processes whose frontmost is true)
   end tell
 end if
 
 try
   
   set jsScriptResults to "TBD"
   
   if pBrowserName = "Safari" then
     tell application "Safari"
       set jsScriptResults to do JavaScript pScriptStr in front document
     end tell
     
   else if pBrowserName contains "Chrome" then
     tell application "Google Chrome"
       set jsScriptResults to execute (active tab of window 1) javascript pScriptStr
     end tell
     
   else
     error ("ERROR:  Invalid Broswer Name: " & pBrowserName ¬
       & return & "Script MUST BE run with the FrontMost app as either \"Safari\" OR \"Chrome\"")
   end if
   
 on error e
   error "Error in handler doJavaScript()" & return & return & e
   
 end try
 
 return jsScriptResults
 
end doJavaScript
--~~~~~~~~~~~~~~~~ END of Handler doJavaScript ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(Christopher Stone) #11

Thanks Jim.

Works Great!


(Christopher Stone) #12

Hey Folks,

Jim’s script errors when run from Script Debugger, because it’s designed to run on the frontmost browser (Safari or Chrome).

You can change the empty string in this handler call to “Safari” or “Google Chrome” and make it work when testing from a script editor.

--- DO JavaScript in FrontMost Browser ---
set sourceStr to my doJavaScript("", jsStr)

-Chris


(Jim Underwood) #13

Chris, I suppose you meant “Jim’s script gets errors . . .”

Yes, the script actually throws an error telling you that either “Safari” or “Chrome” must be the frontmost app when this script is run. That is because the user MUST first click in the Code Block of interest. Otherwise, the JavaScript does NOT know where to look.

As I stated in my OP:

If you try running the script from SD or SE, you will get an error:
image

Script Updated

If anyone finds any issues, or has suggestions, please post below.


(Jonas Whale) #14

Thank you @JMichaelTX
Your script is great!

I have simplified it because I’m using Safari exclusively.

May I ask a question?
Why does the tag name has to be in capitals in your script (otherwise the script won’t work) while document.getElementsByTagName('code')[0] is ok?

 -- build the script javascript
set javaString to "
(function () {

var codeBlockTag  = 'CODE';

//// make range from user selection (can be an insertion point)
var rngSel = window.getSelection().getRangeAt(0);
var parElem = rngSel.startContainer.parentNode;
var parTag = parElem.tagName;

//// search for code block, moving up until it is found 
while (parTag !== codeBlockTag) {
 parElem = parElem.parentElement;
 parTag = parElem.tagName;
 
//// stop the loop if the top is reached
 if (parTag === 'BODY') {
 return false; 
 }
}

//// return the script code if found
return parElem.textContent;
}
)();
"

-- get the entire selected script
tell application "Safari"
	set javaResult to do JavaScript javaString in document 1
	if javaResult = false then display alert "Impossible de récupérer le script. " message "Soit votre sélection ne fait pas partie d'un bloc de code, soit la balise 'CODE' n'est pas reconnue." buttons {"Annuler"} cancel button 1
end tell

-- make a new document script
tell application "Script Debugger"
	activate
	make new document with properties {source text:javaResult}
	if not (compile) then return
end tell

(Jim Underwood) #15

Because that is what element.tagName is expecting.


(Jonas Whale) #16

Ok. :wink:

I have 2 more questions:
What is a node?
And do you know how to use selection.extend method?


(Jim Underwood) #17

Jonas, I am a rank amateur at JavaScript. I have found the best resources to be:

Books:

  1. JavaScript Ref Guide, Tutorials, & Documentation – Mozilla
  2. Douglas Crockford’s Javascript Web Site
  3. JavaScript: The Good Parts – by Douglas Crockford (Kindle Edition)
  4. A Smarter Way to Learn JavaScript – by Mark Myers (Kindle Edition)

(Jonas Whale) #18

Thank you Jim, for the links.
:wink: