From 23adb8e5096fbeea54805da2b4d0e09070af8375 Mon Sep 17 00:00:00 2001 From: "Eliel C. Sardanons" Date: Mon, 10 Nov 2008 13:53:23 +0000 Subject: Move all the XML documentation API from pbx.c to xmldoc.c. Export the XML documentation API: ast_xmldoc_build_synopsis() ast_xmldoc_build_syntax() ast_xmldoc_build_description() ast_xmldoc_build_seealso() ast_xmldoc_build_arguments() ast_xmldoc_printable() ast_xmldoc_load_documentation() git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@155711 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- main/xmldoc.c | 1621 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1621 insertions(+) create mode 100644 main/xmldoc.c (limited to 'main/xmldoc.c') diff --git a/main/xmldoc.c b/main/xmldoc.c new file mode 100644 index 000000000..c24a6b7a5 --- /dev/null +++ b/main/xmldoc.c @@ -0,0 +1,1621 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2008, Eliel C. Sardanons (LU1ALY) + * + * 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 Documentation API + * + * \author Eliel C. Sardanons (LU1ALY) + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" +#include "asterisk/paths.h" +#include "asterisk/linkedlists.h" +#include "asterisk/strings.h" +#include "asterisk/config.h" +#include "asterisk/term.h" +#include "asterisk/xmldoc.h" + +#ifdef AST_XML_DOCS + +/*! \brief Default documentation language. */ +static const char default_documentation_language[] = "en_US"; + +/*! \brief Number of columns to print when showing the XML documentation with a + * 'core show application/function *' CLI command. Used in text wrapping.*/ +static const int xmldoc_text_columns = 74; + +/*! \brief This is a value that we will use to let the wrapping mechanism move the cursor + * backward and forward xmldoc_max_diff positions before cutting the middle of a + * word, trying to find a space or a \n. */ +static const int xmldoc_max_diff = 5; + +/*! \brief XML documentation language. */ +static char documentation_language[6]; + +/*! \brief XML documentation tree */ +struct documentation_tree { + char *filename; /*!< XML document filename. */ + struct ast_xml_doc *doc; /*!< Open document pointer. */ + AST_RWLIST_ENTRY(documentation_tree) entry; +}; + +/*! + * \brief Container of documentation trees + * + * \note A RWLIST is a sufficient container type to use here for now. + * However, some changes will need to be made to implement ref counting + * if reload support is added in the future. + */ +static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree); + +static const struct strcolorized_tags { + const char *init; /*!< Replace initial tag with this string. */ + const char *end; /*!< Replace end tag with this string. */ + const int colorfg; /*!< Foreground color. */ + const char *inittag; /*!< Initial tag description. */ + const char *endtag; /*!< Ending tag description. */ +} colorized_tags[] = { + { "<", ">", COLOR_GREEN, "", "" }, + { "\'", "\'", COLOR_BLUE, "", "" }, + { "*", "*", COLOR_RED, "", "" }, + { "\"", "\"", COLOR_YELLOW, "", "" }, + { "\"", "\"", COLOR_CYAN, "", "" }, + { "${", "}", COLOR_GREEN, "", "" }, + { "", "", COLOR_BLUE, "", "" }, + { "", "", COLOR_BLUE, "", "" }, + { "\'", "\'", COLOR_GRAY, "", "" }, + + /* Special tags */ + { "", "", COLOR_YELLOW, "", "" }, + { "", "", COLOR_RED, "", "" } +}; + +static const struct strspecial_tags { + const char *tagname; /*!< Special tag name. */ + const char *init; /*!< Print this at the beginning. */ + const char *end; /*!< Print this at the end. */ +} special_tags[] = { + { "note", "NOTE: ", "" }, + { "warning", "WARNING!!!: ", "" } +}; + +/*! \internal + * \brief Calculate the space in bytes used by a format string + * that will be passed to a sprintf function. + * \param postbr The format string to use to calculate the length. + * \retval The postbr length. + */ +static int xmldoc_postbrlen(const char *postbr) +{ + int postbrreallen = 0, i; + size_t postbrlen; + + if (!postbr) { + return 0; + } + postbrlen = strlen(postbr); + for (i = 0; i < postbrlen; i++) { + if (postbr[i] == '\t') { + postbrreallen += 8 - (postbrreallen % 8); + } else { + postbrreallen++; + } + } + return postbrreallen; +} + +/*! \internal + * \brief Setup postbr to be used while wrapping the text. + * Add to postbr array all the spaces and tabs at the beginning of text. + * \param postbr output array. + * \param len text array length. + * \param text Text with format string before the actual string. + */ +static void xmldoc_setpostbr(char *postbr, size_t len, const char *text) +{ + int c, postbrlen = 0; + + if (!text) { + return; + } + + for (c = 0; c < len; c++) { + if (text[c] == '\t' || text[c] == ' ') { + postbr[postbrlen++] = text[c]; + } else { + break; + } + } + postbr[postbrlen] = '\0'; +} + +/*! \internal + * \brief Try to find a space or a break in text starting at currentpost + * and moving at most maxdiff positions. + * Helper for xmldoc_string_wrap(). + * \param text Input string where it will search. + * \param currentpos Current position within text. + * \param maxdiff Not move more than maxdiff inside text. + * \retval 1 if a space or break is found inside text while moving. + * \retval 0 if no space or break is found. + */ +static int xmldoc_wait_nextspace(const char *text, int currentpos, int maxdiff) +{ + int i, textlen; + + if (!text) { + return 0; + } + + textlen = strlen(text); + for (i = currentpos; i < textlen; i++) { + if (text[i] == ESC) { + /* Move to the end of the escape sequence */ + while (i < textlen && text[i] != 'm') { + i++; + } + } else if (text[i] == ' ' || text[i] == '\n') { + /* Found the next space or linefeed */ + return 1; + } else if (i - currentpos > maxdiff) { + /* We have looked the max distance and didn't find it */ + return 0; + } + } + + /* Reached the end and did not find it */ + + return 0; +} + +/*! \internal + * \brief Helper function for xmldoc_string_wrap(). + * Try to found a space or a break inside text moving backward + * not more than maxdiff positions. + * \param text The input string where to search for a space. + * \param currentpos The current cursor position. + * \param maxdiff The max number of positions to move within text. + * \retval 0 If no space is found (Notice that text[currentpos] is not a space or a break) + * \retval > 0 If a space or a break is found, and the result is the position relative to + * currentpos. + */ +static int xmldoc_foundspace_backward(const char *text, int currentpos, int maxdiff) +{ + int i; + + for (i = currentpos; i > 0; i--) { + if (text[i] == ' ' || text[i] == '\n') { + return (currentpos - i); + } else if (text[i] == 'm' && (text[i - 1] >= '0' || text[i - 1] <= '9')) { + /* give up, we found the end of a possible ESC sequence. */ + return 0; + } else if (currentpos - i > maxdiff) { + /* give up, we can't move anymore. */ + return 0; + } + } + + /* we found the beginning of the text */ + + return 0; +} + +/*! \internal + * \brief Justify a text to a number of columns. + * \param text Input text to be justified. + * \param columns Number of columns to preserve in the text. + * \param maxdiff Try to not cut a word when goinf down. + * \retval NULL on error. + * \retval The wrapped text. + */ +static char *xmldoc_string_wrap(const char *text, int columns, int maxdiff) +{ + struct ast_str *tmp; + char *ret, postbr[160]; + int count = 1, i, backspace, needtobreak = 0, colmax, textlen; + + /* sanity check */ + if (!text || columns <= 0 || maxdiff < 0) { + ast_log(LOG_WARNING, "Passing wrong arguments while trying to wrap the text\n"); + return NULL; + } + + tmp = ast_str_create(strlen(text) * 3); + + if (!tmp) { + return NULL; + } + + /* Check for blanks and tabs and put them in postbr. */ + xmldoc_setpostbr(postbr, sizeof(postbr), text); + colmax = columns - xmldoc_postbrlen(postbr); + + textlen = strlen(text); + for (i = 0; i < textlen; i++) { + if (needtobreak || !(count % colmax)) { + if (text[i] == ' ') { + ast_str_append(&tmp, 0, "\n%s", postbr); + needtobreak = 0; + count = 1; + } else if (text[i] != '\n') { + needtobreak = 1; + if (xmldoc_wait_nextspace(text, i, maxdiff)) { + /* wait for the next space */ + ast_str_append(&tmp, 0, "%c", text[i]); + continue; + } + /* Try to look backwards */ + backspace = xmldoc_foundspace_backward(text, i, maxdiff); + if (backspace) { + needtobreak = 1; + tmp->used -= backspace; + tmp->str[tmp->used] = '\0'; + i -= backspace + 1; + continue; + } + ast_str_append(&tmp, 0, "\n%s", postbr); + needtobreak = 0; + count = 1; + } + /* skip blanks after a \n */ + while (text[i] == ' ') { + i++; + } + } + if (text[i] == '\n') { + xmldoc_setpostbr(postbr, sizeof(postbr), &text[i] + 1); + colmax = columns - xmldoc_postbrlen(postbr); + needtobreak = 0; + count = 1; + } + if (text[i] == ESC) { + /* Ignore Escape sequences. */ + do { + ast_str_append(&tmp, 0, "%c", text[i]); + i++; + } while (i < textlen && text[i] != 'm'); + } else { + count++; + } + ast_str_append(&tmp, 0, "%c", text[i]); + } + + ret = ast_strdup(tmp->str); + ast_free(tmp); + + return ret; +} + +char *ast_xmldoc_printable(const char *bwinput, int withcolors) +{ + struct ast_str *colorized; + char *wrapped = NULL; + int i, c, len, colorsection; + char *tmp; + size_t bwinputlen; + static const int base_fg = COLOR_CYAN; + + if (!bwinput) { + return NULL; + } + + bwinputlen = strlen(bwinput); + + if (!(colorized = ast_str_create(256))) { + return NULL; + } + + if (withcolors) { + ast_term_color_code(&colorized, base_fg, 0); + if (!colorized) { + return NULL; + } + } + + for (i = 0; i < bwinputlen; i++) { + colorsection = 0; + /* Check if we are at the beginning of a tag to be colorized. */ + for (c = 0; c < ARRAY_LEN(colorized_tags); c++) { + if (strncasecmp(bwinput + i, colorized_tags[c].inittag, strlen(colorized_tags[c].inittag))) { + continue; + } + + if (!(tmp = strcasestr(bwinput + i + strlen(colorized_tags[c].inittag), colorized_tags[c].endtag))) { + continue; + } + + len = tmp - (bwinput + i + strlen(colorized_tags[c].inittag)); + + /* Setup color */ + if (withcolors) { + ast_term_color_code(&colorized, colorized_tags[c].colorfg, 0); + if (!colorized) { + return NULL; + } + } + + /* copy initial string replace */ + ast_str_append(&colorized, 0, "%s", colorized_tags[c].init); + if (!colorized) { + return NULL; + } + { + char buf[len + 1]; + ast_copy_string(buf, bwinput + i + strlen(colorized_tags[c].inittag), sizeof(buf)); + ast_str_append(&colorized, 0, "%s", buf); + } + if (!colorized) { + return NULL; + } + + /* copy the ending string replace */ + ast_str_append(&colorized, 0, "%s", colorized_tags[c].end); + if (!colorized) { + return NULL; + } + + /* Continue with the last color. */ + if (withcolors) { + ast_term_color_code(&colorized, base_fg, 0); + if (!colorized) { + return NULL; + } + } + + i += len + strlen(colorized_tags[c].endtag) + strlen(colorized_tags[c].inittag) - 1; + colorsection = 1; + break; + } + + if (!colorsection) { + ast_str_append(&colorized, 0, "%c", bwinput[i]); + if (!colorized) { + return NULL; + } + } + } + + if (withcolors) { + ast_term_color_code(&colorized, COLOR_BRWHITE, 0); + if (!colorized) { + return NULL; + } + } + + /* Wrap the text, notice that string wrap will avoid cutting an ESC sequence. */ + wrapped = xmldoc_string_wrap(colorized->str, xmldoc_text_columns, xmldoc_max_diff); + + ast_free(colorized); + + return wrapped; +} + +/*! \internal + * \brief Cleanup spaces and tabs after a \n + * \param text String to be cleaned up. + * \param output buffer (not already allocated). + * \param lastspaces Remove last spaces in the string. + */ +static void xmldoc_string_cleanup(const char *text, struct ast_str **output, int lastspaces) +{ + int i; + size_t textlen; + + if (!text) { + *output = NULL; + return; + } + + textlen = strlen(text); + + *output = ast_str_create(textlen); + if (!(*output)) { + ast_log(LOG_ERROR, "Problem allocating output buffer\n"); + return; + } + + for (i = 0; i < textlen; i++) { + if (text[i] == '\n' || text[i] == '\r') { + /* remove spaces/tabs/\n after a \n. */ + while (text[i + 1] == '\t' || text[i + 1] == '\r' || text[i + 1] == '\n') { + i++; + } + ast_str_append(output, 0, " "); + continue; + } else { + ast_str_append(output, 0, "%c", text[i]); + } + } + + /* remove last spaces (we dont want always to remove the trailing spaces). */ + if (lastspaces) { + ast_str_trim_blanks(*output); + } +} + +/*! \internal + * \brief Get the application/function node for 'name' application/function with language 'language' + * if we don't find any, get the first application with 'name' no matter which language with. + * \param type 'application', 'function', ... + * \param name Application or Function name. + * \param language Try to get this language (if not found try with en_US) + * \retval NULL on error. + * \retval A node of type ast_xml_node. + */ +static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *language) +{ + struct ast_xml_node *node = NULL; + struct documentation_tree *doctree; + const char *lang; + + AST_RWLIST_RDLOCK(&xmldoc_tree); + AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { + /* the core xml documents have priority over thirdparty document. */ + node = ast_xml_get_root(doctree->doc); + while ((node = ast_xml_find_element(node, type, "name", name))) { + /* Check language */ + lang = ast_xml_get_attribute(node, "language"); + if (lang && !strcmp(lang, language)) { + ast_xml_free_attr(lang); + break; + } else if (lang) { + ast_xml_free_attr(lang); + } + } + + if (node && ast_xml_node_get_children(node)) { + break; + } + + /* We didn't find the application documentation for the specified language, + so, try to load documentation for any language */ + node = ast_xml_get_root(doctree->doc); + if (ast_xml_node_get_children(node)) { + if ((node = ast_xml_find_element(ast_xml_node_get_children(node), type, "name", name))) { + break; + } + } + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + return node; +} + +/*! \internal + * \brief Helper function used to build the syntax, it allocates the needed buffer (or reallocates it), + * and based on the reverse value it makes use of fmt to print the parameter list inside the + * realloced buffer (syntax). + * \param reverse We are going backwards while generating the syntax? + * \param len Current length of 'syntax' buffer. + * \param syntax Output buffer for the concatenated values. + * \param fmt A format string that will be used in a sprintf call. + */ +static __attribute__((format(printf,4,5))) void xmldoc_reverse_helper(int reverse, int *len, char **syntax, const char *fmt, ...) +{ + int totlen, tmpfmtlen; + char *tmpfmt, tmp; + va_list ap; + + va_start(ap, fmt); + if (ast_vasprintf(&tmpfmt, fmt, ap) < 0) { + va_end(ap); + return; + } + va_end(ap); + + tmpfmtlen = strlen(tmpfmt); + totlen = *len + tmpfmtlen + 1; + + *syntax = ast_realloc(*syntax, totlen); + + if (!*syntax) { + ast_free(tmpfmt); + return; + } + + if (reverse) { + memmove(*syntax + tmpfmtlen, *syntax, *len); + /* Save this char, it will be overwritten by the \0 of strcpy. */ + tmp = (*syntax)[0]; + strcpy(*syntax, tmpfmt); + /* Restore the already saved char. */ + (*syntax)[tmpfmtlen] = tmp; + (*syntax)[totlen - 1] = '\0'; + } else { + strcpy(*syntax + *len, tmpfmt); + } + + *len = totlen - 1; + ast_free(tmpfmt); +} + +/*! \internal + * \brief Check if the passed node has tags inside it. + * \param node Root node to search argument elements. + * \retval 1 If a element is found inside 'node'. + * \retval 0 If no is found inside 'node'. + */ +static int xmldoc_has_arguments(struct ast_xml_node *fixnode) +{ + struct ast_xml_node *node = fixnode; + + for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { + return 1; + } + } + return 0; +} + +/*! \internal + * \brief Build the syntax for a specified starting node. + * \param rootnode A pointer to the ast_xml root node. + * \param rootname Name of the application, function, option, etc. to build the syntax. + * \param childname The name of each parameter node. + * \param printparenthesis Boolean if we must print parenthesis if not parameters are found in the rootnode. + * \param printrootname Boolean if we must print the rootname before the syntax and parenthesis at the begining/end. + * \retval NULL on error. + * \retval An ast_malloc'ed string with the syntax generated. + */ +static char *xmldoc_get_syntax(struct ast_xml_node *rootnode, const char *rootname, const char *childname, int printparenthesis, int printrootname) +{ +#define GOTONEXT(__rev, __a) (__rev ? ast_xml_node_get_prev(__a) : ast_xml_node_get_next(__a)) +#define ISLAST(__rev, __a) (__rev == 1 ? (ast_xml_node_get_prev(__a) ? 0 : 1) : (ast_xml_node_get_next(__a) ? 0 : 1)) +#define MP(__a) ((multiple ? __a : "")) + struct ast_xml_node *node = NULL, *firstparam = NULL, *lastparam = NULL; + const char *paramtype, *multipletype, *paramname, *attrargsep, *parenthesis, *argname; + int reverse, required, paramcount = 0, openbrackets = 0, len = 0, hasparams=0; + int reqfinode = 0, reqlanode = 0, optmidnode = 0, prnparenthesis; + char *syntax = NULL, *argsep; + int paramnamemalloc, multiple; + + if (ast_strlen_zero(rootname) || ast_strlen_zero(childname)) { + ast_log(LOG_WARNING, "Tried to look in XML tree with faulty rootname or childname while creating a syntax.\n"); + return NULL; + } + + if (!rootnode || !ast_xml_node_get_children(rootnode)) { + /* If the rootnode field is not found, at least print name. */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + + /* Get the argument separator from the root node attribute name 'argsep', if not found + defaults to ','. */ + attrargsep = ast_xml_get_attribute(rootnode, "argsep"); + if (attrargsep) { + argsep = ast_strdupa(attrargsep); + ast_xml_free_attr(attrargsep); + } else { + argsep = ast_strdupa(","); + } + + /* Get order of evaluation. */ + for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + required = 0; + hasparams = 1; + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (ast_true(paramtype)) { + required = 1; + } + ast_xml_free_attr(paramtype); + } + + lastparam = node; + reqlanode = required; + + if (!firstparam) { + /* first parameter node */ + firstparam = node; + reqfinode = required; + } + } + + if (!hasparams) { + /* This application, function, option, etc, doesn't have any params. */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + + if (reqfinode && reqlanode) { + /* check midnode */ + for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + if (node != firstparam && node != lastparam) { + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (!ast_true(paramtype)) { + optmidnode = 1; + break; + } + ast_xml_free_attr(paramtype); + } + } + } + } + + if ((!reqfinode && reqlanode) || (reqfinode && reqlanode && optmidnode)) { + reverse = 1; + node = lastparam; + } else { + reverse = 0; + node = firstparam; + } + + /* init syntax string. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, + (printrootname ? (printrootname == 2 ? ")]" : ")"): "")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), + (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); + } + + for (; node; node = GOTONEXT(reverse, node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + + /* Get the argument name, if it is not the leaf, go inside that parameter. */ + if (xmldoc_has_arguments(node)) { + parenthesis = ast_xml_get_attribute(node, "hasparams"); + prnparenthesis = 0; + if (parenthesis) { + prnparenthesis = ast_true(parenthesis); + if (!strcasecmp(parenthesis, "optional")) { + prnparenthesis = 2; + } + ast_xml_free_attr(parenthesis); + } + argname = ast_xml_get_attribute(node, "name"); + if (argname) { + paramname = xmldoc_get_syntax(node, argname, "argument", prnparenthesis, prnparenthesis); + ast_xml_free_attr(argname); + paramnamemalloc = 1; + } else { + /* Malformed XML, print **UNKOWN** */ + paramname = ast_strdup("**unknown**"); + } + paramnamemalloc = 1; + } else { + paramnamemalloc = 0; + paramname = ast_xml_get_attribute(node, "name"); + if (!paramname) { + ast_log(LOG_WARNING, "Malformed XML %s: no %s name\n", rootname, childname); + if (syntax) { + /* Free already allocated syntax */ + ast_free(syntax); + } + /* to give up is ok? */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + } + + /* Defaults to 'false'. */ + multiple = 0; + if ((multipletype = ast_xml_get_attribute(node, "multiple"))) { + if (ast_true(multipletype)) { + multiple = 1; + } + ast_xml_free_attr(multipletype); + } + + required = 0; /* Defaults to 'false'. */ + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (ast_true(paramtype)) { + required = 1; + } + ast_xml_free_attr(paramtype); + } + + /* build syntax core. */ + + if (required) { + /* First parameter */ + if (!paramcount) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s", paramname, MP("["), MP(argsep), MP("...]")); + } else { + /* Time to close open brackets. */ + while (openbrackets > 0) { + xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); + openbrackets--; + } + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", paramname, argsep); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", argsep, paramname); + } + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s", MP("["), MP(argsep), MP("...]")); + } + } else { + /* First parameter */ + if (!paramcount) { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]", paramname, MP("["), MP(argsep), MP("...]")); + } else { + if (ISLAST(reverse, node)) { + /* This is the last parameter. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]%s", paramname, + MP("["), MP(argsep), MP("...]"), argsep); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s[%s%s%s%s]", argsep, paramname, + MP("["), MP(argsep), MP("...]")); + } + } else { + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s%s]", paramname, argsep, + MP("["), MP(argsep), MP("...]")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s%s", argsep, paramname, + MP("["), MP(argsep), MP("...]")); + } + openbrackets++; + } + } + } + if (paramnamemalloc) { + ast_free((char *) paramname); + } else { + ast_xml_free_attr(paramname); + } + + paramcount++; + } + + /* Time to close open brackets. */ + while (openbrackets > 0) { + xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); + openbrackets--; + } + + /* close syntax string. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), + (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")") : "")); + } + + return syntax; +#undef ISLAST +#undef GOTONEXT +#undef MP +} + +char *ast_xmldoc_build_syntax(const char *type, const char *name) +{ + struct ast_xml_node *node; + char *syntax = NULL; + + node = xmldoc_get_node(type, name, documentation_language); + if (!node) { + return NULL; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) { + break; + } + } + + if (node) { + syntax = xmldoc_get_syntax(node, name, "parameter", 1, 1); + } + return syntax; +} + +/*! \internal + * \brief Parse a element. + * \param node The element pointer. + * \param tabs Added this string before the content of the element. + * \param posttabs Added this string after the content of the element. + * \param buffer This must be an already allocated ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 1 If 'node' is a named 'para'. + * \retval 2 If data is appended in buffer. + * \retval 0 on error. + */ +static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer) +{ + const char *tmptext; + struct ast_xml_node *tmp; + int ret = 0; + struct ast_str *tmpstr; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + if (strcasecmp(ast_xml_node_get_name(node), "para")) { + return ret; + } + + ast_str_append(buffer, 0, "%s", tabs); + + ret = 1; + + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* Get the text inside the element and append it to buffer. */ + tmptext = ast_xml_get_text(tmp); + if (tmptext) { + /* Strip \n etc. */ + xmldoc_string_cleanup(tmptext, &tmpstr, 0); + ast_xml_free_text(tmptext); + if (tmpstr) { + if (strcasecmp(ast_xml_node_get_name(tmp), "text")) { + ast_str_append(buffer, 0, "<%s>%s", ast_xml_node_get_name(tmp), + tmpstr->str, ast_xml_node_get_name(tmp)); + } else { + ast_str_append(buffer, 0, "%s", tmpstr->str); + } + ast_free(tmpstr); + ret = 2; + } + } + } + + ast_str_append(buffer, 0, "%s", posttabs); + + return ret; +} + +/*! \internal + * \brief Parse special elements defined in 'struct special_tags' special elements must have a element inside them. + * \param fixnode special tag node pointer. + * \param tabs put tabs before printing the node content. + * \param posttabs put posttabs after printing node content. + * \param buffer Output buffer, the special tags will be appended here. + * \retval 0 if no special element is parsed. + * \retval 1 if a special element is parsed (data is appended to buffer). + * \retval 2 if a special element is parsed and also a element is parsed inside the specialtag. + */ +static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + int ret = 0, i, count = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + for (i = 0; i < ARRAY_LEN(special_tags); i++) { + if (strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) { + continue; + } + + ret = 1; + /* This is a special tag. */ + + /* concat data */ + if (!ast_strlen_zero(special_tags[i].init)) { + ast_str_append(buffer, 0, "%s%s", tabs, special_tags[i].init); + } + + /* parse elements inside special tags. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + /* first just print it without tabs at the begining. */ + if (xmldoc_parse_para(node, (!count ? "" : tabs), posttabs, buffer) == 2) { + ret = 2; + } + } + + if (!ast_strlen_zero(special_tags[i].end)) { + ast_str_append(buffer, 0, "%s%s", special_tags[i].end, posttabs); + } + + break; + } + + return ret; +} + +/*! \internal + * \brief Parse an element from the xml documentation. + * \param fixnode Pointer to the 'argument' xml node. + * \param insideparameter If we are parsing an inside a . + * \param paramtabs pre tabs if we are inside a parameter element. + * \param tabs What to be printed before the argument name. + * \param buffer Output buffer to put values found inside the element. + * \retval 1 If there is content inside the argument. + * \retval 0 If the argument element is not parsed, or there is no content inside it. + */ +static int xmldoc_parse_argument(struct ast_xml_node *fixnode, int insideparameter, const char *paramtabs, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + const char *argname; + int count = 0, ret = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + /* Print the argument names */ + argname = ast_xml_get_attribute(node, "name"); + if (!argname) { + return 0; + } + ast_str_append(buffer, 0, "%s%s%s", tabs, argname, (insideparameter ? "\n" : "")); + ast_xml_free_attr(argname); + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (xmldoc_parse_para(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { + count++; + ret = 1; + } else if (xmldoc_parse_specialtags(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { + count++; + ret = 1; + } + } + + return ret; +} + +/*! \internal + * \brief Parse a node inside a node. + * \param node The variable node to parse. + * \param tabs A string to be appended at the begining of the output that will be stored + * in buffer. + * \param buffer This must be an already created ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 0 if no data is appended. + * \retval 1 if data is appended. + */ +static int xmldoc_parse_variable(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *tmp; + const char *valname; + const char *tmptext; + struct ast_str *cleanstr; + int ret = 0, printedpara=0; + + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + if (xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer)) { + printedpara = 1; + continue; + } else if (xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer)) { + printedpara = 1; + continue; + } + + if (strcasecmp(ast_xml_node_get_name(tmp), "value")) { + continue; + } + + /* Parse a tag only. */ + if (!printedpara) { + ast_str_append(buffer, 0, "\n"); + printedpara = 1; + } + /* Parse each desciption */ + valname = ast_xml_get_attribute(tmp, "name"); + if (valname) { + ret = 1; + ast_str_append(buffer, 0, "%s%s", tabs, valname); + ast_xml_free_attr(valname); + } + tmptext = ast_xml_get_text(tmp); + /* Check inside this node for any explanation about its meaning. */ + if (tmptext) { + /* Cleanup text. */ + xmldoc_string_cleanup(tmptext, &cleanstr, 1); + ast_xml_free_text(tmptext); + if (cleanstr && cleanstr->used > 0) { + ast_str_append(buffer, 0, ":%s", cleanstr->str); + } + ast_free(cleanstr); + } + ast_str_append(buffer, 0, "\n"); + } + + return ret; +} + +/*! \internal + * \brief Parse a node and put all the output inside 'buffer'. + * \param node The variablelist node pointer. + * \param tabs A string to be appended at the begining of the output that will be stored + * in buffer. + * \param buffer This must be an already created ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 1 If a element is parsed. + * \retval 0 On error. + */ +static int xmldoc_parse_variablelist(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *tmp; + const char *varname; + char *vartabs; + int ret = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + if (strcasecmp(ast_xml_node_get_name(node), "variablelist")) { + return ret; + } + + /* use this spacing (add 4 spaces) inside a variablelist node. */ + ast_asprintf(&vartabs, "%s ", tabs); + if (!vartabs) { + return ret; + } + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* We can have a element inside the variable list */ + if ((xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer))) { + ret = 1; + continue; + } else if ((xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer))) { + ret = 1; + continue; + } + + if (!strcasecmp(ast_xml_node_get_name(tmp), "variable")) { + /* Store the variable name in buffer. */ + varname = ast_xml_get_attribute(tmp, "name"); + if (varname) { + ast_str_append(buffer, 0, "%s%s: ", tabs, varname); + ast_xml_free_attr(varname); + /* Parse the possible values. */ + xmldoc_parse_variable(tmp, vartabs, buffer); + ret = 1; + } + } + } + + ast_free(vartabs); + + return ret; +} + +char *ast_xmldoc_build_seealso(const char *type, const char *name) +{ + struct ast_str *outputstr; + char *output; + struct ast_xml_node *node; + const char *typename; + const char *content; + int first = 1; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + return NULL; + } + + /* get the application/function root node. */ + node = xmldoc_get_node(type, name, documentation_language); + if (!node || !ast_xml_node_get_children(node)) { + return NULL; + } + + /* Find the node. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "see-also")) { + break; + } + } + + if (!node || !ast_xml_node_get_children(node)) { + /* we couldnt find a node. */ + return NULL; + } + + /* prepare the output string. */ + outputstr = ast_str_create(128); + if (!outputstr) { + return NULL; + } + + /* get into the node. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), "ref")) { + continue; + } + + /* parse the node. 'type' attribute is required. */ + typename = ast_xml_get_attribute(node, "type"); + if (!typename) { + continue; + } + content = ast_xml_get_text(node); + if (!content) { + ast_xml_free_attr(typename); + continue; + } + if (!strcasecmp(typename, "application")) { + ast_str_append(&outputstr, 0, "%s%s()", (first ? "" : ", "), content); + } else if (!strcasecmp(typename, "function")) { + ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); + } else if (!strcasecmp(typename, "astcli")) { + ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); + } else { + ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); + } + first = 0; + ast_xml_free_text(content); + } + + output = ast_strdup(outputstr->str); + ast_free(outputstr); + + return output; +} + +/*! \internal + * \brief Parse a node. + * \brief fixnode An ast_xml_node pointer to the node. + * \bried buffer The output buffer. + * \retval 0 if content is not found inside the enum element (data is not appended to buffer). + * \retval 1 if content is found and data is appended to buffer. + */ +static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + int ret = 0; + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if ((xmldoc_parse_para(node, (ret ? tabs : " - "), "\n", buffer))) { + ret = 1; + } else if ((xmldoc_parse_specialtags(node, (ret ? tabs : " - "), "\n", buffer))) { + ret = 1; + } + } + return ret; +} + +/*! \internal + * \brief Parse a node. + * \param fixnode As ast_xml pointer to the node. + * \param buffer The ast_str output buffer. + * \retval 0 if no node was parsed. + * \retval 1 if a node was parsed. + */ +static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + const char *enumname; + int ret = 0; + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), "enum")) { + continue; + } + + enumname = ast_xml_get_attribute(node, "name"); + if (enumname) { + ast_str_append(buffer, 0, "%s%s", tabs, enumname); + ast_xml_free_attr(enumname); + + /* parse only enum elements inside a enumlist node. */ + if ((xmldoc_parse_enum(node, tabs, buffer))) { + ret = 1; + } else { + ast_str_append(buffer, 0, "\n"); + } + } + } + return ret; +} + +/*! \internal + * \brief Parse an