Bug? Defining a variable as number and it's "memorized"

This is very hard to describe over text but I will do my best. I also must preface that I’m rather novice so forgive me if I’m not using proper terminology. Here goes:

I made a web scraper for work and one of the items I’m scraping isn’t static/constant but would always be one of the following: a) Two different numbers b) Two of the same numbers c) Only one number d) No number at all.

If there are two numbers presented, I’m required to scrape the larger value. So here’s the part of the script


try
	set Num1 to ScrapeByClass("html_class", 0) as number
	set Num2 to ScrapeByClass("html_class", 1) as number
	if Num2 is greater than Num1 then
		set varNum to Num2
	else
		set varNum to Num1
	end if
on error
	try
		set varNum to Num1
	on error
		set varNum to "N/A"
	end try
end try

return varNum

NOTE: ScrapeByClass is a handler that uses JavaScript to scrape a webpage


to ScrapeByClass(elementClass, num)
	tell application "Safari" to do JavaScript "document.getElementsByClassName('" & elementClass & "')[" & num & "].innerHTML;" in document 1
end ScrapeByClass

So I start with scenario a) where there are two different numbers and I use a if statement to define the larger value, or if it’s the same (scenario b), it would just define it as one of the numbers presented. In scenario c) where there’s only one number given, it goes in the error loop and simply scrape the given single value. Lastly, if none is given, it sets the variable to “N/A” for final result.

The script seems to work fine with the exception of this one issue (or bug?). If I run the script, scrape a value and print it, when I run the script again that has scenario d) with no numbers given. It would still print the last scraped value despite nothing is presented on the webpage. This is not observed when I run the script for the first time on a scenario d) with no numbers.

After spending couple hours messing around, I noticed that when I remove “as number” at the beginning of my script (line 2 and 3). This error is no longer observed. However, because the web scraper handler defines the variable as a string and two strings cannot be compared for a “larger” value, the if condition is broken. So then I’m stuck in between.

Has anyone ever experience such thing?

Initially I thought it’s related to browser caching issue because of the handler. However, this is not the case as I have tried clearing Safari browser data/cache (completely) but the script still remembers the last value scraped when the webpage doesn’t have a value. Also, in my research, I learned that you cannot declare a variable as undefined in AppleScript so it’s not possible to do “If variable is null/nil/”" then …"

I’m at a lost at the fact that AppleScript is “memorizing” a value when the script never involved any data structure or storage.

Any insight/advice is largely appreciated!!!

You can compare strings as numbers by using “considering numeric strings” clause.
How about this?


set a to "10.1"
set b to "2"

considering numeric strings
	set c1 to (a > b)
end considering

set c2 to (a > b)

return {c1, c2}

Model: MacBook Pro 2012
AppleScript: 2.7
Browser: Safari 13.0.1
Operating System: macOS 10.14

Hi.

It’s a feature (peculiarity?) of AppleScript that when a script’s run, the values of any properties, globals, or “run handler” variables are saved back to the script file and are there next time the script’s run. The variables in your script are all “run handler” variables — that is, they’re all set in the main body of the script. So on subsequent runs, Num1, Num2, and varNum start with the values they had at the end of the previous run. If the script errors while trying to set Num1, and Num1 already exists with a value, the existing value’s retained and varNum is set to that.

The easiest cure would be to declare Num1 (and Num2 and varNum if you like, although this doesn’t affect the working of the script) as local at the top of the script, so that it ceases to exist when the script’s finished and its value isn’t saved back to the file:

local Num1, Num2, varNum

try
	set Num1 to ScrapeByClass("html_class", 0) as number
	set Num2 to ScrapeByClass("html_class", 1) as number
	if Num2 is greater than Num1 then
		set varNum to Num2
	else
		set varNum to Num1
	end if
on error
	try
		set varNum to Num1
	on error
		set varNum to "N/A"
	end try
end try

return varNum


to ScrapeByClass(elementClass, num)
	tell application "Safari" to do JavaScript "document.getElementsByClassName('" & elementClass & "')[" & num & "].innerHTML;" in document 1
end ScrapeByClass

This is an interesting approach, which hadn’t occurred to me before. Sometimes I include an unnecessary handler in a simple script just to prevent a variable from being saved back to the script file. In those cases, making the variable local is a much better solution.

There probably are instances in which a script benefits from remembering the value of a variable–the choose-folder command might be an instance. When writing an important script, I guess we should decide which variables should be saved and which shouldn’t.

It’s a bit more complicated than that, in that there’s not always a choice. The values aren’t saved if the script does UI scripting, or uses AppleScriptObjC variables at the top level, or is in a locked/read-only directory, or is code-signed.

Outside applets it’s useful and benign, but applets that change their contents are quite the anachronism, and if I were a betting man, I’d say their days are numbered. The applet shell could be rewritten to save, say, property values elsewhere, but the more likely result is it will just stop happening in applets, as it has in some cases already.

Shane. I hope your prediction is correct, but I’ll use this as an opportunity to rant about one of my pet peeves, which I’ll do by example.

I save the following script as an applet and, after running it on a moderately-sized folder, the script file size goes from 101KB to 280KB. There may be some useful purpose for this but I don’t know what it is. Anyways, making the variable (theFiles) local is a simple solution for this issue, and I’ll either do that or include it in a handler.


set theFolder to choose folder
tell application "Finder"
	set theFiles to every file of the entire contents of theFolder
end tell

In scripts for my own use, I find it simpler to do the opposite: put the main body of the code in an ordinary handler and limit the run handler code to a call to that handler. That way, all the variables used are local unless I explicitly declare them otherwise. Apart from the file bloat, I’ve always found it annoying to have a script’s modification date change every time I run it. :wink:

I’m seeing modification date changes with scripts that use UI scripting (setting run handler variables to returned System Events specifiers), but not with scripts which have any “persistent” variable set to an AppleScriptObjC value. If the auto-save behaviour in Script Editor’s anything to go by, an attempt’s still made to save persistent values in the latter case, but it errors before any are written to the file.

That was my understanding, until some here pointed out that it wasn’t happening in their scripts in Mojave, and I did a check and found the same thing. I don’t have any such scripts I use, so I haven’t checked again.

Right. In fact when ASObjC first came out, the error wasn’t silent.

Thanks Nigel–the simplicity of your approach makes it most useful and the script file size remains unchanged.

runScript()

on runScript()
	set theFolder to choose folder
	tell application "Finder"
		set theFiles to every file of the entire contents of theFolder
	end tell
	--other stuff
end runScript