2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
24 @interface EOQualifier(PrivateMethodes)
26 - (NSString *)qualifierDescription;
28 - (NSException *)invalidImap4SearchQualifier:(NSString *)_reason;
30 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search
31 insertNot:(BOOL)_insertNot;
32 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search;
36 @implementation EOQualifier(IMAPAdditions)
38 - (BOOL)isImap4UnseenQualifier { /* a special key/value qualifier */
42 /* building search qualifiers */
44 static NSArray *FlagKeyWords = nil;
45 static NSArray *OtherKeyWords = nil;
46 static BOOL debugOn = NO;
48 static void _initImap4SearchCategory(void) {
51 if (FlagKeyWords) return;
53 ud = [NSUserDefaults standardUserDefaults];
54 FlagKeyWords = [[NSArray alloc] initWithObjects: @"answered", @"deleted",
55 @"draft", @"flagged", @"new", @"old", @"recent",
56 @"seen", @"unanswered", @"undeleted", @"undraft",
57 @"unflagged", @"unseen", nil];
58 OtherKeyWords = [[NSArray alloc] initWithObjects:
59 @"bcc", @"body", @"cc", @"from", @"subject",
60 @"text", @"to", @"keyword", @"unkeyword", nil];
62 debugOn = [ud boolForKey:@"ImapDebugQualifierGeneration"];
65 - (NSException *)invalidImap4SearchQualifier:(NSString *)_reason {
66 if (_reason == nil) _reason = @"unknown reason";
67 return [NSException exceptionWithName:@"NGImap4SearchQualifierException"
72 - (BOOL)isImap4NotQualifier {
75 - (BOOL)isImap4KeyValueQualifier {
79 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search
80 insertNot:(BOOL)_insertNot
82 return [self invalidImap4SearchQualifier:@"expected key/value qualifier"];
84 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
85 return [self appendToImap4SearchString:_search insertNot:NO];
88 - (id)imap4SearchString { /* returns exception on fail */
89 [self logWithFormat:@"ERROR(%s): subclass %@ must overide this method!",
90 __PRETTY_FUNCTION__, [self class]];
94 @end /* EOQualifier(IMAPAdditions) */
97 @implementation EOAndQualifier(IMAPAdditions)
99 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
103 quals = [self qualifiers];
105 if ((lCount = [quals count]) == 0) /* no subqualifiers */
108 // TODO: use appendToImap4SearchString?
109 [_search appendString:[[quals objectAtIndex:0] imap4SearchString]];
113 for (i = 0; i < lCount; i++) {
114 EOQualifier *qualifier;
117 qualifier = [quals objectAtIndex:i];
119 [self logWithFormat:@" append subqualifier: %@", qualifier];
121 [_search appendString:(i == 0) ? @"(" : @" ("];
122 if ((error = [qualifier appendToImap4SearchString:_search]))
124 [_search appendString:@")"];
127 return nil /* no error */;
130 - (id)imap4SearchString { /* returns exception on fail */
131 NSMutableString *search;
135 _initImap4SearchCategory();
139 @"generate IMAP4 expression for AND qualifier: %@", self];
142 if ((lCount = [[self qualifiers] count]) == 0) /* no subqualifiers */
145 return [[[self qualifiers] objectAtIndex:0] imap4SearchString];
147 search = [NSMutableString stringWithCapacity:lCount * 3];
149 if ((error = [self appendToImap4SearchString:search]) != nil) {
150 if (debugOn) [self logWithFormat:@" error: %@", error];
155 [self logWithFormat:@" generated: '%@'", search];
160 @end /* EOAndQualifier(IMAPAdditions) */
163 @implementation EOOrQualifier(IMAPAdditions)
165 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
166 // TODO: move generation to this method
169 s = [self imap4SearchString];
170 if ([s isKindOfClass:[NSException class]])
173 [_search appendString:s];
177 - (id)imap4SearchString { /* returns exception on fail */
179 NSMutableString *search;
183 _initImap4SearchCategory();
187 @"generate IMAP4 expression for or-qualifier: %@", self];
190 quals = [self qualifiers];
192 if ((lCount = [quals count]) == 0) /* no subqualifiers */
195 return [[quals objectAtIndex:0] imap4SearchString];
197 search = [NSMutableString stringWithCapacity:lCount * 32];
200 Note: or queries are specified as:
201 OR <search-key1> <search-key2>
202 so we need to wrap more ORs in multiple "OR" IMAP4 expressions
203 eg: "OR (OR (subject "abc") (subject "nbc")) from "duck""
206 if ((error = [[quals objectAtIndex:0] appendToImap4SearchString:search]))
209 for (i = 1; i < lCount; i++) {
210 EOQualifier *qualifier;
212 qualifier = [quals objectAtIndex:i];
213 [search insertString:@"OR (" atIndex:0];
214 [search appendString:@") ("];
215 if ((error = [qualifier appendToImap4SearchString:search]))
217 [search appendString:@")"];
221 [self logWithFormat:@" generated: '%@'", search];
225 @end /* EOOrQualifier(IMAPAdditions) */
228 @implementation EOKeyValueQualifier(IMAPAdditions)
230 - (BOOL)isImap4KeyValueQualifier {
234 - (BOOL)isImap4UnseenQualifier {
235 // TODO: this is rather weird: flags suggests an array value!
236 if (![[self key] isEqualToString:@"flags"])
238 return [[self value] isEqualToString:@"unseen"];
241 - (NSException *)appendFlagsCheckToImap4SearchString:(NSMutableString *)search
242 insertNot:(BOOL)insertNot
244 NSEnumerator *enumerator = nil;
248 lvalue = [self value];
249 lselector = [self selector];
251 if (sel_eq(lselector, EOQualifierOperatorEqual)) {
252 lvalue = [NSArray arrayWithObject:lvalue];
254 else if (!sel_eq(lselector, EOQualifierOperatorContains)) {
255 return [self invalidImap4SearchQualifier:
256 @"unexpected EOKeyValueQualifier selector"];
258 if (![lvalue isKindOfClass:[NSArray class]]) {
259 return [self invalidImap4SearchQualifier:
260 @"expected an array in contains-qualifier"];
263 enumerator = [lvalue objectEnumerator];
264 while ((lvalue = [enumerator nextObject]) != nil) {
265 lvalue = [lvalue lowercaseString];
267 if ([FlagKeyWords containsObject:lvalue]) {
268 if (insertNot) [search appendString:@"not "];
269 [search appendString:lvalue];
272 return [self invalidImap4SearchQualifier:
273 @"unexpected keyword for EOKeyValueQualifier"];
279 - (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector {
280 if (sel_eq(lselector, EOQualifierOperatorEqual))
282 if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
283 return @" sentsince ";
284 if (sel_eq(lselector, EOQualifierOperatorLessThan))
285 return @" sentbefore ";
290 - (NSException *)appendToImap4SearchString:(NSMutableString *)search
291 insertNot:(BOOL)insertNot
293 // TODO: this needs to get reworked
294 /* returns exception on fail */
299 lkey = [[self key] lowercaseString];
300 lvalue = [self value];
301 lselector = [self selector];
303 if ([lkey isEqualToString:@"flags"]) {
304 /* NOTE: special "not" processing! */
305 return [self appendFlagsCheckToImap4SearchString:search
306 insertNot:insertNot];
311 [search appendString:@"not "];
313 if ([lkey isEqualToString:@"date"]) {
316 if (![lvalue isKindOfClass:[NSCalendarDate class]]) {
317 return [self invalidImap4SearchQualifier:
318 @"expected a NSDate as value"];
321 if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil)
322 return [self invalidImap4SearchQualifier:@"unexpected selector"];
324 // TODO: operator created but NOT added?
326 // TODO: much faster without descriptionWithCalendarFormat:?!
327 s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"];
328 [search appendString:s];
332 if ([lkey isEqualToString:@"uid"]) {
333 if (!sel_eq(lselector, EOQualifierOperatorEqual))
334 return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"];
336 [search appendString:@"uid "];
337 [search appendString:[lvalue stringValue]];
341 if ([lkey isEqualToString:@"size"]) {
342 if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
343 [search appendString:@"larger "];
344 else if (sel_eq(lselector, EOQualifierOperatorLessThan))
345 [search appendString:@"smaller "];
347 return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"];
349 [search appendString:[lvalue stringValue]];
353 if ([OtherKeyWords containsObject:lkey]) {
354 // TODO: actually most keywords only allow for contains! Eg "subject abc"
355 // is a contains query, not an equal query!
358 In all search keys that use strings, a message matches the key if
359 the string is a substring of the field. The matching is
362 Would be: "a caseInsensitiveLike: '*ABC*'"
364 if (!sel_eq(lselector, EOQualifierOperatorEqual) &&
365 !sel_eq(lselector, EOQualifierOperatorContains)) {
366 [self logWithFormat:@"IMAP4 generation: got: %@, allowed: %@",
367 NSStringFromSelector(lselector),
368 NSStringFromSelector(EOQualifierOperatorEqual)];
369 return [self invalidImap4SearchQualifier:
370 @"unexpected qualifier, disallowed comparison on "
374 [search appendString:lkey];
375 [search appendString:@" \""];
376 [search appendString:[lvalue stringValue]];
377 [search appendString:@"\""];
382 if (!sel_eq(lselector, EOQualifierOperatorEqual))
383 return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"];
385 [search appendString:@"header "];
386 [search appendString:lkey];
387 [search appendString:@" \""];
388 [search appendString:[lvalue stringValue]];
389 [search appendString:@"\""];
393 - (id)imap4SearchString { /* returns exception on fail */
394 NSMutableString *search;
397 _initImap4SearchCategory();
399 if ([self isImap4UnseenQualifier]) {
401 [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]];
405 search = [NSMutableString stringWithCapacity:256];
407 if ((error = [self appendToImap4SearchString:search]))
413 @end /* EOKeyValueQualifier(IMAPAdditions) */
416 @implementation EONotQualifier(IMAPAdditions)
418 - (BOOL)isImap4NotQualifier {
422 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
424 TODO: we do this because the key/value qualifier can generate multiple
427 return [[self qualifier] appendToImap4SearchString:_search insertNot:YES];
430 - (id)imap4SearchString { /* returns exception on fail */
431 NSMutableString *search;
434 _initImap4SearchCategory();
436 search = [NSMutableString stringWithCapacity:256];
438 if ((error = [self appendToImap4SearchString:search]))
444 @end /* EONotQualifier(IMAPAdditions) */