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
)
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;
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
] == '.') ||
651 if (canbedefault
&& *cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
655 else if (number
&& hasdecimal
&& strchr(temp_params
, 'd'))
660 (strchr(temp_params
, 'i') || strchr(temp_params
, 'd')))
662 type_code
= strchr(temp_params
, 'i') ? 'i' : 'd';
664 else if (haspercent
&&
665 (strchr(temp_params
, 't') || strchr(temp_params
, 'c')))
667 type_code
= strchr(temp_params
, 't') ? 't' : 'c';
669 else if (strchr(temp_params
, 's'))
673 if (type_code
== '?')
675 skin_error(INSUFFICIENT_ARGS
, cursor
);
680 type_code
= *tag_args
;
681 /* Storing the type code */
682 params
[i
].type_code
= type_code
;
684 /* Checking a nullable argument for null. */
685 if(*cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
687 if(islower(type_code
))
689 params
[i
].type
= DEFAULT
;
694 skin_error(DEFAULT_NOT_ALLOWED
, cursor
);
698 else if(tolower(type_code
) == 'i')
700 /* Scanning an int argument */
701 if(!isdigit(*cursor
) && *cursor
!= '-')
703 skin_error(INT_EXPECTED
, cursor
);
707 params
[i
].type
= INTEGER
;
708 params
[i
].data
.number
= scan_int(&cursor
);
710 else if(tolower(type_code
) == 'd')
713 bool have_point
= false;
714 bool have_tenth
= false;
715 while ( isdigit(*cursor
) || *cursor
== '.' )
720 val
+= *cursor
- '0';
732 if (have_tenth
== false)
734 params
[i
].type
= DECIMAL
;
735 params
[i
].data
.number
= val
;
737 else if(tolower(type_code
) == 's' || tolower(type_code
) == 'f')
739 /* Scanning a string argument */
740 params
[i
].type
= STRING
;
741 params
[i
].data
.text
= skin_buffer_to_offset(scan_string(&cursor
));
744 else if(tolower(type_code
) == 'c')
746 /* Recursively parsing a code argument */
747 params
[i
].type
= CODE
;
748 params
[i
].data
.code
= skin_buffer_to_offset(skin_parse_code_as_arg(&cursor
));
749 if(params
[i
].data
.code
< 0)
752 else if (tolower(type_code
) == 't')
754 struct skin_element
* child
= skin_alloc_element();
756 if (!skin_parse_tag(child
, &cursor
))
758 child
->next
= skin_buffer_to_offset(NULL
);
759 params
[i
].type
= CODE
;
760 params
[i
].data
.code
= skin_buffer_to_offset(child
);
764 skip_whitespace(&cursor
);
766 if(*cursor
!= ARGLISTSEPARATESYM
&& i
< num_args
- 1)
768 skin_error(SEPARATOR_EXPECTED
, cursor
);
771 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
773 skin_error(CLOSE_EXPECTED
, cursor
);
781 if (*(tag_args
+ 1) == '*')
785 else if (open_square_bracket
&& *tag_args
== ']')
787 tag_args
= open_square_bracket
;
788 open_square_bracket
= NULL
;
794 /* Checking for the optional bar */
801 element
->params
= skin_buffer_to_offset(params
);
803 /* Checking for a premature end */
804 if(*tag_args
!= '\0' && !optional
)
806 skin_error(INSUFFICIENT_ARGS
, cursor
);
812 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
822 * If the conditional flag is set true, then parsing text will stop at an
823 * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional
825 static int skin_parse_text(struct skin_element
* element
, const char** document
,
828 const char* cursor
= *document
;
833 /* First figure out how much text we're copying */
834 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
835 && *cursor
!= COMMENTSYM
836 && !((*cursor
== ARGLISTSEPARATESYM
837 || *cursor
== ARGLISTCLOSESYM
838 || *cursor
== ENUMLISTSEPARATESYM
839 || *cursor
== ENUMLISTCLOSESYM
)
842 /* Dealing with possibility of escaped characters */
843 if(*cursor
== TAGSYM
)
845 if(find_escape_character(cursor
[1]))
857 /* Copying the text into the element struct */
858 element
->type
= TEXT
;
859 element
->line
= skin_line
;
860 element
->next
= skin_buffer_to_offset(NULL
);
861 text
= skin_alloc_string(length
);
862 element
->data
= skin_buffer_to_offset(text
);
863 if (element
->data
< 0)
866 for(dest
= 0; dest
< length
; dest
++)
868 /* Advancing cursor if we've encountered an escaped character */
869 if(*cursor
== TAGSYM
)
872 text
[dest
] = *cursor
;
880 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
890 static int skin_parse_conditional(struct skin_element
* element
, const char** document
)
892 const char* cursor
= *document
+ 1; /* Starting past the "%" */
893 const char* bookmark
;
898 bool feature_available
= true;
899 const char *false_branch
= NULL
;
900 const char *conditional_end
= NULL
;
902 OFFSETTYPE(struct skin_element
*)* children_array
= NULL
;
904 /* Some conditional tags allow for target feature checking,
905 * so to handle that call the callback as usual with type == TAG
906 * then call it a second time with type == CONDITIONAL and check the return
909 element
->line
= skin_line
;
911 /* Parsing the tag first */
912 if(!skin_parse_tag(element
, &cursor
))
915 element
->type
= CONDITIONAL
;
919 switch (callback(element
, callback_data
))
921 case FEATURE_NOT_AVAILABLE
:
922 feature_available
= false;
932 /* Counting the children */
933 if(*(cursor
++) != ENUMLISTOPENSYM
)
935 skin_error(ARGLIST_EXPECTED
, cursor
);
939 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\0')
941 if(*cursor
== COMMENTSYM
)
943 skip_comment(&cursor
);
945 else if(*cursor
== TAGSYM
)
949 else if(*cursor
== ENUMLISTSEPARATESYM
)
956 if (false_branch
== NULL
&& !feature_available
)
958 false_branch
= cursor
;
969 if (*cursor
== ENUMLISTCLOSESYM
&&
970 false_branch
== NULL
&& !feature_available
)
972 false_branch
= cursor
+1;
975 if (element
->tag
->flags
&FEATURE_TAG
)
977 if (feature_available
&& children
> 1)
980 conditional_end
= cursor
;
981 /* if we are skipping the true branch fix that up */
982 cursor
= false_branch
? false_branch
: bookmark
;
986 /* Parsing the children */
988 /* Feature tags could end up having 0 children which breaks
989 * the render in dangerous ways. Minor hack, but insert an empty
990 * child. (e.g %?xx<foo> when xx isnt available ) */
994 const char* emptyline
= "";
996 children_array
= skin_alloc_children(children
);
999 element
->children_count
= children
;
1000 children_array
[0] = skin_buffer_to_offset(skin_parse_code_as_arg(&emptyline
));
1004 children_array
= skin_alloc_children(children
);
1005 if (!children_array
)
1007 element
->children_count
= children
;
1009 for(i
= 0; i
< children
; i
++)
1011 if (*cursor
== '\n')
1016 children_array
[i
] = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor
));
1017 if (children_array
[i
] < 0)
1019 skip_whitespace(&cursor
);
1021 if ((element
->tag
->flags
&FEATURE_TAG
) && feature_available
)
1022 cursor
= conditional_end
;
1025 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPARATESYM
)
1027 skin_error(SEPARATOR_EXPECTED
, cursor
);
1030 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
1032 skin_error(CLOSE_EXPECTED
, cursor
);
1042 element
->children
= skin_buffer_to_offset(children_array
);
1047 static int skin_parse_comment(struct skin_element
* element
, const char** document
)
1049 const char* cursor
= *document
;
1055 * Finding the index of the ending newline or null-terminator
1056 * The length of the string of interest doesn't include the leading #, the
1057 * length we need to reserve is the same as the index of the last character
1059 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
1061 element
->type
= COMMENT
;
1062 element
->line
= skin_line
;
1064 element
->data
= INVALID_OFFSET
;
1066 element
->data
= text
= skin_alloc_string(length
);
1069 /* We copy from one char past cursor to leave out the # */
1070 memcpy((void*)text
, (void*)(cursor
+ 1),
1071 sizeof(char) * (length
-1));
1072 text
[length
- 1] = '\0';
1074 if(cursor
[length
] == '\n')
1077 *document
+= (length
); /* Move cursor up past # and all text */
1078 if(**document
== '\n')
1084 static struct skin_element
* skin_parse_code_as_arg(const char** document
)
1087 const char* cursor
= *document
;
1089 /* Checking for sublines */
1090 while(*cursor
!= '\n' && *cursor
!= '\0'
1091 && *cursor
!= ENUMLISTSEPARATESYM
&& *cursor
!= ARGLISTSEPARATESYM
1092 && *cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= ARGLISTCLOSESYM
)
1094 if(*cursor
== MULTILINESYM
)
1099 else if(*cursor
== TAGSYM
)
1105 /* Advancing the cursor as normal */
1111 return skin_parse_sublines_optional(document
, 1);
1113 return skin_parse_line_optional(document
, 1);
1116 /* Memory management */
1117 struct skin_element
* skin_alloc_element()
1119 struct skin_element
* retval
= (struct skin_element
*)
1120 skin_buffer_alloc(sizeof(struct skin_element
));
1123 retval
->type
= UNKNOWN
;
1124 retval
->next
= skin_buffer_to_offset(NULL
);
1125 retval
->params
= skin_buffer_to_offset(NULL
);
1127 retval
->params_count
= 0;
1128 retval
->children_count
= 0;
1129 retval
->data
= INVALID_OFFSET
;
1134 /* On a ROCKBOX build we try to save space as much as possible
1135 * so if we can, use a shared param pool which should be more then large
1136 * enough for any tag. params should be used straight away by the callback
1139 struct skin_tag_parameter
* skin_alloc_params(int count
)
1141 size_t size
= sizeof(struct skin_tag_parameter
) * count
;
1142 return (struct skin_tag_parameter
*)skin_buffer_alloc(size
);
1146 char* skin_alloc_string(int length
)
1148 return (char*)skin_buffer_alloc(sizeof(char) * (length
+ 1));
1151 OFFSETTYPE(struct skin_element
*)* skin_alloc_children(int count
)
1153 return (OFFSETTYPE(struct skin_element
*)*)
1154 skin_buffer_alloc(sizeof(struct skin_element
*) * count
);
1157 void skin_free_tree(struct skin_element
* root
)
1162 /* First make the recursive call */
1165 skin_free_tree(root
->next
);
1168 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
1171 /* Then recursively free any children, before freeing their pointers */
1172 for(i
= 0; i
< root
->children_count
; i
++)
1173 skin_free_tree(root
->children
[i
]);
1174 if(root
->children_count
> 0)
1175 free(root
->children
);
1177 /* Free any parameters, making sure to deallocate strings */
1178 for(i
= 0; i
< root
->params_count
; i
++)
1179 if(root
->params
[i
].type
== STRING
)
1180 free(root
->params
[i
].data
.text
);
1181 if(root
->params_count
> 0)
1184 /* Finally, delete root's memory */