2

こんにちは!この質問はかなり長いので、必ず最初に座ってください :)

コードのある時点で、ユーザーから文字列を取得し、その文字列を分析します。分析とは、すべての文字を調べて、ユーザーが NSPredicateEditor で設定した述語と照合することを意味します。述語は、次のようにプログラム的に設定されます。

NSArray* keyPaths = [NSArray arrayWithObjects:
                         [NSExpression expressionForKeyPath: @"Character"],
                         [NSExpression expressionForKeyPath: @"Character Before"],
                         [NSExpression expressionForKeyPath: @"Character After"],
                         nil];
// -----------------------------------------
NSArray* constants = [NSArray arrayWithObjects:
                      [NSExpression expressionForConstantValue: @"Any letter"],
                      [NSExpression expressionForConstantValue: @"Letter (uppercase)"],
                      [NSExpression expressionForConstantValue: @"Letter (lowercase)"],
                      [NSExpression expressionForConstantValue: @"Number"],
                      [NSExpression expressionForConstantValue: @"Alphanumerical"],
                      [NSExpression expressionForConstantValue: @"Ponctuation Mark"],
                      nil];
// -----------------------------------------
NSArray* compoundTypes = [NSArray arrayWithObjects:
                          [NSNumber numberWithInteger: NSNotPredicateType],
                          [NSNumber numberWithInteger: NSAndPredicateType],
                          [NSNumber numberWithInteger: NSOrPredicateType],
                          nil];
// -----------------------------------------
NSArray* operatorsA = [NSArray arrayWithObjects:
                       [NSNumber numberWithInteger: NSEqualToPredicateOperatorType],
                       [NSNumber numberWithInteger: NSNotEqualToPredicateOperatorType],
                       nil];

NSArray* operatorsB = [NSArray arrayWithObjects:
                       [NSNumber numberWithInteger: NSInPredicateOperatorType],
                       nil];
// -----------------------------------------
NSPredicateEditorRowTemplate* template1 = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions: keyPaths
                                                                                       rightExpressions: constants
                                                                                               modifier: NSDirectPredicateModifier 
                                                                                              operators: operatorsA
                                                                                                options: 0];

NSPredicateEditorRowTemplate* template2 = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions: keyPaths
                                                                           rightExpressionAttributeType: NSStringAttributeType
                                                                                               modifier: NSDirectPredicateModifier 
                                                                                              operators: operatorsB
                                                                                                options: 0];

NSPredicateEditorRowTemplate* compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes: compoundTypes];
// -----------------------------------------
NSArray* rowTemplates = [NSArray arrayWithObjects: template1, template2, compound, nil];

[myPredicateEditor setRowTemplates: rowTemplates];

3 つのキーパスと、それらを比較できるいくつかの定数があることがわかります。文字列を分析するとき、私は基本的に疑似コードでこれを行いたい:

originalString = [string from NSTextView]
for (char in originalString)
    bChar = [character before char]
    aChar = [character after char]

    predicate = [predicate from myPredicateEditor]

    // using predicate - problem!
    result = [evaluate predicate with:
               bChar somehow 'linked' to keypath 'Character Before'
               char 'linked' to 'Character'
               aChar 'linked' to 'Character After' // These values change, of course
              and also:
               constant "All letters" as "abc...WXYZ"
               constant "Numbers" as "0123456789"
               etc for all other constants set up  // These don't
              ]

    if (result) then do something with char, bChar and aChar

私の問題が基本的にどこにあるかを見ることができます:

  • 「文字の前/後」は、スペースのためにキーパスにすることはできませんが、ユーザーにとってより美しいので、そのままにしておきたいです (代わりに「文字の前」として何かがあると想像してください...)

  • 「Numbers」などの定数は、実際には「0123456789」などの文字列を表しますが、ユーザーにも表示できません

この問題の回避策を見つけることができましたが、現在はすべてのキャラクターで機能するわけではなく、非常に非効率的です (少なくとも私の意見では)。私がしていることは、述語から述語形式を取得し、置き換える必要があるものを置き換え、代わりにその新しい形式を評価することです。これを説明する実際のコードについては、次のとおりです。

#define kPredicateSubstitutionsDict [NSDictionary dictionaryWithObjectsAndKeys: \
@"IN 'abcdefghijklmnopqrstuvwxyz'", @"== \"Letter (lowercase)\"", \
@"IN 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'", @"== \"Letter (uppercase)\"", \
@"IN 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'", @"== \"Any letter\"", \
@"IN '1234567890'", @"== \"Number\"", \
@"IN 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'", @"== \"Alphanumerical\"", \
@"IN ',.;:!?'", @"== \"Ponctuation Mark\"", \
\
@"MATCHES '[^a-z]'"         , @"!= \"Letter (lowercase)\"", \
@"MATCHES '[^A-Z]'"         , @"!= \"Letter (uppercase)\"", \
@"MATCHES '[^a-zA-Z]'"      , @"!= \"Any letter\"", \
@"MATCHES '[^0-9]'"         , @"!= \"Number\"", \
@"MATCHES '[^a-zA-Z0-9]'"   , @"!= \"Alphanumerical\"", \
@"MATCHES '[^,\.;:!\?]'"  , @"!= \"Ponctuation Mark\"", \
\
nil]

// NSPredicate* predicate is setup in another place
// NSString* originalString is also setup in another place
NSString* predFormat = [predicate predicateFormat];
for (NSString* key in [kPredicateSubstitutionsDict allKeys]) {
    prefFormat = [predFormat stringByReplacingOccurrencesOfString: key withString: [kPredicateSubstitutionsDict objectForKey: key]];
}

for (NSInteger i = 0; i < [originalString length]; i++) {
    NSString* charString = [originalString substringWithRange: NSMakeRange(i, 1)];
    NSString* bfString;
    NSString* afString;

    if (i == 0) {
            bfString = @"";
    }
    else {
            bfString = [originalString substringWithRange: NSMakeRange(i - 1, 1)];
    }

    if (i == [originalString length] - 1) {
            afString = @"";
    }
    else {
            afString = [originalString substringWithRange: NSMakeRange(i + 1, 1)];
    }

    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character Before" withString: [NSString stringWithFormat: @"\"%@\"", bfString]];
    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character After" withString: [NSString stringWithFormat: @"\"%@\"", afString]];
    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character" withString: [NSString stringWithFormat: @"\"%@\"", charString]];

    NSPredicate* newPred = [NSPredicate predicateWithFormat: predFormat];
    if ([newPred evaluateWithObject: self]) { // self just so I give it something (nothing is actually gotten from self)
        // if predicate evaluates to true, then do something exciting!
    }
}

ですから、これは私がやっていることの単純化されたバージョンです。タイプミスが見られる場合は、おそらく私のコードには含まれていません。

要約する:

  • ユーザーが多くの文字に対して行う述語を評価し、可能な限り効率的にしようとしながら、かなり変更する必要があります
  • 私のアプローチで見つけた問題は次のとおりです。

    • 全然きれいじゃないと思う
    • すべてのケースで機能するという保証はありません (文字の 1 つが改行/入力文字の場合、述語は形式を理解できないというエラーを発生させます)。

それはすべての人々です!ここまで読んでくれてありがとう。この混乱を解決するとき、あなたの神があなたと共にありますように!

編集:関連することを明確にするために、この問題の引き金と思われるのは、述語エディターをセットアップする最初の時点で、名前を持つ1つの定数を定義できないという事実であると付け加えます(ユーザーに表示されます) ) およびその定数を表し、述語形式に挿入される値。キーパスについても同じです。1 つの表示名と、述語 ($VAR など) の var 文字列である 1 つの値を使用できれば、すべての問題が解決されます。これが可能であれば、方法を教えてください。それが不可能な場合は、私が説明する他の問題に集中してください。

4

2 に答える 2

3

だから主な質問:

NSPredicateEditorRowTemplateユーザー インターフェイスに表示されるものとは異なる定数を使用する を作成できますか?

答え: はい。以前はこれを Interface Builder で設定できましたが、Xcode 4 で設定しようとしてもうまくいきませんでした。IB => Xcode 4 マージで機能が削除された可能性があります。

幸いなことに、コードでそれを行うことができます。残念ながら、NSPredicateEditorRowTemplateサブクラスが必要です。そのようなサブクラスなしでそれを行う方法が見つかりませんでした。

-templateViews要点は、行テンプレートをオーバーライドして、次のようにすることです。

- (NSArray *)templateViews {
  NSArray *views = [super templateViews];
  // views[0] is the left-hand popup
  // views[1] is the operator popup
  // views[2] is the right-hand side (textfield, popup, whatever)
  NSPopUpButton *left = [views objectAtIndex:0];
  NSArray *items = [left itemArray];
  for (NSMenuItem *item in items) {
    //the representedObject of the item is the expression that will be used in the predicate
    NSExpression *keyPathExpression = [item representedObject];

    //we can alter the -title of the menuItem without altering the expression object
    if ([[keyPathExpression keyPath] isEqual:@"before"]) {
      [item setTitle:@"Character Before"];
    } else if ([[keyPathExpression keyPath] isEqual:@"after"]) {
      [item setTitle:@"Character After"];
    }
  }
  return views;
}

そして出来上がり!タイトルがキーパスと一致しない述語エディターのポップアップ。

編集

これを (サブクラス化せずに!) 解決するもう 1 つの方法は、カスタム テキストを .strings ファイルに置き、エディターを英語 (または任意の言語) に "ローカライズ" することです。


次の状況を想像してみてください。ユーザーが述語エディターにこの式 Character/is not/Number を入力します。その述語の形式は "character != "0123456789" です。文字が数字であっても、これは常に真です!これらの演算子を「置き換える」にはどうすればよいですか: is/is not を実際の関数 IN/NOT (IN . ..)?

これらの比較を述語で表現する場合、おそらく and を使用!=しないでしょう==。おそらく、次のようなことをしたいと思うでしょう:

character MATCHES '[0-9]'

そして、次のいずれか:

character MATCHES '[^0-9]'
NOT(character MATCHES '[0-9]')

このために、これを行うために考えられる方法は、2 つの別個の行テンプレートを用意することです。1 つは最初の形式 ( ) の述語を認識して表示しMATCHES '[0-9]'、2 番目の形式は否定を認識して表示します。あなたは の奇妙な領域に入り始めているNSPredicateEditorRowTemplatesと思います。あなたが何を達成しようとしているのか、なぜ文字列のすべての文字を述語と照合したいのか、まだよくわかりませんが、何でも構いません。

カスタム行テンプレートの作成方法の詳細については、次の 2 つのブログ投稿をご覧ください。

于 2011-06-01T21:21:58.197 に答える
1

ビジュアル テキストの設定にサブクラスが必要なのはなぜですか? クラスで次のようなメソッドを単純に作成する方が安価ではないでしょうか。

    - (void)setVisibleMenuTitlesInPredicate:(NSPredicateEditorRowTemplate *)predicateTemplate
    {
        NSArray *predicateTemplateViews = [predicateTemplate templateViews];
        NSPopUpButton *leftMenu = [predicateTemplateViews objectAtIndex:0];
        NSArray *leftMenuList = [leftMenu itemArray];
        for (NSMenuItem *menuItem in leftMenuList) {
            id keyPathValue = [menuItem representedObject];
            if ([[keyPathExpression keyPath] isEqual:@"before"]) {
                  [menuItem setTitle:@"Character Before"];
                } else if ([[keyPathExpression keyPath] isEqual:@"after"]) {
                  [menuItem setTitle:@"Character After"];
                }
    }

そして、setRowTemplates: の前に次のように呼び出します。

    [self setVisibleMenuTitlesInPredicate:predicateTemplateA];
于 2012-09-07T06:05:05.167 に答える