你好!这个问题很长,所以一定要先坐好:)
在我的代码中,有一次我从用户那里得到一个字符串,然后分析这个字符串。我所说的分析是指遍历每个字符,并将其与用户在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"代替…)
像"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!
}
}
所以,给你,这是我正在做的事情的简化版本。如果你看到任何拼写错误,很可能它们不在我的代码中,因为我已经编辑了很多,所以它会更简单。
总结:
- 我需要评估用户针对许多字符做出的谓词,对其进行大量修改,同时尽可能提高效率
我发现我的方法存在以下问题:
- 我觉得一点都不干净
- 我不能保证它适用于所有情况(当其中一个字符是换行符/回车符时,谓词会引发一个错误,说它无法理解格式)
这就是所有的人!感谢您到目前为止的阅读。愿你的上帝与你同在,解决这场混乱!
编辑:为了澄清问题,我想补充一点,这个问题的导火索似乎是,在我设置谓词编辑器的一开始,我就不能定义一个常量,它有一个名称(显示给用户)和一个表示该常量的值,并以谓词格式插入。关键路径也是如此:如果我可以有一个显示名称,然后有一个值,即谓词的var字符串($var或其他什么),那么所有问题都会得到解决。如果可以的话,请告诉我怎么做。如果这是不可能的,那么请关注我描述的其他问题。
所以主要问题是:
你能构建一个使用与用户界面中显示的不同常量的
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;
}
瞧!谓词编辑器中标题与关键路径不匹配的弹出窗口。
编辑
解决这个问题的另一种方法(不需要子类化!)是将自定义文本放在.strings文件中,并将编辑器"本地化"为英语(或您想要的任何语言)。
- 本地化
NSPredicateEditor
NSPredicateEditor
本地化后续工作
想象一下这种情况:用户在谓词编辑器中放入以下表达式Character/is-not/Number。该谓词的格式将是"character!="0123456789"这总是正确的,即使字符是数字!我如何"替换"这些运算符:is/is不是用它们的实函数IN/not(IN…)?
如果要在谓词中表达这些比较,则可能不会使用!=
和==
。很可能,你会想做一些类似的事情:
character MATCHES '[0-9]'
其中之一:
character MATCHES '[^0-9]'
NOT(character MATCHES '[0-9]')
为此,我可以想到的方法是使用两个单独的行模板。一种是以第一种格式(MATCHES '[0-9]'
)识别并显示谓词,而第二种格式则识别并显示否定。我想,你开始进入NSPredicateEditorRowTemplates
这个奇怪的领域了。我仍然不确定你想要实现什么,以及为什么你想将字符串的每个字符与谓词进行匹配,但不管怎样。
有关如何创建自定义行模板的更多信息,请查看以下两篇博客文章:
- 创建一个简单的
NSPredicateEditorRowTemplate
- 创建高级
NSPredicateEditorRowTemplate
为什么子类是设置视觉文本所必需的?在你的类中简单地创建一个方法,比如:,不是更便宜吗
- (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];