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;
34 - (id)imap4SearchString;
38 @implementation EOQualifier(IMAPAdditions)
40 - (BOOL)isImap4UnseenQualifier { /* a special key/value qualifier */
44 /* building search qualifiers */
46 static NSArray *FlagKeyWords = nil;
47 static NSArray *OtherKeyWords = nil;
48 static BOOL debugOn = NO;
50 static void _initImap4SearchCategory(void) {
53 if (FlagKeyWords) return;
55 ud = [NSUserDefaults standardUserDefaults];
56 FlagKeyWords = [[NSArray alloc] initWithObjects: @"answered", @"deleted",
57 @"draft", @"flagged", @"new", @"old", @"recent",
58 @"seen", @"unanswered", @"undeleted", @"undraft",
59 @"unflagged", @"unseen", nil];
60 OtherKeyWords = [[NSArray alloc] initWithObjects:
61 @"bcc", @"body", @"cc", @"from", @"subject",
62 @"text", @"to", @"keyword", @"unkeyword", nil];
64 debugOn = [ud boolForKey:@"ImapDebugQualifierGeneration"];
67 - (NSException *)invalidImap4SearchQualifier:(NSString *)_reason {
68 if (_reason == nil) _reason = @"unknown reason";
69 return [NSException exceptionWithName:@"NGImap4SearchQualifierException"
74 - (BOOL)isImap4NotQualifier {
77 - (BOOL)isImap4KeyValueQualifier {
81 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search
82 insertNot:(BOOL)_insertNot
84 return [self invalidImap4SearchQualifier:@"expected key/value qualifier"];
86 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
87 return [self appendToImap4SearchString:_search insertNot:NO];
90 - (id)imap4SearchString { /* returns exception on fail */
91 [self logWithFormat:@"ERROR(%s): subclass %@ must overide this method!",
92 __PRETTY_FUNCTION__, [self class]];
96 @end /* EOQualifier(IMAPAdditions) */
99 @implementation EOAndQualifier(IMAPAdditions)
101 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
105 quals = [self qualifiers];
107 if ((lCount = [quals count]) == 0) /* no subqualifiers */
110 // TODO: use appendToImap4SearchString?
111 [_search appendString:[[quals objectAtIndex:0] imap4SearchString]];
115 for (i = 0; i < lCount; i++) {
116 EOQualifier *qualifier;
119 qualifier = [quals objectAtIndex:i];
121 [self logWithFormat:@" append subqualifier: %@", qualifier];
123 [_search appendString:(i == 0) ? @"(" : @" ("];
124 if ((error = [qualifier appendToImap4SearchString:_search]))
126 [_search appendString:@")"];
129 return nil /* no error */;
132 - (id)imap4SearchString { /* returns exception on fail */
133 NSMutableString *search;
137 _initImap4SearchCategory();
141 @"generate IMAP4 expression for AND qualifier: %@", self];
144 if ((lCount = [[self qualifiers] count]) == 0) /* no subqualifiers */
147 return [[[self qualifiers] objectAtIndex:0] imap4SearchString];
149 search = [NSMutableString stringWithCapacity:lCount * 3];
151 if ((error = [self appendToImap4SearchString:search]) != nil) {
152 if (debugOn) [self logWithFormat:@" error: %@", error];
157 [self logWithFormat:@" generated: '%@'", search];
162 @end /* EOAndQualifier(IMAPAdditions) */
165 @implementation EOOrQualifier(IMAPAdditions)
167 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
168 // TODO: move generation to this method
171 s = [self imap4SearchString];
172 if ([s isKindOfClass:[NSException class]])
175 [_search appendString:s];
179 - (id)imap4SearchString { /* returns exception on fail */
181 NSMutableString *search;
185 _initImap4SearchCategory();
189 @"generate IMAP4 expression for or-qualifier: %@", self];
192 quals = [self qualifiers];
194 if ((lCount = [quals count]) == 0) /* no subqualifiers */
197 return [[quals objectAtIndex:0] imap4SearchString];
199 search = [NSMutableString stringWithCapacity:lCount * 32];
202 Note: or queries are specified as:
203 OR <search-key1> <search-key2>
204 so we need to wrap more ORs in multiple "OR" IMAP4 expressions
205 eg: "OR (OR (subject "abc") (subject "nbc")) from "duck""
208 if ((error = [[quals objectAtIndex:0] appendToImap4SearchString:search]))
211 for (i = 1; i < lCount; i++) {
212 EOQualifier *qualifier;
214 qualifier = [quals objectAtIndex:i];
215 [search insertString:@"OR (" atIndex:0];
216 [search appendString:@") ("];
217 if ((error = [qualifier appendToImap4SearchString:search]))
219 [search appendString:@")"];
223 [self logWithFormat:@" generated: '%@'", search];
227 @end /* EOOrQualifier(IMAPAdditions) */
230 @implementation EOKeyValueQualifier(IMAPAdditions)
232 - (BOOL)isImap4KeyValueQualifier {
236 - (BOOL)isImap4UnseenQualifier {
237 // TODO: this is rather weird: flags suggests an array value!
238 if (![[self key] isEqualToString:@"flags"])
240 return [[self value] isEqualToString:@"unseen"];
243 - (NSException *)appendFlagsCheckToImap4SearchString:(NSMutableString *)search
244 insertNot:(BOOL)insertNot
246 NSEnumerator *enumerator = nil;
250 lvalue = [self value];
251 lselector = [self selector];
253 // TODO: add support for <> qualifier? (seen => unseen)
255 if (sel_eq(lselector, EOQualifierOperatorEqual)) {
256 lvalue = [NSArray arrayWithObject:lvalue];
258 else if (!sel_eq(lselector, EOQualifierOperatorContains)) {
259 return [self invalidImap4SearchQualifier:
260 @"unexpected EOKeyValueQualifier selector"];
262 if (![lvalue isKindOfClass:[NSArray class]]) {
263 return [self invalidImap4SearchQualifier:
264 @"expected an array in contains-qualifier"];
267 enumerator = [lvalue objectEnumerator];
268 while ((lvalue = [enumerator nextObject]) != nil) {
269 lvalue = [lvalue lowercaseString];
271 if ([FlagKeyWords containsObject:lvalue]) {
272 if (insertNot) [search appendString:@"not "];
273 [search appendString:lvalue];
276 return [self invalidImap4SearchQualifier:
277 @"unexpected keyword for EOKeyValueQualifier"];
283 - (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector {
284 if (sel_eq(lselector, EOQualifierOperatorEqual))
286 if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
287 return @" sentsince ";
288 if (sel_eq(lselector, EOQualifierOperatorLessThan))
289 return @" sentbefore ";
294 - (NSException *)appendToImap4SearchString:(NSMutableString *)search
295 insertNot:(BOOL)insertNot
297 // TODO: this needs to get reworked
298 /* returns exception on fail */
303 lkey = [[self key] lowercaseString];
304 lvalue = [self value];
305 lselector = [self selector];
307 if ([lkey isEqualToString:@"flags"]) {
308 /* NOTE: special "not" processing! */
309 return [self appendFlagsCheckToImap4SearchString:search
310 insertNot:insertNot];
315 [search appendString:@"not "];
317 if ([lkey isEqualToString:@"date"]) {
320 if (![lvalue isKindOfClass:[NSCalendarDate class]]) {
321 return [self invalidImap4SearchQualifier:
322 @"expected a NSDate as value"];
325 if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil)
326 return [self invalidImap4SearchQualifier:@"unexpected selector"];
328 // TODO: operator created but NOT added?
330 // TODO: much faster without descriptionWithCalendarFormat:?!
331 s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"];
332 [search appendString:s];
336 if ([lkey isEqualToString:@"uid"]) {
337 if (!sel_eq(lselector, EOQualifierOperatorEqual))
338 return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"];
340 [search appendString:@"uid "];
341 [search appendString:[lvalue stringValue]];
345 if ([lkey isEqualToString:@"size"]) {
346 if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
347 [search appendString:@"larger "];
348 else if (sel_eq(lselector, EOQualifierOperatorLessThan))
349 [search appendString:@"smaller "];
351 return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"];
353 [search appendString:[lvalue stringValue]];
357 if ([OtherKeyWords containsObject:lkey]) {
358 // TODO: actually most keywords only allow for contains! Eg "subject abc"
359 // is a contains query, not an equal query!
362 In all search keys that use strings, a message matches the key if
363 the string is a substring of the field. The matching is
366 Would be: "a caseInsensitiveLike: '*ABC*'"
368 if (!sel_eq(lselector, EOQualifierOperatorEqual) &&
369 !sel_eq(lselector, EOQualifierOperatorContains)) {
370 [self logWithFormat:@"IMAP4 generation: got: %@, allowed: %@",
371 NSStringFromSelector(lselector),
372 NSStringFromSelector(EOQualifierOperatorEqual)];
373 return [self invalidImap4SearchQualifier:
374 @"unexpected qualifier, disallowed comparison on "
378 [search appendString:lkey];
379 [search appendString:@" \""];
380 [search appendString:[lvalue stringValue]];
381 [search appendString:@"\""];
386 if (!sel_eq(lselector, EOQualifierOperatorEqual))
387 return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"];
389 [search appendString:@"header "];
390 [search appendString:lkey];
391 [search appendString:@" \""];
392 [search appendString:[lvalue stringValue]];
393 [search appendString:@"\""];
397 - (id)imap4SearchString { /* returns exception on fail */
398 NSMutableString *search;
401 _initImap4SearchCategory();
403 if ([self isImap4UnseenQualifier]) {
405 [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]];
409 search = [NSMutableString stringWithCapacity:256];
411 if ((error = [self appendToImap4SearchString:search]))
417 @end /* EOKeyValueQualifier(IMAPAdditions) */
420 @implementation EONotQualifier(IMAPAdditions)
422 - (BOOL)isImap4NotQualifier {
426 - (NSException *)appendToImap4SearchString:(NSMutableString *)_search {
428 TODO: we do this because the key/value qualifier can generate multiple
431 return [[self qualifier] appendToImap4SearchString:_search insertNot:YES];
434 - (id)imap4SearchString { /* returns exception on fail */
435 NSMutableString *search;
438 _initImap4SearchCategory();
440 search = [NSMutableString stringWithCapacity:256];
442 if ((error = [self appendToImap4SearchString:search]))
448 @end /* EONotQualifier(IMAPAdditions) */