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