1 /**
2  * @file lv_xml_component.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_xml_component.h"
10 #if LV_USE_XML
11 
12 #include "lv_xml_component_private.h"
13 #include "lv_xml_private.h"
14 #include "lv_xml_parser.h"
15 #include "lv_xml_style.h"
16 #include "lv_xml_base_types.h"
17 #include "lv_xml_widget.h"
18 #include "parsers/lv_xml_obj_parser.h"
19 #include "../../libs/expat/expat.h"
20 #include "../../misc/lv_fs.h"
21 #include <string.h>
22 
23 /*********************
24  *      DEFINES
25  *********************/
26 
27 /**********************
28  *      TYPEDEFS
29  **********************/
30 
31 /**********************
32  *  STATIC PROTOTYPES
33  **********************/
34 static void start_metadata_handler(void * user_data, const char * name, const char ** attrs);
35 static void end_metadata_handler(void * user_data, const char * name);
36 static void process_const_element(lv_xml_parser_state_t * state, const char ** attrs);
37 static void process_prop_element(lv_xml_parser_state_t * state, const char ** attrs);
38 static char * extract_view_content(const char * xml_definition);
39 
40 /**********************
41  *  STATIC VARIABLES
42  **********************/
43 
44 static lv_ll_t component_ctx_ll;
45 
46 /**********************
47  *      MACROS
48  **********************/
49 
50 /**********************
51  *   GLOBAL FUNCTIONS
52  **********************/
53 
lv_xml_component_init(void)54 void lv_xml_component_init(void)
55 {
56     lv_ll_init(&component_ctx_ll, sizeof(lv_xml_component_ctx_t));
57 }
58 
59 
lv_xml_component_process(lv_xml_parser_state_t * state,const char * name,const char ** attrs)60 lv_obj_t * lv_xml_component_process(lv_xml_parser_state_t * state, const char * name, const char ** attrs)
61 {
62     lv_xml_component_ctx_t * ctx = lv_xml_component_get_ctx(name);
63     if(ctx == NULL) return NULL;
64     lv_obj_t * item = lv_xml_create_from_ctx(state->parent, &state->ctx, ctx, attrs);
65     if(item == NULL) {
66         LV_LOG_WARN("Couldn't create component '%s'", name);
67         return NULL;
68     }
69 
70     /* Apply the properties of the component, e.g. <my_button x="20" styles="red"/> */
71     state->item = item;
72     ctx->root_widget->apply_cb(state, attrs);
73 
74     return item;
75 }
76 
lv_xml_component_get_ctx(const char * component_name)77 lv_xml_component_ctx_t * lv_xml_component_get_ctx(const char * component_name)
78 {
79     lv_xml_component_ctx_t * ctx;
80     LV_LL_READ(&component_ctx_ll, ctx) {
81         if(lv_streq(ctx->name, component_name)) return ctx;
82     }
83 
84     return NULL;
85 }
86 
lv_xml_component_register_from_data(const char * name,const char * xml_def)87 lv_result_t lv_xml_component_register_from_data(const char * name, const char * xml_def)
88 {
89     /* Create a temporary parser state to extract styles/params/consts */
90     lv_xml_parser_state_t state;
91     lv_xml_parser_state_init(&state);
92     state.ctx.name = name;
93 
94     /* Parse the XML to extract metadata */
95     XML_Parser parser = XML_ParserCreate(NULL);
96     XML_SetUserData(parser, &state);
97     XML_SetElementHandler(parser, start_metadata_handler, end_metadata_handler);
98 
99     if(XML_Parse(parser, xml_def, lv_strlen(xml_def), XML_TRUE) == XML_STATUS_ERROR) {
100         LV_LOG_WARN("XML parsing error: %s on line %lu",
101                     XML_ErrorString(XML_GetErrorCode(parser)),
102                     (unsigned long)XML_GetCurrentLineNumber(parser));
103         XML_ParserFree(parser);
104         return LV_RESULT_INVALID;
105     }
106 
107     XML_ParserFree(parser);
108 
109     /* Copy extracted metadata to component processor */
110     lv_xml_component_ctx_t * ctx = lv_ll_ins_head(&component_ctx_ll);
111     lv_memzero(ctx, sizeof(lv_xml_component_ctx_t));
112     lv_memcpy(ctx, &state.ctx, sizeof(lv_xml_component_ctx_t));
113 
114     /* Extract view content directly instead of using XML parser */
115     ctx->view_def = extract_view_content(xml_def);
116     ctx->name = lv_strdup(name);
117     if(!ctx->view_def) {
118         LV_LOG_WARN("Failed to extract view content");
119         /* Clean up and return error */
120         lv_free(ctx);
121         return LV_RESULT_INVALID;
122     }
123 
124     return LV_RESULT_OK;
125 }
126 
127 
lv_xml_component_register_from_file(const char * path)128 lv_result_t lv_xml_component_register_from_file(const char * path)
129 {
130     /* Extract component name from path */
131     /* Create a copy of the filename to modify */
132     char * filename = lv_strdup(lv_fs_get_last(path));
133     const char * ext = lv_fs_get_ext(filename);
134     filename[lv_strlen(filename) - lv_strlen(ext) - 1] = '\0'; /*Trim the extension*/
135 
136     lv_fs_res_t fs_res;
137     lv_fs_file_t f;
138     fs_res = lv_fs_open(&f, path, LV_FS_MODE_RD);
139     if(fs_res != LV_FS_RES_OK) {
140         LV_LOG_WARN("Couldn't open %s", path);
141         lv_free(filename);
142         return LV_RESULT_INVALID;
143     }
144 
145     /* Determine file size */
146     lv_fs_seek(&f, 0, LV_FS_SEEK_END);
147     uint32_t file_size = 0;
148     lv_fs_tell(&f, &file_size);
149     lv_fs_seek(&f, 0, LV_FS_SEEK_SET);
150 
151     /* Create the buffer */
152     char * xml_buf = lv_malloc(file_size + 1);
153     if(xml_buf == NULL) {
154         LV_LOG_WARN("Memory allocation failed for file %s (%d bytes)", path, file_size + 1);
155         lv_free(filename);
156         lv_fs_close(&f);
157         return LV_RESULT_INVALID;
158     }
159 
160     /* Read the file content  */
161     uint32_t rn;
162     lv_fs_read(&f, xml_buf, file_size, &rn);
163     if(rn != file_size) {
164         LV_LOG_WARN("Couldn't read %s fully", path);
165         lv_free(filename);
166         lv_free(xml_buf);
167         lv_fs_close(&f);
168         return LV_RESULT_INVALID;
169     }
170 
171     /* Null-terminate the buffer */
172     xml_buf[rn] = '\0';
173 
174     /* Register the component */
175     lv_result_t res = lv_xml_component_register_from_data(filename, xml_buf);
176 
177     /* Housekeeping */
178     lv_free(filename);
179     lv_free(xml_buf);
180     lv_fs_close(&f);
181 
182     return res;
183 }
184 
lv_xml_component_unregister(const char * name)185 lv_result_t lv_xml_component_unregister(const char * name)
186 {
187     lv_xml_component_ctx_t * ctx = lv_xml_component_get_ctx(name);
188     if(ctx == NULL) return LV_RESULT_INVALID;
189 
190     lv_ll_remove(&component_ctx_ll, ctx);
191 
192     lv_free((char *)ctx->name);
193     lv_free((char *)ctx->view_def);
194     lv_ll_clear(&ctx->param_ll);
195     lv_ll_clear(&ctx->style_ll);
196     lv_free(ctx);
197 
198     return LV_RESULT_OK;
199 }
200 
201 
202 /**********************
203  *   STATIC FUNCTIONS
204  **********************/
205 
process_const_element(lv_xml_parser_state_t * state,const char ** attrs)206 static void process_const_element(lv_xml_parser_state_t * state, const char ** attrs)
207 {
208     const char * name = lv_xml_get_value_of(attrs, "name");
209     const char * value = lv_xml_get_value_of(attrs, "value");
210 
211     if(name == NULL) {
212         LV_LOG_WARN("'name' is missing from a constant");
213         return;
214     }
215     if(value == NULL) {
216         LV_LOG_WARN("'value' is missing from a constant");
217         return;
218     }
219 
220     lv_xml_const_t * cnst = lv_ll_ins_tail(&state->ctx.const_ll);
221     cnst->name = lv_strdup(name);
222     cnst->value = lv_strdup(value);
223 }
224 
process_prop_element(lv_xml_parser_state_t * state,const char ** attrs)225 static void process_prop_element(lv_xml_parser_state_t * state, const char ** attrs)
226 {
227     lv_xml_param_t * prop = lv_ll_ins_tail(&state->ctx.param_ll);
228     prop->name = lv_strdup(lv_xml_get_value_of(attrs, "name"));
229     const char * def = lv_xml_get_value_of(attrs, "default");
230     if(def) prop->def = lv_strdup(def);
231     else prop->def = NULL;
232 
233     const char * type = lv_xml_get_value_of(attrs, "type");
234     if(type == NULL) type = "compound"; /*If there in no type it means there are <param>s*/
235     prop->type = lv_strdup(type);
236 }
237 
start_metadata_handler(void * user_data,const char * name,const char ** attrs)238 static void start_metadata_handler(void * user_data, const char * name, const char ** attrs)
239 {
240     lv_xml_parser_state_t * state = (lv_xml_parser_state_t *)user_data;
241 
242     lv_xml_parser_section_t old_section = state->section;
243     lv_xml_parser_start_section(state, name);
244     if(lv_streq(name, "view")) {
245         const char * extends = lv_xml_get_value_of(attrs, "extends");
246         if(extends == NULL) extends = "lv_obj";
247 
248         state->ctx.root_widget = lv_xml_widget_get_processor(extends);
249         if(state->ctx.root_widget == NULL) {
250             lv_xml_component_ctx_t * extended_component = lv_xml_component_get_ctx(extends);
251             if(extended_component) {
252                 state->ctx.root_widget = extended_component->root_widget;
253             }
254             else {
255                 LV_LOG_WARN("The 'extend'ed widget is not found, using `lv_obj` as a fall back");
256                 state->ctx.root_widget = lv_xml_widget_get_processor("lv_obj");
257             }
258         }
259     }
260 
261     if(lv_streq(name, "widget")) state->ctx.is_widget = 1;
262 
263     if(old_section != state->section) return;   /*Ignore the section opening, e.g. <styles>*/
264 
265     /* Process elements based on current context */
266     switch(state->section) {
267         case LV_XML_PARSER_SECTION_API:
268             process_prop_element(state, attrs);
269             break;
270 
271         case LV_XML_PARSER_SECTION_CONSTS:
272             process_const_element(state, attrs);
273             break;
274 
275         case LV_XML_PARSER_SECTION_STYLES:
276             if(lv_streq(name, "style")) {
277                 lv_xml_style_register(&state->ctx, attrs);
278             }
279             break;
280 
281         default:
282             break;
283     }
284 }
285 
end_metadata_handler(void * user_data,const char * name)286 static void end_metadata_handler(void * user_data, const char * name)
287 {
288     lv_xml_parser_state_t * state = (lv_xml_parser_state_t *)user_data;
289     lv_xml_parser_end_section(state, name);
290 }
291 
extract_view_content(const char * xml_definition)292 static char * extract_view_content(const char * xml_definition)
293 {
294     if(!xml_definition) return NULL;
295 
296     /* Find start of view tag */
297     const char * start = strstr(xml_definition, "<view");
298     if(!start) return NULL;
299 
300     /* Find end of view tag */
301     const char * end = strstr(xml_definition, "</view>");
302     if(!end) return NULL;
303     end += 7; /* Include "</view>" in result */
304 
305     /* Calculate and allocate length */
306     size_t len = end - start;
307     char * view_content = lv_malloc(len + 1);
308     if(!view_content) return NULL;
309 
310     /* Copy content and null terminate */
311     lv_memcpy(view_content, start, len);
312     view_content[len] = '\0';
313 
314     return view_content;
315 }
316 
317 #endif /* LV_USE_XML */
318