/core/externals/update-engine/externals/gdata-objectivec-client/Source/BaseClasses/GDataObject.m
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