Applescript - why can I not use classes which are dynamically assigned? - applescript

Why can't I do the bottom 2 programs, if I can do the top one in Applescript?
set _class to string
return ({"o", "k"} as string)
This works, but the bottom 2 don't.
set _class to string
return ({"o", "k"} as _class)
Or
set _class to string
return ({"o", "k"} as (class of "hello"))
The program doesn't let me compile neither of the bottom 2.

Because classes are evaluated at compile time and therefore must be static.

Since AppleScript's as operator is so crap, all you can really do is define your own handlers that perform the required conversions for you. (And since AS handlers aren't designed to be passed around as objects themselves, you need to wrap them in script objects as well. It's all very tedious.)
script StringCoercion
on coerceValue(theValue)
(* caution: to guarantee predictable behavior, always set
TIDs to a known value before coercing an unknown value
to a string, just in case that value is a list *)
set AppleScript's text item delimiters to ""
return theValue as string
end
end
script ListCoercion
on coerceValue(theValue)
return theValue as list
end
end
set _class to StringCoercion
return _class's coerceValue({"o", "k"})
--> "ok"
Or, if you don't need that much flexibility, just use a conditional block:
set _class to string
set theValue to {"o", "k"}
if _class is string then
return theValue as string
else if _class is list then
return theValue as list
else ...
Honestly, the more the more you think about these things, the more you realize just how poor AppleScript is for [i]everything[/i] that isn't automating applications. Unfortunately, the Apple-supplied alternatives to AS suck for automating applications, so it's pretty much Hobson's choice.
Honestly, best I can recommend is to make your AS code as simple, boring, and not-clever as you can, and hopefully it's less likely to blow up.

Related

Using one variable for multiple items data in descriptive programming

I know that with Descriptive programming you can do something like this:
Browser("StackOverflow").Page("StackOverflow").Link("text:=Go To Next Page ", "html tag:=A").Click
But is it possible to create some kind of string so I can assign more than one data value and pass it as single variable? I've tried many combinations using escape characters and I always get error.
For example in the case above, let's say I have more properties in the Page object, so I'd normally have to do something like this:
Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")...etc...
But I'd like to pass "name:=StackOverflow", "html id:=PageID" as a single variable, so when writing many objects I'd only have to write:
Browser(BrowserString).Page(PageString).WebEdit("name:=asdfgh")
And the first part would remain static, so if the parents' data needs to be modified I'd only have to modify two variables and not all the objects created in all libraries.
Is it possible?
If I was not clear enough please let me know.
Thank you in advance!
I think what you're looking for is UFT's Description object
This allows you finer grained control on the description since in descriptive programming all values are regular expressions but with Description you can turn the regular expression functionality off for a specific property.
Set desc = Description.Create()
desc("html tag").Value = "A"
desc("innertext").Value = "More information..."
desc("innertext").RegularExpression = False
Browser("Example Domain").Navigate "www.example.com"
Browser("Example Domain").Page("Example Domain").WebElement(desc).Click
If you want to represent this with plain string then it's a bit more of a problem, you can write a helper function but I'm not sure I would recommend it.
Function Desc(descString)
Set ret = Description.Create()
values = Split(descString, "::")
For Each value In values
keyVal = Split(value, ":=")
ret(keyVal(0)).Value = keyVal(1)
Next
Set Desc = ret
End Function
' Usage
Browser("StackOverflow").Page("StackOverflow").WebElement(Desc("html tag:=H2::innertext:=some text")).Click
Further reading about descriptive programming.
As an alternative to Motti's excellent answer, you could also Set a variable to match your initial descriptive object and then extend it as required:
Set myPage = Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")
after which you can then use
myPage.WebEdit("name:=asdfgh")
throughout the rest of the code, so long as the myPage object stays in scope...

Logic not working with List items in Applescript

This is a very strange problem I could not understand it, the code is very clear as you can see, I don't know if I am tired or could not see something... please tell me why I am getting False as a result, while it should be True, I have a list with one item and it is the exact one in the variable
thanks
property forbidenFolders : {"/Volumes/USERS/"}
set ff to "/Volumes/USERS/" as text
my isForbidenFolder(ff)
on isForbidenFolder(SelectedFolder)
repeat with i in forbidenFolders
log "forbiden folders: " & i
log "actual folder : " & SelectedFolder
if i = SelectedFolder then
log "this folder is forbiden"
return true
end if
end repeat
log "NOT forbiden"
return false
end isForbidenFolder
result here
That's the reference trap.
The syntax repeat with item in list iterates thru the list with references e.g. a reference to item 1 of list, a reference to item 2 of list etc. rather than the item itself.
To be able to check for equality you have to dereference the item using contents of
if contents of i = SelectedFolder then
When i is set to the item of a list in a repeat loop, you are getting a reference to the item. You need to coerce it into a string for your comparison.
if (i as string) = SelectedFolder

Cocoa Scripting: Use special string types like raw data

My app has some raw data content I want to be able to offer to AppleScript so that it can be at least looked at, if not even handled by saving it to a file or setting it to some other object that supports it.
Now, I don't understand which data type is used to accomplish that.
See this output from Script Editor, for instance:
tell application "Script Editor"
the clipboard as record
--> {Unicode text:"text",
«class BBLM»:«data BBLM6C6C756E»,
string:"text"}
end tell
How do I return these «data ...», which are apparently a combination of a 4-char-code and hex-string-encoded bytes of the actual data.
I've tried returning an NSData object containing the raw bytes data from my scriptable property, but that doesn't work.
Update
It appears it has to do with implementing scripting<type>Descriptor and scripting<type>WithDescriptor. I cannot find any documentation on this other than it being used in the Sketch sample code. I assume these will be invoked for the type if I happen to define such a custom type in my Sdef.
However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef. I'm more in the situation similar to the clipboard: I have clipboard-like data I want to return, so I only know their 4-char-types at runtime. Which means I won't be asked through these handlers. There must be some other way to generically create and receive these types, the same way the clipboard implementation does it.
RE: "...implementing scripting<Key>Descriptor and scripting<Key>WithDescriptor. I cannot find any documentation on this..."
The first place to start is "Key-Value Coding and Cocoa Scripting" section in the Cocoa Scripting Guide (2008). There are a whole slew of these methods that embed the type in the method name. Many are also documented in the Foundation's NSScriptKeyValueCoding Protocol Reference page, but you have to read the "Discussion" section to find them. For instance, in:
- (id)valueWithUniqueID:(id)uniqueID inPropertyWithKey:(NSString *)key
the discussion says: "The method valueIn<Key>WithUniqueID: is invoked if it exists."
So in a Widgets class, you would implement valueInWidgetsWithUniqueID:
scripting<Key>Descriptor and scripting<Key>WithDescriptor are special conversion handlers that are used when you use a element in your application's .sdef, which is why they show up in Sketch to handle the typeRGBColor data type, a list of 3 integers. I can't find these documented outside of the Sketch code either, but I can confirm that
scriptingRGBColorDescriptor
is called by methods in:
NSObject(NSScriptAppleEventConversion)
NSAppleEventDescriptor(NSScriptConversion)
RE: "However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef."
There is a way to solve that problem: you can return a special list structure known as a User-Field Record (typeUserField). This record includes alternating Key and Value descriptors, and does not require anything to be defined in the SDEF.
Here's an item I posted on the ASOC mailing list last year:
http://lists.apple.com/archives/applescriptobjc-dev/2015/Jan/msg00036.html
And here's the code (using AppleScript-ObjectiveC code) to build the typeUserField record from an NSDictionary.
# ASOC implementation of - (NSAppleEventDescriptor *)scriptingRecordDescriptor for an NSDictionary
# Creates an empty record descriptor and an empty list descriptor, then
# Iterates over the dictionary and inserts descriptors for each key and each value into the list descriptor
# Finally, populates the record descriptor with the type 'usrf' and the list descriptor
on makeUserRecordDescriptor(aDict)
log aDict
set recordDescriptor to aedClass's recordDescriptor()
set listDescriptor to aedClass's listDescriptor()
set typeUserField to 1970500198 -- 'usrf'
set itemIndex to 1 -- AS records are 1-based
repeat with aKey in aDict's allKeys()
set aVal to aDict's valueForKey_(aKey)
-- The values can be several different types. This code DOES NOT handle them all.
set isStringValue to aVal's isKindOfClass_(nssClass's |class|) = 1
set isNumericValue to aVal's isKindOfClass_(nsnClass's |class|) = 1
set isBooleanValue to aVal's className()'s containsString_("Boolean") = 1
-- Insert a descriptor for the key into the list descriptor
set anItem to aedClass's descriptorWithString_(aKey)
listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
set itemIndex to itemIndex + 1
-- Insert a descriptor (of the correct type for the value) into the list descriptor
if isStringValue
set anItem to aedClass's descriptorWithString_(aVal)
else if isBooleanValue
set anItem to aedClass's descriptorWithBoolean_(aVal's boolValue())
else if isNumericValue
set intValue to aVal's intValue()
set fpValue to aVal's doubleValue()
if intValue = fpValue
set anItem to aedClass's descriptorWithInt32_(aVal's intValue())
else
set anItem to aedClass's descriptorWithString_(aVal's stringValue) # TODO: 'doub'
end
else
set anItem to aedClass's descriptorWithString_("Unhandled Data Type")
end
listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
set itemIndex to itemIndex + 1
end
recordDescriptor's setDescriptor_forKeyword_(listDescriptor, typeUserField)
return recordDescriptor
end
The magic lies in using NSAppleEventDescriptor. It offers a lot of initializers. It's the one that eventually holds any value that gets passed back to the calling AppleScript (or JXA or whatever uses the Scripting engine).
Apparently, any value returned to the Cocoa Scripting layer, such as strings as NSString and numeric values as NSNumber, end up being assign to a NSAppleEventDescriptor object, and converted by that step to the AppleEvent-internal format.
So, if I want to return a string of bytes, e.g. stored in an NSData object, all I have to do is this from my property method:
-(id)returnSomeBytes {
return [NSAppleEventDescriptor descriptorWithDescriptorType:'Raw ', myNSDataObject];
}
This will end up in AppleScript as «data Raw ...».
I now also understand why the scripting engine won't automatically convert NSData for me: It needs a type code, which NSData doesn't inherit.
The reverse works just as well - any such raw data gets passed to my code as a NSAppleEventDescriptor, which I can then decode accordingly.

VB6 how to get the selected/checked control in a control array

I have to modify a VB6 app and am repeatedly beating my head against a wall over control arrays.
I know that the event handler for the array includes its index value and I can set some variable there, but i should be able to directly access the selected radio button in an array of OptionButton. Currently I'm doing this
For i = 0 To optView.Count - 1
If optView.Item(i).value = True Then
currIndex = i
Exit For
End If
Next
Is this really my only option?
Yes, this is our only option. The control array object does not contain any selecting logic (which makes sense, as "selected" might mean different things for different controls). The only change I'd make is replacing the For with For Each.
Another way to do this that I have used. Write a function, and then call the function, passing in the control name, to return the index number. Then you can reuse this in the future especially, if you add it to a module (.bas).
Function f_GetOptionFromControlArray(opts As Object) As Integer
' From http://support.microsoft.com/KB/147673
' This function can be called like this:
' myVariable = f_GetOptionFromControlArray(optMyButtons) 'Control syntax OK
' myVariable = f_GetOptionFromControlArray(optMyButtons()) 'Array syntax OK
On Error GoTo GetOptionFail
Dim opt As OptionButton
For Each opt In opts
If opt.Value Then
f_GetOptionFromControlArray = opt.Index
Exit Function
End If
Next
GetOptionFail:
f_GetOptionFromControlArray = -1
End Function

Is Nothing comparison gives type mismatch

I am trying to check if the 'Listivew.Tag property is nothing'.
I used to do the 'Is Nothing' check universally for all scenarios as first check to avoid errors
Can someone explain how to do it in VB 6?
If Not .lvwLocation.Tag Is Nothing Then
'COMPANY
str = str & IIf(Len(.lvwLocation.Tag) > 0, " and u.location_id in " & .lvwLocation.Tag, "")
End If
Gives error 'type-mismatch'
Nothing is a valid value for Object variables, and Is is the way to compare object pointers.
But a VB6 control's Tag property is a String, and VB6's String type is not an Object; it's a primitive type. That means a String variable can't be assigned Nothing -- its emptiest possible value is the empty string. (And an Object variable can't be assigned a String value.) For strings just use the same equality/inequality/comparision operators that you use for other primitive (numeric/boolean/date) types:
If .lvwLocation.Tag <> "" Then ...
In VB6 it appears that using Is Nothing to compare Objects works, Every other data type that I tried did not. In .Net Nothing represents the default value of any data type and will work like you expect.
Dim test as Object
If Not test Is Nothing Then
/////
End If
Since it appears the data type of th Tag property in VB6 is a string. I would use something like:
If .lvwLocation.Tag <> "" Then
/////
End If

Resources