]> git.saurik.com Git - apple/bootx.git/blob - bootx.tproj/sl.subproj/plist.c
BootX-75.tar.gz
[apple/bootx.git] / bootx.tproj / sl.subproj / plist.c
1 /*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22 /*
23 * plist.c - plist parsing functions
24 *
25 * Copyright (c) 2000-2005 Apple Computer, Inc.
26 *
27 * DRI: Josh de Cesare
28 * code split out from drivers.c by Soren Spies, 2005
29 */
30
31 #include <sl.h>
32
33 #define kXMLTagPList "plist"
34 #define kXMLTagDict "dict"
35 #define kXMLTagKey "key"
36 #define kXMLTagString "string"
37 #define kXMLTagInteger "integer"
38 #define kXMLTagData "data"
39 #define kXMLTagDate "date"
40 #define kXMLTagFalse "false/"
41 #define kXMLTagTrue "true/"
42 #define kXMLTagArray "array"
43 // for back-references used by libkern serializer
44 #define kXMLTagReference "reference"
45 #define kXMLTagID "ID="
46 #define kXMLTagIDREF "IDREF="
47
48 static long ParseNextTag(char *buffer, TagPtr *tag);
49 static long ParseTagList(char *buffer, TagPtr *tag, long type, long empty);
50 static long ParseTagKey(char *buffer, TagPtr *tag);
51 static long ParseTagString(char *buffer, TagPtr *tag);
52 static long ParseTagInteger(char *buffer, TagPtr *tag);
53 static long ParseTagData(char *buffer, TagPtr *tag);
54 static long ParseTagDate(char *buffer, TagPtr *tag);
55 static long ParseTagBoolean(char *buffer, TagPtr *tag, long type);
56 static long GetNextTag(char *buffer, char **tag, long *start, long *empty);
57 static long FixDataMatchingTag(char *buffer, char *tag);
58 static TagPtr NewTag(void);
59 static char *NewSymbol(char *string);
60 static void FreeSymbol(char *string);
61
62 #if PLIST_DEBUG
63 // for debugging parsing failures
64 static int gTagsParsed;
65 static char *gLastTag;
66 #endif
67
68 TagPtr GetProperty(TagPtr dict, char *key)
69 {
70 TagPtr tagList, tag;
71
72 if (dict->type != kTagTypeDict) return 0;
73
74 tag = 0; // ?
75 tagList = dict->tag;
76 while (tagList) {
77 tag = tagList;
78 tagList = tag->tagNext;
79
80 if ((tag->type != kTagTypeKey) || (tag->string == 0)) continue;
81
82 if (!strcmp(tag->string, key)) {
83 return tag->tag;
84 }
85 }
86
87 return 0;
88 }
89
90 // intended to look for two versions of the tag; now just for sizeof
91 #define MATCHTAG(parsedTag, keyTag) \
92 (!strncmp(parsedTag, keyTag, sizeof(keyTag)-1))
93
94 // a tag cache for iokit's super-plists (alas, to merge w/"Symbol" cache?)
95 // we're not going to use the 0th element; it's used by the whole dict anyway
96 static int lastid;
97 static int numids = 0; // skipping 0th
98 static TagPtr *idtags;
99
100 #define INITIALIDS 10
101 static long InitTagCache()
102 {
103 long rval = -1;
104
105 do {
106 lastid = 0;
107 numids = INITIALIDS;
108 idtags = (TagPtr*)malloc(numids * sizeof(*idtags));
109 if (!idtags) break;
110 bzero(idtags, numids * sizeof(*idtags));
111
112 rval = 0;
113 } while(0);
114
115 return rval;
116 }
117
118 static void FreeTagCache()
119 {
120 if (idtags)
121 free(idtags);
122 idtags = NULL;
123 }
124
125 // get the number from (e.g.): ID="3"
126 static int ExtractID(char *tagName, int tagLen)
127 {
128 char *idptr = tagName + tagLen;
129 int rval = 0;
130
131 while(*idptr != '>') {
132 idptr++;
133 if (MATCHTAG(idptr, kXMLTagID)) {
134 rval = strtol(idptr + sizeof(kXMLTagID)-1+1, 0, 0); // -NUL +"
135 break;
136 } else if (MATCHTAG(idptr, kXMLTagIDREF)) {
137 rval = strtol(idptr + sizeof(kXMLTagIDREF)-1+1, 0, 0); // -NUL +"
138 break;
139 }
140 // there can be multiple modifiers (integers have 'size' first)
141 while(*idptr != ' ' && *idptr != '>') idptr++;
142 }
143
144 return rval;
145 }
146
147 static TagPtr TagFromRef(char *tagName, int tagLen)
148 {
149 int refidx = ExtractID(tagName, tagLen);
150 TagPtr rval = NULL;
151
152 if (refidx <= lastid)
153 rval = idtags[refidx];
154
155 return rval;
156 }
157
158 static long SaveTagRef(TagPtr tag, int tagid)
159 {
160
161 // bumped any time we skip an unsupported tag
162 if (tagid != ++lastid) {
163 if (tagid > lastid) {
164 lastid = tagid;
165 }
166 else {
167 printf("invalid plist: tagid (%d) < lastid (%d)??\n", tagid, lastid);
168 return -1;
169 }
170 }
171
172 // upsize idtags if needed
173 if (numids <= lastid) {
174 while(numids <= lastid)
175 numids *= 2;
176 idtags = (TagPtr*)realloc(idtags, numids * sizeof(*idtags));
177 if (!idtags) return -1;
178 }
179
180 // and record for later
181 idtags[lastid] = tag;
182
183 return 0;
184 }
185
186 long ParseXML(char *buffer, TagPtr *dict)
187 {
188 long length, pos;
189 TagPtr moduleDict;
190
191 #if PLIST_DEBUG
192 gTagsParsed = 0;
193 gLastTag = NULL;
194 #endif
195 if (InitTagCache()) return -1;
196 pos = 0;
197 while (1) {
198 moduleDict = (TagPtr)-1; // have to detect changes to by-ref parameter
199 length = ParseNextTag(buffer + pos, &moduleDict);
200 if (length == -1) break;
201 pos += length;
202
203 if (moduleDict == 0) continue;
204
205 // did we actually create anything?
206 if (moduleDict != (TagPtr)-1) {
207 if (moduleDict->type == kTagTypeDict) break;
208 if (moduleDict->type == kTagTypeArray) break;
209
210 FreeTag(moduleDict);
211 }
212 }
213
214 *dict = moduleDict;
215
216 #if PLIST_DEBUG
217 if (length == -1)
218 printf("ParseXML gagged (-1) after %s (%d tags); buf+pos: %s\n",
219 gLastTag,gTagsParsed,buffer+pos);
220 #endif
221
222 // for tidyness even though kext parsing resets all of malloc
223 FreeTagCache();
224
225 // return 0 for no error
226 return (length != -1) ? 0 : -1;
227 }
228
229 #define PARSESTASHINGTAG(tagBuf, keyTag, parseFunc) do { \
230 TagPtr extantTag = TagFromRef(tagBuf, sizeof(keyTag)-1); \
231 if (extantTag) { \
232 *tag = extantTag; \
233 length = 0; \
234 } else { \
235 int tagid = ExtractID(tagName, sizeof(keyTag)-1); \
236 length = parseFunc(buffer + pos, tag); \
237 if (tagid && length != -1) \
238 if (-1 == SaveTagRef(*tag, tagid)) \
239 return -1; \
240 } \
241 } while(0)
242
243 static long ParseNextTag(char *buffer, TagPtr *tag)
244 {
245 long length, pos, empty = 0;
246 char *tagName;
247 TagPtr refTag;
248
249 length = GetNextTag(buffer, &tagName, 0, &empty);
250 if (length == -1) return -1;
251 #if PLIST_DEBUG
252 gLastTag = tagName;
253 gTagsParsed++;
254 #endif
255
256 pos = length;
257 if (MATCHTAG(tagName, kXMLTagPList)) {
258 length = 0; // just a header; nothing to parse
259 // return-via-reference tag should be left alone
260 } else if (MATCHTAG(tagName, kXMLTagDict)) {
261 length = ParseTagList(buffer + pos, tag, kTagTypeDict, empty);
262 } else if (!strcmp(tagName, kXMLTagKey)) {
263 length = ParseTagKey(buffer + pos, tag);
264 } else if (MATCHTAG(tagName, kXMLTagReference) &&
265 (refTag = TagFromRef(tagName, sizeof(kXMLTagReference)-1))) {
266 *tag = refTag;
267 length = 0;
268 } else if (MATCHTAG(tagName, kXMLTagString)) {
269 PARSESTASHINGTAG(tagName, kXMLTagString, ParseTagString);
270 } else if (MATCHTAG(tagName, kXMLTagInteger)) {
271 PARSESTASHINGTAG(tagName, kXMLTagInteger, ParseTagInteger);
272 } else if (!strcmp(tagName, kXMLTagData)) {
273 length = ParseTagData(buffer + pos, tag);
274 } else if (!strcmp(tagName, kXMLTagDate)) {
275 length = ParseTagDate(buffer + pos, tag);
276 } else if (!strcmp(tagName, kXMLTagFalse)) {
277 length = ParseTagBoolean(buffer + pos, tag, kTagTypeFalse);
278 } else if (!strcmp(tagName, kXMLTagTrue)) {
279 length = ParseTagBoolean(buffer + pos, tag, kTagTypeTrue);
280 } else if (MATCHTAG(tagName, kXMLTagArray)) {
281 length = ParseTagList(buffer + pos, tag, kTagTypeArray, empty);
282 } else {
283 // it wasn't parsed so we consumed no additional characters
284 length = 0;
285 if (tagName[0] == '/') // was it an end tag (indicated w/*tag = 0)
286 *tag = 0;
287 else {
288 //printf("ignored plist tag: %s (*tag: %x)\n", tagName, *tag);
289 *tag = (TagPtr)-1; // we're *not* returning a tag
290 }
291 }
292
293 if (length == -1) return -1;
294
295 return pos + length;
296 }
297
298 static long ParseTagList(char *buffer, TagPtr *tag, long type, long empty)
299 {
300 long length, pos;
301 TagPtr tagList, tmpTag = (TagPtr)-1;
302
303 tagList = 0;
304 pos = 0;
305
306 if (!empty) {
307 while (1) {
308 tmpTag = (TagPtr)-1;
309 length = ParseNextTag(buffer + pos, &tmpTag);
310 if (length == -1) break;
311 pos += length;
312
313 // detect end of list
314 if (tmpTag == 0) break;
315
316 // if we made a new tag, insert into list
317 if (tmpTag != (TagPtr)-1) {
318 tmpTag->tagNext = tagList;
319 tagList = tmpTag;
320 }
321 }
322
323 if (length == -1) {
324 FreeTag(tagList);
325 return -1;
326 }
327 }
328
329 tmpTag = NewTag();
330 if (tmpTag == 0) {
331 FreeTag(tagList);
332 return -1;
333 }
334
335 tmpTag->type = type;
336 tmpTag->string = 0;
337 tmpTag->tag = tagList;
338 tmpTag->tagNext = 0;
339
340 *tag = tmpTag;
341
342 return pos;
343 }
344
345
346 static long ParseTagKey(char *buffer, TagPtr *tag)
347 {
348 long length, length2;
349 char *string;
350 TagPtr tmpTag, subTag = (TagPtr)-1; // eliminate possible stale tag
351
352 length = FixDataMatchingTag(buffer, kXMLTagKey);
353 if (length == -1) return -1;
354
355 length2 = ParseNextTag(buffer + length, &subTag);
356 if (length2 == -1) return -1;
357
358 // XXXX revisit 4063982 if FreeTag becomes real
359 if(subTag == (TagPtr)-1)
360 subTag = NULL;
361
362 tmpTag = NewTag();
363 if (tmpTag == 0) {
364 FreeTag(subTag);
365 return -1;
366 }
367
368 string = NewSymbol(buffer);
369 if (string == 0) {
370 FreeTag(subTag);
371 FreeTag(tmpTag);
372 return -1;
373 }
374
375 tmpTag->type = kTagTypeKey;
376 tmpTag->string = string;
377 tmpTag->tag = subTag;
378 tmpTag->tagNext = 0;
379
380 *tag = tmpTag;
381
382 return length + length2;
383 }
384
385
386 static long ParseTagString(char *buffer, TagPtr *tag)
387 {
388 long length;
389 char *string;
390 TagPtr tmpTag;
391
392 length = FixDataMatchingTag(buffer, kXMLTagString);
393 if (length == -1) return -1;
394
395 tmpTag = NewTag();
396 if (tmpTag == 0) return -1;
397
398 string = NewSymbol(buffer);
399 if (string == 0) {
400 FreeTag(tmpTag);
401 return -1;
402 }
403
404 tmpTag->type = kTagTypeString;
405 tmpTag->string = string;
406 tmpTag->tag = 0;
407 tmpTag->tagNext = 0;
408
409 *tag = tmpTag;
410
411 return length;
412 }
413
414
415 static long ParseTagInteger(char *buffer, TagPtr *tag)
416 {
417 long length;
418 char *intString;
419 TagPtr tmpTag;
420
421 length = FixDataMatchingTag(buffer, kXMLTagInteger);
422 if (length == -1) return -1;
423
424 tmpTag = NewTag();
425 if (tmpTag == 0) return -1;
426
427 intString = NewSymbol(buffer);
428 if (intString == 0) {
429 FreeTag(tmpTag);
430 return -1;
431 }
432
433 tmpTag->type = kTagTypeInteger;
434 tmpTag->string = intString;
435 tmpTag->tag = 0;
436 tmpTag->tagNext = 0;
437
438 *tag = tmpTag;
439
440 return length;
441 }
442
443
444 static long ParseTagData(char *buffer, TagPtr *tag)
445 {
446 long length;
447 TagPtr tmpTag;
448
449 length = FixDataMatchingTag(buffer, kXMLTagData);
450 if (length == -1) return -1;
451
452 tmpTag = NewTag();
453 if (tmpTag == 0) return -1;
454
455 tmpTag->type = kTagTypeData;
456 tmpTag->string = 0;
457 tmpTag->tag = 0;
458 tmpTag->tagNext = 0;
459
460 *tag = tmpTag;
461
462 return length;
463 }
464
465
466 static long ParseTagDate(char *buffer, TagPtr *tag)
467 {
468 long length;
469 TagPtr tmpTag;
470
471 length = FixDataMatchingTag(buffer, kXMLTagDate);
472 if (length == -1) return -1;
473
474 tmpTag = NewTag();
475 if (tmpTag == 0) return -1;
476
477 tmpTag->type = kTagTypeDate;
478 tmpTag->string = 0;
479 tmpTag->tag = 0;
480 tmpTag->tagNext = 0;
481
482 *tag = tmpTag;
483
484 return length;
485 }
486
487
488 static long ParseTagBoolean(char *buffer, TagPtr *tag, long type)
489 {
490 TagPtr tmpTag;
491
492 tmpTag = NewTag();
493 if (tmpTag == 0) return -1;
494
495 tmpTag->type = type;
496 tmpTag->string = 0;
497 tmpTag->tag = 0;
498 tmpTag->tagNext = 0;
499
500 *tag = tmpTag;
501
502 return 0;
503 }
504
505
506 static long GetNextTag(char *buffer, char **tag, long *start, long *empty)
507 {
508 long cnt, cnt2;
509
510 if (tag == 0) return -1;
511
512 // Find the start of the tag.
513 cnt = 0;
514 while ((buffer[cnt] != '\0') && (buffer[cnt] != '<')) cnt++;
515 if (buffer[cnt] == '\0') return -1;
516
517 // Find the end of the tag.
518 cnt2 = cnt + 1;
519 while ((buffer[cnt2] != '\0') && (buffer[cnt2] != '>')) cnt2++;
520 if (buffer[cnt2] == '\0') return -1;
521 if (empty && cnt2 > 1)
522 *empty = buffer[cnt2-1] == '/';
523
524 // Fix the tag data.
525 *tag = buffer + cnt + 1;
526 buffer[cnt2] = '\0';
527 if (start) *start = cnt;
528
529 return cnt2 + 1;
530 }
531
532
533 static long FixDataMatchingTag(char *buffer, char *tag)
534 {
535 long length, start, stop;
536 char *endTag;
537
538 start = 0;
539 while (1) {
540 length = GetNextTag(buffer + start, &endTag, &stop, NULL);
541 if (length == -1) return -1;
542
543 if ((*endTag == '/') && !strcmp(endTag + 1, tag)) break;
544 start += length;
545 }
546
547 buffer[start + stop] = '\0';
548
549 return start + length;
550 }
551
552
553 #define kTagsPerBlock (0x1000)
554
555 static TagPtr gTagsFree;
556
557 static TagPtr NewTag(void)
558 {
559 long cnt;
560 TagPtr tag;
561
562 if (gTagsFree == 0) {
563 tag = (TagPtr)AllocateBootXMemory(kTagsPerBlock * sizeof(Tag));
564 if (tag == 0) return 0;
565
566 // Initalize the new tags.
567 for (cnt = 0; cnt < kTagsPerBlock; cnt++) {
568 tag[cnt].type = kTagTypeNone;
569 tag[cnt].string = 0;
570 tag[cnt].tag = 0;
571 tag[cnt].tagNext = tag + cnt + 1;
572 }
573 tag[kTagsPerBlock - 1].tagNext = 0;
574
575 gTagsFree = tag;
576 }
577
578 tag = gTagsFree;
579 gTagsFree = tag->tagNext;
580
581 return tag;
582 }
583
584
585 // currently a no-op
586 void FreeTag(TagPtr tag)
587 {
588 return; // XXXX revisit callers, particularly ParseTagKey (4063982)
589 if (tag == 0) return;
590
591 if (tag->string) FreeSymbol(tag->string);
592
593 FreeTag(tag->tag);
594 FreeTag(tag->tagNext);
595
596 // Clear and free the tag.
597 tag->type = kTagTypeNone;
598 tag->string = 0;
599 tag->tag = 0;
600 tag->tagNext = gTagsFree;
601 gTagsFree = tag;
602 }
603
604
605 struct Symbol {
606 long refCount;
607 struct Symbol *next;
608 char string[1];
609 };
610 typedef struct Symbol Symbol, *SymbolPtr;
611
612 static SymbolPtr FindSymbol(char *string, SymbolPtr *prevSymbol);
613
614 static SymbolPtr gSymbolsHead;
615
616
617 static char *NewSymbol(char *string)
618 {
619 SymbolPtr symbol;
620
621 // Look for string in the list of symbols.
622 symbol = FindSymbol(string, 0);
623
624 // Add the new symbol.
625 if (symbol == 0) {
626 symbol = AllocateBootXMemory(sizeof(Symbol) + strlen(string));
627 if (symbol == 0) return 0;
628
629 // Set the symbol's data.
630 symbol->refCount = 0;
631 strcpy(symbol->string, string);
632
633 // Add the symbol to the list.
634 symbol->next = gSymbolsHead;
635 gSymbolsHead = symbol;
636 }
637
638 // Update the refCount and return the string.
639 symbol->refCount++;
640 return symbol->string;
641 }
642
643
644 // currently a no-op
645 static void FreeSymbol(char *string)
646 {
647 #if 0
648 SymbolPtr symbol, prev;
649
650 // Look for string in the list of symbols.
651 symbol = FindSymbol(string, &prev);
652 if (symbol == 0) return;
653
654 // Update the refCount.
655 symbol->refCount--;
656
657 if (symbol->refCount != 0) return;
658
659 // Remove the symbol from the list.
660 if (prev != 0) prev->next = symbol->next;
661 else gSymbolsHead = symbol->next;
662
663 // Free the symbol's memory.
664 free(symbol);
665 #endif
666 }
667
668
669 static SymbolPtr FindSymbol(char *string, SymbolPtr *prevSymbol)
670 {
671 SymbolPtr symbol, prev;
672
673 symbol = gSymbolsHead;
674 prev = 0;
675
676 while (symbol != 0) {
677 if (!strcmp(symbol->string, string)) break;
678
679 prev = symbol;
680 symbol = symbol->next;
681 }
682
683 if ((symbol != 0) && (prevSymbol != 0)) *prevSymbol = prev;
684
685 return symbol;
686 }
687
688 #if PLIST_DEBUG
689 static void DumpTagDict(TagPtr tag, long depth);
690 static void DumpTagKey(TagPtr tag, long depth);
691 static void DumpTagString(TagPtr tag, long depth);
692 static void DumpTagInteger(TagPtr tag, long depth);
693 static void DumpTagData(TagPtr tag, long depth);
694 static void DumpTagDate(TagPtr tag, long depth);
695 static void DumpTagBoolean(TagPtr tag, long depth);
696 static void DumpTagArray(TagPtr tag, long depth);
697 static void DumpSpaces(long depth);
698
699 void DumpTag(TagPtr tag, long depth)
700 {
701 if (tag == 0) return;
702
703 switch (tag->type) {
704 case kTagTypeDict :
705 DumpTagDict(tag, depth);
706 break;
707
708 case kTagTypeKey :
709 DumpTagKey(tag, depth);
710 break;
711
712 case kTagTypeString :
713 DumpTagString(tag, depth);
714 break;
715
716 case kTagTypeInteger :
717 DumpTagInteger(tag, depth);
718 break;
719
720 case kTagTypeData :
721 DumpTagData(tag, depth);
722 break;
723
724 case kTagTypeDate :
725 DumpTagDate(tag, depth);
726 break;
727
728 case kTagTypeFalse :
729 case kTagTypeTrue :
730 DumpTagBoolean(tag, depth);
731 break;
732
733 case kTagTypeArray :
734 DumpTagArray(tag, depth);
735 break;
736
737 default :
738 break;
739 }
740 }
741
742
743 static void DumpTagDict(TagPtr tag, long depth)
744 {
745 TagPtr tagList;
746
747 if (tag->tag == 0) {
748 DumpSpaces(depth);
749 printf("<%s/>\n", kXMLTagDict);
750 } else {
751 DumpSpaces(depth);
752 printf("<%s>\n", kXMLTagDict);
753
754 tagList = tag->tag;
755 while (tagList) {
756 DumpTag(tagList, depth + 1);
757 tagList = tagList->tagNext;
758 }
759
760 DumpSpaces(depth);
761 printf("</%s>\n", kXMLTagDict);
762 }
763 }
764
765
766 static void DumpTagKey(TagPtr tag, long depth)
767 {
768 DumpSpaces(depth);
769 printf("<%s>%s</%s>\n", kXMLTagKey, tag->string, kXMLTagKey);
770
771 DumpTag(tag->tag, depth);
772 }
773
774
775 static void DumpTagString(TagPtr tag, long depth)
776 {
777 DumpSpaces(depth);
778 printf("<%s>%s</%s>\n", kXMLTagString, tag->string, kXMLTagString);
779 }
780
781
782 /* integers used to live as char*s but we need 64 bit ints */
783 static void DumpTagInteger(TagPtr tag, long depth)
784 {
785 DumpSpaces(depth);
786 printf("<%s>%s</%s>\n", kXMLTagInteger, tag->string, kXMLTagInteger);
787 }
788
789
790 static void DumpTagData(TagPtr tag, long depth)
791 {
792 DumpSpaces(depth);
793 printf("<%s>%x</%s>\n", kXMLTagData, tag->string, kXMLTagData);
794 }
795
796
797 static void DumpTagDate(TagPtr tag, long depth)
798 {
799 DumpSpaces(depth);
800 printf("<%s>%x</%s>\n", kXMLTagDate, tag->string, kXMLTagDate);
801 }
802
803
804 static void DumpTagBoolean(TagPtr tag, long depth)
805 {
806 DumpSpaces(depth);
807 printf("<%s>\n", (tag->type == kTagTypeTrue) ? kXMLTagTrue : kXMLTagFalse);
808 }
809
810
811 static void DumpTagArray(TagPtr tag, long depth)
812 {
813 TagPtr tagList;
814
815 if (tag->tag == 0) {
816 DumpSpaces(depth);
817 printf("<%s/>\n", kXMLTagArray);
818 } else {
819 DumpSpaces(depth);
820 printf("<%s>\n", kXMLTagArray);
821
822 tagList = tag->tag;
823 while (tagList) {
824 DumpTag(tagList, depth + 1);
825 tagList = tagList->tagNext;
826 }
827
828 DumpSpaces(depth);
829 printf("</%s>\n", kXMLTagArray);
830 }
831 }
832
833
834 static void DumpSpaces(long depth)
835 {
836 long cnt;
837
838 for (cnt = 0; cnt < (depth * 4); cnt++) putchar(' ');
839 }
840 #endif