Browse Source

XML backend: support for expat XML parser

This patch adds support for the expat XML parser. Expat is
small enough to be used in the embedded world, and mature
enough to be used in a production environment. It uses a
SAX-like parsing mechanism that needs to be transformed into
a DOM-like parser to be used with klish.
Emmanuel Deloget 12 years ago
parent
commit
8e5839f925
1 changed files with 503 additions and 0 deletions
  1. 503 0
      clish/shell/shell_expat.c

+ 503 - 0
clish/shell/shell_expat.c

@@ -0,0 +1,503 @@
+/*
+ * ------------------------------------------------------
+ * shell_xmlexpat.c
+ *
+ * This file implements the means to read an XML encoded file
+ * and populate the CLI tree based on the contents. It implements
+ * the clish_xml API using the expat XML parser
+ *
+ * expat is not your typicall XML parser. It does not work
+ * by creating a full in-memory XML tree, but by calling specific
+ * callbacks (element handlers) regularly while parsing. It's up
+ * to the user to create the corresponding XML tree if needed
+ * (obviously, this is what we're doing, as we really need the XML
+ * tree in klish).
+ *
+ * The code below do that. It transforms the output of expat
+ * to a DOM representation of the underlying XML file. This is
+ * a bit overkill, and maybe a later implementation will help to
+ * cut the work to something simpler, but the current klish
+ * implementation requires this.
+ * ------------------------------------------------------
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_LIB_EXPAT)
+#include <string.h>
+#include <stdlib.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <expat.h>
+
+#include "xmlapi.h"
+
+/** DOM_like XML node
+ *
+ * @struct clish_xmlnode_s
+ */
+struct clish_xmlnode_s {
+	char *name;
+	clish_xmlnode_t *parent; /**< parent node */
+	clish_xmlnode_t *children; /**< list of children */
+	clish_xmlnode_t *next; /**< next sibling */
+	clish_xmlnode_t *attributes; /**< attributes are nodes too */
+	char *content; /**< !NULL for text and attributes nodes */
+	clish_xmlnodetype_t type; /**< node type */
+	int depth; /**< node depth */
+	clish_xmldoc_t *doc;
+};
+
+/** DOM-like XML document
+ *
+ * @struct clish_xmldoc_s
+ */
+struct clish_xmldoc_s {
+	clish_xmlnode_t *root; /**< list of root elements */
+	clish_xmlnode_t *current; /**< current element */
+	char *filename; /**< current filename */
+};
+
+/*
+ * Expat need these functions to be able to build a DOM-like tree that
+ * will be usable by klish.
+ */
+/** Put a element at the and of an element list
+ *
+ * @param first first element of the list
+ * @param node element to add
+ * @return new first element of the list
+ */
+static clish_xmlnode_t *clish_expat_list_push_back(clish_xmlnode_t *first, clish_xmlnode_t *node)
+{
+	clish_xmlnode_t *cur = first;
+	clish_xmlnode_t *prev = NULL;
+
+	while (cur) {
+		prev = cur;
+		cur = cur->next;
+	}
+	if (prev) {
+		prev->next = node;
+		return first;
+	}
+	return node;
+}
+
+/** Generic add_attr() function
+ *
+ * @param first first attribute in the attribute list
+ * @param n attribute name
+ * @param v attribute value
+ * @return the new first attribute in the attribute list
+ */
+static clish_xmlnode_t *clish_expat_add_attr(clish_xmlnode_t *first, const char *n, const char *v)
+{
+	clish_xmlnode_t *node;
+
+	node = malloc(sizeof(clish_xmlnode_t));
+	if (!node)
+		return first;
+
+	node->name = strdup(n);
+	node->content = strdup(v);
+	node->children = NULL;
+	node->attributes = NULL;
+	node->next = NULL;
+	node->type = CLISH_XMLNODE_ATTR;
+	node->depth = 0;
+
+	return clish_expat_list_push_back(first, node);
+}
+
+/** Run through an expat attribute list, and create a DOM-like attribute list
+ *
+ * @param node parent node
+ * @param attr NULL-terminated attribute liste
+ *
+ * Each attribute uses two slots in the expat attribute list. The first one is
+ * used to store the name, the second one is used to store the value.
+ */
+static void clish_expat_add_attrlist(clish_xmlnode_t *node, const char **attr)
+{
+	int i;
+
+	for (i = 0; attr[i]; i += 2) {
+		node->attributes = clish_expat_add_attr(node->attributes,
+			attr[i], attr[i+1]);
+	}
+}
+
+/** Generic make_node() function
+ *
+ * @param parent XML parent node
+ * @param type XML node type
+ * @param n node name (can be NULL, strdup'ed)
+ * @param v node content (can be NULL, strdup'ed)
+ * @param attr attribute list
+ * @return a new node or NULL on error
+ */
+static clish_xmlnode_t *clish_expat_make_node(clish_xmlnode_t *parent,
+					      clish_xmlnodetype_t type,
+					      char *n,
+					      char *v,
+					      const char **attr)
+{
+	clish_xmlnode_t *node;
+
+	node = malloc(sizeof(clish_xmlnode_t));
+	if (!node)
+		return NULL;
+	node->name = n ? strdup(n) : NULL;
+	node->content = v ? strdup(v) : NULL;
+	node->children = NULL;
+	node->attributes = NULL;
+	node->next = NULL;
+	node->parent = parent;
+	node->doc = parent ? parent->doc : NULL;
+	node->depth = parent ? parent->depth + 1 : 0;
+	node->type = type;
+
+	if (attr)
+		clish_expat_add_attrlist(node, attr);
+
+	if (parent)
+		parent->children = clish_expat_list_push_back(parent->children, node);
+
+	return node;
+}
+
+/** Add a new XML root
+ *
+ * @param doc XML document
+ * @param el root node name
+ * @param attr expat attribute list
+ * @return a new root element
+ */
+static clish_xmlnode_t *clish_expat_add_root(clish_xmldoc_t *doc, const char *el, const char **attr)
+{
+	clish_xmlnode_t *node;
+
+	node = clish_expat_make_node(NULL, CLISH_XMLNODE_ELM, el, NULL, attr);
+	if (!node)
+		return doc->root;
+
+	doc->root = clish_expat_list_push_back(doc->root, node);
+
+	return node;
+}
+
+/** Add a new XML element as a child
+ *
+ * @param cur parent XML element
+ * @param el element name
+ * @param attr expat attribute list
+ * @return a new XMl element
+ */
+static clish_xmlnode_t *clish_expat_add_child(clish_xmlnode_t *cur, const char *el, const char **attr)
+{
+	clish_xmlnode_t *node;
+
+	node = clish_expat_make_node(cur, CLISH_XMLNODE_ELM, el, NULL, attr);
+	if (!node)
+		return cur;
+
+	return node;
+}
+
+/** Expat handler: element content
+ *
+ * @param data user data
+ * @param s content (not nul-termainated)
+ * @param len content length
+ */
+static void clish_expat_chardata_handler(void *data, const char *s, int len)
+{
+	clish_xmldoc_t *doc = data;
+
+	if (doc->current) {
+		char *content = strndup(s, len);
+
+		clish_expat_make_node(doc->current, CLISH_XMLNODE_TEXT, NULL, content, NULL);
+		/*
+		 * the previous call is a bit too generic, and strdup() content
+		 * so we need to free out own version of content.
+		 */
+		free(content);
+	}
+}
+
+/** Expat handler: start XML element
+ *
+ * @param data user data
+ * @param el element name (nul-terminated)
+ * @param attr expat attribute list
+ */
+static void clish_expat_element_start(void *data, const char *el, const char **attr)
+{
+	clish_xmldoc_t *doc = data;
+
+	if (!doc->current) {
+		doc->current = clish_expat_add_root(doc, el, attr);
+	} else {
+		doc->current = clish_expat_add_child(doc->current, el, attr);
+	}
+}
+
+/** Expat handler: end XML element
+ *
+ * @param data user data
+ * @param el element name
+ */
+static void clish_expat_element_end(void *data, const char *el)
+{
+	clish_xmldoc_t *doc = data;
+
+	if (doc->current) {
+		doc->current = doc->current->parent;
+	}
+}
+
+/** Free a node, its children and its attributes
+ *
+ * @param node node to free
+ */
+static void clish_expat_free_node(clish_xmlnode_t *cur)
+{
+	clish_xmlnode_t *node;
+	clish_xmlnode_t *first;
+
+	if (cur->attributes) {
+		first = cur->attributes;
+		while (first) {
+			node = first;
+			first = first->next;
+			clish_expat_free_node(node);
+		}
+	}
+	if (cur->children) {
+		first = cur->children;
+		while (first) {
+			node = first;
+			first = first->next;
+			clish_expat_free_node(node);
+		}
+	}
+	if (cur->name)
+		free(cur->name);
+	if (cur->content)
+		free(cur->content);
+	free(cur);
+}
+
+/*
+ * Public interface
+ */
+clish_xmldoc_t *clish_xmldoc_read(const char *filename)
+{
+	clish_xmldoc_t *doc;
+	struct stat sb;
+	int fd;
+	char *buffer;
+	XML_Parser parser;
+
+	doc = malloc(sizeof(clish_xmldoc_t));
+	if (!doc)
+		return NULL;
+	memset(doc, 0, sizeof(clish_xmldoc_t));
+	doc->filename = strdup(filename);
+	parser = XML_ParserCreate(NULL);
+	if (!parser)
+		goto error_parser_create;
+	XML_SetUserData(parser, doc);
+	XML_SetCharacterDataHandler(parser, clish_expat_chardata_handler);
+	XML_SetElementHandler(parser,
+		clish_expat_element_start,
+		clish_expat_element_end);
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0)
+		goto error_open;
+	fstat(fd, &sb);
+	buffer = malloc(sb.st_size+1);
+	read(fd, buffer, sb.st_size);
+	buffer[sb.st_size] = 0;
+	close(fd);
+
+	if (!XML_Parse(parser, buffer, sb.st_size, 1))
+		goto error_parse;
+
+	XML_ParserFree(parser);
+	free(buffer);
+
+	return doc;
+
+error_parse:
+	free(buffer);
+
+error_open:
+	XML_ParserFree(parser);
+
+error_parser_create:
+	clish_xmldoc_release(doc);
+
+	return NULL;
+}
+
+void clish_xmldoc_release(clish_xmldoc_t *doc)
+{
+	if (doc) {
+		clish_xmlnode_t *node;
+		while (doc->root) {
+			node = doc->root;
+			doc->root = node->next;
+			clish_expat_free_node(node);
+		}
+		if (doc->filename)
+			free(doc->filename);
+		free(doc);
+	}
+}
+
+int clish_xmldoc_is_valid(clish_xmldoc_t *doc)
+{
+	return doc && doc->root;
+}
+
+int clish_xmldoc_error_caps(clish_xmldoc_t *doc)
+{
+	return CLISH_XMLERR_NOCAPS;
+}
+
+int clish_xmldoc_get_err_line(clish_xmldoc_t *doc)
+{
+	return -1;
+}
+
+int clish_xmldoc_get_err_col(clish_xmldoc_t *doc)
+{
+	return -1;
+}
+
+const char *clish_xmldoc_get_err_msg(clish_xmldoc_t *doc)
+{
+	return "";
+}
+
+int clish_xmlnode_get_type(clish_xmlnode_t *node)
+{
+	if (node)
+		return node->type;
+	return CLISH_XMLNODE_UNKNOWN;
+}
+
+clish_xmlnode_t *clish_xmldoc_get_root(clish_xmldoc_t *doc)
+{
+	if (doc)
+		return doc->root;
+	return NULL;
+}
+
+clish_xmlnode_t *clish_xmlnode_parent(clish_xmlnode_t *node)
+{
+	if (node)
+		return node->parent;
+	return NULL;
+}
+
+clish_xmlnode_t *clish_xmlnode_next_child(clish_xmlnode_t *parent,
+					  clish_xmlnode_t *curchild)
+{
+	if (curchild)
+		return curchild->next;
+	if (parent)
+		return parent->children;
+	return NULL;
+}
+
+char *clish_xmlnode_fetch_attr(clish_xmlnode_t *node,
+			       const char *attrname)
+{
+	if (node) {
+		clish_xmlnode_t *n = node->attributes;
+		while (n) {
+			if (strcmp(n->name, attrname) == 0)
+				return n->content;
+			n = n->next;
+		}
+	}
+	return NULL;
+}
+
+int clish_xmlnode_get_content(clish_xmlnode_t *node, char *content,
+			      unsigned int *contentlen)
+{
+	int minlen = 1;
+
+	if (node && content && contentlen) {
+		clish_xmlnode_t *children = node->children;
+		while (children) {
+			if (children->type == CLISH_XMLNODE_TEXT && children->content)
+				minlen += strlen(children->content);
+			children = children->next;
+		}
+		if (minlen >= *contentlen) {
+			*contentlen = minlen;
+			return -E2BIG;
+		}
+		children = node->children;
+		*content = 0;
+		while (children) {
+			if (children->type == CLISH_XMLNODE_TEXT && children->content)
+				strcat(content, children->content);
+			children = children->next;
+		}
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+int clish_xmlnode_get_name(clish_xmlnode_t *node, char *name,
+			    unsigned int *namelen)
+{
+	if (node && name && namelen) {
+		if (strlen(node->name) >= *namelen) {
+			*namelen = strlen(node->name) + 1;
+			return -E2BIG;
+		}
+		sprintf(name, "%s", node->name);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+void clish_xmlnode_print(clish_xmlnode_t *node, FILE *out)
+{
+	if (node) {
+		int i;
+		clish_xmlnode_t *a;
+		for (i=0; i<node->depth; ++i) {
+			fprintf(out, "  ");
+		}
+		fprintf(out, "<%s", node->name);
+		a = node->attributes;
+		while (a) {
+			fprintf(out, " %s='%s'", a->name, a->content);
+			a = a->next;
+		}
+		fprintf(out, ">...");
+	}
+}
+
+void clish_xml_release(void *p)
+{
+	/* nothing to release */
+}
+
+#endif /* HAVE_LIB_EXPAT */
+