[svn-commits] mvanbaak: branch group/appdocsxml r135436 - in /team/group/appdocsxml: ./ doc...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Sun Aug 3 18:33:11 CDT 2008


Author: mvanbaak
Date: Sun Aug  3 18:33:10 2008
New Revision: 135436

URL: http://svn.digium.com/view/asterisk?view=rev&rev=135436
Log:
Introduce ast_xml_* wrappers.
  Those wrappers are coded with xml parsing library independency in mind.
  Right now, we focus on libxml2 but with this setup we can support others as well
This work was done by eliel, and cleaned up to meet coding guidelines by me

Ignore the core-en_US.xml file in the doc/ directory as it's autogenerated

Add support for the description now we have <para> nodes in there to the TODO file

Added:
    team/group/appdocsxml/include/asterisk/xml.h   (with props)
    team/group/appdocsxml/main/xml.c   (with props)
Modified:
    team/group/appdocsxml/TODO_appdocsxml
    team/group/appdocsxml/doc/   (props changed)
    team/group/appdocsxml/main/Makefile
    team/group/appdocsxml/main/pbx.c

Modified: team/group/appdocsxml/TODO_appdocsxml
URL: http://svn.digium.com/view/asterisk/team/group/appdocsxml/TODO_appdocsxml?view=diff&rev=135436&r1=135435&r2=135436
==============================================================================
--- team/group/appdocsxml/TODO_appdocsxml (original)
+++ team/group/appdocsxml/TODO_appdocsxml Sun Aug  3 18:33:10 2008
@@ -25,7 +25,7 @@
 
 **** Todo ****
 
-Move to libxml2 because libxml is LGPL and thus not ok to link in the asterisk core.
+Make the description etc work again now we have <para> tags in there.
 
 ONCE we have a acceptable format that most agree on, we will
 need to convert all the applications to use this method (obviously).
@@ -51,6 +51,8 @@
 This way we can prove we really have everything we need !
 
 === DONE ===
+
+Move to libxml2 because libxml is LGPL and thus not ok to link in the asterisk core.
 
 Possibly, instead of passing NULL, NULL on ast_register_application
 and ast_function_register, just:

Propchange: team/group/appdocsxml/doc/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Sun Aug  3 18:33:10 2008
@@ -1,1 +1,2 @@
 api
+core-en_US.xml

Added: team/group/appdocsxml/include/asterisk/xml.h
URL: http://svn.digium.com/view/asterisk/team/group/appdocsxml/include/asterisk/xml.h?view=auto&rev=135436
==============================================================================
--- team/group/appdocsxml/include/asterisk/xml.h (added)
+++ team/group/appdocsxml/include/asterisk/xml.h Sun Aug  3 18:33:10 2008
@@ -1,0 +1,108 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008, Eliel C. Sardanons <eliels at gmail.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _ASTERISK_XML_H
+#define _ASTERISK_XML_H
+
+#ifdef HAVE_LIBXML2
+/* libxml2 specific definitions */
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+typedef xmlNode ast_xml_node;
+typedef xmlDoc ast_xml_doc;
+typedef char ast_xml_attr;
+typedef char ast_xml_text;
+
+/* Node member mapping, not always the child node is called 'children' */
+#define AST_XML_CHILD	children
+#define AST_XML_NEXT	next
+#define AST_XML_PREV	prev
+#define AST_XML_PARENT	parent
+#define AST_XML_NAME	name
+
+#endif /* HAVE_LIBXML2 */
+
+#define AST_XML_DESCEND 0
+#define AST_XML_NO_DESCEND 1
+
+/*! \brief Initialize the XML library implementation. 
+ *         This function is used to setup everything needed
+ *         to start working with the xml implementation.
+ */
+int ast_xml_init(void);
+
+/*! \brief Cleanup library allocated global data. */
+int ast_xml_finish(void);
+
+/*! \brief Open an XML document.
+ *  \param filename Document path.
+ *  \return NULL on error, or the ast_xml_doc reference 
+ */
+ast_xml_doc *ast_xml_open(char *filename);
+
+/*! \brief Close an already open document and free the used
+ *        structure.
+ */
+void ast_xml_close(ast_xml_doc *doc);
+
+/*! \brief Get the document root node.
+ *  \param doc Document reference
+ *  \return NULL on error, root node on success.
+ */
+ast_xml_node *ast_xml_get_root(ast_xml_doc *doc);
+
+/*! \brief Free node 
+ *  \param node Node to be released.
+ */
+void ast_xml_free_node(ast_xml_node *node);
+
+/*! \brief Free an attribute returned by ast_xml_get_attribute()
+ *  \param data pointer to be freed.
+ */
+void ast_xml_free_attr(ast_xml_attr *attribute);
+
+/*! \brief Free a content element 
+ *  \param text text to be freed.
+ */
+void ast_xml_free_text(ast_xml_text *text);
+
+/*! \brief Get a node attribute by name
+ *  \param node Node where to search the attribute.
+ *  \param attrname Attribute name.
+ *  \return NULL on error, or the attribute value on success.
+ */
+ast_xml_attr *ast_xml_get_attribute(ast_xml_node *node, const char *attrname);
+
+/*! \brief Find a node element by name.
+ *  \param node This is the node starting point.
+ *  \param name Node name to find.
+ *  \param attrname attribute name to match (if NULL it won't be matched).
+ *  \param attrvalue attribute value to match (if NULL it won't be matched).
+ *  \return NULL if not found, or the node on success.
+ */
+ast_xml_node *ast_xml_find_element(ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue);
+
+/*! \brief Get an element content string.
+ *  \param xmldoc XML document element.
+ *  \param node node from where to get the string.
+ *  \return return the content of node.
+ */
+char *ast_xml_get_text(ast_xml_doc *xmldoc, ast_xml_node *node);
+
+#endif /* _ASTERISK_XML_H */
+

Propchange: team/group/appdocsxml/include/asterisk/xml.h
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/group/appdocsxml/include/asterisk/xml.h
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/group/appdocsxml/include/asterisk/xml.h
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: team/group/appdocsxml/main/Makefile
URL: http://svn.digium.com/view/asterisk/team/group/appdocsxml/main/Makefile?view=diff&rev=135436&r1=135435&r2=135436
==============================================================================
--- team/group/appdocsxml/main/Makefile (original)
+++ team/group/appdocsxml/main/Makefile Sun Aug  3 18:33:10 2008
@@ -28,7 +28,7 @@
 	cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \
 	strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \
 	astobj2.o hashtab.o global_datastores.o version.o \
-	features.o taskprocessor.o timing.o
+	features.o taskprocessor.o timing.o xml.o
 
 # we need to link in the objects statically, not as a library, because
 # otherwise modules will not have them available if none of the static

Modified: team/group/appdocsxml/main/pbx.c
URL: http://svn.digium.com/view/asterisk/team/group/appdocsxml/main/pbx.c?view=diff&rev=135436&r1=135435&r2=135436
==============================================================================
--- team/group/appdocsxml/main/pbx.c (original)
+++ team/group/appdocsxml/main/pbx.c Sun Aug  3 18:33:10 2008
@@ -26,10 +26,6 @@
 #include "asterisk.h"
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#ifdef XML_DOCUMENTATION
-#include "../menuselect/mxml/mxml.h"
-#endif
 
 #include "asterisk/_private.h"
 #include "asterisk/paths.h"	/* use ast_config_AST_SYSTEM_NAME */
@@ -69,6 +65,7 @@
 #include "asterisk/module.h"
 #include "asterisk/indications.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/xml.h"
 
 #define AST_MODULE "pbx"
 
@@ -338,10 +335,9 @@
 static unsigned int hashtab_hash_labels(const void *obj);
 static void __ast_internal_context_destroy( struct ast_context *con);
 #ifdef XML_DOCUMENTATION
-mxml_node_t *documentation_tree = NULL;
-static char *ast_mxml_get_field(const char *type, const char *name, const char *var); 
-static char *ast_mxml_build_syntax(const char *type, const char *name); 
-void _mxml_error(const char *cb);
+ast_xml_doc *documentation_tree = NULL;
+static char *ast_xml_doc_get_field(const char *type, const char *name, const char *var); 
+//static char *ast_xml_doc_build_syntax(const char *type, const char *name);
 #endif
 
 /* a func for qsort to use to sort a char array */
@@ -2785,11 +2781,28 @@
 }
 
 #ifdef XML_DOCUMENTATION
-static char *ast_mxml_build_syntax(const char *type, const char *name) {
-	
+
+#if 0
+/*! \brief Get the application node for 'name' application with language 'language'
+ *         if we don't find any, get the first application no matter which language with
+ *         this name.
+ *  \param type 'application' or 'function'?
+ *  \param name Application or Function name.
+ *  \param language Try to get this language (if not found try with en_US)
+ *  \return A node of type ast_xml_node.
+ */
+static ast_xml_node *ast_xml_doc_get_application(const char *type, const char *name, const char *language) {
+	/* XXX: We should implement this search function, to avoid repeating this code. */	
+	return NULL;
+}
+#endif
+
+#if 0
+static char *ast_xml_doc_build_syntax(const char *type, const char *name) 
+{	
 	const char *tmp = NULL;
 	int req_found = 1; 
-	mxml_node_t *node = NULL, *ret = NULL, *req;
+	ast_xml_node *node = NULL, *ret = NULL, *req, *root;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		ast_log(LOG_WARNING, "Tried to look in XML tree with faulty values.\n");
@@ -2801,9 +2814,15 @@
 		ast_log(LOG_DEBUG, "Parsing XML Tree Error\n");
 		return NULL; 
 	}
-
-	node = mxmlFindElement(documentation_tree, documentation_tree, type, "name", name, MXML_DESCEND);
-	while (node && node->child) {
+	
+	root = ast_xml_get_root(documentation_tree);
+	if (!root) {
+		ast_log(LOG_DEBUG, "Error getting documentation root node\n");
+		return NULL;
+	}
+
+	node = ast_xml_find_element(root->AST_XML_CHILD, type, "name", name);
+	while (node && node->AST_XML_CHILD) {
 		tmp = mxmlElementGetAttr(node, "language");
 		if (!strcmp(tmp, documentation_language)) {
 			ret = mxmlFindElement(node, documentation_tree, NULL, NULL, NULL, MXML_DESCEND_FIRST);
@@ -2830,20 +2849,19 @@
 		ret = mxmlFindElement(ret, documentation_tree, "option", NULL, NULL, MXML_DESCEND);
 	} 
 
-	if (!ret || !ret->child) {
+	if (!ret || !ret->AST_XML_CHILD) {
 		ast_log(LOG_WARNING, "Cannot find option variables in tree '%s'\n", name);
 		return NULL;
 	}
 
-	return ret->child->value.opaque;
-}
-
-static char *ast_mxml_get_field(const char *type, const char *name, const char *var) {
-	
-	const char *tmp = NULL;
-	mxml_node_t *node, *ret;
-
-	node = ret = NULL;
+	return ret->AST_XML_CHILD->value.opaque;
+}
+#endif /* if 0 */
+
+static char *ast_xml_doc_get_field(const char *type, const char *name, const char *var)
+{
+	ast_xml_node *node, *ret = NULL;
+	ast_xml_attr *lang;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		ast_log(LOG_WARNING, "Tried to look in XML tree with faulty values.\n");
@@ -2851,33 +2869,47 @@
 	}
 
 	/* not fully initted yet */
-	if (documentation_tree == NULL) {
-		ast_log(LOG_DEBUG, "Parsing XML Tree Error\n");
+	if (!documentation_tree) {
+		ast_log(LOG_DEBUG, "Parsing Documentation XML Error\n");
 		return NULL; 
 	}
 
-	node = mxmlFindElement(documentation_tree, documentation_tree, type, "name", name, MXML_DESCEND);
-	while (node && node->child) {
-		tmp = mxmlElementGetAttr(node, "language");
-		if (!strcmp(tmp, documentation_language)) {
-			ret = mxmlFindElement(node, documentation_tree, var, NULL, NULL, MXML_DESCEND_FIRST);
-			break;
-		}
-		node = mxmlFindElement(node, documentation_tree, type, "name", name, MXML_DESCEND);
-	}
-
-	/* If we still could not find the language, chose the first one found (english) */
-	if (!ret) {
-		node = mxmlFindElement(documentation_tree, documentation_tree, type, "name", name, MXML_DESCEND);
-		ret = mxmlFindElement(node, documentation_tree, var, NULL, NULL, MXML_DESCEND);
-	}
-
-	if (!ret || !ret->child) {
-		ast_log(LOG_WARNING, "Cannot find varible '%s' in tree '%s'\n", name, var);
+	node = ast_xml_get_root(documentation_tree);
+	node = node->AST_XML_CHILD;
+	while (node) {
+		node = ast_xml_find_element(node, type, "name", name);
+		if (node) {
+			/* Check language */
+			lang = ast_xml_get_attribute(node, "language");
+			if (!strcmp(lang, documentation_language)) {
+				ast_xml_free_attr(lang);
+				break;
+			}
+			ast_xml_free_attr(lang);
+			node = node->AST_XML_NEXT;
+		}
+	}
+	
+	if (node && node->AST_XML_CHILD) {
+		ret = ast_xml_find_element(node->AST_XML_CHILD, var, NULL, NULL);
+	} else {
+		/* We didn't find the application documentation for the specified language,
+		   so, try to load documentation for any language */
+		node = ast_xml_get_root(documentation_tree);
+		if (node && node->AST_XML_CHILD) {
+			node = ast_xml_find_element(node->AST_XML_CHILD, type, "name", name);
+			if (node && node->AST_XML_CHILD) {
+				ret = ast_xml_find_element(node->AST_XML_CHILD, var, NULL, NULL);
+			}
+		}
+	}
+
+	if (!ret || !ret->AST_XML_CHILD) {
+		ast_log(LOG_DEBUG, "Cannot find varible '%s' in tree '%s'\n", name, var);
 		return NULL;
 	}
 
-	return ret->child->value.opaque;
+	return ast_xml_get_text(documentation_tree, ret);
 }
 #endif /* XML_DOCUMENTATION */
 
@@ -2894,8 +2926,8 @@
 #ifdef XML_DOCUMENTATION
 	/* Let's try to find it in the Documentation XML */
 	if (ast_strlen_zero(acf->desc) && ast_strlen_zero(acf->synopsis)) {
-		acf->synopsis = ast_mxml_get_field("function", acf->name, "synopsis");
-		acf->desc = ast_mxml_get_field("function", acf->name, "description");
+		acf->synopsis = ast_xml_doc_get_field("function", acf->name, "synopsis");
+		acf->desc = ast_xml_doc_get_field("function", acf->name, "description");
 	}
 #endif
 
@@ -4604,8 +4636,8 @@
 #ifdef XML_DOCUMENTATION
 	/* Try to lookup the docs in our XML documentation database */
 	if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) {
-		tmp->synopsis = ast_mxml_get_field("application", app, "synopsis");
-		tmp->description = ast_mxml_get_field("application", app, "description");
+		tmp->synopsis = ast_xml_doc_get_field("application", app, "synopsis");
+		tmp->description = ast_xml_doc_get_field("application", app, "description");
 	} else {
 		tmp->synopsis = synopsis;
 		tmp->description = description;
@@ -4635,42 +4667,56 @@
 }
 
 #ifdef XML_DOCUMENTATION
-void _mxml_error(const char *cb) {
-	ast_log(LOG_ERROR, "Loading XML Problem: '%s'\n", cb);
-	documentation_tree = NULL;
-}
 
 /*! \brief Load XML Document into buffer for parsing into a list */
 static int ast_load_documentation(void) 
 {
-	FILE *xmldoc;
+	ast_xml_node *root_node;
 	char *path;
+
+	documentation_tree = NULL;
+
+	/* Initialize the libxml*/
+	ast_xml_init();
 
 	/* This memory is automagically freed */
 	path = alloca(strlen(ast_config_AST_DATA_DIR) + strlen(documentation_language) + strlen("/core-.xml") + 1);
 	sprintf(path, "%s/core-%s.xml", ast_config_AST_DATA_DIR, documentation_language);
 
-	/* For now, I just throw away cdata */
-	xmldoc = fopen(path, "r");
-
-	mxmlSetErrorCallback(_mxml_error);
-
-	if (!xmldoc) {
+	documentation_tree = ast_xml_open(path);
+
+	if (!documentation_tree) {
 		ast_log(LOG_ERROR, "Could not open XML Doc at '%s'\n", path);
-		/* try to load default language */
-		documentation_tree = NULL;
+		if (!strcmp(documentation_language, "en_US")) {
+			ast_xml_finish();
+			return 1;
+		}
+		path = alloca(strlen(ast_config_AST_DATA_DIR) + strlen("en_US") + strlen("/core-.xml") + 1);
+		sprintf(path, "%s/core-%s.xml", ast_config_AST_DATA_DIR, "en_US");
+		ast_log(LOG_WARNING, "Trying to load documentation XML in '%s'\n", path);
+		if (!(documentation_tree = ast_xml_open(path))) {
+			ast_log(LOG_WARNING, "Also failed trying to load default documentation\n");
+			ast_xml_finish();
+			return 1;
+		}
+		
+	}
+
+	/* Get doc root node and check if it starts with '<doc>' */
+	root_node = ast_xml_get_root(documentation_tree);
+	if (!root_node) {
+		ast_log(LOG_ERROR, "Error getting documentation root node");
+		ast_xml_close(documentation_tree);
+		ast_xml_finish();
 		return 1;
 	}
-
-	documentation_tree = mxmlLoadFile(NULL, xmldoc, MXML_OPAQUE_CALLBACK);
-
-	if (!documentation_tree) {
-		ast_log(LOG_ERROR, "Parsing Problem with Documentation Tree\n");
-		documentation_tree = NULL;
+		
+	if (strcmp((char *)root_node->AST_XML_NAME, "docs")) {
+		ast_log(LOG_ERROR, "Documentation file is not well formed!\n");
+		ast_xml_close(documentation_tree);
+		ast_xml_finish();
 		return 1;
 	}
-
-	fclose(xmldoc);
 
 	return 0;
 }
@@ -4884,7 +4930,7 @@
 				/* XXX Figure out how much space we REALLY need */
 				syntax_size = (4096 + 23); 
 				syntax = alloca(syntax_size);
-				syn_handle = ast_mxml_build_syntax("application", aa->name);
+				syn_handle = NULL; //ast_mxml_build_syntax("application", aa->name);
 
 				if (synopsis && description) {
 					snprintf(info, 64 + AST_MAX_APP, "\n  -= Syntax representation of App: '%s' =- \n\n", aa->name);

Added: team/group/appdocsxml/main/xml.c
URL: http://svn.digium.com/view/asterisk/team/group/appdocsxml/main/xml.c?view=auto&rev=135436
==============================================================================
--- team/group/appdocsxml/main/xml.c (added)
+++ team/group/appdocsxml/main/xml.c Sun Aug  3 18:33:10 2008
@@ -1,0 +1,171 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008, Eliel C. Sardanons <eliels at gmail.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief XML abstraction layer
+ *
+ * \author Eliel C. Sardanons <eliels at gmail.com>
+ */
+
+#include "asterisk.h"
+#include "asterisk/xml.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#ifdef HAVE_LIBXML2
+/* libxml2 ast_xml implementation. */
+
+
+int ast_xml_init(void)
+{
+	LIBXML_TEST_VERSION
+
+	return 0;
+}
+
+int ast_xml_finish(void)
+{
+	xmlCleanupParser();
+
+	return 0;
+}
+
+ast_xml_doc *ast_xml_open(char *filename)
+{
+	xmlDoc *doc;
+
+	if (!filename) {
+		return NULL;
+	}
+
+	doc = xmlReadFile(filename, NULL, XML_PARSE_RECOVER);
+
+	return doc;
+}
+
+
+void ast_xml_close(ast_xml_doc *doc)
+{
+	if (!doc) {
+		return;
+	}
+
+	xmlFreeDoc(doc);
+	doc = NULL;
+}
+
+
+ast_xml_node *ast_xml_get_root(ast_xml_doc *doc)
+{
+	xmlNode *root_node;
+
+	if (!doc) {
+		return NULL;
+	}
+
+	root_node = xmlDocGetRootElement(doc);
+
+	return root_node;
+}
+
+void ast_xml_free_node(ast_xml_node *node)
+{
+	if (!node) {
+		return;
+	}
+
+	xmlFreeNode(node);
+	node = NULL;
+}
+
+void ast_xml_free_attr(ast_xml_attr *attribute)
+{
+	if (attribute) {
+		free(attribute);
+	}
+}
+
+void ast_xml_free_text(ast_xml_text *text)
+{
+	if (text) {
+		free(text);
+	}
+}
+
+ast_xml_attr *ast_xml_get_attribute(ast_xml_node *node, const char *attrname)
+{
+	xmlChar *attrvalue;
+
+	if (!node) {
+		return NULL;
+	}
+
+	if (!attrname) {
+		return NULL;
+	}
+
+	attrvalue = xmlGetProp(node, (xmlChar *)attrname);
+
+	return (ast_xml_attr *)attrvalue;
+}
+
+ast_xml_node *ast_xml_find_element(ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue)
+{
+	xmlNodePtr cur;
+	ast_xml_attr *attr;
+
+	if (!root_node) {
+		return NULL;
+	}
+
+	cur = root_node;
+
+	while (cur) {
+		/* Check if the name matchs */
+		if (!strcmp((char *)cur->name, name)) {
+			/* We need to check for a specific attribute name? */
+			if (attrname && attrvalue) {
+				/* Get the attribute, we need to compare it. */
+				if ((attr = ast_xml_get_attribute(cur, attrname))) {
+					/* does attribute name/value matches? */
+					if (!strcmp(attr, attrvalue)) {
+						ast_xml_free_attr(attr);
+						return cur;
+					}
+					ast_xml_free_attr(attr);
+				}
+			} else {
+				return cur;
+			}
+		}
+		cur = cur->next;
+	}
+
+	return NULL;
+}
+
+char *ast_xml_get_text(ast_xml_doc *xmldoc, ast_xml_node *node)
+{
+	if (!node || !node->children || !xmldoc) {
+		return NULL;
+	}
+
+	return (char *)xmlNodeListGetString(xmldoc, node->children, 0);
+}
+
+#endif /* HAVE_LIBXML2 */
+

Propchange: team/group/appdocsxml/main/xml.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/group/appdocsxml/main/xml.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/group/appdocsxml/main/xml.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the svn-commits mailing list