| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2010 Robert Bieber |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include "skin_buffer.h" |
| #include "skin_parser.h" |
| #include "skin_debug.h" |
| #include "tag_table.h" |
| #include "symbols.h" |
| #include "skin_scan.h" |
| |
| /* Global variables for the parser */ |
| int skin_line = 0; |
| char* skin_start = 0; |
| static int viewport_line = 0; |
| |
| #ifdef ROCKBOX |
| static skin_callback callback = NULL; |
| static void* callback_data; |
| #endif |
| |
| /* Auxiliary parsing functions (not visible at global scope) */ |
| static struct skin_element* skin_alloc_element(void); |
| static OFFSETTYPE(struct skin_element*)* skin_alloc_children(int count); |
| static struct skin_tag_parameter* skin_alloc_params(int count); |
| |
| static struct skin_element* skin_parse_viewport(const char** document); |
| static struct skin_element* skin_parse_line(const char** document); |
| static struct skin_element* skin_parse_line_optional(const char** document, |
| int conditional); |
| static struct skin_element* skin_parse_sublines(const char** document); |
| static struct skin_element* skin_parse_sublines_optional(const char** document, |
| int conditional); |
| |
| static int skin_parse_tag(struct skin_element* element, const char** document); |
| static int skin_parse_text(struct skin_element* element, const char** document, |
| int conditional); |
| static int skin_parse_conditional(struct skin_element* element, |
| const char** document); |
| static int skin_parse_comment(struct skin_element* element, const char** document); |
| static struct skin_element* skin_parse_code_as_arg(const char** document); |
| |
| |
| static void skip_whitespace(const char** document) |
| { |
| while(**document == ' ' || **document == '\t') |
| (*document)++; |
| } |
| |
| #ifdef ROCKBOX |
| struct skin_element* skin_parse(const char* document, |
| skin_callback cb, void* cb_data) |
| { |
| callback = cb; |
| callback_data = cb_data; |
| #else |
| struct skin_element* skin_parse(const char* document) |
| { |
| #endif |
| struct skin_element* root = NULL; |
| struct skin_element* last = NULL; |
| |
| const char* cursor = document; /*Keeps track of location in the document*/ |
| |
| skin_line = 1; |
| skin_start = (char*)document; |
| viewport_line = 0; |
| |
| skin_clear_errors(); |
| |
| while(*cursor != '\0') |
| { |
| struct skin_element* tree = skin_parse_viewport(&cursor); |
| if(!root) |
| { |
| root = tree; |
| last = root; |
| } |
| else |
| { |
| last->next = skin_buffer_to_offset(tree); |
| last = tree; |
| } |
| |
| if(!last) |
| { |
| skin_free_tree(root); /* Clearing any memory already used */ |
| return NULL; |
| } |
| |
| /* Making sure last is at the end */ |
| while(IS_VALID_OFFSET(last->next)) |
| last = skin_buffer_from_offset(last->next); |
| |
| } |
| return root; |
| |
| } |
| |
| static struct skin_element* skin_parse_viewport(const char** document) |
| { |
| struct skin_element* root = NULL; |
| struct skin_element* last = NULL; |
| struct skin_element* retval = NULL; |
| |
| retval = skin_alloc_element(); |
| if (!retval) |
| return NULL; |
| retval->type = VIEWPORT; |
| retval->children_count = 1; |
| retval->line = skin_line; |
| viewport_line = skin_line; |
| |
| OFFSETTYPE(struct skin_element*)* children; |
| |
| const char* cursor = *document; /* Keeps track of location in the document */ |
| const char* bookmark; /* Used when we need to look ahead */ |
| |
| int sublines = 0; /* Flag for parsing sublines */ |
| |
| /* Parsing out the viewport tag if there is one */ |
| if(check_viewport(cursor)) |
| { |
| if (!skin_parse_tag(retval, &cursor)) |
| return NULL; |
| if(*cursor == '\n') |
| { |
| cursor++; |
| skin_line++; |
| } |
| } |
| #ifdef ROCKBOX |
| else if (callback) |
| { |
| if (callback(retval, callback_data) == CALLBACK_ERROR) |
| { |
| skin_error(GOT_CALLBACK_ERROR, cursor); |
| return NULL; |
| } |
| } |
| #endif |
| |
| if (check_viewport(cursor)) |
| { |
| retval->children_count = 0; |
| *document = cursor; |
| return retval; |
| } |
| retval->children_count = 1; |
| children = skin_alloc_children(1); |
| if (!children) |
| return NULL; |
| do |
| { |
| |
| /* First, we check to see if this line will contain sublines */ |
| bookmark = cursor; |
| sublines = 0; |
| while(*cursor != '\n' && *cursor != '\0' |
| && !(check_viewport(cursor) && cursor != *document)) |
| { |
| if(*cursor == MULTILINESYM) |
| { |
| sublines = 1; |
| break; |
| } |
| else if(*cursor == TAGSYM) |
| { |
| skip_tag(&cursor); |
| } |
| else if(*cursor == COMMENTSYM) |
| { |
| skip_comment(&cursor); |
| } |
| else |
| { |
| /* Advancing the cursor as normal */ |
| cursor++; |
| } |
| } |
| cursor = bookmark; |
| |
| if(sublines) |
| { |
| struct skin_element* out = skin_parse_sublines(&cursor); |
| if (!root) |
| { |
| root = out; |
| last = root; |
| } |
| else |
| { |
| last->next = skin_buffer_to_offset(out); |
| last = out; |
| } |
| if(!last) |
| return NULL; |
| } |
| else |
| { |
| #ifdef ROCKBOX |
| /* strip all leading comments */ |
| while(*cursor == '#') |
| { |
| skip_comment(&cursor); |
| skin_line++; |
| |
| } |
| if (check_viewport(cursor)) |
| break; |
| #endif |
| |
| struct skin_element* out = skin_parse_line(&cursor); |
| if (!root) |
| { |
| root = out; |
| last = root; |
| } |
| else |
| { |
| last->next = skin_buffer_to_offset(out); |
| last = out; |
| } |
| if(!last) |
| return NULL; |
| |
| } |
| /* Making sure last is at the end */ |
| while(IS_VALID_OFFSET(last->next)) |
| last = skin_buffer_from_offset(last->next); |
| |
| if(*cursor == '\n') |
| { |
| cursor++; |
| skin_line++; |
| } |
| #ifdef ROCKBOX |
| /* strip all comments */ |
| while(*cursor == '#') |
| { |
| skip_comment(&cursor); |
| skin_line++; |
| |
| } |
| if (check_viewport(cursor)) |
| break; |
| #endif |
| |
| } |
| while(*cursor != '\0' && !(check_viewport(cursor) && cursor != *document)); |
| |
| *document = cursor; |
| |
| children[0] = skin_buffer_to_offset(root); |
| retval->children = skin_buffer_to_offset(children); |
| return retval; |
| } |
| |
| /* Auxiliary Parsing Functions */ |
| |
| static struct skin_element* skin_parse_line(const char**document) |
| { |
| return skin_parse_line_optional(document, 0); |
| } |
| |
| /* |
| * If conditional is set to true, then this will break upon encountering |
| * SEPARATESYM. This should only be used when parsing a line inside a |
| * conditional, otherwise just use the wrapper function skin_parse_line() |
| */ |
| static struct skin_element* skin_parse_line_optional(const char** document, |
| int conditional) |
| { |
| const char* cursor = *document; |
| |
| struct skin_element* root = NULL; |
| struct skin_element* current = NULL; |
| struct skin_element* retval = NULL; |
| OFFSETTYPE(struct skin_element*)* children = NULL; |
| |
| /* A wrapper for the line */ |
| retval = skin_alloc_element(); |
| if (!retval) |
| return NULL; |
| retval->type = LINE; |
| retval->line = skin_line; |
| while (*cursor == '\t') |
| cursor++; |
| |
| if(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM |
| && !(conditional && (*cursor == ARGLISTSEPARATESYM |
| || *cursor == ARGLISTCLOSESYM |
| || *cursor == ENUMLISTSEPARATESYM |
| || *cursor == ENUMLISTCLOSESYM))) |
| { |
| retval->children_count = 1; |
| } |
| else |
| { |
| retval->children_count = 0; |
| } |
| |
| if(retval->children_count > 0) |
| { |
| children = skin_alloc_children(1); |
| if (!children) |
| return NULL; |
| } |
| |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| switch (callback(retval, callback_data)) |
| { |
| case CALLBACK_ERROR: |
| skin_error(GOT_CALLBACK_ERROR, cursor); |
| return NULL; |
| default: |
| break; |
| } |
| } |
| #endif |
| |
| while(*cursor != '\n' && *cursor != '\0' && *cursor != MULTILINESYM |
| && !((*cursor == ARGLISTSEPARATESYM |
| || *cursor == ARGLISTCLOSESYM |
| || *cursor == ENUMLISTSEPARATESYM |
| || *cursor == ENUMLISTCLOSESYM) |
| && conditional) |
| && !(check_viewport(cursor) && cursor != *document)) |
| { |
| /* Allocating memory if necessary */ |
| if(root) |
| { |
| struct skin_element *next = skin_alloc_element(); |
| if (!next) |
| return NULL; |
| current->next = skin_buffer_to_offset(next); |
| current = next; |
| } |
| else |
| { |
| current = skin_alloc_element(); |
| if (!current) |
| return NULL; |
| root = current; |
| } |
| |
| /* Parsing the current element */ |
| if(*cursor == TAGSYM && cursor[1] == CONDITIONSYM) |
| { |
| if(!skin_parse_conditional(current, &cursor)) |
| return NULL; |
| } |
| else if(*cursor == TAGSYM && !find_escape_character(cursor[1])) |
| { |
| if(!skin_parse_tag(current, &cursor)) |
| return NULL; |
| } |
| else if(*cursor == COMMENTSYM) |
| { |
| if(!skin_parse_comment(current, &cursor)) |
| return NULL; |
| } |
| else |
| { |
| if(!skin_parse_text(current, &cursor, conditional)) |
| return NULL; |
| } |
| } |
| |
| /* Moving up the calling function's pointer */ |
| *document = cursor; |
| |
| if(root) |
| { |
| children[0] = skin_buffer_to_offset(root); |
| retval->children = skin_buffer_to_offset(children); |
| } |
| return retval; |
| } |
| |
| static struct skin_element* skin_parse_sublines(const char** document) |
| { |
| return skin_parse_sublines_optional(document, 0); |
| } |
| |
| static struct skin_element* skin_parse_sublines_optional(const char** document, |
| int conditional) |
| { |
| struct skin_element* retval; |
| OFFSETTYPE(struct skin_element*)* children; |
| const char* cursor = *document; |
| int sublines = 1; |
| int i; |
| |
| retval = skin_alloc_element(); |
| if (!retval) |
| return NULL; |
| retval->type = LINE_ALTERNATOR; |
| retval->next = skin_buffer_to_offset(NULL); |
| retval->line = skin_line; |
| while (*cursor == '\t') |
| cursor++; |
| |
| /* First we count the sublines */ |
| while(*cursor != '\0' && *cursor != '\n' |
| && !((*cursor == ARGLISTSEPARATESYM |
| || *cursor == ARGLISTCLOSESYM |
| || *cursor == ENUMLISTSEPARATESYM |
| || *cursor == ENUMLISTCLOSESYM) |
| && conditional) |
| && !(check_viewport(cursor) && cursor != *document)) |
| { |
| if(*cursor == COMMENTSYM) |
| { |
| skip_comment(&cursor); |
| } |
| else if(*cursor == TAGSYM) |
| { |
| skip_tag(&cursor); |
| } |
| else if(*cursor == MULTILINESYM) |
| { |
| sublines++; |
| cursor++; |
| } |
| else |
| { |
| cursor++; |
| } |
| } |
| |
| /* ...and then we parse them */ |
| retval->children_count = sublines; |
| children = skin_alloc_children(sublines); |
| if (!children) |
| return NULL; |
| |
| cursor = *document; |
| for(i = 0; i < sublines; i++) |
| { |
| children[i] = skin_buffer_to_offset(skin_parse_line_optional(&cursor, conditional)); |
| if (children[i] < 0) |
| return NULL; |
| skip_whitespace(&cursor); |
| |
| if(*cursor != MULTILINESYM && i != sublines - 1) |
| { |
| skin_error(MULTILINE_EXPECTED, cursor); |
| return NULL; |
| } |
| else if(i != sublines - 1) |
| { |
| cursor++; |
| } |
| } |
| |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| if (callback(retval, callback_data) == CALLBACK_ERROR) |
| { |
| skin_error(GOT_CALLBACK_ERROR, *document); |
| return NULL; |
| } |
| } |
| #endif |
| *document = cursor; |
| retval->children = skin_buffer_to_offset(children); |
| |
| return retval; |
| } |
| |
| static int skin_parse_tag(struct skin_element* element, const char** document) |
| { |
| const char* cursor = *document + 1; |
| const char* bookmark; |
| char *open_square_bracket = NULL; |
| |
| char tag_name[MAX_TAG_LENGTH]; |
| char* tag_args; |
| const struct tag_info *tag; |
| struct skin_tag_parameter* params = NULL; |
| |
| int num_args = 1; |
| int i; |
| int qmark = 0; /* Flag for the all-or-none option */ |
| |
| int optional = 0; |
| |
| /* Checking the tag name */ |
| for (i=0; cursor[i] && i<MAX_TAG_LENGTH; i++) |
| tag_name[i] = cursor[i]; |
| |
| /* First we check the two characters after the '%', then a single char */ |
| tag = NULL; |
| i = MAX_TAG_LENGTH; |
| while (!tag && i > 1) |
| { |
| tag_name[i-1] = '\0'; |
| tag = find_tag(tag_name); |
| i--; |
| } |
| |
| if(!tag) |
| { |
| skin_error(ILLEGAL_TAG, cursor); |
| return 0; |
| } |
| cursor += i; |
| |
| /* Copying basic tag info */ |
| if(element->type != CONDITIONAL && element->type != VIEWPORT) |
| element->type = TAG; |
| element->tag = tag; |
| tag_args = tag->params; |
| element->line = skin_line; |
| |
| /* Checking for the * flag */ |
| if(tag_args[0] == '?') |
| { |
| qmark = 1; |
| tag_args++; |
| } |
| |
| /* If this tag has no arguments, we can bail out now */ |
| if(strlen(tag_args) == 0 |
| || (tag_args[0] == '|' && *cursor != ARGLISTOPENSYM) |
| || (qmark && *cursor != ARGLISTOPENSYM)) |
| { |
| |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| if (callback(element, callback_data) == CALLBACK_ERROR) |
| { |
| skin_error(GOT_CALLBACK_ERROR, cursor); |
| return 0; |
| } |
| } |
| #endif |
| *document = cursor; |
| return 1; |
| } |
| |
| /* Checking the number of arguments and allocating args */ |
| if(*cursor != ARGLISTOPENSYM && tag_args[0] != '|') |
| { |
| skin_error(ARGLIST_EXPECTED, cursor); |
| return 0; |
| } |
| else |
| { |
| cursor++; |
| } |
| |
| bookmark = cursor; |
| while(*cursor != '\n' && *cursor != '\0' && *cursor != ARGLISTCLOSESYM) |
| { |
| /* Skipping over escaped characters */ |
| if(*cursor == TAGSYM && *(cursor+1) != ARGLISTSEPARATESYM) |
| { |
| skip_tag(&cursor); |
| } |
| else if(*cursor == COMMENTSYM) |
| { |
| skip_comment(&cursor); |
| } |
| else if(*cursor == ARGLISTSEPARATESYM) |
| { |
| num_args++; |
| cursor++; |
| } |
| else |
| { |
| cursor++; |
| } |
| } |
| |
| cursor = bookmark; /* Restoring the cursor */ |
| element->params_count = num_args; |
| params = skin_alloc_params(num_args); |
| if (!params) |
| return 0; |
| |
| /* Now we have to actually parse each argument */ |
| for(i = 0; i < num_args; i++) |
| { |
| char type_code; |
| /* Making sure we haven't run out of arguments */ |
| if(*tag_args == '\0') |
| { |
| skin_error(TOO_MANY_ARGS, cursor); |
| return 0; |
| } |
| |
| /* Checking for the optional bar */ |
| if(*tag_args == '|') |
| { |
| optional = 1; |
| tag_args++; |
| } |
| |
| /* Scanning the arguments */ |
| skip_whitespace(&cursor); |
| |
| /* Checking for comments */ |
| if(*cursor == COMMENTSYM) |
| skip_comment(&cursor); |
| |
| if (*tag_args == '[') |
| { |
| /* we need to guess which type of param it is. |
| * guess using this priority: |
| * default > decimal/integer > single tag/code > string |
| */ |
| int j=0; |
| bool canbedefault = false, last_char_is_percent = false; |
| bool haspercent = false, number = true, hasdecimal = false; |
| char temp_params[8]; |
| open_square_bracket = tag_args; |
| tag_args++; |
| while (*tag_args != ']') |
| { |
| if (*tag_args >= 'a' && *tag_args <= 'z') |
| canbedefault = true; |
| temp_params[j++] = tolower(*tag_args++); |
| } |
| temp_params[j] = '\0'; |
| j = 0; |
| while (cursor[j] && cursor[j] != ',' && cursor[j] != ')') |
| { |
| haspercent = haspercent || (cursor[j] == '%'); |
| hasdecimal = hasdecimal || (cursor[j] == '.'); |
| number = number && (isdigit(cursor[j]) || |
| (cursor[j] == '.') || |
| (cursor[j] == '-') || |
| (cursor[j] == '%')); |
| j++; |
| } |
| last_char_is_percent = cursor[j-1] == '%'; |
| type_code = '?'; |
| if (canbedefault && *cursor == DEFAULTSYM && !isdigit(cursor[1])) |
| { |
| type_code = 'i'; |
| } |
| else if (number && hasdecimal && strchr(temp_params, 'd')) |
| { |
| type_code = 'd'; |
| } |
| else if (number && last_char_is_percent && strchr(temp_params, 'p')) |
| { |
| type_code = 'p'; |
| } |
| else if (number && |
| (strchr(temp_params, 'i') || strchr(temp_params, 'd'))) |
| { |
| type_code = strchr(temp_params, 'i') ? 'i' : 'd'; |
| } |
| else if (haspercent && |
| (strchr(temp_params, 't') || strchr(temp_params, 'c'))) |
| { |
| type_code = strchr(temp_params, 't') ? 't' : 'c'; |
| } |
| else if (strchr(temp_params, 's')) |
| { |
| type_code = 's'; |
| } |
| if (type_code == '?') |
| { |
| skin_error(INSUFFICIENT_ARGS, cursor); |
| return 0; |
| } |
| } |
| else |
| type_code = *tag_args; |
| /* Storing the type code */ |
| params[i].type_code = type_code; |
| |
| /* Checking a nullable argument for null. */ |
| if(*cursor == DEFAULTSYM && !isdigit(cursor[1])) |
| { |
| if(islower(type_code)) |
| { |
| params[i].type = DEFAULT; |
| cursor++; |
| } |
| else |
| { |
| skin_error(DEFAULT_NOT_ALLOWED, cursor); |
| return 0; |
| } |
| } |
| else if(tolower(type_code) == 'i') |
| { |
| /* Scanning an int argument */ |
| if(!isdigit(*cursor) && *cursor != '-') |
| { |
| skin_error(INT_EXPECTED, cursor); |
| return 0; |
| } |
| |
| params[i].type = INTEGER; |
| params[i].data.number = scan_int(&cursor); |
| } |
| else if(tolower(type_code) == 'd' || tolower(type_code) == 'p') |
| { |
| int val = 0; |
| bool have_point = false; |
| bool have_tenth = false; |
| while ( isdigit(*cursor) || *cursor == '.' ) |
| { |
| if (*cursor != '.') |
| { |
| val *= 10; |
| val += *cursor - '0'; |
| if (have_point) |
| { |
| have_tenth = true; |
| cursor++; |
| break; |
| } |
| } |
| else |
| have_point = true; |
| cursor++; |
| } |
| if (have_tenth == false) |
| val *= 10; |
| if (tolower(type_code) == 'd') |
| params[i].type = DECIMAL; |
| else |
| { |
| params[i].type = PERCENT; |
| cursor++; /* skip trailing % sign */ |
| } |
| params[i].data.number = val; |
| } |
| else if(tolower(type_code) == 's' || tolower(type_code) == 'f') |
| { |
| /* Scanning a string argument */ |
| params[i].type = STRING; |
| params[i].data.text = skin_buffer_to_offset(scan_string(&cursor)); |
| |
| } |
| else if(tolower(type_code) == 'c') |
| { |
| /* Recursively parsing a code argument */ |
| params[i].type = CODE; |
| params[i].data.code = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor)); |
| if(params[i].data.code < 0) |
| return 0; |
| } |
| else if (tolower(type_code) == 't') |
| { |
| struct skin_element* child = skin_alloc_element(); |
| child->type = TAG; |
| if (!skin_parse_tag(child, &cursor)) |
| return 0; |
| child->next = skin_buffer_to_offset(NULL); |
| params[i].type = CODE; |
| params[i].data.code = skin_buffer_to_offset(child); |
| } |
| |
| |
| skip_whitespace(&cursor); |
| |
| if(*cursor != ARGLISTSEPARATESYM && i < num_args - 1) |
| { |
| skin_error(SEPARATOR_EXPECTED, cursor); |
| return 0; |
| } |
| else if(*cursor != ARGLISTCLOSESYM && i == num_args - 1) |
| { |
| skin_error(CLOSE_EXPECTED, cursor); |
| return 0; |
| } |
| else |
| { |
| cursor++; |
| } |
| |
| if (*(tag_args + 1) == '*') |
| { |
| if (i+1 == num_args) |
| tag_args += 2; |
| else if (open_square_bracket && *tag_args == ']') |
| { |
| tag_args = open_square_bracket; |
| open_square_bracket = NULL; |
| } |
| } |
| else |
| tag_args++; |
| |
| /* Checking for the optional bar */ |
| if(*tag_args == '|') |
| { |
| optional = 1; |
| tag_args++; |
| } |
| } |
| element->params = skin_buffer_to_offset(params); |
| |
| /* Checking for a premature end */ |
| if(*tag_args != '\0' && !optional) |
| { |
| skin_error(INSUFFICIENT_ARGS, cursor); |
| return 0; |
| } |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| if (callback(element, callback_data) == CALLBACK_ERROR) |
| { |
| skin_error(GOT_CALLBACK_ERROR, *document); |
| return 0; |
| } |
| } |
| #endif |
| *document = cursor; |
| |
| return 1; |
| } |
| |
| /* |
| * If the conditional flag is set true, then parsing text will stop at an |
| * ARGLISTSEPARATESYM. Only set that flag when parsing within a conditional |
| */ |
| static int skin_parse_text(struct skin_element* element, const char** document, |
| int conditional) |
| { |
| const char* cursor = *document; |
| int length = 0; |
| int dest; |
| char *text = NULL; |
| |
| /* First figure out how much text we're copying */ |
| while(*cursor != '\0' && *cursor != '\n' && *cursor != MULTILINESYM |
| && *cursor != COMMENTSYM |
| && !((*cursor == ARGLISTSEPARATESYM |
| || *cursor == ARGLISTCLOSESYM |
| || *cursor == ENUMLISTSEPARATESYM |
| || *cursor == ENUMLISTCLOSESYM) |
| && conditional)) |
| { |
| /* Dealing with possibility of escaped characters */ |
| if(*cursor == TAGSYM) |
| { |
| if(find_escape_character(cursor[1])) |
| cursor++; |
| else |
| break; |
| } |
| |
| length++; |
| cursor++; |
| } |
| |
| cursor = *document; |
| |
| /* Copying the text into the element struct */ |
| element->type = TEXT; |
| element->line = skin_line; |
| element->next = skin_buffer_to_offset(NULL); |
| text = skin_alloc_string(length); |
| element->data = skin_buffer_to_offset(text); |
| if (element->data < 0) |
| return 0; |
| |
| for(dest = 0; dest < length; dest++) |
| { |
| /* Advancing cursor if we've encountered an escaped character */ |
| if(*cursor == TAGSYM) |
| cursor++; |
| |
| text[dest] = *cursor; |
| cursor++; |
| } |
| text[length] = '\0'; |
| |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| if (callback(element, callback_data) == CALLBACK_ERROR) |
| { |
| skin_error(GOT_CALLBACK_ERROR, *document); |
| return 0; |
| } |
| } |
| #endif |
| |
| *document = cursor; |
| |
| return 1; |
| } |
| |
| static int skin_parse_conditional(struct skin_element* element, const char** document) |
| { |
| const char* cursor = *document + 1; /* Starting past the "%" */ |
| const char* bookmark; |
| int children = 1; |
| int i; |
| |
| #ifdef ROCKBOX |
| bool feature_available = true; |
| const char *false_branch = NULL; |
| const char *conditional_end = NULL; |
| #endif |
| OFFSETTYPE(struct skin_element*)* children_array = NULL; |
| |
| /* Some conditional tags allow for target feature checking, |
| * so to handle that call the callback as usual with type == TAG |
| * then call it a second time with type == CONDITIONAL and check the return |
| * value */ |
| element->type = TAG; |
| element->line = skin_line; |
| |
| /* Parsing the tag first */ |
| if(!skin_parse_tag(element, &cursor)) |
| return 0; |
| |
| element->type = CONDITIONAL; |
| #ifdef ROCKBOX |
| if (callback) |
| { |
| switch (callback(element, callback_data)) |
| { |
| case FEATURE_NOT_AVAILABLE: |
| feature_available = false; |
| break; |
| case CALLBACK_ERROR: |
| return 0; |
| default: |
| break; |
| } |
| } |
| #endif |
| |
| /* Counting the children */ |
| if(*(cursor++) != ENUMLISTOPENSYM) |
| { |
| skin_error(ARGLIST_EXPECTED, cursor); |
| return 0; |
| } |
| bookmark = cursor; |
| while(*cursor != ENUMLISTCLOSESYM && *cursor != '\0') |
| { |
| if(*cursor == COMMENTSYM) |
| { |
| skip_comment(&cursor); |
| } |
| else if(*cursor == TAGSYM) |
| { |
| skip_tag(&cursor); |
| } |
| else if(*cursor == ENUMLISTSEPARATESYM) |
| { |
| children++; |
| cursor++; |
| if (*cursor == '\n') |
| cursor++; |
| #ifdef ROCKBOX |
| if (false_branch == NULL && !feature_available) |
| { |
| false_branch = cursor; |
| children--; |
| } |
| #endif |
| } |
| else |
| { |
| cursor++; |
| } |
| } |
| #ifdef ROCKBOX |
| if (*cursor == ENUMLISTCLOSESYM && |
| false_branch == NULL && !feature_available) |
| { |
| false_branch = cursor+1; |
| children--; |
| } |
| if (element->tag->flags&FEATURE_TAG) |
| { |
| if (feature_available && children > 1) |
| children--; |
| } |
| conditional_end = cursor; |
| /* if we are skipping the true branch fix that up */ |
| cursor = false_branch ? false_branch : bookmark; |
| #else |
| cursor = bookmark; |
| #endif |
| /* Parsing the children */ |
| |
| /* Feature tags could end up having 0 children which breaks |
| * the render in dangerous ways. Minor hack, but insert an empty |
| * child. (e.g %?xx<foo> when xx isnt available ) */ |
| |
| if (children == 0) |
| { |
| const char* emptyline= ""; |
| children = 1; |
| children_array = skin_alloc_children(children); |
| if (!children_array) |
| return 0; |
| element->children_count = children; |
| children_array[0] = skin_buffer_to_offset(skin_parse_code_as_arg(&emptyline)); |
| } |
| else |
| { |
| children_array = skin_alloc_children(children); |
| if (!children_array) |
| return 0; |
| element->children_count = children; |
| |
| for(i = 0; i < children; i++) |
| { |
| if (*cursor == '\n') |
| { |
| skin_line++; |
| cursor++; |
| } |
| children_array[i] = skin_buffer_to_offset(skin_parse_code_as_arg(&cursor)); |
| if (children_array[i] < 0) |
| return 0; |
| skip_whitespace(&cursor); |
| #ifdef ROCKBOX |
| if ((element->tag->flags&FEATURE_TAG) && feature_available) |
| cursor = conditional_end; |
| #endif |
| |
| if(i < children - 1 && *cursor != ENUMLISTSEPARATESYM) |
| { |
| skin_error(SEPARATOR_EXPECTED, cursor); |
| return 0; |
| } |
| else if(i == children - 1 && *cursor != ENUMLISTCLOSESYM) |
| { |
| skin_error(CLOSE_EXPECTED, cursor); |
| return 0; |
| } |
| else |
| { |
| cursor++; |
| } |
| } |
| } |
| *document = cursor; |
| element->children = skin_buffer_to_offset(children_array); |
| |
| return 1; |
| } |
| |
| static int skin_parse_comment(struct skin_element* element, const char** document) |
| { |
| const char* cursor = *document; |
| #ifndef ROCKBOX |
| char* text = NULL; |
| #endif |
| int length; |
| /* |
| * Finding the index of the ending newline or null-terminator |
| * The length of the string of interest doesn't include the leading #, the |
| * length we need to reserve is the same as the index of the last character |
| */ |
| for(length = 0; cursor[length] != '\n' && cursor[length] != '\0'; length++); |
| |
| element->type = COMMENT; |
| element->line = skin_line; |
| #ifdef ROCKBOX |
| element->data = INVALID_OFFSET; |
| #else |
| element->data = text = skin_alloc_string(length); |
| if (!element->data) |
| return 0; |
| /* We copy from one char past cursor to leave out the # */ |
| memcpy((void*)text, (void*)(cursor + 1), |
| sizeof(char) * (length-1)); |
| text[length - 1] = '\0'; |
| #endif |
| if(cursor[length] == '\n') |
| skin_line++; |
| |
| *document += (length); /* Move cursor up past # and all text */ |
| if(**document == '\n') |
| (*document)++; |
| |
| return 1; |
| } |
| |
| static struct skin_element* skin_parse_code_as_arg(const char** document) |
| { |
| int sublines = 0; |
| const char* cursor = *document; |
| |
| /* Checking for sublines */ |
| while(*cursor != '\n' && *cursor != '\0' |
| && *cursor != ENUMLISTSEPARATESYM && *cursor != ARGLISTSEPARATESYM |
| && *cursor != ENUMLISTCLOSESYM && *cursor != ARGLISTCLOSESYM) |
| { |
| if(*cursor == MULTILINESYM) |
| { |
| sublines = 1; |
| break; |
| } |
| else if(*cursor == TAGSYM) |
| { |
| skip_tag(&cursor); |
| } |
| else |
| { |
| /* Advancing the cursor as normal */ |
| cursor++; |
| } |
| } |
| |
| if(sublines) |
| return skin_parse_sublines_optional(document, 1); |
| else |
| return skin_parse_line_optional(document, 1); |
| } |
| |
| /* Memory management */ |
| static struct skin_element* skin_alloc_element() |
| { |
| struct skin_element* retval = (struct skin_element*) |
| skin_buffer_alloc(sizeof(struct skin_element)); |
| if (!retval) |
| return NULL; |
| retval->type = UNKNOWN; |
| retval->next = skin_buffer_to_offset(NULL); |
| retval->params = skin_buffer_to_offset(NULL); |
| retval->tag = NULL; |
| retval->params_count = 0; |
| retval->children_count = 0; |
| retval->data = INVALID_OFFSET; |
| |
| return retval; |
| |
| } |
| /* On a ROCKBOX build we try to save space as much as possible |
| * so if we can, use a shared param pool which should be more then large |
| * enough for any tag. params should be used straight away by the callback |
| * so this is safe. |
| */ |
| static struct skin_tag_parameter* skin_alloc_params(int count) |
| { |
| size_t size = sizeof(struct skin_tag_parameter) * count; |
| return (struct skin_tag_parameter*)skin_buffer_alloc(size); |
| |
| } |
| |
| char* skin_alloc_string(int length) |
| { |
| return (char*)skin_buffer_alloc(sizeof(char) * (length + 1)); |
| } |
| |
| static OFFSETTYPE(struct skin_element*)* skin_alloc_children(int count) |
| { |
| return (OFFSETTYPE(struct skin_element*)*) |
| skin_buffer_alloc(sizeof(struct skin_element*) * count); |
| } |
| |
| void skin_free_tree(struct skin_element* root) |
| { |
| #ifndef ROCKBOX |
| int i; |
| |
| /* First make the recursive call */ |
| if(!root) |
| return; |
| skin_free_tree(root->next); |
| |
| /* Free any text */ |
| if(root->type == TEXT || root->type == COMMENT) |
| free(root->data); |
| |
| /* Then recursively free any children, before freeing their pointers */ |
| for(i = 0; i < root->children_count; i++) |
| skin_free_tree(root->children[i]); |
| if(root->children_count > 0) |
| free(root->children); |
| |
| /* Free any parameters, making sure to deallocate strings */ |
| for(i = 0; i < root->params_count; i++) |
| if(root->params[i].type == STRING) |
| free(root->params[i].data.text); |
| if(root->params_count > 0) |
| free(root->params); |
| |
| /* Finally, delete root's memory */ |
| free(root); |
| #else |
| (void)root; |
| #endif |
| } |