From 6ea2295e38186715fc7cfae26c4ca68536b7ab04 Mon Sep 17 00:00:00 2001 From: Graeme Campbell Date: Thu, 30 Nov 2023 10:53:35 +1300 Subject: [PATCH] Support translated YANG models. This change allows one model to be translated to another model. This is useful if multiple YANG models for the same type of data exist. In this case a back-end super YANG model is created which is a combination of the contributing models, and requests to the individual models are translated into a request of the super model and responses are translated back into the individual requesting models. In the Apteryx data store information only exists for the super model. The translation is controlled by a translating configuration file named {name}.xlat is that a single super mode --- apteryx-xml.h | 3 + models/xlat_test.lua | 29 ++ models/xlat_test.xlat | 7 + models/xlat_test.xml | 32 ++ schema.c | 856 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 925 insertions(+), 2 deletions(-) create mode 100644 models/xlat_test.lua create mode 100644 models/xlat_test.xlat create mode 100644 models/xlat_test.xml diff --git a/apteryx-xml.h b/apteryx-xml.h index 6d472a5..3cfc66d 100644 --- a/apteryx-xml.h +++ b/apteryx-xml.h @@ -93,6 +93,9 @@ char *sch_translate_from (sch_node * node, char *value); bool sch_validate_pattern (sch_node * node, const char *value); gboolean sch_match_name (const char *s1, const char *s2); bool sch_ns_match (sch_node *node, void *ns); +GNode *sch_translate_input (sch_instance * instance, GNode *node, int flags, + void **xlat_data, sch_node **rschema); +GNode *sch_translate_output (sch_instance * instance, GNode *node, int flags, void *xlat_data); /* Data translation/manipulation */ typedef enum diff --git a/models/xlat_test.lua b/models/xlat_test.lua new file mode 100644 index 0000000..9c6c619 --- /dev/null +++ b/models/xlat_test.lua @@ -0,0 +1,29 @@ +xlat_data = {} + +xlat_data.translate_type = function(field, value, dir) + local value1 = value + if field == 'type' then + value1 = tonumber (value) + print("field " .. field) + print("value1 " .. value1) + print("dir " .. dir) + if dir == 'out' then + if value == '1' then + value1 = '3' + else + value1 = '4' + end + print("value1 " .. value1) + else + if value == '3' then + value1 = '1' + else + value1 = '2' + end + end + end + return value1 +end + +return xlat_data + diff --git a/models/xlat_test.xlat b/models/xlat_test.xlat new file mode 100644 index 0000000..e4f297e --- /dev/null +++ b/models/xlat_test.xlat @@ -0,0 +1,7 @@ +/xlat-test/xlat-animals /test/animals/animal +/xlat-test/xlat-animals/xlat-animal /test/animals/animal +/xlat-test/xlat-animals/xlat-animal/*/name /test/animals/animal/*/name +/xlat-test/xlat-animals/xlat-animal/*/type /test/animals/animal/*/type xlat_data.translate_type +/xlat-test/xlat-animals/xlat-animal/*/colour /test/animals/animal/*/colour +/xlat-test/xlat-animals/xlat-animal/*/food /test/animals/animal/*/food +/xlat-test/xlat-animals/xlat-animal/*/toys /test/animals/animal/*/toys \ No newline at end of file diff --git a/models/xlat_test.xml b/models/xlat_test.xml new file mode 100644 index 0000000..9c61ab3 --- /dev/null +++ b/models/xlat_test.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/schema.c b/schema.c index 89452c5..ba74306 100644 --- a/schema.c +++ b/schema.c @@ -32,6 +32,9 @@ #include #include #include +#include +#include +#include #include "apteryx-xml.h" /* Error handling and debug */ @@ -60,7 +63,10 @@ typedef struct _sch_instance GList *models_list; GHashTable *map_hash_table; GHashTable *model_hash_table; + GHashTable *xlat_hash_table; GList *regexes; + /* Lua state */ + lua_State *ls; } sch_instance; typedef struct _sch_xml_to_gnode_parms_s @@ -77,6 +83,20 @@ typedef struct _sch_xml_to_gnode_parms_s GList *out_replaces; } _sch_xml_to_gnode_parms; +typedef struct _sch_xlat_root +{ + GList *matchs; +} sch_xlat_root; + +typedef struct _sch_xlat_data +{ + sch_xlat_root *root; + GNode *ext_tree; + GNode *int_tree; + char *lua_module; + char *lua_func; +} sch_xlat_data; + /* Retrieve the last error code */ sch_err sch_last_err (void) @@ -91,6 +111,49 @@ sch_last_errmsg (void) return tl_errmsg; } +static char * +first_name_in_path (const char *path) +{ + const char *in = path; + char *slash; + char *name; + + if (!in || in[0] == '\0') + return NULL; + + name = strdup (in); + slash = strchr (name + 1, '/'); + if (slash) + *slash = '\0'; + return name; +} + +static void +sch_lua_error (lua_State *ls, int res) +{ + switch (res) + { + case LUA_ERRRUN: + syslog (LOG_ERR, "LUA: %s\n", lua_tostring (ls, -1)); + break; + case LUA_ERRSYNTAX: + syslog (LOG_ERR, "LUA: %s\n", lua_tostring (ls, -1)); + break; + case LUA_ERRMEM: + syslog (LOG_ERR, "LUA: Memory allocation error\n"); + break; + case LUA_ERRERR: + syslog (LOG_ERR, "LUA: Error handler error\n"); + break; + case LUA_ERRFILE: + syslog (LOG_ERR, "LUA: Couldn't open file\n"); + break; + default: + syslog (LOG_ERR, "LUA: Unknown error\n"); + break; + } +} + /* List full paths for all schema files in the search path */ static void list_schema_files (GList ** files, const char *path) @@ -113,7 +176,8 @@ list_schema_files (GList ** files, const char *path) char *filename; if ((fnmatch ("*.xml", ep->d_name, FNM_PATHNAME) != 0) && (fnmatch ("*.xml.gz", ep->d_name, FNM_PATHNAME) != 0) && - (fnmatch ("*.map", ep->d_name, FNM_PATHNAME) != 0)) + (fnmatch ("*.map", ep->d_name, FNM_PATHNAME) != 0) && + (fnmatch ("*.xlat", ep->d_name, FNM_PATHNAME) != 0)) { continue; } @@ -551,6 +615,136 @@ sch_load_namespace_mappings (sch_instance *instance, const char *filename) g_free (buf); } +GNode * +path_to_tree (char *path) +{ + GNode *ret_node = NULL; + GNode *node = NULL;; + gchar **path_split; + int i; + int split_len; + + path_split = g_strsplit (path, "/", -1); + split_len = g_strv_length (path_split); + for (i = 0; i < split_len; i++) + { + if (i == 0 && strlen (path_split[0]) == 0) + { + if (split_len > 1) + { + char *tmp = path_split[1]; + path_split[1] = g_strdup_printf ("/%s", tmp); + g_free (tmp); + } + continue; + } + if (!node) + { + node = g_node_new (g_strdup (path_split[i])); + ret_node = node; + } + else + node = g_node_append_data (node, g_strdup (path_split[i])); + } + g_strfreev (path_split); + + return ret_node; +} + +static void +free_xlat_root (gpointer data) +{ + sch_xlat_root *root = (sch_xlat_root *) data; + sch_xlat_data *xlat_data; + GList *list; + + for (list = g_list_first (root->matchs); list; list = g_list_next (list)) + { + xlat_data = list->data; + apteryx_free_tree (xlat_data->ext_tree); + apteryx_free_tree (xlat_data->int_tree); + g_free (xlat_data->lua_module); + g_free (xlat_data->lua_func); + g_free (xlat_data); + list->data = NULL; + } + g_list_free (root->matchs); + g_free (root); +} + +static void +sch_load_model_xlat (sch_instance *instance, const char *filename) +{ + FILE *fp = NULL; + gchar **xlat_paths; + char *buf; + char *name; + sch_xlat_root *root; + sch_xlat_data *xlat_data; + + if (!instance) + return; + + buf = g_malloc0 (READ_BUF_SIZE); + fp = fopen (filename, "r"); + if (fp && buf) + { + if (!instance->xlat_hash_table) + instance->xlat_hash_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_xlat_root); + + while (fgets (buf, READ_BUF_SIZE, fp) != NULL) + { + /* Skip comment lines */ + if (buf[0] == '#') + continue; + + /* Remove any trailing LF */ + buf[strcspn(buf, "\n")] = '\0'; + /* xlat file format: request_path translate_path */ + xlat_paths = g_strsplit (buf, " ", 3); + if (xlat_paths[0] && xlat_paths[1]) + { + void *old_key; + + name = first_name_in_path (xlat_paths[0]); + xlat_data = g_malloc0 (sizeof (sch_xlat_data)); + xlat_data->ext_tree = path_to_tree (xlat_paths[0]); + xlat_data->int_tree = path_to_tree (xlat_paths[1]); + if (xlat_paths[2]) + { + gchar **func_parts = g_strsplit (xlat_paths[2], ".", 2); + if (func_parts[0] && func_parts[1]) + { + xlat_data->lua_module = g_strdup (func_parts[0]); + xlat_data->lua_func = g_strdup (func_parts[1]); + } + g_strfreev (func_parts); + } + + /* Look up this node name to check for duplicates. */ + if (g_hash_table_lookup_extended (instance->xlat_hash_table, name, + &old_key, (gpointer *) &root)) + { + /* insert data into GList paths */ + xlat_data->root = root; + root->matchs = g_list_prepend (root->matchs, xlat_data); + g_free (name); + } + else + { + root = g_malloc0 (sizeof (sch_xlat_root)); + xlat_data->root = root; + root->matchs = g_list_prepend (root->matchs, xlat_data); + g_hash_table_insert (instance->xlat_hash_table, name, root); + } + } + g_strfreev (xlat_paths); + } + fclose (fp); + } + g_free (buf); +} + static void sch_load_model_list (sch_instance *instance, const char *path, const char *model_list_filename) { @@ -602,6 +796,69 @@ sch_load_model_list (sch_instance *instance, const char *path, const char *model g_free (buf); } +static bool +sch_load_script_files (sch_instance *instance, const char *path) +{ + struct dirent *entry; + DIR *dir; + bool res = true; + + /* Initialise the Lua state */ + instance->ls = luaL_newstate (); + if (!instance->ls) + { + syslog (LOG_ERR, "XML: Failed to instantiate Lua interpreter\n"); + return false; + } + + /* Load required libraries */ + luaL_openlibs (instance->ls); + + /* Find all the LUA files in this folder */ + dir = opendir (path); + if (dir == NULL) + return true; + + /* Load and execute all LUA files */ + for (entry = readdir (dir); entry; entry = readdir (dir)) + { + const char *ext = strrchr (entry->d_name, '.'); + if (ext && strcmp (".lua", ext) == 0) + { + char *filename = g_strdup_printf ("%s/%s", path, entry->d_name); + int error; + + /* Execute the script */ + lua_getglobal (instance->ls, "debug"); + lua_getfield (instance->ls, -1, "traceback"); + error = luaL_loadfile (instance->ls, filename); + if (error == 0) + error = lua_pcall (instance->ls, 0, 0, 0); + + if (error != 0) + sch_lua_error (instance->ls, error); + + while (lua_gettop (instance->ls)) + lua_pop (instance->ls, 1); + + /* Stop processing files if there has been an error */ + if (error != 0) + { + syslog (LOG_ERR, "XML: Invalid file \"%s\"\n", filename); + g_free (filename); + res = false; + goto exit; + } + g_free (filename); + } + } + + exit: + closedir (dir); + return res; +} + + /* Parse all XML files in the search path and merge trees */ static sch_instance * _sch_load (const char *path, const char *model_list_filename) @@ -638,6 +895,11 @@ _sch_load (const char *path, const char *model_list_filename) sch_load_namespace_mappings (instance, filename); continue; } + if (g_strcmp0 (ext, ".xlat") == 0) + { + sch_load_model_xlat (instance, filename); + continue; + } xmlDoc *doc_new = xmlParseFile (filename); if (doc_new == NULL) { @@ -670,6 +932,8 @@ _sch_load (const char *path, const char *model_list_filename) /* Store a link back to the instance in the xmlDoc stucture */ instance->doc->_private = (void *) instance; + sch_load_script_files (instance, path); + return instance; } @@ -755,8 +1019,13 @@ sch_free (sch_instance * instance) g_hash_table_destroy (instance->map_hash_table); if (instance->model_hash_table) g_hash_table_destroy (instance->model_hash_table); + if (instance->xlat_hash_table) + g_hash_table_destroy (instance->xlat_hash_table); if (instance->regexes) g_list_free_full (instance->regexes, (GDestroyNotify) free_regex); + if (instance->ls) + lua_close (instance->ls); + g_free (instance); } } @@ -2753,7 +3022,8 @@ _sch_gnode_to_xml (sch_instance * instance, sch_node * schema, xmlNs *ns, xmlNod } /* Record any changes to the namespace (including the root node) */ - if (data && schema && (!pschema || ((xmlNode *)pschema)->ns != ((xmlNode *)schema)->ns)) + if (data && schema && ((xmlNode *)schema)->ns && + (!pschema || ((xmlNode *)pschema)->ns != ((xmlNode *)schema)->ns)) { /* Dont store a prefix as we set the default xmlns at each node */ xmlNsPtr nns = xmlNewNs (data, ((xmlNode *)schema)->ns->href, NULL); @@ -2873,12 +3143,41 @@ _operation_ok (_sch_xml_to_gnode_parms *_parms, xmlNode *xml, char *curr_op, cha static void _perform_actions (_sch_xml_to_gnode_parms *_parms, int depth, char *curr_op, char *new_op, char *new_xpath) { + void *xlat_data = NULL; + char *tmp_xpath; + /* Do nothing if not an edit, or operation not changing, unless depth is 0. */ if (!_parms->in_is_edit || (g_strcmp0 (curr_op, new_op) == 0 && depth != 0)) { return; } + tmp_xpath = NULL; + if (_parms->in_instance->xlat_hash_table && g_strcmp0 (new_op, "merge") != 0) + { + char *name = first_name_in_path (new_xpath); + if (g_hash_table_lookup (_parms->in_instance->xlat_hash_table, name)) + { + GNode *orig_query = path_to_tree (new_xpath); + int flags = _parms->in_flags; + + xlat_data = NULL; + GNode *node = sch_translate_input (_parms->in_instance, orig_query, flags, &xlat_data, NULL); + if (node != orig_query) + { + GNode *last = node; + while (last->children) + last = last->children; + tmp_xpath = apteryx_node_path (last); + new_xpath = tmp_xpath; + apteryx_free_tree (node); + } + else + apteryx_free_tree (orig_query); + } + g_free (name); + } + /* Handle operations. */ if (g_strcmp0 (new_op, "delete") == 0) { @@ -2900,6 +3199,9 @@ _perform_actions (_sch_xml_to_gnode_parms *_parms, int depth, char *curr_op, cha _parms->out_replaces = g_list_append (_parms->out_replaces, g_strdup (new_xpath)); DEBUG (_parms->in_flags, "replace <%s>\n", new_xpath); } + + if (tmp_xpath) + g_free (tmp_xpath); } static GNode * @@ -4065,3 +4367,553 @@ sch_json_to_gnode (sch_instance * instance, sch_node * schema, json_t * json, in } return root; } + +static gpointer +copy_node_data (gconstpointer src, gpointer dummy) +{ + char *data = NULL; + + if (src) + data = g_strdup (src); + return data; +} + +static void +sch_translate_data (sch_instance * instance, GNode *node, sch_xlat_data *xlat_data, int flags, bool incoming) +{ + int ret; + int s_0 = lua_gettop (instance->ls); + + lua_getglobal (instance->ls, xlat_data->lua_module); + lua_getfield (instance->ls, -1, xlat_data->lua_func); + if (!lua_isnil (instance->ls, -1)) + { + lua_pushstring (instance->ls, APTERYX_NAME (node)); + lua_pushstring (instance->ls, APTERYX_NAME (node->children)); + lua_pushstring (instance->ls, incoming ? "in" : "out"); + ret = lua_pcall (instance->ls, 3, 1, 0); + if (ret) + sch_lua_error (instance->ls, ret); + else if (lua_gettop (instance->ls) != (s_0 + 2)) + { + syslog (LOG_ERR, "XML LUA: Stack not zero(%d) after function: %s.%s\n", + lua_gettop (instance->ls), xlat_data->lua_module, + xlat_data->lua_func); + lua_pop (instance->ls, 1); + } + else + { + const char *value = lua_tostring (instance->ls, -1); + char *old_value = (char *) node->children->data; + node->children->data = g_strdup (value); + DEBUG (flags, "function: %s old value %s new value %s\n", xlat_data->lua_func, old_value, value); + g_free (old_value); + } + } + else + { + lua_pop(instance->ls, 1); + return; + } +} + +static bool +sch_translate_child_exists (GNode *parent, char *name, GNode **ret_node) +{ + GNode *child; + + for (child = parent->children; child; child = child->next) + { + if (g_strcmp0 (APTERYX_NAME (child), name) == 0) + { + *ret_node = child; + return true; + } + } + + *ret_node = NULL; + return false; +} + +static bool +sch_translate_sibling_exists (GNode *node, char *name, GNode **ret_node) +{ + while (node) + { + if (g_strcmp0 (APTERYX_NAME (node), name) == 0) + { + *ret_node = node; + return true; + } + node = node->next; + } + + *ret_node = NULL; + return false; +} + +static bool +xlat_tree_match (GNode *tree, GNode *pattern, GNode **last_match_node) +{ + bool match = false; + GNode *node1 = tree; + GNode *node2 = pattern; + GNode *ret_node; + GNode *last_valid_node = NULL; + + while (node1 && node2) + { + match = true; + if (!sch_translate_sibling_exists (node1, APTERYX_NAME (node2), &ret_node) && + g_strcmp0 ((char *) node2->data, "*") != 0) + return false; + if (ret_node) + { + last_valid_node = ret_node; + node1 = ret_node->children; + ret_node = NULL; + } + else + { + last_valid_node = node1; + node1 = node1->children; + } + node2 = node2->children; + } + + if (node2) + match = false; + else + *last_match_node = last_valid_node; + + return match; +} + +static char * +sch_translate_get_wildcard_data (GNode *node, GNode *tree, uint32_t pos) +{ + GNode *nnode = tree; + uint32_t matches = 0; + + while (nnode && node) + { + if (g_strcmp0 (APTERYX_NAME (nnode), "*") == 0) + { + matches++; + if (matches == pos) + return g_strdup (APTERYX_NAME (node)); + } + + nnode = nnode->children; + node = node->children; + } + return NULL; +} + +static inline gboolean +_sch_translate_count_nodes (GNode *node, void *data) +{ + int *num_nodes = data; + + if (node->data) + *num_nodes = *num_nodes + 1; + + return false; +} + +static inline gboolean +_sch_translate_mark_children (GNode *node, gpointer data) +{ + GHashTable *node_table = data; + if (!g_hash_table_lookup (node_table, node)) + g_hash_table_insert (node_table, node, node); + + return false; +} + +static void +sch_translate_mark_children (GHashTable *node_table, GNode *node) +{ + if (node->children) + { + if (!g_hash_table_lookup (node_table, node)) + g_node_traverse (node->children, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + _sch_translate_mark_children, node_table); + } +} + +static void +sch_translate_mark_node (GHashTable *node_table, GNode *node) +{ + GNode *parent; + + if (!g_hash_table_lookup (node_table, node)) + { + g_hash_table_insert (node_table, node, node); + parent = node->parent; + while (parent) + { + if (!g_hash_table_lookup (node_table, parent)) + g_hash_table_insert (node_table, parent, parent); + + parent = parent->parent; + } + } +} + +static sch_node * +sch_translate_schema (sch_instance * instance, GNode *node, int flags) +{ + sch_node *schema; + char *colon; + xmlNs *ns = NULL; + char *name = APTERYX_NAME (node); + if (name[0] == '/') + name = name + 1; + + /* Check for a change in namespace */ + schema = xmlDocGetRootElement (instance->doc); + colon = strchr (name, ':'); + if (colon) + { + char *namespace = g_strndup (name, colon - name); + xmlNs *nns = sch_lookup_ns (instance, schema, namespace, flags, false); + free (namespace); + if (nns) + { + /* We found a namespace. Skip the prefix */ + name = colon + 1; + ns = nns; + } + } + + /* Find schema node */ + schema = _sch_node_child (ns, schema, name); + if (schema == NULL) + { + ERROR (flags, SCH_E_NOSCHEMANODE, "No schema match for node %s\n", name); + return NULL; + } + return schema; +} + +static bool +sch_translate_input_matches (sch_instance * instance, GHashTable *node_table, GNode *start_node, + int flags, bool exact, GNode **node, sch_xlat_data *x_data, + sch_node **rschema) +{ + GNode *nnode; + GNode *int_node; + GNode *ret_node; + GNode *remaining = NULL; + GNode *last_match_node = NULL; + bool match = false; + + if (xlat_tree_match (start_node, x_data->ext_tree, &last_match_node)) + { + if (g_hash_table_lookup (node_table, last_match_node)) + return match; + + sch_translate_mark_node (node_table, last_match_node); + if (last_match_node && last_match_node->children) + { + remaining = g_node_copy_deep (last_match_node->children, copy_node_data, NULL); + sch_translate_mark_children (node_table, last_match_node); + } + + int_node = x_data->int_tree; + if (rschema && !*rschema) + { + *rschema = sch_translate_schema (instance, int_node, flags); + } + + if (!*node) + *node = g_node_new (g_strdup (APTERYX_NAME (int_node))); + nnode = *node; + int_node = int_node->children; + while (int_node) + { + if (g_strcmp0 (APTERYX_NAME (int_node), "*") == 0) + { + char *str = sch_translate_get_wildcard_data (start_node, x_data->ext_tree, 1); + if (sch_translate_child_exists (nnode, str, &ret_node)) + { + g_free (str); + nnode = ret_node; + } + else + nnode = g_node_append_data (nnode, str); + } + else + { + if (sch_translate_child_exists (nnode, APTERYX_NAME (int_node), &ret_node)) + nnode = ret_node; + else + nnode = g_node_append_data (nnode, g_strdup (APTERYX_NAME (int_node))); + } + + int_node = int_node->children; + } + if (remaining) + { + if (sch_translate_child_exists (nnode, APTERYX_NAME (remaining), &ret_node)) + { + nnode = g_node_append (ret_node, remaining); + } + else + nnode = g_node_append (nnode, remaining); + } + + if (x_data->lua_module && nnode) + sch_translate_data (instance, nnode->parent, x_data, flags, true); + + match = true; + } + + return match; +} + +GNode * +sch_translate_input (sch_instance * instance, GNode *node, int flags, void **xlat_data, sch_node **rschema) +{ + GNode *ret_node = NULL; + char *name; + sch_xlat_root *root; + GHashTable *node_table = NULL; + GList *list; + sch_xlat_data *x_data; + bool match; + int num_nodes = 0; + int query_depth = 0; + + *xlat_data = NULL; + if (!instance->xlat_hash_table || !node) + return node; + + name = APTERYX_NAME (node); + root = g_hash_table_lookup (instance->xlat_hash_table, name); + + if (!root) + return node; + + if (rschema) + { + *rschema = NULL; + query_depth = g_node_max_height (node); + } + + node_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + g_node_traverse (node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, _sch_translate_count_nodes, &num_nodes); + + for (list = g_list_first (root->matchs); list; list = g_list_next (list)) + { + x_data = list->data; + match = sch_translate_input_matches (instance, node_table, node, flags, true, + &ret_node, x_data, rschema); + if (match) + { + if (!*xlat_data) + *xlat_data = x_data; + + if (num_nodes <= g_hash_table_size (node_table)) + break; + } + } + g_hash_table_destroy (node_table); + + if (ret_node) + { + /* Restconf delete requires the translated schema node */ + if (rschema && *rschema) + { + sch_node *schema = *rschema; + GNode *node1 = ret_node; + for (int depth = 1; depth < query_depth && node1; depth++) + { + node1 = node1->children; + if (node1) + { + schema = _sch_node_child (NULL, schema, APTERYX_NAME (node1)); + } + } + if (schema) + *rschema = schema; + } + apteryx_free_tree (node); + + return ret_node; + } + + return node; +} + +static char * +sch_translate_get_wildcard_data_from_bottom (GNode *node, GNode *tree) +{ + GNode *nnode = tree; + + /* Move to the bottom of the tree */ + while (nnode && nnode->children) + nnode = nnode->children; + + while (nnode && node) + { + if (g_strcmp0 (APTERYX_NAME (nnode), "*") == 0) + return g_strdup (APTERYX_NAME (node)); + + nnode = nnode->parent; + node = node->parent; + } + return NULL; +} + +static void +sch_translate_merge_output (GNode **ret_node, GNode *node, GHashTable *node_table, + sch_xlat_data *xlat_data) +{ + GNode *nnode; + GNode *node1; + GNode *copy; + + /* Write/move down the output tree until we hit a wildcard or end of path */ + // nnode = ret_node; + node1 = xlat_data->ext_tree; + if (!*ret_node) + *ret_node = g_node_new (g_strdup (APTERYX_NAME (node1))); + nnode = *ret_node; + while (node1) + { + if (g_strcmp0 (APTERYX_NAME (node1), "*") == 0) + break; + + if (nnode && g_strcmp0 (APTERYX_NAME (nnode), APTERYX_NAME (node1)) == 0) + { + if (nnode->children && node1->children && g_strcmp0 (APTERYX_NAME (node1->children), "*") != 0) + nnode = nnode->children; + } + else + nnode = g_node_append_data (nnode, g_strdup (APTERYX_NAME (node1))); + + node1 = node1->children; + } + + /* Move down the input tree until we hit a wildcard or end of path */ + if (node1) + { + char *str = sch_translate_get_wildcard_data_from_bottom (node, xlat_data->int_tree); + if (str) + { + GNode *new_nnode; + if (sch_translate_child_exists (nnode, str, &new_nnode)) + { + nnode = new_nnode; + g_free (str); + } + else + nnode = g_node_append_data (nnode, str); + } + + node1 = node1->children; + } + + /* We should now be synchronized at a wildcard in both int and external trees, + add in fields after the wild card */ + while (node1 && node1->children) + { + GNode *new_nnode; + + if (nnode && sch_translate_child_exists (nnode, APTERYX_NAME (node1), &new_nnode)) + nnode = new_nnode; + else + nnode = g_node_append_data (nnode, g_strdup (APTERYX_NAME (node1))); + + node1 = node1->children; + } + + /* Now copy the exact match node to the output tree */ + copy = g_node_copy_deep (node, copy_node_data, NULL); + sch_translate_mark_node (node_table, node); + sch_translate_mark_children (node_table, node); + + /* Check if a field name change is required */ + if (node1) + { + if (g_strcmp0 (APTERYX_NAME (copy), APTERYX_NAME (node1))) + { + g_free (copy->data); + copy->data = g_strdup (APTERYX_NAME (node1)); + } + } + + nnode = g_node_append (nnode, copy); +} + +static void +sch_translate_output_matches (sch_instance * instance, GNode **ret_node, GNode *node, + GNode *int_node, GHashTable *node_table, int flags, + sch_xlat_data *xlat_data) +{ + GNode *child; + GNode *next_child; + + if (g_strcmp0 ((char *) node->data, (char *) int_node->data) && + g_strcmp0 ((char *) int_node->data, "*")) + return; + + if (!int_node->children && g_hash_table_lookup (node_table, node)) + return; + + int_node = int_node->children; + if (!int_node) + { + if (xlat_data->lua_module && node->children) + sch_translate_data (instance, node, xlat_data, flags, false); + + /* We have an exact match, translate the path into the output tree */ + sch_translate_merge_output (ret_node, node, node_table, xlat_data); + + return; + } + + for (child = node->children; child; child = next_child) + { + next_child = child->next; + sch_translate_output_matches (instance, ret_node, child, int_node, node_table, + flags, xlat_data); + } +} + +GNode * +sch_translate_output (sch_instance * instance, GNode *node, int flags, void *xlat_data) +{ + sch_xlat_data *x_data = (sch_xlat_data *) xlat_data; + sch_xlat_root *root; + GList *list; + GHashTable *node_table = NULL; + int num_nodes; + GNode *ret_node = NULL; + + if (!instance->xlat_hash_table || !node) + return node; + + if (!x_data) + return node; + + root = x_data->root; + node_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + + num_nodes = g_node_n_nodes (node, G_TRAVERSE_ALL); + for (list = g_list_first (root->matchs); list; list = g_list_next (list)) + { + x_data = list->data; + sch_translate_output_matches (instance, &ret_node, node, x_data->int_tree, node_table, + flags, x_data); + if (num_nodes <= g_hash_table_size (node_table)) + break; + } + + g_hash_table_destroy (node_table); + apteryx_free_tree (node); + + return ret_node; +}