1 /**
2  * @file lv_svg_token.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 
10 #include "lv_svg_token.h"
11 #if LV_USE_SVG
12 
13 #include "../../../lvgl.h"
14 #include <ctype.h>
15 #include <string.h>
16 
17 /*********************
18 *      DEFINES
19 *********************/
20 
21 /**********************
22 *      TYPEDEFS
23 **********************/
24 
25 /*
26  *  tag mask     quote mask   tag   search  comment  doc type  xml inst
27  * |   0 0 0   |    0 0     |  0  |   0   |    0   |    0    |    0    |
28  */
29 enum {
30     SVG_TAG_MASK   = (1 << 3) - 1,
31     SVG_QUOTE_MASK = (1 << 5) - (1 << 3),
32     SVG_TAG        = 1 << 5,
33     SVG_SEARCH     = 1 << 6,
34     SVG_COMMENT    = 1 << 7,
35     SVG_DOCTYPE    = 1 << 8,
36     SVG_XMLINST    = 1 << 9,
37 };
38 typedef uint32_t _lv_svg_parser_bits_t;
39 
40 enum {
41     SVG_NO_QUOTE = 0,
42     SVG_SINGLE_QUOTE = 1,
43     SVG_DOUBLE_QUOTE = 2,
44 };
45 typedef uint32_t _lv_svg_parser_quote_t;
46 
47 enum {
48     SVG_NO_TAG       = 0,
49     SVG_TAG_NAME     = 1,
50     SVG_ATTR_START   = 2,
51     SVG_ATTR_NAME    = 3,
52     SVG_SEARCH_EQUAL = 4,
53     SVG_SEARCH_VALUE = 5,
54     SVG_QUOTE_VALUE  = 6,
55     SVG_VALUE        = 7,
56 };
57 typedef uint32_t _lv_svg_parser_tag_state_t;
58 
59 typedef struct {
60     uint32_t flags;
61     const char * cur;
62     const char * end;
63 } _lv_svg_parser_state_t;
64 
65 /**********************
66  *  STATIC PROTOTYPES
67  **********************/
_set_state(_lv_svg_parser_state_t * state,uint32_t bit)68 static void _set_state(_lv_svg_parser_state_t * state, uint32_t bit)
69 {
70     state->flags |= bit;
71 }
72 
_clear_state(_lv_svg_parser_state_t * state,uint32_t bit)73 static void _clear_state(_lv_svg_parser_state_t * state, uint32_t bit)
74 {
75     state->flags &= ~bit;
76 }
77 
_is_state(_lv_svg_parser_state_t * state,uint32_t bit)78 static bool _is_state(_lv_svg_parser_state_t * state, uint32_t bit)
79 {
80     return state->flags & bit;
81 }
82 
_set_tag_state(_lv_svg_parser_state_t * state,uint32_t bit)83 static void _set_tag_state(_lv_svg_parser_state_t * state, uint32_t bit)
84 {
85     state->flags = (state->flags & ~SVG_TAG_MASK) | bit;
86 }
87 
_set_quote_state(_lv_svg_parser_state_t * state,uint32_t bit)88 static void _set_quote_state(_lv_svg_parser_state_t * state, uint32_t bit)
89 {
90     state->flags = (state->flags & ~SVG_QUOTE_MASK) | (bit << 3);
91 }
92 
_special_handle(_lv_svg_parser_state_t * state)93 static bool _special_handle(_lv_svg_parser_state_t * state)
94 {
95     return state->flags & (SVG_TAG | SVG_SEARCH | SVG_TAG_MASK | SVG_COMMENT | SVG_DOCTYPE | SVG_XMLINST);
96 }
97 
_lv_svg_token_init(_lv_svg_token_t * token)98 static void _lv_svg_token_init(_lv_svg_token_t * token)
99 {
100     token->start = NULL;
101     token->end = NULL;
102     token->type = LV_SVG_TOKEN_CONTENT;
103     token->flat = false;
104     token->cur_attr = NULL;
105     lv_array_init(&token->attrs, LV_ARRAY_DEFAULT_CAPACITY, sizeof(_lv_svg_token_attr_t));
106 }
107 
_lv_svg_token_reset(_lv_svg_token_t * token)108 static void _lv_svg_token_reset(_lv_svg_token_t * token)
109 {
110     token->start = NULL;
111     token->end = NULL;
112     token->type = LV_SVG_TOKEN_CONTENT;
113     token->flat = false;
114     token->cur_attr = NULL;
115     lv_array_clear(&token->attrs);
116 }
117 
_lv_svg_token_process(_lv_svg_token_t * token,svg_token_process cb,void * data)118 static bool _lv_svg_token_process(_lv_svg_token_t * token, svg_token_process cb, void * data)
119 {
120     if(!token->start || SVG_TOKEN_LEN(token) == 0)
121         return true;
122 
123     bool ret = cb(token, data);
124     _lv_svg_token_reset(token);
125     return ret;
126 }
127 
_new_svg_attr(_lv_svg_token_t * token)128 static _lv_svg_token_attr_t * _new_svg_attr(_lv_svg_token_t * token)
129 {
130     if((lv_array_size(&token->attrs) + 1) > lv_array_capacity(&token->attrs)) {
131         lv_array_resize(&token->attrs, token->attrs.capacity << 1);
132     }
133 
134     token->attrs.size++;
135     _lv_svg_token_attr_t * attr = lv_array_at(&token->attrs, token->attrs.size - 1);
136     lv_memset(attr, 0, sizeof(_lv_svg_token_attr_t));
137     return attr;
138 }
139 
_svg_parser_xml_inst(_lv_svg_parser_state_t * state,_lv_svg_token_t * token)140 static void _svg_parser_xml_inst(_lv_svg_parser_state_t * state, _lv_svg_token_t * token)
141 {
142     LV_UNUSED(token);
143 
144     while(state->cur <= state->end) {
145         char ch = *(state->cur);
146         if(ch == '>' && (*(state->cur - 1)) == '?') {
147             _clear_state(state, SVG_XMLINST);
148             state->cur++;
149             break;
150         }
151         state->cur++;
152     }
153 }
154 
_svg_parser_comment(_lv_svg_parser_state_t * state,_lv_svg_token_t * token)155 static void _svg_parser_comment(_lv_svg_parser_state_t * state, _lv_svg_token_t * token)
156 {
157     LV_UNUSED(token);
158 
159     while(state->cur <= state->end) {
160         char ch = *(state->cur);
161         if(ch == '>' && (*(state->cur - 1)) == '-' && (*(state->cur - 2)) == '-') {
162             _clear_state(state, SVG_COMMENT);
163             state->cur++;
164             break;
165         }
166         state->cur++;
167     }
168 }
169 
_svg_parser_doctype(_lv_svg_parser_state_t * state,_lv_svg_token_t * token)170 static void _svg_parser_doctype(_lv_svg_parser_state_t * state, _lv_svg_token_t * token)
171 {
172     LV_UNUSED(token);
173 
174     //TODO: processing DTD type
175     while(state->cur <= state->end) {
176         char ch = *(state->cur);
177         if(ch == '>') {
178             _clear_state(state, SVG_DOCTYPE);
179             state->cur++;
180             break;
181         }
182         state->cur++;
183     }
184 }
185 
_svg_parser_tag(_lv_svg_parser_state_t * state,_lv_svg_token_t * token,svg_token_process cb,void * data)186 static bool _svg_parser_tag(_lv_svg_parser_state_t * state, _lv_svg_token_t * token, svg_token_process cb, void * data)
187 {
188     while(state->cur <= state->end) {
189         switch(state->flags & SVG_TAG_MASK) {
190             case SVG_NO_TAG: {
191                     if(!_lv_svg_token_process(token, cb, data)) {
192                         return false;
193                     }
194                     state->cur++;
195                 }
196                 return true;
197             case SVG_TAG_NAME: {
198                     char ch = *(state->cur);
199                     if(ch == '/') {
200                         token->type = LV_SVG_TOKEN_END;
201                         state->cur++;
202                         if(!token->start) {
203                             token->start = state->cur;
204                         }
205                         continue;
206                     }
207                     else if(ch == '>' || isspace(ch)) {
208                         token->end = state->cur;
209                         _set_tag_state(state, SVG_ATTR_START);
210                         continue;
211                     }
212                     else {
213                         if(!token->start) {
214                             token->type = LV_SVG_TOKEN_BEGIN;
215                             token->start = state->cur;
216                         }
217                         state->cur++;
218                         continue;
219                     }
220                 }
221                 break;
222             case SVG_ATTR_START: {
223                     char ch = *(state->cur);
224                     if(!isspace(ch) && ch != '\'' && ch != '\"') {
225                         if(ch == '/') {
226                             token->flat = true;
227                             state->cur++;
228                             continue;
229                         }
230                         if(ch == '>') {
231                             _set_tag_state(state, SVG_NO_TAG);
232                         }
233                         else {
234                             token->cur_attr = NULL;
235                             _set_tag_state(state, SVG_ATTR_NAME);
236                         }
237                         continue;
238                     }
239                 }
240                 break;
241             case SVG_ATTR_NAME: {
242                     if(!token->cur_attr) {
243                         token->cur_attr = _new_svg_attr(token);
244                     }
245                     char ch = *(state->cur);
246                     if(isspace(ch) || ch == '=' || ch == '/' || ch == '>') {
247                         token->cur_attr->name_end = state->cur;
248                         _set_tag_state(state, SVG_SEARCH_EQUAL);
249                         continue;
250                     }
251                     else {
252                         if(!token->cur_attr->name_start) {
253                             token->cur_attr->name_start = state->cur;
254                         }
255                         state->cur++;
256                         continue;
257                     }
258                 }
259                 break;
260             case SVG_SEARCH_EQUAL: {
261                     char ch = *(state->cur);
262                     if(!isspace(ch) && ch != '/' && ch != '\'' && ch != '\"') {
263                         if(ch == '=') {
264                             _set_tag_state(state, SVG_SEARCH_VALUE);
265                         }
266                         else {
267                             // attr name has empty value
268                             token->cur_attr = NULL;
269                             _set_tag_state(state, SVG_ATTR_START);
270                             continue;
271                         }
272                     }
273                 }
274                 break;
275             case SVG_SEARCH_VALUE: {
276                     char ch = *(state->cur);
277                     if(!isspace(ch)) {
278                         if(ch == '\'' || ch == '\"') {
279                             if(ch == '\'') {
280                                 _set_quote_state(state, SVG_SINGLE_QUOTE);
281                             }
282                             else {
283                                 _set_quote_state(state, SVG_DOUBLE_QUOTE);
284                             }
285                             _set_tag_state(state, SVG_QUOTE_VALUE);
286                         }
287                         else {
288                             _set_tag_state(state, SVG_VALUE);
289                             continue;
290                         }
291                     }
292                 }
293                 break;
294             case SVG_QUOTE_VALUE: {
295                     char ch = *(state->cur);
296                     if((ch == '\'' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_SINGLE_QUOTE)
297                        || (ch == '\"' && ((state->flags & SVG_QUOTE_MASK) >> 3) == SVG_DOUBLE_QUOTE)) {
298                         if(!token->cur_attr->value_start) {
299                             token->cur_attr->value_start = state->cur;
300                         }
301                         token->cur_attr->value_end = state->cur;
302                         _set_quote_state(state, SVG_NO_QUOTE);
303                         _set_tag_state(state, SVG_ATTR_START);
304                         continue;
305                     }
306                     else {
307                         if(!token->cur_attr->value_start) {
308                             token->cur_attr->value_start = state->cur;
309                         }
310                         state->cur++;
311                         continue;
312                     }
313                 }
314                 break;
315             case SVG_VALUE: {
316                     char ch = *(state->cur);
317                     if(isspace(ch) || ch == '>' || ch == '/') {
318                         if(!token->cur_attr->value_start) {
319                             token->cur_attr->value_start = state->cur;
320                         }
321                         token->cur_attr->value_end = state->cur;
322                         _set_quote_state(state, SVG_NO_QUOTE);
323                         _set_tag_state(state, SVG_ATTR_START);
324                         continue;
325                     }
326                     else {
327                         if(!token->cur_attr->value_start) {
328                             token->cur_attr->value_start = state->cur;
329                         }
330                         state->cur++;
331                         continue;
332                     }
333                 }
334                 break;
335         }
336         state->cur++;
337     }
338     return true;
339 }
340 
341 /**********************
342  *  STATIC VARIABLES
343  **********************/
344 
345 /**********************
346  *      MACROS
347  **********************/
348 
349 /**********************
350  *   GLOBAL FUNCTIONS
351  **********************/
352 
_lv_svg_tokenizer(const char * svg_data,uint32_t data_len,svg_token_process cb,void * data)353 bool _lv_svg_tokenizer(const char * svg_data, uint32_t data_len, svg_token_process cb, void * data)
354 {
355     LV_ASSERT_NULL(svg_data);
356     LV_ASSERT(data_len > 0);
357     LV_ASSERT_NULL(cb);
358     LV_ASSERT_NULL(data);
359 
360     _lv_svg_token_t token;
361     _lv_svg_token_init(&token);
362     _lv_svg_parser_state_t state = {
363         .flags = 0,
364         .cur = svg_data,
365         .end = svg_data + data_len,
366     };
367 
368     while(state.cur <= state.end) {
369         char ch = *(state.cur);
370         if(ch == '\r' || ch == '\n') { // skip LR character
371             state.cur++;
372             continue;
373         }
374         else if(_special_handle(&state)) {
375             if(_is_state(&state, SVG_TAG)) {
376                 _clear_state(&state, SVG_TAG);
377                 switch(ch) {
378                     case '/': // end tag
379                         _set_tag_state(&state, SVG_TAG_NAME);
380                         break;
381                     case '!': {
382                             // <!-- comment or <!DOCTYPE>
383                             _set_state(&state, SVG_SEARCH); // get more character
384                             state.cur++;
385                         }
386                         break;
387                     case '?': {
388                             // xml instruction
389                             _set_state(&state, SVG_XMLINST);
390                             state.cur++;
391                         }
392                         break;
393                     default: {
394                             if(isalpha(ch)) {
395                                 _set_tag_state(&state, SVG_TAG_NAME);
396                             }
397                             else {
398                                 LV_LOG_ERROR("svg document parser error!");
399                                 lv_array_deinit(&token.attrs);
400                                 return false;
401                             }
402                         }
403                 }
404                 // process token
405                 if(!_lv_svg_token_process(&token, cb, data)) {
406                     LV_LOG_ERROR("svg document parser error!");
407                     lv_array_deinit(&token.attrs);
408                     return false;
409                 }
410             }
411             else if(_is_state(&state, SVG_SEARCH)) {
412                 if(ch == '-' || isalpha(ch)) {
413                     if(!token.start) {
414                         token.start = state.cur;
415                     }
416                     token.end = state.cur;
417                 }
418                 else {
419                     // processing as a normal tag name.
420                     _clear_state(&state, SVG_SEARCH);
421                     _set_tag_state(&state, SVG_TAG_NAME);
422                     continue;
423                 }
424 
425                 if(((token.end - token.start) == 1) && (token.start[0] == '-') && (token.start[1] == '-')) {
426                     // is <!-- comment start
427                     _clear_state(&state, SVG_SEARCH);
428                     token.start = token.end = NULL;
429                     _set_state(&state, SVG_COMMENT);
430                 }
431                 else if(((token.end - token.start) == 6) && (strncmp(token.start, "DOCTYPE", 7) == 0)) {
432                     _clear_state(&state, SVG_SEARCH);
433                     token.start = token.end = NULL;
434                     _set_state(&state, SVG_DOCTYPE);
435                 }
436                 state.cur++;
437             }
438             else if(_is_state(&state, SVG_COMMENT)) {
439                 _svg_parser_comment(&state, &token);
440             }
441             else if(_is_state(&state, SVG_DOCTYPE)) {
442                 _svg_parser_doctype(&state, &token);
443             }
444             else if(_is_state(&state, SVG_TAG_MASK)) {
445                 if(!_svg_parser_tag(&state, &token, cb, data)) {
446                     LV_LOG_ERROR("svg document parser error!");
447                     lv_array_deinit(&token.attrs);
448                     return false;
449                 }
450             }
451             else if(_is_state(&state, SVG_XMLINST)) {
452                 _svg_parser_xml_inst(&state, &token);
453             }
454         }
455         else {
456             switch(ch) {
457                 case '<': {
458                         _set_state(&state, SVG_TAG); // start a new tag
459                         state.cur++;
460                     }
461                     break;
462                 default: {
463                         if(!token.start) {
464                             token.start = state.cur;
465                         }
466                         if(state.cur == state.end) {
467                             goto finish;
468                         }
469                         token.end = ++state.cur;
470                     }
471             }
472         }
473     }
474 
475 finish:
476     lv_array_deinit(&token.attrs);
477     return true;
478 }
479 
480 /**********************
481  *   STATIC FUNCTIONS
482  **********************/
483 #endif /*LV_USE_SVG*/
484