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 ****************************************************************************/
29 #include "skin_buffer.h"
30 #include "skin_parser.h"
31 #include "skin_debug.h"
32 #include "tag_table.h"
34 #include "skin_scan.h"
36 /* Global variables for the parser */
39 int viewport_line
= 0;
41 static int tag_recursion_level
= 0;
44 static skin_callback callback
= NULL
;
45 static void* callback_data
;
48 /* Auxiliary parsing functions (not visible at global scope) */
49 static struct skin_element
* skin_parse_viewport(const char** document
);
50 static struct skin_element
* skin_parse_line(const char** document
);
51 static struct skin_element
* skin_parse_line_optional(const char** document
,
53 static struct skin_element
* skin_parse_sublines(const char** document
);
54 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
57 static int skin_parse_tag(struct skin_element
* element
, const char** document
);
58 static int skin_parse_text(struct skin_element
* element
, const char** document
,
60 static int skin_parse_conditional(struct skin_element
* element
,
61 const char** document
);
62 static int skin_parse_comment(struct skin_element
* element
, const char** document
);
63 static struct skin_element
* skin_parse_code_as_arg(const char** document
);
66 static void skip_whitespace(const char** document
)
68 while(**document
== ' ' || **document
== '\t')
73 struct skin_element
* skin_parse(const char* document
,
74 skin_callback cb
, void* cb_data
)
77 callback_data
= cb_data
;
79 struct skin_element
* skin_parse(const char* document
)
82 struct skin_element
* root
= NULL
;
83 struct skin_element
* last
= NULL
;
85 struct skin_element
** to_write
= 0;
87 const char* cursor
= document
; /*Keeps track of location in the document*/
90 skin_start
= (char*)document
;
95 while(*cursor
!= '\0')
101 to_write
= &(last
->next
);
104 *to_write
= skin_parse_viewport(&cursor
);
108 skin_free_tree(root
); /* Clearing any memory already used */
112 /* Making sure last is at the end */
121 static struct skin_element
* skin_parse_viewport(const char** document
)
123 struct skin_element
* root
= NULL
;
124 struct skin_element
* last
= NULL
;
125 struct skin_element
* retval
= NULL
;
127 tag_recursion_level
= 0;
129 retval
= skin_alloc_element();
132 retval
->type
= VIEWPORT
;
133 retval
->children_count
= 1;
134 retval
->line
= skin_line
;
135 viewport_line
= skin_line
;
137 struct skin_element
** to_write
= 0;
139 const char* cursor
= *document
; /* Keeps track of location in the document */
140 const char* bookmark
; /* Used when we need to look ahead */
142 int sublines
= 0; /* Flag for parsing sublines */
144 /* Parsing out the viewport tag if there is one */
145 if(check_viewport(cursor
))
147 if (!skin_parse_tag(retval
, &cursor
))
158 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
163 if (check_viewport(cursor
))
165 retval
->children_count
= 0;
169 retval
->children_count
= 1;
170 retval
->children
= skin_alloc_children(1);
171 if (!retval
->children
)
176 /* First, we check to see if this line will contain sublines */
179 while(*cursor
!= '\n' && *cursor
!= '\0'
180 && !(check_viewport(cursor
) && cursor
!= *document
))
182 if(*cursor
== MULTILINESYM
)
187 else if(*cursor
== TAGSYM
)
189 /* A ';' directly after a '%' doesn't count */
197 else if(*cursor
== COMMENTSYM
)
199 skip_comment(&cursor
);
201 else if(*cursor
== ARGLISTOPENSYM
)
203 skip_arglist(&cursor
);
205 else if(*cursor
== ENUMLISTOPENSYM
)
207 skip_enumlist(&cursor
);
211 /* Advancing the cursor as normal */
220 to_write
= &(last
->next
);
224 *to_write
= skin_parse_sublines(&cursor
);
232 *to_write
= skin_parse_line(&cursor
);
238 /* Making sure last is at the end */
248 while(*cursor
!= '\0' && !(check_viewport(cursor
) && cursor
!= *document
));
252 retval
->children
[0] = root
;
256 /* Auxiliary Parsing Functions */
258 static struct skin_element
* skin_parse_line(const char**document
)
260 return skin_parse_line_optional(document
, 0);
264 * If conditional is set to true, then this will break upon encountering
265 * SEPARATESYM. This should only be used when parsing a line inside a
266 * conditional, otherwise just use the wrapper function skin_parse_line()
268 static struct skin_element
* skin_parse_line_optional(const char** document
,
271 const char* cursor
= *document
;
273 struct skin_element
* root
= NULL
;
274 struct skin_element
* current
= NULL
;
275 struct skin_element
* retval
= NULL
;
277 /* A wrapper for the line */
278 retval
= skin_alloc_element();
282 retval
->line
= skin_line
;
283 if(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
284 && !(conditional
&& (*cursor
== ARGLISTSEPARATESYM
285 || *cursor
== ARGLISTCLOSESYM
286 || *cursor
== ENUMLISTSEPARATESYM
287 || *cursor
== ENUMLISTCLOSESYM
)))
289 retval
->children_count
= 1;
293 retval
->children_count
= 0;
296 if(retval
->children_count
> 0)
298 retval
->children
= skin_alloc_children(1);
299 if (!retval
->children
)
306 switch (callback(retval
, callback_data
))
316 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= MULTILINESYM
317 && !((*cursor
== ARGLISTSEPARATESYM
318 || *cursor
== ARGLISTCLOSESYM
319 || *cursor
== ENUMLISTSEPARATESYM
320 || *cursor
== ENUMLISTCLOSESYM
)
322 && !(check_viewport(cursor
) && cursor
!= *document
))
324 /* Allocating memory if necessary */
327 current
->next
= skin_alloc_element();
330 current
= current
->next
;
334 current
= skin_alloc_element();
340 /* Parsing the current element */
341 if(*cursor
== TAGSYM
&& cursor
[1] == CONDITIONSYM
)
343 if(!skin_parse_conditional(current
, &cursor
))
346 else if(*cursor
== TAGSYM
&& !find_escape_character(cursor
[1]))
348 if(!skin_parse_tag(current
, &cursor
))
351 else if(*cursor
== COMMENTSYM
)
353 if(!skin_parse_comment(current
, &cursor
))
358 if(!skin_parse_text(current
, &cursor
, conditional
))
364 /* Moving up the calling function's pointer */
368 retval
->children
[0] = root
;
372 static struct skin_element
* skin_parse_sublines(const char** document
)
374 return skin_parse_sublines_optional(document
, 0);
377 static struct skin_element
* skin_parse_sublines_optional(const char** document
,
380 struct skin_element
* retval
;
381 const char* cursor
= *document
;
385 retval
= skin_alloc_element();
388 retval
->type
= LINE_ALTERNATOR
;
390 retval
->line
= skin_line
;
392 /* First we count the sublines */
393 while(*cursor
!= '\0' && *cursor
!= '\n'
394 && !((*cursor
== ARGLISTSEPARATESYM
395 || *cursor
== ARGLISTCLOSESYM
396 || *cursor
== ENUMLISTSEPARATESYM
397 || *cursor
== ENUMLISTCLOSESYM
)
399 && !(check_viewport(cursor
) && cursor
!= *document
))
401 if(*cursor
== COMMENTSYM
)
403 skip_comment(&cursor
);
405 else if(*cursor
== ENUMLISTOPENSYM
)
407 skip_enumlist(&cursor
);
409 else if(*cursor
== ARGLISTOPENSYM
)
411 skip_arglist(&cursor
);
413 else if(*cursor
== TAGSYM
)
416 if(*cursor
== '\0' || *cursor
== '\n')
420 else if(*cursor
== MULTILINESYM
)
431 /* ...and then we parse them */
432 retval
->children_count
= sublines
;
433 retval
->children
= skin_alloc_children(sublines
);
434 if (!retval
->children
)
438 for(i
= 0; i
< sublines
; i
++)
440 retval
->children
[i
] = skin_parse_line_optional(&cursor
, conditional
);
441 skip_whitespace(&cursor
);
443 if(*cursor
!= MULTILINESYM
&& i
!= sublines
- 1)
445 skin_error(MULTILINE_EXPECTED
, cursor
);
448 else if(i
!= sublines
- 1)
457 if (callback(retval
, callback_data
) == CALLBACK_ERROR
)
466 static int skin_parse_tag(struct skin_element
* element
, const char** document
)
468 const char* cursor
= *document
+ 1;
469 const char* bookmark
;
473 const struct tag_info
*tag
;
477 int star
= 0; /* Flag for the all-or-none option */
478 int req_args
; /* To mark when we enter optional arguments */
481 tag_recursion_level
++;
483 /* Checking the tag name */
484 tag_name
[0] = cursor
[0];
485 tag_name
[1] = cursor
[1];
488 /* First we check the two characters after the '%', then a single char */
489 tag
= find_tag(tag_name
);
494 tag
= find_tag(tag_name
);
504 skin_error(ILLEGAL_TAG
, cursor
);
508 /* Copying basic tag info */
509 if(element
->type
!= CONDITIONAL
&& element
->type
!= VIEWPORT
)
512 tag_args
= tag
->params
;
513 element
->line
= skin_line
;
515 /* Checking for the * flag */
516 if(tag_args
[0] == '*')
522 /* If this tag has no arguments, we can bail out now */
523 if(strlen(tag_args
) == 0
524 || (tag_args
[0] == '|' && *cursor
!= ARGLISTOPENSYM
)
525 || (star
&& *cursor
!= ARGLISTOPENSYM
))
531 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
539 /* Checking the number of arguments and allocating args */
540 if(*cursor
!= ARGLISTOPENSYM
&& tag_args
[0] != '|')
542 skin_error(ARGLIST_EXPECTED
, cursor
);
551 while(*cursor
!= '\n' && *cursor
!= '\0' && *cursor
!= ARGLISTCLOSESYM
)
553 /* Skipping over escaped characters */
554 if(*cursor
== TAGSYM
)
561 else if(*cursor
== COMMENTSYM
)
563 skip_comment(&cursor
);
565 else if(*cursor
== ARGLISTOPENSYM
)
567 skip_arglist(&cursor
);
569 else if(*cursor
== ARGLISTSEPARATESYM
)
580 cursor
= bookmark
; /* Restoring the cursor */
581 element
->params_count
= num_args
;
582 element
->params
= skin_alloc_params(num_args
, tag_recursion_level
<=1);
583 if (!element
->params
)
586 /* Now we have to actually parse each argument */
587 for(i
= 0; i
< num_args
; i
++)
590 /* Making sure we haven't run out of arguments */
591 if(*tag_args
== '\0')
593 skin_error(TOO_MANY_ARGS
, cursor
);
597 /* Checking for the optional bar */
605 /* Scanning the arguments */
606 skip_whitespace(&cursor
);
608 /* Checking for comments */
609 if(*cursor
== COMMENTSYM
)
610 skip_comment(&cursor
);
612 if (*tag_args
== '[')
614 /* we need to guess which type of param it is.
615 * guess using this priority:
616 * default > decimal/integer > single tag/code > string
619 bool canbedefault
= false;
620 bool haspercent
= false, number
= true, hasdecimal
= false;
623 while (*tag_args
!= ']')
625 if (*tag_args
>= 'a' && *tag_args
<= 'z')
627 temp_params
[j
++] = tolower(*tag_args
++);
629 temp_params
[j
] = '\0';
631 while (cursor
[j
] && cursor
[j
] != ',' && cursor
[j
] != ')')
633 haspercent
= haspercent
|| (cursor
[j
] == '%');
634 hasdecimal
= hasdecimal
|| (cursor
[j
] == '.');
635 number
= number
&& (isdigit(cursor
[j
]) ||
636 (cursor
[j
] == '.') ||
641 if (canbedefault
&& *cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
645 else if (number
&& hasdecimal
&& strchr(temp_params
, 'd'))
650 (strchr(temp_params
, 'i') || strchr(temp_params
, 'd')))
652 type_code
= strchr(temp_params
, 'i') ? 'i' : 'd';
654 else if (haspercent
&&
655 (strchr(temp_params
, 't') || strchr(temp_params
, 'c')))
657 type_code
= strchr(temp_params
, 't') ? 't' : 'c';
659 else if (strchr(temp_params
, 's'))
663 if (type_code
== '*')
665 skin_error(INSUFFICIENT_ARGS
, cursor
);
670 type_code
= *tag_args
;
671 /* Storing the type code */
672 element
->params
[i
].type_code
= type_code
;
674 /* Checking a nullable argument for null. */
675 if(*cursor
== DEFAULTSYM
&& !isdigit(cursor
[1]))
677 if(islower(type_code
))
679 element
->params
[i
].type
= DEFAULT
;
684 skin_error(DEFAULT_NOT_ALLOWED
, cursor
);
688 else if(tolower(type_code
) == 'i')
690 /* Scanning an int argument */
691 if(!isdigit(*cursor
) && *cursor
!= '-')
693 skin_error(INT_EXPECTED
, cursor
);
697 element
->params
[i
].type
= INTEGER
;
698 element
->params
[i
].data
.number
= scan_int(&cursor
);
700 else if(tolower(type_code
) == 'd')
703 bool have_point
= false;
704 bool have_tenth
= false;
705 while ( isdigit(*cursor
) || *cursor
== '.' )
710 val
+= *cursor
- '0';
722 if (have_tenth
== false)
724 element
->params
[i
].type
= DECIMAL
;
725 element
->params
[i
].data
.number
= val
;
727 else if(tolower(type_code
) == 'n' ||
728 tolower(type_code
) == 's' || tolower(type_code
) == 'f')
730 /* Scanning a string argument */
731 element
->params
[i
].type
= STRING
;
732 element
->params
[i
].data
.text
= scan_string(&cursor
);
735 else if(tolower(type_code
) == 'c')
737 /* Recursively parsing a code argument */
738 element
->params
[i
].type
= CODE
;
739 element
->params
[i
].data
.code
= skin_parse_code_as_arg(&cursor
);
740 if(!element
->params
[i
].data
.code
)
743 else if (tolower(type_code
) == 't')
745 struct skin_element
* child
= skin_alloc_element();
747 if (!skin_parse_tag(child
, &cursor
))
750 element
->params
[i
].type
= CODE
;
751 element
->params
[i
].data
.code
= child
;
755 skip_whitespace(&cursor
);
757 if(*cursor
!= ARGLISTSEPARATESYM
&& i
< num_args
- 1)
759 skin_error(SEPARATOR_EXPECTED
, cursor
);
762 else if(*cursor
!= ARGLISTCLOSESYM
&& i
== num_args
- 1)
764 skin_error(CLOSE_EXPECTED
, cursor
);
772 if (*tag_args
!= 'N')
775 /* Checking for the optional bar */
784 /* Checking for a premature end */
785 if(*tag_args
!= '\0' && !optional
)
787 skin_error(INSUFFICIENT_ARGS
, cursor
);
793 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
798 tag_recursion_level
--;
804 * If the conditional flag is set true, then parsing text will stop at an
805 * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional
807 static int skin_parse_text(struct skin_element
* element
, const char** document
,
810 const char* cursor
= *document
;
815 /* First figure out how much text we're copying */
816 while(*cursor
!= '\0' && *cursor
!= '\n' && *cursor
!= MULTILINESYM
817 && *cursor
!= COMMENTSYM
818 && !((*cursor
== ARGLISTSEPARATESYM
819 || *cursor
== ARGLISTCLOSESYM
820 || *cursor
== ENUMLISTSEPARATESYM
821 || *cursor
== ENUMLISTCLOSESYM
)
824 /* Dealing with possibility of escaped characters */
825 if(*cursor
== TAGSYM
)
827 if(find_escape_character(cursor
[1]))
839 /* Copying the text into the element struct */
840 element
->type
= TEXT
;
841 element
->line
= skin_line
;
842 element
->next
= NULL
;
843 element
->data
= text
= skin_alloc_string(length
);
847 for(dest
= 0; dest
< length
; dest
++)
849 /* Advancing cursor if we've encountered an escaped character */
850 if(*cursor
== TAGSYM
)
853 text
[dest
] = *cursor
;
861 if (callback(element
, callback_data
) == CALLBACK_ERROR
)
871 static int skin_parse_conditional(struct skin_element
* element
, const char** document
)
873 const char* cursor
= *document
+ 1; /* Starting past the "%" */
874 const char* bookmark
;
879 bool feature_available
= true;
880 const char *false_branch
= NULL
;
881 const char *conditional_end
= NULL
;
884 /* Some conditional tags allow for target feature checking,
885 * so to handle that call the callback as usual with type == TAG
886 * then call it a second time with type == CONDITIONAL and check the return
889 element
->line
= skin_line
;
891 /* Parsing the tag first */
892 if(!skin_parse_tag(element
, &cursor
))
895 element
->type
= CONDITIONAL
;
899 switch (callback(element
, callback_data
))
901 case FEATURE_NOT_AVAILABLE
:
902 feature_available
= false;
912 /* Counting the children */
913 if(*(cursor
++) != ENUMLISTOPENSYM
)
915 skin_error(ARGLIST_EXPECTED
, cursor
);
919 while(*cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= '\n' && *cursor
!= '\0')
921 if(*cursor
== COMMENTSYM
)
923 skip_comment(&cursor
);
925 else if(*cursor
== ENUMLISTOPENSYM
)
927 skip_enumlist(&cursor
);
929 else if(*cursor
== TAGSYM
)
932 if(*cursor
== '\0' || *cursor
== '\n')
936 else if(*cursor
== ENUMLISTSEPARATESYM
)
941 if (false_branch
== NULL
&& !feature_available
)
943 false_branch
= cursor
;
954 if (*cursor
== ENUMLISTCLOSESYM
&&
955 false_branch
== NULL
&& !feature_available
)
957 false_branch
= cursor
+1;
960 if (element
->tag
->flags
&FEATURE_TAG
)
962 if (feature_available
&& children
> 1)
965 conditional_end
= cursor
;
966 /* if we are skipping the true branch fix that up */
967 cursor
= false_branch
? false_branch
: bookmark
;
971 /* Parsing the children */
973 /* Feature tags could end up having 0 children which breaks
974 * the render in dangerous ways. Minor hack, but insert an empty
975 * child. (e.g %?xx<foo> when xx isnt available ) */
979 const char* emptyline
= "";
981 element
->children
= skin_alloc_children(children
);
982 if (!element
->children
)
984 element
->children_count
= children
;
985 element
->children
[0] = skin_parse_code_as_arg(&emptyline
);
989 element
->children
= skin_alloc_children(children
);
990 if (!element
->children
)
992 element
->children_count
= children
;
994 for(i
= 0; i
< children
; i
++)
996 element
->children
[i
] = skin_parse_code_as_arg(&cursor
);
997 if (element
->children
[i
] == NULL
)
999 skip_whitespace(&cursor
);
1001 if ((element
->tag
->flags
&FEATURE_TAG
) && feature_available
)
1002 cursor
= conditional_end
;
1005 if(i
< children
- 1 && *cursor
!= ENUMLISTSEPARATESYM
)
1007 skin_error(SEPARATOR_EXPECTED
, cursor
);
1010 else if(i
== children
- 1 && *cursor
!= ENUMLISTCLOSESYM
)
1012 skin_error(CLOSE_EXPECTED
, cursor
);
1026 static int skin_parse_comment(struct skin_element
* element
, const char** document
)
1028 const char* cursor
= *document
;
1034 * Finding the index of the ending newline or null-terminator
1035 * The length of the string of interest doesn't include the leading #, the
1036 * length we need to reserve is the same as the index of the last character
1038 for(length
= 0; cursor
[length
] != '\n' && cursor
[length
] != '\0'; length
++);
1040 element
->type
= COMMENT
;
1041 element
->line
= skin_line
;
1043 element
->data
= NULL
;
1045 element
->data
= text
= skin_alloc_string(length
);
1048 /* We copy from one char past cursor to leave out the # */
1049 memcpy((void*)text
, (void*)(cursor
+ 1),
1050 sizeof(char) * (length
-1));
1051 text
[length
- 1] = '\0';
1053 if(cursor
[length
] == '\n')
1056 *document
+= (length
); /* Move cursor up past # and all text */
1057 if(**document
== '\n')
1063 static struct skin_element
* skin_parse_code_as_arg(const char** document
)
1066 const char* cursor
= *document
;
1068 /* Checking for sublines */
1069 while(*cursor
!= '\n' && *cursor
!= '\0'
1070 && *cursor
!= ENUMLISTSEPARATESYM
&& *cursor
!= ARGLISTSEPARATESYM
1071 && *cursor
!= ENUMLISTCLOSESYM
&& *cursor
!= ARGLISTCLOSESYM
)
1073 if(*cursor
== MULTILINESYM
)
1078 else if(*cursor
== TAGSYM
)
1080 /* A ';' directly after a '%' doesn't count */
1088 else if(*cursor
== ARGLISTOPENSYM
)
1090 skip_arglist(&cursor
);
1092 else if(*cursor
== ENUMLISTOPENSYM
)
1094 skip_enumlist(&cursor
);
1098 /* Advancing the cursor as normal */
1104 return skin_parse_sublines_optional(document
, 1);
1106 return skin_parse_line_optional(document
, 1);
1110 /* Memory management */
1111 struct skin_element
* skin_alloc_element()
1113 struct skin_element
* retval
= (struct skin_element
*)
1114 skin_buffer_alloc(sizeof(struct skin_element
));
1117 retval
->type
= UNKNOWN
;
1118 retval
->next
= NULL
;
1120 retval
->params_count
= 0;
1121 retval
->children_count
= 0;
1126 /* On a ROCKBOX build we try to save space as much as possible
1127 * so if we can, use a shared param pool which should be more then large
1128 * enough for any tag. params should be used straight away by the callback
1131 struct skin_tag_parameter
* skin_alloc_params(int count
, bool use_shared_params
)
1134 static struct skin_tag_parameter params
[MAX_TAG_PARAMS
];
1135 if (use_shared_params
&& count
<= MAX_TAG_PARAMS
)
1137 memset(params
, 0, sizeof(params
));
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 struct skin_element
** skin_alloc_children(int count
)
1153 return (struct skin_element
**)
1154 skin_buffer_alloc(sizeof(struct skin_element
*) * count
);
1157 void skin_free_tree(struct skin_element
* root
)
1162 /* First make the recursive call */
1165 skin_free_tree(root
->next
);
1168 if(root
->type
== TEXT
|| root
->type
== COMMENT
)
1171 /* Then recursively free any children, before freeing their pointers */
1172 for(i
= 0; i
< root
->children_count
; i
++)
1173 skin_free_tree(root
->children
[i
]);
1174 if(root
->children_count
> 0)
1175 free(root
->children
);
1177 /* Free any parameters, making sure to deallocate strings */
1178 for(i
= 0; i
< root
->params_count
; i
++)
1179 if(root
->params
[i
].type
== STRING
)
1180 free(root
->params
[i
].data
.text
);
1181 if(root
->params_count
> 0)
1184 /* Finally, delete root's memory */