AMI: Allow for command response documentation
authorKinsey Moore <kmoore@digium.com>
Thu, 24 Jul 2014 13:00:59 +0000 (13:00 +0000)
committerKinsey Moore <kmoore@digium.com>
Thu, 24 Jul 2014 13:00:59 +0000 (13:00 +0000)
Allow for responses to AMI actions/commands to be documented properly
in XML and displayed via the CLI. Response events are documented
exactly as standard AMI events are documented.

Review: https://reviewboard.asterisk.org/r/3812/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@419342 65c4cc65-6c06-0410-ace0-fbb531ad65f3

doc/appdocsxml.dtd
include/asterisk/manager.h
include/asterisk/xmldoc.h
main/config_options.c
main/manager.c
main/manager_bridges.c
main/xmldoc.c

index a81b644..65167d5 100644 (file)
   <!ATTLIST agi language CDATA #REQUIRED>
   <!ATTLIST agi module CDATA #IMPLIED>
 
-  <!ELEMENT manager (synopsis?,syntax?,description?,see-also?)>
+  <!ELEMENT manager (synopsis?,syntax?,description?,see-also?,responses?)>
   <!ATTLIST manager name CDATA #REQUIRED>
   <!ATTLIST manager language CDATA #REQUIRED>
   <!ATTLIST manager module CDATA #IMPLIED>
 
+  <!ELEMENT responses (list-elements?,(managerEvent|xi:include))>
+
+  <!ELEMENT list-elements (managerEvent+|xi:include+)>
+
   <!ELEMENT managerEvent (managerEventInstance+)>
   <!ATTLIST managerEvent name CDATA #REQUIRED>
   <!ATTLIST managerEvent language CDATA #REQUIRED>
index 66dafb1..fad7af1 100644 (file)
@@ -148,6 +148,10 @@ struct manager_action {
                AST_STRING_FIELD(arguments);    /*!< Description of each argument. */
                AST_STRING_FIELD(seealso);      /*!< See also */
        );
+       /*! Possible list element response events. */
+       struct ast_xml_doc_item *list_responses;
+       /*! Final response event. */
+       struct ast_xml_doc_item *final_response;
        /*! Permission required for action.  EVENT_FLAG_* */
        int authority;
        /*! Function to be called */
index c09f693..782fa1e 100644 (file)
@@ -37,6 +37,13 @@ enum ast_doc_src {
 struct ao2_container;
 struct ast_xml_node;
 
+/*!
+ * \brief The struct to be used as the head of an ast_xml_doc_item list
+ *        when being manipulated
+ * \since 13.0.0
+ */
+AST_LIST_HEAD(ast_xml_doc_item_list, ast_xml_doc_item);
+
 /*! \brief Struct that contains the XML documentation for a particular item.  Note
  * that this is an ao2 ref counted object.
  *
@@ -70,7 +77,7 @@ struct ast_xml_doc_item {
         */
        struct ast_xml_node *node;
        /*! The next XML documentation item that matches the same name/item type */
-       struct ast_xml_doc_item *next;
+       AST_LIST_ENTRY(ast_xml_doc_item) next;
 };
 
 /*! \brief Execute an XPath query on the loaded XML documentation
@@ -115,6 +122,34 @@ char *ast_xmldoc_build_seealso(const char *type, const char *name, const char *m
 char *ast_xmldoc_build_arguments(const char *type, const char *name, const char *module);
 
 /*!
+ * \brief Generate the [final response] tag based on type of node ('application',
+ *        'function' or 'agi') and name.
+ *
+ * \param type 'application', 'function' or 'agi'
+ * \param name Name of the application or function to build the 'responses' tag.
+ * \param module The module the item is in (optional, can be NULL)
+ *
+ * \return An XMLDoc item list with the [final response] tag content.
+ *
+ * \since 13.0.0
+ */
+struct ast_xml_doc_item *ast_xmldoc_build_final_response(const char *type, const char *name, const char *module);
+
+/*!
+ * \brief Generate the [list responses] tag based on type of node ('application',
+ *        'function' or 'agi') and name.
+ *
+ * \param type 'application', 'function' or 'agi'
+ * \param name Name of the application or function to build the 'responses' tag.
+ * \param module The module the item is in (optional, can be NULL)
+ *
+ * \return An XMLDoc item list with the [list responses] tag content.
+ *
+ * \since 13.0.0
+ */
+struct ast_xml_doc_item *ast_xmldoc_build_list_responses(const char *type, const char *name, const char *module);
+
+/*!
  *  \brief Colorize and put delimiters (instead of tags) to the xmldoc output.
  *  \param bwinput Not colorized input with tags.
  *  \param withcolors Result output with colors.
index c86db02..89b19e0 100644 (file)
@@ -249,7 +249,7 @@ static struct ast_xml_doc_item *find_xmldoc_option(struct ast_xml_doc_item *conf
                return NULL;
        }
        /* First is just the configInfo, we can skip it */
-       while ((iter = iter->next)) {
+       while ((iter = AST_LIST_NEXT(iter, next))) {
                size_t x;
                if (strcasecmp(iter->name, name)) {
                        continue;
@@ -274,7 +274,7 @@ static struct ast_xml_doc_item *find_xmldoc_type(struct ast_xml_doc_item *config
                return NULL;
        }
        /* First is just the config Info, skip it */
-       while ((iter = iter->next)) {
+       while ((iter = AST_LIST_NEXT(iter, next))) {
                if (!strcasecmp(iter->type, "configObject") && !strcasecmp(iter->name, name)) {
                        break;
                }
@@ -915,7 +915,7 @@ static char *complete_config_type(const char *module, const char *word, int pos,
        }
 
        cur = info;
-       while ((cur = cur->next)) {
+       while ((cur = AST_LIST_NEXT(cur, next))) {
                if (!strcasecmp(cur->type, "configObject") && !strncasecmp(word, cur->name, wordlen) && ++which > state) {
                        c = ast_strdup(cur->name);
                        break;
@@ -944,7 +944,7 @@ static char *complete_config_option(const char *module, const char *option, cons
        }
 
        cur = info;
-       while ((cur = cur->next)) {
+       while ((cur = AST_LIST_NEXT(cur, next))) {
                if (!strcasecmp(cur->type, "configOption") && !strcasecmp(cur->ref, option) && !strncasecmp(word, cur->name, wordlen) && ++which > state) {
                        c = ast_strdup(cur->name);
                        break;
@@ -1109,7 +1109,7 @@ static void cli_show_module_types(struct ast_cli_args *a)
 
        tmp = item;
        ast_cli(a->fd, "Configuration option types for %s:\n", tmp->name);
-       while ((tmp = tmp->next)) {
+       while ((tmp = AST_LIST_NEXT(tmp, next))) {
                if (!strcasecmp(tmp->type, "configObject")) {
                        ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name,
                                ast_str_buffer(tmp->synopsis));
@@ -1135,7 +1135,7 @@ static void cli_show_module_type(struct ast_cli_args *a)
        }
 
        tmp = item;
-       while ((tmp = tmp->next)) {
+       while ((tmp = AST_LIST_NEXT(tmp, next))) {
                if (!strcasecmp(tmp->type, "configObject") && !strcasecmp(tmp->name, a->argv[4])) {
                        match = 1;
                        term_color(option_type, tmp->name, COLOR_MAGENTA, COLOR_BLACK, sizeof(option_type));
@@ -1161,7 +1161,7 @@ static void cli_show_module_type(struct ast_cli_args *a)
 
        /* Now iterate over the options for the type */
        tmp = item;
-       while ((tmp = tmp->next)) {
+       while ((tmp = AST_LIST_NEXT(tmp, next))) {
                if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4])) {
                        ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name,
                                        ast_str_buffer(tmp->synopsis));
@@ -1186,7 +1186,7 @@ static void cli_show_module_options(struct ast_cli_args *a)
                return;
        }
        tmp = item;
-       while ((tmp = tmp->next)) {
+       while ((tmp = AST_LIST_NEXT(tmp, next))) {
                if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4]) && !strcasecmp(tmp->name, a->argv[5])) {
                        if (match) {
                                ast_cli(a->fd, "\n");
index f5d00d1..47a909b 100644 (file)
@@ -1912,6 +1912,8 @@ static int manager_displayconnects(struct mansession_session *session)
        return ret;
 }
 
+static void print_event_instance(struct ast_cli_args *a, struct ast_xml_doc_item *instance);
+
 static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct manager_action *cur;
@@ -1919,7 +1921,8 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
        int num, l, which;
        char *ret = NULL;
 #ifdef AST_XML_DOCS
-       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64], privilege_title[64];
+       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64];
+       char arguments_title[64], privilege_title[64], final_response_title[64], list_responses_title[64];
 #endif
 
        switch (cmd) {
@@ -1955,6 +1958,8 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
        term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
        term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
        term_color(privilege_title, "[Privilege]\n", COLOR_MAGENTA, 0, 40);
+       term_color(final_response_title, "[Final Response]\n", COLOR_MAGENTA, 0, 40);
+       term_color(list_responses_title, "[List Responses]\n", COLOR_MAGENTA, 0, 40);
 #endif
 
        AST_RWLIST_RDLOCK(&actions);
@@ -1971,13 +1976,34 @@ static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_
                                        char *arguments = ast_xmldoc_printable(S_OR(cur->arguments, "Not available"), 1);
                                        char *seealso = ast_xmldoc_printable(S_OR(cur->seealso, "Not available"), 1);
                                        char *privilege = ast_xmldoc_printable(S_OR(authority->str, "Not available"), 1);
-                                       ast_cli(a->fd, "%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n",
+                                       char *responses = ast_xmldoc_printable("None", 1);
+                                       ast_cli(a->fd, "%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s",
                                                syntax_title, syntax,
                                                synopsis_title, synopsis,
                                                description_title, description,
                                                arguments_title, arguments,
                                                seealso_title, seealso,
-                                               privilege_title, privilege);
+                                               privilege_title, privilege,
+                                               list_responses_title);
+
+                                       if (!cur->list_responses) {
+                                               ast_cli(a->fd, "%s\n\n", responses);
+                                       } else {
+                                               struct ast_xml_doc_item *temp;
+                                               for (temp = cur->list_responses; temp; temp = AST_LIST_NEXT(temp, next)) {
+                                                       ast_cli(a->fd, "Event: %s\n", temp->name);
+                                                       print_event_instance(a, temp);
+                                               }
+                                       }
+
+                                       ast_cli(a->fd, "%s", final_response_title);
+
+                                       if (!cur->final_response) {
+                                               ast_cli(a->fd, "%s\n\n", responses);
+                                       } else {
+                                               ast_cli(a->fd, "Event: %s\n", cur->final_response->name);
+                                               print_event_instance(a, cur->final_response);
+                                       }
                                } else
 #endif
                                {
@@ -6309,6 +6335,8 @@ static void action_destroy(void *obj)
                /* The string fields were initialized. */
                ast_string_field_free_memory(doomed);
        }
+       ao2_cleanup(doomed->final_response);
+       ao2_cleanup(doomed->list_responses);
 }
 
 /*! \brief register a new command with manager, including online help. This is
@@ -6354,6 +6382,9 @@ int ast_manager_register2(const char *action, int auth, int (*func)(struct manse
                ast_string_field_set(cur, arguments, tmpxml);
                ast_free(tmpxml);
 
+               cur->final_response = ast_xmldoc_build_final_response("manager", action, NULL);
+               cur->list_responses = ast_xmldoc_build_list_responses("manager", action, NULL);
+
                cur->docsrc = AST_XML_DOC;
        } else
 #endif
@@ -7745,6 +7776,43 @@ static char *handle_manager_show_events(struct ast_cli_entry *e, int cmd, struct
        return CLI_SUCCESS;
 }
 
+static void print_event_instance(struct ast_cli_args *a, struct ast_xml_doc_item *instance)
+{
+       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64];
+
+       term_color(synopsis_title, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
+       term_color(description_title, "[Description]\n", COLOR_MAGENTA, 0, 40);
+       term_color(syntax_title, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
+       term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
+       term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
+
+       if (!ast_strlen_zero(ast_str_buffer(instance->synopsis))) {
+               char *synopsis = ast_xmldoc_printable(ast_str_buffer(instance->synopsis), 1);
+               ast_cli(a->fd, "%s%s\n\n", synopsis_title, synopsis);
+               ast_free(synopsis);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->syntax))) {
+               char *syntax = ast_xmldoc_printable(ast_str_buffer(instance->syntax), 1);
+               ast_cli(a->fd, "%s%s\n\n", syntax_title, syntax);
+               ast_free(syntax);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->description))) {
+               char *description = ast_xmldoc_printable(ast_str_buffer(instance->description), 1);
+               ast_cli(a->fd, "%s%s\n\n", description_title, description);
+               ast_free(description);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->arguments))) {
+               char *arguments = ast_xmldoc_printable(ast_str_buffer(instance->arguments), 1);
+               ast_cli(a->fd, "%s%s\n\n", arguments_title, arguments);
+               ast_free(arguments);
+       }
+       if (!ast_strlen_zero(ast_str_buffer(instance->seealso))) {
+               char *seealso = ast_xmldoc_printable(ast_str_buffer(instance->seealso), 1);
+               ast_cli(a->fd, "%s%s\n\n", seealso_title, seealso);
+               ast_free(seealso);
+       }
+}
+
 static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        RAII_VAR(struct ao2_container *, events, NULL, ao2_cleanup);
@@ -7753,7 +7821,6 @@ static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct
        int length;
        int which;
        char *match = NULL;
-       char syntax_title[64], description_title[64], synopsis_title[64], seealso_title[64], arguments_title[64];
 
        if (cmd == CLI_INIT) {
                e->command = "manager show event";
@@ -7794,39 +7861,9 @@ static char *handle_manager_show_event(struct ast_cli_entry *e, int cmd, struct
                return CLI_SUCCESS;
        }
 
-       term_color(synopsis_title, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
-       term_color(description_title, "[Description]\n", COLOR_MAGENTA, 0, 40);
-       term_color(syntax_title, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
-       term_color(seealso_title, "[See Also]\n", COLOR_MAGENTA, 0, 40);
-       term_color(arguments_title, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
-
        ast_cli(a->fd, "Event: %s\n", a->argv[3]);
-       for (temp = item; temp; temp = temp->next) {
-               if (!ast_strlen_zero(ast_str_buffer(temp->synopsis))) {
-                       char *synopsis = ast_xmldoc_printable(ast_str_buffer(temp->synopsis), 1);
-                       ast_cli(a->fd, "%s%s\n\n", synopsis_title, synopsis);
-                       ast_free(synopsis);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->syntax))) {
-                       char *syntax = ast_xmldoc_printable(ast_str_buffer(temp->syntax), 1);
-                       ast_cli(a->fd, "%s%s\n\n", syntax_title, syntax);
-                       ast_free(syntax);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->description))) {
-                       char *description = ast_xmldoc_printable(ast_str_buffer(temp->description), 1);
-                       ast_cli(a->fd, "%s%s\n\n", description_title, description);
-                       ast_free(description);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->arguments))) {
-                       char *arguments = ast_xmldoc_printable(ast_str_buffer(temp->arguments), 1);
-                       ast_cli(a->fd, "%s%s\n\n", arguments_title, arguments);
-                       ast_free(arguments);
-               }
-               if (!ast_strlen_zero(ast_str_buffer(temp->seealso))) {
-                       char *seealso = ast_xmldoc_printable(ast_str_buffer(temp->seealso), 1);
-                       ast_cli(a->fd, "%s%s\n\n", seealso_title, seealso);
-                       ast_free(seealso);
-               }
+       for (temp = item; temp; temp = AST_LIST_NEXT(temp, next)) {
+               print_event_instance(a, temp);
        }
 
        ao2_ref(item, -1);
index 63a9272..26b0e78 100644 (file)
@@ -100,6 +100,26 @@ static struct stasis_message_router *bridge_state_router;
                <description>
                        <para>Returns detailed information about a bridge and the channels in it.</para>
                </description>
+               <responses>
+                       <list-elements>
+                               <managerEvent language="en_US" name="BridgeInfoChannel">
+                                       <managerEventInstance class="EVENT_FLAG_COMMAND">
+                                               <synopsis>Information about a channel in a bridge.</synopsis>
+                                               <syntax>
+                                                       <channel_snapshot/>
+                                               </syntax>
+                                       </managerEventInstance>
+                               </managerEvent>
+                       </list-elements>
+                       <managerEvent language="en_US" name="BridgeInfoComplete">
+                               <managerEventInstance class="EVENT_FLAG_COMMAND">
+                                       <synopsis>Information about a bridge.</synopsis>
+                                       <syntax>
+                                               <bridge_snapshot/>
+                                       </syntax>
+                               </managerEventInstance>
+                       </managerEvent>
+               </responses>
        </manager>
        <manager name="BridgeDestroy" language="en_US">
                <synopsis>
index 28bea86..a84b059 100644 (file)
@@ -2300,9 +2300,9 @@ static void ast_xml_doc_item_destructor(void *obj)
        ast_free(doc->description);
        ast_string_field_free_memory(doc);
 
-       if (doc->next) {
-               ao2_ref(doc->next, -1);
-               doc->next = NULL;
+       if (AST_LIST_NEXT(doc, next)) {
+               ao2_ref(AST_LIST_NEXT(doc, next), -1);
+               AST_LIST_NEXT(doc, next) = NULL;
        }
 }
 
@@ -2428,6 +2428,139 @@ static struct ast_xml_doc_item *xmldoc_build_documentation_item(struct ast_xml_n
        return item;
 }
 
+/*!
+ * \internal
+ * \brief Build the list responses for an item
+ *
+ * \param manager_action The action node to parse
+ *
+ * \note This method exists for when you already have the node.  This
+ * prevents having to lock the documentation tree twice
+ *
+ * \retval A list of ast_xml_doc_items
+ * \retval NULL on failure
+ *
+ * \since 13.0.0
+ */
+static struct ast_xml_doc_item *xmldoc_build_list_responses(struct ast_xml_node *manager_action)
+{
+       struct ast_xml_node *event;
+       struct ast_xml_node *responses;
+       struct ast_xml_node *list_elements;
+       struct ast_xml_doc_item_list root;
+
+       AST_LIST_HEAD_INIT(&root);
+
+       responses = ast_xml_find_element(ast_xml_node_get_children(manager_action), "responses", NULL, NULL);
+       if (!responses) {
+               return NULL;
+       }
+
+       list_elements = ast_xml_find_element(ast_xml_node_get_children(responses), "list-elements", NULL, NULL);
+       if (!list_elements) {
+               return NULL;
+       }
+
+       /* Iterate over managerEvent nodes */
+       for (event = ast_xml_node_get_children(list_elements); event; event = ast_xml_node_get_next(event)) {
+               struct ast_xml_node *event_instance;
+               const char *name = ast_xml_get_attribute(event, "name");
+               struct ast_xml_doc_item *new_item;
+
+               if (!name || strcmp(ast_xml_node_get_name(event), "managerEvent")) {
+                       continue;
+               }
+
+               event_instance = ast_xml_find_element(ast_xml_node_get_children(event),
+                       "managerEventInstance", NULL, NULL);
+               new_item = xmldoc_build_documentation_item(event_instance, name, "managerEvent");
+               if (!new_item) {
+                       ao2_cleanup(AST_LIST_FIRST(&root));
+                       return NULL;
+               }
+
+               AST_LIST_INSERT_TAIL(&root, new_item, next);
+       }
+
+       return AST_LIST_FIRST(&root);
+}
+
+struct ast_xml_doc_item *ast_xmldoc_build_list_responses(const char *type, const char *name, const char *module)
+{
+       struct ast_xml_node *node;
+
+       if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
+               return NULL;
+       }
+
+       node = xmldoc_get_node(type, name, module, documentation_language);
+
+       if (!node || !ast_xml_node_get_children(node)) {
+               return NULL;
+       }
+
+       return xmldoc_build_list_responses(node);
+}
+
+/*!
+ * \internal
+ * \brief Build the final response for an item
+ *
+ * \param manager_action The action node to parse
+ *
+ * \note This method exists for when you already have the node.  This
+ * prevents having to lock the documentation tree twice
+ *
+ * \retval An ast_xml_doc_item
+ * \retval NULL on failure
+ *
+ * \since 13.0.0
+ */
+static struct ast_xml_doc_item *xmldoc_build_final_response(struct ast_xml_node *manager_action)
+{
+       struct ast_xml_node *responses;
+       struct ast_xml_node *final_response_event;
+       struct ast_xml_node *event_instance;
+
+       responses = ast_xml_find_element(ast_xml_node_get_children(manager_action),
+               "responses", NULL, NULL);
+       if (!responses) {
+               return NULL;
+       }
+
+       final_response_event = ast_xml_find_element(ast_xml_node_get_children(responses),
+               "managerEvent", NULL, NULL);
+       if (!final_response_event) {
+               return NULL;
+       }
+
+       event_instance = ast_xml_find_element(ast_xml_node_get_children(final_response_event),
+               "managerEventInstance", NULL, NULL);
+       if (!event_instance) {
+               return NULL;
+       }
+
+       return xmldoc_build_documentation_item(event_instance,
+               ast_xml_get_attribute(final_response_event, "name"), "managerEvent");
+}
+
+struct ast_xml_doc_item *ast_xmldoc_build_final_response(const char *type, const char *name, const char *module)
+{
+       struct ast_xml_node *node;
+
+       if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
+               return NULL;
+       }
+
+       node = xmldoc_get_node(type, name, module, documentation_language);
+
+       if (!node || !ast_xml_node_get_children(node)) {
+               return NULL;
+       }
+
+       return xmldoc_build_final_response(node);
+}
+
 struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...)
 {
        struct ast_xml_xpath_results *results = NULL;
@@ -2455,7 +2588,7 @@ struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_q
        return results;
 }
 
-static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item **tail)
+static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item_list *root)
 {
        struct ast_xml_node *iter;
        struct ast_xml_doc_item *item;
@@ -2478,9 +2611,8 @@ static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item
                        ast_string_field_set(item, ref, name);
                        ast_xml_free_attr(name);
                }
-               (*tail)->next = item;
-               *tail = (*tail)->next;
-               build_config_docs(iter, tail);
+               AST_LIST_INSERT_TAIL(root, item, next);
+               build_config_docs(iter, root);
        }
 }
 
@@ -2536,7 +2668,6 @@ int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item)
 struct ao2_container *ast_xmldoc_build_documentation(const char *type)
 {
        struct ao2_container *docs;
-       struct ast_xml_doc_item *item = NULL, *root = NULL;
        struct ast_xml_node *node = NULL, *instance = NULL;
        struct documentation_tree *doctree;
        const char *name;
@@ -2555,6 +2686,8 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
                }
 
                for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
+                       struct ast_xml_doc_item *item = NULL;
+
                        /* Ignore empty nodes or nodes that aren't of the type requested */
                        if (!ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), type)) {
                                continue;
@@ -2566,6 +2699,10 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
 
                        switch (xmldoc_get_syntax_type(type)) {
                        case MANAGER_EVENT_SYNTAX:
+                       {
+                               struct ast_xml_doc_item_list root;
+
+                               AST_LIST_HEAD_INIT(&root);
                                for (instance = ast_xml_node_get_children(node); instance; instance = ast_xml_node_get_next(instance)) {
                                        struct ast_xml_doc_item *temp;
                                        if (!ast_xml_node_get_children(instance) || strcasecmp(ast_xml_node_get_name(instance), "managerEventInstance")) {
@@ -2575,28 +2712,27 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
                                        if (!temp) {
                                                break;
                                        }
-                                       if (!item) {
-                                               item = temp;
-                                               root = item;
-                                       } else {
-                                               item->next = temp;
-                                               item = temp;
-                                       }
+                                       AST_LIST_INSERT_TAIL(&root, temp, next);
                                }
-                               item = root;
+                               item = AST_LIST_FIRST(&root);
                                break;
+                       }
                        case CONFIG_INFO_SYNTAX:
                        {
-                               struct ast_xml_doc_item *tail;
                                RAII_VAR(const char *, name, ast_xml_get_attribute(node, "name"), ast_xml_free_attr);
-                               if (item || !ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), "configInfo")) {
+
+                               if (!ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), "configInfo")) {
                                        break;
                                }
-                               if (!(item = xmldoc_build_documentation_item(node, name, "configInfo"))) {
-                                       break;
+
+                               item = xmldoc_build_documentation_item(node, name, "configInfo");
+                               if (item) {
+                                       struct ast_xml_doc_item_list root;
+
+                                       AST_LIST_HEAD_INIT(&root);
+                                       AST_LIST_INSERT_TAIL(&root, item, next);
+                                       build_config_docs(node, &root);
                                }
-                               tail = item;
-                               build_config_docs(node, &tail);
                                break;
                        }
                        default:
@@ -2607,7 +2743,6 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type)
                        if (item) {
                                ao2_link(docs, item);
                                ao2_t_ref(item, -1, "Dispose of creation ref");
-                               item = NULL;
                        }
                }
        }