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