Использование NSPredicate для анализа строк - PullRequest
2 голосов
/ 01 июня 2011

Хауди! Этот вопрос довольно длинный, поэтому сначала обязательно сядьте:)

В какой-то момент моего кода я получаю строку от пользователя и анализирую эту строку. Под анализом я подразумеваю прохождение каждого символа и сопоставление его с предикатом, заданным пользователем в 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];

Итак, вы можете видеть, что у меня есть три пути и несколько констант, с которыми их можно сравнить При анализе строки я в основном хочу сделать это в псевдокоде:

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

Вы можете видеть, в чем заключается моя проблема:

  • 'Character Before / After' не может быть путями к ключу из-за пробела, но я хочу сохранить его таким, чтобы он был более красивым для пользователя (представьте вместо этого что-то вроде 'characterBefore' ...)

  • Константы, такие как «Числа», фактически представляют строки, подобные «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!
    }
}

Итак, пошли, это упрощенная версия того, что я делаю. Если вы видите какие-либо опечатки, скорее всего, их нет в моем коде, потому что я немного их отредактировал, чтобы было проще.

Подведем итог:

  • Мне нужно оценить предикат, который пользователь делает для многих символов, немного его модифицировав, при этом стараясь быть максимально эффективным
  • Проблемы, с которыми я сталкиваюсь при моем подходе:

    • Я не думаю, что это чисто
    • У меня нет гарантии, что он будет работать в каждом случае (когда один из символов является символом перевода строки / ввода, предикат выдает ошибку, говоря, что не может понять формат)

Вот и все, ребята! Спасибо за чтение до сих пор. Пусть твой бог будет с тобой, когда решит этот беспорядок!

РЕДАКТИРОВАТЬ: просто чтобы прояснить ситуацию, я хотел бы добавить, что то, что кажется причиной этой проблемы, является тот факт, что я не могу, в самом начале, когда я настраиваю редактор предикатов, определить одну константу с именем (которое отображается пользователю) и значение, которое представляет эту константу и вставляется в формате предиката. То же самое и для ключевых путей: если бы я мог иметь одно отображаемое имя, а затем одно значение, которое было бы этими строками var для предиката ($ VAR или что-то еще), все проблемы были бы решены. Если это возможно, пожалуйста, скажите мне, как. Если это невозможно, то, пожалуйста, сосредоточьтесь на других проблемах, которые я описал.

Ответы [ 2 ]

3 голосов
/ 02 июня 2011

Итак, главный вопрос:

Можете ли вы построить NSPredicateEditorRowTemplate, который использует константы, отличные от отображаемых в пользовательском интерфейсе?

Ответ: да. Раньше вы могли настроить это в Интерфейсном Разработчике, но мои попытки сделать это в 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;
}

И вуаля! Всплывающее окно в редакторе предикатов, в котором заголовок не соответствует ключевому пути.

1020 * редактировать *

Другим способом решения этой проблемы (без подклассов!) Было бы поместить ваш пользовательский текст в файл .strings и «локализовать» ваш редактор на английский (или любой другой язык, который вы хотите).


Представьте себе такую ​​ситуацию: пользователь помещает в редактор предикатов это выражение Символ / не / Число. Формат для этого предиката будет «символ! =» 0123456789 «Это всегда верно, даже если символ является числом! Как я« заменяю »эти операторы: есть / нет с их реальными функциями IN / NOT (IN. ..)

Если бы вы выражали эти сравнения в предикате, вы, вероятно, не использовали бы != и ==. Вероятно, вы захотите сделать что-то вроде:

character MATCHES '[0-9]'

И один из:

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

Для этого я мог бы придумать два отдельных шаблона строк. Один будет распознавать и отображать предикаты в первом формате (MATCHES '[0-9]'), а второй распознает и отображает отрицание. Я думаю, вы начинаете проникать в странное царство NSPredicateEditorRowTemplates. Я до сих пор не совсем уверен, чего вы пытаетесь достичь и почему вы хотите сопоставлять каждый символ строки с предикатом, но неважно.

Для получения дополнительной информации о том, как создавать пользовательские шаблоны строк, посмотрите эти два сообщения в блоге:

1 голос
/ 07 сентября 2012

Почему подкласс необходим для настройки визуального текста?Разве не было бы дешевле просто создать метод в вашем классе, например:

    - (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];
...