mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-10 04:37:55 +08:00
feat(XML): XML load from directory (#8774)
This commit is contained in:
@@ -184,6 +184,9 @@ The Component XML can be loaded, and any number of instances can be created at r
|
||||
In the simplest case, a Component can be registered with
|
||||
:cpp:expr:`lv_xml_component_register_from_file("A:path/to/my_button.xml")` and an instance can be created with
|
||||
:cpp:expr:`lv_obj_t * obj = lv_xml_create(parent, "my_button", NULL)`.
|
||||
:cpp:expr:`lv_xml_load_all_from_path("A:path/to/dir")`
|
||||
will traverse a directory and register all the XML components,
|
||||
screens, globals, and translations.
|
||||
|
||||
Note that loading the UI from XML has practically no impact on performance.
|
||||
Once the XML files are registered and the UI is created, it behaves the same way
|
||||
|
||||
@@ -537,6 +537,31 @@ const char * lv_fs_get_last(const char * path)
|
||||
|
||||
return &path[i + 1];
|
||||
}
|
||||
|
||||
int lv_fs_path_join(char * buf, size_t buf_sz, const char * base, const char * end)
|
||||
{
|
||||
if(base[0] == '\0') return lv_strlcpy(buf, end, buf_sz);
|
||||
if(end[0] == '\0') return lv_strlcpy(buf, base, buf_sz);
|
||||
|
||||
size_t base_len = lv_strlen(base);
|
||||
char base_end_char = base_len ? base[base_len - 1] : '\0';
|
||||
|
||||
bool base_has_sep = base_end_char == '/' || base_end_char == '\\';
|
||||
bool end_has_sep = end[0] == '/' || end[0] == '\\';
|
||||
|
||||
if(base_has_sep && end_has_sep) {
|
||||
end++;
|
||||
end_has_sep = false;
|
||||
}
|
||||
|
||||
const char * sep = "/";
|
||||
if(base_has_sep || end_has_sep) {
|
||||
sep = "";
|
||||
}
|
||||
|
||||
return lv_snprintf(buf, buf_sz, "%s%s%s", base, sep, end);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* STATIC FUNCTIONS
|
||||
**********************/
|
||||
|
||||
@@ -275,6 +275,19 @@ char * lv_fs_up(char * path);
|
||||
*/
|
||||
const char * lv_fs_get_last(const char * path);
|
||||
|
||||
/**
|
||||
* Concatenate two path components and automatically add/remove a separator as needed.
|
||||
* buf, buf_sz, and the return value are analogous to lv_snprintf
|
||||
* @param buf the buffer to place the result in
|
||||
* @param buf_sz the size of buf. At most buf_sz - 1 characters will be written to buf,
|
||||
* and a null terminator
|
||||
* @param base the first path component
|
||||
* @param end the second path component
|
||||
* @return the number of characters (not including the null terminator)
|
||||
* that would be written to buf, even if buf_sz-1 was smaller
|
||||
*/
|
||||
int lv_fs_path_join(char * buf, size_t buf_sz, const char * base, const char * end);
|
||||
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
|
||||
@@ -22,6 +22,7 @@ extern "C" {
|
||||
#include "lv_xml_translation.h"
|
||||
#include "lv_xml_component.h"
|
||||
#include "lv_xml_widget.h"
|
||||
#include "lv_xml_load.h"
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @file lv_xml_load.c
|
||||
*
|
||||
*/
|
||||
|
||||
/*********************
|
||||
* INCLUDES
|
||||
*********************/
|
||||
|
||||
#include "lv_xml_load.h"
|
||||
#if LV_USE_XML
|
||||
|
||||
#include "lv_xml_private.h"
|
||||
#include "../../libs/expat/expat.h"
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
*********************/
|
||||
|
||||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
|
||||
typedef enum {
|
||||
LV_XML_TYPE_UNKNOWN,
|
||||
LV_XML_TYPE_COMPONENT,
|
||||
LV_XML_TYPE_TRANSLATIONS,
|
||||
} lv_xml_type_t;
|
||||
|
||||
typedef struct {
|
||||
XML_Parser parser;
|
||||
lv_xml_type_t type;
|
||||
} load_from_file_parser_data_t;
|
||||
|
||||
/**********************
|
||||
* STATIC PROTOTYPES
|
||||
**********************/
|
||||
|
||||
static lv_result_t load_all_recursive(char * path_buf, const char * path);
|
||||
static void load_from_file(const char * path);
|
||||
static char * path_filename_without_extension(const char * path);
|
||||
static void load_from_file_start_element_handler(void * user_data, const char * name, const char ** attrs);
|
||||
|
||||
/**********************
|
||||
* STATIC VARIABLES
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* GLOBAL FUNCTIONS
|
||||
**********************/
|
||||
|
||||
lv_result_t lv_xml_load_all_from_path(const char * path)
|
||||
{
|
||||
char path_buf[LV_FS_MAX_PATH_LENGTH];
|
||||
|
||||
/* set the default asset path to the pack path so XML asset paths are relative to it */
|
||||
const char * asset_path = path;
|
||||
/* end the asset path with a '/' */
|
||||
char path_last_char = path[0] ? path[lv_strlen(path) - 1] : '\0';
|
||||
if(path_last_char != '\0' && path_last_char != ':'
|
||||
&& path_last_char != '/' && path_last_char != '\\') {
|
||||
lv_snprintf(path_buf, sizeof(path_buf), "%s/", path);
|
||||
asset_path = path_buf;
|
||||
}
|
||||
lv_xml_set_default_asset_path(asset_path);
|
||||
|
||||
return load_all_recursive(path_buf, path);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* STATIC FUNCTIONS
|
||||
**********************/
|
||||
|
||||
static lv_result_t load_all_recursive(char * path_buf, const char * path)
|
||||
{
|
||||
lv_result_t ret = LV_RESULT_OK;
|
||||
lv_fs_res_t fs_res;
|
||||
|
||||
lv_fs_dir_t dir;
|
||||
fs_res = lv_fs_dir_open(&dir, path);
|
||||
if(fs_res != LV_FS_RES_OK) {
|
||||
LV_LOG_WARN("Couldn't open directory %s", path);
|
||||
return LV_RESULT_INVALID;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
fs_res = lv_fs_dir_read(&dir, path_buf, LV_FS_MAX_PATH_LENGTH);
|
||||
if(fs_res != LV_FS_RES_OK) {
|
||||
LV_LOG_WARN("Couldn't read directory %s", path);
|
||||
ret = LV_RESULT_INVALID;
|
||||
goto dir_close_out;
|
||||
}
|
||||
|
||||
if(path_buf[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
int full_path_len = lv_fs_path_join(NULL, 0, path, path_buf);
|
||||
char * full_path = lv_malloc(full_path_len + 1);
|
||||
LV_ASSERT_MALLOC(full_path);
|
||||
lv_fs_path_join(full_path, full_path_len + 1, path, path_buf);
|
||||
|
||||
if(path_buf[0] == '/') {
|
||||
ret = load_all_recursive(path_buf, full_path);
|
||||
}
|
||||
else {
|
||||
load_from_file(full_path);
|
||||
}
|
||||
|
||||
lv_free(full_path);
|
||||
|
||||
if(ret != LV_RESULT_OK) {
|
||||
goto dir_close_out;
|
||||
}
|
||||
}
|
||||
|
||||
dir_close_out:
|
||||
fs_res = lv_fs_dir_close(&dir);
|
||||
if(fs_res != LV_FS_RES_OK) {
|
||||
LV_LOG_WARN("Error closing directory %s", path);
|
||||
ret = LV_RESULT_INVALID;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void load_from_file(const char * path)
|
||||
{
|
||||
const char * ext = lv_fs_get_ext(path);
|
||||
|
||||
if(lv_streq(ext, "xml")) {
|
||||
lv_fs_res_t fs_res;
|
||||
lv_fs_file_t f;
|
||||
fs_res = lv_fs_open(&f, path, LV_FS_MODE_RD);
|
||||
if(fs_res != LV_FS_RES_OK) {
|
||||
LV_LOG_WARN("Couldn't open %s", path);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Determine file size */
|
||||
lv_fs_seek(&f, 0, LV_FS_SEEK_END);
|
||||
uint32_t file_size = 0;
|
||||
lv_fs_tell(&f, &file_size);
|
||||
lv_fs_seek(&f, 0, LV_FS_SEEK_SET);
|
||||
|
||||
/* Create the buffer */
|
||||
char * xml_buf = lv_malloc(file_size + 1);
|
||||
if(xml_buf == NULL) {
|
||||
LV_LOG_WARN("Memory allocation failed for file %s (%d bytes)", path, file_size + 1);
|
||||
lv_fs_close(&f);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read the file content */
|
||||
uint32_t rn;
|
||||
fs_res = lv_fs_read(&f, xml_buf, file_size, &rn);
|
||||
if(fs_res != LV_FS_RES_OK || rn != file_size) {
|
||||
LV_LOG_WARN("Couldn't read %s fully", path);
|
||||
lv_free(xml_buf);
|
||||
lv_fs_close(&f);
|
||||
return;
|
||||
}
|
||||
|
||||
lv_fs_close(&f);
|
||||
|
||||
/* Null-terminate the buffer */
|
||||
xml_buf[rn] = '\0';
|
||||
|
||||
load_from_file_parser_data_t parser_data;
|
||||
|
||||
XML_Memory_Handling_Suite mem_handlers;
|
||||
mem_handlers.malloc_fcn = lv_malloc;
|
||||
mem_handlers.realloc_fcn = lv_realloc;
|
||||
mem_handlers.free_fcn = lv_free;
|
||||
XML_Parser parser = XML_ParserCreate_MM(NULL, &mem_handlers, NULL);
|
||||
parser_data.parser = parser;
|
||||
parser_data.type = LV_XML_TYPE_UNKNOWN;
|
||||
XML_SetUserData(parser, &parser_data);
|
||||
XML_SetStartElementHandler(parser, load_from_file_start_element_handler);
|
||||
|
||||
/* Parse the XML */
|
||||
enum XML_Status parser_res = XML_Parse(parser, xml_buf, file_size, XML_TRUE);
|
||||
enum XML_Error parser_error = XML_GetErrorCode(parser);
|
||||
if(parser_res == XML_STATUS_ERROR && parser_error != XML_ERROR_ABORTED) {
|
||||
LV_LOG_WARN("XML parsing error: %s on line %lu", XML_ErrorString(parser_error),
|
||||
XML_GetCurrentLineNumber(parser));
|
||||
XML_ParserFree(parser);
|
||||
lv_free(xml_buf);
|
||||
return;
|
||||
}
|
||||
XML_ParserFree(parser);
|
||||
|
||||
switch(parser_data.type) {
|
||||
case LV_XML_TYPE_COMPONENT: {
|
||||
char * component_name = path_filename_without_extension(path);
|
||||
lv_xml_component_register_from_data(component_name, xml_buf);
|
||||
lv_free(component_name);
|
||||
break;
|
||||
}
|
||||
case LV_XML_TYPE_TRANSLATIONS:
|
||||
#if LV_USE_TRANSLATION
|
||||
lv_xml_translation_register_from_data(xml_buf);
|
||||
#else
|
||||
LV_LOG_WARN("Translation XML found but translations not enabled");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
LV_LOG_WARN("Unknown XML type found in pack");
|
||||
break;
|
||||
}
|
||||
|
||||
lv_free(xml_buf);
|
||||
}
|
||||
else {
|
||||
LV_LOG_INFO("Did not use '%s' from XML pack.", path);
|
||||
}
|
||||
}
|
||||
|
||||
static char * path_filename_without_extension(const char * path)
|
||||
{
|
||||
const char * last = lv_fs_get_last(path);
|
||||
const char * ext = lv_fs_get_ext(last);
|
||||
char * filename = lv_strdup(last);
|
||||
LV_ASSERT_MALLOC(filename);
|
||||
if(ext[0]) {
|
||||
filename[lv_strlen(last) - lv_strlen(ext) - 1] = '\0'; /*Trim the extension*/
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
static void load_from_file_start_element_handler(void * user_data, const char * name, const char ** attrs)
|
||||
{
|
||||
LV_UNUSED(attrs);
|
||||
load_from_file_parser_data_t * data = user_data;
|
||||
|
||||
if(lv_streq(name, "component") || lv_streq(name, "screen") || lv_streq(name, "globals")) {
|
||||
data->type = LV_XML_TYPE_COMPONENT;
|
||||
}
|
||||
else if(lv_streq(name, "translations")) {
|
||||
data->type = LV_XML_TYPE_TRANSLATIONS;
|
||||
}
|
||||
|
||||
XML_StopParser(data->parser, XML_FALSE);
|
||||
}
|
||||
|
||||
#endif /*LV_USE_XML*/
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file lv_xml_load.h
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LV_XML_LOAD_H
|
||||
#define LV_XML_LOAD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*********************
|
||||
* INCLUDES
|
||||
*********************/
|
||||
|
||||
#include "../../misc/lv_types.h"
|
||||
#if LV_USE_XML
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
*********************/
|
||||
|
||||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* GLOBAL PROTOTYPES
|
||||
**********************/
|
||||
|
||||
/**
|
||||
* Recurse into a directory, loading all XML components,
|
||||
* screens, globals, and translations.
|
||||
* @param path the path to a directory to load files from
|
||||
* @return `LV_RESULT_OK` if there were no issues or
|
||||
* `LV_RESULT_INVALID` otherwise.
|
||||
*/
|
||||
lv_result_t lv_xml_load_all_from_path(const char * path);
|
||||
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
|
||||
#endif /*LV_USE_XML*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /*extern "C"*/
|
||||
#endif
|
||||
|
||||
#endif /*LV_XML_LOAD_H*/
|
||||
Reference in New Issue
Block a user