PageRenderTime 224ms CodeModel.GetById 18ms app.highlight 191ms RepoModel.GetById 1ms app.codeStats 1ms

/Source/externals/GData/Source/BaseClasses/GDataObject.m

http://google-email-uploader-mac.googlecode.com/
Objective C | 2822 lines | 1782 code | 591 blank | 449 comment | 382 complexity | 8ad1fd8f5c3317bb952d199ae93a1b6e MD5 | raw file

Large files files are truncated, but you can click here to view the full 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//  GDataObject.m
  18//
  19
  20#define GDATAOBJECT_DEFINE_GLOBALS 1
  21
  22#import "GDataObject.h"
  23#import "GDataDateTime.h"
  24
  25// for automatic-determination of feed and entry class types
  26#import "GDataFeedBase.h"
  27#import "GDataEntryBase.h"
  28#import "GDataCategory.h"
  29
  30static inline NSMutableDictionary *GDataCreateStaticDictionary(void) {
  31  NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
  32#if !GDATA_IPHONE
  33  Class cls = NSClassFromString(@"NSGarbageCollector");
  34  if (cls) {
  35    id collector = [cls performSelector:@selector(defaultCollector)];
  36    [collector performSelector:@selector(disableCollectorForPointer:)
  37                    withObject:dict];
  38  }
  39#endif
  40  return dict;
  41}
  42
  43// in a cache of attribute declarations, this marker indicates that the class
  44// also declared that it wants child text parsed as the content value or child
  45// xml held as xml element objects
  46//
  47// these start with a space to avoid colliding with any real attribute name
  48static NSString* const kContentValueDeclarationMarker = @" __content";
  49static NSString* const kChildXMLDeclarationMarker = @" __childXML";
  50
  51// Elements may call -addExtensionDeclarationForParentClass:childClass: and
  52// addAttributeExtensionDeclarationForParentClass: to declare extensions to be
  53// parsed; the declaration applies in the element and all children of the element.
  54@interface GDataExtensionDeclaration : NSObject {
  55  Class parentClass_;
  56  Class childClass_;
  57  BOOL isAttribute_;
  58}
  59- (id)initWithParentClass:(Class)parentClass childClass:(Class)childClass isAttribute:(BOOL)attrFlag;
  60- (Class)parentClass;
  61- (Class)childClass;
  62- (BOOL)isAttribute;
  63@end
  64
  65@interface GDataObject (PrivateMethods)
  66
  67// array of local attribute names to be automatically parsed and
  68// generated
  69- (void)setAttributeDeclarationsCache:(NSDictionary *)decls;
  70- (NSMutableDictionary *)attributeDeclarationsCache;
  71
  72// array of attribute declarations for the current class, from the cache
  73- (void)setAttributeDeclarations:(NSArray *)array;
  74- (NSMutableArray *)attributeDeclarations;
  75
  76- (void)parseAttributesForElement:(NSXMLElement *)element;
  77- (void)addAttributesToElement:(NSXMLElement *)element;
  78
  79// routines for comparing attributes
  80- (BOOL)hasAttributesEqualToAttributesOf:(GDataObject *)other;
  81- (NSArray *)attributesIgnoredForEquality;
  82
  83// element string content
  84- (void)parseContentValueForElement:(NSXMLElement *)element;
  85- (void)addContentValueToElement:(NSXMLElement *)element;
  86
  87- (BOOL)hasContentValueEqualToContentValueOf:(GDataObject *)other;
  88
  89// XML values content (kept unparsed)
  90- (void)keepChildXMLElementsForElement:(NSXMLElement *)element;
  91- (void)addChildXMLElementsToElement:(NSXMLElement *)element;
  92
  93- (BOOL)hasChildXMLElementsEqualToChildXMLElementsOf:(GDataObject *)other;
  94
  95// dictionary of all extensions actually found in the XML element
  96- (void)setExtensions:(NSDictionary *)extensions;
  97- (NSDictionary *)extensions;
  98
  99// cache of arrays of extensions that may be found in this class and in
 100// subclasses of this class.
 101- (void)setExtensionDeclarationsCache:(NSDictionary *)decls;
 102- (NSMutableDictionary *)extensionDeclarationsCache;
 103
 104- (NSMutableArray *)extensionDeclarationsForParentClass:(Class)parentClass;
 105
 106- (void)addExtensionDeclarationForParentClass:(Class)parentClass
 107                                   childClass:(Class)childClass
 108                                  isAttribute:(BOOL)isAttribute;
 109
 110- (void)addUnknownChildNodesForElement:(NSXMLElement *)element;
 111
 112- (void)parseExtensionsForElement:(NSXMLElement *)element;
 113
 114- (void)handleParsedElement:(NSXMLNode *)element;
 115- (void)handleParsedElements:(NSArray *)array;
 116
 117- (NSString *)qualifiedNameForExtensionClass:(Class)theClass;
 118
 119+ (Class)classForCategoryWithScheme:(NSString *)scheme
 120                               term:(NSString *)term
 121                            fromMap:(NSDictionary *)map;
 122@end
 123
 124@implementation GDataObject
 125
 126// The qualified name map avoids the need to regenerate qualified
 127// element names (foo:bar) repeatedly
 128static NSMutableDictionary *gQualifiedNameMap = nil;
 129
 130+ (void)load {
 131  // Initialize gQualifiedNameMap early so we can @synchronize on accesses
 132  // to it
 133  gQualifiedNameMap = GDataCreateStaticDictionary();
 134}
 135
 136+ (id)object {
 137  return [[[self alloc] init] autorelease];
 138}
 139
 140- (id)init {
 141  self = [super init];
 142  if (self) {
 143    // there is no parent
 144    extensionDeclarationsCache_ = [[NSMutableDictionary alloc] init];
 145
 146    attributeDeclarationsCache_ = [[NSMutableDictionary alloc] init];
 147
 148    [self addParseDeclarations];
 149  }
 150  return self;
 151}
 152
 153// intended mainly for testing, initWithServiceVersion allows the service
 154// version to be set prior to declaring extensions; this is useful
 155// for overriding the default service version for the class when
 156// manually allocating a copy of the object
 157- (id)initWithServiceVersion:(NSString *)serviceVersion {
 158  [self setServiceVersion:serviceVersion];
 159  return [self init];
 160}
 161
 162// this init routine is only used when passing in a top-level surrogates
 163// dictionary
 164- (id)initWithXMLElement:(NSXMLElement *)element
 165                  parent:(GDataObject *)parent
 166          serviceVersion:(NSString *)serviceVersion
 167              surrogates:(NSDictionary *)surrogates
 168    shouldIgnoreUnknowns:(BOOL)shouldIgnoreUnknowns {
 169
 170  [self setServiceVersion:serviceVersion];
 171
 172  [self setSurrogates:surrogates];
 173
 174  [self setShouldIgnoreUnknowns:shouldIgnoreUnknowns];
 175
 176  id obj = [self initWithXMLElement:element
 177                             parent:parent];
 178  return obj;
 179}
 180
 181// subclasses will typically override initWithXMLElement:parent:
 182// and do their own parsing after this method returns
 183- (id)initWithXMLElement:(NSXMLElement *)element
 184                  parent:(GDataObject *)parent {
 185  self = [super init];
 186  if (self) {
 187    [self setParent:parent];
 188
 189    if (parent != nil) {
 190      // top-level objects (feeds and entries) have nil parents, and
 191      // have their service version set previously in
 192      // initWithXMLElement:parent:serviceVersion:surrogates:; child
 193      // objects have their service version set here
 194      [self setServiceVersion:[parent serviceVersion]];
 195
 196      // feeds may specify that contained entries and their child elements
 197      // should ignore any unparsed XML
 198      [self setShouldIgnoreUnknowns:[parent shouldIgnoreUnknowns]];
 199
 200      // get the parent's declaration caches, and temporarily hang on to them
 201      // in our ivar to avoid the need to get them recursively from the topmost
 202      // parent
 203      //
 204      // We'll release these below, so that only the topmost parent retains
 205      // them. The topmost parent retains them in case some subclass code still
 206      // wants to do parsing after we return.
 207      extensionDeclarationsCache_ = [[parent extensionDeclarationsCache] retain];
 208      GDATA_DEBUG_ASSERT(extensionDeclarationsCache_ != nil, @"missing extn decl");
 209
 210      attributeDeclarationsCache_ = [[parent attributeDeclarationsCache] retain];
 211      GDATA_DEBUG_ASSERT(extensionDeclarationsCache_ != nil, @"missing attr decl");
 212
 213    } else {
 214      // parent is nil, so this is the topmost parent
 215      extensionDeclarationsCache_ = [[NSMutableDictionary alloc] init];
 216
 217      attributeDeclarationsCache_ = [[NSMutableDictionary alloc] init];
 218    }
 219
 220    [self setNamespaces:[[self class] dictionaryForElementNamespaces:element]];
 221    [self addUnknownChildNodesForElement:element];
 222
 223    // if we've not previously cached declarations for this class,
 224    // add the declarations now
 225    Class currClass = [self class];
 226    NSDictionary *prevExtnDecls = [extensionDeclarationsCache_ objectForKey:currClass];
 227    if (prevExtnDecls == nil) {
 228      [self addExtensionDeclarations];
 229    }
 230
 231    NSMutableArray *prevAttrDecls = [attributeDeclarationsCache_ objectForKey:currClass];
 232    if (prevAttrDecls == nil) {
 233      [self addParseDeclarations];
 234      // if any parse declarations are added, attributeDeclarations_ will be set
 235      // to the cached copy of this object's attribute decls
 236    } else {
 237      GDATA_DEBUG_ASSERT(attributeDeclarations_ == nil, @"attrDecls previously set");
 238      attributeDeclarations_ = [prevAttrDecls retain];
 239    }
 240
 241    [self parseExtensionsForElement:element];
 242    [self parseAttributesForElement:element];
 243    [self parseContentValueForElement:element];
 244    [self keepChildXMLElementsForElement:element];
 245    [self setElementName:[element name]];
 246
 247    if (parent != nil) {
 248      // rather than keep a reference to the cache of declarations in the
 249      // parent, set our pointer to nil; if a subclass continues to parse, the
 250      // getter will obtain them by calling into the parent.  This lets callers
 251      // free up the extensionDeclarations_ when parsing is done by just
 252      // freeing them in the topmost parent with clearExtensionDeclarationsCache
 253      [extensionDeclarationsCache_ release];
 254      extensionDeclarationsCache_ = nil;
 255
 256      [attributeDeclarationsCache_ release];
 257      attributeDeclarationsCache_ = nil;
 258    }
 259
 260#if GDATA_USES_LIBXML
 261    if (!shouldIgnoreUnknowns_) {
 262      // retain the element so that pointers to internal nodes remain valid
 263      [self setProperty:element forKey:kGDataXMLElementPropertyKey];
 264    }
 265#endif
 266  }
 267  return self;
 268}
 269
 270- (BOOL)isEqual:(GDataObject *)other {
 271  if (self == other) return YES;
 272  if (![other isKindOfClass:[self class]]) return NO;
 273
 274  // We used to compare the local names of the objects with
 275  // NSXMLNode's localNameForName: on each object's elementName, but that
 276  // prevents us from comparing the contents of a manually-constructed object
 277  // (which lacks a specific local name) with one found in an actual XML feed.
 278
 279#if GDATA_USES_LIBXML
 280  // libxml adds namespaces when copying elements, so we can't rely
 281  // on those when comparing nodes
 282  return AreEqualOrBothNil([self extensions], [other extensions])
 283    && [self hasAttributesEqualToAttributesOf:other]
 284    && [self hasContentValueEqualToContentValueOf:other]
 285    && [self hasChildXMLElementsEqualToChildXMLElementsOf:other];
 286#else
 287  return AreEqualOrBothNil([self extensions], [other extensions])
 288    && [self hasAttributesEqualToAttributesOf:other]
 289    && [self hasContentValueEqualToContentValueOf:other]
 290    && [self hasChildXMLElementsEqualToChildXMLElementsOf:other]
 291    && AreEqualOrBothNil([self namespaces], [other namespaces]);
 292#endif
 293
 294  // What we're not comparing here:
 295  //   parent object pointers
 296  //   extension declarations
 297  //   unknown attributes & children
 298  //   local element names
 299  //   service version
 300  //   userData
 301}
 302
 303// By definition, for two objects to potentially be considered equal,
 304// they must have the same hash value.  The hash is mostly ignored,
 305// but removeObjectsInArray: in Leopard does seem to check the hash,
 306// and NSObject's default hash method just returns the instance pointer.
 307// We'll define hash here for all of our GDataObjects.
 308- (NSUInteger)hash {
 309  return (NSUInteger) (void *) [GDataObject class];
 310}
 311
 312- (id)copyWithZone:(NSZone *)zone {
 313  GDataObject* newObject = [[[self class] allocWithZone:zone] init];
 314  [newObject setElementName:[self elementName]];
 315  [newObject setParent:nil];
 316  [newObject setServiceVersion:[self serviceVersion]];
 317
 318  NSDictionary *namespaces =
 319    [GDataUtilities mutableDictionaryWithCopiesOfObjectsInDictionary:[self namespaces]];
 320  [newObject setNamespaces:namespaces];
 321
 322  NSDictionary *extensions =
 323    [GDataUtilities mutableDictionaryWithCopiesOfArraysInDictionary:[self extensions]];
 324  [newObject setExtensions:extensions];
 325
 326  NSDictionary *attributes =
 327    [GDataUtilities mutableDictionaryWithCopiesOfObjectsInDictionary:[self attributes]];
 328  [newObject setAttributes:attributes];
 329
 330  [newObject setAttributeDeclarations:[self attributeDeclarations]];
 331  // we copy the attribute declarations, which are retained by this object,
 332  // but we do not copy not the caches of extension or attribute declarations,
 333  // as those will be invalid once the top parent is released
 334
 335  // a marker in the attributes cache indicates the content value and
 336  // and child XML declaration settings
 337  if ([self hasDeclaredContentValue]) {
 338    [newObject setContentStringValue:[self contentStringValue]];
 339  }
 340
 341  if ([self hasDeclaredChildXMLElements]) {
 342    NSArray *childElements = [self childXMLElements];
 343    NSArray *arr = [GDataUtilities arrayWithCopiesOfObjectsInArray:childElements];
 344    [newObject setChildXMLElements:arr];
 345  }
 346
 347  BOOL shouldIgnoreUnknowns = [self shouldIgnoreUnknowns];
 348  [newObject setShouldIgnoreUnknowns:shouldIgnoreUnknowns];
 349
 350  if (!shouldIgnoreUnknowns) {
 351    NSArray *unknownChildren =
 352      [GDataUtilities mutableArrayWithCopiesOfObjectsInArray:[self unknownChildren]];
 353    [newObject setUnknownChildren:unknownChildren];
 354
 355    NSArray *unknownAttributes =
 356      [GDataUtilities mutableArrayWithCopiesOfObjectsInArray:[self unknownAttributes]];
 357    [newObject setUnknownAttributes:unknownAttributes];
 358  }
 359
 360  return newObject;
 361
 362  // What we're not copying:
 363  //   parent object pointer
 364  //   surrogates
 365  //   userData
 366  //   userProperties
 367}
 368
 369- (void)dealloc {
 370  [elementName_ release];
 371  [namespaces_ release];
 372  [extensionDeclarationsCache_ release];
 373  [attributeDeclarationsCache_ release];
 374  [attributeDeclarations_ release];
 375  [extensions_ release];
 376  [attributes_ release];
 377  [contentValue_ release];
 378  [childXMLElements_ release];
 379  [unknownChildren_ release];
 380  [unknownAttributes_ release];
 381  [surrogates_ release];
 382  [serviceVersion_ release];
 383  [coreProtocolVersion_ release];
 384  [userData_ release];
 385  [userProperties_ release];
 386  [super dealloc];
 387}
 388
 389// XMLElement must be implemented by subclasses
 390- (NSXMLElement *)XMLElement {
 391  // subclass should override if they have custom elements or attributes
 392  NSXMLElement *element = [self XMLElementWithExtensionsAndDefaultName:nil];
 393  return element;
 394}
 395
 396- (NSXMLDocument *)XMLDocument {
 397  NSXMLElement *element = [self XMLElement];
 398  NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithRootElement:(id)element] autorelease];
 399  [doc setVersion:@"1.0"];
 400  [doc setCharacterEncoding:@"UTF-8"];
 401  return doc;
 402}
 403
 404- (BOOL)generateContentInputStream:(NSInputStream **)outInputStream
 405                            length:(unsigned long long *)outLength
 406                           headers:(NSDictionary **)outHeaders {
 407  // subclasses may return a data stream representing this object
 408  // for uploading
 409  return NO;
 410}
 411
 412- (NSString *)uploadMIMEType {
 413  // subclasses may return the type of data to be uploaded
 414  return nil;
 415}
 416
 417- (NSData *)uploadData {
 418  // subclasses may return data to be uploaded along with the object
 419  return nil;
 420}
 421
 422- (NSFileHandle *)uploadFileHandle {
 423  // subclasses may return a file handle to be uploaded along with the object
 424  return nil;
 425}
 426
 427- (NSURL *)uploadLocationURL {
 428  // subclasses may return a resumable upload location URL for restarting
 429  // uploads
 430  return nil;
 431}
 432
 433- (BOOL)shouldUploadDataOnly {
 434  return NO;
 435}
 436
 437#pragma mark -
 438
 439- (void)setElementName:(NSString *)name {
 440  [elementName_ release];
 441  elementName_ = [name copy];
 442}
 443
 444- (NSString *)elementName {
 445  return elementName_;
 446}
 447
 448- (void)setNamespaces:(NSDictionary *)dict {
 449  [namespaces_ release];
 450  namespaces_ = [dict mutableCopy];
 451}
 452
 453- (void)addNamespaces:(NSDictionary *)dict {
 454  if (namespaces_ == nil) {
 455    namespaces_ = [[NSMutableDictionary alloc] init];
 456  }
 457  [namespaces_ addEntriesFromDictionary:dict];
 458}
 459
 460- (NSDictionary *)namespaces {
 461  return namespaces_;
 462}
 463
 464- (NSDictionary *)completeNamespaces {
 465  // return a dictionary containing all namespaces
 466  // in this object and its parent objects
 467  NSDictionary *parentNamespaces = [parent_ completeNamespaces];
 468  NSDictionary *ownNamespaces = namespaces_;
 469
 470  if (ownNamespaces == nil) return parentNamespaces;
 471  if (parentNamespaces == nil) return ownNamespaces;
 472
 473  // combine them, replacing parent-defined prefixes with own ones
 474  NSMutableDictionary *mutableDict;
 475
 476  mutableDict = [NSMutableDictionary dictionaryWithDictionary:parentNamespaces];
 477  [mutableDict addEntriesFromDictionary:ownNamespaces];
 478  return mutableDict;
 479}
 480
 481- (void)pruneInheritedNamespaces {
 482
 483  if (parent_ == nil || [namespaces_ count] == 0) return;
 484
 485  // if a prefix is explicitly defined the same for the parent as it is locally,
 486  // remove it, since we can rely on the parent's definition
 487  NSMutableDictionary *prunedNamespaces
 488    = [NSMutableDictionary dictionaryWithDictionary:namespaces_];
 489
 490  NSDictionary *parentNamespaces = [parent_ completeNamespaces];
 491
 492  for (NSString *prefix in namespaces_) {
 493
 494    NSString *ownURI = [namespaces_ objectForKey:prefix];
 495    NSString *parentURI = [parentNamespaces objectForKey:prefix];
 496
 497    if (AreEqualOrBothNil(ownURI, parentURI)) {
 498      [prunedNamespaces removeObjectForKey:prefix];
 499    }
 500  }
 501
 502  [self setNamespaces:prunedNamespaces];
 503}
 504
 505- (void)setParent:(GDataObject *)obj {
 506  parent_ = obj; // parent_ is a weak (not retained) reference
 507}
 508
 509- (GDataObject *)parent {
 510  return parent_;
 511}
 512
 513- (void)setAttributeDeclarationsCache:(NSDictionary *)cache {
 514  [attributeDeclarationsCache_ autorelease];
 515  attributeDeclarationsCache_ = [cache mutableCopy];
 516}
 517
 518- (NSMutableDictionary *)attributeDeclarationsCache {
 519  // warning: rely on this only during parsing; it will not be safe if the
 520  //          top parent is no longer allocated
 521  if (attributeDeclarationsCache_) {
 522    return attributeDeclarationsCache_;
 523  }
 524  return [[self parent] attributeDeclarationsCache];
 525}
 526
 527- (void)setAttributeDeclarations:(NSArray *)array {
 528  [attributeDeclarations_ autorelease];
 529  attributeDeclarations_ = [array mutableCopy];
 530}
 531
 532- (NSMutableArray *)attributeDeclarations {
 533  return attributeDeclarations_;
 534}
 535
 536- (void)setAttributes:(NSDictionary *)dict {
 537  [attributes_ autorelease];
 538  attributes_ = [dict mutableCopy];
 539}
 540
 541- (NSDictionary *)attributes {
 542  return attributes_;
 543}
 544
 545- (void)setExtensions:(NSDictionary *)extensions {
 546  [extensions_ autorelease];
 547  extensions_ = [extensions mutableCopy];
 548}
 549
 550- (NSDictionary *)extensions {
 551  return extensions_;
 552}
 553
 554- (void)setExtensionDeclarationsCache:(NSDictionary *)decls {
 555  [extensionDeclarationsCache_ autorelease];
 556  extensionDeclarationsCache_ = [decls mutableCopy];
 557}
 558
 559- (NSMutableDictionary *)extensionDeclarationsCache {
 560  // warning: rely on this only during parsing; it will not be safe if the
 561  //          top parent is no longer allocated
 562  if (extensionDeclarationsCache_) {
 563    return extensionDeclarationsCache_;
 564  }
 565
 566  return [[self parent] extensionDeclarationsCache];
 567}
 568
 569- (void)clearExtensionDeclarationsCache {
 570  // allows external classes to free up the declarations
 571  [self setExtensionDeclarationsCache:nil];
 572}
 573
 574- (void)setUnknownChildren:(NSArray *)arr {
 575  [unknownChildren_ autorelease];
 576  unknownChildren_ = [arr mutableCopy];
 577}
 578
 579- (NSArray *)unknownChildren {
 580  return unknownChildren_;
 581}
 582
 583- (void)setUnknownAttributes:(NSArray *)arr {
 584  [unknownAttributes_ autorelease];
 585  unknownAttributes_ = [arr mutableCopy];
 586}
 587
 588- (NSArray *)unknownAttributes {
 589  return unknownAttributes_;
 590}
 591
 592- (void)setShouldIgnoreUnknowns:(BOOL)flag {
 593  shouldIgnoreUnknowns_ = flag;
 594}
 595
 596- (BOOL)shouldIgnoreUnknowns {
 597  return shouldIgnoreUnknowns_;
 598}
 599
 600- (void)setSurrogates:(NSDictionary *)surrogates {
 601  [surrogates_ autorelease];
 602  surrogates_ = [surrogates retain];
 603}
 604
 605- (NSDictionary *)surrogates {
 606  return surrogates_;
 607}
 608
 609+ (NSString *)defaultServiceVersion {
 610  return nil;
 611}
 612
 613- (void)setServiceVersion:(NSString *)str {
 614  if (!AreEqualOrBothNil(str, serviceVersion_)) {
 615    // reset the core protocol version, since it's based on the service version
 616    [self setCoreProtocolVersion:nil];
 617
 618    [serviceVersion_ autorelease];
 619    serviceVersion_ = [str copy];
 620  }
 621}
 622
 623- (NSString *)serviceVersion {
 624  if (serviceVersion_ != nil) {
 625    return serviceVersion_;
 626  }
 627
 628  NSString *str = [[self class] defaultServiceVersion];
 629  return str;
 630}
 631
 632- (BOOL)isServiceVersionAtLeast:(NSString *)otherVersion {
 633  NSString *serviceVersion = [self serviceVersion];
 634  NSComparisonResult result = [GDataUtilities compareVersion:serviceVersion
 635                                                   toVersion:otherVersion];
 636  return (result != NSOrderedAscending);
 637}
 638
 639- (BOOL)isServiceVersionAtMost:(NSString *)otherVersion {
 640  NSString *serviceVersion = [self serviceVersion];
 641  NSComparisonResult result = [GDataUtilities compareVersion:serviceVersion
 642                                                   toVersion:otherVersion];
 643  return (result != NSOrderedDescending);
 644}
 645
 646- (void)setCoreProtocolVersion:(NSString *)str {
 647  [coreProtocolVersion_ autorelease];
 648  coreProtocolVersion_ = [str copy];
 649}
 650
 651- (NSString *)coreProtocolVersion {
 652  if (coreProtocolVersion_ != nil) {
 653    return coreProtocolVersion_;
 654  }
 655
 656  NSString *serviceVersion = [self serviceVersion];
 657  NSString *coreVersion = [[self class] coreProtocolVersionForServiceVersion:serviceVersion];
 658
 659  [self setCoreProtocolVersion:coreVersion];
 660  return coreVersion;
 661}
 662
 663- (BOOL)isCoreProtocolVersion1 {
 664  NSString *coreVersion = [self coreProtocolVersion];
 665
 666  // technically the version number is <integer>.<integer> rather than a float,
 667  // but intValue is a simple way to test just the major portion
 668  int majorVer = [coreVersion intValue];
 669  return (majorVer <= 1);
 670}
 671
 672+ (NSString *)coreProtocolVersionForServiceVersion:(NSString *)str {
 673  // subclasses may override this when their service versions
 674  // do not match the core protocol version
 675  return str;
 676}
 677
 678#pragma mark userData and properties
 679
 680- (void)setUserData:(id)userData {
 681  [userData_ autorelease];
 682  userData_ = [userData retain];
 683}
 684
 685- (id)userData {
 686  // be sure the returned pointer has the life of the autorelease pool,
 687  // in case self is released immediately
 688  return [[userData_ retain] autorelease];
 689}
 690
 691- (void)setProperties:(NSDictionary *)dict {
 692  [userProperties_ autorelease];
 693  userProperties_ = [dict mutableCopy];
 694}
 695
 696- (NSDictionary *)properties {
 697  // be sure the returned pointer has the life of the autorelease pool,
 698  // in case self is released immediately
 699  return [[userProperties_ retain] autorelease];
 700}
 701
 702- (void)setProperty:(id)obj forKey:(NSString *)key {
 703
 704  if (obj == nil) {
 705    // user passed in nil, so delete the property
 706    [userProperties_ removeObjectForKey:key];
 707  } else {
 708    // be sure the property dictionary exists
 709    if (userProperties_ == nil) {
 710      [self setProperties:[NSDictionary dictionary]];
 711    }
 712    [userProperties_ setObject:obj forKey:key];
 713  }
 714}
 715
 716- (id)propertyForKey:(NSString *)key {
 717  id obj = [userProperties_ objectForKey:key];
 718
 719  // be sure the returned pointer has the life of the autorelease pool,
 720  // in case self is released immediately
 721  return [[obj retain] autorelease];
 722}
 723
 724#pragma mark XML generation helpers
 725
 726- (void)addNamespacesToElement:(NSXMLElement *)element {
 727
 728  // we keep namespaces in a dictionary with prefixes
 729  // as keys.  We'll step through our namespaces and convert them
 730  // to NSXML-stype namespaces.
 731  for (NSString *prefix in namespaces_) {
 732
 733    NSString *uri = [namespaces_ objectForKey:prefix];
 734
 735    // no per-version namespace transforms are currently needed
 736    // uri = [self updatedVersionedNamespaceURIForPrefix:prefix
 737    //                                              URI:uri];
 738
 739    [element addNamespace:[NSXMLElement namespaceWithName:prefix
 740                                              stringValue:uri]];
 741  }
 742}
 743
 744- (void)addExtensionsToElement:(NSXMLElement *)element {
 745  // extensions are in a dictionary of arrays, keyed by the class
 746  // of each kind of element
 747
 748  // note: this adds actual extensions, not declarations
 749  NSDictionary *extensions = [self extensions];
 750
 751  // step through each extension, by class, and add those
 752  // objects to the XML element
 753  for (Class oneClass in extensions) {
 754
 755    id objectOrArray = [extensions_ objectForKey:oneClass];
 756
 757    if ([objectOrArray isKindOfClass:[NSArray class]]) {
 758      [self addToElement:element XMLElementsForArray:objectOrArray];
 759    } else {
 760      [self addToElement:element XMLElementForObject:objectOrArray];
 761    }
 762  }
 763}
 764
 765- (void)addUnknownChildNodesToElement:(NSXMLElement *)element {
 766
 767  // we'll add every element and attribute as "unknown", then remove them
 768  // from this list as we parse them to create the GData object. Anything
 769  // left remaining in this list is considered unknown.
 770
 771  if (shouldIgnoreUnknowns_) return;
 772
 773  // we have to copy the children so they don't point at the previous parent
 774  // nodes
 775  for (NSXMLNode *child in unknownChildren_) {
 776    [element addChild:[[child copy] autorelease]];
 777  }
 778
 779  for (NSXMLNode *attr in unknownAttributes_) {
 780
 781    GDATA_DEBUG_ASSERT([element attributeForName:[attr name]] == nil,
 782              @"adding duplicate of attribute %@ (perhaps an object parsed with"
 783              "attributeForName: instead of attributeForName:fromElement:)",
 784              attr);
 785
 786    [element addAttribute:[[attr copy] autorelease]];
 787  }
 788}
 789
 790// this method creates a basic XML element from this GData object.
 791//
 792// this is called by the XMLElement method of subclasses; they will add their
 793// own attributes and children to the element returned by this method
 794//
 795// extensions may pass nil for defaultName to use the name specified in their
 796// extensionElementLocalName and extensionElementPrefix
 797
 798- (NSXMLElement *)XMLElementWithExtensionsAndDefaultName:(NSString *)defaultName {
 799
 800#if 0
 801  // code sometimes useful for finding unparsed xml; this can be turned on
 802  // during testing
 803  if ([unknownAttributes_ count]) {
 804    NSLog(@"%@ %p: unknown attributes %@\n%@\n", [self class], self, unknownAttributes_, self);
 805  }
 806  if ([unknownChildren_ count]) {
 807    NSLog(@"%@ %p: unknown children %@\n%@\n", [self class], self, unknownChildren_, self);
 808  }
 809#endif
 810
 811  // use the name from the XML
 812  NSString *elementName = [self elementName];
 813  if (!elementName) {
 814
 815    // if no name from the XML, use the name our class's XML element
 816    // routine supplied as a default
 817    if (defaultName) {
 818      elementName = defaultName;
 819    } else {
 820      // if no default name from the class, and this class is an extension,
 821      // use the extension's default element name
 822      if ([[self class] conformsToProtocol:@protocol(GDataExtension)]) {
 823
 824        elementName = [self qualifiedNameForExtensionClass:[self class]];
 825      } else {
 826        // if not an extension, just use the class name
 827        elementName = NSStringFromClass([self class]);
 828
 829        GDATA_DEBUG_LOG(@"GDataObject generating XML element with unknown name for class %@",
 830              elementName);
 831      }
 832    }
 833  }
 834
 835  NSXMLElement *element = [NSXMLNode elementWithName:elementName];
 836  [self addNamespacesToElement:element];
 837  [self addAttributesToElement:element];
 838  [self addContentValueToElement:element];
 839  [self addChildXMLElementsToElement:element];
 840  [self addExtensionsToElement:element];
 841  [self addUnknownChildNodesToElement:element];
 842  return element;
 843}
 844
 845- (NSXMLNode *)addToElement:(NSXMLElement *)element
 846     attributeValueIfNonNil:(NSString *)val
 847                   withName:(NSString *)name {
 848  if (val) {
 849    NSString *filtered = [GDataUtilities stringWithControlsFilteredForString:val];
 850
 851    NSXMLNode* attr = [NSXMLNode attributeWithName:name stringValue:filtered];
 852    [element addAttribute:attr];
 853    return attr;
 854  }
 855  return nil;
 856}
 857
 858- (NSXMLNode *)addToElement:(NSXMLElement *)element
 859     attributeValueIfNonNil:(NSString *)val
 860          withQualifiedName:(NSString *)qName
 861                        URI:(NSString *)attributeURI {
 862
 863  if (attributeURI == nil) {
 864    return [self addToElement:element
 865       attributeValueIfNonNil:val
 866                     withName:qName];
 867  }
 868
 869  if (val) {
 870    NSString *filtered = [GDataUtilities stringWithControlsFilteredForString:val];
 871
 872    NSXMLNode *attr = [NSXMLNode attributeWithName:qName
 873                                               URI:attributeURI
 874                                       stringValue:filtered];
 875    if (attr != nil) {
 876      [element addAttribute:attr];
 877      return attr;
 878    }
 879  }
 880  return nil;
 881}
 882
 883- (NSXMLNode *)addToElement:(NSXMLElement *)element
 884  attributeValueWithInteger:(NSInteger)val
 885                   withName:(NSString *)name {
 886  NSString* str = [NSString stringWithFormat:@"%ld", (long)val];
 887  NSXMLNode* attr = [NSXMLNode attributeWithName:name stringValue:str];
 888  [element addAttribute:attr];
 889  return attr;
 890}
 891
 892// adding a child to an XML element
 893- (NSXMLNode *)addToElement:(NSXMLElement *)element
 894childWithStringValueIfNonEmpty:(NSString *)str
 895                   withName:(NSString *)name {
 896  if ([str length] > 0) {
 897    NSXMLNode *child = [NSXMLElement elementWithName:name stringValue:str];
 898    [element addChild:child];
 899    return child;
 900  }
 901  return nil;
 902}
 903
 904// call the object's XMLElement method, and add the result as a new XML child
 905// element
 906- (void)addToElement:(NSXMLElement *)element
 907 XMLElementForObject:(id)object {
 908
 909  if ([object isKindOfClass:[GDataAttribute class]]) {
 910
 911    // attribute extensions are not GDataObjects and don't implement
 912    // XMLElement; we just get the attribute value from them
 913    NSString *str = [object stringValue];
 914    NSString *qName = [self qualifiedNameForExtensionClass:[object class]];
 915    NSString *theURI = [[object class] extensionElementURI];
 916
 917    [self addToElement:element
 918attributeValueIfNonNil:str
 919     withQualifiedName:qName
 920                   URI:theURI];
 921
 922  } else {
 923    // element extension
 924    NSXMLElement *child = [object XMLElement];
 925    if (child) {
 926      [element addChild:child];
 927    }
 928  }
 929}
 930
 931// call the XMLElement method for each object in the array
 932- (void)addToElement:(NSXMLElement *)element
 933 XMLElementsForArray:(NSArray *)arrayOfGDataObjects {
 934  for(id item in arrayOfGDataObjects) {
 935    [self addToElement:element XMLElementForObject:item];
 936  }
 937}
 938
 939#pragma mark description method helpers
 940
 941#if !GDATA_SIMPLE_DESCRIPTIONS
 942// if the description label begins with version<= or version>= then do a service
 943// version check
 944//
 945// returns the label with any version prefix removed, or returns nil if the
 946// description fails the version check and should not be evaluated
 947
 948- (NSString *)labelAdjustedForVersion:(NSString *)origLabel {
 949
 950  BOOL checkMinVersion = NO;
 951  BOOL checkMaxVersion = NO;
 952  NSString *prefix = nil;
 953
 954  static NSString *const kMinVersionPrefix = @"version>=";
 955  static NSString *const kMaxVersionPrefix = @"version<=";
 956
 957  if ([origLabel hasPrefix:kMinVersionPrefix]) {
 958    checkMinVersion = YES;
 959    prefix = kMinVersionPrefix;
 960  } else if ([origLabel hasPrefix:kMaxVersionPrefix]) {
 961    checkMaxVersion = YES;
 962    prefix = kMaxVersionPrefix;
 963  }
 964
 965  if (!checkMaxVersion && !checkMinVersion) return origLabel;
 966
 967  // there is a version prefix; scan and test the version string,
 968  // and if the test succeeds, return the label without the prefix
 969  NSString *newLabel = origLabel;
 970  NSString *versionStr = nil;
 971  NSScanner *scanner = [NSScanner scannerWithString:origLabel];
 972
 973  if ([scanner scanString:prefix intoString:NULL]
 974      && [scanner scanUpToString:@":" intoString:&versionStr]
 975      && [scanner scanString:@":" intoString:NULL]
 976      && [scanner scanUpToString:@"\n" intoString:&newLabel]) {
 977
 978    if ((checkMinVersion && ![self isServiceVersionAtLeast:versionStr])
 979        || (checkMaxVersion && ![self isServiceVersionAtMost:versionStr])) {
 980      // version test failed
 981      return nil;
 982    }
 983  }
 984  return newLabel;
 985}
 986#endif
 987
 988- (void)addDescriptionRecords:(GDataDescriptionRecord *)descRecordList
 989                      toItems:(NSMutableArray *)items {
 990#if !GDATA_SIMPLE_DESCRIPTIONS
 991  // the final descRecord in the list should be { nil, nil, 0 }
 992
 993  for (NSUInteger idx = 0; descRecordList[idx].label != nil; idx++) {
 994
 995    GDataDescRecTypes reportType = descRecordList[idx].reportType;
 996    NSString *label = descRecordList[idx].label;
 997    NSString *keyPath = descRecordList[idx].keyPath;
 998
 999    label = [self labelAdjustedForVersion:label];
1000    if (label == nil) continue;
1001
1002    id value;
1003    NSString *str;
1004
1005    if (reportType == kGDataDescValueIsKeyPath) {
1006      value = keyPath;
1007    } else {
1008      value = [self valueForKeyPath:keyPath];
1009    }
1010
1011    switch (reportType) {
1012
1013      case kGDataDescValueLabeled:
1014      case kGDataDescValueIsKeyPath:
1015        [self addToArray:items objectDescriptionIfNonNil:value withName:label];
1016        break;
1017
1018      case kGDataDescLabelIfNonNil:
1019        if (value != nil) [items addObject:label];
1020        break;
1021
1022      case kGDataDescArrayCount:
1023        if ([(NSArray *)value count] > 0) {
1024          str = [NSString stringWithFormat:@"%lu", (unsigned long) [(NSArray *)value count]];
1025          [self addToArray:items objectDescriptionIfNonNil:str withName:label];
1026        }
1027        break;
1028
1029      case kGDataDescArrayDescs:
1030        if ([(NSArray *)value count] > 0) {
1031          [self addToArray:items objectDescriptionIfNonNil:value withName:label];
1032        }
1033        break;
1034
1035      case kGDataDescBooleanLabeled:
1036        // display the label with YES or NO
1037        str = ([value boolValue] ? @"YES" : @"NO");
1038        [self addToArray:items objectDescriptionIfNonNil:str withName:label];
1039        break;
1040
1041      case kGDataDescBooleanPresent:
1042        // display the label:YES only if present
1043        if ([value boolValue]) {
1044          [self addToArray:items objectDescriptionIfNonNil:@"YES" withName:label];
1045        }
1046        break;
1047
1048      case kGDataDescNonZeroLength:
1049        // display the length if non-zero
1050        if ([(NSData *)value length] > 0) {
1051          str = [NSString stringWithFormat:@"#%lu",
1052                 (unsigned long) [(NSData *)value length]];
1053          [self addToArray:items objectDescriptionIfNonNil:str withName:label];
1054        }
1055        break;
1056    }
1057  }
1058#endif
1059}
1060
1061- (void)addToArray:(NSMutableArray *)stringItems
1062objectDescriptionIfNonNil:(id)obj
1063          withName:(NSString *)name {
1064
1065  if (obj) {
1066    if (name) {
1067      [stringItems addObject:[NSString stringWithFormat:@"%@:%@", name, obj]];
1068    } else {
1069      [stringItems addObject:[obj description]];
1070    }
1071  }
1072}
1073
1074- (void)addAttributeDescriptionsToArray:(NSMutableArray *)stringItems {
1075
1076  // add attribute descriptions in the order the attributes were declared
1077  NSArray *attributeDeclarations = [self attributeDeclarations];
1078  for (NSString *name in attributeDeclarations) {
1079
1080    NSString *value = [attributes_ valueForKey:name];
1081    [self addToArray:stringItems objectDescriptionIfNonNil:value withName:name];
1082  }
1083}
1084
1085- (void)addContentDescriptionToArray:(NSMutableArray *)stringItems
1086                            withName:(NSString *)name {
1087  if ([self hasDeclaredContentValue]) {
1088    NSString *value = [self contentStringValue];
1089    [self addToArray:stringItems objectDescriptionIfNonNil:value withName:name];
1090  }
1091}
1092
1093- (void)addChildXMLElementsDescriptionToArray:(NSMutableArray *)stringItems {
1094  if ([self hasDeclaredChildXMLElements]) {
1095
1096    NSArray *childXMLElements = [self childXMLElements];
1097    if ([childXMLElements count] > 0) {
1098
1099      NSArray *xmlStrings = [childXMLElements valueForKey:@"XMLString"];
1100      NSString *combinedStr = [xmlStrings componentsJoinedByString:@""];
1101
1102      [self addToArray:stringItems objectDescriptionIfNonNil:combinedStr withName:@"XML"];
1103    }
1104  }
1105}
1106
1107- (NSMutableArray *)itemsForDescription {
1108  NSMutableArray *items = [NSMutableArray array];
1109  [self addAttributeDescriptionsToArray:items];
1110  [self addContentDescriptionToArray:items withName:@"content"];
1111
1112#if GDATA_SIMPLE_DESCRIPTIONS
1113  // with GDATA_SIMPLE_DESCRIPTIONS set, subclasses aren't adding their
1114  // own description items for extensions, so we'll just list the extension
1115  // elements that are present, by their qualified xml names
1116  //
1117  // The description string will look like
1118  //   {extensions:(gCal:color,link(3),gd:etag,id,updated)}
1119  NSMutableArray *extnsItems = [NSMutableArray array];
1120
1121  for (Class extClass in extensions_) {
1122
1123    // add the qualified XML name for each extension, followed by (n) when
1124    // there is more than one instance
1125    NSString *qname = [self qualifiedNameForExtensionClass:extClass];
1126
1127    // there's one instance of this extension, unless the value is an array
1128    NSUInteger numberOfInstances = 1;
1129    id extnObj = [extensions_ objectForKey:extClass];
1130    if ([extnObj isKindOfClass:[NSArray class]]) {
1131      numberOfInstances = [extnObj count];
1132    }
1133
1134    if (numberOfInstances == 1) {
1135      [extnsItems addObject:qname];
1136    } else {
1137      // append number of occurrences to the xml name
1138      NSString *str = [NSString stringWithFormat:@"%@(%lu)", qname,
1139                       (unsigned long) numberOfInstances];
1140      [extnsItems addObject:str];
1141    }
1142  }
1143
1144  if ([extnsItems count] > 0) {
1145    // sort for predictable ordering in unit tests
1146    NSArray *sortedItems = [extnsItems sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
1147    NSString *extnsStr = [NSString stringWithFormat:@"extensions:(%@)",
1148                          [sortedItems componentsJoinedByString:@","]];
1149    [items addObject:extnsStr];
1150  }
1151#endif
1152
1153  [self addChildXMLElementsDescriptionToArray:items];
1154  return items;
1155}
1156
1157- (NSString *)descriptionWithItems:(NSArray *)items {
1158
1159  NSString *str;
1160
1161  if ([items count] > 0) {
1162    str = [NSString stringWithFormat:@"%@ %p: {%@}",
1163      [self class], self, [items componentsJoinedByString:@" "]];
1164
1165  } else {
1166    str = [NSString stringWithFormat:@"%@ %p", [self class], self];
1167  }
1168  return str;
1169}
1170
1171- (NSString *)description {
1172
1173  NSMutableArray *items = [self itemsForDescription];
1174
1175#if !GDATA_SIMPLE_DESCRIPTIONS
1176  // add names of unknown children and attributes to the descriptions
1177  if ([unknownChildren_ count] > 0) {
1178    // remove repeats and put the element names in < > so they are more
1179    // readable
1180    NSArray *names = [unknownChildren_ valueForKey:@"name"];
1181    NSSet *namesSet = [NSSet setWithArray:names];
1182    NSMutableArray *fmtNames = [NSMutableArray arrayWithCapacity:[namesSet count]];
1183
1184    for (NSString *name in namesSet) {
1185      NSString *fmtName = [NSString stringWithFormat:@"<%@>", name];
1186      [fmtNames addObject:fmtName];
1187    }
1188
1189    // sort the names so the output is deterministic despite the set/array
1190    // conversion
1191    NSArray *sortedNames = [fmtNames sortedArrayUsingSelector:@selector(compare:)];
1192    NSString *desc = [sortedNames componentsJoinedByString:@","];
1193    [self addToArray:items objectDescriptionIfNonNil:desc withName:@"unparsed"];
1194  }
1195
1196  if ([unknownAttributes_ count] > 0) {
1197    NSArray *names = [unknownAttributes_ valueForKey:@"name"];
1198    NSString *desc = [names componentsJoinedByString:@","];
1199    [self addToArray:items objectDescriptionIfNonNil:desc withName:@"unparsedAttr"];
1200  }
1201#endif
1202
1203  NSString *str = [self descriptionWithItems:items];
1204  return str;
1205}
1206
1207
1208#pragma mark XML parsing helpers
1209
1210+ (NSDictionary *)dictionaryForElementNamespaces:(NSXMLElement *)element {
1211
1212  NSMutableDictionary *dict = nil;
1213
1214  // for each namespace node, add a dictionary entry with the namespace
1215  // name (prefix) as key and the URI as value
1216  //
1217  // note: the prefix may be an empty string
1218
1219  NSArray *namespaceNodes = [element namespaces];
1220
1221  NSUInteger numberOfNamespaces = [namespaceNodes count];
1222
1223  if (numberOfNamespaces > 0) {
1224
1225    dict = [NSMutableDictionary dictionary];
1226
1227    for (unsigned int idx = 0; idx < numberOfNamespaces; idx++) {
1228      NSXMLNode *node = [namespaceNodes objectAtIndex:idx];
1229      [dict setObject:[node stringValue]
1230               forKey:[node name]];
1231    }
1232  }
1233  return dict;
1234}
1235
1236// classOrSurrogateForClass searches this object instance and all parent
1237// instances for a user surrogate for the supplied class, and returns
1238// the surrogate, or else the supplied class if no surrogate is found for it
1239- (Class)classOrSurrogateForClass:(Class)standardClass {
1240
1241  for (GDataObject *currentObject = self;
1242       currentObject != nil;
1243       currentObject = [currentObject parent]) {
1244
1245    // look for an object with a surrogates dict containing the standardClass
1246    NSDictionary *currentSurrogates = [currentObject surrogates];
1247
1248    Class surrogate = (Class)[currentSurrogates objectForKey:standardClass];
1249    if (surrogate) return surrogate;
1250  }
1251  return standardClass;
1252}
1253
1254// The following routines which parse XML elements remove the parsed elements
1255// from the list of unknowns.
1256
1257// objectForElementWithNameIfAny:objectClass:objectClass: creates
1258// a single GDataObject of the specified class for the first XML child element
1259// with the specified name. Returns nil if no child element is present
1260//
1261// If objectClass is nil, the class is looked up from the registrations
1262// of entry and feed classes.
1263- (id)objectForChildOfElement:(NSXMLElement *)parentElement
1264                qualifiedName:(NSString *)qualifiedName
1265                 namespaceURI:(NSString *)namespaceURI
1266                  objectClass:(Class)objectClass {
1267  id object = nil;
1268  NSXMLElement *element = [self childWithQualifiedName:qualifiedName
1269                                          namespaceURI:namespaceURI
1270                                           fromElement:parentElement];
1271  if (element) {
1272
1273    if (objectClass == nil) {
1274      // if the object is a feed or an entry, we might be able to determine the
1275      // type from the XML
1276      objectClass = [[self class] objectClassForXMLElement:element];
1277    }
1278
1279    objectClass = [self classOrSurrogateForClass:objectClass];
1280
1281    object = [[[objectClass alloc] initWithXMLElement:element
1282                                               parent:self] autorelease];
1283  }
1284  return object;
1285}
1286
1287
1288// get child elements from an element matching the given name and namespace
1289// (trying the namespace first, falling back on the fully-qualified name)
1290- (NSArray *)elementsForName:(NSString *)qualifiedName
1291                namespaceURI:(NSString *)namespaceURI
1292               parentElement:(NSXMLElement *)parentElement {
1293
1294  NSArray *objElements = nil;
1295
1296  if ([namespaceURI length] > 0) {
1297
1298    NSString *localName = [NSXMLNode localNameForName:qualifiedName];
1299
1300    objElements = [parentElement elementsForLocalName:localName
1301                                                  URI:namespaceURI];
1302  }
1303
1304  // if we couldn't find the elements by name, fall back on the fully-qualified
1305  // name
1306  if ([objElements count] == 0) {
1307
1308    objElements = [parentElement elementsForName:qualifiedName];
1309  }
1310  return objElements;
1311
1312}
1313
1314// return all child elements of an element which have the given namespace
1315// prefix
1316- (NSMutableArray *)childrenOfElement:(NSXMLElement *)parentElement
1317                           withPrefix:(NSString *)prefix {
1318  NSArray *allChildren = [parentElement children];
1319  NSMutableArray *matchingChildren = [NSMutableArray array];
1320  for (NSXMLNode *childNode in allChildren) {
1321    if ([childNode kind] == NSXMLElementKind
1322        && [[childNode prefix] isEqual:prefix]) {
1323
1324      [matchingChildren addObject:childNode];
1325    }
1326  }
1327
1328  return matchingChildren;
1329}
1330
1331// returns a GDataObject or an array of them of the specified class for each XML
1332// child element with the specified name
1333//
1334// If objectClass is nil, the class is looked up from the registrations
1335// of entry and feed classes.
1336
1337- (id)objectOrArrayForChildrenOfElement:(NSXMLElement *)parentElement
1338                          qualifiedName:(NSString *)qualifiedName
1339                           namespaceURI:(NSString *)namespaceURI
1340                            objectClass:(Class)objectClass {
1341  id result = nil;
1342  BOOL isResultAnArray = NO;
1343
1344  NSArray *objElements = nil;
1345
1346  NSString *localName = [NSXMLNode localNameForName:qualifiedName];
1347  if (![localName isEqual:@"*"]) {
1348
1349    // searching for an actual element name (not a wildcard)
1350    objElements = [self elementsForName:qualifiedName
1351                           namespaceURI:namespaceURI
1352                          parentElement:parentElement];
1353  }
1354
1355  else {
1356    // we weren't given a local name, so get all objects for this namespace
1357    // URI's prefix
1358    NSString *prefixSought = [NSXMLNode prefixForName:qualifiedName];
1359    if ([prefixSought length] == 0) {
1360      prefixSought = [parentElement resolvePrefixForNamespaceURI:namespaceURI];
1361    }
1362
1363    if (prefixSought) {
1364      objElements = [self childrenOfElement:parentElement
1365                                 withPrefix:prefixSought];
1366    }
1367  }
1368
1369  // if we're creating entries, we'll use an autorelease pool around each
1370  // allocation, just to bound overall pool size.  We'll check the class
1371  // of the first created object to determine if we want pools.
1372  BOOL hasCheckedObjectClass = NO;
1373  BOOL useLocalAutoreleasePool = NO;
1374  Class entryBaseClass = [GDataEntryBase class];
1375
1376  // step through all child elements and create an appropriate GData object
1377  for (NSXMLElement *objElement in objElements) {
1378
1379    Class elementClass = objectClass;
1380    if (elementClass == nil) {
1381      // if the object is a feed or an entry, we might be able to determine the
1382      // type for this element from the XML
1383      elementClass = [[self class] objectClassForXMLElement:objElement];
1384
1385      // if a base feed class doesn't specify entry class, and the entry object
1386      // class can't be determined by examining its XML, fall back on
1387      // instantiating the base entry class
1388      if (elementClass == nil
1389        && [qualifiedName isEqual:@"entry"]
1390        && [namespaceURI isEqual:kGDataNamespaceAtom]) {
1391
1392        elementClass = entryBaseClass;
1393      }
1394    }
1395
1396    elementClass = [self classOrSurrogateForClass:elementClass];
1397
1398    NSAutoreleasePool *pool = nil;
1399
1400    if (!hasCheckedObjectClass) {
1401      useLocalAutoreleasePool = [elementClass isSubclassOfClass:entryBaseClass];
1402      hasCheckedObjectClass = YES;
1403    }
1404
1405    if (useLocalAutoreleasePool) {
1406      pool = [[NSAutoreleasePool alloc] init];
1407    }
1408
1409    id obj = [[elementClass alloc] initWithXMLElement:objElement
1410                                               parent:self];
1411
1412    // We drain here to keep the clang static analyzer quiet.
1413    [pool drain];
1414
1415    [obj autorelease];
1416
1417    if (obj) {
1418      if (result == nil) {
1419        // first result
1420        result = obj;
1421      } else if (!isResultAnArray) {
1422        // second result; create an array with the previous and the new result
1423        result = [NSMutableArray arrayWithObjects:result, obj, nil];
1424        isResultAnArray = YES;
1425      } else {
1426        // third or later result
1427        [result addObject:obj];
1428      }
1429    }
1430  }
1431
1432  // remove these elements from the unknown list
1433  [self handleParsedElements:objElements];
1434
1435  return result;
1436}
1437
1438// childOfElement:withName returns the element with the name, or nil if there
1439// are not exactly one of the element.  Pass "*" wildcards for name and URI
1440// to retrieve the child element if there is exactly one.
1441- (NSXMLElement *)childWithQualifiedName:(NSString *)qualifiedName
1442                            namespaceURI:(NSString *)namespaceURI
1443                             fromElement:(NSXMLElement *)parentElement {
1444
1445  NSArray *elementArray;
1446
1447  if ([qualifiedName isEqual:@"*"] && [namespaceURI isEqual:@"*"]) {
1448    // wilcards
1449    elementArray = [parentElement children];
1450  } else {
1451    // find the element by name and namespace URI
1452    elementArray = [self elementsForName:qualifiedName
1453                            namespaceURI:namespaceURI
1454                           parentElement:parentElement];
1455  }
1456
1457  NSUInteger numberOfElements = [elementArray count];
1458
1459  if (numberOfElements == 1) {
1460    NSXMLElement *element = [elementArray objectAtIndex:0];
1461
1462    // remove this element from the unknown list
1463    [self handleParsedElement:element];
1464
1465    return element;
1466  }
1467
1468  // We might want to get rid of this assert if there turns out to be
1469  // legitimate reasons to call this where there are >1 elements available
1470  GDATA_ASSERT(numberOfElements == 0, @"childWithQualifiedName: could not handle "
1471               "multiple '%@' elements in list, use elementsForName:\n"
1472               "Found elements: %@\nURI: %@", qualifiedName, elementArray,
1473               namespaceURI);
1474  return nil;
1475}
1476
1477#pragma mark element parsing
1478
1479- (void)handleParsedElement:(NSXMLNode *)element {
1480  if (unknownChildren_ != nil && element != nil) {
1481    [unknownChildren_ removeObjectIdenticalTo:element];
1482
1483    if ([unknownChildren_ count] == 0) {
1484      [unknownChildren_ release];
1485      unknownChildren_ = nil;
1486    }
1487  }
1488}
1489
1490- (void)handleParsedElements:(NSArray *)array {
1491  if (unknownChildren_ != nil) {
1492    // rather than use NSMutableArray's removeObjects:, it's faster to iterate and
1493    // and use removeObjectIdenticalTo: since it avoids comparing the underlying
1494    // XML for equality
1495    for (NSXMLNode* element in array) {
1496      [unknownChildren_ removeObjectIdenticalTo:element];
1497    }
1498
1499    if ([unknownChildren_ count] == 0) {
1500      [unknownChildren_ release];
1501      unknownChildren_ = nil;
1502    }
1503  }
1504}
1505
1506- (NSString *)stringValueFromElement:(NSXMLElement *)element {
1507  // Originally, this was
1508  //    NSString *result = [element stringValue];
1509  // but that recursively descends children to build the string
1510  // so we'll just walk the remaining nodes and build the string ourselves
1511
1512  if (element == nil) {
1513    return nil;
1514  }
1515
1516  NSString *result = nil;
1517
1518  // consider all text child nodes used to make this string value to now be
1519  // known
1520  //
1521  // in most cases, there is only one text node, so we'll optimize for that
1522  NSArray *children = [element children];
1523
1524  for (NSXMLNode *childNode in children) {
1525    if ([childNode kind] == NSXMLTextKind) {
1526
1527      NSString *newNodeString = [childNode stringValue];
1528
1529      if (result == nil) {
1530        result = newNodeString;
1531      } else {
1532        result = [result stringByAppendingString:newNodeString];
1533      }
1534      [self handleParsedElement:childNode];
1535    }
1536  }
1537
1538  return (result != nil ? result : @"");
1539}
1540
1541- (GDataDateTi

Large files files are truncated, but you can click here to view the full file