Partial fix for FS#12289 - comment lines would waste lots of buffer space. Still...
[maemo-rb.git] / lib / skin_parser / skin_parser.c
blob74082529494fab1a617333bb8749b2e101529e97
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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 ****************************************************************************/
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <stdbool.h>
25 #include <string.h>
26 #include <ctype.h>
28 #include "skin_buffer.h"
29 #include "skin_parser.h"
30 #include "skin_debug.h"
31 #include "tag_table.h"
32 #include "symbols.h"
33 #include "skin_scan.h"
35 /* Global variables for the parser */
36 int skin_line = 0;
37 char* skin_start = 0;
38 int viewport_line = 0;
40 static int tag_recursion_level = 0;
42 #ifdef ROCKBOX
43 static skin_callback callback = NULL;
44 static void* callback_data;
45 #endif
47 /* Auxiliary parsing functions (not visible at global scope) */
48 static struct skin_element* skin_parse_viewport(const char** document);
49 static struct skin_element* skin_parse_line(const char** document);
50 static struct skin_element* skin_parse_line_optional(const char** document,
51 int conditional);
52 static struct skin_element* skin_parse_sublines(const char** document);
53 static struct skin_element* skin_parse_sublines_optional(const char** document,
54 int conditional);
56 static int skin_parse_tag(struct skin_element* element, const char** document);
57 static int skin_parse_text(struct skin_element* element, const char** document,
58 int conditional);
59 static int skin_parse_conditional(struct skin_element* element,
60 const char** document);
61 static int skin_parse_comment(struct skin_element* element, const char** document);
62 static struct skin_element* skin_parse_code_as_arg(const char** document);
65 static void skip_whitespace(const char** document)
67 while(**document == ' ' || **document == '\t')
68 (*document)++;
71 #ifdef ROCKBOX
72 struct skin_element* skin_parse(const char* document,
73 skin_callback cb, void* cb_data)
75 callback = cb;
76 callback_data = cb_data;
77 #else
78 struct skin_element* skin_parse(const char* document)
80 #endif
81 struct skin_element* root = NULL;
82 struct skin_element* last = NULL;
84 struct skin_element** to_write = 0;
86 const char* cursor = document; /*Keeps track of location in the document*/
88 skin_line = 1;
89 skin_start = (char*)document;
90 viewport_line = 0;
92 skin_clear_errors();
94 while(*cursor != '\0')
96 if(!root)
97 to_write = &root;
98 else
99 to_write = &(last->next);
102 *to_write = skin_parse_viewport(&cursor);
103 last = *to_write;
104 if(!last)
106 skin_free_tree(root); /* Clearing any memory already used */
107 return NULL;
110 /* Making sure last is at the end */
111 while(last->next)
112 last = last->next;
115 return root;
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 tag_recursion_level = 0;
127 retval = skin_alloc_element();
128 if (!retval)
129 return NULL;
130 retval->type = VIEWPORT;
131 retval->children_count = 1;
132 retval->line = skin_line;
133 viewport_line = skin_line;
135 struct skin_element** to_write = 0;
137 const char* cursor = *document; /* Keeps track of location in the document */
138 const char* bookmark; /* Used when we need to look ahead */
140 int sublines = 0; /* Flag for parsing sublines */
142 /* Parsing out the viewport tag if there is one */
143 if(check_viewport(cursor))
145 if (!skin_parse_tag(retval, &cursor))
146 return NULL;
147 if(*cursor == '\n')
149 cursor++;
150 skin_line++;
153 #ifdef ROCKBOX
154 else if (callback)
156 if (callback(retval, callback_data) == CALLBACK_ERROR)
157 return NULL;
159 #endif
161 if (check_viewport(cursor))
163 retval->children_count = 0;
164 *document = cursor;
165 return retval;
167 retval->children_count = 1;
168 retval->children = skin_alloc_children(1);
169 if (!retval->children)
170 return NULL;
174 /* First, we check to see if this line will contain sublines */
175 bookmark = cursor;
176 sublines = 0;
177 while(*cursor != '\n' && *cursor != '\0'
178 && !(check_viewport(cursor) && cursor != *document))
180 if(*cursor == MULTILINESYM)
182 sublines = 1;
183 break;
185 else if(*cursor == TAGSYM)
187 /* A ';' directly after a '%' doesn't count */
188 cursor ++;
190 if(*cursor == '\0')
191 break;
193 cursor++;
195 else if(*cursor == COMMENTSYM)
197 skip_comment(&cursor);
199 else if(*cursor == ARGLISTOPENSYM)
201 skip_arglist(&cursor);
203 else if(*cursor == ENUMLISTOPENSYM)
205 skip_enumlist(&cursor);
207 else
209 /* Advancing the cursor as normal */
210 cursor++;
213 cursor = bookmark;
215 if(!root)
216 to_write = &root;
217 else
218 to_write = &(last->next);
220 if(sublines)
222 *to_write = skin_parse_sublines(&cursor);
223 last = *to_write;
224 if(!last)
225 return NULL;
227 else
229 #ifdef ROCKBOX
230 /* strip all leading comments */
231 while(*cursor == '#')
233 skip_comment(&cursor);
234 skin_line++;
237 if (check_viewport(cursor))
238 break;
239 #endif
240 *to_write = skin_parse_line(&cursor);
241 last = *to_write;
242 if(!last)
243 return NULL;
246 /* Making sure last is at the end */
247 while(last->next)
248 last = last->next;
250 if(*cursor == '\n')
252 cursor++;
253 skin_line++;
255 #ifdef ROCKBOX
256 /* strip all comments */
257 while(*cursor == '#')
259 skip_comment(&cursor);
260 skin_line++;
263 if (check_viewport(cursor))
264 break;
265 #endif
268 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document));
270 *document = cursor;
272 retval->children[0] = root;
273 return retval;
276 /* Auxiliary Parsing Functions */
278 static struct skin_element* skin_parse_line(const char**document)
280 return skin_parse_line_optional(document, 0);
284 * If conditional is set to true, then this will break upon encountering
285 * SEPARATESYM. This should only be used when parsing a line inside a
286 * conditional, otherwise just use the wrapper function skin_parse_line()
288 static struct skin_element* skin_parse_line_optional(const char** document,
289 int conditional)
291 const char* cursor = *document;
293 struct skin_element* root = NULL;
294 struct skin_element* current = NULL;
295 struct skin_element* retval = NULL;
297 /* A wrapper for the line */
298 retval = skin_alloc_element();
299 if (!retval)
300 return NULL;
301 retval->type = LINE;
302 retval->line = skin_line;
303 if(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
304 && !(conditional && (*cursor == ARGLISTSEPARATESYM
305 || *cursor == ARGLISTCLOSESYM
306 || *cursor == ENUMLISTSEPARATESYM
307 || *cursor == ENUMLISTCLOSESYM)))
309 retval->children_count = 1;
311 else
313 retval->children_count = 0;
316 if(retval->children_count > 0)
318 retval->children = skin_alloc_children(1);
319 if (!retval->children)
320 return NULL;
323 #ifdef ROCKBOX
324 if (callback)
326 switch (callback(retval, callback_data))
328 case CALLBACK_ERROR:
329 return NULL;
330 default:
331 break;
334 #endif
336 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
337 && !((*cursor == ARGLISTSEPARATESYM
338 || *cursor == ARGLISTCLOSESYM
339 || *cursor == ENUMLISTSEPARATESYM
340 || *cursor == ENUMLISTCLOSESYM)
341 && conditional)
342 && !(check_viewport(cursor) && cursor != *document))
344 /* Allocating memory if necessary */
345 if(root)
347 current->next = skin_alloc_element();
348 if (!current->next)
349 return NULL;
350 current = current->next;
352 else
354 current = skin_alloc_element();
355 if (!current)
356 return NULL;
357 root = current;
360 /* Parsing the current element */
361 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
363 if(!skin_parse_conditional(current, &cursor))
364 return NULL;
366 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
368 if(!skin_parse_tag(current, &cursor))
369 return NULL;
371 else if(*cursor == COMMENTSYM)
373 if(!skin_parse_comment(current, &cursor))
374 return NULL;
376 else
378 if(!skin_parse_text(current, &cursor, conditional))
379 return NULL;
383 /* Moving up the calling function's pointer */
384 *document = cursor;
386 if(root)
387 retval->children[0] = root;
388 return retval;
391 static struct skin_element* skin_parse_sublines(const char** document)
393 return skin_parse_sublines_optional(document, 0);
396 static struct skin_element* skin_parse_sublines_optional(const char** document,
397 int conditional)
399 struct skin_element* retval;
400 const char* cursor = *document;
401 int sublines = 1;
402 int i;
404 retval = skin_alloc_element();
405 if (!retval)
406 return NULL;
407 retval->type = LINE_ALTERNATOR;
408 retval->next = NULL;
409 retval->line = skin_line;
411 /* First we count the sublines */
412 while(*cursor != '\0' && *cursor != '\n'
413 && !((*cursor == ARGLISTSEPARATESYM
414 || *cursor == ARGLISTCLOSESYM
415 || *cursor == ENUMLISTSEPARATESYM
416 || *cursor == ENUMLISTCLOSESYM)
417 && conditional)
418 && !(check_viewport(cursor) && cursor != *document))
420 if(*cursor == COMMENTSYM)
422 skip_comment(&cursor);
424 else if(*cursor == ENUMLISTOPENSYM)
426 skip_enumlist(&cursor);
428 else if(*cursor == ARGLISTOPENSYM)
430 skip_arglist(&cursor);
432 else if(*cursor == TAGSYM)
434 cursor++;
435 if(*cursor == '\0' || *cursor == '\n')
436 break;
437 cursor++;
439 else if(*cursor == MULTILINESYM)
441 sublines++;
442 cursor++;
444 else
446 cursor++;
450 /* ...and then we parse them */
451 retval->children_count = sublines;
452 retval->children = skin_alloc_children(sublines);
453 if (!retval->children)
454 return NULL;
456 cursor = *document;
457 for(i = 0; i < sublines; i++)
459 retval->children[i] = skin_parse_line_optional(&cursor, conditional);
460 skip_whitespace(&cursor);
462 if(*cursor != MULTILINESYM && i != sublines - 1)
464 skin_error(MULTILINE_EXPECTED, cursor);
465 return NULL;
467 else if(i != sublines - 1)
469 cursor++;
473 #ifdef ROCKBOX
474 if (callback)
476 if (callback(retval, callback_data) == CALLBACK_ERROR)
477 return NULL;
479 #endif
480 *document = cursor;
482 return retval;
485 static int skin_parse_tag(struct skin_element* element, const char** document)
487 const char* cursor = *document + 1;
488 const char* bookmark;
490 char tag_name[3];
491 char* tag_args;
492 const struct tag_info *tag;
494 int num_args = 1;
495 int i;
496 int star = 0; /* Flag for the all-or-none option */
498 int optional = 0;
499 tag_recursion_level++;
501 /* Checking the tag name */
502 tag_name[0] = cursor[0];
503 tag_name[1] = cursor[1];
504 tag_name[2] = '\0';
506 /* First we check the two characters after the '%', then a single char */
507 tag = find_tag(tag_name);
509 if(!tag)
511 tag_name[1] = '\0';
512 tag = find_tag(tag_name);
513 cursor++;
515 else
517 cursor += 2;
520 if(!tag)
522 skin_error(ILLEGAL_TAG, cursor);
523 return 0;
526 /* Copying basic tag info */
527 if(element->type != CONDITIONAL && element->type != VIEWPORT)
528 element->type = TAG;
529 element->tag = tag;
530 tag_args = tag->params;
531 element->line = skin_line;
533 /* Checking for the * flag */
534 if(tag_args[0] == '*')
536 star = 1;
537 tag_args++;
540 /* If this tag has no arguments, we can bail out now */
541 if(strlen(tag_args) == 0
542 || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM)
543 || (star && *cursor != ARGLISTOPENSYM))
546 #ifdef ROCKBOX
547 if (callback)
549 if (callback(element, callback_data) == CALLBACK_ERROR)
550 return 0;
552 #endif
553 *document = cursor;
554 return 1;
557 /* Checking the number of arguments and allocating args */
558 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
560 skin_error(ARGLIST_EXPECTED, cursor);
561 return 0;
563 else
565 cursor++;
568 bookmark = cursor;
569 while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM)
571 /* Skipping over escaped characters */
572 if(*cursor == TAGSYM)
574 cursor++;
575 if(*cursor == '\0')
576 break;
577 cursor++;
579 else if(*cursor == COMMENTSYM)
581 skip_comment(&cursor);
583 else if(*cursor == ARGLISTOPENSYM)
585 skip_arglist(&cursor);
587 else if(*cursor == ARGLISTSEPARATESYM)
589 num_args++;
590 cursor++;
592 else
594 cursor++;
598 cursor = bookmark; /* Restoring the cursor */
599 element->params_count = num_args;
600 element->params = skin_alloc_params(num_args, tag_recursion_level<=1);
601 if (!element->params)
602 return 0;
604 /* Now we have to actually parse each argument */
605 for(i = 0; i < num_args; i++)
607 char type_code;
608 /* Making sure we haven't run out of arguments */
609 if(*tag_args == '\0')
611 skin_error(TOO_MANY_ARGS, cursor);
612 return 0;
615 /* Checking for the optional bar */
616 if(*tag_args == '|')
618 optional = 1;
619 tag_args++;
622 /* Scanning the arguments */
623 skip_whitespace(&cursor);
625 /* Checking for comments */
626 if(*cursor == COMMENTSYM)
627 skip_comment(&cursor);
629 if (*tag_args == '[')
631 /* we need to guess which type of param it is.
632 * guess using this priority:
633 * default > decimal/integer > single tag/code > string
635 int j=0;
636 bool canbedefault = false;
637 bool haspercent = false, number = true, hasdecimal = false;
638 char temp_params[8];
639 tag_args++;
640 while (*tag_args != ']')
642 if (*tag_args >= 'a' && *tag_args <= 'z')
643 canbedefault = true;
644 temp_params[j++] = tolower(*tag_args++);
646 temp_params[j] = '\0';
647 j = 0;
648 while (cursor[j] && cursor[j] != ',' && cursor[j] != ')')
650 haspercent = haspercent || (cursor[j] == '%');
651 hasdecimal = hasdecimal || (cursor[j] == '.');
652 number = number && (isdigit(cursor[j]) ||
653 (cursor[j] == '.') ||
654 (cursor[j] == '-'));
655 j++;
657 type_code = '*';
658 if (canbedefault && *cursor == DEFAULTSYM && !isdigit(cursor[1]))
660 type_code = 'i';
662 else if (number && hasdecimal && strchr(temp_params, 'd'))
664 type_code = 'd';
666 else if (number &&
667 (strchr(temp_params, 'i') || strchr(temp_params, 'd')))
669 type_code = strchr(temp_params, 'i') ? 'i' : 'd';
671 else if (haspercent &&
672 (strchr(temp_params, 't') || strchr(temp_params, 'c')))
674 type_code = strchr(temp_params, 't') ? 't' : 'c';
676 else if (strchr(temp_params, 's'))
678 type_code = 's';
680 if (type_code == '*')
682 skin_error(INSUFFICIENT_ARGS, cursor);
683 return 0;
686 else
687 type_code = *tag_args;
688 /* Storing the type code */
689 element->params[i].type_code = type_code;
691 /* Checking a nullable argument for null. */
692 if(*cursor == DEFAULTSYM && !isdigit(cursor[1]))
694 if(islower(type_code))
696 element->params[i].type = DEFAULT;
697 cursor++;
699 else
701 skin_error(DEFAULT_NOT_ALLOWED, cursor);
702 return 0;
705 else if(tolower(type_code) == 'i')
707 /* Scanning an int argument */
708 if(!isdigit(*cursor) && *cursor != '-')
710 skin_error(INT_EXPECTED, cursor);
711 return 0;
714 element->params[i].type = INTEGER;
715 element->params[i].data.number = scan_int(&cursor);
717 else if(tolower(type_code) == 'd')
719 int val = 0;
720 bool have_point = false;
721 bool have_tenth = false;
722 while ( isdigit(*cursor) || *cursor == '.' )
724 if (*cursor != '.')
726 val *= 10;
727 val += *cursor - '0';
728 if (have_point)
730 have_tenth = true;
731 cursor++;
732 break;
735 else
736 have_point = true;
737 cursor++;
739 if (have_tenth == false)
740 val *= 10;
741 element->params[i].type = DECIMAL;
742 element->params[i].data.number = val;
744 else if(tolower(type_code) == 'n' ||
745 tolower(type_code) == 's' || tolower(type_code) == 'f')
747 /* Scanning a string argument */
748 element->params[i].type = STRING;
749 element->params[i].data.text = scan_string(&cursor);
752 else if(tolower(type_code) == 'c')
754 /* Recursively parsing a code argument */
755 element->params[i].type = CODE;
756 element->params[i].data.code = skin_parse_code_as_arg(&cursor);
757 if(!element->params[i].data.code)
758 return 0;
760 else if (tolower(type_code) == 't')
762 struct skin_element* child = skin_alloc_element();
763 child->type = TAG;
764 if (!skin_parse_tag(child, &cursor))
765 return 0;
766 child->next = NULL;
767 element->params[i].type = CODE;
768 element->params[i].data.code = child;
772 skip_whitespace(&cursor);
774 if(*cursor != ARGLISTSEPARATESYM && i < num_args - 1)
776 skin_error(SEPARATOR_EXPECTED, cursor);
777 return 0;
779 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
781 skin_error(CLOSE_EXPECTED, cursor);
782 return 0;
784 else
786 cursor++;
789 if (*tag_args != 'N')
790 tag_args++;
792 /* Checking for the optional bar */
793 if(*tag_args == '|')
795 optional = 1;
796 tag_args++;
800 /* Checking for a premature end */
801 if(*tag_args != '\0' && !optional)
803 skin_error(INSUFFICIENT_ARGS, cursor);
804 return 0;
806 #ifdef ROCKBOX
807 if (callback)
809 if (callback(element, callback_data) == CALLBACK_ERROR)
810 return 0;
812 #endif
813 *document = cursor;
814 tag_recursion_level--;
816 return 1;
820 * If the conditional flag is set true, then parsing text will stop at an
821 * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional
823 static int skin_parse_text(struct skin_element* element, const char** document,
824 int conditional)
826 const char* cursor = *document;
827 int length = 0;
828 int dest;
829 char *text = NULL;
831 /* First figure out how much text we're copying */
832 while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM
833 && *cursor != COMMENTSYM
834 && !((*cursor == ARGLISTSEPARATESYM
835 || *cursor == ARGLISTCLOSESYM
836 || *cursor == ENUMLISTSEPARATESYM
837 || *cursor == ENUMLISTCLOSESYM)
838 && conditional))
840 /* Dealing with possibility of escaped characters */
841 if(*cursor == TAGSYM)
843 if(find_escape_character(cursor[1]))
844 cursor++;
845 else
846 break;
849 length++;
850 cursor++;
853 cursor = *document;
855 /* Copying the text into the element struct */
856 element->type = TEXT;
857 element->line = skin_line;
858 element->next = NULL;
859 element->data = text = skin_alloc_string(length);
860 if (!element->data)
861 return 0;
863 for(dest = 0; dest < length; dest++)
865 /* Advancing cursor if we've encountered an escaped character */
866 if(*cursor == TAGSYM)
867 cursor++;
869 text[dest] = *cursor;
870 cursor++;
872 text[length] = '\0';
874 #ifdef ROCKBOX
875 if (callback)
877 if (callback(element, callback_data) == CALLBACK_ERROR)
878 return 0;
880 #endif
882 *document = cursor;
884 return 1;
887 static int skin_parse_conditional(struct skin_element* element, const char** document)
889 const char* cursor = *document + 1; /* Starting past the "%" */
890 const char* bookmark;
891 int children = 1;
892 int i;
894 #ifdef ROCKBOX
895 bool feature_available = true;
896 const char *false_branch = NULL;
897 const char *conditional_end = NULL;
898 #endif
900 /* Some conditional tags allow for target feature checking,
901 * so to handle that call the callback as usual with type == TAG
902 * then call it a second time with type == CONDITIONAL and check the return
903 * value */
904 element->type = TAG;
905 element->line = skin_line;
907 /* Parsing the tag first */
908 if(!skin_parse_tag(element, &cursor))
909 return 0;
911 element->type = CONDITIONAL;
912 #ifdef ROCKBOX
913 if (callback)
915 switch (callback(element, callback_data))
917 case FEATURE_NOT_AVAILABLE:
918 feature_available = false;
919 break;
920 case CALLBACK_ERROR:
921 return 0;
922 default:
923 break;
926 #endif
928 /* Counting the children */
929 if(*(cursor++) != ENUMLISTOPENSYM)
931 skin_error(ARGLIST_EXPECTED, cursor);
932 return 0;
934 bookmark = cursor;
935 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\n' && *cursor != '\0')
937 if(*cursor == COMMENTSYM)
939 skip_comment(&cursor);
941 else if(*cursor == ENUMLISTOPENSYM)
943 skip_enumlist(&cursor);
945 else if(*cursor == TAGSYM)
947 cursor++;
948 if(*cursor == '\0' || *cursor == '\n')
949 break;
950 cursor++;
952 else if(*cursor == ENUMLISTSEPARATESYM)
954 children++;
955 cursor++;
956 #ifdef ROCKBOX
957 if (false_branch == NULL && !feature_available)
959 false_branch = cursor;
960 children--;
962 #endif
964 else
966 cursor++;
969 #ifdef ROCKBOX
970 if (*cursor == ENUMLISTCLOSESYM &&
971 false_branch == NULL && !feature_available)
973 false_branch = cursor+1;
974 children--;
976 if (element->tag->flags&FEATURE_TAG)
978 if (feature_available && children > 1)
979 children--;
981 conditional_end = cursor;
982 /* if we are skipping the true branch fix that up */
983 cursor = false_branch ? false_branch : bookmark;
984 #else
985 cursor = bookmark;
986 #endif
987 /* Parsing the children */
989 /* Feature tags could end up having 0 children which breaks
990 * the render in dangerous ways. Minor hack, but insert an empty
991 * child. (e.g %?xx<foo> when xx isnt available ) */
993 if (children == 0)
995 const char* emptyline= "";
996 children = 1;
997 element->children = skin_alloc_children(children);
998 if (!element->children)
999 return 0;
1000 element->children_count = children;
1001 element->children[0] = skin_parse_code_as_arg(&emptyline);
1003 else
1005 element->children = skin_alloc_children(children);
1006 if (!element->children)
1007 return 0;
1008 element->children_count = children;
1010 for(i = 0; i < children; i++)
1012 element->children[i] = skin_parse_code_as_arg(&cursor);
1013 if (element->children[i] == NULL)
1014 return 0;
1015 skip_whitespace(&cursor);
1016 #ifdef ROCKBOX
1017 if ((element->tag->flags&FEATURE_TAG) && feature_available)
1018 cursor = conditional_end;
1019 #endif
1021 if(i < children - 1 && *cursor != ENUMLISTSEPARATESYM)
1023 skin_error(SEPARATOR_EXPECTED, cursor);
1024 return 0;
1026 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
1028 skin_error(CLOSE_EXPECTED, cursor);
1029 return 0;
1031 else
1033 cursor++;
1037 *document = cursor;
1039 return 1;
1042 static int skin_parse_comment(struct skin_element* element, const char** document)
1044 const char* cursor = *document;
1045 #ifndef ROCKBOX
1046 char* text = NULL;
1047 #endif
1048 int length;
1050 * Finding the index of the ending newline or null-terminator
1051 * The length of the string of interest doesn't include the leading #, the
1052 * length we need to reserve is the same as the index of the last character
1054 for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++);
1056 element->type = COMMENT;
1057 element->line = skin_line;
1058 #ifdef ROCKBOX
1059 element->data = NULL;
1060 #else
1061 element->data = text = skin_alloc_string(length);
1062 if (!element->data)
1063 return 0;
1064 /* We copy from one char past cursor to leave out the # */
1065 memcpy((void*)text, (void*)(cursor + 1),
1066 sizeof(char) * (length-1));
1067 text[length - 1] = '\0';
1068 #endif
1069 if(cursor[length] == '\n')
1070 skin_line++;
1072 *document += (length); /* Move cursor up past # and all text */
1073 if(**document == '\n')
1074 (*document)++;
1076 return 1;
1079 static struct skin_element* skin_parse_code_as_arg(const char** document)
1081 int sublines = 0;
1082 const char* cursor = *document;
1084 /* Checking for sublines */
1085 while(*cursor != '\n' && *cursor != '\0'
1086 && *cursor != ENUMLISTSEPARATESYM && *cursor != ARGLISTSEPARATESYM
1087 && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM)
1089 if(*cursor == MULTILINESYM)
1091 sublines = 1;
1092 break;
1094 else if(*cursor == TAGSYM)
1096 /* A ';' directly after a '%' doesn't count */
1097 cursor ++;
1099 if(*cursor == '\0')
1100 break;
1102 cursor++;
1104 else if(*cursor == ARGLISTOPENSYM)
1106 skip_arglist(&cursor);
1108 else if(*cursor == ENUMLISTOPENSYM)
1110 skip_enumlist(&cursor);
1112 else
1114 /* Advancing the cursor as normal */
1115 cursor++;
1119 if(sublines)
1120 return skin_parse_sublines_optional(document, 1);
1121 else
1122 return skin_parse_line_optional(document, 1);
1126 /* Memory management */
1127 struct skin_element* skin_alloc_element()
1129 struct skin_element* retval = (struct skin_element*)
1130 skin_buffer_alloc(sizeof(struct skin_element));
1131 if (!retval)
1132 return NULL;
1133 retval->type = UNKNOWN;
1134 retval->next = NULL;
1135 retval->tag = NULL;
1136 retval->params_count = 0;
1137 retval->children_count = 0;
1139 return retval;
1142 /* On a ROCKBOX build we try to save space as much as possible
1143 * so if we can, use a shared param pool which should be more then large
1144 * enough for any tag. params should be used straight away by the callback
1145 * so this is safe.
1147 struct skin_tag_parameter* skin_alloc_params(int count, bool use_shared_params)
1149 #ifdef ROCKBOX
1150 static struct skin_tag_parameter params[MAX_TAG_PARAMS];
1151 if (use_shared_params && count <= MAX_TAG_PARAMS)
1153 memset(params, 0, sizeof(params));
1154 return params;
1156 #endif
1157 size_t size = sizeof(struct skin_tag_parameter) * count;
1158 return (struct skin_tag_parameter*)skin_buffer_alloc(size);
1162 char* skin_alloc_string(int length)
1164 return (char*)skin_buffer_alloc(sizeof(char) * (length + 1));
1167 struct skin_element** skin_alloc_children(int count)
1169 return (struct skin_element**)
1170 skin_buffer_alloc(sizeof(struct skin_element*) * count);
1173 void skin_free_tree(struct skin_element* root)
1175 #ifndef ROCKBOX
1176 int i;
1178 /* First make the recursive call */
1179 if(!root)
1180 return;
1181 skin_free_tree(root->next);
1183 /* Free any text */
1184 if(root->type == TEXT || root->type == COMMENT)
1185 free(root->data);
1187 /* Then recursively free any children, before freeing their pointers */
1188 for(i = 0; i < root->children_count; i++)
1189 skin_free_tree(root->children[i]);
1190 if(root->children_count > 0)
1191 free(root->children);
1193 /* Free any parameters, making sure to deallocate strings */
1194 for(i = 0; i < root->params_count; i++)
1195 if(root->params[i].type == STRING)
1196 free(root->params[i].data.text);
1197 if(root->params_count > 0)
1198 free(root->params);
1200 /* Finally, delete root's memory */
1201 free(root);
1202 #else
1203 (void)root;
1204 #endif