1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2010 Robert Bieber
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
28 #include "skin_buffer.h"
29 #include "skin_parser.h"
30 #include "skin_debug.h"
31 #include "tag_table.h"
33 #include "skin_scan.h"
35 /* Global variables for the parser */
38 int viewport_line
= 0;
41 static skin_callback callback
= NULL
;
42 static void* callback_data
;
45 /* Auxiliary parsing functions (not visible at global scope) */
46 static struct skin_element
* skin_parse_viewport(const char** document
);
47 static struct skin_element
* skin_parse_line(const char** document
);
48 static struct skin_element
* skin_parse_line_optional(const char** document
,
50 static struct skin_element
* skin_parse_sublines(const char** document
);
51 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
54 static int skin_parse_tag(struct skin_element
* element
, const char** document
);
55 static int skin_parse_text(struct skin_element
* element
, const char** document
,
57 static int skin_parse_conditional(struct skin_element
* element
,
58 const char** document
);
59 static int skin_parse_comment(struct skin_element
* element
, const char** document
);
60 static struct skin_element
* skin_parse_code_as_arg(const char** document
);
63 static void skip_whitespace(const char** document
)
65 while(**document
== ' ' || **document
== '\t')
70 struct skin_element
* skin_parse(const char* document
,
71 skin_callback cb
, void* cb_data
)
74 callback_data
= cb_data
;
76 struct skin_element
* skin_parse(const char* document
)
79 struct skin_element
* root
= NULL
;
80 struct skin_element
* last
= NULL
;
82 const char* cursor
= document
; /*Keeps track of location in the document*/
85 skin_start
= (char*)document
;
90 while(*cursor
!= '\0')
92 struct skin_element
* tree
= skin_parse_viewport(&cursor
);
100 last
->next
= skin_buffer_to_offset(tree
);
106 skin_free_tree(root
); /* Clearing any memory already used */
110 /* Making sure last is at the end */
111 while(IS_VALID_OFFSET(last
->next
))
112 last
= skin_buffer_from_offset(last
->next
);
119 static struct skin_element
* skin_parse_viewport(const char** document
)
121 struct skin_element
* root
= NULL
;
122 struct skin_element
* last
= NULL
;
123 struct skin_element
* retval
= NULL
;
125 retval
= skin_alloc_element();
128 retval
->type
= VIEWPORT
;
129 retval
->children_count
= 1;
130 retval
->line
= skin_line
;
131 viewport_line
= skin_line
;
133 OFFSETTYPE(struct skin_element
*)* children
;
135 const char* cursor
= *document
; /* Keeps track of location in the document */
136 const char* bookmark
; /* Used when we need to look ahead */
138 int sublines
= 0; /* Flag for parsing sublines */
140 /* Parsing out the viewport tag if there is one */
141 if(check_viewport(cursor
))
143 if (!skin_parse_tag(retval
, &cursor
))
154 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
159 if (check_viewport(cursor
))
161 retval
->children_count
= 0;
165 retval
->children_count
= 1;
166 children
= skin_alloc_children(1);
172 /* First, we check to see if this line will contain sublines */
175 while(*cursor
!= '\n' && *cursor
!= '\0'
176 && !(check_viewport(cursor
) && cursor
!= *document
))
178 if(*cursor
== MULTILINESYM
)
183 else if(*cursor
== TAGSYM
)
187 else if(*cursor
== COMMENTSYM
)
189 skip_comment(&cursor
);
193 /* Advancing the cursor as normal */
201 struct skin_element
* out
= skin_parse_sublines(&cursor
);
209 last
->next
= skin_buffer_to_offset(out
);
218 /* strip all leading comments */
219 while(*cursor
== '#')
221 skip_comment(&cursor
);
225 if (check_viewport(cursor
))
229 struct skin_element
* out
= skin_parse_line(&cursor
);
237 last
->next
= skin_buffer_to_offset(out
);
244 /* Making sure last is at the end */
245 while(IS_VALID_OFFSET(last
->next
))
246 last
= skin_buffer_from_offset(last
->next
);
254 /* strip all comments */
255 while(*cursor
== '#')
257 skip_comment(&cursor
);
261 if (check_viewport(cursor
))
266 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
270 children
[0] = skin_buffer_to_offset(root
);
271 retval
->children
= skin_buffer_to_offset(children
);
275 /* Auxiliary Parsing Functions */
277 static struct skin_element
* skin_parse_line(const char**document
)
279 return skin_parse_line_optional(document
, 0);
283 * If conditional is set to true, then this will break upon encountering
284 * SEPARATESYM. This should only be used when parsing a line inside a
285 * conditional, otherwise just use the wrapper function skin_parse_line()
287 static struct skin_element
* skin_parse_line_optional(const char** document
,
290 const char* cursor
= *document
;
292 struct skin_element
* root
= NULL
;
293 struct skin_element
* current
= NULL
;
294 struct skin_element
* retval
= NULL
;
295 OFFSETTYPE(struct skin_element
*)* children
= NULL
;
297 /* A wrapper for the line */
298 retval
= skin_alloc_element();
302 retval
->line
= skin_line
;
303 while (*cursor
== '\t')
306 if(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
307 && !(conditional
&& (*cursor
== ARGLISTSEPARATESYM
308 || *cursor
== ARGLISTCLOSESYM
309 || *cursor
== ENUMLISTSEPARATESYM
310 || *cursor
== ENUMLISTCLOSESYM
)))
312 retval
->children_count
= 1;
316 retval
->children_count
= 0;
319 if(retval
->children_count
> 0)
321 children
= skin_alloc_children(1);
329 switch (callback(retval
, callback_data
))
339 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
340 && !((*cursor
== ARGLISTSEPARATESYM
341 || *cursor
== ARGLISTCLOSESYM
342 || *cursor
== ENUMLISTSEPARATESYM
343 || *cursor
== ENUMLISTCLOSESYM
)
345 && !(check_viewport(cursor
) && cursor
!= *document
))
347 /* Allocating memory if necessary */
350 struct skin_element
*next
= skin_alloc_element();
353 current
->next
= skin_buffer_to_offset(next
);
358 current
= skin_alloc_element();
364 /* Parsing the current element */
365 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
367 if(!skin_parse_conditional(current
, &cursor
))
370 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
372 if(!skin_parse_tag(current
, &cursor
))
375 else if(*cursor
== COMMENTSYM
)
377 if(!skin_parse_comment(current
, &cursor
))
382 if(!skin_parse_text(current
, &cursor
, conditional
))
387 /* Moving up the calling function's pointer */
392 children
[0] = skin_buffer_to_offset(root
);
393 retval
->children
= skin_buffer_to_offset(children
);
398 static struct skin_element
* skin_parse_sublines(const char** document
)
400 return skin_parse_sublines_optional(document
, 0);
403 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
406 struct skin_element
* retval
;
407 OFFSETTYPE(struct skin_element
*)* children
;
408 const char* cursor
= *document
;
412 retval
= skin_alloc_element();
415 retval
->type
= LINE_ALTERNATOR
;
416 retval
->next
= skin_buffer_to_offset(NULL
);
417 retval
->line
= skin_line
;
418 while (*cursor
== '\t')
421 /* First we count the sublines */
422 while(*cursor
!= '\0' && *cursor
!= '\n'
423 && !((*cursor
== ARGLISTSEPARATESYM
424 || *cursor
== ARGLISTCLOSESYM
425 || *cursor
== ENUMLISTSEPARATESYM
426 || *cursor
== ENUMLISTCLOSESYM
)
428 && !(check_viewport(cursor
) && cursor
!= *document
))
430 if(*cursor
== COMMENTSYM
)
432 skip_comment(&cursor
);
434 else if(*cursor
== TAGSYM
)
438 else if(*cursor
== MULTILINESYM
)
449 /* ...and then we parse them */
450 retval
->children_count
= sublines
;
451 children
= skin_alloc_children(sublines
);
456 for(i
= 0; i
< sublines
; i
++)
458 children
[i
] = skin_buffer_to_offset(skin_parse_line_optional(&cursor
, conditional
));
461 skip_whitespace(&cursor
);
463 if(*cursor
!= MULTILINESYM
&& i
!= sublines
- 1)
465 skin_error(MULTILINE_EXPECTED
, cursor
);
468 else if(i
!= sublines
- 1)
477 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
482 retval
->children
= skin_buffer_to_offset(children
);
487 static int skin_parse_tag(struct skin_element
* element
, const char** document
)
489 const char* cursor
= *document
+ 1;
490 const char* bookmark
;
491 char *open_square_bracket
= NULL
;
493 char tag_name
[MAX_TAG_LENGTH
];
495 const struct tag_info
*tag
;
496 struct skin_tag_parameter
* params
= NULL
;
500 int qmark
= 0; /* Flag for the all-or-none option */
504 /* Checking the tag name */
505 for (i
=0; cursor
[i
] && i
<MAX_TAG_LENGTH
; i
++)
506 tag_name
[i
] = cursor
[i
];
508 /* First we check the two characters after the '%', then a single char */
511 while (!tag
&& i
> 1)
513 tag_name
[i
-1] = '\0';
514 tag
= find_tag(tag_name
);
520 skin_error(ILLEGAL_TAG
, cursor
);
525 /* Copying basic tag info */
526 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
529 tag_args
= tag
->params
;
530 element
->line
= skin_line
;
532 /* Checking for the * flag */
533 if(tag_args
[0] == '?')
539 /* If this tag has no arguments, we can bail out now */
540 if(strlen(tag_args
) == 0
541 || (tag_args
[0] == '|' && *cursor
!= ARGLISTOPENSYM
)
542 || (qmark
&& *cursor
!= ARGLISTOPENSYM
))
548 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
556 /* Checking the number of arguments and allocating args */
557 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
559 skin_error(ARGLIST_EXPECTED
, cursor
);
568 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
570 /* Skipping over escaped characters */
571 if(*cursor
== TAGSYM
&& *(cursor
+1) != ARGLISTSEPARATESYM
)
575 else if(*cursor
== COMMENTSYM
)
577 skip_comment(&cursor
);
579 else if(*cursor
== ARGLISTSEPARATESYM
)
590 cursor
= bookmark
; /* Restoring the cursor */
591 element
->params_count
= num_args
;
592 params
= skin_alloc_params(num_args
);
596 /* Now we have to actually parse each argument */
597 for(i
= 0; i
< num_args
; i
++)
600 /* Making sure we haven't run out of arguments */
601 if(*tag_args
== '\0')
603 skin_error(TOO_MANY_ARGS
, cursor
);
607 /* Checking for the optional bar */
614 /* Scanning the arguments */
615 skip_whitespace(&cursor
);
617 /* Checking for comments */
618 if(*cursor
== COMMENTSYM
)
619 skip_comment(&cursor
);
621 if (*tag_args
== '[')
623 /* we need to guess which type of param it is.
624 * guess using this priority:
625 * default > decimal/integer > single tag/code > string
628 bool canbedefault
= false, last_char_is_percent
= false;
629 bool haspercent
= false, number
= true, hasdecimal
= false;
631 open_square_bracket
= tag_args
;
633 while (*tag_args
!= ']')
635 if (*tag_args
>= 'a' && *tag_args
<= 'z')
637 temp_params
[j
++] = tolower(*tag_args
++);
639 temp_params
[j
] = '\0';
641 while (cursor
[j
] && cursor
[j
] != ',' && cursor
[j
] != ')')
643 haspercent
= haspercent
|| (cursor
[j
] == '%');
644 hasdecimal
= hasdecimal
|| (cursor
[j
] == '.');
645 number
= number
&& (isdigit(cursor
[j
]) ||
646 (cursor
[j
] == '.') ||
647 (cursor
[j
] == '-') ||
651 last_char_is_percent
= cursor
[j
-1] == '%';
653 if (canbedefault
&& *cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
657 else if (number
&& hasdecimal
&& strchr(temp_params
, 'd'))
661 else if (number
&& last_char_is_percent
&& strchr(temp_params
, 'p'))
666 (strchr(temp_params
, 'i') || strchr(temp_params
, 'd')))
668 type_code
= strchr(temp_params
, 'i') ? 'i' : 'd';
670 else if (haspercent
&&
671 (strchr(temp_params
, 't') || strchr(temp_params
, 'c')))
673 type_code
= strchr(temp_params
, 't') ? 't' : 'c';
675 else if (strchr(temp_params
, 's'))
679 if (type_code
== '?')
681 skin_error(INSUFFICIENT_ARGS
, cursor
);
686 type_code
= *tag_args
;
687 /* Storing the type code */
688 params
[i
].type_code
= type_code
;
690 /* Checking a nullable argument for null. */
691 if(*cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
693 if(islower(type_code
))
695 params
[i
].type
= DEFAULT
;
700 skin_error(DEFAULT_NOT_ALLOWED
, cursor
);
704 else if(tolower(type_code
) == 'i')
706 /* Scanning an int argument */
707 if(!isdigit(*cursor
) && *cursor
!= '-')
709 skin_error(INT_EXPECTED
, cursor
);
713 params
[i
].type
= INTEGER
;
714 params
[i
].data
.number
= scan_int(&cursor
);
716 else if(tolower(type_code
) == 'd' || tolower(type_code
) == 'p')
719 bool have_point
= false;
720 bool have_tenth
= false;
721 while ( isdigit(*cursor
) || *cursor
== '.' )
726 val
+= *cursor
- '0';
738 if (have_tenth
== false)
740 if (tolower(type_code
) == 'd')
741 params
[i
].type
= DECIMAL
;
744 params
[i
].type
= PERCENT
;
745 cursor
++; /* skip trailing % sign */
747 params
[i
].data
.number
= val
;
749 else if(tolower(type_code
) == 's' || tolower(type_code
) == 'f')
751 /* Scanning a string argument */
752 params
[i
].type
= STRING
;
753 params
[i
].data
.text
= skin_buffer_to_offset(scan_string(&cursor
));
756 else if(tolower(type_code
) == 'c')
758 /* Recursively parsing a code argument */
759 params
[i
].type
= CODE
;
760 params
[i
].data
.code
= skin_buffer_to_offset(skin_parse_code_as_arg(&cursor
));
761 if(params
[i
].data
.code
< 0)
764 else if (tolower(type_code
) == 't')
766 struct skin_element
* child
= skin_alloc_element();
768 if (!skin_parse_tag(child
, &cursor
))
770 child
->next
= skin_buffer_to_offset(NULL
);
771 params
[i
].type
= CODE
;
772 params
[i
].data
.code
= skin_buffer_to_offset(child
);
776 skip_whitespace(&cursor
);
778 if(*cursor
!= ARGLISTSEPARATESYM
&& i
< num_args
- 1)
780 skin_error(SEPARATOR_EXPECTED
, cursor
);
783 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
785 skin_error(CLOSE_EXPECTED
, cursor
);
793 if (*(tag_args
+ 1) == '*')
797 else if (open_square_bracket
&& *tag_args
== ']')
799 tag_args
= open_square_bracket
;
800 open_square_bracket
= NULL
;
806 /* Checking for the optional bar */
813 element
->params
= skin_buffer_to_offset(params
);
815 /* Checking for a premature end */
816 if(*tag_args
!= '\0' && !optional
)
818 skin_error(INSUFFICIENT_ARGS
, cursor
);
824 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
834 * If the conditional flag is set true, then parsing text will stop at an
835 * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional
837 static int skin_parse_text(struct skin_element
* element
, const char** document
,
840 const char* cursor
= *document
;
845 /* First figure out how much text we're copying */
846 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
847 && *cursor
!= COMMENTSYM
848 && !((*cursor
== ARGLISTSEPARATESYM
849 || *cursor
== ARGLISTCLOSESYM
850 || *cursor
== ENUMLISTSEPARATESYM
851 || *cursor
== ENUMLISTCLOSESYM
)
854 /* Dealing with possibility of escaped characters */
855 if(*cursor
== TAGSYM
)
857 if(find_escape_character(cursor
[1]))
869 /* Copying the text into the element struct */
870 element
->type
= TEXT
;
871 element
->line
= skin_line
;
872 element
->next
= skin_buffer_to_offset(NULL
);
873 text
= skin_alloc_string(length
);
874 element
->data
= skin_buffer_to_offset(text
);
875 if (element
->data
< 0)
878 for(dest
= 0; dest
< length
; dest
++)
880 /* Advancing cursor if we've encountered an escaped character */
881 if(*cursor
== TAGSYM
)
884 text
[dest
] = *cursor
;
892 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
902 static int skin_parse_conditional(struct skin_element
* element
, const char** document
)
904 const char* cursor
= *document
+ 1; /* Starting past the "%" */
905 const char* bookmark
;
910 bool feature_available
= true;
911 const char *false_branch
= NULL
;
912 const char *conditional_end
= NULL
;
914 OFFSETTYPE(struct skin_element
*)* children_array
= NULL
;
916 /* Some conditional tags allow for target feature checking,
917 * so to handle that call the callback as usual with type == TAG
918 * then call it a second time with type == CONDITIONAL and check the return
921 element
->line
= skin_line
;
923 /* Parsing the tag first */
924 if(!skin_parse_tag(element
, &cursor
))
927 element
->type
= CONDITIONAL
;
931 switch (callback(element
, callback_data
))
933 case FEATURE_NOT_AVAILABLE
:
934 feature_available
= false;
944 /* Counting the children */
945 if(*(cursor
++) != ENUMLISTOPENSYM
)
947 skin_error(ARGLIST_EXPECTED
, cursor
);
951 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\0')
953 if(*cursor
== COMMENTSYM
)
955 skip_comment(&cursor
);
957 else if(*cursor
== TAGSYM
)
961 else if(*cursor
== ENUMLISTSEPARATESYM
)
968 if (false_branch
== NULL
&& !feature_available
)
970 false_branch
= cursor
;
981 if (*cursor
== ENUMLISTCLOSESYM
&&
982 false_branch
== NULL
&& !feature_available
)
984 false_branch
= cursor
+1;
987 if (element
->tag
->flags
&FEATURE_TAG
)
989 if (feature_available
&& children
> 1)
992 conditional_end
= cursor
;
993 /* if we are skipping the true branch fix that up */
994 cursor
= false_branch
? false_branch
: bookmark
;
998 /* Parsing the children */
1000 /* Feature tags could end up having 0 children which breaks
1001 * the render in dangerous ways. Minor hack, but insert an empty
1002 * child. (e.g %?xx<foo> when xx isnt available ) */
1006 const char* emptyline
= "";
1008 children_array
= skin_alloc_children(children
);
1009 if (!children_array
)
1011 element
->children_count
= children
;
1012 children_array
[0] = skin_buffer_to_offset(skin_parse_code_as_arg(&emptyline
));
1016 children_array
= skin_alloc_children(children
);
1017 if (!children_array
)
1019 element
->children_count
= children
;
1021 for(i
= 0; i
< children
; i
++)
1023 if (*cursor
== '\n')
1028 children_array
[i
] = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor
));
1029 if (children_array
[i
] < 0)
1031 skip_whitespace(&cursor
);
1033 if ((element
->tag
->flags
&FEATURE_TAG
) && feature_available
)
1034 cursor
= conditional_end
;
1037 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPARATESYM
)
1039 skin_error(SEPARATOR_EXPECTED
, cursor
);
1042 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
1044 skin_error(CLOSE_EXPECTED
, cursor
);
1054 element
->children
= skin_buffer_to_offset(children_array
);
1059 static int skin_parse_comment(struct skin_element
* element
, const char** document
)
1061 const char* cursor
= *document
;
1067 * Finding the index of the ending newline or null-terminator
1068 * The length of the string of interest doesn't include the leading #, the
1069 * length we need to reserve is the same as the index of the last character
1071 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
1073 element
->type
= COMMENT
;
1074 element
->line
= skin_line
;
1076 element
->data
= INVALID_OFFSET
;
1078 element
->data
= text
= skin_alloc_string(length
);
1081 /* We copy from one char past cursor to leave out the # */
1082 memcpy((void*)text
, (void*)(cursor
+ 1),
1083 sizeof(char) * (length
-1));
1084 text
[length
- 1] = '\0';
1086 if(cursor
[length
] == '\n')
1089 *document
+= (length
); /* Move cursor up past # and all text */
1090 if(**document
== '\n')
1096 static struct skin_element
* skin_parse_code_as_arg(const char** document
)
1099 const char* cursor
= *document
;
1101 /* Checking for sublines */
1102 while(*cursor
!= '\n' && *cursor
!= '\0'
1103 && *cursor
!= ENUMLISTSEPARATESYM
&& *cursor
!= ARGLISTSEPARATESYM
1104 && *cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= ARGLISTCLOSESYM
)
1106 if(*cursor
== MULTILINESYM
)
1111 else if(*cursor
== TAGSYM
)
1117 /* Advancing the cursor as normal */
1123 return skin_parse_sublines_optional(document
, 1);
1125 return skin_parse_line_optional(document
, 1);
1128 /* Memory management */
1129 struct skin_element
* skin_alloc_element()
1131 struct skin_element
* retval
= (struct skin_element
*)
1132 skin_buffer_alloc(sizeof(struct skin_element
));
1135 retval
->type
= UNKNOWN
;
1136 retval
->next
= skin_buffer_to_offset(NULL
);
1137 retval
->params
= skin_buffer_to_offset(NULL
);
1139 retval
->params_count
= 0;
1140 retval
->children_count
= 0;
1141 retval
->data
= INVALID_OFFSET
;
1146 /* On a ROCKBOX build we try to save space as much as possible
1147 * so if we can, use a shared param pool which should be more then large
1148 * enough for any tag. params should be used straight away by the callback
1151 struct skin_tag_parameter
* skin_alloc_params(int count
)
1153 size_t size
= sizeof(struct skin_tag_parameter
) * count
;
1154 return (struct skin_tag_parameter
*)skin_buffer_alloc(size
);
1158 char* skin_alloc_string(int length
)
1160 return (char*)skin_buffer_alloc(sizeof(char) * (length
+ 1));
1163 OFFSETTYPE(struct skin_element
*)* skin_alloc_children(int count
)
1165 return (OFFSETTYPE(struct skin_element
*)*)
1166 skin_buffer_alloc(sizeof(struct skin_element
*) * count
);
1169 void skin_free_tree(struct skin_element
* root
)
1174 /* First make the recursive call */
1177 skin_free_tree(root
->next
);
1180 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
1183 /* Then recursively free any children, before freeing their pointers */
1184 for(i
= 0; i
< root
->children_count
; i
++)
1185 skin_free_tree(root
->children
[i
]);
1186 if(root
->children_count
> 0)
1187 free(root
->children
);
1189 /* Free any parameters, making sure to deallocate strings */
1190 for(i
= 0; i
< root
->params_count
; i
++)
1191 if(root
->params
[i
].type
== STRING
)
1192 free(root
->params
[i
].data
.text
);
1193 if(root
->params_count
> 0)
1196 /* Finally, delete root's memory */