Ideal way to deal with variables inside if statements?

I made this for school and there’s one part that’s upsetting because its looks quite ugly. The script itself takes Skim annotations and creates an OmniOutliner document based on the color of the highlight. The ugliness is as follows…

I would like to pull out the “add style_” line, but its makes the script editor angry when I do.

				if exists named styles of style of row_last then
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of last row's children of doc
					add style_three to named styles of style of head_three
				else if level of parent of last row of doc ≤ 2 then
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of rows of parent of last row of doc
					add style_three to named styles of style of head_three
				else
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of parent of last row's parent of doc
					add style_three to named styles of style of head_three
				end if

All of it

tell application "System Events"
	if not (exists process "OmniOutliner") then
		do shell script "open -a \"OmniOutliner\""
	end if
end tell

tell application "Finder" to set dtb to bounds of window of desktop

tell application "Skim"
	
	set all_notes to every note of front document
	set pdf_name to (name of front document)
	
	tell application id "OOut"
		
		make new document with properties {name:pdf_name}
		
		set (bounds of windows where visible is true) to dtb
		tell application "System Events" to set frontmost of process "OmniOutliner" to true
		
		set status visible of front document to false
		
		tell style of front document
			set value of attribute "font-size" to 16
			set value of attribute "font-family" to "Avenir Next"
			--set value of attribute "font-family" to "M+ 1p"
			set value of attribute "font-fill" to {21047, 21047, 21047, 65535}
			set value of attribute "item-to-note-space(com.omnigroup.OmniOutliner)" to "4.0"
			set value of attribute "item-child-indentation(com.omnigroup.OmniOutliner)" to "30.0"
			set value of attribute "shadow-color" to {57729, 57729, 57729, 65535}
			--set value of attribute "shadow-offset" to "-1.0" --Why doesn't this work?
			set value of attribute "shadow-radius" to "1.2"
		end tell
		
		tell column title style of front document
			set value of attribute "font-fill" to {57729, 57729, 57729, 65535}
			set value of attribute "underline-style" to "none"
			set value of attribute "font-weight" to 1.0
		end tell
		
		make new row with properties {topic:pdf_name} at end of rows of front document
		
		tell front document
			set style_one to make new named style with properties {name:"Heading 1"}
			set value of attribute "font-fill" of style_one to {16587, 28329, 55163, 65535}
			
			set style_two to make new named style with properties {name:"Heading 2"}
			set value of attribute "font-fill" of style_two to {39981, 38846, 25155, 65535}
			
			set style_three to make new named style with properties {name:"Heading 3"}
			set value of attribute "font-fill" of style_three to {23514, 43610, 18771, 65535}
			
			set style_five to make new named style with properties {name:"Green Highlight"}
			set value of attribute "text-background-color" of style_five to {59367, 61166, 56540, 65535}
			
			set style_six to make new named style with properties {name:"Red Highlight"}
			set value of attribute "text-background-color" of style_six to {63479, 59110, 58339, 65535}
		end tell
		
	end tell
	
	
	repeat with i from 1 to count of all_notes
		set _note to item i of all_notes
		set _page to "pg. " & index of page of _note
		
		set note_text to text of _note
		set rgba to color of _note
		set {fav1, fav2, fav3, fav4, fav5, fav6} to favorite colors
		
		tell application id "OOut"
			set doc to front document
			set row_last to last row of doc
			
			if rgba is fav1 then
				set head_one to make new row with properties {topic:note_text, note:_page} at end of rows of doc
				add style_one to named styles of style of head_one
				
			else if rgba is fav2 then
				try
					set head_two to make new row with properties {topic:note_text, note:_page} at end of last child of doc
				on error
					set head_two to make new row with properties {topic:note_text, note:_page} at end of rows of doc
				end try
				add style_two to named styles of style of head_two
				
			else if rgba is fav3 then
				if exists named styles of style of row_last then
					set head_three to make new row with properties {topic:note_text, note:_page} at end of last row's children of doc
					add style_three to named styles of style of head_three
				else if level of parent of last row of doc ≤ 2 then
					set head_three to make new row with properties {topic:note_text, note:_page} at end of rows of parent of last row of doc
					add style_three to named styles of style of head_three
				else
					set head_three to make new row with properties {topic:note_text, note:_page} at end of parent of last row's parent of doc
					add style_three to named styles of style of head_three
				end if
				
			else if rgba is fav4 then
				if exists named styles of style of row_last then
					make new row with properties {topic:note_text} at end of last row's children of doc
				else if level of row_last > 1 and has subtopics of preceding sibling of row_last contains {} or {false} then
					make new row with properties {topic:note_text} at end of parent of last row of doc
				else
					make new row with properties {topic:note_text} at end of last child of doc
				end if
				
			else if rgba is fav5 then
				if exists named styles of style of row_last then
					set head_five to make new row with properties {topic:note_text} at end of last row's children of doc
					add style_five to named styles of style of head_five
				else if level of row_last > 1 and has subtopics of preceding sibling of row_last contains {} or {false} then
					set head_five to make new row with properties {topic:note_text} at end of parent of last row of doc
					add style_five to named styles of style of head_five
				else
					set head_five to make new row with properties {topic:note_text} at end of last child of doc
					add style_five to named styles of style of head_five
				end if
				
			else if rgba is fav6 then
				if exists named styles of style of row_last then
					set head_six to make new row with properties {topic:note_text} at end of last row's children of doc
					add style_six to named styles of style of head_six
				else if level of row_last > 1 and has subtopics of preceding sibling of row_last contains {} or {false} then
					set head_six to make new row with properties {topic:note_text} at end of parent of last row of doc
					add style_six to named styles of style of head_six
				else
					set head_six to make new row with properties {topic:note_text} at end of last child of doc
					add style_six to named styles of style of head_six
				end if
			end if
			
		end tell
	end repeat
	tell application id "OOut" to expandAll rows of front document
end tell

Would it be poor practice to add global before the if statement?

			else if rgba is fav3 then
				
				global head_three
				if exists named styles of style of row_last then
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of last row's children of doc
				else if level of parent of last row of doc ≤ 2 then
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of rows of parent of last row of doc
				else
					set head_three to make new row with properties {topic:note_text, note:note_url} at end of parent of last row's parent of doc
				end if
				add style_three to named styles of style of head_three
				
				
			else if rgba is fav4 then.....

Hi Dr.

I don’t know why you would need to make the variable global.

What do you mean by pull out that statement. What happens?

gl,
kel

Hi.

I don’t have OmniOutliner and only have an old, unused copy of Skim, so I can’t help directly with the problem. But here are some comments from reading the script.

  1. Don’t nest ‘tell’ statements to different applications. It makes scripts harder to read and could possibly lead to terminology clashes.

Undesirable:

tell application "Skim"

	-- Blah blah blah.

	tell application id "OOut"

		-- Blah blah blah.

		tell application "System Events to set frontmost of process "OmniOutliner" to true

		-- Blah blah blah.

	end tell

	--- Blah blah blah.

end tell

Preferable:

tell application "Skim"

	-- Blah blah blah.

end tell

tell application id "OOut"

	-- Blah blah blah.

	activate

	-- Blah blah blah.

end tell

tell application "Skim"

	--- Blah blah blah.

end tell
  1. This is definitely wrong:
else if level of row_last > 1 and has subtopics of preceding sibling of row_last contains {} or {false} then

Unfortunately, not having application id “OOut”, I don’t know what the values should be. But the logic syntax should be something like this:

else if level of row_last > 1 and (has subtopics of preceding sibling of row_last contains {} or has subtopics of preceding sibling of row_last contains {false}) then

That is, ‘if’ should be followed by booleans or by complete clauses which return booleans. Also, ‘and’ has a higher “precedence” than ‘or’, so if the overall condition is row_last’s level being greater than 1 and the ‘has topics’ value being either of two possibilities, the two ‘or’-ed conditions need to be bracketed together.

  1. With regard to your second post, the received wisdom is not to use globals unless absolutely necessary. They’re not necessary in your script in post #1,

I used nested tell statements, because I needed terms from one application, but I’m assuming there is a better way? I’ve come across using terms from. Would this apply in this situation?

As far as the original problem, instead of global I defined a local variable inside the else if statement as an empty list. Is there anything wrong with doing this?

			else if rgba is fav3 then
				
				set head_three to {}
				--if head_mark contains (name of named styles of style of row_last as string) then
				if previous_rows_style is in head_mark then
					set head_three to make new row with properties {topic:my titlecase(note_text, options_export), note:note_url} at end of last row's children of doc
				else if level of parent of last row of doc ≤ 2 then
					set head_three to make new row with properties {topic:my titlecase(note_text, options_export), note:note_url} at end of rows of parent of row_last
				else
					set head_three to make new row with properties {topic:my titlecase(note_text, options_export), note:note_url} at end of parent of last row's parent of doc
				end if
				
				add style_three to named styles of style of head_three
				
				
			else if rgba is fav4 then

Hello.

Usings terms from, or having nested tell blocks, may be there for different reasons. Sometimes using terms from are used, when you haven’t the app and want the script to compile. Nested tell blocks with System Events are special, since you can use System Events to inflict on an application process. In general, nested tell blocks to different apps are best avoided.

As for your second question, It is good practice to have variables with the smallest scope possible in genral. In your special case, it is unnecessary however, because you have an else clause in the if test, seeing to that the variable will always be assigned a value. The variable will be visible outside the if test, because the if test doesn’t define a scope.

This is easier shown than making you read up on variables and scope in the AppleScript Language guide. :slight_smile:

(But at some point in your scripting career, the AppleScript Language guide will be an invaluable read.)

to demonstrateScope()
	set theQuestion to "What is the meaning of life the universe and everything"
	
	if theQuestion is "How did we come here?" then
		set theAnswer to "20"
	else if theQuestion is "Where do we go?" then
		set theAnswer to "19"
	else if theQuestion is "What is the meaning of life the universe and everything" then
		set theAnswer to "42"
	else
		set theAnswer to "99"
	end if
	
	log theAnswer
end demonstrateScope

demonstrateScope()
--> 42

Edit

The variable above, is a local variable, since it is within a handler, now, if we didn’t use a handler, then the variables would be global, since they are declared in the implicit run handler of the script. Variables, that aren’t declared local in the run handler of a top level script are implicitly global.

set theQuestion to "What is the meaning of life the universe and everything"

if theQuestion is "How did we come here?" then
	set theAnswer to "20"
else if theQuestion is "Where do we go?" then
	set theAnswer to "19"
else if theQuestion is "What is the meaning of life the universe and everything" then
	set theAnswer to "42"
else
	set theAnswer to "99"
end if

log theAnswer
--> 42

An implicitly global is persistent between runs, like a declared global is, and a property, a property has some subtle differences to a global variable, a global variable, may be visible between scripts, a caller and a calle, whereas a property, must be addressed (theProp of callee) for it to work, a global, can sort of float in the common runtime context of the scripts.


try
	log theAnswer
	-- will give an andswer on the second run, since the value of the answer is now saved in the script context.
end try
set theQuestion to "What is the meaning of life the universe and everything"

if theQuestion is "How did we come here?" then
	set theAnswer to "20"
else if theQuestion is "Where do we go?" then
	set theAnswer to "19"
else if theQuestion is "What is the meaning of life the universe and everything" then
	set theAnswer to "42"
else
	set theAnswer to "99"
end if

log theAnswer
--> 42
# 42 (on the second run

Great explanation McUsr:)

In addition:

I agree, but still there is some confusing aspect about the property’s boundary. When I explicitly address the callee, we’ll get the expected results:

property aProperty : "Property Value"

script test
	return aProperty of me
end script

run script test

The script will return “Property Value” as expected. That is because aProperty can’t be found and will be looked up at the parent. Even if we address explicitly me as the object, a property will always follow the parenting chain, as in the ASLG described. The reason is that the parent is implicitly set to the script object containing script object test, which is the root script object.

So we expect an error if we break the parenting chain and say that an script object has no parent:

property aProperty : "Property Value"

script test
	property parent : missing value
	
	return aProperty of me
end script

run script test

As expected I get the error that aProperty can’t be found in missing value. Because the property aProperty is not defined by itself it will look up the parent which we set to missing value. Of course the missing value class is doesn’t contain a property aProperty

But what happens if we don’t address a property and it’s owner and still have the parent chain broken:

property aProperty : "Property Value"

script test
	property parent : missing value
	
	return aProperty
end script

run script test

The property will return the proper value when we use it like a global (not addressing it’s owner explicitly). So properties can behave like globals depending on how you get (call) them globally or not.

Hello Bazzie Wazzie.

Your last example flummoxed me, I have never tried that before, that replicates the use of a global I think, as long as you don’t load the script from a script object at least. Then I for one, prefer to use a global (when loading script objects), I haven’t investigated if your example works when loaded, but I see no reason really why it shouldn’t.

Edit

There is just one thing that may make a global more universal in scope in between script objects of different files, and that is, that the global is declared and is visible during compile time, whereas your last example with the property, won’t have a reference to the property visible in its scope when the script is compiled, possibly resulting in a compile error.

Hello.

I have been putting some more thought into this, since I have wondered a little about as to why undeclared variables slips by compile time, and generates an error at runtime.
I have no reasonable clue as to why that is, still, all the time the compiler, catches bad terminlogy.

But, there is the top-level bug, if you load a top-level script object into your script, the two top level “address-spaces” will be merged together. If you then have the same property declared in both scripts, then they will be merged into one, where one will overwrite the other.

I wonder, if that is why the compiler refuses to check for undeclared variables. Because the variable may exist in a to be merged address space.

Here, in this context of your example, the outwards scoping rule will find the property at the top level, because that is the last place it would look for it, since it is at the top of the parent child chain. If the script object had declared parent : AppleScript, then I guess it would result in an error when the script was run, because it can’t resolve it run time.
I am not sure if the parent chain is per say broken if you declare your child an orphan! I think that it will then default to the top-level object as its parent, like script objects within script objects that have no declared parent will.

That is what I thought too at first. But if that would be true, the following script won’t run. But on the contrary it still returns another script object’s property value.

script _parent
	property aProperty : "Property Value"
	on run
		script test
			property parent : missing value
			
			return aProperty
		end script
	end run
end script

run script (run script _parent)

So I’ll continue to save you some more research :slight_smile:

Then another problem could be that the property parent is only be set when the given value is of class script, there is some type checking involved. So I tested with two script objects where one is defined inside another and breaking the parent chain by skipping in the parenting chain. So my approach was creating an instance of child of child and setting the parent to top level script object. So logically I couldn’t get the property value of the first child script object and should return in an error when trying it to call from the child’s child.

Here the script I shows what I meant.

script _parent
	property aProperty : "Property Value"
	
	on newInstance(_parent)
		script test
			property parent : _parent -- var _parent is grandparent
			
			return aProperty
		end script
	end newInstance
end script

set childChild to _parent's newInstance(me)
-- only run the script if the parent of childChild is me
-- so we're sure the parent chain is broken
if childChild's parent = me then
	run script childChild
end if

If you run the script it’s given the wrong results if your analogy was right. As the script shows when the parenting chain is broken or set differently on purpose it doesn’t always follow the parenting chain as expected.

Back to my initial point: Properties can behave like globals or object properties depending on how they are are called. So the statement “whereas a property, must be addressed (theProp of callee) for it to work” is in my opinion false.

Actually, you caught me red handed, there, as I was far more scholastic, the empiristic.

Something has been changed along the way, in AS 2.3, before the implicit parent chain would have made a nested object the child of the top level object, that was an oddity, noone, not even Matt Neuburg could understand.

If it was so, then the script below would return toplevel, since the resolvment of polymorphism, necessarily must follow the parent chain.
Edited
That isn’t so, I omitted, any references to the parent chain, and the the outward scoping rule kicked in, the compiler, or run time, looked outwards for the first match, regardless of the implicit parent chain.

property level : "topLevel"
script secondLevel
	property level : "secondLevel"
	script thirdLevel
		on whichLevel()
			log "My parent is " & level
		end whichLevel
	end script
end script

secondLevel's thirdLevel's whichLevel()
-- (*My parent is secondLevel*)

So. Something has changed along the way. And thanks for enlightening me. :slight_smile:

Edit
Matt Neuburgs statements still holds, and it has nothing with polymorphism to do, the outwards scoping, kicked in, and the first property, that matched, was used.

When I ask for parent’s level, in an edited script, then it returns the “topLevel” as its parent.

property level : "topLevel"
script secondLevel
	property level : "secondLevel"
	script thirdLevel
		on whichLevel()
			log "My parent is " & parent's level
		end whichLevel
	end script
end script

secondLevel's thirdLevel's whichLevel()
-- (*My parent is topLevel*)
log secondLevel's thirdLevel's level
-- (*topLevel*) # Polymorphic - added this one later, to show that the parent chain kicked in, when 
# trying to resolve the property, from the "outside", and not from a handler "inside" the script object.

I also think it is fair to say that when a global is resolved, then it will be resolved directly at the toplevel, and that when resolving a property, it will be sought to be resolved, along the way, from some nested script, outwards in scope, until toplevel is reached.

Edited
I have just tried this, and any properties with the same name, will overshadow the same name as the global, so the outwards scoping rule takes precedence during resolving a variable , even if the compiler knows there is a global with the same name, that is used inside a script object.

global level
set level to "Universal"
script secondLevel
	property level : "secondLevel"
	script thirdLevel
		on whichLevel()
			log "My parent is " & level
		end whichLevel
	end script
end script

secondLevel's thirdLevel's whichLevel()
-- (*My parent is topLevel*)
log secondLevel's thirdLevel's level
-- (*topLevel*) # Polymorphic

I am not sure if I have ever written that, but at least in order to get a property, from a specific script-object, then that would be a wise thing to do. :slight_smile: If you have a parent chain, then you can of course access the property through the parent, as you can with me, if that is appropriate in some context.

At least properties and globals, are very much the same, it is just that globals (explicit) has a wider scope, with visibility outside a script object.
Both are saved in a “script context”. A property has other qualities, in that it can be used for polymorphism, which a global, realistically can not, (except for the top level, which really doesn’t give much leverage), both follows the outward scoping rule when they are sought resolved.

The examples I have mocked up, is no more than examples, in reality, you can’t have a nested child object, nested inside its parent object anyways, so this was just for showing/learning what kicked in. :slight_smile:

There is a difference between how properties can work and how properties “must” be used. When properties are called the way in your example you are explicitly addressing the objects. I’m just saying it’s not a “must” thing, properties can be called without addressing the owner, then they will behave like globals and are returned in the order they appear how they’re are compiled without regard of the parenting chain.

I won’t say Matt is wrong here or something. I mean when properties are used, the way properties of objects should be used, he’s absolutely right. If I put my in front of the return value then I would address a property of a script object the normal way. It will return an error like expected that aProperty can’t be found. The parenting chain is followed as expected. But when you call them as a global, it will work as a global.

I won’t argue if it’s not something that has always existed but it is something I have been using for a much longer time. I used properties this way as globals in old AppleScript-Studio projects (Mac OS X 10.3 - 10.6), so it must be there for a longer time (read: before AppleScript 2). I was using it for using singletons still having an init value and using them as globals throughout the entire application.c

See post #6, and my quote in my addition to your post :slight_smile:

I have edited my post above, heavily.

If I wrote “must” in the quote, then that is a bad thing, it is about finding ways to do stuff, that is appropriate in the problemsolving context.

Edit But I did write that in the context, of accessing properties from the “outside” of a script object, not through a handler of the script object.

One more thing, I believe that AppleScript studio properties, was different from AppleScript properties, in that their state wasn’t saved between runs, but I may be wrong. (I have also made some AppleScript Studio projects, one that involved tableviews, database events, and a bunch of presidents. :slight_smile:

Last thing. Matt Neuburgs, statement still holds, a nested script object, has still the top level script as its parent through the implicit parent chain. (I was wrong, I didn’t realize that I didn’t leverage the outward scoping rule, and not the implicit parent chain for starters, but all that should be fixed now in my post above. :slight_smile:

Hi guys. Some points to consider.

The scope of properties is global to the scripts or script objects in which they’re declared (except in handlers where the labels are used as parameter variables or are explicitly declared local).

BJ’s example script objects are instantiated within other scripts where property declarations have been made. Using these properties’ labels in the script objects too means the properties of the containing scripts (unless they’re redeclared as properties or something else in the script objects themselves). This is scope, not inheritance.

So changing a script object’s ‘parent’ doesn’t make any difference if the labels are used without reference to anything else. The only way ‘inherited’ properties can be accessed is by reference to their owners.


script fred
	property aProperty : "Property Value"
end script

script bert
	property parent : fred
	
	return aProperty
end script

bert's aProperty
--> "Property Value"

run script bert
--> error "The variable aProperty is not defined." number -2753

It doesn’t really matter if the script object is made using a handler or not for the parenting chain, that wasn’t really the point of it. It’s important to know when and when not the parenting chain is followed. Also to show when my and me should be used and when not. What happens when the parenting is not used. And knowing you don’t need to address the owner per se.

Because they were instantiated, like AppleScriptObjC, properties are not persistent. But when writing apps you should either way use NSUserDefaults instead and not properties.

Update:

Indeed :slight_smile:

Hello.

It is also a good thing that properties are global within the script object, because then you can make variables, that behaves like static in C for instance, in so far that it is only visible, to the handlers within the script object.

to makeCounter()
	script counter
		property lvalue : 0
		to increment()
			set lvalue to lvalue + 1
			return lvalue
		end increment
	end script
end makeCounter
set i to makeCounter()
log i's increment()
--> 1

What I started out with, when I continued or revived this thread, is that there is a phenomena called the “top level bug” in AppleScript, which means that the context of a toplevel script object, and any other script object’s context, are merged, unless the script object being loaded by load script takes some precautions in declaring its parent to be AppleScript and so on.

I suspect that since this is possible, then “anything goes” as far as the compiler is concern, and therefore, it can’t track uninitiated variables.

One more thing
The construct above is called a “closure” in Javascript, well, in AppleScript, you can use a global as a variable as a ‘label’ (rvalue) too, when setting a property of a script object, this is an AppleScript ‘closure’ which is like a ‘lisp closure’, the value of the free variable is thereafter ‘frozen’ to the value it had when it was used as a ‘label’ to set a value of a property in some script object.