/** * @file lv_svg_token.c * */ /********************* * INCLUDES *********************/ #include "lv_svg_token.h" #if LV_USE_SVG #include "../../../lvgl.h" #include #include /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /* * tag mask quote mask tag search comment doc type xml inst * | 0 0 0 | 0 0 | 0 | 0 | 0 | 0 | 0 | */ enum { SVG_TAG_MASK = (1 << 3) - 1, SVG_QUOTE_MASK = (1 << 5) - (1 << 3), SVG_TAG = 1 << 5, SVG_SEARCH = 1 << 6, SVG_COMMENT = 1 << 7, SVG_DOCTYPE = 1 << 8, SVG_XMLINST = 1 << 9, }; typedef uint32_t _lv_svg_parser_bits_t; enum { SVG_NO_QUOTE = 0, SVG_SINGLE_QUOTE = 1, SVG_DOUBLE_QUOTE = 2, }; typedef uint32_t _lv_svg_parser_quote_t; enum { SVG_NO_TAG = 0, SVG_TAG_NAME = 1, SVG_ATTR_START = 2, SVG_ATTR_NAME = 3, SVG_SEARCH_EQUAL = 4, SVG_SEARCH_VALUE = 5, SVG_QUOTE_VALUE = 6, SVG_VALUE = 7, }; typedef uint32_t _lv_svg_parser_tag_state_t; typedef struct { uint32_t flags; const char * cur; const char * end; } _lv_svg_parser_state_t; /********************** * STATIC PROTOTYPES **********************/ static void _set_state(_lv_svg_parser_state_t * state, uint32_t bit) { state->flags |= bit; } static void _clear_state(_lv_svg_parser_state_t * state, uint32_t bit) { state->flags &= ~bit; } static bool _is_state(_lv_svg_parser_state_t * state, uint32_t bit) { return state->flags & bit; } static void _set_tag_state(_lv_svg_parser_state_t * state, uint32_t bit) { state->flags = (state->flags & ~SVG_TAG_MASK) | bit; } static void _set_quote_state(_lv_svg_parser_state_t * state, uint32_t bit) { state->flags = (state->flags & ~SVG_QUOTE_MASK) | (bit << 3); } static bool _special_handle(_lv_svg_parser_state_t * state) { return state->flags & (SVG_TAG | SVG_SEARCH | SVG_TAG_MASK | SVG_COMMENT | SVG_DOCTYPE | SVG_XMLINST); } static void _lv_svg_token_init(_lv_svg_token_t * token) { token->start = NULL; token->end = NULL; token->type = LV_SVG_TOKEN_CONTENT; token->flat = false; token->cur_attr = NULL; lv_array_init(&token->attrs, LV_ARRAY_DEFAULT_CAPACITY, sizeof(_lv_svg_token_attr_t)); } static void _lv_svg_token_reset(_lv_svg_token_t * token) { token->start = NULL; token->end = NULL; token->type = LV_SVG_TOKEN_CONTENT; token->flat = false; token->cur_attr = NULL; lv_array_clear(&token->attrs); } static bool _lv_svg_token_process(_lv_svg_token_t * token, svg_token_process cb, void * data) { if(!token->start || SVG_TOKEN_LEN(token) == 0) return true; bool ret = cb(token, data); _lv_svg_token_reset(token); return ret; } static _lv_svg_token_attr_t * _new_svg_attr(_lv_svg_token_t * token) { if((lv_array_size(&token->attrs) + 1) > lv_array_capacity(&token->attrs)) { lv_array_resize(&token->attrs, token->attrs.capacity << 1); } token->attrs.size++; _lv_svg_token_attr_t * attr = lv_array_at(&token->attrs, token->attrs.size - 1); lv_memset(attr, 0, sizeof(_lv_svg_token_attr_t)); return attr; } static void _svg_parser_xml_inst(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) { LV_UNUSED(token); while(state->cur <= state->end) { char ch = *(state->cur); if(ch == '>' && (*(state->cur - 1)) == '?') { _clear_state(state, SVG_XMLINST); state->cur++; break; } state->cur++; } } static void _svg_parser_comment(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) { LV_UNUSED(token); while(state->cur <= state->end) { char ch = *(state->cur); if(ch == '>' && (*(state->cur - 1)) == '-' && (*(state->cur - 2)) == '-') { _clear_state(state, SVG_COMMENT); state->cur++; break; } state->cur++; } } static void _svg_parser_doctype(_lv_svg_parser_state_t * state, _lv_svg_token_t * token) { LV_UNUSED(token); //TODO: processing DTD type while(state->cur <= state->end) { char ch = *(state->cur); if(ch == '>') { _clear_state(state, SVG_DOCTYPE); state->cur++; break; } state->cur++; } } static bool _svg_parser_tag(_lv_svg_parser_state_t * state, _lv_svg_token_t * token, svg_token_process cb, void * data) { while(state->cur <= state->end) { switch(state->flags & SVG_TAG_MASK) { case SVG_NO_TAG: { if(!_lv_svg_token_process(token, cb, data)) { return false; } state->cur++; } return true; case SVG_TAG_NAME: { char ch = *(state->cur); if(ch == '/') { token->type = LV_SVG_TOKEN_END; state->cur++; if(!token->start) { token->start = state->cur; } continue; } else if(ch == '>' || isspace(ch)) { token->end = state->cur; _set_tag_state(state, SVG_ATTR_START); continue; } else { if(!token->start) { token->type = LV_SVG_TOKEN_BEGIN; token->start = state->cur; } state->cur++; continue; } } break; case SVG_ATTR_START: { char ch = *(state->cur); if(!isspace(ch) && ch != '\'' && ch != '\"') { if(ch == '/') { token->flat = true; state->cur++; continue; } if(ch == '>') { _set_tag_state(state, SVG_NO_TAG); } else { token->cur_attr = NULL; _set_tag_state(state, SVG_ATTR_NAME); } continue; } } break; case SVG_ATTR_NAME: { if(!token->cur_attr) { token->cur_attr = _new_svg_attr(token); } char ch = *(state->cur); if(isspace(ch) || ch == '=' || ch == '/' || ch == '>') { token->cur_attr->name_end = state->cur; _set_tag_state(state, SVG_SEARCH_EQUAL); continue; } else { if(!token->cur_attr->name_start) { token->cur_attr->name_start = state->cur; } state->cur++; continue; } } break; case SVG_SEARCH_EQUAL: { char ch = *(state->cur); if(!isspace(ch) && ch != '/' && ch != '\'' && ch != '\"') { if(ch == '=') { _set_tag_state(state, SVG_SEARCH_VALUE); } else { // attr name has empty value token->cur_attr = NULL; _set_tag_state(state, SVG_ATTR_START); continue; } } } break; case SVG_SEARCH_VALUE: { char ch = *(state->cur); if(!isspace(ch)) { if(ch == '\'' || ch == '\"') { if(ch == '\'') { _set_quote_state(state, SVG_SINGLE_QUOTE); } else { _set_quote_state(state, SVG_DOUBLE_QUOTE); } _set_tag_state(state, SVG_QUOTE_VALUE); } else { _set_tag_state(state, SVG_VALUE); continue; } } } break; case SVG_QUOTE_VALUE: { char ch = *(state->cur); if((ch == '\'' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_SINGLE_QUOTE) || (ch == '\"' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_DOUBLE_QUOTE)) { if(!token->cur_attr->value_start) { token->cur_attr->value_start = state->cur; } token->cur_attr->value_end = state->cur; _set_quote_state(state, SVG_NO_QUOTE); _set_tag_state(state, SVG_ATTR_START); continue; } else { if(!token->cur_attr->value_start) { token->cur_attr->value_start = state->cur; } state->cur++; continue; } } break; case SVG_VALUE: { char ch = *(state->cur); if(isspace(ch) || ch == '>' || ch == '/') { if(!token->cur_attr->value_start) { token->cur_attr->value_start = state->cur; } token->cur_attr->value_end = state->cur; _set_quote_state(state, SVG_NO_QUOTE); _set_tag_state(state, SVG_ATTR_START); continue; } else { if(!token->cur_attr->value_start) { token->cur_attr->value_start = state->cur; } state->cur++; continue; } } break; } state->cur++; } return true; } /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ bool _lv_svg_tokenizer(const char * svg_data, uint32_t data_len, svg_token_process cb, void * data) { LV_ASSERT_NULL(svg_data); LV_ASSERT(data_len > 0); LV_ASSERT_NULL(cb); LV_ASSERT_NULL(data); _lv_svg_token_t token; _lv_svg_token_init(&token); _lv_svg_parser_state_t state = { .flags = 0, .cur = svg_data, .end = svg_data + data_len, }; while(state.cur <= state.end) { char ch = *(state.cur); if(ch == '\r' || ch == '\n') { // skip LR character state.cur++; continue; } else if(_special_handle(&state)) { if(_is_state(&state, SVG_TAG)) { _clear_state(&state, SVG_TAG); switch(ch) { case '/': // end tag _set_tag_state(&state, SVG_TAG_NAME); break; case '!': { //