Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/macos/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/vim/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Macos 如何在可编写脚本的应用程序中将任意AppleScript记录传递给Cocoa?_Macos_Cocoa_Applescript_Cocoa Scripting - Fatal编程技术网

Macos 如何在可编写脚本的应用程序中将任意AppleScript记录传递给Cocoa?

Macos 如何在可编写脚本的应用程序中将任意AppleScript记录传递给Cocoa?,macos,cocoa,applescript,cocoa-scripting,Macos,Cocoa,Applescript,Cocoa Scripting,我有一个Cocoa应用程序,其中有一个.sdef XML文件中描述的AppleScript字典。sdef中定义的所有AppleScript类、命令等都是工作属性 除了我的“提交表单”命令。“submitform”命令是我唯一一个试图将参数传递给Cocoa的命令,该参数是从AppleScript到Cocoa的任意信息哈希表。我假设这应该通过传递一条AppleScript记录来完成,该记录将自动转换为Cocoa端的NSDictionary tell application "Fluidium"

我有一个Cocoa应用程序,其中有一个.sdef XML文件中描述的AppleScript字典。sdef中定义的所有AppleScript类、命令等都是工作属性

除了我的“提交表单”命令。“submitform”命令是我唯一一个试图将参数传递给Cocoa的命令,该参数是从AppleScript到Cocoa的任意信息哈希表。我假设这应该通过传递一条AppleScript
记录来完成,该记录将自动转换为Cocoa端的
NSDictionary

tell application "Fluidium"
    tell selected tab of browser window 1
        submit form with name "foo" with values {bar:"baz"}
    end tell
end tell
“with values”参数是我遇到问题的
record
->
NSDictionary
参数。请注意,记录/字典的键不能预先知道/定义。他们是武断的

以下是此命令在我的sdef XML中的定义:

<command name="submit form" code="FuSSSbmt" description="...">
    <direct-parameter type="specifier" optional="yes" description="..."/>
    <parameter type="text" name="with name" code="Name" optional="yes" description="...">
        <cocoa key="name"/>
    </parameter>
    <parameter type="record" name="with values" code="Vals" optional="yes" description="...">
        <cocoa key="values"/>
    </parameter>
</command>
“tab”对象正确响应我定义的所有其他AppleScript命令。如果我没有发送可选的“with values”参数,“tab”对象也会响应“submit form”命令。所以我知道我的基本设置是正确的。唯一的问题似乎是任意的
记录
->
NSDictionary
参数

当我在
AppleScript Editor.app
中执行上面的AppleScript时,我在Cocoa端得到以下错误:

+[NSDictionary scriptingRecordWithDescriptor:]: unrecognized selector sent to class 0x7fff707c6048
这张在AppleScript侧:

error "Fluidium got an error: selected tab of browser window 1 doesn’t understand the submit form message." number -1708 from selected tab of browser window 1
谁能告诉我我错过了什么?作为参考,整个应用程序在GitHub上是开源的:

没错——NSDictionary和AppleScript记录似乎会混合在一起,但实际上它们不会(NSDictionary使用对象键——比如字符串),而AppleScript记录使用四个字母的字符代码(这要感谢它们的AppleEvent/Classic Mac OS传统)

因此,在您的例子中,您实际需要做的是解压缩您拥有的AppleScript记录并将其翻译到您的NSDictionary中。您可以自己编写代码,但这很复杂,而且需要深入AE经理

然而,这项工作实际上已经在一些用于(appscript是Python、Ruby和Objective-C的一个库,它允许您与AppleScriptable应用程序进行通信,而无需实际使用AppleScript。appscript objc可以用于使用Cocoa脚本的地方,但该技术的缺点较少。)

代码是。几周前我向作者提交了一个补丁,这样你就可以为AppScript Objc构建底层的基础,这就是你需要的所有东西:你需要做的就是打包和解压Apple脚本/ Apple事件记录。


对于其他谷歌用户来说,还有另一种方法可以做到这一点,那就是不使用appscript:。其中有一种方法可以将字典转换为Apple事件记录。

如果您知道要包装的字典中的字段,并且要映射到AppleScript或从AppleScript映射的键的类型是可预测的,那么最好的解决方案似乎如前所述在另一个答案中,它也有助于链接到苹果的文档,而我本人至少完全没有从脚本指南中找到这些文档

如果上述要求因任何原因不符合您的需要,另一种解决方案是将
+scriptingRecordWithDescriptor:
作为NSDictionary的一个类别来实现。我在项目中找到了此解决方案,该问题涉及到。下面是来自:

@实现NSDictionary(FUScripting)
+(id)scriptingRecordWithDescriptor:(NSAppleEventDescriptor*)索引{
//NSLog(@“inDesc:%@”,inDesc);
NSMutableDictionary*d=[NSMutableDictionary];
NSAppleEventDescriptor*带值SPARAM=[inDesc描述符工作项:'usrf'];/“usrf”键控记录字段
//NSLog(@“withValuesParam:%@”,withValuesParam);
NSString*name=nil;
NSString*值=零;
//这是一个索引!
NSInteger i=1;
NSInteger count=[withValuesParam numberOfItems];

对于(;iCocoa将无缝地将
NSDictionary
对象转换为AppleScript(AS)记录,反之亦然,您只需告诉它如何执行即可

首先,您需要在脚本定义(
.sdef
)文件中定义一个
记录类型
,例如


名称
是此值在AS记录中的名称。如果名称等于
NSDictionary
键,则不需要
标记(
成功
方法
正文
,在上面的示例中),如果不是,则可以使用
标记告诉Cocoa读取此值的正确键(在上面的示例中,
code
是AS记录中的名称,但在
NSDictionary
中,键将改为
replyCode
;我在这里只是为了演示而做的)

<> P>非常重要的是,告诉可可这个字段应该是什么类型,否则可可不知道如何把这个值转换成As值。默认情况下所有的值都是可选的,但是如果它们存在,它们必须具有预期的类型。这里是一个小表,说明最常见的基础类型与类型(不完全)匹配:

类型>基础型 -------------+----------------- 布尔数 日期| NSDate 文件| NSURL 整数| n个数 编号| NSNumber 实数 文本| NSString
请参阅苹果的“Cocoa脚本指南简介”

当然,一个值本身可以是另一个嵌套记录,只需为它定义一个
记录类型
,在
属性
规范中使用
记录类型
名称,然后在
NSDictionary
中,该值必须是匹配的字典

好的,让我们尝试一个完整的示例。让我们在
.sdef
文件中定义一个简单的HTTP get命令:


现在我们需要实施
+[NSDictionary scriptingRecordWithDescriptor:]: unrecognized selector sent to class 0x7fff707c6048
error "Fluidium got an error: selected tab of browser window 1 doesn’t understand the submit form message." number -1708 from selected tab of browser window 1
@implementation NSDictionary (FUScripting)

+ (id)scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)inDesc {
    //NSLog(@"inDesc: %@", inDesc);

    NSMutableDictionary *d = [NSMutableDictionary dictionary];

    NSAppleEventDescriptor *withValuesParam = [inDesc descriptorForKeyword:'usrf']; // 'usrf' keyASUserRecordFields
    //NSLog(@"withValuesParam: %@", withValuesParam);

    NSString *name = nil;
    NSString *value = nil;

    // this is 1-indexed!
    NSInteger i = 1;
    NSInteger count = [withValuesParam numberOfItems];
    for ( ; i <= count; i++) {
        NSAppleEventDescriptor *desc = [withValuesParam descriptorAtIndex:i];
        //NSLog(@"descriptorAtIndex: %@", desc);

        NSString *s = [desc stringValue];
        if (name) {
            value = s;
            [d setObject:value forKey:name];
            name = nil;
            value = nil;
        } else {
            name = s;
        }
    }

    return [d copy];
}

@end
#import <Foundation/Foundation.h>

// The code below assumes you are using ARC (Automatic Reference Counting).
// It will leak memory if you don't!

// We just subclass NSScriptCommand
@interface HTTPFetcher : NSScriptCommand
@end


@implementation HTTPFetcher

static NSString
    *const SuccessKey   = @"success",
    *const MethodKey    = @"method",
    *const ReplyCodeKey = @"replyCode",
    *const BodyKey      = @"body"
;

// This is the only method we must override
- (id)performDefaultImplementation {
    // We expect a string parameter
    id directParameter = [self directParameter];
    if (![directParameter isKindOfClass:[NSString class]]) return nil;

    // Valid URL?
    NSString * urlString = directParameter;
    NSURL * url = [NSURL URLWithString:urlString];
    if (!url) return @{ SuccessKey : @(false) };

    // We must run synchronously, even if that blocks main thread
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    if (!sem) return nil;

    // Setup the simplest HTTP get request possible.
    NSURLRequest * req = [NSURLRequest requestWithURL:url];
    if (!req) return nil;

    // This is where the final script result is stored.
    __block NSDictionary * result = nil;

    // Setup a data task
    NSURLSession * ses = [NSURLSession sharedSession];
    NSURLSessionDataTask * tsk = [ses dataTaskWithRequest:req
        completionHandler:^(
            NSData *_Nullable data,
            NSURLResponse *_Nullable response,
            NSError *_Nullable error
        ) {
            if (error) {
                result = @{ SuccessKey : @(false) };

            } else {
                NSHTTPURLResponse * urlResp = (
                    [response isKindOfClass:[NSHTTPURLResponse class]] ?
                    (NSHTTPURLResponse *)response : nil
                );

                // Of course that is bad code! Instead of always assuming UTF8
                // encoding, we should look at the HTTP headers and see if
                // there is a charset enconding given. If we downloaded a
                // webpage it may also be found as a meta tag in the header
                // section of the HTML. If that all fails, we should at
                // least try to guess the correct encoding.
                NSString * body = (
                    data ?
                    [[NSString alloc]
                        initWithData:data encoding:NSUTF8StringEncoding
                    ]
                    : nil
                );

                NSMutableDictionary * mresult = [
                    @{ SuccessKey: @(true),
                        MethodKey: req.HTTPMethod
                    } mutableCopy
                ];
                if (urlResp) {
                    mresult[ReplyCodeKey] = @(urlResp.statusCode);
                }
                if (body) {
                    mresult[BodyKey] = body;
                }
                result = mresult;
            }

            // Unblock the main thread
            dispatch_semaphore_signal(sem);
        }
    ];
    if (!tsk) return nil;

    // Start the task and wait until it has finished
    [tsk resume];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    return result;
}
tell application "MyCoolApp"
    set httpResp to http get "http://badserver.invalid"
end tell
tell application "MyCoolApp"
    set httpResp to http get "http://stackoverflow.com"
end tell
#import <Foundation/Foundation.h>

@interface HTTPResponsePrinter : NSScriptCommand
@end


@implementation HTTPResponsePrinter

- (id)performDefaultImplementation {
    // We expect a dictionary parameter
    id directParameter = [self directParameter];
    if (![directParameter isKindOfClass:[NSDictionary class]]) return nil;

    NSDictionary * dict = directParameter;
    NSLog(@"Dictionary is %@", dict);
    return nil;
}

@end
tell application "MyCoolApp"
    set httpResp to http get "http://stackoverflow.com"
    print http response httpResp
end tell
#import "NSDictionary+AppleScript.h"

@implementation NSDictionary (AppleScript)

// returns a Dictionary from a apple script record
+ (NSArray <NSDictionary *> * )scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)anEventDescriptor {
    NSScriptSuiteRegistry * theRegistry = [NSScriptSuiteRegistry sharedScriptSuiteRegistry] ;

    DescType theScriptClassDescriptor = [anEventDescriptor descriptorType] ;

    DescType printDescriptorType = NSSwapInt(theScriptClassDescriptor) ;
    NSString * theEventDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
    //NSLog(@"Event descriptor type: %@", theEventDescriptorType) ; // "list" if a list, "reco" if a simple record , class identifier if a class

    // Forming a list of AppleEventDescriptors
    NSInteger i ;
    NSAppleEventDescriptor * aDescriptor ;
    NSMutableArray <NSAppleEventDescriptor*> * listOfEventDescriptors = [NSMutableArray array] ;
    if ([theEventDescriptorType isEqualToString:@"list"]) {
        NSInteger numberOfEvents = [anEventDescriptor numberOfItems] ;
        for (i = 1 ; i <= numberOfEvents ; i++) {
            aDescriptor = [anEventDescriptor descriptorAtIndex:i] ;
            if (aDescriptor) [listOfEventDescriptors addObject:aDescriptor] ;
        }
    }
    else [listOfEventDescriptors addObject:anEventDescriptor] ;

    // transforming every NSAppleEventDescriptor into an NSDictionary - key: cocoa key - object: NSString - the parameter value as string
    NSMutableArray <NSDictionary *> * theResult = [NSMutableArray arrayWithCapacity:listOfEventDescriptors.count] ;
    for (aDescriptor in listOfEventDescriptors) {
        theScriptClassDescriptor = [aDescriptor descriptorType] ;

        DescType printDescriptorType = NSSwapInt(theScriptClassDescriptor) ;
        NSString * theEventDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
        //NSLog(@"Event descriptor type: %@", theEventDescriptorType) ;

        NSMutableDictionary * aRecord = [NSMutableDictionary dictionary] ;
        NSInteger numberOfAppleEventItems = [aDescriptor numberOfItems] ;
        //NSLog(@"Number of items: %li", numberOfAppleEventItems) ;

        NSScriptClassDescription * (^determineClassDescription)() = ^NSScriptClassDescription *() {
            NSScriptClassDescription * theResult ;

            NSDictionary * theClassDescriptions = [theRegistry classDescriptionsInSuite:@"Arcadiate Suite"] ;
            NSArray * allClassDescriptions = theClassDescriptions.allValues ;
            NSInteger numOfClasses = allClassDescriptions.count ;
            if (numOfClasses == 0) return theResult ;

            NSMutableData * thePropertiesCounter = [NSMutableData dataWithLength:(numOfClasses * sizeof(NSInteger))] ;
            NSInteger *propertiesCounter = [thePropertiesCounter mutableBytes] ;
            AEKeyword aKeyWord  ;
            NSInteger classCounter = 0 ;
            NSScriptClassDescription * aClassDescription ;
            NSInteger i ;
            NSString * aCocoaKey ;
            for (aClassDescription in allClassDescriptions) {
                for (i = 1 ; i <= numberOfAppleEventItems ; i++) {
                    aKeyWord = [aDescriptor keywordForDescriptorAtIndex:i] ;
                    aCocoaKey = [aClassDescription keyWithAppleEventCode:aKeyWord] ;
                    if (aCocoaKey.length > 0) propertiesCounter[classCounter] ++ ;
                }
                classCounter ++ ;
            }
            NSInteger maxClassIndex = NSNotFound ;
            for (i = 0 ; i < numOfClasses ; i++) {
                if (propertiesCounter[i] > 0) {
                    if (maxClassIndex != NSNotFound) {
                        if (propertiesCounter[i] > propertiesCounter[maxClassIndex]) maxClassIndex = i ;
                    }
                    else maxClassIndex = i ;
                }
            }
            //NSLog(@"Max class index: %li", maxClassIndex) ;
            //if (maxClassIndex != NSNotFound) NSLog(@"Number of matching properties: %li", propertiesCounter[maxClassIndex]) ;
            if (maxClassIndex != NSNotFound) theResult = allClassDescriptions[maxClassIndex] ;
            return theResult ;
        } ;

        NSScriptClassDescription * theRelevantScriptClass ;
        if ([theEventDescriptorType isEqualToString:@"reco"]) theRelevantScriptClass = determineClassDescription() ;
        else theRelevantScriptClass = [theRegistry classDescriptionWithAppleEventCode:theScriptClassDescriptor] ;
        if (theRelevantScriptClass) {
        //NSLog(@"Targeted Script Class: %@", theRelevantScriptClass) ;

            NSString * aCocoaKey, *stringValue ;
            NSInteger integerValue ;
            BOOL booleanValue ;
            id aValue ;
            stringValue = [theRelevantScriptClass implementationClassName] ;
            if (stringValue.length > 0) aRecord[@"className"] = aValue ;
            AEKeyword aKeyWord ;
            NSAppleEventDescriptor * parameterDescriptor ;
            NSString * printableParameterDescriptorType ;
            DescType parameterDescriptorType ;
            for (i = 1 ; i <= numberOfAppleEventItems ; i++) {
                aValue = nil ;
                aKeyWord = [aDescriptor keywordForDescriptorAtIndex:i] ;
                aCocoaKey = [theRelevantScriptClass keyWithAppleEventCode:aKeyWord] ;
                parameterDescriptor = [aDescriptor paramDescriptorForKeyword:aKeyWord] ;
                parameterDescriptorType = [parameterDescriptor descriptorType] ;
                printDescriptorType = NSSwapInt(parameterDescriptorType) ;
                printableParameterDescriptorType = [[NSString alloc] initWithBytes:&printDescriptorType length:sizeof(DescType) encoding:NSUTF8StringEncoding] ;
                //NSLog(@"Parameter type: %@", printableParameterDescriptorType) ;

                if ([printableParameterDescriptorType isEqualToString:@"doub"]) {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = @([stringValue doubleValue]) ;
                    }
                }
                else if ([printableParameterDescriptorType isEqualToString:@"long"]) {
                    integerValue = [parameterDescriptor int32Value] ;
                    aValue = @(integerValue) ;
                }
                else if ([printableParameterDescriptorType isEqualToString:@"utxt"]) {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = stringValue ;
                    }
                }
                else if ( ([printableParameterDescriptorType isEqualToString:@"true"]) || ([printableParameterDescriptorType isEqualToString:@"fals"]) ) {
                    booleanValue = [parameterDescriptor booleanValue] ;
                    aValue = @(booleanValue) ;
                }
                else {
                    stringValue = [parameterDescriptor stringValue] ;
                    if (stringValue.length > 0) {
                        aValue = stringValue ;
                    }
                }
                if ((aCocoaKey.length != 0) && (aValue)) aRecord[aCocoaKey] = aValue ;
            }
        }
        [theResult addObject:aRecord] ;
    }
    return theResult ;
}
@end