diff options
author | Eliel C. Sardanons <eliels@gmail.com> | 2008-11-10 13:53:23 +0000 |
---|---|---|
committer | Eliel C. Sardanons <eliels@gmail.com> | 2008-11-10 13:53:23 +0000 |
commit | 23adb8e5096fbeea54805da2b4d0e09070af8375 (patch) | |
tree | fecca24f0474fbec482f8759862bb4d28d625d2c | |
parent | 09d28140599031e67d8c563c92cfb5df93d0a60d (diff) |
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
-rw-r--r-- | include/asterisk/_private.h | 4 | ||||
-rw-r--r-- | include/asterisk/term.h | 4 | ||||
-rw-r--r-- | include/asterisk/xmldoc.h | 85 | ||||
-rw-r--r-- | main/Makefile | 2 | ||||
-rw-r--r-- | main/asterisk.c | 4 | ||||
-rw-r--r-- | main/pbx.c | 1667 | ||||
-rw-r--r-- | main/xmldoc.c | 1621 |
7 files changed, 1747 insertions, 1640 deletions
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index be4542d7a..83ae166de 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -56,10 +56,10 @@ int ast_timing_init(void); /*!< Provided by timing.c */ */ int ast_module_reload(const char *name); -/*! \brief Load XML documentation. Provided by pbx.c +/*! \brief Load XML documentation. Provided by xmldoc.c * \retval 1 on error. * \retval 0 on success. */ -int ast_load_documentation(void); +int ast_xmldoc_load_documentation(void); #endif /* _ASTERISK__PRIVATE_H */ diff --git a/include/asterisk/term.h b/include/asterisk/term.h index 8c6cab43e..d75cf99d0 100644 --- a/include/asterisk/term.h +++ b/include/asterisk/term.h @@ -62,6 +62,10 @@ extern "C" { #define COLOR_BRWHITE (37 | 128) /*@} */ +/*! \brief Maximum number of characters needed for a color escape sequence, + * plus a null char */ +#define AST_TERM_MAX_ESCAPE_CHARS 23 + char *term_color(char *outbuf, const char *inbuf, int fgcolor, int bgcolor, int maxout); /*! diff --git a/include/asterisk/xmldoc.h b/include/asterisk/xmldoc.h new file mode 100644 index 000000000..0e8275a2d --- /dev/null +++ b/include/asterisk/xmldoc.h @@ -0,0 +1,85 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2008, Eliel C. Sardanons (LU1ALY) <eliels@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_XMLDOC_H +#define _ASTERISK_XMLDOC_H + +/*! \file + * \brief Asterisk XML Documentation API + */ + +#include "asterisk/xml.h" + +#ifdef AST_XML_DOCS + +/*! + * \brief Get the syntax for a specified application or function. + * \param type Application, Function or AGI ? + * \param name Name of the application or function. + * \retval NULL on error. + * \retval The generated syntax in a ast_malloc'ed string. + */ +char *ast_xmldoc_build_syntax(const char *type, const char *name); + +/*! + * \brief Parse the <see-also> node content. + * \param type 'application', 'function' or 'agi'. + * \param name Application or functions name. + * \retval NULL on error. + * \retval Content of the see-also node. + */ +char *ast_xmldoc_build_seealso(const char *type, const char *name); + +/*! + * \brief Generate the [arguments] 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 'arguments' tag. + * \retval NULL on error. + * \retval Output buffer with the [arguments] tag content. + */ +char *ast_xmldoc_build_arguments(const char *type, const char *name); + +/*! + * \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. + * \retval NULL on error. + * \retval New malloced buffer colorized and with delimiters. + */ +char *ast_xmldoc_printable(const char *bwinput, int withcolors); + +/*! + * \brief Generate synopsis documentation from XML. + * \param type The source of documentation (application, function, etc). + * \param name The name of the application, function, etc. + * \retval NULL on error. + * \retval A malloc'ed string with the synopsis. + */ +char *ast_xmldoc_build_synopsis(const char *type, const char *name); + +/*! + * \brief Generate description documentation from XML. + * \param type The source of documentation (application, function, etc). + * \param name The name of the application, function, etc. + * \retval NULL on error. + * \retval A malloc'ed string with the formatted description. + */ +char *ast_xmldoc_build_description(const char *type, const char *name); + +#endif /* AST_XML_DOCS */ + +#endif /* _ASTERISK_XMLDOC_H */ diff --git a/main/Makefile b/main/Makefile index 3ed689991..51acf20ee 100644 --- a/main/Makefile +++ b/main/Makefile @@ -28,7 +28,7 @@ OBJS= tcptls.o io.o sched.o logger.o frame.o loader.o config.o channel.o \ 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 datastore.o xml.o + features.o taskprocessor.o timing.o datastore.o xml.o xmldoc.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 diff --git a/main/asterisk.c b/main/asterisk.c index 22454349d..27c221353 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -117,8 +117,8 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/devicestate.h" #include "asterisk/module.h" #include "asterisk/dsp.h" -#include "asterisk/xml.h" #include "asterisk/buildinfo.h" +#include "asterisk/xmldoc.h" #include "asterisk/doxyref.h" /* Doxygen documentation */ @@ -3346,7 +3346,7 @@ int main(int argc, char *argv[]) #ifdef AST_XML_DOCS /* Load XML documentation. */ - ast_load_documentation(); + ast_xmldoc_load_documentation(); #endif if (load_modules(1)) { /* Load modules, pre-load only */ diff --git a/main/pbx.c b/main/pbx.c index e120eeebc..737fe6d53 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -64,7 +64,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "asterisk/indications.h" #include "asterisk/taskprocessor.h" -#include "asterisk/xml.h" +#include "asterisk/xmldoc.h" /*! * \note I M P O R T A N T : @@ -921,42 +921,6 @@ struct pbx_exception { int priority; /*!< Priority associated with this exception */ }; -#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); -#endif - -/*! \brief Maximum number of characters needed for a color escape sequence, plus a null char */ -#define MAX_ESCAPE_CHARS 23 - static int pbx_builtin_answer(struct ast_channel *, void *); static int pbx_builtin_goto(struct ast_channel *, void *); static int pbx_builtin_hangup(struct ast_channel *, void *); @@ -1002,9 +966,6 @@ static unsigned int hashtab_hash_extens(const void *obj); static unsigned int hashtab_hash_priority(const void *obj); static unsigned int hashtab_hash_labels(const void *obj); static void __ast_internal_context_destroy( struct ast_context *con); -#ifdef AST_XML_DOCS -static char *xmldoc_colorization(const char *bwinput); -#endif /* a func for qsort to use to sort a char array */ static int compare_char(const void *a, const void *b) @@ -3124,7 +3085,7 @@ static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_c return CLI_FAILURE; } - syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + MAX_ESCAPE_CHARS; + syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; if (!(syntax = ast_malloc(syntax_size))) { ast_cli(a->fd, "Memory allocation failure!\n"); return CLI_FAILURE; @@ -3140,23 +3101,23 @@ static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_c term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); #ifdef AST_XML_DOCS if (acf->docsrc == AST_XML_DOC) { - arguments = xmldoc_colorization(S_OR(acf->arguments, "Not available")); - synopsis = xmldoc_colorization(S_OR(acf->synopsis, "Not available")); - description = xmldoc_colorization(S_OR(acf->desc, "Not available")); - seealso = xmldoc_colorization(S_OR(acf->seealso, "Not available")); - } else + arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1); + synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1); + description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1); + seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1); + } else #endif { - synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + MAX_ESCAPE_CHARS; + synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; synopsis = ast_malloc(synopsis_size); - description_size = strlen(S_OR(acf->desc, "Not Available")) + MAX_ESCAPE_CHARS; + description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; description = ast_malloc(description_size); - arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + MAX_ESCAPE_CHARS; + arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; arguments = ast_malloc(arguments_size); - seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + MAX_ESCAPE_CHARS; + seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; seealso = ast_malloc(seealso_size); /* check allocated memory. */ @@ -3221,1455 +3182,6 @@ int ast_custom_function_unregister(struct ast_custom_function *acf) return cur ? 0 : -1; } -#ifdef AST_XML_DOCS -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, "<replaceable>", "</replaceable>" }, - { "\'", "\'", COLOR_BLUE, "<literal>", "</literal>" }, - { "*", "*", COLOR_RED, "<emphasis>", "</emphasis>" }, - { "\"", "\"", COLOR_YELLOW, "<filename>", "</filename>" }, - { "\"", "\"", COLOR_CYAN, "<directory>", "</directory>" }, - { "${", "}", COLOR_GREEN, "<variable>", "</variable>" }, - { "", "", COLOR_BLUE, "<value>", "</value>" }, - { "", "", COLOR_BLUE, "<enum>", "</enum>" }, - { "\'", "\'", COLOR_GRAY, "<astcli>", "</astcli>" }, - - /* Special tags */ - { "", "", COLOR_YELLOW, "<note>", "</note>" }, - { "", "", COLOR_RED, "<warning>", "</warning>" } -}; - -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>NOTE:</note> ", "" }, - { "warning", "<warning>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; -} - -/*! \internal - * \brief Colorize the xmldoc output. - * \param bwinput Not colorized input. - * \retval NULL on error. - * \retval New malloced buffer colorized. - */ -static char *xmldoc_colorization(const char *bwinput) -{ - 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; - } - - 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 */ - 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. */ - 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; - } - } - } - - 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 <argument> tags inside it. - * \param node Root node to search argument elements. - * \retval 1 If a <argument> element is found inside 'node'. - * \retval 0 If no <argument> 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 -} - -/*! \internal - * \brief Get the syntax for a specified application or function. - * \param type Application or Function ? - * \param name Name of the application or function. - * \retval NULL on error. - * \retval The generated syntax in a ast_malloc'ed string. - */ -static char *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 <para> element. - * \param node The <para> element pointer. - * \param tabs Added this string before the content of the <para> element. - * \param posttabs Added this string after the content of the <para> 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 <para> 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</%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 <para> 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 <para> 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 <para> elements inside special tags. */ - for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { - /* first <para> 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 <argument> element from the xml documentation. - * \param fixnode Pointer to the 'argument' xml node. - * \param insideparameter If we are parsing an <argument> inside a <parameter>. - * \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 <argument> 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 <variable> node inside a <variablelist> 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 <value> tag only. */ - if (!printedpara) { - ast_str_append(buffer, 0, "\n"); - printedpara = 1; - } - /* Parse each <value name='valuename'>desciption</value> */ - valname = ast_xml_get_attribute(tmp, "name"); - if (valname) { - ret = 1; - ast_str_append(buffer, 0, "%s<value>%s</value>", 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 <variablelist> 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 <variablelist> 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 <para> 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<variable>%s</variable>: ", tabs, varname); - ast_xml_free_attr(varname); - /* Parse the <variable> possible values. */ - xmldoc_parse_variable(tmp, vartabs, buffer); - ret = 1; - } - } - } - - ast_free(vartabs); - - return ret; -} - -/*! \internal - * \brief Parse the <see-also> node content. - * \param type 'application' or 'function'. - * \param name Application or functions name. - * \retval NULL on error. - * \retval Content of the see-also node. - */ -static char *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 <see-also> 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 <see-also> node. */ - return NULL; - } - - /* prepare the output string. */ - outputstr = ast_str_create(128); - if (!outputstr) { - return NULL; - } - - /* get into the <see-also> 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 <ref> 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<astcli>%s</astcli>", (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 <enum> node. - * \brief fixnode An ast_xml_node pointer to the <enum> 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 <enumlist> node. - * \param fixnode As ast_xml pointer to the <enumlist> node. - * \param buffer The ast_str output buffer. - * \retval 0 if no <enumlist> node was parsed. - * \retval 1 if a <enumlist> 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<enum>%s</enum>", 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 <option> node. - * \param fixnode An ast_xml pointer to the <option> node. - * \param tabs A string to be appended at the begining of each line being added to the - * buffer string. - * \param buffer The output buffer. - * \retval 0 if no option node is parsed. - * \retval 1 if an option node is parsed. - */ -static int xmldoc_parse_option(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) -{ - struct ast_xml_node *node; - int ret = 0; - char *optiontabs; - - ast_asprintf(&optiontabs, "%s ", tabs); - if (!optiontabs) { - return ret; - } - 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")) { - /* if this is the first data appended to buffer, print a \n*/ - if (!ret && ast_xml_node_get_children(node)) { - /* print \n */ - ast_str_append(buffer, 0, "\n"); - } - if (xmldoc_parse_argument(node, 0, NULL, optiontabs, buffer)) { - ret = 1; - } - continue; - } - - if (xmldoc_parse_para(node, (ret ? tabs : ""), "\n", buffer)) { - ret = 1; - } else if (xmldoc_parse_specialtags(node, (ret ? tabs : ""), "\n", buffer)) { - ret = 1; - } - - xmldoc_parse_variablelist(node, optiontabs, buffer); - - xmldoc_parse_enumlist(node, optiontabs, buffer); - } - ast_free(optiontabs); - - return ret; -} - -/*! \internal - * \brief Parse an <optionlist> element from the xml documentation. - * \param fixnode Pointer to the optionlist xml node. - * \param tabs A string to be appended at the begining of each line being added to the - * buffer string. - * \param buffer Output buffer to put what is inside the optionlist tag. - */ -static void xmldoc_parse_optionlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) -{ - struct ast_xml_node *node; - const char *optname; - char *optionsyntax; - - for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { - /* Start appending every option tag. */ - if (strcasecmp(ast_xml_node_get_name(node), "option")) { - continue; - } - - /* Get the option name. */ - optname = ast_xml_get_attribute(node, "name"); - if (!optname) { - continue; - } - - optionsyntax = xmldoc_get_syntax(node, optname, "argument", 0, 1); - if (!optionsyntax) { - continue; - } - - ast_str_append(buffer, 0, "%s%s: ", tabs, optionsyntax); - - if (!xmldoc_parse_option(node, tabs, buffer)) { - ast_str_append(buffer, 0, "\n"); - } - } -} - -/*! \internal - * \brief Parse a 'parameter' tag inside a syntax element. - * \param fixnode A pointer to the 'parameter' xml node. - * \param tabs A string to be appended at the beginning of each line being printed inside - * 'buffer'. - * \param buffer String buffer to put values found inside the parameter element. - */ -static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) -{ - const char *paramname; - struct ast_xml_node *node = fixnode; - int hasarguments, printed = 0; - char *internaltabs; - - if (strcasecmp(ast_xml_node_get_name(node), "parameter")) { - return; - } - - hasarguments = xmldoc_has_arguments(node); - if (!(paramname = ast_xml_get_attribute(node, "name"))) { - /* parameter MUST have an attribute name. */ - return; - } - - ast_asprintf(&internaltabs, "%s ", tabs); - if (!internaltabs) { - return; - } - - if (!hasarguments) { - ast_str_append(buffer, 0, "%s\n", paramname); - ast_xml_free_attr(paramname); - printed = 1; - } - - for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { - if (!strcasecmp(ast_xml_node_get_name(node), "optionlist")) { - xmldoc_parse_optionlist(node, internaltabs, buffer); - } else if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) { - xmldoc_parse_enumlist(node, internaltabs, buffer); - } else if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { - xmldoc_parse_argument(node, 1, internaltabs, (!hasarguments ? " " : ""), buffer); - } else if (!strcasecmp(ast_xml_node_get_name(node), "para")) { - if (!printed) { - ast_str_append(buffer, 0, "%s\n", paramname); - ast_xml_free_attr(paramname); - printed = 1; - } - xmldoc_parse_para(node, internaltabs, "\n", buffer); - continue; - } else if ((xmldoc_parse_specialtags(node, internaltabs, "\n", buffer))) { - continue; - } - } - ast_free(internaltabs); -} - -/*! \internal - * \brief Generate the [arguments] tag based on type of node 'application' or - * 'function' and name. - * \param type 'application' or 'function' ? - * \param name Name of the application or function to build the 'arguments' tag. - * \retval NULL on error. - * \retval Output buffer with the [arguments] tag content. - */ -static char *xmldoc_build_arguments(const char *type, const char *name) -{ - struct ast_xml_node *node; - struct ast_str *ret = ast_str_create(128); - char *retstr = NULL; - - if (ast_strlen_zero(type) || ast_strlen_zero(name)) { - return NULL; - } - - node = xmldoc_get_node(type, name, documentation_language); - - if (!node || !ast_xml_node_get_children(node)) { - return NULL; - } - - /* Find the syntax field. */ - 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 || !ast_xml_node_get_children(node)) { - /* We couldn't find the syntax node. */ - return NULL; - } - - for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { - xmldoc_parse_parameter(node, "", &ret); - } - - if (ret->used > 0) { - /* remove last '\n' */ - if (ret->str[ret->used - 1] == '\n') { - ret->str[ret->used - 1] = '\0'; - ret->used--; - } - retstr = ast_strdup(ret->str); - } - ast_free(ret); - - return retstr; -} - -/*! \internal - * \brief Return the string within a node formatted with <para> and <variablelist> elements. - * \param node Parent node where content resides. - * \param raw If set, return the node's content without further processing. - * \param raw_wrap Wrap raw text. - * \retval NULL on error - * \retval Node content on success. - */ -static struct ast_str *xmldoc_get_formatted(struct ast_xml_node *node, int raw_output, int raw_wrap) -{ - struct ast_xml_node *tmp; - const char *notcleanret, *tmpstr; - struct ast_str *ret = ast_str_create(128); - - if (raw_output) { - notcleanret = ast_xml_get_text(node); - tmpstr = notcleanret; - xmldoc_string_cleanup(ast_skip_blanks(notcleanret), &ret, 0); - ast_xml_free_text(tmpstr); - } else { - for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { - /* if found, parse a <para> element. */ - if (xmldoc_parse_para(tmp, "", "\n", &ret)) { - continue; - } else if (xmldoc_parse_specialtags(tmp, "", "\n", &ret)) { - continue; - } - /* if found, parse a <variablelist> element. */ - xmldoc_parse_variablelist(tmp, "", &ret); - xmldoc_parse_enumlist(tmp, " ", &ret); - } - /* remove last '\n' */ - /* XXX Don't modify ast_str internals manually */ - if (ret->str[ret->used-1] == '\n') { - ret->str[ret->used-1] = '\0'; - ret->used--; - } - } - return ret; -} - -/*! \internal - * \brief Get the content of a field (synopsis, description, etc) from an asterisk document tree - * \param type Type of element (application, function, ...). - * \param name Name of element (Dial, Echo, Playback, ...). - * \param var Name of field to return (synopsis, description, etc). - * \param raw Field only contains text, no other elements inside it. - * \retval NULL On error. - * \retval Field text content on success. - */ -static char *xmldoc_build_field(const char *type, const char *name, const char *var, int raw) -{ - struct ast_xml_node *node; - char *ret = NULL; - struct ast_str *formatted; - - if (ast_strlen_zero(type) || ast_strlen_zero(name)) { - ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n"); - return ret; - } - - node = xmldoc_get_node(type, name, documentation_language); - - if (!node) { - ast_log(LOG_WARNING, "Counldn't find %s %s in XML documentation\n", type, name); - return ret; - } - - node = ast_xml_find_element(ast_xml_node_get_children(node), var, NULL, NULL); - - if (!node || !ast_xml_node_get_children(node)) { - ast_log(LOG_DEBUG, "Cannot find variable '%s' in tree '%s'\n", name, var); - return ret; - } - - formatted = xmldoc_get_formatted(node, raw, raw); - if (formatted->used > 0) { - ret = ast_strdup(formatted->str); - } - ast_free(formatted); - - return ret; -} -#endif /* AST_XML_DOCS */ - /*! \internal * \brief Retrieve the XML documentation of a specified ast_custom_function, * and populate ast_custom_function string fields. @@ -4693,27 +3205,27 @@ static int acf_retrieve_docs(struct ast_custom_function *acf) } /* load synopsis */ - tmpxml = xmldoc_build_field("function", acf->name, "synopsis", 1); + tmpxml = ast_xmldoc_build_synopsis("function", acf->name); ast_string_field_set(acf, synopsis, tmpxml); ast_free(tmpxml); /* load description */ - tmpxml = xmldoc_build_field("function", acf->name, "description", 0); + tmpxml = ast_xmldoc_build_description("function", acf->name); ast_string_field_set(acf, desc, tmpxml); ast_free(tmpxml); /* load syntax */ - tmpxml = xmldoc_build_syntax("function", acf->name); + tmpxml = ast_xmldoc_build_syntax("function", acf->name); ast_string_field_set(acf, syntax, tmpxml); ast_free(tmpxml); /* load arguments */ - tmpxml = xmldoc_build_arguments("function", acf->name); + tmpxml = ast_xmldoc_build_arguments("function", acf->name); ast_string_field_set(acf, arguments, tmpxml); ast_free(tmpxml); /* load seealso */ - tmpxml = xmldoc_build_seealso("function", acf->name); + tmpxml = ast_xmldoc_build_seealso("function", acf->name); ast_string_field_set(acf, seealso, tmpxml); ast_free(tmpxml); @@ -6460,27 +4972,27 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel /* Try to lookup the docs in our XML documentation database */ if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) { /* load synopsis */ - tmpxml = xmldoc_build_field("application", app, "synopsis", 1); + tmpxml = ast_xmldoc_build_synopsis("application", app); ast_string_field_set(tmp, synopsis, tmpxml); ast_free(tmpxml); /* load description */ - tmpxml = xmldoc_build_field("application", app, "description", 0); + tmpxml = ast_xmldoc_build_description("application", app); ast_string_field_set(tmp, description, tmpxml); ast_free(tmpxml); /* load syntax */ - tmpxml = xmldoc_build_syntax("application", app); + tmpxml = ast_xmldoc_build_syntax("application", app); ast_string_field_set(tmp, syntax, tmpxml); ast_free(tmpxml); /* load arguments */ - tmpxml = xmldoc_build_arguments("application", app); + tmpxml = ast_xmldoc_build_arguments("application", app); ast_string_field_set(tmp, arguments, tmpxml); ast_free(tmpxml); /* load seealso */ - tmpxml = xmldoc_build_seealso("application", app); + tmpxml = ast_xmldoc_build_seealso("application", app); ast_string_field_set(tmp, seealso, tmpxml); ast_free(tmpxml); tmp->docsrc = AST_XML_DOC; @@ -6515,121 +5027,6 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel return 0; } -#ifdef AST_XML_DOCS -/*! \brief Close and unload XML documentation. */ -static void xmldoc_unload_documentation(void) -{ - struct documentation_tree *doctree; - - AST_RWLIST_WRLOCK(&xmldoc_tree); - while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) { - ast_free(doctree->filename); - ast_xml_close(doctree->doc); - } - AST_RWLIST_UNLOCK(&xmldoc_tree); - - ast_xml_finish(); -} - -int ast_load_documentation(void) -{ - struct ast_xml_node *root_node; - struct ast_xml_doc *tmpdoc; - struct documentation_tree *doc_tree; - char *xmlpattern; - struct ast_config *cfg = NULL; - struct ast_variable *var = NULL; - struct ast_flags cnfflags = { 0 }; - int globret, i, dup, duplicate; - glob_t globbuf; - - /* setup default XML documentation language */ - snprintf(documentation_language, sizeof(documentation_language), default_documentation_language); - - if ((cfg = ast_config_load2("asterisk.conf", "" /* core can't reload */, cnfflags))) { - for (var = ast_variable_browse(cfg, "options"); var; var = var->next) { - if (!strcasecmp(var->name, "documentation_language")) { - if (!ast_strlen_zero(var->value)) { - snprintf(documentation_language, sizeof(documentation_language), "%s", var->value); - } - } - } - ast_config_destroy(cfg); - } - - /* initialize the XML library. */ - ast_xml_init(); - - /* register function to be run when asterisk finish. */ - ast_register_atexit(xmldoc_unload_documentation); - - /* Get every *-LANG.xml file inside $(ASTDATADIR)/documentation */ - ast_asprintf(&xmlpattern, "%s/documentation{/thirdparty/,/}*-{%s,%.2s_??,%s}.xml", ast_config_AST_DATA_DIR, - documentation_language, documentation_language, default_documentation_language); - globbuf.gl_offs = 0; /* initialize it to silence gcc */ - globret = glob(xmlpattern, MY_GLOB_FLAGS, NULL, &globbuf); - if (globret == GLOB_NOSPACE) { - ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Not enough memory\n", xmlpattern); - ast_free(xmlpattern); - return 1; - } else if (globret == GLOB_ABORTED) { - ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Read error\n", xmlpattern); - ast_free(xmlpattern); - return 1; - } - ast_free(xmlpattern); - - AST_RWLIST_WRLOCK(&xmldoc_tree); - /* loop over expanded files */ - for (i = 0; i < globbuf.gl_pathc; i++) { - /* check for duplicates (if we already [try to] open the same file. */ - duplicate = 0; - for (dup = 0; dup < i; dup++) { - if (!strcmp(globbuf.gl_pathv[i], globbuf.gl_pathv[dup])) { - duplicate = 1; - break; - } - } - if (duplicate) { - continue; - } - tmpdoc = NULL; - tmpdoc = ast_xml_open(globbuf.gl_pathv[i]); - if (!tmpdoc) { - ast_log(LOG_ERROR, "Could not open XML documentation at '%s'\n", globbuf.gl_pathv[i]); - continue; - } - /* Get doc root node and check if it starts with '<docs>' */ - root_node = ast_xml_get_root(tmpdoc); - if (!root_node) { - ast_log(LOG_ERROR, "Error getting documentation root node"); - ast_xml_close(tmpdoc); - continue; - } - /* Check root node name for malformed xmls. */ - if (strcmp(ast_xml_node_get_name(root_node), "docs")) { - ast_log(LOG_ERROR, "Documentation file is not well formed!\n"); - ast_xml_close(tmpdoc); - continue; - } - doc_tree = ast_calloc(1, sizeof(*doc_tree)); - if (!doc_tree) { - ast_log(LOG_ERROR, "Unable to allocate documentation_tree structure!\n"); - ast_xml_close(tmpdoc); - continue; - } - doc_tree->doc = tmpdoc; - doc_tree->filename = ast_strdup(globbuf.gl_pathv[i]); - AST_RWLIST_INSERT_TAIL(&xmldoc_tree, doc_tree, entry); - } - AST_RWLIST_UNLOCK(&xmldoc_tree); - - globfree(&globbuf); - - return 0; -} -#endif /* AST_XML_DOCS */ - /* * Append to the list. We don't have a tail pointer because we need * to scan the list anyways to check for duplicates during insertion. @@ -6683,27 +5080,27 @@ static void print_app_docs(struct ast_app *aa, int fd) #ifdef AST_XML_DOCS if (aa->docsrc == AST_XML_DOC) { - description = xmldoc_colorization(S_OR(aa->description, "Not available")); - arguments = xmldoc_colorization(S_OR(aa->arguments, "Not available")); - synopsis = xmldoc_colorization(S_OR(aa->synopsis, "Not available")); - seealso = xmldoc_colorization(S_OR(aa->seealso, "Not available")); - + description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1); + arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1); + synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1); + seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1); + if (!synopsis || !description || !arguments || !seealso) { goto return_cleanup; } - } else + } else #endif { - synopsis_size = strlen(S_OR(aa->synopsis, "Not Available")) + MAX_ESCAPE_CHARS; + synopsis_size = strlen(S_OR(aa->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; synopsis = ast_malloc(synopsis_size); - description_size = strlen(S_OR(aa->description, "Not Available")) + MAX_ESCAPE_CHARS; + description_size = strlen(S_OR(aa->description, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; description = ast_malloc(description_size); - arguments_size = strlen(S_OR(aa->arguments, "Not Available")) + MAX_ESCAPE_CHARS; + arguments_size = strlen(S_OR(aa->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; arguments = ast_malloc(arguments_size); - seealso_size = strlen(S_OR(aa->seealso, "Not Available")) + MAX_ESCAPE_CHARS; + seealso_size = strlen(S_OR(aa->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; seealso = ast_malloc(seealso_size); if (!synopsis || !description || !arguments || !seealso) { @@ -6717,7 +5114,7 @@ static void print_app_docs(struct ast_app *aa, int fd) } /* Handle the syntax the same for both XML and raw docs */ - syntax_size = strlen(S_OR(aa->syntax, "Not Available")) + MAX_ESCAPE_CHARS; + syntax_size = strlen(S_OR(aa->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; if (!(syntax = ast_malloc(syntax_size))) { goto return_cleanup; } 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) <eliels@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 Documentation API + * + * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com> + */ + +#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, "<replaceable>", "</replaceable>" }, + { "\'", "\'", COLOR_BLUE, "<literal>", "</literal>" }, + { "*", "*", COLOR_RED, "<emphasis>", "</emphasis>" }, + { "\"", "\"", COLOR_YELLOW, "<filename>", "</filename>" }, + { "\"", "\"", COLOR_CYAN, "<directory>", "</directory>" }, + { "${", "}", COLOR_GREEN, "<variable>", "</variable>" }, + { "", "", COLOR_BLUE, "<value>", "</value>" }, + { "", "", COLOR_BLUE, "<enum>", "</enum>" }, + { "\'", "\'", COLOR_GRAY, "<astcli>", "</astcli>" }, + + /* Special tags */ + { "", "", COLOR_YELLOW, "<note>", "</note>" }, + { "", "", COLOR_RED, "<warning>", "</warning>" } +}; + +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>NOTE:</note> ", "" }, + { "warning", "<warning>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 <argument> tags inside it. + * \param node Root node to search argument elements. + * \retval 1 If a <argument> element is found inside 'node'. + * \retval 0 If no <argument> 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 <para> element. + * \param node The <para> element pointer. + * \param tabs Added this string before the content of the <para> element. + * \param posttabs Added this string after the content of the <para> 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 <para> 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</%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 <para> 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 <para> 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 <para> elements inside special tags. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + /* first <para> 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 <argument> element from the xml documentation. + * \param fixnode Pointer to the 'argument' xml node. + * \param insideparameter If we are parsing an <argument> inside a <parameter>. + * \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 <argument> 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 <variable> node inside a <variablelist> 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 <value> tag only. */ + if (!printedpara) { + ast_str_append(buffer, 0, "\n"); + printedpara = 1; + } + /* Parse each <value name='valuename'>desciption</value> */ + valname = ast_xml_get_attribute(tmp, "name"); + if (valname) { + ret = 1; + ast_str_append(buffer, 0, "%s<value>%s</value>", 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 <variablelist> 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 <variablelist> 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 <para> 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<variable>%s</variable>: ", tabs, varname); + ast_xml_free_attr(varname); + /* Parse the <variable> 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 <see-also> 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 <see-also> node. */ + return NULL; + } + + /* prepare the output string. */ + outputstr = ast_str_create(128); + if (!outputstr) { + return NULL; + } + + /* get into the <see-also> 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 <ref> 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<astcli>%s</astcli>", (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 <enum> node. + * \brief fixnode An ast_xml_node pointer to the <enum> 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 <enumlist> node. + * \param fixnode As ast_xml pointer to the <enumlist> node. + * \param buffer The ast_str output buffer. + * \retval 0 if no <enumlist> node was parsed. + * \retval 1 if a <enumlist> 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<enum>%s</enum>", 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 <option> node. + * \param fixnode An ast_xml pointer to the <option> node. + * \param tabs A string to be appended at the begining of each line being added to the + * buffer string. + * \param buffer The output buffer. + * \retval 0 if no option node is parsed. + * \retval 1 if an option node is parsed. + */ +static int xmldoc_parse_option(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node; + int ret = 0; + char *optiontabs; + + ast_asprintf(&optiontabs, "%s ", tabs); + if (!optiontabs) { + return ret; + } + 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")) { + /* if this is the first data appended to buffer, print a \n*/ + if (!ret && ast_xml_node_get_children(node)) { + /* print \n */ + ast_str_append(buffer, 0, "\n"); + } + if (xmldoc_parse_argument(node, 0, NULL, optiontabs, buffer)) { + ret = 1; + } + continue; + } + + if (xmldoc_parse_para(node, (ret ? tabs : ""), "\n", buffer)) { + ret = 1; + } else if (xmldoc_parse_specialtags(node, (ret ? tabs : ""), "\n", buffer)) { + ret = 1; + } + + xmldoc_parse_variablelist(node, optiontabs, buffer); + + xmldoc_parse_enumlist(node, optiontabs, buffer); + } + ast_free(optiontabs); + + return ret; +} + +/*! \internal + * \brief Parse an <optionlist> element from the xml documentation. + * \param fixnode Pointer to the optionlist xml node. + * \param tabs A string to be appended at the begining of each line being added to the + * buffer string. + * \param buffer Output buffer to put what is inside the optionlist tag. + */ +static void xmldoc_parse_optionlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node; + const char *optname; + char *optionsyntax; + + for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { + /* Start appending every option tag. */ + if (strcasecmp(ast_xml_node_get_name(node), "option")) { + continue; + } + + /* Get the option name. */ + optname = ast_xml_get_attribute(node, "name"); + if (!optname) { + continue; + } + + optionsyntax = xmldoc_get_syntax(node, optname, "argument", 0, 1); + if (!optionsyntax) { + continue; + } + + ast_str_append(buffer, 0, "%s%s: ", tabs, optionsyntax); + + if (!xmldoc_parse_option(node, tabs, buffer)) { + ast_str_append(buffer, 0, "\n"); + } + } +} + +/*! \internal + * \brief Parse a 'parameter' tag inside a syntax element. + * \param fixnode A pointer to the 'parameter' xml node. + * \param tabs A string to be appended at the beginning of each line being printed inside + * 'buffer'. + * \param buffer String buffer to put values found inside the parameter element. + */ +static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + const char *paramname; + struct ast_xml_node *node = fixnode; + int hasarguments, printed = 0; + char *internaltabs; + + if (strcasecmp(ast_xml_node_get_name(node), "parameter")) { + return; + } + + hasarguments = xmldoc_has_arguments(node); + if (!(paramname = ast_xml_get_attribute(node, "name"))) { + /* parameter MUST have an attribute name. */ + return; + } + + ast_asprintf(&internaltabs, "%s ", tabs); + if (!internaltabs) { + return; + } + + if (!hasarguments) { + ast_str_append(buffer, 0, "%s\n", paramname); + ast_xml_free_attr(paramname); + printed = 1; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "optionlist")) { + xmldoc_parse_optionlist(node, internaltabs, buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) { + xmldoc_parse_enumlist(node, internaltabs, buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { + xmldoc_parse_argument(node, 1, internaltabs, (!hasarguments ? " " : ""), buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "para")) { + if (!printed) { + ast_str_append(buffer, 0, "%s\n", paramname); + ast_xml_free_attr(paramname); + printed = 1; + } + xmldoc_parse_para(node, internaltabs, "\n", buffer); + continue; + } else if ((xmldoc_parse_specialtags(node, internaltabs, "\n", buffer))) { + continue; + } + } + ast_free(internaltabs); +} + +char *ast_xmldoc_build_arguments(const char *type, const char *name) +{ + struct ast_xml_node *node; + struct ast_str *ret = ast_str_create(128); + char *retstr = NULL; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + return NULL; + } + + node = xmldoc_get_node(type, name, documentation_language); + + if (!node || !ast_xml_node_get_children(node)) { + return NULL; + } + + /* Find the syntax field. */ + 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 || !ast_xml_node_get_children(node)) { + /* We couldn't find the syntax node. */ + return NULL; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + xmldoc_parse_parameter(node, "", &ret); + } + + if (ret->used > 0) { + /* remove last '\n' */ + if (ret->str[ret->used - 1] == '\n') { + ret->str[ret->used - 1] = '\0'; + ret->used--; + } + retstr = ast_strdup(ret->str); + } + ast_free(ret); + + return retstr; +} + +/*! \internal + * \brief Return the string within a node formatted with <para> and <variablelist> elements. + * \param node Parent node where content resides. + * \param raw If set, return the node's content without further processing. + * \param raw_wrap Wrap raw text. + * \retval NULL on error + * \retval Node content on success. + */ +static struct ast_str *xmldoc_get_formatted(struct ast_xml_node *node, int raw_output, int raw_wrap) +{ + struct ast_xml_node *tmp; + const char *notcleanret, *tmpstr; + struct ast_str *ret = ast_str_create(128); + + if (raw_output) { + notcleanret = ast_xml_get_text(node); + tmpstr = notcleanret; + xmldoc_string_cleanup(ast_skip_blanks(notcleanret), &ret, 0); + ast_xml_free_text(tmpstr); + } else { + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* if found, parse a <para> element. */ + if (xmldoc_parse_para(tmp, "", "\n", &ret)) { + continue; + } else if (xmldoc_parse_specialtags(tmp, "", "\n", &ret)) { + continue; + } + /* if found, parse a <variablelist> element. */ + xmldoc_parse_variablelist(tmp, "", &ret); + xmldoc_parse_enumlist(tmp, " ", &ret); + } + /* remove last '\n' */ + /* XXX Don't modify ast_str internals manually */ + if (ret->str[ret->used-1] == '\n') { + ret->str[ret->used-1] = '\0'; + ret->used--; + } + } + return ret; +} + +/*! + * \brief Get the content of a field (synopsis, description, etc) from an asterisk document tree + * \param type Type of element (application, function, ...). + * \param name Name of element (Dial, Echo, Playback, ...). + * \param var Name of field to return (synopsis, description, etc). + * \param raw Field only contains text, no other elements inside it. + * \retval NULL On error. + * \retval Field text content on success. + */ +static char *xmldoc_build_field(const char *type, const char *name, const char *var, int raw) +{ + struct ast_xml_node *node; + char *ret = NULL; + struct ast_str *formatted; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n"); + return ret; + } + + node = xmldoc_get_node(type, name, documentation_language); + + if (!node) { + ast_log(LOG_WARNING, "Counldn't find %s %s in XML documentation\n", type, name); + return ret; + } + + node = ast_xml_find_element(ast_xml_node_get_children(node), var, NULL, NULL); + + if (!node || !ast_xml_node_get_children(node)) { + ast_log(LOG_DEBUG, "Cannot find variable '%s' in tree '%s'\n", name, var); + return ret; + } + + formatted = xmldoc_get_formatted(node, raw, raw); + if (formatted->used > 0) { + ret = ast_strdup(formatted->str); + } + ast_free(formatted); + + return ret; +} + +char *ast_xmldoc_build_synopsis(const char *type, const char *name) +{ + return xmldoc_build_field(type, name, "synopsis", 1); +} + +char *ast_xmldoc_build_description(const char *type, const char *name) +{ + return xmldoc_build_field(type, name, "description", 0); +} + +/*! \brief Close and unload XML documentation. */ +static void xmldoc_unload_documentation(void) +{ + struct documentation_tree *doctree; + + AST_RWLIST_WRLOCK(&xmldoc_tree); + while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) { + ast_free(doctree->filename); + ast_xml_close(doctree->doc); + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + ast_xml_finish(); +} + +int ast_xmldoc_load_documentation(void) +{ + struct ast_xml_node *root_node; + struct ast_xml_doc *tmpdoc; + struct documentation_tree *doc_tree; + char *xmlpattern; + struct ast_config *cfg = NULL; + struct ast_variable *var = NULL; + struct ast_flags cnfflags = { 0 }; + int globret, i, dup, duplicate; + glob_t globbuf; + + /* setup default XML documentation language */ + snprintf(documentation_language, sizeof(documentation_language), default_documentation_language); + + if ((cfg = ast_config_load2("asterisk.conf", "" /* core can't reload */, cnfflags))) { + for (var = ast_variable_browse(cfg, "options"); var; var = var->next) { + if (!strcasecmp(var->name, "documentation_language")) { + if (!ast_strlen_zero(var->value)) { + snprintf(documentation_language, sizeof(documentation_language), "%s", var->value); + } + } + } + ast_config_destroy(cfg); + } + + /* initialize the XML library. */ + ast_xml_init(); + + /* register function to be run when asterisk finish. */ + ast_register_atexit(xmldoc_unload_documentation); + + /* Get every *-LANG.xml file inside $(ASTDATADIR)/documentation */ + ast_asprintf(&xmlpattern, "%s/documentation{/thirdparty/,/}*-{%s,%.2s_??,%s}.xml", ast_config_AST_DATA_DIR, + documentation_language, documentation_language, default_documentation_language); + globbuf.gl_offs = 0; /* initialize it to silence gcc */ + globret = glob(xmlpattern, MY_GLOB_FLAGS, NULL, &globbuf); + if (globret == GLOB_NOSPACE) { + ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Not enough memory\n", xmlpattern); + ast_free(xmlpattern); + return 1; + } else if (globret == GLOB_ABORTED) { + ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Read error\n", xmlpattern); + ast_free(xmlpattern); + return 1; + } + ast_free(xmlpattern); + + AST_RWLIST_WRLOCK(&xmldoc_tree); + /* loop over expanded files */ + for (i = 0; i < globbuf.gl_pathc; i++) { + /* check for duplicates (if we already [try to] open the same file. */ + duplicate = 0; + for (dup = 0; dup < i; dup++) { + if (!strcmp(globbuf.gl_pathv[i], globbuf.gl_pathv[dup])) { + duplicate = 1; + break; + } + } + if (duplicate) { + continue; + } + tmpdoc = NULL; + tmpdoc = ast_xml_open(globbuf.gl_pathv[i]); + if (!tmpdoc) { + ast_log(LOG_ERROR, "Could not open XML documentation at '%s'\n", globbuf.gl_pathv[i]); + continue; + } + /* Get doc root node and check if it starts with '<docs>' */ + root_node = ast_xml_get_root(tmpdoc); + if (!root_node) { + ast_log(LOG_ERROR, "Error getting documentation root node"); + ast_xml_close(tmpdoc); + continue; + } + /* Check root node name for malformed xmls. */ + if (strcmp(ast_xml_node_get_name(root_node), "docs")) { + ast_log(LOG_ERROR, "Documentation file is not well formed!\n"); + ast_xml_close(tmpdoc); + continue; + } + doc_tree = ast_calloc(1, sizeof(*doc_tree)); + if (!doc_tree) { + ast_log(LOG_ERROR, "Unable to allocate documentation_tree structure!\n"); + ast_xml_close(tmpdoc); + continue; + } + doc_tree->doc = tmpdoc; + doc_tree->filename = ast_strdup(globbuf.gl_pathv[i]); + AST_RWLIST_INSERT_TAIL(&xmldoc_tree, doc_tree, entry); + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + globfree(&globbuf); + + return 0; +} + +#endif /* AST_XML_DOCS */ + + |