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;
40 static int tag_recursion_level
= 0;
43 static skin_callback callback
= NULL
;
44 static void* callback_data
;
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
,
52 static struct skin_element
* skin_parse_sublines(const char** document
);
53 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
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
,
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')
72 struct skin_element
* skin_parse(const char* document
,
73 skin_callback cb
, void* cb_data
)
76 callback_data
= cb_data
;
78 struct skin_element
* skin_parse(const char* document
)
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*/
89 skin_start
= (char*)document
;
94 while(*cursor
!= '\0')
99 to_write
= &(last
->next
);
102 *to_write
= skin_parse_viewport(&cursor
);
106 skin_free_tree(root
); /* Clearing any memory already used */
110 /* Making sure last is at the end */
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();
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
))
156 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
161 if (check_viewport(cursor
))
163 retval
->children_count
= 0;
167 retval
->children_count
= 1;
168 retval
->children
= skin_alloc_children(1);
169 if (!retval
->children
)
174 /* First, we check to see if this line will contain sublines */
177 while(*cursor
!= '\n' && *cursor
!= '\0'
178 && !(check_viewport(cursor
) && cursor
!= *document
))
180 if(*cursor
== MULTILINESYM
)
185 else if(*cursor
== TAGSYM
)
187 /* A ';' directly after a '%' doesn't count */
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
);
209 /* Advancing the cursor as normal */
218 to_write
= &(last
->next
);
222 *to_write
= skin_parse_sublines(&cursor
);
230 /* strip all leading comments */
231 while(*cursor
== '#')
233 skip_comment(&cursor
);
237 if (check_viewport(cursor
))
240 *to_write
= skin_parse_line(&cursor
);
246 /* Making sure last is at the end */
256 /* strip all comments */
257 while(*cursor
== '#')
259 skip_comment(&cursor
);
263 if (check_viewport(cursor
))
268 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
272 retval
->children
[0] = root
;
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
,
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();
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;
313 retval
->children_count
= 0;
316 if(retval
->children_count
> 0)
318 retval
->children
= skin_alloc_children(1);
319 if (!retval
->children
)
326 switch (callback(retval
, callback_data
))
336 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
337 && !((*cursor
== ARGLISTSEPARATESYM
338 || *cursor
== ARGLISTCLOSESYM
339 || *cursor
== ENUMLISTSEPARATESYM
340 || *cursor
== ENUMLISTCLOSESYM
)
342 && !(check_viewport(cursor
) && cursor
!= *document
))
344 /* Allocating memory if necessary */
347 current
->next
= skin_alloc_element();
350 current
= current
->next
;
354 current
= skin_alloc_element();
360 /* Parsing the current element */
361 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
363 if(!skin_parse_conditional(current
, &cursor
))
366 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
368 if(!skin_parse_tag(current
, &cursor
))
371 else if(*cursor
== COMMENTSYM
)
373 if(!skin_parse_comment(current
, &cursor
))
378 if(!skin_parse_text(current
, &cursor
, conditional
))
383 /* Moving up the calling function's pointer */
387 retval
->children
[0] = root
;
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
,
399 struct skin_element
* retval
;
400 const char* cursor
= *document
;
404 retval
= skin_alloc_element();
407 retval
->type
= LINE_ALTERNATOR
;
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
)
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
)
435 if(*cursor
== '\0' || *cursor
== '\n')
439 else if(*cursor
== MULTILINESYM
)
450 /* ...and then we parse them */
451 retval
->children_count
= sublines
;
452 retval
->children
= skin_alloc_children(sublines
);
453 if (!retval
->children
)
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
);
467 else if(i
!= sublines
- 1)
476 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
485 static int skin_parse_tag(struct skin_element
* element
, const char** document
)
487 const char* cursor
= *document
+ 1;
488 const char* bookmark
;
492 const struct tag_info
*tag
;
496 int star
= 0; /* Flag for the all-or-none option */
499 tag_recursion_level
++;
501 /* Checking the tag name */
502 tag_name
[0] = cursor
[0];
503 tag_name
[1] = cursor
[1];
506 /* First we check the two characters after the '%', then a single char */
507 tag
= find_tag(tag_name
);
512 tag
= find_tag(tag_name
);
522 skin_error(ILLEGAL_TAG
, cursor
);
526 /* Copying basic tag info */
527 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
530 tag_args
= tag
->params
;
531 element
->line
= skin_line
;
533 /* Checking for the * flag */
534 if(tag_args
[0] == '*')
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
))
549 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
557 /* Checking the number of arguments and allocating args */
558 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
560 skin_error(ARGLIST_EXPECTED
, cursor
);
569 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
571 /* Skipping over escaped characters */
572 if(*cursor
== TAGSYM
)
579 else if(*cursor
== COMMENTSYM
)
581 skip_comment(&cursor
);
583 else if(*cursor
== ARGLISTOPENSYM
)
585 skip_arglist(&cursor
);
587 else if(*cursor
== ARGLISTSEPARATESYM
)
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
)
604 /* Now we have to actually parse each argument */
605 for(i
= 0; i
< num_args
; i
++)
608 /* Making sure we haven't run out of arguments */
609 if(*tag_args
== '\0')
611 skin_error(TOO_MANY_ARGS
, cursor
);
615 /* Checking for the optional bar */
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
636 bool canbedefault
= false;
637 bool haspercent
= false, number
= true, hasdecimal
= false;
640 while (*tag_args
!= ']')
642 if (*tag_args
>= 'a' && *tag_args
<= 'z')
644 temp_params
[j
++] = tolower(*tag_args
++);
646 temp_params
[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
] == '.') ||
658 if (canbedefault
&& *cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
662 else if (number
&& hasdecimal
&& strchr(temp_params
, 'd'))
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'))
680 if (type_code
== '*')
682 skin_error(INSUFFICIENT_ARGS
, cursor
);
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
;
701 skin_error(DEFAULT_NOT_ALLOWED
, cursor
);
705 else if(tolower(type_code
) == 'i')
707 /* Scanning an int argument */
708 if(!isdigit(*cursor
) && *cursor
!= '-')
710 skin_error(INT_EXPECTED
, cursor
);
714 element
->params
[i
].type
= INTEGER
;
715 element
->params
[i
].data
.number
= scan_int(&cursor
);
717 else if(tolower(type_code
) == 'd')
720 bool have_point
= false;
721 bool have_tenth
= false;
722 while ( isdigit(*cursor
) || *cursor
== '.' )
727 val
+= *cursor
- '0';
739 if (have_tenth
== false)
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
)
760 else if (tolower(type_code
) == 't')
762 struct skin_element
* child
= skin_alloc_element();
764 if (!skin_parse_tag(child
, &cursor
))
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
);
779 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
781 skin_error(CLOSE_EXPECTED
, cursor
);
789 if (*tag_args
!= 'N')
792 /* Checking for the optional bar */
800 /* Checking for a premature end */
801 if(*tag_args
!= '\0' && !optional
)
803 skin_error(INSUFFICIENT_ARGS
, cursor
);
809 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
814 tag_recursion_level
--;
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
,
826 const char* cursor
= *document
;
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
)
840 /* Dealing with possibility of escaped characters */
841 if(*cursor
== TAGSYM
)
843 if(find_escape_character(cursor
[1]))
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
);
863 for(dest
= 0; dest
< length
; dest
++)
865 /* Advancing cursor if we've encountered an escaped character */
866 if(*cursor
== TAGSYM
)
869 text
[dest
] = *cursor
;
877 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
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
;
895 bool feature_available
= true;
896 const char *false_branch
= NULL
;
897 const char *conditional_end
= NULL
;
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
905 element
->line
= skin_line
;
907 /* Parsing the tag first */
908 if(!skin_parse_tag(element
, &cursor
))
911 element
->type
= CONDITIONAL
;
915 switch (callback(element
, callback_data
))
917 case FEATURE_NOT_AVAILABLE
:
918 feature_available
= false;
928 /* Counting the children */
929 if(*(cursor
++) != ENUMLISTOPENSYM
)
931 skin_error(ARGLIST_EXPECTED
, 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
)
948 if(*cursor
== '\0' || *cursor
== '\n')
952 else if(*cursor
== ENUMLISTSEPARATESYM
)
957 if (false_branch
== NULL
&& !feature_available
)
959 false_branch
= cursor
;
970 if (*cursor
== ENUMLISTCLOSESYM
&&
971 false_branch
== NULL
&& !feature_available
)
973 false_branch
= cursor
+1;
976 if (element
->tag
->flags
&FEATURE_TAG
)
978 if (feature_available
&& children
> 1)
981 conditional_end
= cursor
;
982 /* if we are skipping the true branch fix that up */
983 cursor
= false_branch
? false_branch
: bookmark
;
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 ) */
995 const char* emptyline
= "";
997 element
->children
= skin_alloc_children(children
);
998 if (!element
->children
)
1000 element
->children_count
= children
;
1001 element
->children
[0] = skin_parse_code_as_arg(&emptyline
);
1005 element
->children
= skin_alloc_children(children
);
1006 if (!element
->children
)
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
)
1015 skip_whitespace(&cursor
);
1017 if ((element
->tag
->flags
&FEATURE_TAG
) && feature_available
)
1018 cursor
= conditional_end
;
1021 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPARATESYM
)
1023 skin_error(SEPARATOR_EXPECTED
, cursor
);
1026 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
1028 skin_error(CLOSE_EXPECTED
, cursor
);
1042 static int skin_parse_comment(struct skin_element
* element
, const char** document
)
1044 const char* cursor
= *document
;
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
;
1059 element
->data
= NULL
;
1061 element
->data
= text
= skin_alloc_string(length
);
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';
1069 if(cursor
[length
] == '\n')
1072 *document
+= (length
); /* Move cursor up past # and all text */
1073 if(**document
== '\n')
1079 static struct skin_element
* skin_parse_code_as_arg(const char** document
)
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
)
1094 else if(*cursor
== TAGSYM
)
1096 /* A ';' directly after a '%' doesn't count */
1104 else if(*cursor
== ARGLISTOPENSYM
)
1106 skip_arglist(&cursor
);
1108 else if(*cursor
== ENUMLISTOPENSYM
)
1110 skip_enumlist(&cursor
);
1114 /* Advancing the cursor as normal */
1120 return skin_parse_sublines_optional(document
, 1);
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
));
1133 retval
->type
= UNKNOWN
;
1134 retval
->next
= NULL
;
1136 retval
->params_count
= 0;
1137 retval
->children_count
= 0;
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
1147 struct skin_tag_parameter
* skin_alloc_params(int count
, bool use_shared_params
)
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
));
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
)
1178 /* First make the recursive call */
1181 skin_free_tree(root
->next
);
1184 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
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)
1200 /* Finally, delete root's memory */