Frameworks/Adium Framework/Source/AIXMLElement.m
author Stephen Holt <sholt@adium.im>
Wed Aug 26 13:17:35 2009 -0400 (2009-08-26)
changeset 2597 07e07f9788a6
parent 2578 c5b0c0b0c141
child 2791 a18df53b0617
permissions -rw-r--r--
Undo the part of [3855f70905bd]: It caused strings to be incorrectly escaped and causing our XML parsing to break. Rather, escape hash-symbols when parsing in XML data as not to confuse AIHTMLDecoder. Refs #8141. Fixes #12856.
zacw@1226
     1
/* AIXMLElement.m
zacw@1226
     2
 *
zacw@1226
     3
 * Created by Peter Hosey on 2006-06-07.
zacw@1226
     4
 *
zacw@1226
     5
 * This class is explicitly released under the BSD license with the following modification:
zacw@1226
     6
 * It may be used without reproduction of its copyright notice within The Adium Project.
zacw@1226
     7
 *
zacw@1226
     8
 * This class was created for use in the Adium project, which is released under the GPL.
zacw@1226
     9
 * The release of this specific class (AIXMLElement) under BSD in no way changes the licensing of any other portion
zacw@1226
    10
 * of the Adium project.
zacw@1226
    11
 *
zacw@1226
    12
 ****
zacw@1226
    13
 Copyright © 2006 Peter Hosey, Colin Barrett
zacw@1226
    14
 All rights reserved.
zacw@1226
    15
 
zacw@1226
    16
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
zacw@1226
    17
 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
zacw@1226
    18
 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
zacw@1226
    19
 Neither the name of Peter Hosey nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission.
zacw@1226
    20
 
zacw@1226
    21
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
zacw@1226
    22
 */
zacw@1226
    23
zacw@1226
    24
#import <Adium/AIXMLElement.h>
zacw@1226
    25
zacw@1226
    26
#import <AIUtilities/AIStringAdditions.h>
zacw@1226
    27
David@1643
    28
@interface AIXMLElement()
David@1643
    29
@property (readwrite, retain, nonatomic) NSMutableArray *attributeNames;
David@1643
    30
@property (readwrite, retain, nonatomic) NSMutableArray *attributeValues;
David@1643
    31
@end
David@1643
    32
zacw@1226
    33
@implementation AIXMLElement
zacw@1226
    34
zacw@1226
    35
+ (id) elementWithNamespaceName:(NSString *)namespace elementName:(NSString *)newName
zacw@1226
    36
{
zacw@1226
    37
	if (namespace)
zacw@1226
    38
		newName = [NSString stringWithFormat:@"%@:%@", namespace, newName];
zacw@1226
    39
	return [self elementWithName:newName];
zacw@1226
    40
}
zacw@1226
    41
- (id) initWithNamespaceName:(NSString *)namespace elementName:(NSString *)newName
zacw@1226
    42
{
zacw@1226
    43
	if (namespace)
zacw@1226
    44
		newName = [NSString stringWithFormat:@"%@:%@", namespace, newName];
zacw@1226
    45
	return [self initWithName:newName];
zacw@1226
    46
}
zacw@1226
    47
+ (id) elementWithName:(NSString *)newName
zacw@1226
    48
{
zacw@1226
    49
	return [[[self alloc] initWithName:newName] autorelease];
zacw@1226
    50
}
zacw@1226
    51
- (id) initWithName:(NSString *)newName
zacw@1226
    52
{
zacw@1226
    53
	NSParameterAssert(newName != nil);
zacw@1226
    54
zacw@1226
    55
	if ((self = [super init])) {
zacw@1226
    56
		name = [newName copy];
David@1643
    57
		self.attributeNames  = [NSMutableArray array];
David@1643
    58
		self.attributeValues = [NSMutableArray array];
zacw@1226
    59
		contents = [[NSMutableArray alloc] init];
zacw@1226
    60
zacw@1226
    61
		//If a list of self-closing tags exists, this could change to a lookup into a static NSSet
zacw@1226
    62
		selfCloses = (([newName caseInsensitiveCompare:@"br"] == NSOrderedSame) ? YES : NO);
zacw@1226
    63
	}
zacw@1226
    64
	return self;
zacw@1226
    65
}
David@1643
    66
David@1643
    67
@synthesize attributeNames, attributeValues;
David@1643
    68
zacw@1226
    69
- (id) init
zacw@1226
    70
{
zacw@1226
    71
	NSException *exc = [NSException exceptionWithName:@"Can't init AIXMLElement"
zacw@1226
    72
											   reason:@"AIXMLElement does not support the -init method; use -initWithName: instead."
zacw@1226
    73
											 userInfo:nil];
zacw@1226
    74
	[exc raise];
zacw@1226
    75
	return nil;
zacw@1226
    76
}
David@1643
    77
zacw@1226
    78
- (void) dealloc
zacw@1226
    79
{
zacw@1226
    80
	[name release];
zacw@1226
    81
	[attributeNames  release];
zacw@1226
    82
	[attributeValues release];
zacw@1226
    83
	[contents release];
zacw@1226
    84
zacw@1226
    85
	[super dealloc];
zacw@1226
    86
}
zacw@1226
    87
zacw@1226
    88
- (id) copyWithZone:(NSZone *)zone {
zacw@1226
    89
	AIXMLElement *other = [[AIXMLElement allocWithZone:zone] initWithName:name];
David@1643
    90
	other.attributeNames  = [NSMutableArray arrayWithArray:attributeNames];
David@1643
    91
	other.attributeValues = [NSMutableArray arrayWithArray:attributeValues];
David@1643
    92
	other.selfCloses = selfCloses;
David@1643
    93
	other.contents = self.contents; //uses setArray, so this copies
zacw@1226
    94
zacw@1226
    95
	return other;
zacw@1226
    96
}
zacw@1226
    97
zacw@1226
    98
#pragma mark -
zacw@1226
    99
zacw@1226
   100
- (NSString *) name
zacw@1226
   101
{
zacw@1226
   102
	return name;
zacw@1226
   103
}
zacw@1226
   104
zacw@1226
   105
- (unsigned)numberOfAttributes
zacw@1226
   106
{
zacw@1226
   107
	return [attributeNames count];
zacw@1226
   108
}
zacw@1226
   109
- (NSDictionary *)attributes
zacw@1226
   110
{
zacw@1226
   111
	return [NSDictionary dictionaryWithObjects:attributeValues forKeys:attributeNames];
zacw@1226
   112
}
zacw@1226
   113
- (void) setAttributeNames:(NSArray *)newAttrNames values:(NSArray *)newAttrVals
zacw@1226
   114
{
zacw@1226
   115
	NSAssert2([newAttrNames count] == [newAttrVals count], @"Attribute names and values have different lengths, %ui and %ui respectively", [newAttrNames count], [newAttrVals count]);
zacw@1226
   116
	unsigned numberOfDuplicates = [newAttrNames count] - [[NSSet setWithArray:newAttrNames] count];
zacw@1226
   117
	NSAssert1(numberOfDuplicates == 0, @"Duplicate attributes are not allowed; found %ui duplicate(s)",  numberOfDuplicates);
zacw@1226
   118
	
zacw@1226
   119
	[attributeNames setArray:newAttrNames];
zacw@1226
   120
	[attributeValues setArray:newAttrVals];
zacw@1226
   121
}
zacw@1226
   122
zacw@1226
   123
- (void)setValue:(NSString *)attrVal forAttribute:(NSString *)attrName
zacw@1226
   124
{
zacw@1226
   125
	NSUInteger index = [attributeNames indexOfObject:attrName];
zacw@1226
   126
	if (index != NSNotFound) {
zacw@1226
   127
		[attributeValues replaceObjectAtIndex:index withObject:attrVal];
zacw@1226
   128
	} else {
zacw@1226
   129
		[attributeNames addObject:attrName];
zacw@1226
   130
		[attributeValues addObject:attrVal];
zacw@1226
   131
	}
zacw@1226
   132
}
zacw@1226
   133
- (NSString *)valueForAttribute:(NSString *)attrName
zacw@1226
   134
{
zacw@1226
   135
	NSUInteger index = [attributeNames indexOfObject:attrName];
zacw@1226
   136
	if (index != NSNotFound)
zacw@1226
   137
		return [attributeValues objectAtIndex:index];
zacw@1226
   138
	return nil;
zacw@1226
   139
}
zacw@1226
   140
David@1643
   141
@synthesize selfCloses;
zacw@1226
   142
zacw@1226
   143
#pragma mark -
zacw@1226
   144
zacw@1226
   145
//NSString: Unescaped string data (will be escaped for XMLification).
zacw@1226
   146
//AIXMLElement: Sub-element (e.g. span in a p).
zacw@1227
   147
- (void)addEscapedObject:(id)obj
zacw@1227
   148
{
zacw@1227
   149
	//Warn but don't assert if null is added.  Adding nothing is a no-op, but we may want to investigate where this is happening further.
zacw@1227
   150
	if (!obj) NSLog(@"Warning: Attempted to add (null) to %@", obj);
zacw@1227
   151
zacw@1227
   152
	NSAssert2(([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[AIXMLElement class]]), @"%@: addObject: %@ is of incorrect class",self,obj);
zacw@1227
   153
	
zacw@1227
   154
	[contents addObject:obj];
zacw@1227
   155
}
zacw@1227
   156
zacw@1226
   157
- (void) addObject:(id)obj
zacw@1226
   158
{
zacw@1226
   159
	//Warn but don't assert if null is added.  Adding nothing is a no-op, but we may want to investigate where this is happening further.
zacw@1226
   160
	if (!obj) NSLog(@"Warning: Attempted to add (null) to %@", obj);
zacw@1226
   161
zacw@1226
   162
	BOOL isString = [obj isKindOfClass:[NSString class]];
zacw@1226
   163
	NSAssert2((isString || [obj isKindOfClass:[AIXMLElement class]]), @"%@: addObject: %@ is of incorrect class",self,obj);
zacw@1226
   164
zacw@1226
   165
	if(isString) {
zacw@1226
   166
		obj = [obj stringByEscapingForXMLWithEntities:nil];
zacw@1226
   167
	}
zacw@1226
   168
zacw@1226
   169
	[contents addObject:obj];
zacw@1226
   170
}
zacw@1226
   171
- (void) addObjectsFromArray:(NSArray *)array
zacw@1226
   172
{
zacw@1226
   173
	//We do it this way for the assertion, and to get free escaping of strings.
zacw@1226
   174
	id obj;
zacw@1226
   175
	for (obj in array) {
zacw@1226
   176
		[self addObject:obj];
zacw@1226
   177
	}
zacw@1226
   178
}
zacw@1226
   179
- (void) insertObject:(id)obj atIndex:(unsigned)idx
zacw@1226
   180
{
zacw@1226
   181
	BOOL isString = [obj isKindOfClass:[NSString class]];
zacw@1226
   182
	NSParameterAssert(isString || [obj isKindOfClass:[AIXMLElement class]]);
zacw@1226
   183
zacw@1226
   184
	if(isString) {
zacw@1226
   185
		obj = [obj stringByEscapingForXMLWithEntities:nil];
zacw@1226
   186
	}
zacw@1226
   187
zacw@1226
   188
	[contents insertObject:obj atIndex:idx];
zacw@1226
   189
}
zacw@1226
   190
zacw@1226
   191
- (NSArray *)contents
zacw@1226
   192
{
zacw@1226
   193
	return contents;
zacw@1226
   194
}
zacw@1226
   195
- (void)setContents:(NSArray *)newContents
zacw@1226
   196
{
zacw@1226
   197
	[contents setArray:newContents];
zacw@1226
   198
}
zacw@1226
   199
zacw@1226
   200
- (NSString *)contentsAsXMLString
zacw@1226
   201
{
zacw@1226
   202
	NSMutableString *contentString = [NSMutableString string];
zacw@1226
   203
	id obj = nil;
zacw@1226
   204
	for (obj in contents) {
zacw@1226
   205
		if ([obj isKindOfClass:[NSString class]])
zacw@1226
   206
			[contentString appendString:obj];
zacw@1226
   207
		else if ([obj isKindOfClass:[AIXMLElement class]])
zacw@1226
   208
			[contentString appendString:[obj XMLString]];
zacw@1226
   209
	}
zacw@1226
   210
	return contentString;
zacw@1226
   211
}
zacw@1226
   212
zacw@1226
   213
#pragma mark -
zacw@1226
   214
zacw@1226
   215
- (NSString *) quotedXMLAttributeValueStringForString:(NSString *)str
zacw@1226
   216
{
zacw@1226
   217
	return [NSString stringWithFormat:@"\"%@\"", [str stringByEscapingForXMLWithEntities:nil]];
zacw@1226
   218
}
zacw@1226
   219
zacw@1226
   220
- (void) appendXMLStringtoString:(NSMutableString *)string
zacw@1226
   221
{
zacw@1226
   222
	[string appendFormat:@"<%@", name];
zacw@1226
   223
	if ([attributeNames count]) {
zacw@1226
   224
		unsigned attributeIdx = 0U;
zacw@1226
   225
		NSString *key;
zacw@1226
   226
		for (key in attributeNames) {
zacw@1226
   227
			NSString *value = [attributeValues objectAtIndex:attributeIdx++];
zacw@1226
   228
			if ([value respondsToSelector:@selector(stringValue)]) {
zacw@1226
   229
				value = [(NSNumber *)value stringValue];
zacw@1226
   230
			} else if ([value respondsToSelector:@selector(absoluteString)]) {
zacw@1226
   231
				value = [(NSURL *)value absoluteString];
zacw@1226
   232
			}
sholt@2597
   233
			[string appendFormat:@" %@=%@", key, [self quotedXMLAttributeValueStringForString:value]];
zacw@1226
   234
		}
zacw@1226
   235
	}
zacw@1226
   236
	if ((![contents count]) && (selfCloses)) {
zacw@1226
   237
		[string appendString:@" /"];
zacw@1226
   238
	}
zacw@1226
   239
	[string appendString:@">"];
zacw@1226
   240
zacw@1226
   241
	id obj;
zacw@1226
   242
	for (obj in contents) {
zacw@1226
   243
		if ([obj isKindOfClass:[NSString class]]) {
zacw@1226
   244
			[string appendString:(NSString *)obj];
zacw@1226
   245
		} else if([obj isKindOfClass:[AIXMLElement class]]) {
zacw@1226
   246
			[(AIXMLElement *)obj appendXMLStringtoString:string];
zacw@1226
   247
		}
zacw@1226
   248
	}
zacw@1226
   249
zacw@1226
   250
	if ([contents count] || !selfCloses) {
zacw@1226
   251
		[string appendFormat:@"</%@>", name];
zacw@1226
   252
	}
zacw@1226
   253
}
zacw@1226
   254
- (NSString *) XMLString
zacw@1226
   255
{
zacw@1226
   256
	NSMutableString *string = [NSMutableString string];
zacw@1226
   257
	[self appendXMLStringtoString:string];
zacw@1226
   258
	return [NSString stringWithString:string];
zacw@1226
   259
}
zacw@1226
   260
zacw@1226
   261
- (void) appendUTF8XMLBytesToData:(NSMutableData *)data
zacw@1226
   262
{
zacw@1226
   263
	NSMutableString *startTag = [NSMutableString stringWithFormat:@"<%@", name];
zacw@1226
   264
	if ([self numberOfAttributes]) {
zacw@1226
   265
		unsigned attributeIdx = 0U;
zacw@1226
   266
		NSString *key;
zacw@1226
   267
		for (key in attributeNames) {
zacw@1226
   268
			NSString *value = [attributeValues objectAtIndex:attributeIdx++];
zacw@1226
   269
			if ([value respondsToSelector:@selector(stringValue)]) {
zacw@1226
   270
				value = [(NSNumber *)value stringValue];
zacw@1226
   271
			} else if ([value respondsToSelector:@selector(absoluteString)]) {
zacw@1226
   272
				value = [(NSURL *)value absoluteString];
zacw@1226
   273
			}
zacw@1226
   274
			[startTag appendFormat:@" %@=%@", key, [self quotedXMLAttributeValueStringForString:value]];
zacw@1226
   275
		}
zacw@1226
   276
	}
zacw@1226
   277
	if ((![contents count]) && (selfCloses)) {
zacw@1226
   278
		[startTag appendString:@" /"];
zacw@1226
   279
	}
zacw@1226
   280
	[startTag appendString:@">"];
zacw@1226
   281
	[data appendData:[startTag dataUsingEncoding:NSUTF8StringEncoding]];
zacw@1226
   282
zacw@1226
   283
	id obj;
zacw@1226
   284
	for (obj in contents) {
zacw@1226
   285
		if ([obj isKindOfClass:[NSString class]]) {
zacw@1226
   286
			[data appendData:[(NSString *)obj dataUsingEncoding:NSUTF8StringEncoding]];
zacw@1226
   287
		} else if([obj isKindOfClass:[AIXMLElement class]]) {
zacw@1226
   288
			[(AIXMLElement *)obj appendUTF8XMLBytesToData:data];
zacw@1226
   289
		}
zacw@1226
   290
	}
zacw@1226
   291
zacw@1226
   292
	if ([contents count] || !selfCloses) {
zacw@1226
   293
		[data appendData:[[NSString stringWithFormat:@"</%@>", name] dataUsingEncoding:NSUTF8StringEncoding]];
zacw@1226
   294
	}
zacw@1226
   295
}
zacw@1226
   296
- (NSData *)UTF8XMLData
zacw@1226
   297
{
zacw@1226
   298
	NSMutableData *data = [NSMutableData data];
zacw@1226
   299
	[self appendUTF8XMLBytesToData:data];
zacw@1226
   300
	return [NSData dataWithData:data];
zacw@1226
   301
}
zacw@1226
   302
zacw@1226
   303
- (NSString *)description
zacw@1226
   304
{
zacw@1226
   305
	NSMutableString *string = [NSMutableString stringWithFormat:@"<%@ AIXMLElement:id=\"%p\"", name, self];
zacw@1226
   306
	if ([attributeNames count] && [attributeValues count]) { //there's no way these could be different values, but whatever
zacw@1226
   307
		unsigned attributeIdx = 0U;
zacw@1226
   308
		NSString *key;
zacw@1226
   309
		for (key in attributeNames) {
zacw@1226
   310
			NSString *value = [attributeValues objectAtIndex:attributeIdx++];
zacw@1226
   311
			if ([value respondsToSelector:@selector(stringValue)]) {
zacw@1226
   312
				value = [(NSNumber *)value stringValue];
zacw@1226
   313
			} else if ([value respondsToSelector:@selector(absoluteString)]) {
zacw@1226
   314
				value = [(NSURL *)value absoluteString];
zacw@1226
   315
			}
zacw@1226
   316
			[string appendFormat:@" %@=%@", key, [self quotedXMLAttributeValueStringForString:value]];
zacw@1226
   317
		}
zacw@1226
   318
	}
zacw@1226
   319
	[string appendString:@" />"];
zacw@1226
   320
zacw@1226
   321
	return [NSString stringWithString:string];
zacw@1226
   322
}
zacw@1226
   323
zacw@1226
   324
zacw@1226
   325
zacw@1226
   326
#pragma mark KVC
zacw@1226
   327
zacw@1226
   328
/*
zacw@1226
   329
These aren't working. I recommend calling -objectForKey on the return value of -attributes.
zacw@1226
   330
zacw@1226
   331
Adium[302:117] The following unhandled exception was ignored: NSUnknownKeyException ([<AIXMLElement 0xce582b0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key auto.)
zacw@1226
   332
zacw@1226
   333
*/
zacw@1226
   334
/*
zacw@1226
   335
- (id) valueForKey:(NSString *)key {
zacw@1226
   336
	unsigned idx = [attributeNames indexOfObject:key];	
zacw@1226
   337
	return (idx != NSNotFound) ? [attributeValues objectAtIndex:idx] : [super valueForKey:key];
zacw@1226
   338
}
zacw@1226
   339
//FIXME: this shouldn't clobber setObject:forKey: on NSObject.
zacw@1226
   340
- (void) setValue:(id)obj forKey:(NSString *)key {
zacw@1226
   341
	unsigned idx = [attributeNames indexOfObject:key];
zacw@1226
   342
	if(idx == NSNotFound) {
zacw@1226
   343
		[attributeNames addObject:key];
zacw@1226
   344
		[attributeValues addObject:obj];
zacw@1226
   345
	} else {
zacw@1226
   346
		[attributeValues replaceObjectAtIndex:idx withObject:obj];
zacw@1226
   347
	}
zacw@1226
   348
}
zacw@1226
   349
zacw@1226
   350
*/
zacw@1226
   351
zacw@1226
   352
@end