PageRenderTime 429ms CodeModel.GetById 13ms app.highlight 395ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/update-engine/externals/gdata-objectivec-client/Source/BaseClasses/GDataObject.m

http://macfuse.googlecode.com/
Objective C | 2822 lines | 1782 code | 591 blank | 449 comment | 382 complexity | 979f3f606e6704b412a550b6c7c0d4e7 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//  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- (GDataDateTime *)dateTimeFromElement:(NSXMLElement *)element {
1542  NSString *str = [self stringValueFromElement:element];
1543  if ([str length] > 0) {
1544    return [GDataDateTime dateTimeWithRFC3339String:str];
1545  }
1546  return nil;
1547}
1548
1549
1550- (NSNumber *)intNumberValueFromElement:(NSXMLElement *)element {
1551  NSString *str = [self stringValueFromElement:element];
1552  if ([str length] > 0) {
1553    NSNumber *number = [NSNumber numberWithInt:[str intValue]];
1554    return number;
1555  }
1556  return nil;
1557}
1558
1559- (NSNumber *)doubleNumberValueFromElement:(NSXMLElement *)element {
1560  NSString *str = [self stringValueFromElement:element];
1561  return [GDataUtilities doubleNumberOrInfForString:str];
1562}
1563
1564#pragma mark attribute parsing
1565
1566- (void)handleParsedAttribute:(NSXMLNode *)attribute {
1567
1568  if (unknownAttributes_ != nil && attribute != nil) {
1569    [unknownAttributes_ removeObjectIdenticalTo:attribute];
1570
1571    if ([unknownAttributes_ count] == 0) {
1572      [unknownAttributes_ release];
1573      unknownAttributes_ = nil;
1574    }
1575  }
1576}
1577
1578- (NSXMLNode *)attributeForName:(NSString *)attributeName
1579                    fromElement:(NSXMLElement *)element {
1580
1581  NSXMLNode* attribute = [element attributeForName:attributeName];
1582
1583  [self handleParsedAttribute:attribute];
1584
1585  return attribute;
1586}
1587
1588- (NSXMLNode *)attributeForLocalName:(NSString *)localName
1589                                 URI:(NSString *)attributeURI
1590                         fromElement:(NSXMLElement *)element {
1591
1592  NSXMLNode* attribute = [element attributeForLocalName:localName
1593                                                    URI:attributeURI];
1594  [self handleParsedAttribute:attribute];
1595
1596  return attribute;
1597}
1598
1599- (NSString *)stringForAttributeLocalName:(NSString *)localName
1600                                      URI:(NSString *)attributeURI
1601                              fromElement:(NSXMLElement *)element {
1602
1603  NSXMLNode* attribute = [self attributeForLocalName:localName
1604                                                 URI:attributeURI
1605                                         fromElement:element];
1606  return [attribute stringValue];
1607}
1608
1609
1610- (NSString *)stringForAttributeName:(NSString *)attributeName
1611                         fromElement:(NSXMLElement *)element {
1612  NSXMLNode* attribute = [self attributeForName:attributeName
1613                                    fromElement:element];
1614  return [attribute stringValue];
1615}
1616
1617- (GDataDateTime *)dateTimeForAttributeName:(NSString *)attributeName
1618                                fromElement:(NSXMLElement *)element {
1619
1620  NSXMLNode* attribute = [self attributeForName:attributeName
1621                                    fromElement:element];
1622
1623  NSString* str = [attribute stringValue];
1624  if ([str length] > 0) {
1625    return [GDataDateTime dateTimeWithRFC3339String:str];
1626  }
1627  return nil;
1628}
1629
1630- (BOOL)boolForAttributeName:(NSString *)attributeName
1631                 fromElement:(NSXMLElement *)element {
1632  NSXMLNode* attribute = [self attributeForName:attributeName
1633                                    fromElement:element];
1634  NSString* str = [attribute stringValue];
1635  BOOL isTrue = (str && [str caseInsensitiveCompare:@"true"] == NSOrderedSame);
1636  return isTrue;
1637}
1638
1639- (NSNumber *)doubleNumberForAttributeName:(NSString *)attributeName
1640                               fromElement:(NSXMLElement *)element {
1641  NSXMLNode* attribute = [self attributeForName:attributeName
1642                                    fromElement:element];
1643  NSString* str = [attribute stringValue];
1644  return [GDataUtilities doubleNumberOrInfForString:str];
1645}
1646
1647- (NSNumber *)intNumberForAttributeName:(NSString *)attributeName
1648                            fromElement:(NSXMLElement *)element {
1649  NSXMLNode* attribute = [self attributeForName:attributeName
1650                                    fromElement:element];
1651  NSString* str = [attribute stringValue];
1652  if (str) {
1653    NSNumber *number = [NSNumber numberWithInt:[str intValue]];
1654    return number;
1655  }
1656  return nil;
1657}
1658
1659
1660#pragma mark Extensions
1661
1662- (void)addExtensionDeclarations {
1663  // overridden by subclasses which have extensions to add, like:
1664  //
1665  //  [self addExtensionDeclarationForParentClass:[GDataLink class]
1666  //                                   childClass:[GDataWebContent class]];
1667  // and
1668  //
1669  //  [self addAttributeExtensionDeclarationForParentClass:[GDataExtendedProperty class]
1670  //                                            childClass:[GDataExtPropValueAttribute class]];
1671
1672}
1673
1674- (void)addParseDeclarations {
1675
1676  // overridden by subclasses which have local attributes, like:
1677  //
1678  //  [self addLocalAttributeDeclarations:[NSArray arrayWithObject:@"size"]];
1679  //
1680  //  Subclasses should add the attributes in the order they most usefully will
1681  //  appear in the object's -description output (or alternatively they may
1682  //  override -description).
1683  //
1684  // Note: this is only for namespace-less attributes or attributes with the
1685  // fixed xml: namespace, not for attributes that are qualified with variable
1686  // prefixes.  Those attributes should be parsed explicitly in
1687  // initWithXMLElement: methods, and generated by XMLElement: methods.
1688}
1689
1690// subclasses call these to declare possible extensions for themselves and their
1691// children.
1692- (void)addExtensionDeclarationForParentClass:(Class)parentClass
1693                                   childClass:(Class)childClass {
1694  // add an element extension
1695  [self addExtensionDeclarationForParentClass:parentClass
1696                                   childClass:childClass
1697                                  isAttribute:NO];
1698}
1699
1700- (void)addExtensionDeclarationForParentClass:(Class)parentClass
1701                                 childClasses:(Class)firstChildClass, ... {
1702
1703  // like the method above, but for a list of child classes
1704  Class nextClass;
1705  va_list argumentList;
1706
1707  if (firstChildClass != nil) {
1708    [self addExtensionDeclarationForParentClass:parentClass
1709                                     childClass:firstChildClass
1710                                    isAttribute:NO];
1711
1712    va_start(argumentList, firstChildClass);
1713    while ((nextClass = (Class)va_arg(argumentList, Class)) != nil) {
1714
1715      [self addExtensionDeclarationForParentClass:parentClass
1716                                       childClass:nextClass
1717                                      isAttribute:NO];
1718    }
1719    va_end(argumentList);
1720  }
1721}
1722
1723- (void)addAttributeExtensionDeclarationForParentClass:(Class)parentClass
1724                                   childClass:(Class)childClass {
1725  // add an attribute extension
1726  [self addExtensionDeclarationForParentClass:parentClass
1727                                   childClass:childClass
1728                                  isAttribute:YES];
1729}
1730
1731- (void)addExtensionDeclarationForParentClass:(Class)parentClass
1732                                   childClass:(Class)childClass
1733                                  isAttribute:(BOOL)isAttribute {
1734
1735  // get or make the dictionary which caches the extension declarations for
1736  // this class
1737  Class currClass = [self class];
1738  NSMutableDictionary *extensionDeclarationsCache = [self extensionDeclarationsCache];
1739  GDATA_DEBUG_ASSERT(extensionDeclarationsCache != nil, @"missing extnDecls");
1740
1741  NSMutableDictionary *extensionDecls = [extensionDeclarationsCache objectForKey:currClass];
1742
1743  if (extensionDecls == nil) {
1744    extensionDecls = [NSMutableDictionary dictionary];
1745    [extensionDeclarationsCache setObject:extensionDecls forKey:(id<NSCopying>)currClass];
1746  }
1747
1748  // get this class's extensions for the specified parent class
1749  NSMutableArray *array = [extensionDecls objectForKey:parentClass];
1750  if (array == nil) {
1751    array = [NSMutableArray array];
1752    [extensionDecls setObject:array forKey:(id<NSCopying>)parentClass];
1753  }
1754
1755  GDATA_DEBUG_ASSERT([childClass conformsToProtocol:@protocol(GDataExtension)],
1756                @"%@ does not conform to GDataExtension protocol", childClass);
1757
1758  GDataExtensionDeclaration *decl =
1759    [[[GDataExtensionDeclaration alloc] initWithParentClass:parentClass
1760                                                 childClass:childClass
1761                                                isAttribute:isAttribute] autorelease];
1762  [array addObject:decl];
1763}
1764
1765- (void)removeExtensionDeclarationForParentClass:(Class)parentClass
1766                                      childClass:(Class)childClass {
1767  GDataExtensionDeclaration *decl =
1768    [[[GDataExtensionDeclaration alloc] initWithParentClass:parentClass
1769                                                 childClass:childClass
1770                                                isAttribute:NO] autorelease];
1771
1772  NSMutableArray *array = [self extensionDeclarationsForParentClass:parentClass];
1773  [array removeObject:decl];
1774}
1775
1776- (void)removeAttributeExtensionDeclarationForParentClass:(Class)parentClass
1777                                               childClass:(Class)childClass {
1778  GDataExtensionDeclaration *decl =
1779    [[[GDataExtensionDeclaration alloc] initWithParentClass:parentClass
1780                                                 childClass:childClass
1781                                                isAttribute:YES] autorelease];
1782
1783  NSMutableArray *array = [self extensionDeclarationsForParentClass:parentClass];
1784  [array removeObject:decl];
1785}
1786
1787// utility routine for getting declared extensions to the specified class
1788- (NSMutableArray *)extensionDeclarationsForParentClass:(Class)parentClass {
1789
1790  // get the declarations for this class
1791  Class currClass = [self class];
1792  NSMutableDictionary *cache = [self extensionDeclarationsCache];
1793  NSMutableDictionary *classMap = [cache objectForKey:currClass];
1794
1795  // get the extensions for the specified parent class
1796  NSMutableArray *array = [classMap objectForKey:parentClass];
1797  return array;
1798}
1799
1800// objectsForExtensionClass: returns the array of all
1801// extension objects of the specified class, or nil
1802//
1803// this is typically called by the getter methods of subclasses
1804
1805- (NSArray *)objectsForExtensionClass:(Class)theClass {
1806  id obj = [extensions_ objectForKey:theClass];
1807  if (obj == nil) return nil;
1808
1809  if ([obj isKindOfClass:[NSArray class]]) {
1810    return obj;
1811  }
1812
1813  return [NSArray arrayWithObject:obj];
1814}
1815
1816// objectForExtensionClass: returns the first element of
1817// any extension objects of the specified class, or nil
1818//
1819// this is typically called by the getter methods of subclasses
1820
1821- (id)objectForExtensionClass:(Class)theClass {
1822  id obj = [extensions_ objectForKey:theClass];
1823
1824  if ([obj isKindOfClass:[NSArray class]]) {
1825    if ([(NSArray *)obj count] > 0) {
1826      return [obj objectAtIndex:0];
1827    }
1828    // an empty array
1829    return nil;
1830  }
1831
1832  return obj;
1833}
1834
1835// attributeValueForExtensionClass: returns the value of the first object of
1836// the array of attribute extension objects of the specified class, or nil
1837- (NSString *)attributeValueForExtensionClass:(Class)theClass {
1838  GDataAttribute *attr = [self objectForExtensionClass:theClass];
1839  NSString *str = [attr stringValue];
1840  return str;
1841}
1842
1843- (void)setAttributeValue:(NSString *)str forExtensionClass:(Class)theClass {
1844  GDataAttribute *obj = [theClass attributeWithValue:str];
1845  [self setObject:obj forExtensionClass:theClass];
1846}
1847
1848// generate the qualified name for this extension's element
1849- (NSString *)qualifiedNameForExtensionClass:(Class)theClass {
1850
1851  NSString *name;
1852
1853  @synchronized(gQualifiedNameMap) {
1854
1855    name = [gQualifiedNameMap objectForKey:theClass];
1856    if (name == nil) {
1857
1858      NSString *extensionURI = [theClass extensionElementURI];
1859
1860      if (extensionURI == nil || [extensionURI isEqual:kGDataNamespaceAtom]) {
1861        name = [theClass extensionElementLocalName];
1862      } else {
1863        name = [NSString stringWithFormat:@"%@:%@",
1864                [theClass extensionElementPrefix],
1865                [theClass extensionElementLocalName]];
1866      }
1867
1868      [gQualifiedNameMap setObject:name forKey:(id<NSCopying>)theClass];
1869    }
1870  }
1871  return name;
1872}
1873
1874- (void)ensureObject:(GDataObject *)obj hasXMLNameForExtensionClass:(Class)theClass {
1875  // utility routine for setObjects:forExtensionClass:
1876  if ([obj isKindOfClass:[GDataObject class]]
1877      && [[obj elementName] length] == 0) {
1878
1879    NSString *name = [self qualifiedNameForExtensionClass:theClass];
1880    [obj setElementName:name];
1881  }
1882}
1883
1884// replace all actual extensions of the specified class with an array
1885//
1886// this is typically called by the setter methods of subclasses
1887
1888- (void)setObjects:(NSArray *)objects forExtensionClass:(Class)theClass {
1889
1890  GDATA_DEBUG_ASSERT(objects == nil || [objects isKindOfClass:[NSArray class]],
1891                     @"array expected");
1892
1893  if (extensions_ == nil && objects != nil) {
1894    extensions_ = [[NSMutableDictionary alloc] init];
1895  }
1896
1897  if (objects) {
1898    // be sure each object has an element name so we can generate XML for it
1899    for (GDataObject *obj in objects) {
1900      [self ensureObject:obj hasXMLNameForExtensionClass:theClass];
1901    }
1902    [extensions_ setObject:objects forKey:(id<NSCopying>)theClass];
1903  } else {
1904    [extensions_ removeObjectForKey:theClass];
1905  }
1906}
1907
1908// replace all actual extensions of the specified class with a single object
1909//
1910// this is typically called by the setter methods of subclasses
1911
1912- (void)setObject:(id)object forExtensionClass:(Class)theClass {
1913
1914  GDATA_DEBUG_ASSERT(![object isKindOfClass:[NSArray class]], @"array unexpected");
1915
1916  if (extensions_ == nil && object != nil) {
1917    extensions_ = [[NSMutableDictionary alloc] init];
1918  }
1919
1920  if (object) {
1921    [self ensureObject:object hasXMLNameForExtensionClass:theClass];
1922    [extensions_ setObject:object forKey:(id<NSCopying>)theClass];
1923  } else {
1924    [extensions_ removeObjectForKey:theClass];
1925  }
1926}
1927
1928// add an extension of the specified class
1929//
1930// this is typically called by addObject methods of subclasses
1931
1932- (void)addObject:(id)newObj forExtensionClass:(Class)theClass {
1933
1934  if (newObj == nil) return;
1935
1936  id previousObjOrArray = [extensions_ objectForKey:theClass];
1937  if (previousObjOrArray) {
1938
1939    if ([previousObjOrArray isKindOfClass:[NSArray class]]) {
1940
1941      // add to the existing array
1942      [self ensureObject:newObj hasXMLNameForExtensionClass:theClass];
1943      [previousObjOrArray addObject:newObj];
1944
1945    } else {
1946
1947      // create an array with the previous object and the new object
1948      NSMutableArray *array = [NSMutableArray arrayWithObjects:
1949                               previousObjOrArray, newObj, nil];
1950      [extensions_ setObject:array forKey:(id<NSCopying>)theClass];
1951    }
1952  } else {
1953
1954    // no previous object
1955    [self setObject:newObj forExtensionClass:theClass];
1956  }
1957}
1958
1959// remove a known extension of the specified class
1960//
1961// this is typically called by removeObject methods of subclasses
1962
1963- (void)removeObject:(id)object forExtensionClass:(Class)theClass {
1964  id previousObjOrArray = [extensions_ objectForKey:theClass];
1965  if ([previousObjOrArray isKindOfClass:[NSArray class]]) {
1966
1967    // remove from the array
1968    [(NSMutableArray *)previousObjOrArray removeObject:object];
1969
1970  } else if ([(GDataObject *)object isEqual:previousObjOrArray]) {
1971
1972    // no array, so remove if it matches the sole object
1973    [extensions_ removeObjectForKey:theClass];
1974  }
1975}
1976
1977// addUnknownChildNodesForElement: is called by initWithXMLElement.  It builds
1978// the initial list of unknown child elements; this list is whittled down by
1979// parseExtensionsForElement and objectForChildOfElement.
1980- (void)addUnknownChildNodesForElement:(NSXMLElement *)element {
1981
1982  GDATA_DEBUG_ASSERT(unknownChildren_ == nil, @"unknChildren added twice");
1983  GDATA_DEBUG_ASSERT(unknownAttributes_ == nil, @"unknAttr added twice");
1984
1985  if (!shouldIgnoreUnknowns_) {
1986
1987    NSArray *children = [element children];
1988    if ([children count] > 0) {
1989      unknownChildren_ = [[NSMutableArray alloc] initWithArray:children];
1990    }
1991
1992    NSArray *attributes = [element attributes];
1993    if ([attributes count] > 0) {
1994      unknownAttributes_ = [[NSMutableArray alloc] initWithArray:attributes];
1995    }
1996  }
1997}
1998
1999// parseExtensionsForElement: is called by initWithXMLElement. It starts
2000// from the current object and works up the chain of parents, grabbing
2001// the declared extensions by each GDataObject in the ancestry and looking
2002// at the current element to see if any of the declared extensions are present.
2003
2004- (void)parseExtensionsForElement:(NSXMLElement *)element {
2005  Class classBeingParsed = [self class];
2006
2007  // For performance, we'll avoid looking up extension elements whose
2008  // local names aren't present in the element.  We don't bother doing
2009  // this for attribute extensions since those are so rare (most attributes
2010  // are parsed just by local declaration in parseAttributesForElement:.)
2011
2012  NSArray *childLocalNames = [element valueForKeyPath:@"children.localName"];
2013
2014  // allow wildcard lookups
2015  childLocalNames = [childLocalNames arrayByAddingObject:@"*"];
2016
2017  Class arrayClass = [NSArray class];
2018
2019  for (GDataObject * currentExtensionSupplier = self;
2020       currentExtensionSupplier != nil;
2021       currentExtensionSupplier = [currentExtensionSupplier parent]) {
2022
2023    // find all extensions in this supplier with the current class as the parent
2024    NSArray *extnDecls = [currentExtensionSupplier extensionDeclarationsForParentClass:classBeingParsed];
2025
2026    if (extnDecls) {
2027      for (GDataExtensionDeclaration *decl in extnDecls) {
2028        // if we've not already found this class when parsing at an earlier supplier
2029        Class extensionClass = [decl childClass];
2030        if ([extensions_ objectForKey:extensionClass] == nil) {
2031
2032          // if this extension's local name really matches some child's local
2033          // name (or this is an attribute extension)
2034
2035          NSString *declLocalName = [extensionClass extensionElementLocalName];
2036          if ([childLocalNames containsObject:declLocalName]
2037              || [decl isAttribute]) {
2038
2039            GDATA_DEBUG_ASSERT([extensionClass conformsToProtocol:@protocol(GDataExtension)],
2040                      @"%@ does not conform to GDataExtension protocol",
2041                      extensionClass);
2042
2043            NSString *namespaceURI = [extensionClass extensionElementURI];
2044            NSString *qualifiedName = [self qualifiedNameForExtensionClass:extensionClass];
2045
2046            id objectOrArray = nil;
2047
2048            if ([decl isAttribute]) {
2049              // parse for an attribute extension
2050              NSString *str = [self stringForAttributeName:qualifiedName
2051                                               fromElement:element];
2052              if (str) {
2053                id attr = [[[extensionClass alloc] init] autorelease];
2054                [attr setStringValue:str];
2055                objectOrArray = attr;
2056              }
2057
2058            } else {
2059              // parse for an element extension
2060              objectOrArray = [self objectOrArrayForChildrenOfElement:element
2061                                                        qualifiedName:qualifiedName
2062                                                         namespaceURI:namespaceURI
2063                                                          objectClass:extensionClass];
2064            }
2065
2066            if ([objectOrArray isKindOfClass:arrayClass]) {
2067              if ([(NSArray *)objectOrArray count] > 0) {
2068
2069                // save the non-empty array of extensions
2070                [self setObjects:objectOrArray forExtensionClass:extensionClass];
2071              }
2072            } else if (objectOrArray != nil) {
2073
2074              // save the single extension
2075              [self setObject:objectOrArray forExtensionClass:extensionClass];
2076            }
2077          }
2078        }
2079      }
2080    }
2081  }
2082}
2083
2084#pragma mark Local Attributes
2085
2086- (void)addLocalAttributeDeclarations:(NSArray *)attributeLocalNames {
2087
2088  // get or make the array which caches the attribute declarations for
2089  // this class
2090  if (attributeDeclarations_ == nil) {
2091
2092    Class currClass = [self class];
2093    NSMutableDictionary *cache = [self attributeDeclarationsCache];
2094    GDATA_DEBUG_ASSERT(cache != nil, @"missing attrDeclsCache");
2095
2096    // we keep a strong pointer to the array in the cache since the cache
2097    // belongs to the feed or the topmost parent, and that may go away
2098    attributeDeclarations_ = [[cache objectForKey:currClass] retain];
2099    if (attributeDeclarations_ == nil) {
2100      attributeDeclarations_ = [[NSMutableArray alloc] init];
2101      [cache setObject:attributeDeclarations_ forKey:(id<NSCopying>)currClass];
2102    }
2103  }
2104
2105#if DEBUG
2106  // check that no local attributes being declared have a prefix, except for
2107  // the hardcoded xml: prefix. Namespaced attributes must be parsed and
2108  // emitted manually, or be declared as GDataAttribute extensions;
2109  // they cannot be handled as local attributes, since this class makes no
2110  // attempt to keep track of namespace URIs for local attributes
2111  for (NSString *attr in attributeLocalNames) {
2112    GDATA_ASSERT([attr rangeOfString:@":"].location == NSNotFound
2113                 || [attr hasPrefix:@"xml:"],
2114                 @"invalid namespaced local attribute: %@", attr);
2115  }
2116#endif
2117
2118  [attributeDeclarations_ addObjectsFromArray:attributeLocalNames];
2119}
2120
2121- (void)addAttributeDeclarationMarker:(NSString *)marker {
2122
2123  if (![attributeDeclarations_ containsObject:marker]) {
2124
2125    // add the marker
2126    if (attributeDeclarations_ != nil) {
2127
2128      // no need to create the cache
2129      [attributeDeclarations_ addObject:marker];
2130    } else {
2131
2132      // create the cache by calling addLocalAttributeDeclarations:
2133      NSArray *array = [NSArray arrayWithObject:marker];
2134      [self addLocalAttributeDeclarations:array];
2135    }
2136  }
2137}
2138
2139// attribute value getters
2140- (NSString *)stringValueForAttribute:(NSString *)name {
2141
2142  GDATA_DEBUG_ASSERT([[self attributeDeclarations] containsObject:name],
2143            @"%@ getting undeclared attribute: %@", [self class], name);
2144
2145  return [attributes_ valueForKey:name];
2146}
2147
2148- (NSNumber *)intNumberForAttribute:(NSString *)name {
2149
2150  NSString *str = [self stringValueForAttribute:name];
2151  if ([str length] > 0) {
2152    NSNumber *number = [NSNumber numberWithInt:[str intValue]];
2153    return number;
2154  }
2155  return nil;
2156}
2157
2158- (NSNumber *)doubleNumberForAttribute:(NSString *)name {
2159
2160  NSString *str = [self stringValueForAttribute:name];
2161  return [GDataUtilities doubleNumberOrInfForString:str];
2162}
2163
2164- (NSNumber *)longLongNumberForAttribute:(NSString *)name {
2165
2166  NSString *str = [self stringValueForAttribute:name];
2167  if (str) {
2168    long long val = [str longLongValue];
2169    NSNumber *number = [NSNumber numberWithLongLong:val];
2170    return number;
2171  }
2172  return nil;
2173}
2174
2175- (NSDecimalNumber *)decimalNumberForAttribute:(NSString *)name {
2176
2177  NSString *str = [self stringValueForAttribute:name];
2178  if ([str length] > 0) {
2179
2180    // require periods as the separator
2181    NSLocale *usLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
2182    NSDecimalNumber *number = [NSDecimalNumber decimalNumberWithString:str
2183                                    locale:usLocale];
2184    return number;
2185  }
2186  return nil;
2187}
2188
2189- (GDataDateTime *)dateTimeForAttribute:(NSString *)name  {
2190
2191  NSString *str = [self stringValueForAttribute:name];
2192  if ([str length] > 0) {
2193    GDataDateTime *dateTime = [GDataDateTime dateTimeWithRFC3339String:str];
2194    return dateTime;
2195  }
2196  return nil;
2197}
2198
2199- (BOOL)boolValueForAttribute:(NSString *)name defaultValue:(BOOL)defaultVal {
2200  NSString *str = [self stringValueForAttribute:name];
2201  BOOL isTrue;
2202
2203  if (defaultVal) {
2204    // default to true, so true if attribute is missing or is not "false"
2205    isTrue = (str == nil
2206              || [str caseInsensitiveCompare:@"false"] != NSOrderedSame);
2207  } else {
2208    // default to false, so true only if attribute is present and "true"
2209    isTrue = (str != nil
2210              && [str caseInsensitiveCompare:@"true"] == NSOrderedSame);
2211  }
2212  return isTrue;
2213}
2214
2215// attribute value setters
2216- (void)setStringValue:(NSString *)str forAttribute:(NSString *)name {
2217
2218  GDATA_DEBUG_ASSERT([[self attributeDeclarations] containsObject:name],
2219            @"%@ setting undeclared attribute: %@", [self class], name);
2220
2221  if (attributes_ == nil) {
2222    attributes_ = [[NSMutableDictionary alloc] init];
2223  }
2224
2225  [attributes_ setValue:str forKey:name];
2226}
2227
2228- (void)setBoolValue:(BOOL)flag defaultValue:(BOOL)defaultVal forAttribute:(NSString *)name {
2229  NSString *str;
2230  if (defaultVal) {
2231    // default to true, so include attribute only if false
2232    str = (flag ? nil : @"false");
2233  } else {
2234    // default to false, so include attribute only if true
2235    str = (flag ? @"true" : nil);
2236  }
2237  [self setStringValue:str forAttribute:name];
2238}
2239
2240- (void)setExplicitBoolValue:(BOOL)flag forAttribute:(NSString *)name {
2241  NSString *value = (flag ? @"true" : @"false");
2242  [self setStringValue:value forAttribute:name];
2243}
2244
2245- (void)setDecimalNumberValue:(NSDecimalNumber *)num forAttribute:(NSString *)name {
2246
2247  // for most NSNumbers, just calling -stringValue is fine, but for decimal
2248  // numbers we want to specify that a period be the separator
2249  NSLocale *usLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
2250
2251  NSString *str = [num descriptionWithLocale:usLocale];
2252  [self setStringValue:str forAttribute:name];
2253}
2254
2255- (void)setDateTimeValue:(GDataDateTime *)cdate forAttribute:(NSString *)name {
2256  NSString *str = [cdate RFC3339String];
2257  [self setStringValue:str forAttribute:name];
2258}
2259
2260
2261// parseAttributesForElement: is called by initWithXMLElement.
2262// It stores the value of all declared & present attributes in the dictionary
2263- (void)parseAttributesForElement:(NSXMLElement *)element {
2264
2265  // for better performance, look up the values for declared attributes only
2266  // if they are really present in the node
2267  NSArray *attributes = [element attributes];
2268  NSArray *attributeDeclarations = [self attributeDeclarations];
2269
2270  for (NSXMLNode *attribute in attributes) {
2271
2272    NSString *attrName = [attribute name];
2273    if ([attributeDeclarations containsObject:attrName]) {
2274
2275      NSString *str = [attribute stringValue];
2276      if (str != nil) {
2277        [self setStringValue:str forAttribute:attrName];
2278      }
2279
2280      [self handleParsedAttribute:attribute];
2281    }
2282  }
2283}
2284
2285// XML generator for local attributes
2286- (void)addAttributesToElement:(NSXMLElement *)element {
2287
2288  for (NSString *name in attributes_) {
2289
2290    NSString *value = [attributes_ valueForKey:name];
2291    if (value != nil) {
2292      [self addToElement:element attributeValueIfNonNil:value withName:name];
2293    }
2294  }
2295}
2296
2297// attribute comparison: subclasses may implement attributesIgnoredForEquality:
2298// to specify attributes not to be considered for equality comparison
2299
2300- (BOOL)hasAttributesEqualToAttributesOf:(GDataObject *)other {
2301
2302  NSArray *attributesToIgnore = [self attributesIgnoredForEquality];
2303
2304  NSDictionary *selfAttrs = [self attributes];
2305  NSDictionary *otherAttrs = [other attributes];
2306
2307  if ([attributesToIgnore count] == 0) {
2308    // none to ignore; just compare attribute dictionaries
2309    return AreEqualOrBothNil(selfAttrs, otherAttrs);
2310  }
2311
2312  // step through attributes, comparing each non-ignored attribute
2313  // to look for a mismatch
2314  NSArray *attributeDeclarations = [self attributeDeclarations];
2315  for (NSString *attrKey in attributeDeclarations) {
2316
2317    if (![attributesToIgnore containsObject:attrKey]) {
2318
2319      NSString *val1 = [selfAttrs objectForKey:attrKey];
2320      NSString *val2 = [otherAttrs objectForKey:attrKey];
2321
2322      if (!AreEqualOrBothNil(val1, val2)) {
2323        return NO;
2324      }
2325    }
2326  }
2327  return YES;
2328}
2329
2330- (NSArray *)attributesIgnoredForEquality {
2331  // subclasses may override this to specify attributes that should
2332  // not be considered when comparing objects for equality
2333  return nil;
2334}
2335
2336#pragma mark Content Value
2337
2338- (void)addContentValueDeclaration {
2339  // derived classes should call this if they want the element's content
2340  // to be automatically parsed as a string
2341  [self addAttributeDeclarationMarker:kContentValueDeclarationMarker];
2342}
2343
2344- (BOOL)hasDeclaredContentValue {
2345  NSMutableArray *attrDecls = [self attributeDeclarations];
2346  BOOL flag = [attrDecls containsObject:kContentValueDeclarationMarker];
2347  return flag;
2348}
2349
2350- (void)setContentStringValue:(NSString *)str {
2351
2352  GDATA_ASSERT([self hasDeclaredContentValue], @"%@ setting undeclared content value",
2353               [self class]);
2354
2355  [contentValue_ autorelease];
2356  contentValue_ = [str copy];
2357}
2358
2359- (NSString *)contentStringValue {
2360
2361  GDATA_ASSERT([self hasDeclaredContentValue], @"%@ getting undeclared content value",
2362               [self class]);
2363
2364  return contentValue_;
2365
2366}
2367
2368// parseContentForElement: is called by initWithXMLElement.
2369// This stores the content value parsed from the element.
2370- (void)parseContentValueForElement:(NSXMLElement *)element {
2371
2372  if ([self hasDeclaredContentValue]) {
2373    [self setContentStringValue:[self stringValueFromElement:element]];
2374  }
2375}
2376
2377// XML generator for content
2378- (void)addContentValueToElement:(NSXMLElement *)element {
2379
2380  if ([self hasDeclaredContentValue]) {
2381    NSString *str = [self contentStringValue];
2382    if ([str length] > 0) {
2383      [element addStringValue:str];
2384    }
2385  }
2386}
2387
2388- (BOOL)hasContentValueEqualToContentValueOf:(GDataObject *)other {
2389
2390  if (![self hasDeclaredContentValue]) {
2391    // no content being stored
2392    return YES;
2393  }
2394
2395  return AreEqualOrBothNil([self contentStringValue], [other contentStringValue]);
2396}
2397
2398#pragma mark Child XML Elements
2399
2400- (void)addChildXMLElementsDeclaration {
2401  // derived classes should call this if they want the element's unparsed
2402  // XML children to be accessible later
2403  [self addAttributeDeclarationMarker:kChildXMLDeclarationMarker];
2404}
2405
2406- (BOOL)hasDeclaredChildXMLElements {
2407  NSMutableArray *attrDecls = [self attributeDeclarations];
2408  BOOL flag = [attrDecls containsObject:kChildXMLDeclarationMarker];
2409  return flag;
2410}
2411
2412- (NSArray *)childXMLElements {
2413  if ([childXMLElements_ count] == 0) {
2414    return nil;
2415  }
2416  return childXMLElements_;
2417}
2418
2419- (void)setChildXMLElements:(NSArray *)array {
2420  GDATA_DEBUG_ASSERT([self hasDeclaredChildXMLElements],
2421                     @"%@ setting undeclared XML values", [self class]);
2422
2423  [childXMLElements_ release];
2424  childXMLElements_ = [array mutableCopy];
2425}
2426
2427- (void)addChildXMLElement:(NSXMLNode *)node {
2428  GDATA_DEBUG_ASSERT([self hasDeclaredChildXMLElements],
2429                     @"%@ adding undeclared XML values", [self class]);
2430
2431  if (childXMLElements_ == nil) {
2432    childXMLElements_ = [[NSMutableArray alloc] init];
2433  }
2434  [childXMLElements_ addObject:node];
2435}
2436
2437// keepChildXMLElementsForElement: is called by initWithXMLElement.
2438// This stores a copy of the element's child XMLElements.
2439- (void)keepChildXMLElementsForElement:(NSXMLElement *)element {
2440
2441  if ([self hasDeclaredChildXMLElements]) {
2442
2443    NSArray *children = [element children];
2444    if (children != nil) {
2445
2446      // save only top-level nodes that are elements
2447      for (NSXMLNode *childNode in children) {
2448        if ([childNode kind] == NSXMLElementKind) {
2449          if (childXMLElements_ == nil) {
2450            childXMLElements_ = [[NSMutableArray alloc] init];
2451          }
2452          NSXMLNode *childCopy = [[childNode copy] autorelease];
2453          [childXMLElements_ addObject:childCopy];
2454
2455          [self handleParsedElement:childNode];
2456        }
2457      }
2458    }
2459  }
2460}
2461
2462// XML generator for kept child XML elements
2463- (void)addChildXMLElementsToElement:(NSXMLElement *)element {
2464
2465  if ([self hasDeclaredChildXMLElements]) {
2466
2467    NSArray *childXMLElements = [self childXMLElements];
2468    if (childXMLElements != nil) {
2469
2470      for (NSXMLNode *child in childXMLElements) {
2471        [element addChild:child];
2472      }
2473    }
2474  }
2475}
2476
2477- (BOOL)hasChildXMLElementsEqualToChildXMLElementsOf:(GDataObject *)other {
2478
2479  if (![self hasDeclaredChildXMLElements]) {
2480    // no values being stored
2481    return YES;
2482  }
2483  return AreEqualOrBothNil([self childXMLElements], [other childXMLElements]);
2484}
2485
2486#pragma mark Dynamic GDataObject
2487
2488// Dynamic object generation is used when the class being created is nil.
2489//
2490// These maps are populated by +load routines in feeds and entries.
2491// They specify category elements which identify the class of feed or entry
2492// to be created for a blob of XML.
2493
2494static NSString *const kCategoryTemplate = @"{\"%@\":\"%@\"}";
2495
2496
2497// registerClass:inMap:forCategoryWithScheme:term: does the work for
2498// registerFeedClass: and registerEntryClass:
2499//
2500// This adds the class to the {"scheme":"term"} map, ensuring
2501// that it won't conflict with a previous class or category
2502// entry
2503
2504+ (void)registerClass:(Class)theClass
2505                inMap:(NSMutableDictionary **)map
2506forCategoryWithScheme:(NSString *)scheme
2507                 term:(NSString *)term {
2508
2509  // there's no autorelease pool in place at +load time, so we'll create our own
2510  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2511
2512  if (*map == nil) {
2513    *map = GDataCreateStaticDictionary();
2514  }
2515
2516  // ensure this is a unique registration
2517  GDATA_DEBUG_ASSERT(nil == [*map objectForKey:theClass],
2518               @"%@ already registered", theClass);
2519
2520#if !NS_BLOCK_ASSERTIONS
2521  Class prevClass = [self classForCategoryWithScheme:scheme
2522                                                term:term
2523                                             fromMap:*map];
2524  GDATA_ASSERT(prevClass == nil, @"%@ registration conflicts with %@",
2525               theClass, prevClass);
2526#endif
2527
2528  // we have a map from the key "scheme:term" to the class
2529  //
2530  // generally, scheme will be nil or kGDataCategoryScheme, so we'll
2531  // use just the term as the key for those categories, avoiding
2532  // the need to format a string when looking up
2533
2534  NSString *key;
2535  if (scheme == nil || [scheme isEqual:kGDataCategoryScheme]) {
2536    key = term;
2537  } else {
2538    key = [NSString stringWithFormat:kCategoryTemplate,
2539           scheme, term ? term : @""];
2540  }
2541
2542  [*map setValue:theClass forKey:key];
2543
2544  // We drain here to keep the clang static analyzer quiet.
2545  [pool drain];
2546}
2547
2548
2549// classForCategoryWithScheme does the work for feedClassForCategory
2550// and entryClassForCategory.  This method searches the entry
2551// or feed map for a class with a matching category.
2552//
2553// If the registration of the class specified a value, then the corresponding
2554// parameter values |scheme| or |term| must match and not be nil.
2555+ (Class)classForCategoryWithScheme:(NSString *)scheme
2556                               term:(NSString *)term
2557                            fromMap:(NSDictionary *)map {
2558
2559  // |scheme| and |term| are from the XML that we're using to look up
2560  // a registered class.  The |term| value should be non-nil,
2561  // though the values stored in the map may have nil scheme or term.
2562  //
2563  // if the registered scheme was nil or kGDataCategoryScheme then the key
2564  // is just the term value.
2565
2566  NSString *key = term;
2567  Class result = (Class)[map objectForKey:key];
2568  if (result) return result;
2569
2570  if (scheme) {
2571    key = [NSString stringWithFormat:kCategoryTemplate, scheme, term];
2572    result = (Class)[map objectForKey:key];
2573    if (result) return result;
2574
2575    key = [NSString stringWithFormat:kCategoryTemplate, scheme, @""];
2576    result = (Class)[map objectForKey:key];
2577    if (result) return result;
2578  }
2579
2580  return nil;
2581}
2582
2583// objectClassForXMLElement: returns a found registered feed
2584// or entry class for the XML according to its contained category,
2585// or an Atom service document class
2586//
2587// If no registered class is found with a matching category,
2588// this returns GDataFeedBase for feed elements, GDataEntryBase
2589// for entry elements.
2590+ (Class)objectClassForXMLElement:(NSXMLElement *)element {
2591
2592  Class result = nil;
2593  NSString *elementName = [element localName];
2594  BOOL isFeed = [elementName isEqual:@"feed"];
2595  BOOL isEntry = [elementName isEqual:@"entry"];
2596
2597  if (isFeed || isEntry) {
2598    // get the kind attribute, and see if it matches a registered feed or entry
2599    // class
2600    NSXMLNode *kindAttr = [element attributeForLocalName:@"kind"
2601                                                     URI:kGDataNamespaceGData];
2602    NSString *kind = [kindAttr stringValue];
2603
2604    if (kind) {
2605      if (isFeed) {
2606        result = [GDataFeedBase feedClassForKindAttributeValue:kind];
2607      } else {
2608        result = [GDataEntryBase entryClassForKindAttributeValue:kind];
2609      }
2610    }
2611
2612    if (result == nil) {
2613      // step through the feed or entry's category elements, looking for one
2614      // that matches a registered feed or entry class
2615      //
2616      // category elements look like <category scheme="blah" term="blahblah"/>
2617      // and there may be more than one
2618
2619      NSArray *categories = [element elementsForLocalName:@"category"
2620                                                      URI:kGDataNamespaceAtom];
2621      if ([categories count] == 0) {
2622        NSString *atomPrefix = [element resolvePrefixForNamespaceURI:kGDataNamespaceAtom];
2623        if ([atomPrefix length] == 0) {
2624          categories = [element elementsForName:@"category"];
2625        }
2626      }
2627
2628      for (NSXMLElement *categoryNode in categories) {
2629
2630        NSString *scheme = [[categoryNode attributeForName:@"scheme"] stringValue];
2631        NSString *term = [[categoryNode attributeForName:@"term"] stringValue];
2632
2633        if (scheme || term) {
2634          // we have a scheme or a term, so look for a registered class
2635          if (isFeed) {
2636            result = [GDataFeedBase feedClassForCategoryWithScheme:scheme
2637                                                              term:term];
2638          } else {
2639            result = [GDataEntryBase entryClassForCategoryWithScheme:scheme
2640                                                                    term:term];
2641          }
2642          if (result) {
2643            break;
2644          }
2645        }
2646      }
2647    }
2648  }
2649
2650  if (result == nil) {
2651    if (isFeed) {
2652      // default to returning a feed base class
2653      result = [GDataFeedBase class];
2654    } else if (isEntry) {
2655      // default to returning this feed's entry base class
2656      if ([self isSubclassOfClass:[GDataFeedBase class]]) {
2657        result = (Class)[self performSelector:@selector(defaultClassForEntries)];
2658      } else {
2659        result = [GDataEntryBase class];
2660      }
2661    } else if ([elementName isEqual:@"service"]) {
2662      // introspection - return service document, if the class is available
2663      NSString *serviceDocClassName = @"GDataAtomServiceDocument";
2664
2665  #ifdef GDATA_TARGET_NAMESPACE
2666      // prepend the class name prefix
2667      serviceDocClassName = [NSString stringWithFormat:@"%s_%@",
2668                            GDATA_TARGET_NAMESPACE_STRING, serviceDocClassName];
2669  #endif
2670
2671      result = NSClassFromString(serviceDocClassName);
2672
2673      GDATA_DEBUG_ASSERT(result != nil, @"service class %@ unavailable",
2674                         serviceDocClassName);
2675    } else {
2676      // this element is not a feed, entry, or service class; give up
2677    }
2678  }
2679
2680  return result;
2681}
2682
2683@end
2684
2685@implementation NSXMLElement (GDataObjectExtensions)
2686
2687- (void)addStringValue:(NSString *)str {
2688  // NSXMLNode's setStringValue: wipes out other children, so we'll use this
2689  // instead
2690
2691  // filter out non-whitespace control characters
2692  NSString *filtered = [GDataUtilities stringWithControlsFilteredForString:str];
2693
2694  NSXMLNode *strNode = [NSXMLNode textWithStringValue:filtered];
2695  [self addChild:strNode];
2696}
2697
2698+ (id)elementWithName:(NSString *)name attributeName:(NSString *)attrName attributeValue:(NSString *)attrValue {
2699
2700  NSString *filtered = [GDataUtilities stringWithControlsFilteredForString:attrValue];
2701
2702  NSXMLNode *attr = [NSXMLNode attributeWithName:attrName stringValue:filtered];
2703  NSXMLElement *element = [NSXMLNode elementWithName:name];
2704  [element addAttribute:attr];
2705  return element;
2706}
2707
2708@end
2709
2710@implementation GDataExtensionDeclaration
2711
2712- (id)initWithParentClass:(Class)parentClass
2713               childClass:(Class)childClass
2714              isAttribute:(BOOL)isAttribute {
2715  self = [super init];
2716  if (self) {
2717    parentClass_ = parentClass;
2718    childClass_ = childClass;
2719    isAttribute_ = isAttribute;
2720  }
2721  return self;
2722}
2723
2724- (NSString *)description {
2725  return [NSString stringWithFormat:@"%@: {%@ can contain %@}%@",
2726    [self class], parentClass_, childClass_,
2727          isAttribute_ ? @" (attribute)" : @""];
2728}
2729
2730- (Class)parentClass {
2731  return parentClass_;
2732}
2733
2734- (Class)childClass {
2735  return childClass_;
2736}
2737
2738- (BOOL)isAttribute {
2739  return isAttribute_;
2740}
2741
2742- (BOOL)isEqual:(GDataExtensionDeclaration *)other {
2743  if (self == other) return YES;
2744  if (![other isKindOfClass:[GDataExtensionDeclaration class]]) return NO;
2745
2746  return AreEqualOrBothNil((id)[self parentClass], (id)[other parentClass])
2747    && AreEqualOrBothNil((id)[self childClass], (id)[other childClass])
2748    && [self isAttribute] == [other isAttribute];
2749}
2750
2751- (NSUInteger)hash {
2752  return (NSUInteger) (void *) [GDataExtensionDeclaration class];
2753}
2754
2755@end
2756
2757@implementation GDataAttribute
2758
2759// This is the base class for attribute extensions.
2760//
2761// Functionally, this just stores a string value for the attribute.
2762
2763+ (GDataAttribute *)attributeWithValue:(NSString *)str {
2764  return [[[self alloc] initWithValue:str] autorelease];
2765}
2766
2767- (id)initWithValue:(NSString *)value {
2768  self = [super init];
2769  if (self) {
2770    [self setStringValue:value];
2771  }
2772  return self;
2773}
2774
2775- (void)dealloc {
2776  [value_ release];
2777  [super dealloc];
2778}
2779
2780- (id)copyWithZone:(NSZone *)zone {
2781  GDataAttribute* newObj = [[[self class] allocWithZone:zone] init];
2782  [newObj setStringValue:[self stringValue]];
2783  return newObj;
2784}
2785
2786- (NSString *)description {
2787
2788  NSString *name;
2789
2790  NSString *localName = [[self class] extensionElementLocalName];
2791  NSString *prefix = [[self class] extensionElementPrefix];
2792  if (prefix) {
2793    name = [NSString stringWithFormat:@"%@:%@", prefix, localName];
2794  } else {
2795    name = localName;
2796  }
2797
2798  return [NSString stringWithFormat:@"%@ %p: {%@=%@}",
2799          [self class], self, name, [self stringValue]];
2800}
2801
2802- (BOOL)isEqual:(GDataAttribute *)other {
2803  if (self == other) return YES;
2804  if (![other isKindOfClass:[GDataAttribute class]]) return NO;
2805
2806  return AreEqualOrBothNil([self stringValue], [other stringValue]);
2807}
2808
2809- (NSUInteger)hash {
2810  return (NSUInteger) (void *) [GDataAttribute class];
2811}
2812
2813- (void)setStringValue:(NSString *)str {
2814  [value_ autorelease];
2815  value_ = [str copy];
2816}
2817
2818- (NSString *)stringValue {
2819  return value_;
2820}
2821
2822@end