6

.sdefXMLファイルで記述されたAppleScriptディクショナリを備えたCocoaアプリケーションがあります。sdefで定義されているすべてのAppleScriptクラス、コマンドなどは動作プロパティです。

私の「フォームの送信」コマンドを除いて。「submitform」コマンドは、AppleScriptからCocoaへの情報の任意のハッシュテーブルであるパラメータを渡そうとする私の唯一のコマンドです。これは、Cocoa側でrecord自動的に変換されるAppleScriptを渡すことによって実行する必要があると思います。NSDictionary

tell application "Fluidium"
    tell selected tab of browser window 1
        submit form with name "foo" with values {bar:"baz"}
    end tell
end tell

「withvalues」パラメータは、問題が発生しているrecord->NSDictionaryパラメータです。レコード/辞書のキーを事前に知る/定義することはできないことに注意してください。それらは任意です。

これが私のsdefXMLでのこのコマンドの定義です。

<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>

そして、sdefにこのコマンドに応答する「tab」オブジェクトがあります。

<class name="tab" code="fTab" description="A browser tab.">
    ...
    <responds-to command="submit form">
        <cocoa method="handleSubmitFormCommand:"/>
    </responds-to>

とココア:

- (id)handleSubmitFormCommand:(NSScriptCommand *)cmd {
    ...
}

「tab」オブジェクトは、私が定義した他のすべてのAppleScriptコマンドに正しく応答します。オプションの「withvalues」パラメータを送信しない場合、「tab」オブジェクトは「submitform」コマンドにも応答します。だから私は基本的なセットアップが正しく行われていることを知っています。唯一の問題は、任意のrecord->NSDictionaryパラメータのようです。

上記のAppleScriptをで実行するとAppleScript Editor.app、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のオープンソースです。

http://github.com/itod/fluidium

4

4 に答える 4

7

Cocoaは、オブジェクトをAppleScript(AS)レコードにシームレスに変換NSDictionaryします。その逆の場合は、その方法を指示するだけで済みます。

まず、record-typeスクリプト定義(.sdef)ファイルでを定義する必要があります。

<record-type  name="http response" code="HTRE">
    <property name="success" code="HTSU" type="boolean"
        description="Was the HTTP call successful?"
    />

    <property name="method" code="HTME" type="text"
        description="Request method (GET|POST|...)."
    />

    <property name="code" code="HTRC" type="integer"
        description="HTTP response code (200|404|...)."
    >
        <cocoa key="replyCode"/>
    </property>

    <property name="body" code="HTBO" type="text"
        description="The body of the HTTP response."
    />
</record-type>

これnameは、この値がASレコードに持つ名前です。名前がNSDictionaryキーと等しい場合(上記の例では、、) 、<cocoa>タグは必要ありません。そうでない場合は、タグを使用して、この値を読み取るための正しいキーをCocoaに伝えることができます(上記の例では、AS内の名前です)。記録しますが、キーには代わりになります。ここでは、デモンストレーションの目的でこれを作成しました)。successmethodbody<cocoa>codeNSDictionaryreplyCode

このフィールドが持つASタイプをCocoaに伝えることは非常に重要です。そうしないと、Cocoaはその値をAS値に変換する方法を知りません。すべての値はデフォルトではオプションですが、存在する場合は、期待されるタイプである必要があります。最も一般的なFoundationタイプがASタイプとどのように一致するか(不完全)の小さな表を次に示します。

 AS Type     | Foundation Type
-------------+-----------------
 boolean     | NSNumber
 date        | NSDate
 file        | NSURL
 integer     | NSNumber
 number      | NSNumber
 real        | NSNumber
 text        | NSString

Appleの「Cocoaスクリプティングガイドの概要」の表1-1を参照してください。

もちろん、値自体を別のネストされたレコードにすることもできます。そのためにを定義し、仕様で名前をrecord-type使用すると、値で一致するディクショナリである必要があります。record-typepropertyNSDictionary

さて、完全なサンプルを試してみましょう。.sdefファイルで簡単なHTTPgetコマンドを定義しましょう。

<command name="http get" code="httpGET_">
    <cocoa class="HTTPFetcher"/>
    <direct-parameter type="text"
        description="URL to fetch."
    />
    <result type="http response"/>
</command>

次に、そのコマンドをObj-Cに実装する必要があります。これは非常に単純です。

#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;
}

もちろん、nil内部障害が発生した場合に戻ることは、エラー処理としては不適切です。代わりにエラーを返すことができます。ここで使用できるASの特別なエラー処理メソッド(たとえば、から継承した特定のプロパティの設定NSScriptCommand)もありますが、これは結局のところ単なるサンプルです。

最後に、それをテストするためのASコードが必要です。

tell application "MyCoolApp"
    set httpResp to http get "http://badserver.invalid"
end tell

結果:

{success:false}

予想通り、成功したのは次のとおりです。

tell application "MyCoolApp"
    set httpResp to http get "http://stackoverflow.com"
end tell

結果:

{success:true, body:"<!DOCTYPE html>...",  method:"GET", code:200}

また、予想通り。

しかし、待ってください、あなたはそれを逆に望んでいましたよね?さて、それも試してみましょう。タイプを再利用して、別のコマンドを作成します。

<command name="print http response" code="httpPRRE">
    <cocoa class="HTTPResponsePrinter"/>
    <direct-parameter type="http response"
        description="HTTP response to print"
    />
</command>

また、そのコマンドも実装します。

#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

そして、彼女は私たちのアプリがコンソールに記録するものです:

Dictionary is {
    body = "<!DOCTYPE html>...";
    method = GET;
    replyCode = 200;
    success = 1;
}

したがって、もちろん、それは両方の方法で機能します。

さて、これは実際には恣意的ではないことに不満を言うかもしれません。結局のところ、どのキーが存在するか(存在する可能性があります)、存在する場合はどのタイプになるかを定義する必要があります。あなたが正しいです。ただし、通常、データはそれほど恣意的ではありません。つまり、すべてのコードがデータを理解できる必要があるため、少なくとも特定の種類のルールとパターンに従う必要があります。

データ自体を理解せずに2つの明確に定義されたデータ形式間で変換するダンプツールのように、どのデータを期待するのか本当にわからない場合、なぜそれをレコードとして渡すのですか?そのレコードを簡単に解析可能な文字列値(プロパティリスト、JSON、XML、CSVなど)に変換してから、Cocoaを文字列として渡し、最後にオブジェクトに変換して戻しませんか?これは非常にシンプルですが、非常に強力なアプローチです。CocoaでのプロパティリストまたはJSONの解析は、おそらく4行のコードで実行されます。さて、これは最速のアプローチではないかもしれませんが、AppleScriptと高性能について一文で言及している人は、そもそも根本的な間違いを犯しています。AppleScriptは確かにたくさんあるかもしれませんが、「高速」はあなたが期待できる特性のどれでもありません。

于 2016-05-19T15:20:48.307 に答える
3

そうです-NSDictionariesとAppleScriptレコードは混ざり合っているように見えますが、実際には(NSDictionariesはオブジェクトキーを使用します-文字列など)、AppleScriptレコードが4文字の文字コードを使用します(AppleEvent / Classic Mac OSの遺産のおかげです)。

AppleのAppleScriptImplementerのメーリングリストでこのスレッドを参照してください

したがって、実際に行う必要があるのは、あなたの場合、持っているAppleScriptレコードを解凍し、それをNSDictionaryに変換することです。すべて自分でコードを書くこともできますが、それは複雑で、AEマネージャーを深く掘り下げます。

ただし、この作業は実際にはappscript / appscript-objcの一部のアンダーレイコードで実行されています(appscriptはPython、Ruby、Objective-Cのライブラリであり、実際にAppleScriptを使用しなくてもAppleScriptableアプリケーションと通信できます。appscript-objc Cocoa Scriptingを使用する場所で使用できますが、そのテクノロジーの厄介な制限は少なくなります。)

コードはsourceforgeで入手できます。数週間前に作成者にパッチを提出したので、appscript-objcの基盤を構築できます。この場合、必要なのはApplescript/AppleEventレコードをパックおよびアンパックすることだけです。

他のグーグルの場合、これを行う別の方法があります。それは、appscriptを使用しないことです:ToxicAppleEvents。そこには、辞書をAppleEventRecordsに変換する方法があります。

于 2010-01-23T19:17:20.823 に答える
3

ラップしている辞書のフィールドがわかっていて、AppleScriptにマップしたり、AppleScriptからマップしたいキーのタイプが予測可能である場合、Appleのドキュメントにリンクしている別の回答に記載されているように、レコード定義を使用するのが最善の解決策のようです。少なくとも個人的には、スクリプトガイドから完全に見逃していたこと。

上記の要件が何らかの理由でニーズに合わない場合、代替ソリューションは+scriptingRecordWithDescriptor:NSDictionaryのカテゴリとして実装することです。私はFluidiumプロジェクトでこの解決策を見つけました。これがNSDictionary+FUScripting.mからの貼り付けです:

@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

+ scriptingRecordWithDecriptor:を同等の種類のカスタムコマンドで使用すると、うまくいったことを確認できます。

于 2014-08-25T22:16:23.657 に答える
0

11.9.2016、Mac OS 10.11.6問題は次のとおりです。ココアの世界でAppleScriptレコードをNSDictionaryに変換する方法は?

AppleScriptレコードは、AppleScriptプロパティをキーとして使用し、数値または文字列を値として使用します。

NSDictionaryは、対応するココアキーをキー(NSStringオブジェクトの形式)として使用し、AppleScriptレコードの最も基本的な4つのタイプ(文字列、整数、倍精度、ブール値)のNSNumberまたはNSString値を使用します。

+(id)scriptingRecordWithDescriptor:(NSAppleEventDescriptor *)inDescの提案されたソリューションは、私の場合は機能しませんでした。

私の実装における基本的な変更は、AppleScript環境の各クラスが独自のプロパティとAppleScriptコードを定義することです。決定するキーオブジェクトは、AppleScriptコードとCocoaキーの間の関係を含むNSScriptClassDescriptionです。さらに複雑なのは、メソッドのパラメーターとして使用されるNSAppleEventDescriptorが、着信AppleScriptレコード(または私の場合はレコードのリスト)を表すことです。このNSAppleEventDescriptorは、さまざまな形式を持つことができます。

AppleScriptレコードの1つのエントリは特別です:{class: "scriptclassname"}。コードはその存在をテストします。

コードで行う必要がある唯一の置換は、「アップルスクリプトスイートの名前」の代わりに、アプリケーションのアップルスクリプトスイートの名前を導入することです。このメソッドは、NSDictionaryのカテゴリとして実装されます

#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
于 2016-09-11T17:42:26.980 に答える