Line data Source code
1 0 : /*
2 : OldSchoolPropertyListWriting.m
3 : Copyright 2006-2013 Jens Ayton
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6 : and associated documentation files (the "Software"), to deal in the Software without
7 : restriction, including without limitation the rights to use, copy, modify, merge, publish,
8 : distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
9 : Software is furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in all copies or
12 : substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
15 : BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 : NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 : DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 : */
20 :
21 : #include <assert.h>
22 :
23 : #import "OldSchoolPropertyListWriting.h"
24 : #import "NSNumberOOExtensions.h"
25 :
26 :
27 : static void AppendNewLineAndIndent(NSMutableString *ioString, unsigned indentDepth);
28 :
29 :
30 : @implementation NSString (OldSchoolPropertyListWriting)
31 :
32 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
33 : {
34 : NSCharacterSet *charSet;
35 : NSRange foundRange, searchRange;
36 : NSString *foundString;
37 : NSMutableString *newString;
38 : NSUInteger length;
39 :
40 : length = [self length];
41 : if (0 != length
42 : && [self rangeOfCharacterFromSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]].location == NSNotFound
43 : && ![[NSCharacterSet decimalDigitCharacterSet] longCharacterIsMember:[self characterAtIndex:0]])
44 : {
45 : // This is an alphanumeric string whose first character is not a digit
46 : return [[self copy] autorelease];
47 : }
48 : else
49 : {
50 : charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"\r\n\\"];
51 : foundRange = [self rangeOfCharacterFromSet:charSet options:NSLiteralSearch];
52 : if (NSNotFound == foundRange.location)
53 : {
54 : newString = (NSMutableString *)self;
55 : }
56 : else
57 : {
58 : // Escape quotes, backslashes and newlines
59 : newString = [[[self substringToIndex:foundRange.location] mutableCopy] autorelease];
60 :
61 : for (;;)
62 : {
63 : // Append escaped character
64 : foundString = [self substringWithRange:foundRange];
65 : if ([foundString isEqual:@"\""]) [newString appendString:@"\\\""];
66 : else if ([foundString isEqual:@"\n"]) [newString appendString:@"\\\n"];
67 : else if ([foundString isEqual:@"\r"]) [newString appendString:@"\\\r"];
68 : else if ([foundString isEqual:@"\\"]) [newString appendString:@"\\\\"];
69 : else
70 : {
71 : [NSException raise:NSInternalInconsistencyException format:@"%s: expected \" or newline, found %@", __PRETTY_FUNCTION__, foundString];
72 : }
73 :
74 : // Use rest of string…
75 : searchRange.location = foundRange.location + foundRange.length;
76 : searchRange.length = length - searchRange.location;
77 :
78 : // …to search for next char needing escaping
79 : foundRange = [self rangeOfCharacterFromSet:charSet options:NSLiteralSearch range:searchRange];
80 : if (NSNotFound == foundRange.location)
81 : {
82 : [newString appendString:[self substringWithRange:searchRange]];
83 : break;
84 : }
85 : }
86 : }
87 :
88 : return [NSString stringWithFormat:@"\"%@\"", newString];
89 : }
90 : }
91 :
92 : @end
93 :
94 :
95 : @implementation NSNumber (OldSchoolPropertyListWriting)
96 :
97 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
98 : {
99 : NSString *result;
100 : double dVal;
101 :
102 : if ([self oo_isBoolean])
103 : {
104 : if ([self boolValue]) result = @"true";
105 : else result = @"false";
106 : }
107 : else if ([self oo_isFloatingPointNumber])
108 : {
109 : dVal = [self doubleValue];
110 : result = [NSString stringWithFormat:@"%.8g", dVal];
111 : }
112 : else result = [NSString stringWithFormat:@"%@", self];
113 :
114 : // Allow infinities, but remember that they’ll be read in as strings
115 : #if 0
116 : if ([result isEqual:@"inf"] || [result isEqual:@"-inf"])
117 : {
118 : *outErrorDescription = @"infinities cannot be represented in old-school property lists";
119 : return nil;
120 : }
121 : #endif
122 :
123 : return result;
124 : }
125 :
126 : @end
127 :
128 :
129 : @implementation NSData (OldSchoolPropertyListWriting)
130 :
131 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
132 : {
133 : const uint8_t *srcBytes;
134 : uint8_t *dstBytes, *curr;
135 : NSUInteger i, j, srcLength, dstLength;
136 : const char hexTable[] = "0123456789ABCDEF";
137 : NSString *result;
138 :
139 : srcBytes = [self bytes];
140 : srcLength = [self length];
141 :
142 : dstLength = 2 * srcLength + srcLength/8 + 2 + (srcLength/64 * (1 + inIndentation));
143 :
144 : dstBytes = malloc(dstLength);
145 : if (dstBytes == NULL)
146 : {
147 : if (NULL != outErrorDescription)
148 : {
149 : *outErrorDescription = [NSString stringWithFormat:@"failed to allocate space (%lu bytes) for conversion of NSData to old-school property list representation", dstLength];
150 : }
151 : return nil;
152 : }
153 :
154 : curr = dstBytes;
155 : *curr++ = '<';
156 : for (i = 0; i != srcLength; ++i)
157 : {
158 : if (0 != i && 0 == (i & 3))
159 : {
160 : if (0 == (i & 31))
161 : {
162 : *curr++ = '\n';
163 : j = inIndentation;
164 : while (--j) *curr++ = '\t';
165 : }
166 : *curr++ = ' ';
167 : }
168 : *curr++ = hexTable[srcBytes[i] >> 4];
169 : *curr++ = hexTable[srcBytes[i] & 0xF];
170 : }
171 : *curr = '>';
172 :
173 : assert((size_t)(curr - dstBytes) <= dstLength);
174 :
175 : result = [[NSString alloc] initWithBytesNoCopy:dstBytes length:dstLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
176 : return [result autorelease];
177 : }
178 :
179 : @end
180 :
181 :
182 : @implementation NSArray (OldSchoolPropertyListWriting)
183 :
184 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
185 : {
186 : NSMutableString *result;
187 : NSUInteger i, count;
188 : id object;
189 :
190 : result = [NSMutableString string];
191 :
192 : [result appendString:@"("];
193 :
194 : count = [self count];
195 : AppendNewLineAndIndent(result, inIndentation + 1);
196 :
197 : for (i = 0; i != count; ++i)
198 : {
199 : if (0 != i)
200 : {
201 : [result appendString:@","];
202 : AppendNewLineAndIndent(result, inIndentation + 1);
203 : }
204 :
205 : object = [self objectAtIndex:i];
206 : if (![object conformsToProtocol:@protocol (OldSchoolPropertyListWriting)])
207 : {
208 : if (nil != object && NULL != outErrorDescription)
209 : {
210 : *outErrorDescription = [NSString stringWithFormat:@"non-plist object in dictionary"];
211 : }
212 : return nil;
213 : }
214 :
215 : object = [object oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription];
216 : if (nil == object) return nil;
217 : [result appendString:object];
218 : }
219 :
220 : AppendNewLineAndIndent(result, inIndentation);
221 : [result appendString:@")"];
222 : return result;
223 : }
224 :
225 : @end
226 :
227 :
228 : @implementation NSDictionary (OldSchoolPropertyListWriting)
229 :
230 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
231 : {
232 : NSMutableString *result;
233 : NSUInteger i, count;
234 : NSArray *allKeys;
235 : id key, value;
236 : NSString *valueDesc;
237 :
238 : result = [NSMutableString string];
239 : allKeys = [[self allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
240 : count = [allKeys count];
241 :
242 : [result appendString:@"{"];
243 :
244 : AppendNewLineAndIndent(result, inIndentation + 1);
245 :
246 : for (i = 0; i != count; ++i)
247 : {
248 : if (0 != i)
249 : {
250 : AppendNewLineAndIndent(result, inIndentation + 1);
251 : }
252 :
253 : key = [allKeys objectAtIndex:i];
254 : if (![key isKindOfClass:[NSString class]])
255 : {
256 : if (NULL != outErrorDescription) *outErrorDescription = [NSString stringWithFormat:@"non-string key in dictionary"];
257 : return nil;
258 : }
259 : value = [self objectForKey:key];
260 : if (![value conformsToProtocol:@protocol(OldSchoolPropertyListWriting)])
261 : {
262 : if (nil != value && NULL != outErrorDescription)
263 : {
264 : *outErrorDescription = [NSString stringWithFormat:@"non-plist object in dictionary"];
265 : }
266 : return nil;
267 : }
268 :
269 : key = [key oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription];
270 : if (nil == key) return nil;
271 : valueDesc = [value oldSchoolPListFormatWithIndentation:inIndentation + 1 errorDescription:outErrorDescription];
272 : if (nil == valueDesc) return nil;
273 :
274 : [result appendFormat:@"%@ =", key];
275 : if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]])
276 : {
277 : AppendNewLineAndIndent(result, inIndentation + 1);
278 : }
279 : else
280 : {
281 : [result appendString:@" "];
282 : }
283 : [result appendFormat:@"%@;", valueDesc];
284 : }
285 :
286 : AppendNewLineAndIndent(result, inIndentation);
287 : [result appendString:@"}"];
288 :
289 : return result;
290 : }
291 :
292 : @end
293 :
294 :
295 : @interface NSObject (OldSchoolPropertyListWriting_Private)
296 :
297 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription;
298 :
299 : @end
300 :
301 :
302 : @implementation NSObject (OldSchoolPropertyListWriting)
303 :
304 : - (NSData *)oldSchoolPListFormatWithErrorDescription:(NSString **)outErrorDescription
305 : {
306 : NSString *string;
307 :
308 : string = [self oldSchoolPListFormatWithIndentation:0 errorDescription:outErrorDescription];
309 : return [[string stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding];
310 : }
311 :
312 :
313 0 : - (NSString *)oldSchoolPListFormatWithIndentation:(unsigned)inIndentation errorDescription:(NSString **)outErrorDescription
314 : {
315 : if (NULL != outErrorDescription)
316 : {
317 : *outErrorDescription = [NSString stringWithFormat:@"Class %@ does not support OldSchoolPropertyListWriting", [self className]];
318 : }
319 : return nil;
320 : }
321 :
322 : @end
323 :
324 :
325 0 : static void AppendNewLineAndIndent(NSMutableString *ioString, unsigned indentDepth)
326 : {
327 : [ioString appendString:@"\n"];
328 : while (indentDepth--) [ioString appendString:@"\t"];
329 : }
|