Thu Apr 28 2011 16:56:49

Asterisk developer's documentation


xmldoc.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2008, Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
00005  *
00006  * See http://www.asterisk.org for more information about
00007  * the Asterisk project. Please do not directly contact
00008  * any of the maintainers of this project for assistance;
00009  * the project provides a web site, mailing lists and IRC
00010  * channels for your use.
00011  *
00012  * This program is free software, distributed under the terms of
00013  * the GNU General Public License Version 2. See the LICENSE file
00014  * at the top of the source tree.
00015  */
00016 
00017 /*! \file
00018  *
00019  * \brief XML Documentation API
00020  *
00021  * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
00022  */
00023 
00024 #include "asterisk.h"
00025 
00026 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 300520 $")
00027 
00028 #include "asterisk/_private.h"
00029 #include "asterisk/paths.h"
00030 #include "asterisk/linkedlists.h"
00031 #include "asterisk/strings.h"
00032 #include "asterisk/config.h"
00033 #include "asterisk/term.h"
00034 #include "asterisk/xmldoc.h"
00035 
00036 #ifdef AST_XML_DOCS
00037 
00038 /*! \brief Default documentation language. */
00039 static const char default_documentation_language[] = "en_US";
00040 
00041 /*! \brief Number of columns to print when showing the XML documentation with a
00042  *         'core show application/function *' CLI command. Used in text wrapping.*/
00043 static const int xmldoc_text_columns = 74;
00044 
00045 /*! \brief This is a value that we will use to let the wrapping mechanism move the cursor
00046  *         backward and forward xmldoc_max_diff positions before cutting the middle of a
00047  *         word, trying to find a space or a \n. */
00048 static const int xmldoc_max_diff = 5;
00049 
00050 /*! \brief XML documentation language. */
00051 static char documentation_language[6];
00052 
00053 /*! \brief XML documentation tree */
00054 struct documentation_tree {
00055    char *filename;               /*!< XML document filename. */
00056    struct ast_xml_doc *doc;         /*!< Open document pointer. */
00057    AST_RWLIST_ENTRY(documentation_tree) entry;
00058 };
00059 
00060 static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *name, int printname);
00061 static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer);
00062 
00063 /*!
00064  * \brief Container of documentation trees
00065  *
00066  * \note A RWLIST is a sufficient container type to use here for now.
00067  *       However, some changes will need to be made to implement ref counting
00068  *       if reload support is added in the future.
00069  */
00070 static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree);
00071 
00072 static const struct strcolorized_tags {
00073    const char *init;      /*!< Replace initial tag with this string. */
00074    const char *end;       /*!< Replace end tag with this string. */
00075    const int colorfg;     /*!< Foreground color. */
00076    const char *inittag;   /*!< Initial tag description. */
00077    const char *endtag;    /*!< Ending tag description. */
00078 } colorized_tags[] = {
00079    { "<",  ">",  COLOR_GREEN,  "<replaceable>", "</replaceable>" },
00080    { "\'", "\'", COLOR_BLUE,   "<literal>",     "</literal>" },
00081    { "*",  "*",  COLOR_RED,    "<emphasis>",    "</emphasis>" },
00082    { "\"", "\"", COLOR_YELLOW, "<filename>",    "</filename>" },
00083    { "\"", "\"", COLOR_CYAN,   "<directory>",   "</directory>" },
00084    { "${", "}",  COLOR_GREEN,  "<variable>",    "</variable>" },
00085    { "",   "",   COLOR_BLUE,   "<value>",       "</value>" },
00086    { "",   "",   COLOR_BLUE,   "<enum>",        "</enum>" },
00087    { "\'", "\'", COLOR_GRAY,   "<astcli>",      "</astcli>" },
00088 
00089    /* Special tags */
00090    { "", "", COLOR_YELLOW, "<note>",   "</note>" },
00091    { "", "", COLOR_RED,   "<warning>", "</warning>" }
00092 };
00093 
00094 static const struct strspecial_tags {
00095    const char *tagname;    /*!< Special tag name. */
00096    const char *init;    /*!< Print this at the beginning. */
00097    const char *end;     /*!< Print this at the end. */
00098 } special_tags[] = {
00099    { "note",    "<note>NOTE:</note> ",             "" },
00100    { "warning", "<warning>WARNING!!!:</warning> ", "" }
00101 };
00102 
00103 /*! \internal
00104  *  \brief Calculate the space in bytes used by a format string
00105  *         that will be passed to a sprintf function.
00106  *  \param postbr The format string to use to calculate the length.
00107  *  \retval The postbr length.
00108  */
00109 static int xmldoc_postbrlen(const char *postbr)
00110 {
00111    int postbrreallen = 0, i;
00112    size_t postbrlen;
00113 
00114    if (!postbr) {
00115       return 0;
00116    }
00117    postbrlen = strlen(postbr);
00118    for (i = 0; i < postbrlen; i++) {
00119       if (postbr[i] == '\t') {
00120          postbrreallen += 8 - (postbrreallen % 8);
00121       } else {
00122          postbrreallen++;
00123       }
00124    }
00125    return postbrreallen;
00126 }
00127 
00128 /*! \internal
00129  *  \brief Setup postbr to be used while wrapping the text.
00130  *         Add to postbr array all the spaces and tabs at the beginning of text.
00131  *  \param postbr output array.
00132  *  \param len text array length.
00133  *  \param text Text with format string before the actual string.
00134  */
00135 static void xmldoc_setpostbr(char *postbr, size_t len, const char *text)
00136 {
00137    int c, postbrlen = 0;
00138 
00139    if (!text) {
00140       return;
00141    }
00142 
00143    for (c = 0; c < len; c++) {
00144       if (text[c] == '\t' || text[c] == ' ') {
00145          postbr[postbrlen++] = text[c];
00146       } else {
00147          break;
00148       }
00149    }
00150    postbr[postbrlen] = '\0';
00151 }
00152 
00153 /*! \internal
00154  *  \brief Try to find a space or a break in text starting at currentpost
00155  *         and moving at most maxdiff positions.
00156  *         Helper for xmldoc_string_wrap().
00157  *  \param text Input string where it will search.
00158  *  \param currentpos Current position within text.
00159  *  \param maxdiff Not move more than maxdiff inside text.
00160  *  \retval 1 if a space or break is found inside text while moving.
00161  *  \retval 0 if no space or break is found.
00162  */
00163 static int xmldoc_wait_nextspace(const char *text, int currentpos, int maxdiff)
00164 {
00165    int i, textlen;
00166 
00167    if (!text) {
00168       return 0;
00169    }
00170 
00171    textlen = strlen(text);
00172    for (i = currentpos; i < textlen; i++) {
00173       if (text[i] == ESC) {
00174          /* Move to the end of the escape sequence */
00175          while (i < textlen && text[i] != 'm') {
00176             i++;
00177          }
00178       } else if (text[i] == ' ' || text[i] == '\n') {
00179          /* Found the next space or linefeed */
00180          return 1;
00181       } else if (i - currentpos > maxdiff) {
00182          /* We have looked the max distance and didn't find it */
00183          return 0;
00184       }
00185    }
00186 
00187    /* Reached the end and did not find it */
00188 
00189    return 0;
00190 }
00191 
00192 /*! \internal
00193  *  \brief Helper function for xmldoc_string_wrap().
00194  *    Try to found a space or a break inside text moving backward
00195  *    not more than maxdiff positions.
00196  *  \param text The input string where to search for a space.
00197  *  \param currentpos The current cursor position.
00198  *  \param maxdiff The max number of positions to move within text.
00199  *  \retval 0 If no space is found (Notice that text[currentpos] is not a space or a break)
00200  *  \retval > 0 If a space or a break is found, and the result is the position relative to
00201  *    currentpos.
00202  */
00203 static int xmldoc_foundspace_backward(const char *text, int currentpos, int maxdiff)
00204 {
00205    int i;
00206 
00207    for (i = currentpos; i > 0; i--) {
00208       if (text[i] == ' ' || text[i] == '\n') {
00209          return (currentpos - i);
00210       } else if (text[i] == 'm' && (text[i - 1] >= '0' || text[i - 1] <= '9')) {
00211          /* give up, we found the end of a possible ESC sequence. */
00212          return 0;
00213       } else if (currentpos - i > maxdiff) {
00214          /* give up, we can't move anymore. */
00215          return 0;
00216       }
00217    }
00218 
00219    /* we found the beginning of the text */
00220 
00221    return 0;
00222 }
00223 
00224 /*! \internal
00225  *  \brief Justify a text to a number of columns.
00226  *  \param text Input text to be justified.
00227  *  \param columns Number of columns to preserve in the text.
00228  *  \param maxdiff Try to not cut a word when goinf down.
00229  *  \retval NULL on error.
00230  *  \retval The wrapped text.
00231  */
00232 static char *xmldoc_string_wrap(const char *text, int columns, int maxdiff)
00233 {
00234    struct ast_str *tmp;
00235    char *ret, postbr[160];
00236    int count = 1, i, backspace, needtobreak = 0, colmax, textlen;
00237 
00238    /* sanity check */
00239    if (!text || columns <= 0 || maxdiff < 0) {
00240       ast_log(LOG_WARNING, "Passing wrong arguments while trying to wrap the text\n");
00241       return NULL;
00242    }
00243 
00244    tmp = ast_str_create(strlen(text) * 3);
00245 
00246    if (!tmp) {
00247       return NULL;
00248    }
00249 
00250    /* Check for blanks and tabs and put them in postbr. */
00251    xmldoc_setpostbr(postbr, sizeof(postbr), text);
00252    colmax = columns - xmldoc_postbrlen(postbr);
00253 
00254    textlen = strlen(text);
00255    for (i = 0; i < textlen; i++) {
00256       if (needtobreak || !(count % colmax)) {
00257          if (text[i] == ' ') {
00258             ast_str_append(&tmp, 0, "\n%s", postbr);
00259             needtobreak = 0;
00260             count = 1;
00261          } else if (text[i] != '\n') {
00262             needtobreak = 1;
00263             if (xmldoc_wait_nextspace(text, i, maxdiff)) {
00264                /* wait for the next space */
00265                ast_str_append(&tmp, 0, "%c", text[i]);
00266                continue;
00267             }
00268             /* Try to look backwards */
00269             backspace = xmldoc_foundspace_backward(text, i, maxdiff);
00270             if (backspace) {
00271                needtobreak = 1;
00272                ast_str_truncate(tmp, -backspace);
00273                i -= backspace + 1;
00274                continue;
00275             }
00276             ast_str_append(&tmp, 0, "\n%s", postbr);
00277             needtobreak = 0;
00278             count = 1;
00279          }
00280          /* skip blanks after a \n */
00281          while (text[i] == ' ') {
00282             i++;
00283          }
00284       }
00285       if (text[i] == '\n') {
00286          xmldoc_setpostbr(postbr, sizeof(postbr), &text[i] + 1);
00287          colmax = columns - xmldoc_postbrlen(postbr);
00288          needtobreak = 0;
00289          count = 1;
00290       }
00291       if (text[i] == ESC) {
00292          /* Ignore Escape sequences. */
00293          do {
00294             ast_str_append(&tmp, 0, "%c", text[i]);
00295             i++;
00296          } while (i < textlen && text[i] != 'm');
00297       } else {
00298          count++;
00299       }
00300       ast_str_append(&tmp, 0, "%c", text[i]);
00301    }
00302 
00303    ret = ast_strdup(ast_str_buffer(tmp));
00304    ast_free(tmp);
00305 
00306    return ret;
00307 }
00308 
00309 char *ast_xmldoc_printable(const char *bwinput, int withcolors)
00310 {
00311    struct ast_str *colorized;
00312    char *wrapped = NULL;
00313    int i, c, len, colorsection;
00314    char *tmp;
00315    size_t bwinputlen;
00316    static const int base_fg = COLOR_CYAN;
00317 
00318    if (!bwinput) {
00319       return NULL;
00320    }
00321 
00322    bwinputlen = strlen(bwinput);
00323 
00324    if (!(colorized = ast_str_create(256))) {
00325       return NULL;
00326    }
00327 
00328    if (withcolors) {
00329       ast_term_color_code(&colorized, base_fg, 0);
00330       if (!colorized) {
00331          return NULL;
00332       }
00333    }
00334 
00335    for (i = 0; i < bwinputlen; i++) {
00336       colorsection = 0;
00337       /* Check if we are at the beginning of a tag to be colorized. */
00338       for (c = 0; c < ARRAY_LEN(colorized_tags); c++) {
00339          if (strncasecmp(bwinput + i, colorized_tags[c].inittag, strlen(colorized_tags[c].inittag))) {
00340             continue;
00341          }
00342 
00343          if (!(tmp = strcasestr(bwinput + i + strlen(colorized_tags[c].inittag), colorized_tags[c].endtag))) {
00344             continue;
00345          }
00346 
00347          len = tmp - (bwinput + i + strlen(colorized_tags[c].inittag));
00348 
00349          /* Setup color */
00350          if (withcolors) {
00351             ast_term_color_code(&colorized, colorized_tags[c].colorfg, 0);
00352             if (!colorized) {
00353                return NULL;
00354             }
00355          }
00356 
00357          /* copy initial string replace */
00358          ast_str_append(&colorized, 0, "%s", colorized_tags[c].init);
00359          if (!colorized) {
00360             return NULL;
00361          }
00362          {
00363             char buf[len + 1];
00364             ast_copy_string(buf, bwinput + i + strlen(colorized_tags[c].inittag), sizeof(buf));
00365             ast_str_append(&colorized, 0, "%s", buf);
00366          }
00367          if (!colorized) {
00368             return NULL;
00369          }
00370 
00371          /* copy the ending string replace */
00372          ast_str_append(&colorized, 0, "%s", colorized_tags[c].end);
00373          if (!colorized) {
00374             return NULL;
00375          }
00376 
00377          /* Continue with the last color. */
00378          if (withcolors) {
00379             ast_term_color_code(&colorized, base_fg, 0);
00380             if (!colorized) {
00381                return NULL;
00382             }
00383          }
00384 
00385          i += len + strlen(colorized_tags[c].endtag) + strlen(colorized_tags[c].inittag) - 1;
00386          colorsection = 1;
00387          break;
00388       }
00389 
00390       if (!colorsection) {
00391          ast_str_append(&colorized, 0, "%c", bwinput[i]);
00392          if (!colorized) {
00393             return NULL;
00394          }
00395       }
00396    }
00397 
00398    if (withcolors) {
00399       ast_str_append(&colorized, 0, "%s", term_end());
00400       if (!colorized) {
00401          return NULL;
00402       }
00403    }
00404 
00405    /* Wrap the text, notice that string wrap will avoid cutting an ESC sequence. */
00406    wrapped = xmldoc_string_wrap(ast_str_buffer(colorized), xmldoc_text_columns, xmldoc_max_diff);
00407 
00408    ast_free(colorized);
00409 
00410    return wrapped;
00411 }
00412 
00413 /*! \internal
00414  *  \brief Cleanup spaces and tabs after a \n
00415  *  \param text String to be cleaned up.
00416  *  \param output buffer (not already allocated).
00417  *  \param lastspaces Remove last spaces in the string.
00418  */
00419 static void xmldoc_string_cleanup(const char *text, struct ast_str **output, int lastspaces)
00420 {
00421    int i;
00422    size_t textlen;
00423 
00424    if (!text) {
00425       *output = NULL;
00426       return;
00427    }
00428 
00429    textlen = strlen(text);
00430 
00431    *output = ast_str_create(textlen);
00432    if (!(*output)) {
00433       ast_log(LOG_ERROR, "Problem allocating output buffer\n");
00434       return;
00435    }
00436 
00437    for (i = 0; i < textlen; i++) {
00438       if (text[i] == '\n' || text[i] == '\r') {
00439          /* remove spaces/tabs/\n after a \n. */
00440          while (text[i + 1] == '\t' || text[i + 1] == '\r' || text[i + 1] == '\n') {
00441             i++;
00442          }
00443          ast_str_append(output, 0, " ");
00444          continue;
00445       } else {
00446          ast_str_append(output, 0, "%c", text[i]);
00447       }
00448    }
00449 
00450    /* remove last spaces (we dont want always to remove the trailing spaces). */
00451    if (lastspaces) {
00452       ast_str_trim_blanks(*output);
00453    }
00454 }
00455 
00456 /*! \internal
00457  *  \brief Get the application/function node for 'name' application/function with language 'language'
00458  *         if we don't find any, get the first application with 'name' no matter which language with.
00459  *  \param type 'application', 'function', ...
00460  *  \param name Application or Function name.
00461  *  \param language Try to get this language (if not found try with en_US)
00462  *  \retval NULL on error.
00463  *  \retval A node of type ast_xml_node.
00464  */
00465 static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *language)
00466 {
00467    struct ast_xml_node *node = NULL;
00468    struct documentation_tree *doctree;
00469    const char *lang;
00470 
00471    AST_RWLIST_RDLOCK(&xmldoc_tree);
00472    AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
00473       /* the core xml documents have priority over thirdparty document. */
00474       node = ast_xml_get_root(doctree->doc);
00475       while ((node = ast_xml_find_element(node, type, "name", name))) {
00476          /* Check language */
00477          lang = ast_xml_get_attribute(node, "language");
00478          if (lang && !strcmp(lang, language)) {
00479             ast_xml_free_attr(lang);
00480             break;
00481          } else if (lang) {
00482             ast_xml_free_attr(lang);
00483          }
00484       }
00485 
00486       if (node && ast_xml_node_get_children(node)) {
00487          break;
00488       }
00489 
00490       /* We didn't find the application documentation for the specified language,
00491       so, try to load documentation for any language */
00492       node = ast_xml_get_root(doctree->doc);
00493       if (ast_xml_node_get_children(node)) {
00494          if ((node = ast_xml_find_element(ast_xml_node_get_children(node), type, "name", name))) {
00495             break;
00496          }
00497       }
00498    }
00499    AST_RWLIST_UNLOCK(&xmldoc_tree);
00500 
00501    return node;
00502 }
00503 
00504 /*! \internal
00505  *  \brief Helper function used to build the syntax, it allocates the needed buffer (or reallocates it),
00506  *         and based on the reverse value it makes use of fmt to print the parameter list inside the
00507  *         realloced buffer (syntax).
00508  *  \param reverse We are going backwards while generating the syntax?
00509  *  \param len Current length of 'syntax' buffer.
00510  *  \param syntax Output buffer for the concatenated values.
00511  *  \param fmt A format string that will be used in a sprintf call.
00512  */
00513 static void __attribute__((format(printf, 4, 5))) xmldoc_reverse_helper(int reverse, int *len, char **syntax, const char *fmt, ...)
00514 {
00515    int totlen, tmpfmtlen;
00516    char *tmpfmt, tmp;
00517    va_list ap;
00518 
00519    va_start(ap, fmt);
00520    if (ast_vasprintf(&tmpfmt, fmt, ap) < 0) {
00521       va_end(ap);
00522       return;
00523    }
00524    va_end(ap);
00525 
00526    tmpfmtlen = strlen(tmpfmt);
00527    totlen = *len + tmpfmtlen + 1;
00528 
00529    *syntax = ast_realloc(*syntax, totlen);
00530 
00531    if (!*syntax) {
00532       ast_free(tmpfmt);
00533       return;
00534    }
00535 
00536    if (reverse) {
00537       memmove(*syntax + tmpfmtlen, *syntax, *len);
00538       /* Save this char, it will be overwritten by the \0 of strcpy. */
00539       tmp = (*syntax)[0];
00540       strcpy(*syntax, tmpfmt);
00541       /* Restore the already saved char. */
00542       (*syntax)[tmpfmtlen] = tmp;
00543       (*syntax)[totlen - 1] = '\0';
00544    } else {
00545       strcpy(*syntax + *len, tmpfmt);
00546    }
00547 
00548    *len = totlen - 1;
00549    ast_free(tmpfmt);
00550 }
00551 
00552 /*! \internal
00553  *  \brief Check if the passed node has 'what' tags inside it.
00554  *  \param node Root node to search 'what' elements.
00555  *  \param what node name to search inside node.
00556  *  \retval 1 If a 'what' element is found inside 'node'.
00557  *  \retval 0 If no 'what' is found inside 'node'.
00558  */
00559 static int xmldoc_has_inside(struct ast_xml_node *fixnode, const char *what)
00560 {
00561    struct ast_xml_node *node = fixnode;
00562 
00563    for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
00564       if (!strcasecmp(ast_xml_node_get_name(node), what)) {
00565          return 1;
00566       }
00567    }
00568    return 0;
00569 }
00570 
00571 /*! \internal
00572  *  \brief Check if the passed node has at least one node inside it.
00573  *  \param node Root node to search node elements.
00574  *  \retval 1 If a node element is found inside 'node'.
00575  *  \retval 0 If no node is found inside 'node'.
00576  */
00577 static int xmldoc_has_nodes(struct ast_xml_node *fixnode)
00578 {
00579    struct ast_xml_node *node = fixnode;
00580 
00581    for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
00582       if (strcasecmp(ast_xml_node_get_name(node), "text")) {
00583          return 1;
00584       }
00585    }
00586    return 0;
00587 }
00588 
00589 /*! \internal
00590  *  \brief Check if the passed node has at least one specialtag.
00591  *  \param node Root node to search "specialtags" elements.
00592  *  \retval 1 If a "specialtag" element is found inside 'node'.
00593  *  \retval 0 If no "specialtag" is found inside 'node'.
00594  */
00595 static int xmldoc_has_specialtags(struct ast_xml_node *fixnode)
00596 {
00597    struct ast_xml_node *node = fixnode;
00598    int i;
00599 
00600    for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
00601       for (i = 0; i < ARRAY_LEN(special_tags); i++) {
00602          if (!strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) {
00603             return 1;
00604          }
00605       }
00606    }
00607    return 0;
00608 }
00609 
00610 /*! \internal
00611  *  \brief Build the syntax for a specified starting node.
00612  *  \param rootnode A pointer to the ast_xml root node.
00613  *  \param rootname Name of the application, function, option, etc. to build the syntax.
00614  *  \param childname The name of each parameter node.
00615  *  \param printparenthesis Boolean if we must print parenthesis if not parameters are found in the rootnode.
00616  *  \param printrootname Boolean if we must print the rootname before the syntax and parenthesis at the begining/end.
00617  *  \retval NULL on error.
00618  *  \retval An ast_malloc'ed string with the syntax generated.
00619  */
00620 static char *xmldoc_get_syntax_fun(struct ast_xml_node *rootnode, const char *rootname, const char *childname, int printparenthesis, int printrootname)
00621 {
00622 #define GOTONEXT(__rev, __a) (__rev ? ast_xml_node_get_prev(__a) : ast_xml_node_get_next(__a))
00623 #define ISLAST(__rev, __a)  (__rev == 1 ? (ast_xml_node_get_prev(__a) ? 0 : 1) : (ast_xml_node_get_next(__a) ? 0 : 1))
00624 #define MP(__a) ((multiple ? __a : ""))
00625    struct ast_xml_node *node = NULL, *firstparam = NULL, *lastparam = NULL;
00626    const char *paramtype, *multipletype, *paramnameattr, *attrargsep, *parenthesis, *argname;
00627    int reverse, required, paramcount = 0, openbrackets = 0, len = 0, hasparams=0;
00628    int reqfinode = 0, reqlanode = 0, optmidnode = 0, prnparenthesis, multiple;
00629    char *syntax = NULL, *argsep, *paramname;
00630 
00631    if (ast_strlen_zero(rootname) || ast_strlen_zero(childname)) {
00632       ast_log(LOG_WARNING, "Tried to look in XML tree with faulty rootname or childname while creating a syntax.\n");
00633       return NULL;
00634    }
00635 
00636    if (!rootnode || !ast_xml_node_get_children(rootnode)) {
00637       /* If the rootnode field is not found, at least print name. */
00638       ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
00639       return syntax;
00640    }
00641 
00642    /* Get the argument separator from the root node attribute name 'argsep', if not found
00643    defaults to ','. */
00644    attrargsep = ast_xml_get_attribute(rootnode, "argsep");
00645    if (attrargsep) {
00646       argsep = ast_strdupa(attrargsep);
00647       ast_xml_free_attr(attrargsep);
00648    } else {
00649       argsep = ast_strdupa(",");
00650    }
00651 
00652    /* Get order of evaluation. */
00653    for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) {
00654       if (strcasecmp(ast_xml_node_get_name(node), childname)) {
00655          continue;
00656       }
00657       required = 0;
00658       hasparams = 1;
00659       if ((paramtype = ast_xml_get_attribute(node, "required"))) {
00660          if (ast_true(paramtype)) {
00661             required = 1;
00662          }
00663          ast_xml_free_attr(paramtype);
00664       }
00665 
00666       lastparam = node;
00667       reqlanode = required;
00668 
00669       if (!firstparam) {
00670          /* first parameter node */
00671          firstparam = node;
00672          reqfinode = required;
00673       }
00674    }
00675 
00676    if (!hasparams) {
00677       /* This application, function, option, etc, doesn't have any params. */
00678       ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
00679       return syntax;
00680    }
00681 
00682    if (reqfinode && reqlanode) {
00683       /* check midnode */
00684       for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) {
00685          if (strcasecmp(ast_xml_node_get_name(node), childname)) {
00686             continue;
00687          }
00688          if (node != firstparam && node != lastparam) {
00689             if ((paramtype = ast_xml_get_attribute(node, "required"))) {
00690                if (!ast_true(paramtype)) {
00691                   optmidnode = 1;
00692                   break;
00693                }
00694                ast_xml_free_attr(paramtype);
00695             }
00696          }
00697       }
00698    }
00699 
00700    if ((!reqfinode && reqlanode) || (reqfinode && reqlanode && optmidnode)) {
00701       reverse = 1;
00702       node = lastparam;
00703    } else {
00704       reverse = 0;
00705       node = firstparam;
00706    }
00707 
00708    /* init syntax string. */
00709    if (reverse) {
00710       xmldoc_reverse_helper(reverse, &len, &syntax,
00711          (printrootname ? (printrootname == 2 ? ")]" : ")"): ""));
00712    } else {
00713       xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""),
00714          (printrootname ? (printrootname == 2 ? "[(" : "(") : ""));
00715    }
00716 
00717    for (; node; node = GOTONEXT(reverse, node)) {
00718       if (strcasecmp(ast_xml_node_get_name(node), childname)) {
00719          continue;
00720       }
00721 
00722       /* Get the argument name, if it is not the leaf, go inside that parameter. */
00723       if (xmldoc_has_inside(node, "argument")) {
00724          parenthesis = ast_xml_get_attribute(node, "hasparams");
00725          prnparenthesis = 0;
00726          if (parenthesis) {
00727             prnparenthesis = ast_true(parenthesis);
00728             if (!strcasecmp(parenthesis, "optional")) {
00729                prnparenthesis = 2;
00730             }
00731             ast_xml_free_attr(parenthesis);
00732          }
00733          argname = ast_xml_get_attribute(node, "name");
00734          if (argname) {
00735             paramname = xmldoc_get_syntax_fun(node, argname, "argument", prnparenthesis, prnparenthesis);
00736             ast_xml_free_attr(argname);
00737          } else {
00738             /* Malformed XML, print **UNKOWN** */
00739             paramname = ast_strdup("**unknown**");
00740          }
00741       } else {
00742          paramnameattr = ast_xml_get_attribute(node, "name");
00743          if (!paramnameattr) {
00744             ast_log(LOG_WARNING, "Malformed XML %s: no %s name\n", rootname, childname);
00745             if (syntax) {
00746                /* Free already allocated syntax */
00747                ast_free(syntax);
00748             }
00749             /* to give up is ok? */
00750             ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
00751             return syntax;
00752          }
00753          paramname = ast_strdup(paramnameattr);
00754          ast_xml_free_attr(paramnameattr);
00755       }
00756 
00757       /* Defaults to 'false'. */
00758       multiple = 0;
00759       if ((multipletype = ast_xml_get_attribute(node, "multiple"))) {
00760          if (ast_true(multipletype)) {
00761             multiple = 1;
00762          }
00763          ast_xml_free_attr(multipletype);
00764       }
00765 
00766       required = 0;  /* Defaults to 'false'. */
00767       if ((paramtype = ast_xml_get_attribute(node, "required"))) {
00768          if (ast_true(paramtype)) {
00769             required = 1;
00770          }
00771          ast_xml_free_attr(paramtype);
00772       }
00773 
00774       /* build syntax core. */
00775 
00776       if (required) {
00777          /* First parameter */
00778          if (!paramcount) {
00779             xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s", paramname, MP("["), MP(argsep), MP("...]"));
00780          } else {
00781             /* Time to close open brackets. */
00782             while (openbrackets > 0) {
00783                xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]"));
00784                openbrackets--;
00785             }
00786             if (reverse) {
00787                xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", paramname, argsep);
00788             } else {
00789                xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", argsep, paramname);
00790             }
00791             xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s", MP("["), MP(argsep), MP("...]"));
00792          }
00793       } else {
00794          /* First parameter */
00795          if (!paramcount) {
00796             xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]", paramname, MP("["), MP(argsep), MP("...]"));
00797          } else {
00798             if (ISLAST(reverse, node)) {
00799                /* This is the last parameter. */
00800                if (reverse) {
00801                   xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]%s", paramname,
00802                            MP("["), MP(argsep), MP("...]"), argsep);
00803                } else {
00804                   xmldoc_reverse_helper(reverse, &len, &syntax, "%s[%s%s%s%s]", argsep, paramname,
00805                            MP("["), MP(argsep), MP("...]"));
00806                }
00807             } else {
00808                if (reverse) {
00809                   xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s%s]", paramname, argsep,
00810                            MP("["), MP(argsep), MP("...]"));
00811                } else {
00812                   xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s%s", argsep, paramname,
00813                            MP("["), MP(argsep), MP("...]"));
00814                }
00815                openbrackets++;
00816             }
00817          }
00818       }
00819       ast_free(paramname);
00820 
00821       paramcount++;
00822    }
00823 
00824    /* Time to close open brackets. */
00825    while (openbrackets > 0) {
00826       xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]"));
00827       openbrackets--;
00828    }
00829 
00830    /* close syntax string. */
00831    if (reverse) {
00832       xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""),
00833          (printrootname ? (printrootname == 2 ? "[(" : "(") : ""));
00834    } else {
00835       xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")") : ""));
00836    }
00837 
00838    return syntax;
00839 #undef ISLAST
00840 #undef GOTONEXT
00841 #undef MP
00842 }
00843 
00844 /*! \internal
00845  *  \brief Parse an enumlist inside a <parameter> to generate a COMMAND
00846  *         syntax.
00847  *  \param fixnode A pointer to the <enumlist> node.
00848  *  \retval {<unknown>} on error.
00849  *  \retval A string inside brackets {} with the enum's separated by pipes |.
00850  */
00851 static char *xmldoc_parse_cmd_enumlist(struct ast_xml_node *fixnode)
00852 {
00853    struct ast_xml_node *node = fixnode;
00854    struct ast_str *paramname;
00855    char *enumname, *ret;
00856    int first = 1;
00857 
00858    paramname = ast_str_create(128);
00859    if (!paramname) {
00860       return ast_strdup("{<unkown>}");
00861    }
00862 
00863    ast_str_append(&paramname, 0, "{");
00864 
00865    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
00866       if (strcasecmp(ast_xml_node_get_name(node), "enum")) {
00867          continue;
00868       }
00869 
00870       enumname = xmldoc_get_syntax_cmd(node, "", 0);
00871       if (!enumname) {
00872          continue;
00873       }
00874       if (!first) {
00875          ast_str_append(&paramname, 0, "|");
00876       }
00877       ast_str_append(&paramname, 0, "%s", enumname);
00878       first = 0;
00879       ast_free(enumname);
00880    }
00881 
00882    ast_str_append(&paramname, 0, "}");
00883 
00884    ret = ast_strdup(ast_str_buffer(paramname));
00885    ast_free(paramname);
00886 
00887    return ret;
00888 }
00889 
00890 /*! \internal
00891  *  \brief Generate a syntax of COMMAND type.
00892  *  \param fixnode The <syntax> node pointer.
00893  *  \param name The name of the 'command'.
00894  *  \param printname Print the name of the command before the paramters?
00895  *  \retval On error, return just 'name'.
00896  *  \retval On success return the generated syntax.
00897  */
00898 static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *name, int printname)
00899 {
00900    struct ast_str *syntax;
00901    struct ast_xml_node *tmpnode, *node = fixnode;
00902    char *ret, *paramname;
00903    const char *paramtype, *attrname, *literal;
00904    int required, isenum, first = 1, isliteral;
00905 
00906    syntax = ast_str_create(128);
00907    if (!syntax) {
00908       /* at least try to return something... */
00909       return ast_strdup(name);
00910    }
00911 
00912    /* append name to output string. */
00913    if (printname) {
00914       ast_str_append(&syntax, 0, "%s", name);
00915       first = 0;
00916    }
00917 
00918    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
00919       if (strcasecmp(ast_xml_node_get_name(node), "parameter")) {
00920          continue;
00921       }
00922 
00923       if (xmldoc_has_inside(node, "parameter")) {
00924          /* is this a recursive parameter. */
00925          paramname = xmldoc_get_syntax_cmd(node, "", 0);
00926          isenum = 1;
00927       } else if (!xmldoc_has_inside(node, "enumlist")) {
00928          /* this is a simple parameter. */
00929          attrname = ast_xml_get_attribute(node, "name");
00930          if (!attrname) {
00931             /* ignore this bogus parameter and continue. */
00932             continue;
00933          }
00934          paramname = ast_strdup(attrname);
00935          ast_xml_free_attr(attrname);
00936          isenum = 0;
00937       } else {
00938          /* parse enumlist (note that this is a special enumlist
00939          that is used to describe a syntax like {<param1>|<param2>|...} */
00940          for (tmpnode = ast_xml_node_get_children(node); tmpnode; tmpnode = ast_xml_node_get_next(tmpnode)) {
00941             if (!strcasecmp(ast_xml_node_get_name(tmpnode), "enumlist")) {
00942                break;
00943             }
00944          }
00945          paramname = xmldoc_parse_cmd_enumlist(tmpnode);
00946          isenum = 1;
00947       }
00948 
00949       /* Is this parameter required? */
00950       required = 0;
00951       paramtype = ast_xml_get_attribute(node, "required");
00952       if (paramtype) {
00953          required = ast_true(paramtype);
00954          ast_xml_free_attr(paramtype);
00955       }
00956 
00957       /* Is this a replaceable value or a fixed parameter value? */
00958       isliteral = 0;
00959       literal = ast_xml_get_attribute(node, "literal");
00960       if (literal) {
00961          isliteral = ast_true(literal);
00962          ast_xml_free_attr(literal);
00963       }
00964 
00965       /* if required="false" print with [...].
00966        * if literal="true" or is enum print without <..>.
00967        * if not first print a space at the beginning.
00968        */
00969       ast_str_append(&syntax, 0, "%s%s%s%s%s%s",
00970             (first ? "" : " "),
00971             (required ? "" : "["),
00972             (isenum || isliteral ? "" : "<"),
00973             paramname,
00974             (isenum || isliteral ? "" : ">"),
00975             (required ? "" : "]"));
00976       first = 0;
00977       ast_free(paramname);
00978    }
00979 
00980    /* return a common string. */
00981    ret = ast_strdup(ast_str_buffer(syntax));
00982    ast_free(syntax);
00983 
00984    return ret;
00985 }
00986 
00987 /*! \brief Types of syntax that we are able to generate. */
00988 enum syntaxtype {
00989    FUNCTION_SYNTAX,
00990    COMMAND_SYNTAX
00991 };
00992 
00993 /*! \brief Mapping between type of node and type of syntax to generate. */
00994 struct strsyntaxtype {
00995    const char *type;
00996    enum syntaxtype stxtype;
00997 } stxtype[] = {
00998    { "function",     FUNCTION_SYNTAX   },
00999    { "application",  FUNCTION_SYNTAX   },
01000    { "agi",    COMMAND_SYNTAX }
01001 };
01002 
01003 /*! \internal
01004  *  \brief Get syntax type based on type of node.
01005  *  \param type Type of node.
01006  *  \retval The type of syntax to generate based on the type of node.
01007  */
01008 static enum syntaxtype xmldoc_get_syntax_type(const char *type)
01009 {
01010    int i;
01011    for (i=0; i < ARRAY_LEN(stxtype); i++) {
01012       if (!strcasecmp(stxtype[i].type, type)) {
01013          return stxtype[i].stxtype;
01014       }
01015    }
01016 
01017    return FUNCTION_SYNTAX;
01018 }
01019 
01020 char *ast_xmldoc_build_syntax(const char *type, const char *name)
01021 {
01022    struct ast_xml_node *node;
01023    char *syntax = NULL;
01024 
01025    node = xmldoc_get_node(type, name, documentation_language);
01026    if (!node) {
01027       return NULL;
01028    }
01029 
01030    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01031       if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) {
01032          break;
01033       }
01034    }
01035 
01036    if (node) {
01037       if (xmldoc_get_syntax_type(type) == FUNCTION_SYNTAX) {
01038          syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1);
01039       } else {
01040          syntax = xmldoc_get_syntax_cmd(node, name, 1);
01041       }
01042    }
01043    return syntax;
01044 }
01045 
01046 /*! \internal
01047  *  \brief Parse a <para> element.
01048  *  \param node The <para> element pointer.
01049  *  \param tabs Added this string before the content of the <para> element.
01050  *  \param posttabs Added this string after the content of the <para> element.
01051  *  \param buffer This must be an already allocated ast_str. It will be used
01052  *         to store the result (if already has something it will be appended to the current
01053  *         string).
01054  *  \retval 1 If 'node' is a named 'para'.
01055  *  \retval 2 If data is appended in buffer.
01056  *  \retval 0 on error.
01057  */
01058 static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer)
01059 {
01060    const char *tmptext;
01061    struct ast_xml_node *tmp;
01062    int ret = 0;
01063    struct ast_str *tmpstr;
01064 
01065    if (!node || !ast_xml_node_get_children(node)) {
01066       return ret;
01067    }
01068 
01069    if (strcasecmp(ast_xml_node_get_name(node), "para")) {
01070       return ret;
01071    }
01072 
01073    ast_str_append(buffer, 0, "%s", tabs);
01074 
01075    ret = 1;
01076 
01077    for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
01078       /* Get the text inside the <para> element and append it to buffer. */
01079       tmptext = ast_xml_get_text(tmp);
01080       if (tmptext) {
01081          /* Strip \n etc. */
01082          xmldoc_string_cleanup(tmptext, &tmpstr, 0);
01083          ast_xml_free_text(tmptext);
01084          if (tmpstr) {
01085             if (strcasecmp(ast_xml_node_get_name(tmp), "text")) {
01086                ast_str_append(buffer, 0, "<%s>%s</%s>", ast_xml_node_get_name(tmp),
01087                      ast_str_buffer(tmpstr), ast_xml_node_get_name(tmp));
01088             } else {
01089                ast_str_append(buffer, 0, "%s", ast_str_buffer(tmpstr));
01090             }
01091             ast_free(tmpstr);
01092             ret = 2;
01093          }
01094       }
01095    }
01096 
01097    ast_str_append(buffer, 0, "%s", posttabs);
01098 
01099    return ret;
01100 }
01101 
01102 /*! \internal
01103  *  \brief Parse special elements defined in 'struct special_tags' special elements must have a <para> element inside them.
01104  *  \param fixnode special tag node pointer.
01105  *  \param tabs put tabs before printing the node content.
01106  *  \param posttabs put posttabs after printing node content.
01107  *  \param buffer Output buffer, the special tags will be appended here.
01108  *  \retval 0 if no special element is parsed.
01109  *  \retval 1 if a special element is parsed (data is appended to buffer).
01110  *  \retval 2 if a special element is parsed and also a <para> element is parsed inside the specialtag.
01111  */
01112 static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer)
01113 {
01114    struct ast_xml_node *node = fixnode;
01115    int ret = 0, i, count = 0;
01116 
01117    if (!node || !ast_xml_node_get_children(node)) {
01118       return ret;
01119    }
01120 
01121    for (i = 0; i < ARRAY_LEN(special_tags); i++) {
01122       if (strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) {
01123          continue;
01124       }
01125 
01126       ret = 1;
01127       /* This is a special tag. */
01128 
01129       /* concat data */
01130       if (!ast_strlen_zero(special_tags[i].init)) {
01131          ast_str_append(buffer, 0, "%s%s", tabs, special_tags[i].init);
01132       }
01133 
01134       /* parse <para> elements inside special tags. */
01135       for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01136          /* first <para> just print it without tabs at the begining. */
01137          if (xmldoc_parse_para(node, (!count ? "" : tabs), posttabs, buffer) == 2) {
01138             ret = 2;
01139          }
01140       }
01141 
01142       if (!ast_strlen_zero(special_tags[i].end)) {
01143          ast_str_append(buffer, 0, "%s%s", special_tags[i].end, posttabs);
01144       }
01145 
01146       break;
01147    }
01148 
01149    return ret;
01150 }
01151 
01152 /*! \internal
01153  *  \brief Parse an <argument> element from the xml documentation.
01154  *  \param fixnode Pointer to the 'argument' xml node.
01155  *  \param insideparameter If we are parsing an <argument> inside a <parameter>.
01156  *  \param paramtabs pre tabs if we are inside a parameter element.
01157  *  \param tabs What to be printed before the argument name.
01158  *  \param buffer Output buffer to put values found inside the <argument> element.
01159  *  \retval 1 If there is content inside the argument.
01160  *  \retval 0 If the argument element is not parsed, or there is no content inside it.
01161  */
01162 static int xmldoc_parse_argument(struct ast_xml_node *fixnode, int insideparameter, const char *paramtabs, const char *tabs, struct ast_str **buffer)
01163 {
01164    struct ast_xml_node *node = fixnode;
01165    const char *argname;
01166    int count = 0, ret = 0;
01167 
01168    if (!node || !ast_xml_node_get_children(node)) {
01169       return ret;
01170    }
01171 
01172    /* Print the argument names */
01173    argname = ast_xml_get_attribute(node, "name");
01174    if (!argname) {
01175       return 0;
01176    }
01177    if (xmldoc_has_inside(node, "para") || xmldoc_has_specialtags(node)) {
01178       ast_str_append(buffer, 0, "%s%s%s", tabs, argname, (insideparameter ? "\n" : ""));
01179       ast_xml_free_attr(argname);
01180    } else {
01181       ast_xml_free_attr(argname);
01182       return 0;
01183    }
01184 
01185    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01186       if (xmldoc_parse_para(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) {
01187          count++;
01188          ret = 1;
01189       } else if (xmldoc_parse_specialtags(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) {
01190          count++;
01191          ret = 1;
01192       }
01193    }
01194 
01195    return ret;
01196 }
01197 
01198 /*! \internal
01199  *  \brief Parse a <variable> node inside a <variablelist> node.
01200  *  \param node The variable node to parse.
01201  *  \param tabs A string to be appended at the begining of the output that will be stored
01202  *         in buffer.
01203  *  \param buffer This must be an already created ast_str. It will be used
01204  *         to store the result (if already has something it will be appended to the current
01205  *         string).
01206  *  \retval 0 if no data is appended.
01207  *  \retval 1 if data is appended.
01208  */
01209 static int xmldoc_parse_variable(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer)
01210 {
01211    struct ast_xml_node *tmp;
01212    const char *valname;
01213    const char *tmptext;
01214    struct ast_str *cleanstr;
01215    int ret = 0, printedpara=0;
01216 
01217    for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
01218       if (xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer)) {
01219          printedpara = 1;
01220          continue;
01221       } else if (xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer)) {
01222          printedpara = 1;
01223          continue;
01224       }
01225 
01226       if (strcasecmp(ast_xml_node_get_name(tmp), "value")) {
01227          continue;
01228       }
01229 
01230       /* Parse a <value> tag only. */
01231       if (!printedpara) {
01232          ast_str_append(buffer, 0, "\n");
01233          printedpara = 1;
01234       }
01235       /* Parse each <value name='valuename'>desciption</value> */
01236       valname = ast_xml_get_attribute(tmp, "name");
01237       if (valname) {
01238          ret = 1;
01239          ast_str_append(buffer, 0, "%s<value>%s</value>", tabs, valname);
01240          ast_xml_free_attr(valname);
01241       }
01242       tmptext = ast_xml_get_text(tmp);
01243       /* Check inside this node for any explanation about its meaning. */
01244       if (tmptext) {
01245          /* Cleanup text. */
01246          xmldoc_string_cleanup(tmptext, &cleanstr, 1);
01247          ast_xml_free_text(tmptext);
01248          if (cleanstr && ast_str_strlen(cleanstr) > 0) {
01249             ast_str_append(buffer, 0, ":%s", ast_str_buffer(cleanstr));
01250          }
01251          ast_free(cleanstr);
01252       }
01253       ast_str_append(buffer, 0, "\n");
01254    }
01255 
01256    return ret;
01257 }
01258 
01259 /*! \internal
01260  *  \brief Parse a <variablelist> node and put all the output inside 'buffer'.
01261  *  \param node The variablelist node pointer.
01262  *  \param tabs A string to be appended at the begining of the output that will be stored
01263  *         in buffer.
01264  *  \param buffer This must be an already created ast_str. It will be used
01265  *         to store the result (if already has something it will be appended to the current
01266  *         string).
01267  *  \retval 1 If a <variablelist> element is parsed.
01268  *  \retval 0 On error.
01269  */
01270 static int xmldoc_parse_variablelist(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer)
01271 {
01272    struct ast_xml_node *tmp;
01273    const char *varname;
01274    char *vartabs;
01275    int ret = 0;
01276 
01277    if (!node || !ast_xml_node_get_children(node)) {
01278       return ret;
01279    }
01280 
01281    if (strcasecmp(ast_xml_node_get_name(node), "variablelist")) {
01282       return ret;
01283    }
01284 
01285    /* use this spacing (add 4 spaces) inside a variablelist node. */
01286    ast_asprintf(&vartabs, "%s    ", tabs);
01287    if (!vartabs) {
01288       return ret;
01289    }
01290    for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
01291       /* We can have a <para> element inside the variable list */
01292       if ((xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer))) {
01293          ret = 1;
01294          continue;
01295       } else if ((xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer))) {
01296          ret = 1;
01297          continue;
01298       }
01299 
01300       if (!strcasecmp(ast_xml_node_get_name(tmp), "variable")) {
01301          /* Store the variable name in buffer. */
01302          varname = ast_xml_get_attribute(tmp, "name");
01303          if (varname) {
01304             ast_str_append(buffer, 0, "%s<variable>%s</variable>: ", tabs, varname);
01305             ast_xml_free_attr(varname);
01306             /* Parse the <variable> possible values. */
01307             xmldoc_parse_variable(tmp, vartabs, buffer);
01308             ret = 1;
01309          }
01310       }
01311    }
01312 
01313    ast_free(vartabs);
01314 
01315    return ret;
01316 }
01317 
01318 char *ast_xmldoc_build_seealso(const char *type, const char *name)
01319 {
01320    struct ast_str *outputstr;
01321    char *output;
01322    struct ast_xml_node *node;
01323    const char *typename;
01324    const char *content;
01325    int first = 1;
01326 
01327    if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
01328       return NULL;
01329    }
01330 
01331    /* get the application/function root node. */
01332    node = xmldoc_get_node(type, name, documentation_language);
01333    if (!node || !ast_xml_node_get_children(node)) {
01334       return NULL;
01335    }
01336 
01337    /* Find the <see-also> node. */
01338    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01339       if (!strcasecmp(ast_xml_node_get_name(node), "see-also")) {
01340          break;
01341       }
01342    }
01343 
01344    if (!node || !ast_xml_node_get_children(node)) {
01345       /* we couldnt find a <see-also> node. */
01346       return NULL;
01347    }
01348 
01349    /* prepare the output string. */
01350    outputstr = ast_str_create(128);
01351    if (!outputstr) {
01352       return NULL;
01353    }
01354 
01355    /* get into the <see-also> node. */
01356    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01357       if (strcasecmp(ast_xml_node_get_name(node), "ref")) {
01358          continue;
01359       }
01360 
01361       /* parse the <ref> node. 'type' attribute is required. */
01362       typename = ast_xml_get_attribute(node, "type");
01363       if (!typename) {
01364          continue;
01365       }
01366       content = ast_xml_get_text(node);
01367       if (!content) {
01368          ast_xml_free_attr(typename);
01369          continue;
01370       }
01371       if (!strcasecmp(typename, "application")) {
01372          ast_str_append(&outputstr, 0, "%s%s()",   (first ? "" : ", "), content);
01373       } else if (!strcasecmp(typename, "function")) {
01374          ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content);
01375       } else if (!strcasecmp(typename, "astcli")) {
01376          ast_str_append(&outputstr, 0, "%s<astcli>%s</astcli>", (first ? "" : ", "), content);
01377       } else {
01378          ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content);
01379       }
01380       first = 0;
01381       ast_xml_free_text(content);
01382       ast_xml_free_attr(typename);
01383    }
01384 
01385    output = ast_strdup(ast_str_buffer(outputstr));
01386    ast_free(outputstr);
01387 
01388    return output;
01389 }
01390 
01391 /*! \internal
01392  *  \brief Parse a <enum> node.
01393  *  \brief fixnode An ast_xml_node pointer to the <enum> node.
01394  *  \bried buffer The output buffer.
01395  *  \retval 0 if content is not found inside the enum element (data is not appended to buffer).
01396  *  \retval 1 if content is found and data is appended to buffer.
01397  */
01398 static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
01399 {
01400    struct ast_xml_node *node = fixnode;
01401    int ret = 0;
01402    char *optiontabs;
01403 
01404    ast_asprintf(&optiontabs, "%s    ", tabs);
01405 
01406    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01407       if ((xmldoc_parse_para(node, (ret ? tabs : " - "), "\n", buffer))) {
01408          ret = 1;
01409       } else if ((xmldoc_parse_specialtags(node, (ret ? tabs : " - "), "\n", buffer))) {
01410          ret = 1;
01411       }
01412 
01413       xmldoc_parse_enumlist(node, optiontabs, buffer);
01414    }
01415 
01416    ast_free(optiontabs);
01417 
01418    return ret;
01419 }
01420 
01421 /*! \internal
01422  *  \brief Parse a <enumlist> node.
01423  *  \param fixnode As ast_xml pointer to the <enumlist> node.
01424  *  \param buffer The ast_str output buffer.
01425  *  \retval 0 if no <enumlist> node was parsed.
01426  *  \retval 1 if a <enumlist> node was parsed.
01427  */
01428 static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
01429 {
01430    struct ast_xml_node *node = fixnode;
01431    const char *enumname;
01432    int ret = 0;
01433 
01434    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01435       if (strcasecmp(ast_xml_node_get_name(node), "enum")) {
01436          continue;
01437       }
01438 
01439       enumname = ast_xml_get_attribute(node, "name");
01440       if (enumname) {
01441          ast_str_append(buffer, 0, "%s<enum>%s</enum>", tabs, enumname);
01442          ast_xml_free_attr(enumname);
01443 
01444          /* parse only enum elements inside a enumlist node. */
01445          if ((xmldoc_parse_enum(node, tabs, buffer))) {
01446             ret = 1;
01447          } else {
01448             ast_str_append(buffer, 0, "\n");
01449          }
01450       }
01451    }
01452    return ret;
01453 }
01454 
01455 /*! \internal
01456  *  \brief Parse an <option> node.
01457  *  \param fixnode An ast_xml pointer to the <option> node.
01458  *  \param tabs A string to be appended at the begining of each line being added to the
01459  *              buffer string.
01460  *  \param buffer The output buffer.
01461  *  \retval 0 if no option node is parsed.
01462  *  \retval 1 if an option node is parsed.
01463  */
01464 static int xmldoc_parse_option(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
01465 {
01466    struct ast_xml_node *node;
01467    int ret = 0;
01468    char *optiontabs;
01469 
01470    ast_asprintf(&optiontabs, "%s    ", tabs);
01471    if (!optiontabs) {
01472       return ret;
01473    }
01474    for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
01475       if (!strcasecmp(ast_xml_node_get_name(node), "argument")) {
01476          /* if this is the first data appended to buffer, print a \n*/
01477          if (!ret && ast_xml_node_get_children(node)) {
01478             /* print \n */
01479             ast_str_append(buffer, 0, "\n");
01480          }
01481          if (xmldoc_parse_argument(node, 0, NULL, optiontabs, buffer)) {
01482             ret = 1;
01483          }
01484          continue;
01485       }
01486 
01487       if (xmldoc_parse_para(node, (ret ? tabs :  ""), "\n", buffer)) {
01488          ret = 1;
01489       } else if (xmldoc_parse_specialtags(node, (ret ? tabs :  ""), "\n", buffer)) {
01490          ret = 1;
01491       }
01492 
01493       xmldoc_parse_variablelist(node, optiontabs, buffer);
01494 
01495       xmldoc_parse_enumlist(node, optiontabs, buffer);
01496    }
01497    ast_free(optiontabs);
01498 
01499    return ret;
01500 }
01501 
01502 /*! \internal
01503  *  \brief Parse an <optionlist> element from the xml documentation.
01504  *  \param fixnode Pointer to the optionlist xml node.
01505  *  \param tabs A string to be appended at the begining of each line being added to the
01506  *              buffer string.
01507  *  \param buffer Output buffer to put what is inside the optionlist tag.
01508  */
01509 static void xmldoc_parse_optionlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
01510 {
01511    struct ast_xml_node *node;
01512    const char *optname, *hasparams;
01513    char *optionsyntax;
01514    int optparams;
01515 
01516    for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
01517       /* Start appending every option tag. */
01518       if (strcasecmp(ast_xml_node_get_name(node), "option")) {
01519          continue;
01520       }
01521 
01522       /* Get the option name. */
01523       optname = ast_xml_get_attribute(node, "name");
01524       if (!optname) {
01525          continue;
01526       }
01527 
01528       optparams = 1;
01529       hasparams = ast_xml_get_attribute(node, "hasparams");
01530       if (hasparams && !strcasecmp(hasparams, "optional")) {
01531          optparams = 2;
01532       }
01533 
01534       optionsyntax = xmldoc_get_syntax_fun(node, optname, "argument", 0, optparams);
01535       if (!optionsyntax) {
01536          ast_xml_free_attr(optname);
01537          ast_xml_free_attr(hasparams);
01538          continue;
01539       }
01540 
01541       ast_str_append(buffer, 0, "%s%s: ", tabs, optionsyntax);
01542 
01543       if (!xmldoc_parse_option(node, tabs, buffer)) {
01544          ast_str_append(buffer, 0, "\n");
01545       }
01546       ast_xml_free_attr(optname);
01547       ast_xml_free_attr(hasparams);
01548    }
01549 }
01550 
01551 /*! \internal
01552  *  \brief Parse a 'parameter' tag inside a syntax element.
01553  *  \param fixnode A pointer to the 'parameter' xml node.
01554  *  \param tabs A string to be appended at the beginning of each line being printed inside
01555  *              'buffer'.
01556  *  \param buffer String buffer to put values found inside the parameter element.
01557  */
01558 static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
01559 {
01560    const char *paramname;
01561    struct ast_xml_node *node = fixnode;
01562    int hasarguments, printed = 0;
01563    char *internaltabs;
01564 
01565    if (strcasecmp(ast_xml_node_get_name(node), "parameter")) {
01566       return;
01567    }
01568 
01569    hasarguments = xmldoc_has_inside(node, "argument");
01570    if (!(paramname = ast_xml_get_attribute(node, "name"))) {
01571       /* parameter MUST have an attribute name. */
01572       return;
01573    }
01574 
01575    ast_asprintf(&internaltabs, "%s    ", tabs);
01576    if (!internaltabs) {
01577       return;
01578    }
01579 
01580    if (!hasarguments && xmldoc_has_nodes(node)) {
01581       ast_str_append(buffer, 0, "%s\n", paramname);
01582       ast_xml_free_attr(paramname);
01583       printed = 1;
01584    }
01585 
01586    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01587       if (!strcasecmp(ast_xml_node_get_name(node), "optionlist")) {
01588          xmldoc_parse_optionlist(node, internaltabs, buffer);
01589       } else if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) {
01590          xmldoc_parse_enumlist(node, internaltabs, buffer);
01591       } else if (!strcasecmp(ast_xml_node_get_name(node), "argument")) {
01592          xmldoc_parse_argument(node, 1, internaltabs, (!hasarguments ? "        " : ""), buffer);
01593       } else if (!strcasecmp(ast_xml_node_get_name(node), "para")) {
01594          if (!printed) {
01595             ast_str_append(buffer, 0, "%s\n", paramname);
01596             ast_xml_free_attr(paramname);
01597             printed = 1;
01598          }
01599          xmldoc_parse_para(node, internaltabs, "\n", buffer);
01600          continue;
01601       } else if ((xmldoc_parse_specialtags(node, internaltabs, "\n", buffer))) {
01602          continue;
01603       }
01604    }
01605    if (!printed) {
01606       ast_xml_free_attr(paramname);
01607    }
01608    ast_free(internaltabs);
01609 }
01610 
01611 char *ast_xmldoc_build_arguments(const char *type, const char *name)
01612 {
01613    struct ast_xml_node *node;
01614    struct ast_str *ret = ast_str_create(128);
01615    char *retstr = NULL;
01616 
01617    if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
01618       return NULL;
01619    }
01620 
01621    node = xmldoc_get_node(type, name, documentation_language);
01622 
01623    if (!node || !ast_xml_node_get_children(node)) {
01624       return NULL;
01625    }
01626 
01627    /* Find the syntax field. */
01628    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01629       if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) {
01630          break;
01631       }
01632    }
01633 
01634    if (!node || !ast_xml_node_get_children(node)) {
01635       /* We couldn't find the syntax node. */
01636       return NULL;
01637    }
01638 
01639    for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
01640       xmldoc_parse_parameter(node, "", &ret);
01641    }
01642 
01643    if (ast_str_strlen(ret) > 0) {
01644       /* remove last '\n' */
01645       char *buf = ast_str_buffer(ret);
01646       if (buf[ast_str_strlen(ret) - 1] == '\n') {
01647          ast_str_truncate(ret, -1);
01648       }
01649       retstr = ast_strdup(ast_str_buffer(ret));
01650    }
01651    ast_free(ret);
01652 
01653    return retstr;
01654 }
01655 
01656 /*! \internal
01657  *  \brief Return the string within a node formatted with <para> and <variablelist> elements.
01658  *  \param node Parent node where content resides.
01659  *  \param raw If set, return the node's content without further processing.
01660  *  \param raw_wrap Wrap raw text.
01661  *  \retval NULL on error
01662  *  \retval Node content on success.
01663  */
01664 static struct ast_str *xmldoc_get_formatted(struct ast_xml_node *node, int raw_output, int raw_wrap)
01665 {
01666    struct ast_xml_node *tmp;
01667    const char *notcleanret, *tmpstr;
01668    struct ast_str *ret = ast_str_create(128);
01669 
01670    if (raw_output) {
01671       notcleanret = ast_xml_get_text(node);
01672       tmpstr = notcleanret;
01673       xmldoc_string_cleanup(ast_skip_blanks(notcleanret), &ret, 0);
01674       ast_xml_free_text(tmpstr);
01675    } else {
01676       for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
01677          /* if found, parse a <para> element. */
01678          if (xmldoc_parse_para(tmp, "", "\n", &ret)) {
01679             continue;
01680          } else if (xmldoc_parse_specialtags(tmp, "", "\n", &ret)) {
01681             continue;
01682          }
01683          /* if found, parse a <variablelist> element. */
01684          xmldoc_parse_variablelist(tmp, "", &ret);
01685          xmldoc_parse_enumlist(tmp, "    ", &ret);
01686       }
01687       /* remove last '\n' */
01688       /* XXX Don't modify ast_str internals manually */
01689       tmpstr = ast_str_buffer(ret);
01690       if (tmpstr[ast_str_strlen(ret) - 1] == '\n') {
01691          ast_str_truncate(ret, -1);
01692       }
01693    }
01694    return ret;
01695 }
01696 
01697 /*!
01698  *  \brief Get the content of a field (synopsis, description, etc) from an asterisk document tree
01699  *  \param type Type of element (application, function, ...).
01700  *  \param name Name of element (Dial, Echo, Playback, ...).
01701  *  \param var Name of field to return (synopsis, description, etc).
01702  *  \param raw Field only contains text, no other elements inside it.
01703  *  \retval NULL On error.
01704  *  \retval Field text content on success.
01705  */
01706 static char *xmldoc_build_field(const char *type, const char *name, const char *var, int raw)
01707 {
01708    struct ast_xml_node *node;
01709    char *ret = NULL;
01710    struct ast_str *formatted;
01711 
01712    if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
01713       ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n");
01714       return ret;
01715    }
01716 
01717    node = xmldoc_get_node(type, name, documentation_language);
01718 
01719    if (!node) {
01720       ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation\n", type, name);
01721       return ret;
01722    }
01723 
01724    node = ast_xml_find_element(ast_xml_node_get_children(node), var, NULL, NULL);
01725 
01726    if (!node || !ast_xml_node_get_children(node)) {
01727       ast_log(LOG_DEBUG, "Cannot find variable '%s' in tree '%s'\n", var, name);
01728       return ret;
01729    }
01730 
01731    formatted = xmldoc_get_formatted(node, raw, raw);
01732    if (ast_str_strlen(formatted) > 0) {
01733       ret = ast_strdup(ast_str_buffer(formatted));
01734    }
01735    ast_free(formatted);
01736 
01737    return ret;
01738 }
01739 
01740 char *ast_xmldoc_build_synopsis(const char *type, const char *name)
01741 {
01742    return xmldoc_build_field(type, name, "synopsis", 1);
01743 }
01744 
01745 char *ast_xmldoc_build_description(const char *type, const char *name)
01746 {
01747    return xmldoc_build_field(type, name, "description", 0);
01748 }
01749 
01750 #if !defined(HAVE_GLOB_NOMAGIC) || !defined(HAVE_GLOB_BRACE) || defined(DEBUG_NONGNU)
01751 static int xml_pathmatch(char *xmlpattern, int xmlpattern_maxlen, glob_t *globbuf)
01752 {
01753    int globret;
01754 
01755    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/thirdparty/*-%s.xml",
01756       ast_config_AST_DATA_DIR, documentation_language);
01757    if((globret = glob(xmlpattern, GLOB_NOCHECK, NULL, globbuf))) {
01758       return globret;
01759    }
01760 
01761    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/thirdparty/*-%.2s_??.xml",
01762       ast_config_AST_DATA_DIR, documentation_language);
01763    if((globret = glob(xmlpattern, GLOB_APPEND | GLOB_NOCHECK, NULL, globbuf))) {
01764       return globret;
01765    }
01766 
01767    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/thirdparty/*-%s.xml",
01768       ast_config_AST_DATA_DIR, default_documentation_language);
01769    if((globret = glob(xmlpattern, GLOB_APPEND | GLOB_NOCHECK, NULL, globbuf))) {
01770       return globret;
01771    }
01772 
01773    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/*-%s.xml",
01774       ast_config_AST_DATA_DIR, documentation_language);
01775    if((globret = glob(xmlpattern, GLOB_APPEND | GLOB_NOCHECK, NULL, globbuf))) {
01776       return globret;
01777    }
01778 
01779    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/*-%.2s_??.xml",
01780       ast_config_AST_DATA_DIR, documentation_language);
01781    if((globret = glob(xmlpattern, GLOB_APPEND | GLOB_NOCHECK, NULL, globbuf))) {
01782       return globret;
01783    }
01784 
01785    snprintf(xmlpattern, xmlpattern_maxlen, "%s/documentation/*-%s.xml",
01786       ast_config_AST_DATA_DIR, default_documentation_language);
01787    globret = glob(xmlpattern, GLOB_APPEND | GLOB_NOCHECK, NULL, globbuf);
01788 
01789    return globret;
01790 }
01791 #endif
01792 
01793 /*! \brief Close and unload XML documentation. */
01794 static void xmldoc_unload_documentation(void)
01795 {
01796         struct documentation_tree *doctree;
01797 
01798    AST_RWLIST_WRLOCK(&xmldoc_tree);
01799    while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
01800       ast_free(doctree->filename);
01801       ast_xml_close(doctree->doc);
01802    }
01803    AST_RWLIST_UNLOCK(&xmldoc_tree);
01804 
01805    ast_xml_finish();
01806 }
01807 
01808 int ast_xmldoc_load_documentation(void)
01809 {
01810    struct ast_xml_node *root_node;
01811    struct ast_xml_doc *tmpdoc;
01812    struct documentation_tree *doc_tree;
01813    char *xmlpattern;
01814    struct ast_config *cfg = NULL;
01815    struct ast_variable *var = NULL;
01816    struct ast_flags cnfflags = { 0 };
01817    int globret, i, dup, duplicate;
01818    glob_t globbuf;
01819 #if !defined(HAVE_GLOB_NOMAGIC) || !defined(HAVE_GLOB_BRACE) || defined(DEBUG_NONGNU)
01820    int xmlpattern_maxlen;
01821 #endif
01822 
01823    /* setup default XML documentation language */
01824    snprintf(documentation_language, sizeof(documentation_language), default_documentation_language);
01825 
01826    if ((cfg = ast_config_load2("asterisk.conf", "" /* core can't reload */, cnfflags)) && cfg != CONFIG_STATUS_FILEINVALID) {
01827       for (var = ast_variable_browse(cfg, "options"); var; var = var->next) {
01828          if (!strcasecmp(var->name, "documentation_language")) {
01829             if (!ast_strlen_zero(var->value)) {
01830                snprintf(documentation_language, sizeof(documentation_language), "%s", var->value);
01831             }
01832          }
01833       }
01834       ast_config_destroy(cfg);
01835    }
01836 
01837    /* initialize the XML library. */
01838    ast_xml_init();
01839 
01840    /* register function to be run when asterisk finish. */
01841    ast_register_atexit(xmldoc_unload_documentation);
01842 
01843    globbuf.gl_offs = 0;    /* slots to reserve in gl_pathv */
01844 
01845 #if !defined(HAVE_GLOB_NOMAGIC) || !defined(HAVE_GLOB_BRACE) || defined(DEBUG_NONGNU)
01846    xmlpattern_maxlen = strlen(ast_config_AST_DATA_DIR) + strlen("/documentation/thirdparty") + strlen("/*-??_??.xml") + 1;
01847    xmlpattern = ast_malloc(xmlpattern_maxlen);
01848    globret = xml_pathmatch(xmlpattern, xmlpattern_maxlen, &globbuf);
01849 #else
01850    /* Get every *-LANG.xml file inside $(ASTDATADIR)/documentation */
01851    ast_asprintf(&xmlpattern, "%s/documentation{/thirdparty/,/}*-{%s,%.2s_??,%s}.xml", ast_config_AST_DATA_DIR,
01852       documentation_language, documentation_language, default_documentation_language);
01853    globret = glob(xmlpattern, MY_GLOB_FLAGS, NULL, &globbuf);
01854 #endif
01855 
01856    ast_debug(3, "gl_pathc %zd\n", globbuf.gl_pathc);
01857    if (globret == GLOB_NOSPACE) {
01858       ast_log(LOG_WARNING, "XML load failure, glob expansion of pattern '%s' failed: Not enough memory\n", xmlpattern);
01859       ast_free(xmlpattern);
01860       return 1;
01861    } else if (globret  == GLOB_ABORTED) {
01862       ast_log(LOG_WARNING, "XML load failure, glob expansion of pattern '%s' failed: Read error\n", xmlpattern);
01863       ast_free(xmlpattern);
01864       return 1;
01865    }
01866    ast_free(xmlpattern);
01867 
01868    AST_RWLIST_WRLOCK(&xmldoc_tree);
01869    /* loop over expanded files */
01870    for (i = 0; i < globbuf.gl_pathc; i++) {
01871       /* check for duplicates (if we already [try to] open the same file. */
01872       duplicate = 0;
01873       for (dup = 0; dup < i; dup++) {
01874          if (!strcmp(globbuf.gl_pathv[i], globbuf.gl_pathv[dup])) {
01875             duplicate = 1;
01876             break;
01877          }
01878       }
01879       if (duplicate || strchr(globbuf.gl_pathv[i], '*')) {
01880       /* skip duplicates as well as pathnames not found 
01881        * (due to use of GLOB_NOCHECK in xml_pathmatch) */
01882          continue;
01883       }
01884       tmpdoc = NULL;
01885       tmpdoc = ast_xml_open(globbuf.gl_pathv[i]);
01886       if (!tmpdoc) {
01887          ast_log(LOG_ERROR, "Could not open XML documentation at '%s'\n", globbuf.gl_pathv[i]);
01888          continue;
01889       }
01890       /* Get doc root node and check if it starts with '<docs>' */
01891       root_node = ast_xml_get_root(tmpdoc);
01892       if (!root_node) {
01893          ast_log(LOG_ERROR, "Error getting documentation root node");
01894          ast_xml_close(tmpdoc);
01895          continue;
01896       }
01897       /* Check root node name for malformed xmls. */
01898       if (strcmp(ast_xml_node_get_name(root_node), "docs")) {
01899          ast_log(LOG_ERROR, "Documentation file is not well formed!\n");
01900          ast_xml_close(tmpdoc);
01901          continue;
01902       }
01903       doc_tree = ast_calloc(1, sizeof(*doc_tree));
01904       if (!doc_tree) {
01905          ast_log(LOG_ERROR, "Unable to allocate documentation_tree structure!\n");
01906          ast_xml_close(tmpdoc);
01907          continue;
01908       }
01909       doc_tree->doc = tmpdoc;
01910       doc_tree->filename = ast_strdup(globbuf.gl_pathv[i]);
01911       AST_RWLIST_INSERT_TAIL(&xmldoc_tree, doc_tree, entry);
01912    }
01913    AST_RWLIST_UNLOCK(&xmldoc_tree);
01914 
01915    globfree(&globbuf);
01916 
01917    return 0;
01918 }
01919 
01920 #endif /* AST_XML_DOCS */
01921 
01922