|
|
1.1 root 1: /*
2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3: *
4: * @APPLE_LICENSE_HEADER_START@
5: *
6: * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7: * Reserved. This file contains Original Code and/or Modifications of
8: * Original Code as defined in and that are subject to the Apple Public
9: * Source License Version 1.0 (the 'License'). You may not use this file
10: * except in compliance with the License. Please obtain a copy of the
11: * License at http://www.apple.com/publicsource and read it before using
12: * this file.
13: *
14: * The Original Code and all software distributed under the License are
15: * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
19: * License for the specific language governing rights and limitations
20: * under the License."
21: *
22: * @APPLE_LICENSE_HEADER_END@
23: */
24: /* NXPropertyList.m
25: Copyright 1991, NeXT, Inc.
26: Bertrand, August 1991
27: */
28: #ifndef KERNEL
29: #import "NXPropertyList.h"
30:
31: #import <ctype.h>
32: #import <streams/streams.h>
33: #import <stdio.h>
34:
35: /******** String extras ********/
36:
37: @interface NXMutableString (NX_BS_Extras)
38: //?? SHOULD BE IN NXString, AND EFFICIENT!
39: - (void)appendCharacters:(const unichar *)chars length:(unsigned)length;
40: @end
41:
42: @implementation NXMutableString (NX_BS_Extras)
43: - (void)appendCharacters:(const unichar *)chars length:(unsigned)length {
44: NXReadWriteString *temp = [NXReadOnlyString alloc];
45: [temp initFromCharactersNoCopy:(unichar *)chars length:length freeWhenDone:NO];
46: [self append:temp];
47: [temp free];
48: }
49: @end
50:
51: @interface Object (NX_BS_Extras)
52: //?? Should be in ObjC
53: // Assumed by ipclib
54: - (void)writeToStream:(NXStream *)stream;
55: @end
56:
57: @implementation Object (NX_BS_Extras)
58: - (void)writeToStream:(NXStream *)stream {
59: NXPrintf(stream, "0x%x", self);
60: }
61: @end
62:
63: /******** Class implementation ********/
64:
65: @implementation NXPropertyList
66:
67: static void freeKeyAndValue(NXMapTable *table, void *key, void *value) {
68: [(id)key free];
69: [(id)value free];
70: }
71:
72: - init {
73: NXMapTablePrototype proto = NXObjectMapPrototype;
74: proto.free = freeKeyAndValue;
75: table = NXCreateMapTable(proto, 0);
76: return self;
77: }
78:
79: - free {
80: /* we test for table NULL for cases when free is called during initialization */
81: if (table) NXFreeMapTable(table);
82: return [super free];
83: }
84:
85: - (unsigned)count {
86: return NXCountMapTable(table);
87: }
88:
89: - (BOOL)member:(NXString *)key {
90: id value;
91: return (NXMapMember(table, key, (void **)&value) != NX_MAPNOTAKEY);
92: }
93:
94: - get:(NXString *)key {
95: return NXMapGet(table, key);
96: }
97:
98: - insert:(NXString *)key value:value {
99: id oldValue;
100: void *oldKey = NXMapMember(table, key, (void **)&oldValue);
101: if (oldKey == NX_MAPNOTAKEY) {
102: oldValue = nil;
103: } else {
104: (void)NXMapRemove(table, oldKey);
105: [(id)oldKey free];
106: }
107: (void)NXMapInsert(table, [key immutableCopy], value);
108: return oldValue;
109: }
110:
111: - remove:(NXString *)key {
112: id oldValue;
113: void *oldKey = NXMapMember(table, key, (void **)&oldValue);
114: if (oldKey == NX_MAPNOTAKEY) return nil;
115: NXMapRemove(table, oldKey);
116: [(id)oldKey free];
117: return oldValue;
118: }
119:
120: - empty {
121: NXResetMapTable(table);
122: return self;
123: }
124:
125: - (NXMapState)initEnumeration {
126: return NXInitMapState(table);
127: }
128:
129: - (BOOL)enumerate:(NXMapState *)state key:(NXString **)refKey value:(id *)refValue {
130: return NXNextMapState(table, state, (void **)refKey, (void **)refValue) != 0;
131: }
132:
133: @end
134:
135: /******** Basic ASCII read/write of property lists ********/
136:
137: @implementation NXPropertyList (Basic_IO)
138: - initFromStream:(NXStream *)stream {
139: NXPropertyListReadContext context = {
140: 0, [NXReadWriteString new],
141: [NXReadOnlyString class],
142: [NXReadOnlyString class],
143: [NXCleanList class],
144: [NXPropertyList class],
145: YES,
146: [self zone],
147: NULL
148: };
149: id new = [self initFromStream:stream context:&context];
150: [context.buffer free];
151: if (context.uniquingTable) NXFreeHashTable(context.uniquingTable);
152: return new;
153: }
154:
155: - initFromPath:(NXString *)path {
156: NXPropertyListReadContext context = {
157: 0, [NXReadWriteString new],
158: [NXReadOnlyString class],
159: [NXReadOnlyString class],
160: [NXCleanList class],
161: [NXPropertyList class],
162: YES,
163: [self zone],
164: NULL
165: };
166: id new = [self initFromPath:path context:&context];
167: [context.buffer free];
168: if (context.uniquingTable) NXFreeHashTable(context.uniquingTable);
169: return new;
170: }
171:
172: - (void)writeToStream:(NXStream *)stream {
173: NXPropertyListWriteContext context = {
174: 4, 0,
175: NO, "\n"
176: };
177: [self writeToStream:stream context:&context];
178: }
179:
180: - (BOOL)writeToPath:(NXString *)path safely:(BOOL)safe {
181: char cpath[MAXPATHLEN];
182: char temp[MAXPATHLEN];
183: NXStream *stream;
184: BOOL ok;
185: if (! path) return NO;
186: [path getCString:cpath]; cpath[[path length]] = 0;
187: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
188: strcpy(temp, cpath);
189: if (safe) strcat(temp, "~");
190: [self writeToStream:stream];
191: ok = ! NXSaveToFile(stream, temp);
192: NXCloseMemory(stream, NX_FREEBUFFER);
193: if (safe && ok) ok = ! rename(temp, cpath);
194: return ok;
195: }
196:
197: - (BOOL)writeToPath:(NXString *)path {
198: return [self writeToPath:path safely:YES];
199: }
200:
201: @end
202:
203: /******** Read/write utilities ********/
204:
205: // constants
206: #define BEGIN_PAR '('
207: #define END_PAR ')'
208: #define BEGIN_CURLY '{'
209: #define END_CURLY '}'
210:
211: static int NXGetNonSpace(NXStream *stream, int *line) {
212: int ch;
213: while ((ch = NXGetc(stream)) != EOF) {
214: if (ch == '\n') (*line)++;
215: if (ch == '/') {
216: if ((ch = NXGetc(stream)) == '/') {
217: while ((ch = NXGetc(stream)) != EOF && ch != '\n') {};
218: if (ch == '\n') (*line)++;
219: } else if (ch == '*') {
220: while ((ch = NXGetc(stream)) != EOF) {
221: if (ch == '*') {
222: ch = NXGetc(stream);
223: if (ch == '/') break;
224: NXUngetc(stream);
225: } else if (ch == '\n') (*line)++;
226: }
227: } else {
228: NXUngetc(stream);
229: return '/';
230: }
231: } else if (! isspace(ch)) return ch;
232: }
233: return EOF;
234: }
235:
236: static inline int isTokenChar(int ch) {
237: return (isalnum(ch) || ch == '_' || ch == '$' || ch == ':' || ch == '.' || ch == '/') ? 1 : 0;
238: }
239:
240: static int NXGetSlashedChar(NXStream *stream, int *line) {
241: int ch;
242: switch (ch = NXGetc(stream)) {
243: case '0':
244: case '1':
245: case '2':
246: case '3':
247: case '4':
248: case '5':
249: case '6':
250: case '7': {
251: int num = ch - '0';
252: /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
253: if ((ch = NXGetc(stream)) >= '0' && ch <= '7') {
254: num = (num << 3) + ch - '0';
255: if ((ch = NXGetc(stream)) >= '0' && ch <= '7') {
256: num = (num << 3) + ch - '0';
257: } else NXUngetc(stream);
258: } else NXUngetc(stream);
259: return num;
260: }
261: case 'a': return '\a';
262: case 'b': return '\b';
263: case 'f': return '\f';
264: case 'n': return '\n';
265: case 'r': return '\r';
266: case 't': return '\t';
267: case 'v': return '\v';
268: case '"': return '\"';
269: case '\n': (*line)++;
270: return '\n';
271: }
272: return ch;
273: }
274:
275: static id readValue(NXStream *stream, NXPropertyListReadContext *context) {
276: int ch = NXGetNonSpace(stream, &context->line);
277: if (ch == BEGIN_CURLY || ch == BEGIN_PAR) {
278: id factory;
279: int endch;
280: id value;
281: if (ch == BEGIN_CURLY) {
282: factory = context->propertyListValueFactory;
283: endch = END_CURLY;
284: } else {
285: factory = context->listValueFactory;
286: endch = END_PAR;
287: }
288: value = [[factory allocFromZone:context->zone] initFromStream:stream context:context];
289: if (! value) return nil;
290: ch = NXGetNonSpace(stream, &context->line);
291: if (ch != endch) {
292: [value free];
293: return nil;
294: }
295: return value;
296: } else {
297: NXUngetc(stream);
298: return [[context->stringValueFactory allocFromZone:context->zone] initFromStream:stream context:context];
299: }
300: }
301:
302: static void writeSpaces(NXStream *stream, int spaces) {
303: while (spaces >= 8) {
304: NXPutc(stream, '\t'); spaces -= 8;
305: }
306: while (spaces--) NXPutc(stream, ' ');
307: }
308:
309: /******** A list that really frees its elements ********/
310:
311: @implementation NXCleanList:List
312: - free {
313: [self freeObjects];
314: return [super free];
315: }
316: @end
317:
318: /******** Fancy ASCII read/write of property lists ********/
319:
320: @implementation NXPropertyList (Fancy_IO)
321: - initFromStream:(NXStream *)stream context:(NXPropertyListReadContext *)context {
322: int ch = NXGetNonSpace(stream, &context->line);
323: BOOL ok = YES;
324: [self init];
325: while (ok && (ch == '"' || isTokenChar(ch))) {
326: NXString *key;
327: id value;
328: NXUngetc(stream);
329: key = [[context->keyFactory allocFromZone:context->zone] initFromStream:stream context:context];
330: if (! key) return NO;
331: ch = NXGetNonSpace(stream, &context->line);
332: if (ch == '=') {
333: value = readValue(stream, context);
334: if (! value) {
335: printf("*** NXPropertyList: Syntax error line %u\n", context->line);
336: [self free];
337: return nil;
338: }
339: [self insert:key value:value];
340: ch = NXGetNonSpace(stream, &context->line);
341: } else {
342: [self insert:key value:(context->noValueIsSame) ? [key immutableCopy] : nil];
343: }
344: [key free];
345: ok = NO;
346: while (ch == ';') {
347: ok = YES; ch = NXGetNonSpace(stream, &context->line);
348: }
349: }
350: NXUngetc(stream);
351: return self;
352: }
353:
354: - (void)writeToStream:(NXStream *)stream context:(NXPropertyListWriteContext *)context {
355: NXMapState state = [self initEnumeration];
356: id key;
357: id value;
358: NXPropertyListWriteContext original = *context;
359: unsigned spaces = context->indent;
360: if (original.topLevelBrackets) NXPutc(stream, BEGIN_CURLY);
361: while ([self enumerate:&state key:&key value:&value]) {
362: context->topLevelBrackets = YES;
363: context->pairSeparator = original.pairSeparator;
364: writeSpaces(stream, spaces);
365: if (key == value) {
366: [key writeToStream:stream context:context];
367: NXPrintf(stream, ";");
368: } else if ([value isKindOf:[NXPropertyList class]]) {
369: [key writeToStream:stream context:context];
370: NXPrintf(stream, " = {%s", original.pairSeparator);
371: context->topLevelBrackets = NO;
372: context->indent = spaces + context->indentDelta;
373: [value writeToStream:stream context:context];
374: writeSpaces(stream, spaces);
375: NXPrintf(stream, "};");
376: } else if (value) {
377: [key writeToStream:stream context:context];
378: NXPrintf(stream, " = ");
379: [value writeToStream:stream context:context];
380: NXPrintf(stream, ";");
381: }
382: NXPrintf(stream, "%s", original.pairSeparator);
383: }
384: if (original.topLevelBrackets) NXPutc(stream, END_CURLY);
385: }
386: - initFromPath:(NXString *)path context:(NXPropertyListReadContext *)context {
387: char cpath[MAXPATHLEN];
388: NXStream *stream;
389: id new;
390: if (! path) goto nope;
391: [path getCString:cpath]; cpath[[path length]] = 0;
392: stream = NXMapFile(cpath, NX_READONLY);
393: if (! stream) goto nope;
394: new = [self initFromStream:stream context:context];
395: if (NXGetc(stream) != EOF) {
396: char cpath[MAXPATHLEN];
397: [path getCString:cpath];
398: printf("NXPropertyList: discarded input at line %u of file %s\n", context->line, cpath);
399: }
400: NXCloseMemory(stream, NX_FREEBUFFER);
401: return new;
402: nope:
403: [self free];
404: return nil;
405: }
406:
407: @end
408:
409: @implementation NXCleanList (Fancy_IO)
410: - initFromStream:(NXStream *)stream context:(NXPropertyListReadContext *)context {
411: BOOL ok = YES;
412: int ch = NXGetNonSpace(stream, &context->line);
413: while (ok && (ch == '"' || isTokenChar(ch) || ch == BEGIN_CURLY || ch == BEGIN_PAR)) {
414: id value;
415: NXUngetc(stream);
416: value = readValue(stream, context);
417: if (! value) {
418: printf("*** NXCleanList: Syntax error line %u\n", context->line);
419: [self free];
420: return nil;
421: }
422: [self addObject:value];
423: ch = NXGetNonSpace(stream, &context->line);
424: ok = NO;
425: while (ch == ',') {
426: ok = YES; ch = NXGetNonSpace(stream, &context->line);
427: }
428: }
429: NXUngetc(stream);
430: return self;
431: }
432:
433:
434: - (void)writeToStream:(NXStream *)stream context:(NXPropertyListWriteContext *)context {
435: unsigned index = 0;
436: unsigned count = [self count];
437: BOOL top = context->topLevelBrackets;
438: if (top) NXPutc(stream, BEGIN_PAR);
439: while (index < count) {
440: context->topLevelBrackets = YES;
441: context->pairSeparator = "";
442: context->indent = 0;
443: [[self objectAt:index] writeToStream:stream context:context];
444: if (index != count-1) NXPrintf(stream, ", ");
445: index++;
446: }
447: if (top) NXPutc(stream, END_PAR);
448: }
449:
450: @end
451:
452: @implementation NXString (Fancy_IO)
453:
454: #define MAX_TOKEN 1024
455:
456: static inline void append1(NXMutableString *buffer, BOOL *bufferUsed, int ch, unichar *buf, unsigned *buflen) {
457: if (*buflen == MAX_TOKEN) {
458: if (! *bufferUsed) [buffer replaceWith:@""];
459: *bufferUsed = YES;
460: [buffer appendCharacters:buf length:MAX_TOKEN];
461: *buflen = 0;
462: }
463: buf[(*buflen)++] = ch;
464: }
465:
466: static inline NXString *init(NXString *self, NXMutableString *buffer, BOOL bufferUsed, unichar *buf, unsigned *buflen) {
467: if (bufferUsed) {
468: [buffer appendCharacters:buf length:*buflen];
469: return [self initFromString:buffer];
470: }
471: return [self initFromCharacters:buf length:*buflen];
472: }
473:
474: - initFromStream:(NXStream *)stream context:(NXPropertyListReadContext *)context {
475: int ch = NXGetNonSpace(stream, &context->line);
476: if (ch == '"') {
477: BOOL bufferUsed = NO;
478: unichar buf[MAX_TOKEN];
479: unsigned buflen = 0;
480: while (((ch = NXGetc(stream)) != EOF) && (ch != '"')) {
481: if (ch == '\n') (context->line)++;
482: if (ch == '\\') ch = NXGetSlashedChar(stream, &context->line);
483: append1(context->buffer, &bufferUsed, ch, buf, &buflen);
484: }
485: if (ch == EOF) {
486: [self free];
487: return nil;
488: }
489: return init(self, context->buffer, bufferUsed, buf, &buflen);
490: } else if (isTokenChar(ch)) {
491: BOOL bufferUsed = NO;
492: unichar buf[MAX_TOKEN];
493: unsigned buflen = 0;
494: append1(context->buffer, &bufferUsed, ch, buf, &buflen);
495: while (((ch = NXGetc(stream)) != EOF) && isTokenChar(ch)) {
496: append1(context->buffer, &bufferUsed, ch, buf, &buflen);
497: }
498: NXUngetc(stream);
499: return init(self, context->buffer, bufferUsed, buf, &buflen);
500: } else {
501: NXUngetc(stream);
502: [self free];
503: return nil;
504: }
505: }
506:
507: - (void)writeToStream:(NXStream *)stream context:(NXPropertyListWriteContext *)context {
508: unsigned index = 0;
509: unsigned count = [self length];
510: BOOL token = (count != 0);
511: while (index < count) {
512: int ch = [self characterAt:index];
513: if (! isTokenChar(ch)) { token = NO; break; }
514: index ++;
515: }
516: if (token) {
517: NXPrintf(stream, "%@", self);
518: } else {
519: NXPutc(stream, '"');
520: index = 0;
521: while (index < count) {
522: int ch = [self characterAt:index];
523: int ch1 = '\\', ch2 = 0;
524: switch (ch) {
525: case '\\': ch2 = ch; break;
526: case '"': ch2 = ch; break;
527: case '\a': ch2 = 'a'; break;
528: case '\b': ch2 = 'b'; break;
529: case '\f': ch2 = 'f'; break;
530: case '\n': ch2 = 'n'; break;
531: case '\t': ch2 = 't'; break;
532: case '\v': ch2 = 'v'; break;
533: default:
534: if (ch >= ' ' && ch <= '~') {
535: ch1 = ch;
536: } else {
537: /* attention here: if the next character is a number, we need to avoid \0 followed by 5 that would become \05. We do that by printing 3 digits */
538: NXPrintf(stream, "\\%+03o", ch);
539: ch1 = 0;
540: }
541: }
542: if (ch1) {
543: NXPutc(stream, ch1);
544: if (ch2) NXPutc(stream, ch2);
545: }
546: index ++;
547: }
548: NXPutc(stream, '"');
549: }
550: }
551:
552: @end
553:
554: @implementation NXReadOnlyString (Fancy_IO)
555: // Only this class tries uniquing the strings
556: static unsigned hashString(const void *info, const void *data) {
557: return [(NXString *)data hash];
558: }
559: static int isEqualString(const void *info, const void *data1, const void *data2) {
560: return [(NXString *)data1 isEqual:(NXString *)data2];
561: }
562: static void freeString(const void *info, const void *data) {
563: [(NXString *)data free];
564: }
565:
566: - initFromStream:(NXStream *)stream context:(NXPropertyListReadContext *)context {
567: NXString *new = [super initFromStream:stream context:context];
568: NXString *original;
569: if (! new) return new;
570: if (! context->uniquingTable) {
571: NXHashTablePrototype stringSetProto = {hashString, isEqualString, freeString, 0};
572: context->uniquingTable = NXCreateHashTable(stringSetProto, 0, 0);
573: }
574: original = NXHashGet(context->uniquingTable, new);
575: if (! original) {
576: NXHashInsert(context->uniquingTable, [new immutableCopy]);
577: return new;
578: } else {
579: [new free];
580: return [original immutableCopy];
581: }
582: }
583: @end
584:
585: @implementation Object (Fancy_IO)
586: - (void)writeToStream:(NXStream *)stream context:(NXPropertyListWriteContext *)context {
587: [self writeToStream:stream];
588: }
589: @end
590:
591: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.