skin_engine: rework the parser to be closer to the langauge grammar.
[maemo-rb.git] / lib / skin_parser / skin_parser.c
blob44a1c0324545f52451306d10fef889c3464b9d41
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 #ifdef ROCKBOX
41 static skin_callback callback = NULL;
42 static void* callback_data;
43 #endif
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,
49 int conditional);
50 static struct skin_element* skin_parse_sublines(const char** document);
51 static struct skin_element* skin_parse_sublines_optional(const char** document,
52 int conditional);
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,
56 int conditional);
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')
66 (*document)++;
69 #ifdef ROCKBOX
70 struct skin_element* skin_parse(const char* document,
71 skin_callback cb, void* cb_data)
73 callback = cb;
74 callback_data = cb_data;
75 #else
76 struct skin_element* skin_parse(const char* document)
78 #endif
79 struct skin_element* root = NULL;
80 struct skin_element* last = NULL;
82 const char* cursor = document; /*Keeps track of location in the document*/
84 skin_line = 1;
85 skin_start = (char*)document;
86 viewport_line = 0;
88 skin_clear_errors();
90 while(*cursor != '\0')
92 struct skin_element* tree = skin_parse_viewport(&cursor);
93 if(!root)
95 root = tree;
96 last = root;
98 else
100 last->next = skin_buffer_to_offset(tree);
101 last = tree;
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(IS_VALID_OFFSET(last->next))
112 last = skin_buffer_from_offset(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 retval = skin_alloc_element();
126 if (!retval)
127 return NULL;
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))
144 return NULL;
145 if(*cursor == '\n')
147 cursor++;
148 skin_line++;
151 #ifdef ROCKBOX
152 else if (callback)
154 if (callback(retval, callback_data) == CALLBACK_ERROR)
155 return NULL;
157 #endif
159 if (check_viewport(cursor))
161 retval->children_count = 0;
162 *document = cursor;
163 return retval;
165 retval->children_count = 1;
166 children = skin_alloc_children(1);
167 if (!children)
168 return NULL;
172 /* First, we check to see if this line will contain sublines */
173 bookmark = cursor;
174 sublines = 0;
175 while(*cursor != '\n' && *cursor != '\0'
176 && !(check_viewport(cursor) && cursor != *document))
178 if(*cursor == MULTILINESYM)
180 sublines = 1;
181 break;
183 else if(*cursor == TAGSYM)
185 skip_tag(&cursor);
187 else if(*cursor == COMMENTSYM)
189 skip_comment(&cursor);
191 else
193 /* Advancing the cursor as normal */
194 cursor++;
197 cursor = bookmark;
199 if(sublines)
201 struct skin_element* out = skin_parse_sublines(&cursor);
202 if (!root)
204 root = out;
205 last = root;
207 else
209 last->next = skin_buffer_to_offset(out);
210 last = out;
212 if(!last)
213 return NULL;
215 else
217 #ifdef ROCKBOX
218 /* strip all leading comments */
219 while(*cursor == '#')
221 skip_comment(&cursor);
222 skin_line++;
225 if (check_viewport(cursor))
226 break;
227 #endif
229 struct skin_element* out = skin_parse_line(&cursor);
230 if (!root)
232 root = out;
233 last = root;
235 else
237 last->next = skin_buffer_to_offset(out);
238 last = out;
240 if(!last)
241 return NULL;
244 /* Making sure last is at the end */
245 while(IS_VALID_OFFSET(last->next))
246 last = skin_buffer_from_offset(last->next);
248 if(*cursor == '\n')
250 cursor++;
251 skin_line++;
253 #ifdef ROCKBOX
254 /* strip all comments */
255 while(*cursor == '#')
257 skip_comment(&cursor);
258 skin_line++;
261 if (check_viewport(cursor))
262 break;
263 #endif
266 while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document));
268 *document = cursor;
270 children[0] = skin_buffer_to_offset(root);
271 retval->children = skin_buffer_to_offset(children);
272 return retval;
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,
288 int conditional)
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();
299 if (!retval)
300 return NULL;
301 retval->type = LINE;
302 retval->line = skin_line;
303 while (*cursor == '\t')
304 cursor++;
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;
314 else
316 retval->children_count = 0;
319 if(retval->children_count > 0)
321 children = skin_alloc_children(1);
322 if (!children)
323 return NULL;
326 #ifdef ROCKBOX
327 if (callback)
329 switch (callback(retval, callback_data))
331 case CALLBACK_ERROR:
332 return NULL;
333 default:
334 break;
337 #endif
339 while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM
340 && !((*cursor == ARGLISTSEPARATESYM
341 || *cursor == ARGLISTCLOSESYM
342 || *cursor == ENUMLISTSEPARATESYM
343 || *cursor == ENUMLISTCLOSESYM)
344 && conditional)
345 && !(check_viewport(cursor) && cursor != *document))
347 /* Allocating memory if necessary */
348 if(root)
350 struct skin_element *next = skin_alloc_element();
351 if (!next)
352 return NULL;
353 current->next = skin_buffer_to_offset(next);
354 current = next;
356 else
358 current = skin_alloc_element();
359 if (!current)
360 return NULL;
361 root = current;
364 /* Parsing the current element */
365 if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM)
367 if(!skin_parse_conditional(current, &cursor))
368 return NULL;
370 else if(*cursor == TAGSYM && !find_escape_character(cursor[1]))
372 if(!skin_parse_tag(current, &cursor))
373 return NULL;
375 else if(*cursor == COMMENTSYM)
377 if(!skin_parse_comment(current, &cursor))
378 return NULL;
380 else
382 if(!skin_parse_text(current, &cursor, conditional))
383 return NULL;
387 /* Moving up the calling function's pointer */
388 *document = cursor;
390 if(root)
392 children[0] = skin_buffer_to_offset(root);
393 retval->children = skin_buffer_to_offset(children);
395 return retval;
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,
404 int conditional)
406 struct skin_element* retval;
407 OFFSETTYPE(struct skin_element*)* children;
408 const char* cursor = *document;
409 int sublines = 1;
410 int i;
412 retval = skin_alloc_element();
413 if (!retval)
414 return NULL;
415 retval->type = LINE_ALTERNATOR;
416 retval->next = skin_buffer_to_offset(NULL);
417 retval->line = skin_line;
418 while (*cursor == '\t')
419 cursor++;
421 /* First we count the sublines */
422 while(*cursor != '\0' && *cursor != '\n'
423 && !((*cursor == ARGLISTSEPARATESYM
424 || *cursor == ARGLISTCLOSESYM
425 || *cursor == ENUMLISTSEPARATESYM
426 || *cursor == ENUMLISTCLOSESYM)
427 && conditional)
428 && !(check_viewport(cursor) && cursor != *document))
430 if(*cursor == COMMENTSYM)
432 skip_comment(&cursor);
434 else if(*cursor == TAGSYM)
436 skip_tag(&cursor);
438 else if(*cursor == MULTILINESYM)
440 sublines++;
441 cursor++;
443 else
445 cursor++;
449 /* ...and then we parse them */
450 retval->children_count = sublines;
451 children = skin_alloc_children(sublines);
452 if (!children)
453 return NULL;
455 cursor = *document;
456 for(i = 0; i < sublines; i++)
458 children[i] = skin_buffer_to_offset(skin_parse_line_optional(&cursor, conditional));
459 if (children[i] < 0)
460 return NULL;
461 skip_whitespace(&cursor);
463 if(*cursor != MULTILINESYM && i != sublines - 1)
465 skin_error(MULTILINE_EXPECTED, cursor);
466 return NULL;
468 else if(i != sublines - 1)
470 cursor++;
474 #ifdef ROCKBOX
475 if (callback)
477 if (callback(retval, callback_data) == CALLBACK_ERROR)
478 return NULL;
480 #endif
481 *document = cursor;
482 retval->children = skin_buffer_to_offset(children);
484 return retval;
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];
494 char* tag_args;
495 const struct tag_info *tag;
496 struct skin_tag_parameter* params = NULL;
498 int num_args = 1;
499 int i;
500 int qmark = 0; /* Flag for the all-or-none option */
502 int optional = 0;
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 */
509 tag = NULL;
510 i = MAX_TAG_LENGTH;
511 while (!tag && i > 1)
513 tag_name[i-1] = '\0';
514 tag = find_tag(tag_name);
515 i--;
518 if(!tag)
520 skin_error(ILLEGAL_TAG, cursor);
521 return 0;
523 cursor += i;
525 /* Copying basic tag info */
526 if(element->type != CONDITIONAL && element->type != VIEWPORT)
527 element->type = TAG;
528 element->tag = tag;
529 tag_args = tag->params;
530 element->line = skin_line;
532 /* Checking for the * flag */
533 if(tag_args[0] == '?')
535 qmark = 1;
536 tag_args++;
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))
545 #ifdef ROCKBOX
546 if (callback)
548 if (callback(element, callback_data) == CALLBACK_ERROR)
549 return 0;
551 #endif
552 *document = cursor;
553 return 1;
556 /* Checking the number of arguments and allocating args */
557 if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|')
559 skin_error(ARGLIST_EXPECTED, cursor);
560 return 0;
562 else
564 cursor++;
567 bookmark = cursor;
568 while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM)
570 /* Skipping over escaped characters */
571 if(*cursor == TAGSYM)
573 skip_tag(&cursor);
575 else if(*cursor == COMMENTSYM)
577 skip_comment(&cursor);
579 else if(*cursor == ARGLISTSEPARATESYM)
581 num_args++;
582 cursor++;
584 else
586 cursor++;
590 cursor = bookmark; /* Restoring the cursor */
591 element->params_count = num_args;
592 params = skin_alloc_params(num_args);
593 if (!params)
594 return 0;
596 /* Now we have to actually parse each argument */
597 for(i = 0; i < num_args; i++)
599 char type_code;
600 /* Making sure we haven't run out of arguments */
601 if(*tag_args == '\0')
603 skin_error(TOO_MANY_ARGS, cursor);
604 return 0;
607 /* Checking for the optional bar */
608 if(*tag_args == '|')
610 optional = 1;
611 tag_args++;
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
627 int j=0;
628 bool canbedefault = false;
629 bool haspercent = false, number = true, hasdecimal = false;
630 char temp_params[8];
631 open_square_bracket = tag_args;
632 tag_args++;
633 while (*tag_args != ']')
635 if (*tag_args >= 'a' && *tag_args <= 'z')
636 canbedefault = true;
637 temp_params[j++] = tolower(*tag_args++);
639 temp_params[j] = '\0';
640 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] == '-'));
648 j++;
650 type_code = '?';
651 if (canbedefault && *cursor == DEFAULTSYM && !isdigit(cursor[1]))
653 type_code = 'i';
655 else if (number && hasdecimal && strchr(temp_params, 'd'))
657 type_code = 'd';
659 else if (number &&
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'))
671 type_code = 's';
673 if (type_code == '?')
675 skin_error(INSUFFICIENT_ARGS, cursor);
676 return 0;
679 else
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;
690 cursor++;
692 else
694 skin_error(DEFAULT_NOT_ALLOWED, cursor);
695 return 0;
698 else if(tolower(type_code) == 'i')
700 /* Scanning an int argument */
701 if(!isdigit(*cursor) && *cursor != '-')
703 skin_error(INT_EXPECTED, cursor);
704 return 0;
707 params[i].type = INTEGER;
708 params[i].data.number = scan_int(&cursor);
710 else if(tolower(type_code) == 'd')
712 int val = 0;
713 bool have_point = false;
714 bool have_tenth = false;
715 while ( isdigit(*cursor) || *cursor == '.' )
717 if (*cursor != '.')
719 val *= 10;
720 val += *cursor - '0';
721 if (have_point)
723 have_tenth = true;
724 cursor++;
725 break;
728 else
729 have_point = true;
730 cursor++;
732 if (have_tenth == false)
733 val *= 10;
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)
750 return 0;
752 else if (tolower(type_code) == 't')
754 struct skin_element* child = skin_alloc_element();
755 child->type = TAG;
756 if (!skin_parse_tag(child, &cursor))
757 return 0;
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);
769 return 0;
771 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
773 skin_error(CLOSE_EXPECTED, cursor);
774 return 0;
776 else
778 cursor++;
781 if (*(tag_args + 1) == '*')
783 if (i+1 == num_args)
784 tag_args += 2;
785 else if (open_square_bracket)
787 tag_args = open_square_bracket;
788 open_square_bracket = NULL;
791 else
792 tag_args++;
794 /* Checking for the optional bar */
795 if(*tag_args == '|')
797 optional = 1;
798 tag_args++;
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);
807 return 0;
809 #ifdef ROCKBOX
810 if (callback)
812 if (callback(element, callback_data) == CALLBACK_ERROR)
813 return 0;
815 #endif
816 *document = cursor;
818 return 1;
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,
826 int conditional)
828 const char* cursor = *document;
829 int length = 0;
830 int dest;
831 char *text = NULL;
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)
840 && conditional))
842 /* Dealing with possibility of escaped characters */
843 if(*cursor == TAGSYM)
845 if(find_escape_character(cursor[1]))
846 cursor++;
847 else
848 break;
851 length++;
852 cursor++;
855 cursor = *document;
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)
864 return 0;
866 for(dest = 0; dest < length; dest++)
868 /* Advancing cursor if we've encountered an escaped character */
869 if(*cursor == TAGSYM)
870 cursor++;
872 text[dest] = *cursor;
873 cursor++;
875 text[length] = '\0';
877 #ifdef ROCKBOX
878 if (callback)
880 if (callback(element, callback_data) == CALLBACK_ERROR)
881 return 0;
883 #endif
885 *document = cursor;
887 return 1;
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;
894 int children = 1;
895 int i;
897 #ifdef ROCKBOX
898 bool feature_available = true;
899 const char *false_branch = NULL;
900 const char *conditional_end = NULL;
901 #endif
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
907 * value */
908 element->type = TAG;
909 element->line = skin_line;
911 /* Parsing the tag first */
912 if(!skin_parse_tag(element, &cursor))
913 return 0;
915 element->type = CONDITIONAL;
916 #ifdef ROCKBOX
917 if (callback)
919 switch (callback(element, callback_data))
921 case FEATURE_NOT_AVAILABLE:
922 feature_available = false;
923 break;
924 case CALLBACK_ERROR:
925 return 0;
926 default:
927 break;
930 #endif
932 /* Counting the children */
933 if(*(cursor++) != ENUMLISTOPENSYM)
935 skin_error(ARGLIST_EXPECTED, cursor);
936 return 0;
938 bookmark = cursor;
939 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\0')
941 if(*cursor == COMMENTSYM)
943 skip_comment(&cursor);
945 else if(*cursor == TAGSYM)
947 skip_tag(&cursor);
949 else if(*cursor == ENUMLISTSEPARATESYM)
951 children++;
952 cursor++;
953 if (*cursor == '\n')
954 cursor++;
955 #ifdef ROCKBOX
956 if (false_branch == NULL && !feature_available)
958 false_branch = cursor;
959 children--;
961 #endif
963 else
965 cursor++;
968 #ifdef ROCKBOX
969 if (*cursor == ENUMLISTCLOSESYM &&
970 false_branch == NULL && !feature_available)
972 false_branch = cursor+1;
973 children--;
975 if (element->tag->flags&FEATURE_TAG)
977 if (feature_available && children > 1)
978 children--;
980 conditional_end = cursor;
981 /* if we are skipping the true branch fix that up */
982 cursor = false_branch ? false_branch : bookmark;
983 #else
984 cursor = bookmark;
985 #endif
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 ) */
992 if (children == 0)
994 const char* emptyline= "";
995 children = 1;
996 children_array = skin_alloc_children(children);
997 if (!children_array)
998 return 0;
999 element->children_count = children;
1000 children_array[0] = skin_buffer_to_offset(skin_parse_code_as_arg(&emptyline));
1002 else
1004 children_array = skin_alloc_children(children);
1005 if (!children_array)
1006 return 0;
1007 element->children_count = children;
1009 for(i = 0; i < children; i++)
1011 if (*cursor == '\n')
1013 skin_line++;
1014 cursor++;
1016 children_array[i] = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor));
1017 if (children_array[i] < 0)
1018 return 0;
1019 skip_whitespace(&cursor);
1020 #ifdef ROCKBOX
1021 if ((element->tag->flags&FEATURE_TAG) && feature_available)
1022 cursor = conditional_end;
1023 #endif
1025 if(i < children - 1 && *cursor != ENUMLISTSEPARATESYM)
1027 skin_error(SEPARATOR_EXPECTED, cursor);
1028 return 0;
1030 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
1032 skin_error(CLOSE_EXPECTED, cursor);
1033 return 0;
1035 else
1037 cursor++;
1041 *document = cursor;
1042 element->children = skin_buffer_to_offset(children_array);
1044 return 1;
1047 static int skin_parse_comment(struct skin_element* element, const char** document)
1049 const char* cursor = *document;
1050 #ifndef ROCKBOX
1051 char* text = NULL;
1052 #endif
1053 int length;
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;
1063 #ifdef ROCKBOX
1064 element->data = INVALID_OFFSET;
1065 #else
1066 element->data = text = skin_alloc_string(length);
1067 if (!element->data)
1068 return 0;
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';
1073 #endif
1074 if(cursor[length] == '\n')
1075 skin_line++;
1077 *document += (length); /* Move cursor up past # and all text */
1078 if(**document == '\n')
1079 (*document)++;
1081 return 1;
1084 static struct skin_element* skin_parse_code_as_arg(const char** document)
1086 int sublines = 0;
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)
1096 sublines = 1;
1097 break;
1099 else if(*cursor == TAGSYM)
1101 skip_tag(&cursor);
1103 else
1105 /* Advancing the cursor as normal */
1106 cursor++;
1110 if(sublines)
1111 return skin_parse_sublines_optional(document, 1);
1112 else
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));
1121 if (!retval)
1122 return NULL;
1123 retval->type = UNKNOWN;
1124 retval->next = skin_buffer_to_offset(NULL);
1125 retval->params = skin_buffer_to_offset(NULL);
1126 retval->tag = NULL;
1127 retval->params_count = 0;
1128 retval->children_count = 0;
1129 retval->data = INVALID_OFFSET;
1131 return retval;
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
1137 * so this is safe.
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)
1159 #ifndef ROCKBOX
1160 int i;
1162 /* First make the recursive call */
1163 if(!root)
1164 return;
1165 skin_free_tree(root->next);
1167 /* Free any text */
1168 if(root->type == TEXT || root->type == COMMENT)
1169 free(root->data);
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)
1182 free(root->params);
1184 /* Finally, delete root's memory */
1185 free(root);
1186 #else
1187 (void)root;
1188 #endif