changeset 1607:14690e624e9d

Add modules
author Myhailo Danylenko <isbear@ukrpost.net>
date Sun, 11 Oct 2009 16:01:52 +0200
parents d7f26538c24c
children c8cf21f07666
files mcabber/configure.ac mcabber/src/commands.c mcabber/src/commands.h mcabber/src/compl.c mcabber/src/compl.h mcabber/src/events.h mcabber/src/hooks.c mcabber/src/hooks.h mcabber/src/main.c mcabber/src/settings.c mcabber/src/xmpp_helper.c mcabber/src/xmpp_helper.h
diffstat 12 files changed, 426 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/configure.ac	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/configure.ac	Sun Oct 11 16:01:52 2009 +0200
@@ -130,13 +130,22 @@
               [Define if ncurses has ESCDELAY variable])
 fi
 
+AC_ARG_ENABLE(modules, [  --enable-modules         enable dynamic modules loading],
+              enable_modules=$enableval)
+if test "x$enable_modules" = "xyes"; then
+  AC_DEFINE(MODULES_ENABLE, 1, [Define if you want dynamic modules loading])
+  gmodule_module=gmodule
+else
+  gmodule_module=''
+fi
+
 # Check for glib
 AM_PATH_GLIB_2_0(2.14.0,
                  [AC_DEFINE([HAVE_GLIB_REGEX], 1,
                             [Define if GLib has regex support])],
                  [AM_PATH_GLIB_2_0(2.0.0, , AC_MSG_ERROR([glib is required]),
-                                  [g_list_append])],
-                 [g_regex_new])
+                                  [g_list_append], ["$gmodule_module"])],
+                 [g_regex_new "$gmodule_module"])
 
 # Check for loudmouth
 PKG_CHECK_MODULES(LOUDMOUTH, loudmouth-1.0 >= 1.4.3)
--- a/mcabber/src/commands.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/commands.c	Sun Oct 11 16:01:52 2009 +0200
@@ -93,11 +93,47 @@
 // Global variable for the commands list
 static GSList *Commands;
 
+#ifdef MODULES_ENABLE
+#include <gmodule.h>
+
+static void do_load(char *arg);
+static void do_unload(char *arg);
+
+typedef struct {
+  char *name;
+  GModule *module;
+} loaded_module_t;
+
+GSList *loaded_modules = NULL;
+
+static gint cmd_del_comparator (cmd *a, const char *name)
+{
+  return strcmp(a->name, name);
+}
+
+gpointer cmd_del(const char *name)
+{
+  GSList *command = g_slist_find_custom (Commands, name, (GCompareFunc) cmd_del_comparator);
+  if (command) {
+    cmd *cmnd = command->data;
+    gpointer userdata = cmnd->userdata;
+    Commands = g_slist_delete_link(Commands, command);
+    compl_del_category_word(COMPL_CMD, cmnd->name);
+    g_free(cmnd);
+    return userdata;
+  }
+  return NULL;
+}
 
 //  cmd_add()
 // Adds a command to the commands list and to the CMD completion list
+void cmd_add(const char *name, const char *help, guint flags_row1,
+             guint flags_row2, void (*f)(char*), gpointer userdata)
+#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL);
+#else
 static void cmd_add(const char *name, const char *help,
         guint flags_row1, guint flags_row2, void (*f)(char*))
+#endif
 {
   cmd *n_cmd = g_new0(cmd, 1);
   strncpy(n_cmd->name, name, 32-1);
@@ -105,6 +141,9 @@
   n_cmd->completion_flags[0] = flags_row1;
   n_cmd->completion_flags[1] = flags_row2;
   n_cmd->func = f;
+#ifdef MODULES_ENABLE
+  n_cmd->userdata = userdata;
+#endif
   Commands = g_slist_append(Commands, n_cmd);
   // Add to completion CMD category
   compl_add_category_word(COMPL_CMD, name);
@@ -165,6 +204,10 @@
   cmd_add("status_to", "Show or set your status for one recipient",
           COMPL_JID, COMPL_STATUS, &do_status_to);
   cmd_add("version", "Show mcabber version", 0, 0, &do_version);
+#ifdef MODULES_ENABLE
+  cmd_add("load", "Load module", 0, 0, &do_load);
+  cmd_add("unload", "Unload module", 0, 0, &do_unload);
+#endif
 
   // Status category
   compl_add_category_word(COMPL_STATUS, "online");
@@ -299,6 +342,23 @@
   compl_add_category_word(COMPL_COLOR, "mucnick");
 }
 
+#ifdef MODULES_ENABLE
+void cmd_deinit ()
+{
+  GSList *el = loaded_modules;
+  while (el) {
+    loaded_module_t *module = el->data;
+    if (!g_module_close ((GModule *) module->module))
+      scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s",
+                    g_module_error ());
+    g_free (module->name);
+    g_free (module);
+    el = g_slist_next (el);
+  }
+  g_slist_free (loaded_modules);
+}
+#endif
+
 //  expandalias(line)
 // If there is one, expand the alias in line and returns a new allocated line
 // If no alias is found, returns line
@@ -435,7 +495,14 @@
     p++;
   // Call command-specific function
   retval_for_cmds = 0;
+#ifdef MODULES_ENABLE
+  if (curcmd->userdata)
+    (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
+  else
+    (*curcmd->func)(p);
+#else
   (*curcmd->func)(p);
+#endif
   g_free(xpline);
   return retval_for_cmds;
 }
@@ -2902,6 +2969,57 @@
   g_slist_free(bm);
 }
 
+#ifdef MODULES_ENABLE
+static void do_load(char *arg)
+{
+  if (!arg || !*arg) {
+    scr_LogPrint (LPRINT_LOGNORM, "Missing modulename.");
+    return;
+  }
+  char *mdir = expand_filename (settings_opt_get ("modules_dir"));
+  char *path = g_module_build_path (mdir, arg);
+  GModule *mod = g_module_open (path, G_MODULE_BIND_LAZY);
+  if (!mod)
+    scr_LogPrint (LPRINT_LOGNORM, "Module %s loading failed: %s",
+                  path, g_module_error ());
+  else {
+    loaded_module_t *module = g_new (loaded_module_t, 1);
+    module->name = g_strdup (arg);
+    module->module = mod;
+    loaded_modules = g_slist_prepend (loaded_modules, module);
+    scr_LogPrint (LPRINT_LOGNORM, "Loaded module %s", arg);
+  }
+  g_free (path);
+  if (mdir)
+    g_free (mdir);
+}
+
+static int module_list_comparator (loaded_module_t *module, const char *name)
+{
+  return g_strcmp0 (module->name, name);
+}
+
+static void do_unload(char *arg)
+{
+  if (!arg || !*arg) {
+    scr_LogPrint (LPRINT_LOGNORM, "Missing modulename.");
+    return;
+  }
+  GSList *module = g_slist_find_custom (loaded_modules, arg, (GCompareFunc) module_list_comparator);
+  if (module) {
+    loaded_module_t *mod = module->data;
+    if (!g_module_close ((GModule *) mod->module))
+      scr_LogPrint (LPRINT_LOGNORM, "Module unloading failed: %s",
+                    g_module_error ());
+    else {
+      g_free (mod->name);
+      g_free (mod);
+      loaded_modules = g_slist_delete_link (loaded_modules, module);
+    }
+  } else
+    scr_LogPrint (LPRINT_LOGNORM, "Module %s not loaded.", arg);
+}
+#endif
 
 static void do_room(char *arg)
 {
--- a/mcabber/src/commands.h	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/commands.h	Sun Oct 11 16:01:52 2009 +0200
@@ -3,12 +3,17 @@
 
 #include <glib.h>
 
+#include "config.h"
+
 // Command structure
 typedef struct {
   char name[32];
   const char *help;
   guint completion_flags[2];
   void (*func)(char *);
+#ifdef MODULES_ENABLE
+  gpointer userdata;
+#endif
 } cmd;
 
 void cmd_init(void);
@@ -16,6 +21,11 @@
 int  process_line(const char *line);
 int  process_command(const char *line, guint iscmd);
 char *expandalias(const char *line);
+#ifdef MODULES_ENABLE
+void cmd_deinit(void);
+gpointer cmd_del(const char *name);
+void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata);
+#endif
 
 extern char *mcabber_version(void);
 extern void mcabber_set_terminate_ui(void);
--- a/mcabber/src/compl.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/compl.c	Sun Oct 11 16:01:52 2009 +0200
@@ -53,6 +53,44 @@
 static GSList *Categories;
 static compl *InputCompl;
 
+#ifdef MODULES_ENABLE
+guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \
+                        COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \
+                        COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \
+                        COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \
+                        COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \
+                        COMPL_OTR|COMPL_OTRPOLICY| \
+                        0;
+
+//  compl_new_category()
+// Reserves id for new completion category.
+// Returns 0, if no more categories can be allocated.
+// Note, that user should not make any assumptions about id nature,
+// as it is likely to change in future.
+guint compl_new_category (void)
+{
+  guint i = 0;
+  while ((registered_cats >> i) & 1)
+    i++;
+  if (i >= sizeof (guint)*8)
+    return 0;
+  else {
+    guint id = 1 << i;
+    registered_cats |= id;
+    return id;
+  }
+}
+
+//  compl_del_category (id)
+// Frees reserved id for category.
+// Note, that for now it not validates its input, so, be careful
+// and specify exactly what you get from compl_new_category.
+void compl_del_category (guint id)
+{
+  registered_cats &= ~id;
+}
+#endif
+
 //  new_completion(prefix, compl_cat)
 // . prefix    = beginning of the word, typed by the user
 // . compl_cat = pointer to a completion category list (list of *char)
--- a/mcabber/src/compl.h	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/compl.h	Sun Oct 11 16:01:52 2009 +0200
@@ -3,6 +3,8 @@
 
 #include <glib.h>
 
+#include "config.h"
+
 #define COMPL_CMD         (1U<<0)
 #define COMPL_JID         (1U<<1)
 #define COMPL_URLJID      (1U<<2)   // Not implemented yet
@@ -24,6 +26,12 @@
 #define COMPL_COLOR       (1U<<18)
 #define COMPL_OTR         (1U<<19)
 #define COMPL_OTRPOLICY   (1U<<20)
+#ifdef MODULES_ENABLE
+#define COMPL_MAX_BUILTIN (1U<<20)
+
+guint compl_new_category (void);
+void  compl_del_category (guint id);
+#endif
 
 void    compl_add_category_word(guint, const char *command);
 void    compl_del_category_word(guint categ, const char *word);
--- a/mcabber/src/events.h	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/events.h	Sun Oct 11 16:01:52 2009 +0200
@@ -1,6 +1,7 @@
 #ifndef __EVENTS_H__
 #define __EVENTS_H__ 1
 
+#include "config.h" // MODULES_ENABLE
 
 #define EVS_DEFAULT_TIMEOUT 90
 #define EVS_MAX_TIMEOUT     432000
@@ -11,7 +12,10 @@
 
 typedef enum {
   EVS_TYPE_SUBSCRIPTION = 1,
-  EVS_TYPE_INVITATION = 2
+  EVS_TYPE_INVITATION = 2,
+#ifdef MODULES_ENABLE
+  EVS_TYPE_USER = 3,
+#endif
 } evs_type;
 
 /* Common structure for events (evs) and IQ requests (iqs) */
--- a/mcabber/src/hooks.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/hooks.c	Sun Oct 11 16:01:52 2009 +0200
@@ -36,6 +36,43 @@
 #include "commands.h"
 #include "fifo.h"
 
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+typedef struct {
+  hk_handler_t handler;
+  gpointer     userdata;
+} hook_list_data_t;
+
+static GSList *hk_handler_queue = NULL;
+
+void hk_add_handler (hk_handler_t handler, gpointer userdata)
+{
+  hook_list_data_t *h = g_new (hook_list_data_t, 1);
+  h->handler  = handler;
+  h->userdata = userdata;
+  hk_handler_queue = g_slist_append (hk_handler_queue, h);
+}
+
+static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b)
+{
+  if (a->handler == b->handler && a->userdata == b->userdata)
+    return 0;
+  else
+    return 1;
+}
+
+void hk_del_handler (hk_handler_t handler, gpointer userdata)
+{
+  hook_list_data_t h = { handler, userdata };
+  GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb);
+  if (el) {
+    g_free (el->data);
+    hk_handler_queue = g_slist_delete_link (hk_handler_queue, el);
+  }
+}
+#endif
+
 static char *extcmd;
 
 static const char *COMMAND_ME = "/me ";
@@ -209,6 +246,42 @@
   if (settings_opt_get_int("eventcmd_use_nickname"))
     ename = roster_getname(bjid);
 
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+#if 0
+      hk_arg_t *args = g_new (hk_arg_t, 5);
+      args[0].name = "hook";
+      args[0].value = "hook-message-in";
+      args[1].name = "jid";
+      args[1].value = bjid;
+      args[2].name = "message";
+      args[2].value = wmsg;
+      args[3].name = "groupchat";
+      args[3].value = is_groupchat ? "true" : "false";
+      args[4].name = NULL;
+      args[4].value = NULL;
+#else
+      // We can use a const array for keys/static values, so modules
+      // can do fast known to them args check by just comparing pointers...
+      hk_arg_t args[] = {
+        { "hook", "hook-message-in" },
+        { "jid", bjid },
+        { "message", wmsg },
+        { "groupchat", is_groupchat ? "true" : "false" },
+        { NULL, NULL },
+      };
+#endif
+      while (h) {
+        hook_list_data_t *data = h->data;
+        (data->handler) (args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
   // External command
   // - We do not call hk_ext_cmd() for history lines in MUC
   // - We do call hk_ext_cmd() for private messages in a room
@@ -287,6 +360,25 @@
   if (!nick)
     hlog_write_message(bjid, timestamp, 1, msg);
 
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      hk_arg_t args[] = {
+        { "hook", "hook-message-out" },
+        { "jid", bjid },
+        { "message", wmsg },
+        { NULL, NULL },
+      };
+      while (h) {
+        hook_list_data_t *data = h->data;
+        (data->handler) (args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
   // External command
   hk_ext_cmd(bjid, 'M', 'S', NULL);
 
@@ -357,6 +449,33 @@
   buddylist_build();
   scr_DrawRoster();
   hlog_write_status(bjid, timestamp, status, status_msg);
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      char os[2] = " \0";
+      char ns[2] = " \0";
+      hk_arg_t args[] = {
+        { "hook", "hook-status-change" },
+        { "jid", bjid },
+        { "resource", rn },
+        { "old_status", os },
+        { "new_status", ns },
+        { "message", status_msg ? status_msg : "" },
+        { NULL, NULL },
+      };
+      os[0] = imstatus2char[oldstat];
+      ns[0] = imstatus2char[status];
+      while (h) {
+        hook_list_data_t *data = h->data;
+        (data->handler) (args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
   // External command
   hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
 }
@@ -367,6 +486,28 @@
   scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s",
                imstatus2char[old_status], imstatus2char[new_status],
                (msg ? msg : ""));
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      char ns[2] = " \0";
+      hk_arg_t args[] = {
+        { "hook", "hook-my-status-change" },
+        { "new_status", ns },
+        { "message", msg ? msg : "" },
+        { NULL, NULL },
+      };
+      ns[0] = imstatus2char[new_status];
+      while (h) {
+        hook_list_data_t *data = h->data;
+        (data->handler) (args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
   //hlog_write_status(NULL, 0, status);
 }
 
@@ -390,6 +531,23 @@
   if (process_command(cmdline, TRUE) == 255)
     mcabber_set_terminate_ui();
 
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      hk_arg_t args[] = {
+        { "hook", hookname },
+        { NULL, NULL },
+      };
+      while (h) {
+        hook_list_data_t *data = h->data;
+        (data->handler) (args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
   g_free(cmdline);
   g_free(buf);
 }
--- a/mcabber/src/hooks.h	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/hooks.h	Sun Oct 11 16:01:52 2009 +0200
@@ -9,6 +9,21 @@
 #define ENCRYPTED_PGP   1
 #define ENCRYPTED_OTR   2
 
+#include "config.h"
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+typedef struct {
+  const char *name;
+  const char *value;
+} hk_arg_t;
+
+typedef void (*hk_handler_t) (hk_arg_t *args, gpointer userdata);
+
+void hk_add_handler (hk_handler_t handler, gpointer userdata);
+void hk_del_handler (hk_handler_t handler, gpointer userdata);
+#endif
+
 void hk_mainloop(void);
 void hk_message_in(const char *bjid, const char *resname,
                    time_t timestamp, const char *msg, LmMessageSubType type,
--- a/mcabber/src/main.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/main.c	Sun Oct 11 16:01:52 2009 +0200
@@ -169,6 +169,9 @@
 #ifdef HAVE_GPGME
   puts("Compiled with GPG support.");
 #endif
+#ifdef MODULES_ENABLE
+  puts ("Compiled with modules support");
+#endif
 #ifdef HAVE_LIBOTR
   puts("Compiled with OTR support.");
 #endif
@@ -416,6 +419,9 @@
   g_main_loop_run(main_loop);
 
   scr_TerminateCurses();
+#ifdef MODULES_ENABLE
+  cmd_deinit();
+#endif
   fifo_deinit();
 #ifdef HAVE_LIBOTR
   otr_terminate();
--- a/mcabber/src/settings.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/settings.c	Sun Oct 11 16:01:52 2009 +0200
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "config.h"
 #include "settings.h"
 #include "commands.h"
 #include "logprint.h"
@@ -176,12 +177,15 @@
       continue;
 
     // We only allow assignments line, except for commands "pgp", "source",
-    // "color" and "otrpolicy", unless we're in runtime (i.e. not startup).
+    // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup).
     if (runtime ||
         (strchr(line, '=') != NULL)        ||
         startswith(line, "pgp ", FALSE)    ||
         startswith(line, "source ", FALSE) ||
         startswith(line, "color ", FALSE)  ||
+#ifdef MODULES_ENABLE
+        startswith(line, "load ", FALSE)   ||
+#endif
         startswith(line, "otrpolicy", FALSE)) {
       // Only accept a few "safe" commands
       if (!runtime &&
@@ -191,6 +195,9 @@
           !startswith(line, "pgp ", FALSE)    &&
           !startswith(line, "source ", FALSE) &&
           !startswith(line, "color ", FALSE)  &&
+#ifdef MODULES_ENABLE
+          !startswith(line, "load ", FALSE)   &&
+#endif
           !startswith(line, "otrpolicy ", FALSE)) {
         scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
                      "this command can't be used here", ln);
--- a/mcabber/src/xmpp_helper.c	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/xmpp_helper.c	Sun Oct 11 16:01:52 2009 +0200
@@ -30,6 +30,7 @@
 #include "utils.h"
 #include "caps.h"
 #include "logprint.h"
+#include "config.h"
 
 time_t iqlast; // last message/status change time
 
@@ -74,6 +75,37 @@
 };
 
 
+#ifdef MODULES_ENABLE
+static GSList *xmpp_additional_features = NULL;
+static char *ver, *ver_notavail;
+
+void xmpp_add_feature (const char *xmlns)
+{
+  if (xmlns) {
+    ver = NULL;
+    ver_notavail = NULL;
+    xmpp_additional_features = g_slist_append(xmpp_additional_features,
+                                              g_strdup (xmlns));
+  }
+}
+
+void xmpp_del_feature (const char *xmlns)
+{
+  GSList *feature = xmpp_additional_features;
+  while (feature) {
+    if (!strcmp(feature->data, xmlns)) {
+      ver = NULL;
+      ver_notavail = NULL;
+      g_free (feature->data);
+      xmpp_additional_features = g_slist_delete_link(xmpp_additional_features,
+                                                     feature);
+      return;
+    }
+    feature = g_slist_next (feature);
+  }
+}
+#endif
+
 const gchar* lm_message_node_get_child_value(LmMessageNode *node,
                                              const gchar *child)
 {
@@ -180,7 +212,9 @@
 // number) so that it doesn't conflict with the official client.
 const char *entity_version(enum imstatus status)
 {
+#ifndef MODULES_ENABLE
   static char *ver, *ver_notavail;
+#endif
 
   if (ver && (status != notavail))
     return ver;
@@ -204,6 +238,15 @@
       (!settings_opt_get_int("iq_last_disable_when_notavail") ||
        status != notavail))
    caps_add_feature("", NS_LAST);
+#ifdef MODULES_ENABLE
+  {
+    GSList *el = xmpp_additional_features;
+    while (el) {
+      caps_add_feature("", el->data);
+      el = g_slist_next (el);
+    }
+  }
+#endif
 
   if (status == notavail) {
     ver_notavail = caps_generate();
--- a/mcabber/src/xmpp_helper.h	Sun Oct 11 16:01:31 2009 +0200
+++ b/mcabber/src/xmpp_helper.h	Sun Oct 11 16:01:52 2009 +0200
@@ -6,6 +6,7 @@
 
 #include "xmpp.h"
 #include "xmpp_defines.h"
+#include "config.h"
 
 extern time_t iqlast;           /* last message/status change time */
 
@@ -23,6 +24,11 @@
 };
 
 
+#ifdef MODULES_ENABLE
+void xmpp_add_feature (const char *xmlns);
+void xmpp_del_feature (const char *xmlns);
+#endif
+
 LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
 LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
                                           const char *xmlns);