//////////////////////////////////////// // shell_tinyxml_read.cpp // // This file implements the means to read an XML encoded file and populate the // CLI tree based on the contents. //////////////////////////////////////// extern "C" { #include "private.h" #include "lub/string.h" #include "lub/ctype.h" } /*lint +libh(tinyxml/tinyxml.h) Add this to the library file list */ #include #include "tinyxml/tinyxml.h" #include #include typedef void (PROCESS_FN) (clish_shell_t * instance, TiXmlElement * element, void *parent); // Define a control block for handling the decode of an XML file typedef struct clish_xml_cb_s clish_xml_cb_t; struct clish_xml_cb_s { const char *element; PROCESS_FN *handler; }; // forward declare the handler functions static PROCESS_FN process_clish_module, process_startup, process_view, process_command, process_param, process_action, process_ptype, process_overview, process_detail, process_namespace, process_config, process_var, process_wdog, process_hotkey; static clish_xml_cb_t xml_elements[] = { {"CLISH_MODULE", process_clish_module}, {"STARTUP", process_startup}, {"VIEW", process_view}, {"COMMAND", process_command}, {"PARAM", process_param}, {"ACTION", process_action}, {"PTYPE", process_ptype}, {"OVERVIEW", process_overview}, {"DETAIL", process_detail}, {"NAMESPACE", process_namespace}, {"CONFIG", process_config}, {"VAR", process_var}, {"WATCHDOG", process_wdog}, {"HOTKEY", process_hotkey}, {NULL, NULL} }; /////////////////////////////////////// // This function reads an element from the XML stream and processes it. /////////////////////////////////////// static void process_node(clish_shell_t * shell, TiXmlNode * node, void *parent) { switch (node->Type()) { case TiXmlNode::DOCUMENT: break; case TiXmlNode::ELEMENT: clish_xml_cb_t * cb; for (cb = &xml_elements[0]; cb->element; cb++) { if (0 == strcmp(node->Value(), cb->element)) { TiXmlElement *element = (TiXmlElement *) node; #ifdef DEBUG fprintf(stderr, "NODE:"); element->Print(stderr, 0); fprintf(stderr, "\n***\n"); #endif // process the elements at this level cb->handler(shell, element, parent); break; } } break; case TiXmlNode::COMMENT: case TiXmlNode::TEXT: case TiXmlNode::DECLARATION: case TiXmlNode::TYPECOUNT: case TiXmlNode::UNKNOWN: default: break; } } /////////////////////////////////////// static void process_children(clish_shell_t * shell, TiXmlElement * element, void *parent = NULL) { for (TiXmlNode * node = element->FirstChild(); node; node = element->IterateChildren(node)) { // Now deal with all the contained elements process_node(shell, node, parent); } } /////////////////////////////////////// static void process_clish_module(clish_shell_t * shell, TiXmlElement * element, void *) { // create the global view if (!shell->global) shell->global = clish_shell_find_create_view(shell, "global", ""); process_children(shell, element, shell->global); } /////////////////////////////////////// static void process_view(clish_shell_t * shell, TiXmlElement * element, void *) { clish_view_t *view; const char *name = element->Attribute("name"); const char *prompt = element->Attribute("prompt"); const char *depth = element->Attribute("depth"); const char *restore = element->Attribute("restore"); const char *access = element->Attribute("access"); bool allowed = true; /* Check permissions */ if (access) { allowed = false; if (shell->client_hooks->access_fn) allowed = shell->client_hooks->access_fn(shell, access) ? true : false; } if (!allowed) return; // re-use a view if it already exists view = clish_shell_find_create_view(shell, name, prompt); if (depth && (*depth != '\0') && (lub_ctype_isdigit(*depth))) { unsigned res = atoi(depth); clish_view__set_depth(view, res); } if (restore) { if (!lub_string_nocasecmp(restore, "depth")) clish_view__set_restore(view, CLISH_RESTORE_DEPTH); else if (!lub_string_nocasecmp(restore, "view")) clish_view__set_restore(view, CLISH_RESTORE_VIEW); else clish_view__set_restore(view, CLISH_RESTORE_NONE); } process_children(shell, element, view); } /////////////////////////////////////// static void process_ptype(clish_shell_t * shell, TiXmlElement * element, void *) { clish_ptype_method_e method; clish_ptype_preprocess_e preprocess; clish_ptype_t *ptype; const char *name = element->Attribute("name"); const char *help = element->Attribute("help"); const char *pattern = element->Attribute("pattern"); const char *method_name = element->Attribute("method"); const char *preprocess_name = element->Attribute("preprocess"); assert(name); assert(pattern); method = clish_ptype_method_resolve(method_name); preprocess = clish_ptype_preprocess_resolve(preprocess_name); ptype = clish_shell_find_create_ptype(shell, name, help, pattern, method, preprocess); assert(ptype); } /////////////////////////////////////// static void process_overview(clish_shell_t * shell, TiXmlElement * element, void *) { // read the following text element TiXmlNode *text = element->FirstChild(); if (text) { assert(TiXmlNode::TEXT == text->Type()); // set the overview text for this view assert(NULL == shell->overview); // store the overview shell->overview = lub_string_dup(text->Value()); } } //////////////////////////////////////// static void process_command(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_view_t *v = (clish_view_t *) parent; clish_command_t *cmd = NULL; bool allowed = true; clish_command_t *old; char *alias_name = NULL; clish_view_t *alias_view = NULL; const char *access = element->Attribute("access"); const char *name = element->Attribute("name"); const char *help = element->Attribute("help"); const char *view = element->Attribute("view"); const char *viewid = element->Attribute("viewid"); const char *escape_chars = element->Attribute("escape_chars"); const char *args_name = element->Attribute("args"); const char *args_help = element->Attribute("args_help"); const char *lock = element->Attribute("lock"); const char *interrupt = element->Attribute("interrupt"); const char *ref = element->Attribute("ref"); /* Check permissions */ if (access) { allowed = false; if (shell->client_hooks->access_fn) allowed = shell->client_hooks->access_fn(shell, access) ? true : false; } if (!allowed) return; old = clish_view_find_command(v, name, BOOL_FALSE); // check this command doesn't already exist if (old) { // flag the duplication then ignore further definition printf("DUPLICATE COMMAND: %s\n", clish_command__get_name(old)); return; } assert(name); assert(help); /* Reference 'ref' field */ if (ref) { char *saveptr; const char *delim = "@"; char *view_name = NULL; char *cmdn = NULL; char *str = lub_string_dup(ref); cmdn = strtok_r(str, delim, &saveptr); if (!cmdn) { printf("EMPTY REFERENCE COMMAND: %s\n", name); lub_string_free(str); return; } alias_name = lub_string_dup(cmdn); view_name = strtok_r(NULL, delim, &saveptr); if (!view_name) alias_view = v; else alias_view = clish_shell_find_create_view(shell, view_name, NULL); lub_string_free(str); } /* create a command */ cmd = clish_view_new_command(v, name, help); assert(cmd); clish_command__set_pview(cmd, v); /* define some specialist escape characters */ if (escape_chars) clish_command__set_escape_chars(cmd, escape_chars); if (args_name) { /* define a "rest of line" argument */ clish_param_t *param; clish_ptype_t *tmp = NULL; assert(args_help); tmp = clish_shell_find_ptype(shell, "internal_ARGS"); assert(tmp); param = clish_param_new(args_name, args_help, tmp); clish_command__set_args(cmd, param); } // define the view which this command changes to if (view) { clish_view_t *next = clish_shell_find_create_view(shell, view, NULL); // reference the next view clish_command__set_view(cmd, next); } // define the view id which this command changes to if (viewid) clish_command__set_viewid(cmd, viewid); /* lock field */ if (lock && (lub_string_nocasecmp(lock, "false") == 0)) clish_command__set_lock(cmd, BOOL_FALSE); else clish_command__set_lock(cmd, BOOL_TRUE); /* interrupt field */ if (interrupt && (lub_string_nocasecmp(interrupt, "true") == 0)) clish_command__set_interrupt(cmd, BOOL_TRUE); else clish_command__set_interrupt(cmd, BOOL_FALSE); /* Set alias */ if (alias_name) { assert(!((alias_view == v) && (!strcmp(alias_name, name)))); clish_command__set_alias(cmd, alias_name); assert(alias_view); clish_command__set_alias_view(cmd, alias_view); lub_string_free(alias_name); } process_children(shell, element, cmd); } /////////////////////////////////////// static void process_startup(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_view_t *v = (clish_view_t *) parent; clish_command_t *cmd = NULL; const char *view = element->Attribute("view"); const char *viewid = element->Attribute("viewid"); const char *default_shebang = element->Attribute("default_shebang"); const char *timeout = element->Attribute("timeout"); const char *lock = element->Attribute("lock"); const char *interrupt = element->Attribute("interrupt"); assert(!shell->startup); assert(view); /* create a command with NULL help */ cmd = clish_view_new_command(v, "startup", NULL); clish_command__set_lock(cmd, BOOL_FALSE); // define the view which this command changes to clish_view_t *next = clish_shell_find_create_view(shell, view, NULL); // reference the next view clish_command__set_view(cmd, next); // define the view id which this command changes to if (viewid) clish_command__set_viewid(cmd, viewid); if (default_shebang) clish_shell__set_default_shebang(shell, default_shebang); if (timeout) clish_shell__set_timeout(shell, atoi(timeout)); /* lock field */ if (lock && (lub_string_nocasecmp(lock, "false") == 0)) clish_command__set_lock(cmd, BOOL_FALSE); else clish_command__set_lock(cmd, BOOL_TRUE); /* interrupt field */ if (interrupt && (lub_string_nocasecmp(interrupt, "true") == 0)) clish_command__set_interrupt(cmd, BOOL_TRUE); else clish_command__set_interrupt(cmd, BOOL_FALSE); // remember this command shell->startup = cmd; process_children(shell, element, cmd); } /////////////////////////////////////// static void process_param(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_command_t *cmd = NULL; clish_param_t *p_param = NULL; if (0 == lub_string_nocasecmp(element->Parent()->Value(), "PARAM")) p_param = (clish_param_t *)parent; else cmd = (clish_command_t *)parent; if (cmd || p_param) { assert((!cmd) || (cmd != shell->startup)); const char *name = element->Attribute("name"); const char *help = element->Attribute("help"); const char *ptype = element->Attribute("ptype"); const char *prefix = element->Attribute("prefix"); const char *defval = element->Attribute("default"); const char *mode = element->Attribute("mode"); const char *optional = element->Attribute("optional"); const char *order = element->Attribute("order"); const char *value = element->Attribute("value"); const char *hidden = element->Attribute("hidden"); const char *test = element->Attribute("test"); const char *completion = element->Attribute("completion"); clish_param_t *param; clish_ptype_t *tmp = NULL; // create a command assert(name); assert(help); assert(ptype); if (ptype && *ptype) { tmp = clish_shell_find_create_ptype(shell, ptype, NULL, NULL, CLISH_PTYPE_REGEXP, CLISH_PTYPE_NONE); assert(tmp); } param = clish_param_new(name, help, tmp); /* If prefix is set clish will emulate old optional * command syntax over newer optional command mechanism. * It will create nested PARAM. */ if (prefix) { const char *ptype_name = "__SUBCOMMAND"; clish_param_t *opt_param = NULL; /* Create a ptype for prefix-named subcommand that * will contain the nested optional parameter. The * name of ptype is hardcoded. It's not good but * it's only the service ptype. */ tmp = (clish_ptype_t *)lub_bintree_find( &shell->ptype_tree, ptype_name); if (!tmp) tmp = clish_shell_find_create_ptype(shell, ptype_name, "Option", "[^\\\\]+", CLISH_PTYPE_REGEXP, CLISH_PTYPE_NONE); assert(tmp); opt_param = clish_param_new(prefix, help, tmp); clish_param__set_mode(opt_param, CLISH_PARAM_SUBCOMMAND); clish_param__set_optional(opt_param, BOOL_TRUE); if (test) clish_param__set_test(opt_param, test); if (cmd) // add the parameter to the command clish_command_insert_param(cmd, opt_param); if (p_param) // add the parameter to the param clish_param_insert_param(p_param, opt_param); /* Unset cmd and set parent param to opt_param */ cmd = NULL; p_param = opt_param; } if (defval) clish_param__set_default(param, defval); if (hidden && (lub_string_nocasecmp(hidden, "true") == 0)) clish_param__set_hidden(param, BOOL_TRUE); else clish_param__set_hidden(param, BOOL_FALSE); if (mode) { if (!lub_string_nocasecmp(mode, "switch")) { clish_param__set_mode(param, CLISH_PARAM_SWITCH); /* Force hidden attribute */ clish_param__set_hidden(param, BOOL_TRUE); } else if (!lub_string_nocasecmp(mode, "subcommand")) clish_param__set_mode(param, CLISH_PARAM_SUBCOMMAND); else clish_param__set_mode(param, CLISH_PARAM_COMMON); } if (optional && (lub_string_nocasecmp(optional, "true") == 0)) clish_param__set_optional(param, BOOL_TRUE); else clish_param__set_optional(param, BOOL_FALSE); if (order && (lub_string_nocasecmp(order, "true") == 0)) clish_param__set_order(param, BOOL_TRUE); else clish_param__set_order(param, BOOL_FALSE); if (value) { clish_param__set_value(param, value); /* Force mode to subcommand */ clish_param__set_mode(param, CLISH_PARAM_SUBCOMMAND); } if (test && !prefix) clish_param__set_test(param, test); if (completion) clish_param__set_completion(param, completion); if (cmd) // add the parameter to the command clish_command_insert_param(cmd, param); if (p_param) // add the parameter to the param clish_param_insert_param(p_param, param); process_children(shell, element, param); } } //////////////////////////////////////// static void process_action(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_action_t *action = NULL; TiXmlNode *text = element->FirstChild(); const char *builtin = element->Attribute("builtin"); const char *shebang = element->Attribute("shebang"); if (!lub_string_nocasecmp(element->Parent()->Value(), "VAR")) action = clish_var__get_action((clish_var_t *)parent); else action = clish_command__get_action((clish_command_t *)parent); assert(action); if (text) { assert(TiXmlNode::TEXT == text->Type()); // store the action clish_action__set_script(action, text->Value()); } if (builtin) clish_action__set_builtin(action, builtin); if (shebang) clish_action__set_shebang(action, shebang); } //////////////////////////////////////// static void process_detail(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_command_t *cmd = (clish_command_t *) parent; // read the following text element TiXmlNode *text = element->FirstChild(); if (cmd && text) { assert(TiXmlNode::TEXT == text->Type()); // store the action clish_command__set_detail(cmd, text->Value()); } } /////////////////////////////////////// static void process_namespace(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_view_t *v = (clish_view_t *) parent; clish_nspace_t *nspace = NULL; const char *view = element->Attribute("ref"); const char *prefix = element->Attribute("prefix"); const char *prefix_help = element->Attribute("prefix_help"); const char *help = element->Attribute("help"); const char *completion = element->Attribute("completion"); const char *context_help = element->Attribute("context_help"); const char *inherit = element->Attribute("inherit"); const char *access = element->Attribute("access"); bool allowed = true; if (access) { allowed = false; if (shell->client_hooks->access_fn) allowed = shell->client_hooks->access_fn(shell, access) ? true : false; } if (!allowed) return; assert(view); clish_view_t *ref_view = clish_shell_find_create_view(shell, view, NULL); assert(ref_view); /* Don't include itself without prefix */ if ((ref_view == v) && !prefix) return; nspace = clish_nspace_new(ref_view); assert(nspace); clish_view_insert_nspace(v, nspace); if (prefix) { clish_nspace__set_prefix(nspace, prefix); if (prefix_help) clish_nspace_create_prefix_cmd(nspace, "prefix", prefix_help); else clish_nspace_create_prefix_cmd(nspace, "prefix", "Prefix for the imported commands."); } if (help && (lub_string_nocasecmp(help, "true") == 0)) clish_nspace__set_help(nspace, BOOL_TRUE); else clish_nspace__set_help(nspace, BOOL_FALSE); if (completion && (lub_string_nocasecmp(completion, "false") == 0)) clish_nspace__set_completion(nspace, BOOL_FALSE); else clish_nspace__set_completion(nspace, BOOL_TRUE); if (context_help && (lub_string_nocasecmp(context_help, "true") == 0)) clish_nspace__set_context_help(nspace, BOOL_TRUE); else clish_nspace__set_context_help(nspace, BOOL_FALSE); if (inherit && (lub_string_nocasecmp(inherit, "false") == 0)) clish_nspace__set_inherit(nspace, BOOL_FALSE); else clish_nspace__set_inherit(nspace, BOOL_TRUE); } //////////////////////////////////////// static void process_config(clish_shell_t * shell, TiXmlElement * element, void *parent) { clish_command_t *cmd = (clish_command_t *)parent; clish_config_t *config; if (!cmd) return; config = clish_command__get_config(cmd); // read the following text element const char *operation = element->Attribute("operation"); const char *priority = element->Attribute("priority"); const char *pattern = element->Attribute("pattern"); const char *file = element->Attribute("file"); const char *splitter = element->Attribute("splitter"); const char *seq = element->Attribute("sequence"); const char *unique = element->Attribute("unique"); const char *depth = element->Attribute("depth"); if (operation && !lub_string_nocasecmp(operation, "unset")) clish_config__set_op(config, CLISH_CONFIG_UNSET); else if (operation && !lub_string_nocasecmp(operation, "none")) clish_config__set_op(config, CLISH_CONFIG_NONE); else if (operation && !lub_string_nocasecmp(operation, "dump")) clish_config__set_op(config, CLISH_CONFIG_DUMP); else { clish_config__set_op(config, CLISH_CONFIG_SET); /* The priority if no clearly specified */ clish_config__set_priority(config, 0x7f00); } if (priority && (*priority != '\0')) { long val = 0; char *endptr; unsigned short pri; val = strtol(priority, &endptr, 0); if (endptr == priority) pri = 0; else if (val > 0xffff) pri = 0xffff; else if (val < 0) pri = 0; else pri = (unsigned short)val; clish_config__set_priority(config, pri); } if (pattern) clish_config__set_pattern(config, pattern); else clish_config__set_pattern(config, "^${__cmd}"); if (file) clish_config__set_file(config, file); if (splitter && (lub_string_nocasecmp(splitter, "false") == 0)) clish_config__set_splitter(config, BOOL_FALSE); else clish_config__set_splitter(config, BOOL_TRUE); if (unique && (lub_string_nocasecmp(unique, "false") == 0)) clish_config__set_unique(config, BOOL_FALSE); else clish_config__set_unique(config, BOOL_TRUE); if (seq) clish_config__set_seq(config, seq); else /* The entries without sequence cannot be non-unique */ clish_config__set_unique(config, BOOL_TRUE); if (depth) clish_config__set_depth(config, depth); } /////////////////////////////////////// static void process_var(clish_shell_t * shell, TiXmlElement * element, void *) { clish_var_t *var = NULL; const char *name = element->Attribute("name"); const char *dynamic = element->Attribute("dynamic"); const char *value = element->Attribute("value"); assert(name); /* Check if this var doesn't already exist */ var = (clish_var_t *)lub_bintree_find(&shell->var_tree, name); if (var) { printf("DUPLICATE VAR: %s\n", name); assert(!var); } /* Create var instance */ var = clish_var_new(name); lub_bintree_insert(&shell->var_tree, var); if (dynamic && (lub_string_nocasecmp(dynamic, "true") == 0)) clish_var__set_dynamic(var, BOOL_TRUE); if (value) clish_var__set_value(var, value); process_children(shell, element, var); } /////////////////////////////////////// static void process_wdog(clish_shell_t *shell, TiXmlElement *element, void *parent) { clish_view_t *v = (clish_view_t *)parent; clish_command_t *cmd = NULL; assert(!shell->wdog); /* create a command with NULL help */ cmd = clish_view_new_command(v, "watchdog", NULL); clish_command__set_lock(cmd, BOOL_FALSE); /* Remember this command */ shell->wdog = cmd; process_children(shell, element, cmd); } /////////////////////////////////////// static void process_hotkey(clish_shell_t *shell, TiXmlElement *element, void *parent) { clish_view_t *v = (clish_view_t *)parent; const char *key = element->Attribute("key"); const char *cmd = element->Attribute("cmd"); assert(key); assert(cmd); assert (!clish_view_insert_hotkey(v, key, cmd)); } /////////////////////////////////////// int clish_shell_xml_read(clish_shell_t * shell, const char *filename) { int ret = -1; TiXmlDocument doc; // keep the white space TiXmlBase::SetCondenseWhiteSpace(false); if (doc.LoadFile(filename)) { TiXmlNode *child = 0; while ((child = doc.IterateChildren(child))) { process_node(shell, child, NULL); } ret = 0; } else { printf("Unable to open %s (%s at line %d, col %d)\n", filename, doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol()); } return ret; } ///////////////////////////////////////