PageRenderTime 128ms CodeModel.GetById 13ms app.highlight 109ms RepoModel.GetById 2ms app.codeStats 0ms

/Source/externals/GData/Source/BaseClasses/GDataQuery.m

http://google-email-uploader-mac.googlecode.com/
Objective C | 699 lines | 484 code | 144 blank | 71 comment | 57 complexity | e9898e880e6303ea3fccac707f029372 MD5 | raw file
  1/* Copyright (c) 2007 Google Inc.
  2 *
  3 * Licensed under the Apache License, Version 2.0 (the "License");
  4 * you may not use this file except in compliance with the License.
  5 * You may obtain a copy of the License at
  6 *
  7 *     http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software
 10 * distributed under the License is distributed on an "AS IS" BASIS,
 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 * See the License for the specific language governing permissions and
 13 * limitations under the License.
 14 */
 15
 16//
 17//  GDataQuery.m
 18//
 19
 20#define GDATAQUERY_DEFINE_GLOBALS 1
 21#import "GDataQuery.h"
 22
 23static NSString *const kAltParamName                 = @"alt";
 24static NSString *const kAuthorParamName              = @"author";
 25static NSString *const kErrorParamName               = @"err";
 26static NSString *const kFieldsParamName              = @"fields";
 27static NSString *const kFullTextQueryStringParamName = @"q";
 28static NSString *const kLanguageParamName            = @"hl";
 29static NSString *const kMaxResultsParamName          = @"max-results";
 30static NSString *const kOrderByParamName             = @"orderby";
 31static NSString *const kPrettyPrintParamName         = @"prettyprint";
 32static NSString *const kProtocolVersionParamName     = @"v";
 33static NSString *const kPublishedMaxParamName        = @"published-max";
 34static NSString *const kPublishedMinParamName        = @"published-min";
 35static NSString *const kShowDeletedParamName         = @"showdeleted";
 36static NSString *const kRequireAllDeletedParamName   = @"requirealldeleted";
 37static NSString *const kOnlyDeletedParamName         = @"onlydeleted";
 38static NSString *const kSortOrderParamName           = @"sortorder";
 39static NSString *const kStartIndexParamName          = @"start-index";
 40static NSString *const kStrictParamName              = @"strict";
 41static NSString *const kUpdatedMaxParamName          = @"updated-max";
 42static NSString *const kUpdatedMinParamName          = @"updated-min";
 43
 44@implementation GDataCategoryFilter
 45
 46+ (GDataCategoryFilter *)categoryFilter {
 47  return [[[self alloc] init] autorelease];
 48}
 49
 50- (void)dealloc {
 51  [categories_ release];
 52  [excludeCategories_ release];
 53  [super dealloc];
 54}
 55
 56- (id)copyWithZone:(NSZone *)zone {
 57  GDataCategoryFilter *newFilter = [[[self class] allocWithZone:zone] init];
 58
 59  NSArray *catsCopy = [GDataUtilities mutableArrayWithCopiesOfObjectsInArray:categories_];
 60  [newFilter setCategories:catsCopy];
 61
 62  NSArray *excludeCatsCopy = [GDataUtilities mutableArrayWithCopiesOfObjectsInArray:excludeCategories_];
 63  [newFilter setExcludeCategories:excludeCatsCopy];
 64  return newFilter;
 65}
 66
 67- (NSString *)description {
 68  return [NSString stringWithFormat:@"%@ %p: {%@}",
 69    [self class], self, [self stringValue]];
 70}
 71
 72- (NSArray *)categories {
 73  return categories_;
 74}
 75
 76- (void)setCategories:(NSArray *)array {
 77  [categories_ autorelease];
 78  categories_ = [array mutableCopy];
 79}
 80
 81- (void)addCategory:(GDataCategory *)category {
 82  if (!categories_) {
 83    categories_ = [[NSMutableArray alloc] init];
 84  }
 85
 86  [categories_ addObject:category];
 87}
 88
 89- (void)addCategoryWithScheme:(NSString *)scheme term:(NSString *)term {
 90  GDataCategory *cat = [GDataCategory categoryWithScheme:scheme term:term];
 91  [self addCategory:cat];
 92}
 93
 94- (NSArray *)excludeCategories {
 95  return excludeCategories_;
 96}
 97
 98- (void)setExcludeCategories:(NSArray *)array {
 99  [excludeCategories_ autorelease];
100  excludeCategories_ = [array mutableCopy];
101}
102
103- (void)addExcludeCategory:(GDataCategory *)excludeCategory {
104  if (!excludeCategories_) {
105    excludeCategories_ = [[NSMutableArray alloc] init];
106  }
107
108  [excludeCategories_ addObject:excludeCategory];
109}
110
111- (void)addExcludeCategoryWithScheme:(NSString *)scheme term:(NSString *)term {
112  GDataCategory *cat = [GDataCategory categoryWithScheme:scheme term:term];
113  [self addExcludeCategory:cat];
114}
115
116- (NSString *)queryStringForCategory:(GDataCategory *)category {
117
118  // precede the category term with {scheme} if there's a scheme
119  NSString *prefix = @"";
120  NSString *scheme = [category scheme];
121  if (scheme) {
122    prefix = [NSString stringWithFormat:@"{%@}", scheme];
123  }
124
125  NSString *term = [category term];
126  NSString *result = [NSString stringWithFormat:@"%@%@", prefix, term];
127  return result;
128}
129
130- (NSString *)stringValue {
131
132  NSMutableString *result = [NSMutableString string];
133
134  // append include categories
135  for (GDataCategory *cat in categories_) {
136    if ([result length] > 0) {
137      [result appendString:@"|"];
138    }
139    [result appendString:[self queryStringForCategory:cat]];
140  }
141
142  // append exclude categories, preceded by "-"
143  for (GDataCategory *cat in excludeCategories_) {
144    if ([result length] > 0) {
145      [result appendString:@"|"];
146    }
147    [result appendFormat:@"-%@", [self queryStringForCategory:cat]];
148  }
149  return result;
150}
151
152@end
153
154//
155// GDataQuery
156//
157@implementation GDataQuery
158
159+ (id)queryWithFeedURL:(NSURL *)feedURL {
160  return [[[self alloc] initWithFeedURL:feedURL] autorelease];
161}
162
163- (id)initWithFeedURL:(NSURL *)feedURL {
164  self = [super init];
165  if (self) {
166    [self setFeedURL:feedURL];
167  }
168  return self;
169}
170
171- (void)dealloc {
172  [feedURL_ release];
173  [categoryFilters_ release];
174  [customParameters_ release];
175  [super dealloc];
176}
177
178
179- (id)copyWithZone:(NSZone *)zone {
180  GDataQuery *query = [[[self class] allocWithZone:zone] init];
181  [query setFeedURL:feedURL_];
182  [query setCategoryFilters:categoryFilters_];
183  [query setCustomParameters:customParameters_];
184  return query;
185}
186
187- (NSString *)description {
188 return [NSString stringWithFormat:@"%@ %p: {%@}",
189   [self class], self, [[self URL] absoluteString]];
190}
191
192#pragma mark -
193
194- (NSURL *)feedURL {
195  return feedURL_;
196}
197
198- (void)setFeedURL:(NSURL *)feedURL {
199  [feedURL_ release];
200  feedURL_ = [feedURL retain];
201}
202
203- (NSInteger)startIndex {
204  return [self intValueForParameterWithName:kStartIndexParamName
205                      missingParameterValue:-1];
206}
207
208- (void)setStartIndex:(NSInteger)startIndex {
209  [self addCustomParameterWithName:kStartIndexParamName
210                          intValue:startIndex
211                    removeForValue:-1];
212}
213
214- (NSInteger)maxResults {
215  return [self intValueForParameterWithName:kMaxResultsParamName
216                      missingParameterValue:-1];
217}
218
219- (void)setMaxResults:(NSInteger)maxResults {
220  [self addCustomParameterWithName:kMaxResultsParamName
221                          intValue:maxResults
222                    removeForValue:-1];
223}
224
225- (NSString *)fieldSelection {
226  NSString *str = [self valueForParameterWithName:kFieldsParamName];
227  return str;
228}
229
230- (void)setFieldSelection:(NSString *)str {
231  // the library needs gd:kind attributes in feeds and entries during parsing of
232  // the fetched XML, so field selection queries should always include those
233  //
234  // There may be other valid ways to specify a field selection that includes
235  // the gd:kind attribute (in which case this conditional should be improved)
236  // but hopefully this will minimize the time developers spend wondering
237  // why the returned XML isn't instantiating the expected classes.
238#if DEBUG
239  if (str != nil
240      && [str rangeOfString:@"@gd:kind"].location == NSNotFound
241      && [str rangeOfString:@"@gd:*"].location == NSNotFound) {
242    GDATA_DEBUG_LOG(@"query field selection \"%@\" needs @gd:* or @gd:kind",
243                    str);
244  }
245#endif
246
247  [self addCustomParameterWithName:kFieldsParamName
248                             value:str];
249}
250
251- (NSString *)fullTextQueryString {
252  NSString *str;
253
254  str = [self valueForParameterWithName:kFullTextQueryStringParamName];
255  return str;
256}
257
258- (void)setFullTextQueryString:(NSString *)str {
259  [self addCustomParameterWithName:kFullTextQueryStringParamName
260                             value:str];
261}
262
263- (NSString *)author {
264  NSString *str = [self valueForParameterWithName:kAuthorParamName];
265  return str;
266}
267
268- (void)setAuthor:(NSString *)str {
269  [self addCustomParameterWithName:kAuthorParamName
270                             value:str];
271}
272
273- (NSString *)orderBy {
274  NSString *str = [self valueForParameterWithName:kOrderByParamName];
275  return str;
276}
277
278- (void)setOrderBy:(NSString *)str {
279  [self addCustomParameterWithName:kOrderByParamName
280                             value:str];
281}
282
283- (BOOL)isAscendingOrder {
284  NSString *str = [self valueForParameterWithName:kSortOrderParamName];
285
286  BOOL isAscending = (str != nil)
287    && ([str caseInsensitiveCompare:@"ascending"] == NSOrderedSame);
288
289  return isAscending;
290}
291
292- (void)setIsAscendingOrder:(BOOL)flag {
293  NSString *str = (flag ? @"ascending" : @"descending");
294
295  [self addCustomParameterWithName:kSortOrderParamName
296                             value:str];
297}
298
299- (BOOL)shouldShowDeleted {
300  return [self boolValueForParameterWithName:kShowDeletedParamName
301                                defaultValue:NO];
302}
303
304- (void)setShouldShowDeleted:(BOOL)flag {
305  [self addCustomParameterWithName:kShowDeletedParamName
306                         boolValue:flag
307                      defaultValue:NO];
308}
309
310- (BOOL)shouldRequireAllDeleted {
311  return [self boolValueForParameterWithName:kRequireAllDeletedParamName
312                                defaultValue:NO];
313}
314
315- (void)setShouldRequireAllDeleted:(BOOL)flag {
316  [self addCustomParameterWithName:kRequireAllDeletedParamName
317                         boolValue:flag
318                      defaultValue:NO];
319}
320
321- (BOOL)shouldShowOnlyDeleted {
322  return [self boolValueForParameterWithName:kOnlyDeletedParamName
323                                defaultValue:NO];
324}
325
326- (void)setShouldShowOnlyDeleted:(BOOL)flag {
327  [self addCustomParameterWithName:kOnlyDeletedParamName
328                         boolValue:flag
329                      defaultValue:NO];
330}
331
332- (BOOL)isStrict {
333  return [self boolValueForParameterWithName:kStrictParamName
334                                defaultValue:NO];
335}
336
337- (void)setIsStrict:(BOOL)flag {
338  [self addCustomParameterWithName:kStrictParamName
339                         boolValue:flag
340                      defaultValue:NO];
341}
342
343- (BOOL)shouldPrettyPrint {
344  return [self boolValueForParameterWithName:kPrettyPrintParamName
345                                defaultValue:NO];
346}
347
348- (void)setShouldPrettyPrint:(BOOL)flag {
349  [self addCustomParameterWithName:kPrettyPrintParamName
350                         boolValue:flag
351                      defaultValue:NO];
352}
353
354- (NSString *)protocolVersion {
355  return [self valueForParameterWithName:kProtocolVersionParamName];
356}
357
358- (void)setProtocolVersion:(NSString *)str {
359  [self addCustomParameterWithName:kProtocolVersionParamName
360                             value:str];
361}
362
363- (NSString *)resultFormat {
364  NSString *str = [self valueForParameterWithName:kAltParamName];
365  return str;
366}
367
368- (void)setResultFormat:(NSString *)str {
369  [self addCustomParameterWithName:kAltParamName value:str];
370}
371
372- (NSString *)language {
373  return [self valueForParameterWithName:kLanguageParamName];
374}
375
376- (void)setLanguage:(NSString *)str {
377  [self addCustomParameterWithName:kLanguageParamName
378                             value:str];
379}
380
381- (GDataDateTime *)publishedMinDateTime {
382  return [self dateTimeForParameterWithName:kPublishedMinParamName];
383}
384
385- (void)setPublishedMinDateTime:(GDataDateTime *)dateTime {
386  [self addCustomParameterWithName:kPublishedMinParamName
387                          dateTime:dateTime];
388}
389
390- (GDataDateTime *)publishedMaxDateTime {
391  return [self dateTimeForParameterWithName:kPublishedMaxParamName];
392}
393
394- (void)setPublishedMaxDateTime:(GDataDateTime *)dateTime {
395  [self addCustomParameterWithName:kPublishedMaxParamName
396                          dateTime:dateTime];
397}
398
399- (GDataDateTime *)updatedMinDateTime {
400  return [self dateTimeForParameterWithName:kUpdatedMinParamName];
401}
402
403- (void)setUpdatedMinDateTime:(GDataDateTime *)dateTime {
404  [self addCustomParameterWithName:kUpdatedMinParamName
405                          dateTime:dateTime];
406}
407
408- (GDataDateTime *)updatedMaxDateTime {
409  return [self dateTimeForParameterWithName:kUpdatedMaxParamName];
410}
411
412- (void)setUpdatedMaxDateTime:(GDataDateTime *)dateTime {
413  [self addCustomParameterWithName:kUpdatedMaxParamName
414                          dateTime:dateTime];
415}
416
417- (BOOL)shouldFormatErrorsAsXML {
418  NSString *value = [self valueForParameterWithName:kErrorParamName];
419  BOOL flag = (value != nil)
420    && ([value caseInsensitiveCompare:@"xml"] == NSOrderedSame);
421  return flag;
422}
423
424- (void)setShouldFormatErrorsAsXML:(BOOL)flag {
425  [self addCustomParameterWithName:kErrorParamName
426                             value:(flag ? @"xml" : nil)];
427}
428
429- (NSArray *)categoryFilters {
430  return categoryFilters_;
431}
432
433- (void)setCategoryFilters:(NSArray *)filters {
434  [categoryFilters_ autorelease];
435  categoryFilters_ = [filters mutableCopy];
436}
437
438- (void)addCategoryFilter:(GDataCategoryFilter *)filter {
439  if (!categoryFilters_) {
440    categoryFilters_ = [[NSMutableArray alloc] init];
441  }
442
443  [categoryFilters_ addObject:filter];
444}
445
446- (void)addCategoryFilterWithCategory:(GDataCategory *)category {
447
448  GDataCategoryFilter *filter = [GDataCategoryFilter categoryFilter];
449  [filter addCategory:category];
450
451  [self addCategoryFilter:filter];
452}
453
454- (void)addCategoryFilterWithScheme:(NSString *)scheme term:(NSString *)term {
455
456  GDataCategoryFilter *filter = [GDataCategoryFilter categoryFilter];
457  [filter addCategoryWithScheme:scheme term:term];
458
459  [self addCategoryFilter:filter];
460}
461
462#pragma mark -
463
464- (NSDictionary *)customParameters {
465  return customParameters_;
466}
467
468- (void)setCustomParameters:(NSDictionary *)dict {
469  [customParameters_ autorelease];
470  customParameters_ = [dict mutableCopy];
471}
472
473- (void)addCustomParameterWithName:(NSString *)name
474                             value:(NSString *)value {
475
476  if (value == nil) {
477    [self removeCustomParameterWithName:name];
478    return;
479  }
480
481  if (!customParameters_) {
482    customParameters_ = [[NSMutableDictionary alloc] init];
483  }
484
485  [customParameters_ setValue:value forKey:name];
486}
487
488- (void)removeCustomParameterWithName:(NSString *)name {
489  [customParameters_ removeObjectForKey:name];
490}
491
492- (NSString *)valueForParameterWithName:(NSString *)name {
493  NSString *str = [[self customParameters] objectForKey:name];
494  return str;
495}
496
497// convenience methods for dateTime parameters
498- (void)addCustomParameterWithName:(NSString *)name
499                          dateTime:(GDataDateTime *)dateTime {
500
501  [self addCustomParameterWithName:name
502                             value:[dateTime RFC3339String]];
503}
504
505- (GDataDateTime *)dateTimeForParameterWithName:(NSString *)name {
506
507  NSString *str = [customParameters_ objectForKey:name];
508  if (str) {
509    return [GDataDateTime dateTimeWithRFC3339String:str];
510  }
511  return nil;
512}
513
514// convenience methods for int parameters
515//
516// if val==invalidVal, the parameter is removed
517- (void)addCustomParameterWithName:(NSString *)name
518                          intValue:(NSInteger)val
519                    removeForValue:(NSInteger)invalidVal {
520
521  NSString *str = nil;
522
523  if (val != invalidVal) {
524    str = [[NSNumber numberWithInt:(int)val] stringValue];
525  }
526
527  [self addCustomParameterWithName:name
528                             value:str];
529}
530
531// if the named parameter is not found, missingVal is returned
532- (NSInteger)intValueForParameterWithName:(NSString *)name
533                    missingParameterValue:(NSInteger)missingVal {
534
535  NSString *str = [customParameters_ objectForKey:name];
536  if (str != nil) return (NSInteger) [str intValue];
537  
538  return missingVal;
539}
540
541// convenience method for boolean parameters
542- (void)addCustomParameterWithName:(NSString *)name
543                         boolValue:(BOOL)flag
544                      defaultValue:(BOOL)defaultValue {
545
546  NSString *str = nil;
547  if (defaultValue) {
548    // default is true
549    if (!flag) str = @"false";
550  } else {
551    // default is false
552    if (flag) str = @"true";
553  }
554
555  // nil value will remove the parameter
556  [self addCustomParameterWithName:name
557                             value:str];
558}
559
560- (BOOL)boolValueForParameterWithName:(NSString *)name
561                         defaultValue:(BOOL)defaultValue {
562
563  NSString *str = [self valueForParameterWithName:name];
564  if (defaultValue) {
565    // default is true, so return true if param is missing or
566    // is "true"
567    return (str == nil)
568      || ([str caseInsensitiveCompare:@"true"] == NSOrderedSame);
569  } else {
570    // default is false, so return true only if the param is present
571    // and "true"
572    return (str != nil)
573      && ([str caseInsensitiveCompare:@"true"] == NSOrderedSame);
574  }
575}
576
577#pragma mark -
578
579// categoryFilterString generates the category portion of the URL path
580- (NSString *)categoryFilterString {
581
582  // make a path string containing the category filters
583  NSMutableString *pathStr = [NSMutableString string];
584
585  if ([categoryFilters_ count] > 0) {
586    [pathStr appendString:@"-"];
587
588    for (id filter in categoryFilters_) {
589      NSString *filterValue = [filter stringValue];
590      NSString *filterStr = [GDataUtilities stringByURLEncodingForURI:filterValue];
591      if ([filterStr length] > 0) {
592        [pathStr appendFormat:@"/%@", filterStr];
593      }
594    }
595  }
596
597  return pathStr;
598}
599
600- (NSString *)queryParamString {
601  // make a query string containing all the query params.  We'll put them
602  // all into an array, then use NSArray's componentsJoinedByString:
603
604  NSMutableArray *queryItems = [NSMutableArray array];
605
606  // sort the custom parameter keys so that we have deterministic parameter
607  // order for unit tests
608  NSDictionary *customParameters = [self customParameters];
609  NSArray *customKeys = [customParameters allKeys];
610  NSArray *sortedCustomKeys = [customKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
611
612  for (id paramKey in sortedCustomKeys) {
613    NSString *paramValue = [customParameters valueForKey:paramKey];
614
615    NSString *paramItem = [NSString stringWithFormat:@"%@=%@",
616                           [GDataUtilities stringByURLEncodingStringParameter:paramKey],
617                           [GDataUtilities stringByURLEncodingStringParameter:paramValue]];
618
619    [queryItems addObject:paramItem];
620  }
621
622  NSString *paramStr = [queryItems componentsJoinedByString:@"&"];
623  return paramStr;
624}
625
626- (NSURL *)URL {
627  // add the category filter string and the query params to the feed base URL
628
629  // NSURL's URLWithString:relativeToURL: would be appropriate, but it deletes the
630  // last path component of the feed when we're appending something.
631  // Note that a similar deletion occurs in Java's resolve call.
632  //
633  // CFURLCreateCopyAppendingPathComponent seems to implicitly percent-escape
634  // the path component, including any ? character, so we can't use it here,
635  // either.
636  //
637  // We'll just do simple string appending instead.
638
639  NSString *categoryFilterStr = [self categoryFilterString];
640  NSString *queryParamString = [self queryParamString];
641
642  // split the original URL into path and query components
643
644  NSString *feedURLString = [[self feedURL] absoluteString];
645
646  NSString *origPath, *origQuery, *newURLStr, *newQuery;
647
648  // find the first question mark
649  NSRange quoteMark = [feedURLString rangeOfString:@"?"];
650  if (quoteMark.location == NSNotFound) {
651    // no query part
652    origPath = feedURLString;
653    origQuery = @"";
654  } else {
655    // has a query part
656    origPath = [feedURLString substringToIndex:quoteMark.location];
657    if (quoteMark.location < [feedURLString length]) {
658      // skip the leading ? mark
659      origQuery = [feedURLString substringFromIndex:quoteMark.location + 1];
660    } else {
661      // nothing follows the ? mark
662      origQuery = @"";
663    }
664  }
665
666  newURLStr = origPath;
667
668  // add the generated category filter string, if any, to the URL string,
669  // ensuring it's preceded by a slash
670  if ([categoryFilterStr length] > 0) {
671    if (![newURLStr hasSuffix:@"/"]) {
672      newURLStr = [newURLStr stringByAppendingString:@"/"];
673    }
674    newURLStr = [newURLStr stringByAppendingString:categoryFilterStr];
675  }
676
677  // append the generated param query string, if any, to the original query
678  if ([origQuery length] > 0) {
679    // there was an original query
680    if ([queryParamString length] > 0) {
681      newQuery = [origQuery stringByAppendingFormat:@"&%@", queryParamString];
682    } else {
683      newQuery = origQuery;
684    }
685  } else {
686    // there was no original query
687    newQuery = queryParamString;
688  }
689
690  // append the query to the URL
691  if ([newQuery length] > 0) {
692    newURLStr = [newURLStr stringByAppendingFormat:@"?%@", newQuery];
693  }
694
695  NSURL *fullURL = [NSURL URLWithString:newURLStr];
696  return fullURL;
697}
698
699@end