Prepare new maemo release
[maemo-rb.git] / lib / skin_parser / skin_parser.c
blob1f4b87a32823597565fa9c56d5911a6ab7b80895
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 && *(cursor+1) != ARGLISTSEPARATESYM)
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, last_char_is_percent = 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 (cursor[j] == '%'));
649 j++;
651 last_char_is_percent = cursor[j-1] == '%';
652 type_code = '?';
653 if (canbedefault && *cursor == DEFAULTSYM && !isdigit(cursor[1]))
655 type_code = 'i';
657 else if (number && hasdecimal && strchr(temp_params, 'd'))
659 type_code = 'd';
661 else if (number && last_char_is_percent && strchr(temp_params, 'p'))
663 type_code = 'p';
665 else if (number &&
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'))
677 type_code = 's';
679 if (type_code == '?')
681 skin_error(INSUFFICIENT_ARGS, cursor);
682 return 0;
685 else
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;
696 cursor++;
698 else
700 skin_error(DEFAULT_NOT_ALLOWED, cursor);
701 return 0;
704 else if(tolower(type_code) == 'i')
706 /* Scanning an int argument */
707 if(!isdigit(*cursor) && *cursor != '-')
709 skin_error(INT_EXPECTED, cursor);
710 return 0;
713 params[i].type = INTEGER;
714 params[i].data.number = scan_int(&cursor);
716 else if(tolower(type_code) == 'd' || tolower(type_code) == 'p')
718 int val = 0;
719 bool have_point = false;
720 bool have_tenth = false;
721 while ( isdigit(*cursor) || *cursor == '.' )
723 if (*cursor != '.')
725 val *= 10;
726 val += *cursor - '0';
727 if (have_point)
729 have_tenth = true;
730 cursor++;
731 break;
734 else
735 have_point = true;
736 cursor++;
738 if (have_tenth == false)
739 val *= 10;
740 if (tolower(type_code) == 'd')
741 params[i].type = DECIMAL;
742 else
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)
762 return 0;
764 else if (tolower(type_code) == 't')
766 struct skin_element* child = skin_alloc_element();
767 child->type = TAG;
768 if (!skin_parse_tag(child, &cursor))
769 return 0;
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);
781 return 0;
783 else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1)
785 skin_error(CLOSE_EXPECTED, cursor);
786 return 0;
788 else
790 cursor++;
793 if (*(tag_args + 1) == '*')
795 if (i+1 == num_args)
796 tag_args += 2;
797 else if (open_square_bracket && *tag_args == ']')
799 tag_args = open_square_bracket;
800 open_square_bracket = NULL;
803 else
804 tag_args++;
806 /* Checking for the optional bar */
807 if(*tag_args == '|')
809 optional = 1;
810 tag_args++;
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);
819 return 0;
821 #ifdef ROCKBOX
822 if (callback)
824 if (callback(element, callback_data) == CALLBACK_ERROR)
825 return 0;
827 #endif
828 *document = cursor;
830 return 1;
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,
838 int conditional)
840 const char* cursor = *document;
841 int length = 0;
842 int dest;
843 char *text = NULL;
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)
852 && conditional))
854 /* Dealing with possibility of escaped characters */
855 if(*cursor == TAGSYM)
857 if(find_escape_character(cursor[1]))
858 cursor++;
859 else
860 break;
863 length++;
864 cursor++;
867 cursor = *document;
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)
876 return 0;
878 for(dest = 0; dest < length; dest++)
880 /* Advancing cursor if we've encountered an escaped character */
881 if(*cursor == TAGSYM)
882 cursor++;
884 text[dest] = *cursor;
885 cursor++;
887 text[length] = '\0';
889 #ifdef ROCKBOX
890 if (callback)
892 if (callback(element, callback_data) == CALLBACK_ERROR)
893 return 0;
895 #endif
897 *document = cursor;
899 return 1;
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;
906 int children = 1;
907 int i;
909 #ifdef ROCKBOX
910 bool feature_available = true;
911 const char *false_branch = NULL;
912 const char *conditional_end = NULL;
913 #endif
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
919 * value */
920 element->type = TAG;
921 element->line = skin_line;
923 /* Parsing the tag first */
924 if(!skin_parse_tag(element, &cursor))
925 return 0;
927 element->type = CONDITIONAL;
928 #ifdef ROCKBOX
929 if (callback)
931 switch (callback(element, callback_data))
933 case FEATURE_NOT_AVAILABLE:
934 feature_available = false;
935 break;
936 case CALLBACK_ERROR:
937 return 0;
938 default:
939 break;
942 #endif
944 /* Counting the children */
945 if(*(cursor++) != ENUMLISTOPENSYM)
947 skin_error(ARGLIST_EXPECTED, cursor);
948 return 0;
950 bookmark = cursor;
951 while(*cursor != ENUMLISTCLOSESYM && *cursor != '\0')
953 if(*cursor == COMMENTSYM)
955 skip_comment(&cursor);
957 else if(*cursor == TAGSYM)
959 skip_tag(&cursor);
961 else if(*cursor == ENUMLISTSEPARATESYM)
963 children++;
964 cursor++;
965 if (*cursor == '\n')
966 cursor++;
967 #ifdef ROCKBOX
968 if (false_branch == NULL && !feature_available)
970 false_branch = cursor;
971 children--;
973 #endif
975 else
977 cursor++;
980 #ifdef ROCKBOX
981 if (*cursor == ENUMLISTCLOSESYM &&
982 false_branch == NULL && !feature_available)
984 false_branch = cursor+1;
985 children--;
987 if (element->tag->flags&FEATURE_TAG)
989 if (feature_available && children > 1)
990 children--;
992 conditional_end = cursor;
993 /* if we are skipping the true branch fix that up */
994 cursor = false_branch ? false_branch : bookmark;
995 #else
996 cursor = bookmark;
997 #endif
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 ) */
1004 if (children == 0)
1006 const char* emptyline= "";
1007 children = 1;
1008 children_array = skin_alloc_children(children);
1009 if (!children_array)
1010 return 0;
1011 element->children_count = children;
1012 children_array[0] = skin_buffer_to_offset(skin_parse_code_as_arg(&emptyline));
1014 else
1016 children_array = skin_alloc_children(children);
1017 if (!children_array)
1018 return 0;
1019 element->children_count = children;
1021 for(i = 0; i < children; i++)
1023 if (*cursor == '\n')
1025 skin_line++;
1026 cursor++;
1028 children_array[i] = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor));
1029 if (children_array[i] < 0)
1030 return 0;
1031 skip_whitespace(&cursor);
1032 #ifdef ROCKBOX
1033 if ((element->tag->flags&FEATURE_TAG) && feature_available)
1034 cursor = conditional_end;
1035 #endif
1037 if(i < children - 1 && *cursor != ENUMLISTSEPARATESYM)
1039 skin_error(SEPARATOR_EXPECTED, cursor);
1040 return 0;
1042 else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM)
1044 skin_error(CLOSE_EXPECTED, cursor);
1045 return 0;
1047 else
1049 cursor++;
1053 *document = cursor;
1054 element->children = skin_buffer_to_offset(children_array);
1056 return 1;
1059 static int skin_parse_comment(struct skin_element* element, const char** document)
1061 const char* cursor = *document;
1062 #ifndef ROCKBOX
1063 char* text = NULL;
1064 #endif
1065 int length;
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;
1075 #ifdef ROCKBOX
1076 element->data = INVALID_OFFSET;
1077 #else
1078 element->data = text = skin_alloc_string(length);
1079 if (!element->data)
1080 return 0;
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';
1085 #endif
1086 if(cursor[length] == '\n')
1087 skin_line++;
1089 *document += (length); /* Move cursor up past # and all text */
1090 if(**document == '\n')
1091 (*document)++;
1093 return 1;
1096 static struct skin_element* skin_parse_code_as_arg(const char** document)
1098 int sublines = 0;
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)
1108 sublines = 1;
1109 break;
1111 else if(*cursor == TAGSYM)
1113 skip_tag(&cursor);
1115 else
1117 /* Advancing the cursor as normal */
1118 cursor++;
1122 if(sublines)
1123 return skin_parse_sublines_optional(document, 1);
1124 else
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));
1133 if (!retval)
1134 return NULL;
1135 retval->type = UNKNOWN;
1136 retval->next = skin_buffer_to_offset(NULL);
1137 retval->params = skin_buffer_to_offset(NULL);
1138 retval->tag = NULL;
1139 retval->params_count = 0;
1140 retval->children_count = 0;
1141 retval->data = INVALID_OFFSET;
1143 return retval;
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
1149 * so this is safe.
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)
1171 #ifndef ROCKBOX
1172 int i;
1174 /* First make the recursive call */
1175 if(!root)
1176 return;
1177 skin_free_tree(root->next);
1179 /* Free any text */
1180 if(root->type == TEXT || root->type == COMMENT)
1181 free(root->data);
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)
1194 free(root->params);
1196 /* Finally, delete root's memory */
1197 free(root);
1198 #else
1199 (void)root;
1200 #endif