5921960c4ee0db83ca23d3cd82eb89dfa860c2c8
[asterisk/asterisk.git] / main / xmldoc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008, Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16
17 /*! \file
18  *
19  * \brief XML Documentation API
20  *
21  * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
22  */
23
24 #include "asterisk.h"
25
26 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
27
28 #include "asterisk/_private.h"
29 #include "asterisk/paths.h"
30 #include "asterisk/linkedlists.h"
31 #include "asterisk/strings.h"
32 #include "asterisk/config.h"
33 #include "asterisk/term.h"
34 #include "asterisk/xmldoc.h"
35
36 #ifdef AST_XML_DOCS
37
38 /*! \brief Default documentation language. */
39 static const char default_documentation_language[] = "en_US";
40
41 /*! \brief Number of columns to print when showing the XML documentation with a
42  *         'core show application/function *' CLI command. Used in text wrapping.*/
43 static const int xmldoc_text_columns = 74;
44
45 /*! \brief This is a value that we will use to let the wrapping mechanism move the cursor
46  *         backward and forward xmldoc_max_diff positions before cutting the middle of a
47  *         word, trying to find a space or a \n. */
48 static const int xmldoc_max_diff = 5;
49
50 /*! \brief XML documentation language. */
51 static char documentation_language[6];
52
53 /*! \brief XML documentation tree */
54 struct documentation_tree {
55         char *filename;                                 /*!< XML document filename. */
56         struct ast_xml_doc *doc;                        /*!< Open document pointer. */
57         AST_RWLIST_ENTRY(documentation_tree) entry;
58 };
59
60 static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *name, int printname);
61
62 /*!
63  * \brief Container of documentation trees
64  *
65  * \note A RWLIST is a sufficient container type to use here for now.
66  *       However, some changes will need to be made to implement ref counting
67  *       if reload support is added in the future.
68  */
69 static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree);
70
71 static const struct strcolorized_tags {
72         const char *init;      /*!< Replace initial tag with this string. */
73         const char *end;       /*!< Replace end tag with this string. */
74         const int colorfg;     /*!< Foreground color. */
75         const char *inittag;   /*!< Initial tag description. */
76         const char *endtag;    /*!< Ending tag description. */
77 } colorized_tags[] = {
78         { "<",  ">",  COLOR_GREEN,  "<replaceable>", "</replaceable>" },
79         { "\'", "\'", COLOR_BLUE,   "<literal>",     "</literal>" },
80         { "*",  "*",  COLOR_RED,    "<emphasis>",    "</emphasis>" },
81         { "\"", "\"", COLOR_YELLOW, "<filename>",    "</filename>" },
82         { "\"", "\"", COLOR_CYAN,   "<directory>",   "</directory>" },
83         { "${", "}",  COLOR_GREEN,  "<variable>",    "</variable>" },
84         { "",   "",   COLOR_BLUE,   "<value>",       "</value>" },
85         { "",   "",   COLOR_BLUE,   "<enum>",        "</enum>" },
86         { "\'", "\'", COLOR_GRAY,   "<astcli>",      "</astcli>" },
87
88         /* Special tags */
89         { "", "", COLOR_YELLOW, "<note>",   "</note>" },
90         { "", "", COLOR_RED,   "<warning>", "</warning>" }
91 };
92
93 static const struct strspecial_tags {
94         const char *tagname;            /*!< Special tag name. */
95         const char *init;               /*!< Print this at the beginning. */
96         const char *end;                /*!< Print this at the end. */
97 } special_tags[] = {
98         { "note",    "<note>NOTE:</note> ",             "" },
99         { "warning", "<warning>WARNING!!!:</warning> ", "" }
100 };
101
102 /*! \internal
103  *  \brief Calculate the space in bytes used by a format string
104  *         that will be passed to a sprintf function.
105  *  \param postbr The format string to use to calculate the length.
106  *  \retval The postbr length.
107  */
108 static int xmldoc_postbrlen(const char *postbr)
109 {
110         int postbrreallen = 0, i;
111         size_t postbrlen;
112
113         if (!postbr) {
114                 return 0;
115         }
116         postbrlen = strlen(postbr);
117         for (i = 0; i < postbrlen; i++) {
118                 if (postbr[i] == '\t') {
119                         postbrreallen += 8 - (postbrreallen % 8);
120                 } else {
121                         postbrreallen++;
122                 }
123         }
124         return postbrreallen;
125 }
126
127 /*! \internal
128  *  \brief Setup postbr to be used while wrapping the text.
129  *         Add to postbr array all the spaces and tabs at the beginning of text.
130  *  \param postbr output array.
131  *  \param len text array length.
132  *  \param text Text with format string before the actual string.
133  */
134 static void xmldoc_setpostbr(char *postbr, size_t len, const char *text)
135 {
136         int c, postbrlen = 0;
137
138         if (!text) {
139                 return;
140         }
141
142         for (c = 0; c < len; c++) {
143                 if (text[c] == '\t' || text[c] == ' ') {
144                         postbr[postbrlen++] = text[c];
145                 } else {
146                         break;
147                 }
148         }
149         postbr[postbrlen] = '\0';
150 }
151
152 /*! \internal
153  *  \brief Try to find a space or a break in text starting at currentpost
154  *         and moving at most maxdiff positions.
155  *         Helper for xmldoc_string_wrap().
156  *  \param text Input string where it will search.
157  *  \param currentpos Current position within text.
158  *  \param maxdiff Not move more than maxdiff inside text.
159  *  \retval 1 if a space or break is found inside text while moving.
160  *  \retval 0 if no space or break is found.
161  */
162 static int xmldoc_wait_nextspace(const char *text, int currentpos, int maxdiff)
163 {
164         int i, textlen;
165
166         if (!text) {
167                 return 0;
168         }
169
170         textlen = strlen(text);
171         for (i = currentpos; i < textlen; i++) {
172                 if (text[i] == ESC) {
173                         /* Move to the end of the escape sequence */
174                         while (i < textlen && text[i] != 'm') {
175                                 i++;
176                         }
177                 } else if (text[i] == ' ' || text[i] == '\n') {
178                         /* Found the next space or linefeed */
179                         return 1;
180                 } else if (i - currentpos > maxdiff) {
181                         /* We have looked the max distance and didn't find it */
182                         return 0;
183                 }
184         }
185
186         /* Reached the end and did not find it */
187
188         return 0;
189 }
190
191 /*! \internal
192  *  \brief Helper function for xmldoc_string_wrap().
193  *         Try to found a space or a break inside text moving backward
194  *         not more than maxdiff positions.
195  *  \param text The input string where to search for a space.
196  *  \param currentpos The current cursor position.
197  *  \param maxdiff The max number of positions to move within text.
198  *  \retval 0 If no space is found (Notice that text[currentpos] is not a space or a break)
199  *  \retval > 0 If a space or a break is found, and the result is the position relative to
200  *              currentpos.
201  */
202 static int xmldoc_foundspace_backward(const char *text, int currentpos, int maxdiff)
203 {
204         int i;
205
206         for (i = currentpos; i > 0; i--) {
207                 if (text[i] == ' ' || text[i] == '\n') {
208                         return (currentpos - i);
209                 } else if (text[i] == 'm' && (text[i - 1] >= '0' || text[i - 1] <= '9')) {
210                         /* give up, we found the end of a possible ESC sequence. */
211                         return 0;
212                 } else if (currentpos - i > maxdiff) {
213                         /* give up, we can't move anymore. */
214                         return 0;
215                 }
216         }
217
218         /* we found the beginning of the text */
219
220         return 0;
221 }
222
223 /*! \internal
224  *  \brief Justify a text to a number of columns.
225  *  \param text Input text to be justified.
226  *  \param columns Number of columns to preserve in the text.
227  *  \param maxdiff Try to not cut a word when goinf down.
228  *  \retval NULL on error.
229  *  \retval The wrapped text.
230  */
231 static char *xmldoc_string_wrap(const char *text, int columns, int maxdiff)
232 {
233         struct ast_str *tmp;
234         char *ret, postbr[160];
235         int count = 1, i, backspace, needtobreak = 0, colmax, textlen;
236
237         /* sanity check */
238         if (!text || columns <= 0 || maxdiff < 0) {
239                 ast_log(LOG_WARNING, "Passing wrong arguments while trying to wrap the text\n");
240                 return NULL;
241         }
242
243         tmp = ast_str_create(strlen(text) * 3);
244
245         if (!tmp) {
246                 return NULL;
247         }
248
249         /* Check for blanks and tabs and put them in postbr. */
250         xmldoc_setpostbr(postbr, sizeof(postbr), text);
251         colmax = columns - xmldoc_postbrlen(postbr);
252
253         textlen = strlen(text);
254         for (i = 0; i < textlen; i++) {
255                 if (needtobreak || !(count % colmax)) {
256                         if (text[i] == ' ') {
257                                 ast_str_append(&tmp, 0, "\n%s", postbr);
258                                 needtobreak = 0;
259                                 count = 1;
260                         } else if (text[i] != '\n') {
261                                 needtobreak = 1;
262                                 if (xmldoc_wait_nextspace(text, i, maxdiff)) {
263                                         /* wait for the next space */
264                                         ast_str_append(&tmp, 0, "%c", text[i]);
265                                         continue;
266                                 }
267                                 /* Try to look backwards */
268                                 backspace = xmldoc_foundspace_backward(text, i, maxdiff);
269                                 if (backspace) {
270                                         needtobreak = 1;
271                                         tmp->used -= backspace;
272                                         tmp->str[tmp->used] = '\0';
273                                         i -= backspace + 1;
274                                         continue;
275                                 }
276                                 ast_str_append(&tmp, 0, "\n%s", postbr);
277                                 needtobreak = 0;
278                                 count = 1;
279                         }
280                         /* skip blanks after a \n */
281                         while (text[i] == ' ') {
282                                 i++;
283                         }
284                 }
285                 if (text[i] == '\n') {
286                         xmldoc_setpostbr(postbr, sizeof(postbr), &text[i] + 1);
287                         colmax = columns - xmldoc_postbrlen(postbr);
288                         needtobreak = 0;
289                         count = 1;
290                 }
291                 if (text[i] == ESC) {
292                         /* Ignore Escape sequences. */
293                         do {
294                                 ast_str_append(&tmp, 0, "%c", text[i]);
295                                 i++;
296                         } while (i < textlen && text[i] != 'm');
297                 } else {
298                         count++;
299                 }
300                 ast_str_append(&tmp, 0, "%c", text[i]);
301         }
302
303         ret = ast_strdup(tmp->str);
304         ast_free(tmp);
305
306         return ret;
307 }
308
309 char *ast_xmldoc_printable(const char *bwinput, int withcolors)
310 {
311         struct ast_str *colorized;
312         char *wrapped = NULL;
313         int i, c, len, colorsection;
314         char *tmp;
315         size_t bwinputlen;
316         static const int base_fg = COLOR_CYAN;
317
318         if (!bwinput) {
319                 return NULL;
320         }
321
322         bwinputlen = strlen(bwinput);
323
324         if (!(colorized = ast_str_create(256))) {
325                 return NULL;
326         }
327
328         if (withcolors) {
329                 ast_term_color_code(&colorized, base_fg, 0);
330                 if (!colorized) {
331                         return NULL;
332                 }
333         }
334
335         for (i = 0; i < bwinputlen; i++) {
336                 colorsection = 0;
337                 /* Check if we are at the beginning of a tag to be colorized. */
338                 for (c = 0; c < ARRAY_LEN(colorized_tags); c++) {
339                         if (strncasecmp(bwinput + i, colorized_tags[c].inittag, strlen(colorized_tags[c].inittag))) {
340                                 continue;
341                         }
342
343                         if (!(tmp = strcasestr(bwinput + i + strlen(colorized_tags[c].inittag), colorized_tags[c].endtag))) {
344                                 continue;
345                         }
346
347                         len = tmp - (bwinput + i + strlen(colorized_tags[c].inittag));
348
349                         /* Setup color */
350                         if (withcolors) {
351                                 ast_term_color_code(&colorized, colorized_tags[c].colorfg, 0);
352                                 if (!colorized) {
353                                         return NULL;
354                                 }
355                         }
356
357                         /* copy initial string replace */
358                         ast_str_append(&colorized, 0, "%s", colorized_tags[c].init);
359                         if (!colorized) {
360                                 return NULL;
361                         }
362                         {
363                                 char buf[len + 1];
364                                 ast_copy_string(buf, bwinput + i + strlen(colorized_tags[c].inittag), sizeof(buf));
365                                 ast_str_append(&colorized, 0, "%s", buf);
366                         }
367                         if (!colorized) {
368                                 return NULL;
369                         }
370
371                         /* copy the ending string replace */
372                         ast_str_append(&colorized, 0, "%s", colorized_tags[c].end);
373                         if (!colorized) {
374                                 return NULL;
375                         }
376
377                         /* Continue with the last color. */
378                         if (withcolors) {
379                                 ast_term_color_code(&colorized, base_fg, 0);
380                                 if (!colorized) {
381                                         return NULL;
382                                 }
383                         }
384
385                         i += len + strlen(colorized_tags[c].endtag) + strlen(colorized_tags[c].inittag) - 1;
386                         colorsection = 1;
387                         break;
388                 }
389
390                 if (!colorsection) {
391                         ast_str_append(&colorized, 0, "%c", bwinput[i]);
392                         if (!colorized) {
393                                 return NULL;
394                         }
395                 }
396         }
397
398         if (withcolors) {
399                 ast_term_color_code(&colorized, COLOR_BRWHITE, 0);
400                 if (!colorized) {
401                         return NULL;
402                 }
403         }
404
405         /* Wrap the text, notice that string wrap will avoid cutting an ESC sequence. */
406         wrapped = xmldoc_string_wrap(colorized->str, xmldoc_text_columns, xmldoc_max_diff);
407
408         ast_free(colorized);
409
410         return wrapped;
411 }
412
413 /*! \internal
414  *  \brief Cleanup spaces and tabs after a \n
415  *  \param text String to be cleaned up.
416  *  \param output buffer (not already allocated).
417  *  \param lastspaces Remove last spaces in the string.
418  */
419 static void xmldoc_string_cleanup(const char *text, struct ast_str **output, int lastspaces)
420 {
421         int i;
422         size_t textlen;
423
424         if (!text) {
425                 *output = NULL;
426                 return;
427         }
428
429         textlen = strlen(text);
430
431         *output = ast_str_create(textlen);
432         if (!(*output)) {
433                 ast_log(LOG_ERROR, "Problem allocating output buffer\n");
434                 return;
435         }
436
437         for (i = 0; i < textlen; i++) {
438                 if (text[i] == '\n' || text[i] == '\r') {
439                         /* remove spaces/tabs/\n after a \n. */
440                         while (text[i + 1] == '\t' || text[i + 1] == '\r' || text[i + 1] == '\n') {
441                                 i++;
442                         }
443                         ast_str_append(output, 0, " ");
444                         continue;
445                 } else {
446                         ast_str_append(output, 0, "%c", text[i]);
447                 }
448         }
449
450         /* remove last spaces (we dont want always to remove the trailing spaces). */
451         if (lastspaces) {
452                 ast_str_trim_blanks(*output);
453         }
454 }
455
456 /*! \internal
457  *  \brief Get the application/function node for 'name' application/function with language 'language'
458  *         if we don't find any, get the first application with 'name' no matter which language with.
459  *  \param type 'application', 'function', ...
460  *  \param name Application or Function name.
461  *  \param language Try to get this language (if not found try with en_US)
462  *  \retval NULL on error.
463  *  \retval A node of type ast_xml_node.
464  */
465 static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *language)
466 {
467         struct ast_xml_node *node = NULL;
468         struct documentation_tree *doctree;
469         const char *lang;
470
471         AST_RWLIST_RDLOCK(&xmldoc_tree);
472         AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
473                 /* the core xml documents have priority over thirdparty document. */
474                 node = ast_xml_get_root(doctree->doc);
475                 while ((node = ast_xml_find_element(node, type, "name", name))) {
476                         /* Check language */
477                         lang = ast_xml_get_attribute(node, "language");
478                         if (lang && !strcmp(lang, language)) {
479                                 ast_xml_free_attr(lang);
480                                 break;
481                         } else if (lang) {
482                                 ast_xml_free_attr(lang);
483                         }
484                 }
485
486                 if (node && ast_xml_node_get_children(node)) {
487                         break;
488                 }
489
490                 /* We didn't find the application documentation for the specified language,
491                 so, try to load documentation for any language */
492                 node = ast_xml_get_root(doctree->doc);
493                 if (ast_xml_node_get_children(node)) {
494                         if ((node = ast_xml_find_element(ast_xml_node_get_children(node), type, "name", name))) {
495                                 break;
496                         }
497                 }
498         }
499         AST_RWLIST_UNLOCK(&xmldoc_tree);
500
501         return node;
502 }
503
504 /*! \internal
505  *  \brief Helper function used to build the syntax, it allocates the needed buffer (or reallocates it),
506  *         and based on the reverse value it makes use of fmt to print the parameter list inside the
507  *         realloced buffer (syntax).
508  *  \param reverse We are going backwards while generating the syntax?
509  *  \param len Current length of 'syntax' buffer.
510  *  \param syntax Output buffer for the concatenated values.
511  *  \param fmt A format string that will be used in a sprintf call.
512  */
513 static __attribute__((format(printf,4,5))) void xmldoc_reverse_helper(int reverse, int *len, char **syntax, const char *fmt, ...)
514 {
515         int totlen, tmpfmtlen;
516         char *tmpfmt, tmp;
517         va_list ap;
518
519         va_start(ap, fmt);
520         if (ast_vasprintf(&tmpfmt, fmt, ap) < 0) {
521                 va_end(ap);
522                 return;
523         }
524         va_end(ap);
525
526         tmpfmtlen = strlen(tmpfmt);
527         totlen = *len + tmpfmtlen + 1;
528
529         *syntax = ast_realloc(*syntax, totlen);
530
531         if (!*syntax) {
532                 ast_free(tmpfmt);
533                 return;
534         }
535
536         if (reverse) {
537                 memmove(*syntax + tmpfmtlen, *syntax, *len);
538                 /* Save this char, it will be overwritten by the \0 of strcpy. */
539                 tmp = (*syntax)[0];
540                 strcpy(*syntax, tmpfmt);
541                 /* Restore the already saved char. */
542                 (*syntax)[tmpfmtlen] = tmp;
543                 (*syntax)[totlen - 1] = '\0';
544         } else {
545                 strcpy(*syntax + *len, tmpfmt);
546         }
547
548         *len = totlen - 1;
549         ast_free(tmpfmt);
550 }
551
552 /*! \internal
553  *  \brief Check if the passed node has 'what' tags inside it.
554  *  \param node Root node to search 'what' elements.
555  *  \param what node name to search inside node.
556  *  \retval 1 If a <argument> element is found inside 'node'.
557  *  \retval 0 If no <argument> is found inside 'node'.
558  */
559 static int xmldoc_has_inside(struct ast_xml_node *fixnode, const char *what)
560 {
561         struct ast_xml_node *node = fixnode;
562
563         for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
564                 if (!strcasecmp(ast_xml_node_get_name(node), what)) {
565                         return 1;
566                 }
567         }
568         return 0;
569 }
570
571 /*! \internal
572  *  \brief Build the syntax for a specified starting node.
573  *  \param rootnode A pointer to the ast_xml root node.
574  *  \param rootname Name of the application, function, option, etc. to build the syntax.
575  *  \param childname The name of each parameter node.
576  *  \param printparenthesis Boolean if we must print parenthesis if not parameters are found in the rootnode.
577  *  \param printrootname Boolean if we must print the rootname before the syntax and parenthesis at the begining/end.
578  *  \retval NULL on error.
579  *  \retval An ast_malloc'ed string with the syntax generated.
580  */
581 static char *xmldoc_get_syntax_fun(struct ast_xml_node *rootnode, const char *rootname, const char *childname, int printparenthesis, int printrootname)
582 {
583 #define GOTONEXT(__rev, __a) (__rev ? ast_xml_node_get_prev(__a) : ast_xml_node_get_next(__a))
584 #define ISLAST(__rev, __a)  (__rev == 1 ? (ast_xml_node_get_prev(__a) ? 0 : 1) : (ast_xml_node_get_next(__a) ? 0 : 1))
585 #define MP(__a) ((multiple ? __a : ""))
586         struct ast_xml_node *node = NULL, *firstparam = NULL, *lastparam = NULL;
587         const char *paramtype, *multipletype, *paramname, *attrargsep, *parenthesis, *argname;
588         int reverse, required, paramcount = 0, openbrackets = 0, len = 0, hasparams=0;
589         int reqfinode = 0, reqlanode = 0, optmidnode = 0, prnparenthesis;
590         char *syntax = NULL, *argsep;
591         int paramnamemalloc, multiple;
592
593         if (ast_strlen_zero(rootname) || ast_strlen_zero(childname)) {
594                 ast_log(LOG_WARNING, "Tried to look in XML tree with faulty rootname or childname while creating a syntax.\n");
595                 return NULL;
596         }
597
598         if (!rootnode || !ast_xml_node_get_children(rootnode)) {
599                 /* If the rootnode field is not found, at least print name. */
600                 ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
601                 return syntax;
602         }
603
604         /* Get the argument separator from the root node attribute name 'argsep', if not found
605         defaults to ','. */
606         attrargsep = ast_xml_get_attribute(rootnode, "argsep");
607         if (attrargsep) {
608                 argsep = ast_strdupa(attrargsep);
609                 ast_xml_free_attr(attrargsep);
610         } else {
611                 argsep = ast_strdupa(",");
612         }
613
614         /* Get order of evaluation. */
615         for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) {
616                 if (strcasecmp(ast_xml_node_get_name(node), childname)) {
617                         continue;
618                 }
619                 required = 0;
620                 hasparams = 1;
621                 if ((paramtype = ast_xml_get_attribute(node, "required"))) {
622                         if (ast_true(paramtype)) {
623                                 required = 1;
624                         }
625                         ast_xml_free_attr(paramtype);
626                 }
627
628                 lastparam = node;
629                 reqlanode = required;
630
631                 if (!firstparam) {
632                         /* first parameter node */
633                         firstparam = node;
634                         reqfinode = required;
635                 }
636         }
637
638         if (!hasparams) {
639                 /* This application, function, option, etc, doesn't have any params. */
640                 ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
641                 return syntax;
642         }
643
644         if (reqfinode && reqlanode) {
645                 /* check midnode */
646                 for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) {
647                         if (strcasecmp(ast_xml_node_get_name(node), childname)) {
648                                 continue;
649                         }
650                         if (node != firstparam && node != lastparam) {
651                                 if ((paramtype = ast_xml_get_attribute(node, "required"))) {
652                                         if (!ast_true(paramtype)) {
653                                                 optmidnode = 1;
654                                                 break;
655                                         }
656                                         ast_xml_free_attr(paramtype);
657                                 }
658                         }
659                 }
660         }
661
662         if ((!reqfinode && reqlanode) || (reqfinode && reqlanode && optmidnode)) {
663                 reverse = 1;
664                 node = lastparam;
665         } else {
666                 reverse = 0;
667                 node = firstparam;
668         }
669
670         /* init syntax string. */
671         if (reverse) {
672                 xmldoc_reverse_helper(reverse, &len, &syntax,
673                         (printrootname ? (printrootname == 2 ? ")]" : ")"): ""));
674         } else {
675                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""),
676                         (printrootname ? (printrootname == 2 ? "[(" : "(") : ""));
677         }
678
679         for (; node; node = GOTONEXT(reverse, node)) {
680                 if (strcasecmp(ast_xml_node_get_name(node), childname)) {
681                         continue;
682                 }
683
684                 /* Get the argument name, if it is not the leaf, go inside that parameter. */
685                 if (xmldoc_has_inside(node, "argument")) {
686                         parenthesis = ast_xml_get_attribute(node, "hasparams");
687                         prnparenthesis = 0;
688                         if (parenthesis) {
689                                 prnparenthesis = ast_true(parenthesis);
690                                 if (!strcasecmp(parenthesis, "optional")) {
691                                         prnparenthesis = 2;
692                                 }
693                                 ast_xml_free_attr(parenthesis);
694                         }
695                         argname = ast_xml_get_attribute(node, "name");
696                         if (argname) {
697                                 paramname = xmldoc_get_syntax_fun(node, argname, "argument", prnparenthesis, prnparenthesis);
698                                 ast_xml_free_attr(argname);
699                                 paramnamemalloc = 1;
700                         } else {
701                                 /* Malformed XML, print **UNKOWN** */
702                                 paramname = ast_strdup("**unknown**");
703                         }
704                         paramnamemalloc = 1;
705                 } else {
706                         paramnamemalloc = 0;
707                         paramname = ast_xml_get_attribute(node, "name");
708                         if (!paramname) {
709                                 ast_log(LOG_WARNING, "Malformed XML %s: no %s name\n", rootname, childname);
710                                 if (syntax) {
711                                         /* Free already allocated syntax */
712                                         ast_free(syntax);
713                                 }
714                                 /* to give up is ok? */
715                                 ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : ""));
716                                 return syntax;
717                         }
718                 }
719
720                 /* Defaults to 'false'. */
721                 multiple = 0;
722                 if ((multipletype = ast_xml_get_attribute(node, "multiple"))) {
723                         if (ast_true(multipletype)) {
724                                 multiple = 1;
725                         }
726                         ast_xml_free_attr(multipletype);
727                 }
728
729                 required = 0;   /* Defaults to 'false'. */
730                 if ((paramtype = ast_xml_get_attribute(node, "required"))) {
731                         if (ast_true(paramtype)) {
732                                 required = 1;
733                         }
734                         ast_xml_free_attr(paramtype);
735                 }
736
737                 /* build syntax core. */
738
739                 if (required) {
740                         /* First parameter */
741                         if (!paramcount) {
742                                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s", paramname, MP("["), MP(argsep), MP("...]"));
743                         } else {
744                                 /* Time to close open brackets. */
745                                 while (openbrackets > 0) {
746                                         xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]"));
747                                         openbrackets--;
748                                 }
749                                 if (reverse) {
750                                         xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", paramname, argsep);
751                                 } else {
752                                         xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", argsep, paramname);
753                                 }
754                                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s", MP("["), MP(argsep), MP("...]"));
755                         }
756                 } else {
757                         /* First parameter */
758                         if (!paramcount) {
759                                 xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]", paramname, MP("["), MP(argsep), MP("...]"));
760                         } else {
761                                 if (ISLAST(reverse, node)) {
762                                         /* This is the last parameter. */
763                                         if (reverse) {
764                                                 xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]%s", paramname,
765                                                                         MP("["), MP(argsep), MP("...]"), argsep);
766                                         } else {
767                                                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s[%s%s%s%s]", argsep, paramname,
768                                                                         MP("["), MP(argsep), MP("...]"));
769                                         }
770                                 } else {
771                                         if (reverse) {
772                                                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s%s]", paramname, argsep,
773                                                                         MP("["), MP(argsep), MP("...]"));
774                                         } else {
775                                                 xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s%s", argsep, paramname,
776                                                                         MP("["), MP(argsep), MP("...]"));
777                                         }
778                                         openbrackets++;
779                                 }
780                         }
781                 }
782                 if (paramnamemalloc) {
783                         ast_free((char *) paramname);
784                 } else {
785                         ast_xml_free_attr(paramname);
786                 }
787
788                 paramcount++;
789         }
790
791         /* Time to close open brackets. */
792         while (openbrackets > 0) {
793                 xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]"));
794                 openbrackets--;
795         }
796
797         /* close syntax string. */
798         if (reverse) {
799                 xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""),
800                         (printrootname ? (printrootname == 2 ? "[(" : "(") : ""));
801         } else {
802                 xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")") : ""));
803         }
804
805         return syntax;
806 #undef ISLAST
807 #undef GOTONEXT
808 #undef MP
809 }
810
811 /*! \internal
812  *  \brief Parse an enumlist inside a <parameter> to generate a COMMAND
813  *         syntax.
814  *  \param fixnode A pointer to the <enumlist> node.
815  *  \retval {<unknown>} on error.
816  *  \retval A string inside brackets {} with the enum's separated by pipes |.
817  */
818 static char *xmldoc_parse_cmd_enumlist(struct ast_xml_node *fixnode)
819 {
820         struct ast_xml_node *node = fixnode;
821         struct ast_str *paramname;
822         char *enumname, *ret;
823         int first = 1;
824
825         paramname = ast_str_create(128);
826         if (!paramname) {
827                 return ast_strdup("{<unkown>}");
828         }
829
830         ast_str_append(&paramname, 0, "{");
831
832         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
833                 if (strcasecmp(ast_xml_node_get_name(node), "enum")) {
834                         continue;
835                 }
836
837                 enumname = xmldoc_get_syntax_cmd(node, "", 0);
838                 if (!enumname) {
839                         continue;
840                 }
841                 if (!first) {
842                         ast_str_append(&paramname, 0, "|");
843                 }
844                 ast_str_append(&paramname, 0, "%s", enumname);
845                 first = 0;
846                 ast_free(enumname);
847         }
848
849         ast_str_append(&paramname, 0, "}");
850
851         ret = ast_strdup(paramname->str);
852         ast_free(paramname);
853
854         return ret;
855 }
856
857 /*! \internal
858  *  \brief Generate a syntax of COMMAND type.
859  *  \param fixnode The <syntax> node pointer.
860  *  \param name The name of the 'command'.
861  *  \param printname Print the name of the command before the paramters?
862  *  \retval On error, return just 'name'.
863  *  \retval On success return the generated syntax.
864  */
865 static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *name, int printname)
866 {
867         struct ast_str *syntax;
868         struct ast_xml_node *tmpnode, *node = fixnode;
869         char *ret, *paramname;
870         const char *paramtype, *attrname, *literal;
871         int required, isenum, first = 1, isliteral;
872
873         syntax = ast_str_create(128);
874         if (!syntax) {
875                 /* at least try to return something... */
876                 return ast_strdup(name);
877         }
878
879         /* append name to output string. */
880         if (printname) {
881                 ast_str_append(&syntax, 0, "%s", name);
882                 first = 0;
883         }
884
885         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
886                 if (strcasecmp(ast_xml_node_get_name(node), "parameter")) {
887                         continue;
888                 }
889
890                 if (xmldoc_has_inside(node, "parameter")) {
891                         /* is this a recursive parameter. */
892                         paramname = xmldoc_get_syntax_cmd(node, "", 0);
893                         isenum = 1;
894                 } else if (!xmldoc_has_inside(node, "enumlist")) {
895                         /* this is a simple parameter. */
896                         attrname = ast_xml_get_attribute(node, "name");
897                         if (!attrname) {
898                                 /* ignore this bogus parameter and continue. */
899                                 continue;
900                         }
901                         paramname = ast_strdup(attrname);
902                         ast_xml_free_attr(attrname);
903                         isenum = 0;
904                 } else {
905                         /* parse enumlist (note that this is a special enumlist
906                         that is used to describe a syntax like {<param1>|<param2>|...} */
907                         for (tmpnode = ast_xml_node_get_children(node); tmpnode; tmpnode = ast_xml_node_get_next(tmpnode)) {
908                                 if (!strcasecmp(ast_xml_node_get_name(tmpnode), "enumlist")) {
909                                         break;
910                                 }
911                         }
912                         paramname = xmldoc_parse_cmd_enumlist(tmpnode);
913                         isenum = 1;
914                 }
915
916                 /* Is this parameter required? */
917                 required = 0;
918                 paramtype = ast_xml_get_attribute(node, "required");
919                 if (paramtype) {
920                         required = ast_true(paramtype);
921                         ast_xml_free_attr(paramtype);
922                 }
923
924                 /* Is this a replaceable value or a fixed parameter value? */
925                 isliteral = 0;
926                 literal = ast_xml_get_attribute(node, "literal");
927                 if (literal) {
928                         isliteral = ast_true(literal);
929                         ast_xml_free_attr(literal);
930                 }
931
932                 /* if required="false" print with [...].
933                  * if literal="true" or is enum print without <..>.
934                  * if not first print a space at the beginning.
935                  */
936                 ast_str_append(&syntax, 0, "%s%s%s%s%s%s",
937                                 (first ? "" : " "),
938                                 (required ? "" : "["),
939                                 (isenum || isliteral ? "" : "<"),
940                                 paramname,
941                                 (isenum || isliteral ? "" : ">"),
942                                 (required ? "" : "]"));
943                 first = 0;
944                 ast_free(paramname);
945         }
946
947         /* return a common string. */
948         ret = ast_strdup(syntax->str);
949         ast_free(syntax);
950
951         return ret;
952 }
953
954 /*! \brief Types of syntax that we are able to generate. */
955 enum syntaxtype {
956         FUNCTION_SYNTAX,
957         COMMAND_SYNTAX
958 };
959
960 /*! \brief Mapping between type of node and type of syntax to generate. */
961 struct strsyntaxtype {
962         const char *type;
963         enum syntaxtype stxtype;
964 } stxtype[] = {
965         { "function",           FUNCTION_SYNTAX },
966         { "application",        FUNCTION_SYNTAX },
967         { "agi",                COMMAND_SYNTAX  }
968 };
969
970 /*! \internal
971  *  \brief Get syntax type based on type of node.
972  *  \param type Type of node.
973  *  \retval The type of syntax to generate based on the type of node.
974  */
975 static enum syntaxtype xmldoc_get_syntax_type(const char *type)
976 {
977         int i;
978         for (i=0; i < ARRAY_LEN(stxtype); i++) {
979                 if (!strcasecmp(stxtype[i].type, type)) {
980                         return stxtype[i].stxtype;
981                 }
982         }
983
984         return FUNCTION_SYNTAX;
985 }
986
987 char *ast_xmldoc_build_syntax(const char *type, const char *name)
988 {
989         struct ast_xml_node *node;
990         char *syntax = NULL;
991
992         node = xmldoc_get_node(type, name, documentation_language);
993         if (!node) {
994                 return NULL;
995         }
996
997         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
998                 if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) {
999                         break;
1000                 }
1001         }
1002
1003         if (node) {
1004                 if (xmldoc_get_syntax_type(type) == FUNCTION_SYNTAX) {
1005                         syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1);
1006                 } else {
1007                         syntax = xmldoc_get_syntax_cmd(node, name, 1);
1008                 }
1009         }
1010         return syntax;
1011 }
1012
1013 /*! \internal
1014  *  \brief Parse a <para> element.
1015  *  \param node The <para> element pointer.
1016  *  \param tabs Added this string before the content of the <para> element.
1017  *  \param posttabs Added this string after the content of the <para> element.
1018  *  \param buffer This must be an already allocated ast_str. It will be used
1019  *         to store the result (if already has something it will be appended to the current
1020  *         string).
1021  *  \retval 1 If 'node' is a named 'para'.
1022  *  \retval 2 If data is appended in buffer.
1023  *  \retval 0 on error.
1024  */
1025 static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer)
1026 {
1027         const char *tmptext;
1028         struct ast_xml_node *tmp;
1029         int ret = 0;
1030         struct ast_str *tmpstr;
1031
1032         if (!node || !ast_xml_node_get_children(node)) {
1033                 return ret;
1034         }
1035
1036         if (strcasecmp(ast_xml_node_get_name(node), "para")) {
1037                 return ret;
1038         }
1039
1040         ast_str_append(buffer, 0, "%s", tabs);
1041
1042         ret = 1;
1043
1044         for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
1045                 /* Get the text inside the <para> element and append it to buffer. */
1046                 tmptext = ast_xml_get_text(tmp);
1047                 if (tmptext) {
1048                         /* Strip \n etc. */
1049                         xmldoc_string_cleanup(tmptext, &tmpstr, 0);
1050                         ast_xml_free_text(tmptext);
1051                         if (tmpstr) {
1052                                 if (strcasecmp(ast_xml_node_get_name(tmp), "text")) {
1053                                         ast_str_append(buffer, 0, "<%s>%s</%s>", ast_xml_node_get_name(tmp),
1054                                                         tmpstr->str, ast_xml_node_get_name(tmp));
1055                                 } else {
1056                                         ast_str_append(buffer, 0, "%s", tmpstr->str);
1057                                 }
1058                                 ast_free(tmpstr);
1059                                 ret = 2;
1060                         }
1061                 }
1062         }
1063
1064         ast_str_append(buffer, 0, "%s", posttabs);
1065
1066         return ret;
1067 }
1068
1069 /*! \internal
1070  *  \brief Parse special elements defined in 'struct special_tags' special elements must have a <para> element inside them.
1071  *  \param fixnode special tag node pointer.
1072  *  \param tabs put tabs before printing the node content.
1073  *  \param posttabs put posttabs after printing node content.
1074  *  \param buffer Output buffer, the special tags will be appended here.
1075  *  \retval 0 if no special element is parsed.
1076  *  \retval 1 if a special element is parsed (data is appended to buffer).
1077  *  \retval 2 if a special element is parsed and also a <para> element is parsed inside the specialtag.
1078  */
1079 static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer)
1080 {
1081         struct ast_xml_node *node = fixnode;
1082         int ret = 0, i, count = 0;
1083
1084         if (!node || !ast_xml_node_get_children(node)) {
1085                 return ret;
1086         }
1087
1088         for (i = 0; i < ARRAY_LEN(special_tags); i++) {
1089                 if (strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) {
1090                         continue;
1091                 }
1092
1093                 ret = 1;
1094                 /* This is a special tag. */
1095
1096                 /* concat data */
1097                 if (!ast_strlen_zero(special_tags[i].init)) {
1098                         ast_str_append(buffer, 0, "%s%s", tabs, special_tags[i].init);
1099                 }
1100
1101                 /* parse <para> elements inside special tags. */
1102                 for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1103                         /* first <para> just print it without tabs at the begining. */
1104                         if (xmldoc_parse_para(node, (!count ? "" : tabs), posttabs, buffer) == 2) {
1105                                 ret = 2;
1106                         }
1107                 }
1108
1109                 if (!ast_strlen_zero(special_tags[i].end)) {
1110                         ast_str_append(buffer, 0, "%s%s", special_tags[i].end, posttabs);
1111                 }
1112
1113                 break;
1114         }
1115
1116         return ret;
1117 }
1118
1119 /*! \internal
1120  *  \brief Parse an <argument> element from the xml documentation.
1121  *  \param fixnode Pointer to the 'argument' xml node.
1122  *  \param insideparameter If we are parsing an <argument> inside a <parameter>.
1123  *  \param paramtabs pre tabs if we are inside a parameter element.
1124  *  \param tabs What to be printed before the argument name.
1125  *  \param buffer Output buffer to put values found inside the <argument> element.
1126  *  \retval 1 If there is content inside the argument.
1127  *  \retval 0 If the argument element is not parsed, or there is no content inside it.
1128  */
1129 static int xmldoc_parse_argument(struct ast_xml_node *fixnode, int insideparameter, const char *paramtabs, const char *tabs, struct ast_str **buffer)
1130 {
1131         struct ast_xml_node *node = fixnode;
1132         const char *argname;
1133         int count = 0, ret = 0;
1134
1135         if (!node || !ast_xml_node_get_children(node)) {
1136                 return ret;
1137         }
1138
1139         /* Print the argument names */
1140         argname = ast_xml_get_attribute(node, "name");
1141         if (!argname) {
1142                 return 0;
1143         }
1144         ast_str_append(buffer, 0, "%s%s%s", tabs, argname, (insideparameter ? "\n" : ""));
1145         ast_xml_free_attr(argname);
1146
1147         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1148                 if (xmldoc_parse_para(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) {
1149                         count++;
1150                         ret = 1;
1151                 } else if (xmldoc_parse_specialtags(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) {
1152                         count++;
1153                         ret = 1;
1154                 }
1155         }
1156
1157         return ret;
1158 }
1159
1160 /*! \internal
1161  *  \brief Parse a <variable> node inside a <variablelist> node.
1162  *  \param node The variable node to parse.
1163  *  \param tabs A string to be appended at the begining of the output that will be stored
1164  *         in buffer.
1165  *  \param buffer This must be an already created ast_str. It will be used
1166  *         to store the result (if already has something it will be appended to the current
1167  *         string).
1168  *  \retval 0 if no data is appended.
1169  *  \retval 1 if data is appended.
1170  */
1171 static int xmldoc_parse_variable(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer)
1172 {
1173         struct ast_xml_node *tmp;
1174         const char *valname;
1175         const char *tmptext;
1176         struct ast_str *cleanstr;
1177         int ret = 0, printedpara=0;
1178
1179         for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
1180                 if (xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer)) {
1181                         printedpara = 1;
1182                         continue;
1183                 } else if (xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer)) {
1184                         printedpara = 1;
1185                         continue;
1186                 }
1187
1188                 if (strcasecmp(ast_xml_node_get_name(tmp), "value")) {
1189                         continue;
1190                 }
1191
1192                 /* Parse a <value> tag only. */
1193                 if (!printedpara) {
1194                         ast_str_append(buffer, 0, "\n");
1195                         printedpara = 1;
1196                 }
1197                 /* Parse each <value name='valuename'>desciption</value> */
1198                 valname = ast_xml_get_attribute(tmp, "name");
1199                 if (valname) {
1200                         ret = 1;
1201                         ast_str_append(buffer, 0, "%s<value>%s</value>", tabs, valname);
1202                         ast_xml_free_attr(valname);
1203                 }
1204                 tmptext = ast_xml_get_text(tmp);
1205                 /* Check inside this node for any explanation about its meaning. */
1206                 if (tmptext) {
1207                         /* Cleanup text. */
1208                         xmldoc_string_cleanup(tmptext, &cleanstr, 1);
1209                         ast_xml_free_text(tmptext);
1210                         if (cleanstr && cleanstr->used > 0) {
1211                                 ast_str_append(buffer, 0, ":%s", cleanstr->str);
1212                         }
1213                         ast_free(cleanstr);
1214                 }
1215                 ast_str_append(buffer, 0, "\n");
1216         }
1217
1218         return ret;
1219 }
1220
1221 /*! \internal
1222  *  \brief Parse a <variablelist> node and put all the output inside 'buffer'.
1223  *  \param node The variablelist node pointer.
1224  *  \param tabs A string to be appended at the begining of the output that will be stored
1225  *         in buffer.
1226  *  \param buffer This must be an already created ast_str. It will be used
1227  *         to store the result (if already has something it will be appended to the current
1228  *         string).
1229  *  \retval 1 If a <variablelist> element is parsed.
1230  *  \retval 0 On error.
1231  */
1232 static int xmldoc_parse_variablelist(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer)
1233 {
1234         struct ast_xml_node *tmp;
1235         const char *varname;
1236         char *vartabs;
1237         int ret = 0;
1238
1239         if (!node || !ast_xml_node_get_children(node)) {
1240                 return ret;
1241         }
1242
1243         if (strcasecmp(ast_xml_node_get_name(node), "variablelist")) {
1244                 return ret;
1245         }
1246
1247         /* use this spacing (add 4 spaces) inside a variablelist node. */
1248         ast_asprintf(&vartabs, "%s    ", tabs);
1249         if (!vartabs) {
1250                 return ret;
1251         }
1252         for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
1253                 /* We can have a <para> element inside the variable list */
1254                 if ((xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer))) {
1255                         ret = 1;
1256                         continue;
1257                 } else if ((xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer))) {
1258                         ret = 1;
1259                         continue;
1260                 }
1261
1262                 if (!strcasecmp(ast_xml_node_get_name(tmp), "variable")) {
1263                         /* Store the variable name in buffer. */
1264                         varname = ast_xml_get_attribute(tmp, "name");
1265                         if (varname) {
1266                                 ast_str_append(buffer, 0, "%s<variable>%s</variable>: ", tabs, varname);
1267                                 ast_xml_free_attr(varname);
1268                                 /* Parse the <variable> possible values. */
1269                                 xmldoc_parse_variable(tmp, vartabs, buffer);
1270                                 ret = 1;
1271                         }
1272                 }
1273         }
1274
1275         ast_free(vartabs);
1276
1277         return ret;
1278 }
1279
1280 char *ast_xmldoc_build_seealso(const char *type, const char *name)
1281 {
1282         struct ast_str *outputstr;
1283         char *output;
1284         struct ast_xml_node *node;
1285         const char *typename;
1286         const char *content;
1287         int first = 1;
1288
1289         if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
1290                 return NULL;
1291         }
1292
1293         /* get the application/function root node. */
1294         node = xmldoc_get_node(type, name, documentation_language);
1295         if (!node || !ast_xml_node_get_children(node)) {
1296                 return NULL;
1297         }
1298
1299         /* Find the <see-also> node. */
1300         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1301                 if (!strcasecmp(ast_xml_node_get_name(node), "see-also")) {
1302                         break;
1303                 }
1304         }
1305
1306         if (!node || !ast_xml_node_get_children(node)) {
1307                 /* we couldnt find a <see-also> node. */
1308                 return NULL;
1309         }
1310
1311         /* prepare the output string. */
1312         outputstr = ast_str_create(128);
1313         if (!outputstr) {
1314                 return NULL;
1315         }
1316
1317         /* get into the <see-also> node. */
1318         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1319                 if (strcasecmp(ast_xml_node_get_name(node), "ref")) {
1320                         continue;
1321                 }
1322
1323                 /* parse the <ref> node. 'type' attribute is required. */
1324                 typename = ast_xml_get_attribute(node, "type");
1325                 if (!typename) {
1326                         continue;
1327                 }
1328                 content = ast_xml_get_text(node);
1329                 if (!content) {
1330                         ast_xml_free_attr(typename);
1331                         continue;
1332                 }
1333                 if (!strcasecmp(typename, "application")) {
1334                         ast_str_append(&outputstr, 0, "%s%s()", (first ? "" : ", "), content);
1335                 } else if (!strcasecmp(typename, "function")) {
1336                         ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content);
1337                 } else if (!strcasecmp(typename, "astcli")) {
1338                         ast_str_append(&outputstr, 0, "%s<astcli>%s</astcli>", (first ? "" : ", "), content);
1339                 } else {
1340                         ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content);
1341                 }
1342                 first = 0;
1343                 ast_xml_free_text(content);
1344         }
1345
1346         output = ast_strdup(outputstr->str);
1347         ast_free(outputstr);
1348
1349         return output;
1350 }
1351
1352 /*! \internal
1353  *  \brief Parse a <enum> node.
1354  *  \brief fixnode An ast_xml_node pointer to the <enum> node.
1355  *  \bried buffer The output buffer.
1356  *  \retval 0 if content is not found inside the enum element (data is not appended to buffer).
1357  *  \retval 1 if content is found and data is appended to buffer.
1358  */
1359 static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
1360 {
1361         struct ast_xml_node *node = fixnode;
1362         int ret = 0;
1363
1364         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1365                 if ((xmldoc_parse_para(node, (ret ? tabs : " - "), "\n", buffer))) {
1366                         ret = 1;
1367                 } else if ((xmldoc_parse_specialtags(node, (ret ? tabs : " - "), "\n", buffer))) {
1368                         ret = 1;
1369                 }
1370         }
1371         return ret;
1372 }
1373
1374 /*! \internal
1375  *  \brief Parse a <enumlist> node.
1376  *  \param fixnode As ast_xml pointer to the <enumlist> node.
1377  *  \param buffer The ast_str output buffer.
1378  *  \retval 0 if no <enumlist> node was parsed.
1379  *  \retval 1 if a <enumlist> node was parsed.
1380  */
1381 static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
1382 {
1383         struct ast_xml_node *node = fixnode;
1384         const char *enumname;
1385         int ret = 0;
1386
1387         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1388                 if (strcasecmp(ast_xml_node_get_name(node), "enum")) {
1389                         continue;
1390                 }
1391
1392                 enumname = ast_xml_get_attribute(node, "name");
1393                 if (enumname) {
1394                         ast_str_append(buffer, 0, "%s<enum>%s</enum>", tabs, enumname);
1395                         ast_xml_free_attr(enumname);
1396
1397                         /* parse only enum elements inside a enumlist node. */
1398                         if ((xmldoc_parse_enum(node, tabs, buffer))) {
1399                                 ret = 1;
1400                         } else {
1401                                 ast_str_append(buffer, 0, "\n");
1402                         }
1403                 }
1404         }
1405         return ret;
1406 }
1407
1408 /*! \internal
1409  *  \brief Parse an <option> node.
1410  *  \param fixnode An ast_xml pointer to the <option> node.
1411  *  \param tabs A string to be appended at the begining of each line being added to the
1412  *              buffer string.
1413  *  \param buffer The output buffer.
1414  *  \retval 0 if no option node is parsed.
1415  *  \retval 1 if an option node is parsed.
1416  */
1417 static int xmldoc_parse_option(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
1418 {
1419         struct ast_xml_node *node;
1420         int ret = 0;
1421         char *optiontabs;
1422
1423         ast_asprintf(&optiontabs, "%s    ", tabs);
1424         if (!optiontabs) {
1425                 return ret;
1426         }
1427         for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
1428                 if (!strcasecmp(ast_xml_node_get_name(node), "argument")) {
1429                         /* if this is the first data appended to buffer, print a \n*/
1430                         if (!ret && ast_xml_node_get_children(node)) {
1431                                 /* print \n */
1432                                 ast_str_append(buffer, 0, "\n");
1433                         }
1434                         if (xmldoc_parse_argument(node, 0, NULL, optiontabs, buffer)) {
1435                                 ret = 1;
1436                         }
1437                         continue;
1438                 }
1439
1440                 if (xmldoc_parse_para(node, (ret ? tabs :  ""), "\n", buffer)) {
1441                         ret = 1;
1442                 } else if (xmldoc_parse_specialtags(node, (ret ? tabs :  ""), "\n", buffer)) {
1443                         ret = 1;
1444                 }
1445
1446                 xmldoc_parse_variablelist(node, optiontabs, buffer);
1447
1448                 xmldoc_parse_enumlist(node, optiontabs, buffer);
1449         }
1450         ast_free(optiontabs);
1451
1452         return ret;
1453 }
1454
1455 /*! \internal
1456  *  \brief Parse an <optionlist> element from the xml documentation.
1457  *  \param fixnode Pointer to the optionlist xml node.
1458  *  \param tabs A string to be appended at the begining of each line being added to the
1459  *              buffer string.
1460  *  \param buffer Output buffer to put what is inside the optionlist tag.
1461  */
1462 static void xmldoc_parse_optionlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
1463 {
1464         struct ast_xml_node *node;
1465         const char *optname, *hasparams;
1466         char *optionsyntax;
1467         int optparams;
1468
1469         for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) {
1470                 /* Start appending every option tag. */
1471                 if (strcasecmp(ast_xml_node_get_name(node), "option")) {
1472                         continue;
1473                 }
1474
1475                 /* Get the option name. */
1476                 optname = ast_xml_get_attribute(node, "name");
1477                 if (!optname) {
1478                         continue;
1479                 }
1480
1481                 optparams = 1;
1482                 hasparams = ast_xml_get_attribute(node, "hasparams");
1483                 if (hasparams && !strcasecmp(hasparams, "optional")) {
1484                         optparams = 2;
1485                 }
1486
1487                 optionsyntax = xmldoc_get_syntax_fun(node, optname, "argument", 0, optparams);
1488                 if (!optionsyntax) {
1489                         ast_xml_free_attr(optname);
1490                         ast_xml_free_attr(hasparams);
1491                         continue;
1492                 }
1493
1494                 ast_str_append(buffer, 0, "%s%s: ", tabs, optionsyntax);
1495
1496                 if (!xmldoc_parse_option(node, tabs, buffer)) {
1497                         ast_str_append(buffer, 0, "\n");
1498                 }
1499                 ast_xml_free_attr(optname);
1500                 ast_xml_free_attr(hasparams);
1501         }
1502 }
1503
1504 /*! \internal
1505  *  \brief Parse a 'parameter' tag inside a syntax element.
1506  *  \param fixnode A pointer to the 'parameter' xml node.
1507  *  \param tabs A string to be appended at the beginning of each line being printed inside
1508  *              'buffer'.
1509  *  \param buffer String buffer to put values found inside the parameter element.
1510  */
1511 static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer)
1512 {
1513         const char *paramname;
1514         struct ast_xml_node *node = fixnode;
1515         int hasarguments, printed = 0;
1516         char *internaltabs;
1517
1518         if (strcasecmp(ast_xml_node_get_name(node), "parameter")) {
1519                 return;
1520         }
1521
1522         hasarguments = xmldoc_has_inside(node, "argument");
1523         if (!(paramname = ast_xml_get_attribute(node, "name"))) {
1524                 /* parameter MUST have an attribute name. */
1525                 return;
1526         }
1527
1528         ast_asprintf(&internaltabs, "%s    ", tabs);
1529         if (!internaltabs) {
1530                 return;
1531         }
1532
1533         if (!hasarguments) {
1534                 ast_str_append(buffer, 0, "%s\n", paramname);
1535                 ast_xml_free_attr(paramname);
1536                 printed = 1;
1537         }
1538
1539         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1540                 if (!strcasecmp(ast_xml_node_get_name(node), "optionlist")) {
1541                         xmldoc_parse_optionlist(node, internaltabs, buffer);
1542                 } else if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) {
1543                         xmldoc_parse_enumlist(node, internaltabs, buffer);
1544                 } else if (!strcasecmp(ast_xml_node_get_name(node), "argument")) {
1545                         xmldoc_parse_argument(node, 1, internaltabs, (!hasarguments ? "        " : ""), buffer);
1546                 } else if (!strcasecmp(ast_xml_node_get_name(node), "para")) {
1547                         if (!printed) {
1548                                 ast_str_append(buffer, 0, "%s\n", paramname);
1549                                 ast_xml_free_attr(paramname);
1550                                 printed = 1;
1551                         }
1552                         xmldoc_parse_para(node, internaltabs, "\n", buffer);
1553                         continue;
1554                 } else if ((xmldoc_parse_specialtags(node, internaltabs, "\n", buffer))) {
1555                         continue;
1556                 }
1557         }
1558         ast_free(internaltabs);
1559 }
1560
1561 char *ast_xmldoc_build_arguments(const char *type, const char *name)
1562 {
1563         struct ast_xml_node *node;
1564         struct ast_str *ret = ast_str_create(128);
1565         char *retstr = NULL;
1566
1567         if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
1568                 return NULL;
1569         }
1570
1571         node = xmldoc_get_node(type, name, documentation_language);
1572
1573         if (!node || !ast_xml_node_get_children(node)) {
1574                 return NULL;
1575         }
1576
1577         /* Find the syntax field. */
1578         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1579                 if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) {
1580                         break;
1581                 }
1582         }
1583
1584         if (!node || !ast_xml_node_get_children(node)) {
1585                 /* We couldn't find the syntax node. */
1586                 return NULL;
1587         }
1588
1589         for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) {
1590                 xmldoc_parse_parameter(node, "", &ret);
1591         }
1592
1593         if (ret->used > 0) {
1594                 /* remove last '\n' */
1595                 if (ret->str[ret->used - 1] == '\n') {
1596                         ret->str[ret->used - 1] = '\0';
1597                         ret->used--;
1598                 }
1599                 retstr = ast_strdup(ret->str);
1600         }
1601         ast_free(ret);
1602
1603         return retstr;
1604 }
1605
1606 /*! \internal
1607  *  \brief Return the string within a node formatted with <para> and <variablelist> elements.
1608  *  \param node Parent node where content resides.
1609  *  \param raw If set, return the node's content without further processing.
1610  *  \param raw_wrap Wrap raw text.
1611  *  \retval NULL on error
1612  *  \retval Node content on success.
1613  */
1614 static struct ast_str *xmldoc_get_formatted(struct ast_xml_node *node, int raw_output, int raw_wrap)
1615 {
1616         struct ast_xml_node *tmp;
1617         const char *notcleanret, *tmpstr;
1618         struct ast_str *ret = ast_str_create(128);
1619
1620         if (raw_output) {
1621                 notcleanret = ast_xml_get_text(node);
1622                 tmpstr = notcleanret;
1623                 xmldoc_string_cleanup(ast_skip_blanks(notcleanret), &ret, 0);
1624                 ast_xml_free_text(tmpstr);
1625         } else {
1626                 for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) {
1627                         /* if found, parse a <para> element. */
1628                         if (xmldoc_parse_para(tmp, "", "\n", &ret)) {
1629                                 continue;
1630                         } else if (xmldoc_parse_specialtags(tmp, "", "\n", &ret)) {
1631                                 continue;
1632                         }
1633                         /* if found, parse a <variablelist> element. */
1634                         xmldoc_parse_variablelist(tmp, "", &ret);
1635                         xmldoc_parse_enumlist(tmp, "    ", &ret);
1636                 }
1637                 /* remove last '\n' */
1638                 /* XXX Don't modify ast_str internals manually */
1639                 if (ret->str[ret->used-1] == '\n') {
1640                         ret->str[ret->used-1] = '\0';
1641                         ret->used--;
1642                 }
1643         }
1644         return ret;
1645 }
1646
1647 /*!
1648  *  \brief Get the content of a field (synopsis, description, etc) from an asterisk document tree
1649  *  \param type Type of element (application, function, ...).
1650  *  \param name Name of element (Dial, Echo, Playback, ...).
1651  *  \param var Name of field to return (synopsis, description, etc).
1652  *  \param raw Field only contains text, no other elements inside it.
1653  *  \retval NULL On error.
1654  *  \retval Field text content on success.
1655  */
1656 static char *xmldoc_build_field(const char *type, const char *name, const char *var, int raw)
1657 {
1658         struct ast_xml_node *node;
1659         char *ret = NULL;
1660         struct ast_str *formatted;
1661
1662         if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
1663                 ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n");
1664                 return ret;
1665         }
1666
1667         node = xmldoc_get_node(type, name, documentation_language);
1668
1669         if (!node) {
1670                 ast_log(LOG_WARNING, "Counldn't find %s %s in XML documentation\n", type, name);
1671                 return ret;
1672         }
1673
1674         node = ast_xml_find_element(ast_xml_node_get_children(node), var, NULL, NULL);
1675
1676         if (!node || !ast_xml_node_get_children(node)) {
1677                 ast_log(LOG_DEBUG, "Cannot find variable '%s' in tree '%s'\n", name, var);
1678                 return ret;
1679         }
1680
1681         formatted = xmldoc_get_formatted(node, raw, raw);
1682         if (formatted->used > 0) {
1683                 ret = ast_strdup(formatted->str);
1684         }
1685         ast_free(formatted);
1686
1687         return ret;
1688 }
1689
1690 char *ast_xmldoc_build_synopsis(const char *type, const char *name)
1691 {
1692         return xmldoc_build_field(type, name, "synopsis", 1);
1693 }
1694
1695 char *ast_xmldoc_build_description(const char *type, const char *name)
1696 {
1697         return xmldoc_build_field(type, name, "description", 0);
1698 }
1699
1700 /*! \brief Close and unload XML documentation. */
1701 static void xmldoc_unload_documentation(void)
1702 {
1703         struct documentation_tree *doctree;
1704
1705         AST_RWLIST_WRLOCK(&xmldoc_tree);
1706         while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
1707                 ast_free(doctree->filename);
1708                 ast_xml_close(doctree->doc);
1709         }
1710         AST_RWLIST_UNLOCK(&xmldoc_tree);
1711
1712         ast_xml_finish();
1713 }
1714
1715 int ast_xmldoc_load_documentation(void)
1716 {
1717         struct ast_xml_node *root_node;
1718         struct ast_xml_doc *tmpdoc;
1719         struct documentation_tree *doc_tree;
1720         char *xmlpattern;
1721         struct ast_config *cfg = NULL;
1722         struct ast_variable *var = NULL;
1723         struct ast_flags cnfflags = { 0 };
1724         int globret, i, dup, duplicate;
1725         glob_t globbuf;
1726
1727         /* setup default XML documentation language */
1728         snprintf(documentation_language, sizeof(documentation_language), default_documentation_language);
1729
1730         if ((cfg = ast_config_load2("asterisk.conf", "" /* core can't reload */, cnfflags))) {
1731                 for (var = ast_variable_browse(cfg, "options"); var; var = var->next) {
1732                         if (!strcasecmp(var->name, "documentation_language")) {
1733                                 if (!ast_strlen_zero(var->value)) {
1734                                         snprintf(documentation_language, sizeof(documentation_language), "%s", var->value);
1735                                 }
1736                         }
1737                 }
1738                 ast_config_destroy(cfg);
1739         }
1740
1741         /* initialize the XML library. */
1742         ast_xml_init();
1743
1744         /* register function to be run when asterisk finish. */
1745         ast_register_atexit(xmldoc_unload_documentation);
1746
1747         /* Get every *-LANG.xml file inside $(ASTDATADIR)/documentation */
1748         ast_asprintf(&xmlpattern, "%s/documentation{/thirdparty/,/}*-{%s,%.2s_??,%s}.xml", ast_config_AST_DATA_DIR,
1749                         documentation_language, documentation_language, default_documentation_language);
1750         globbuf.gl_offs = 0;    /* initialize it to silence gcc */
1751         globret = glob(xmlpattern, MY_GLOB_FLAGS, NULL, &globbuf);
1752         if (globret == GLOB_NOSPACE) {
1753                 ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Not enough memory\n", xmlpattern);
1754                 ast_free(xmlpattern);
1755                 return 1;
1756         } else if (globret  == GLOB_ABORTED) {
1757                 ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Read error\n", xmlpattern);
1758                 ast_free(xmlpattern);
1759                 return 1;
1760         }
1761         ast_free(xmlpattern);
1762
1763         AST_RWLIST_WRLOCK(&xmldoc_tree);
1764         /* loop over expanded files */
1765         for (i = 0; i < globbuf.gl_pathc; i++) {
1766                 /* check for duplicates (if we already [try to] open the same file. */
1767                 duplicate = 0;
1768                 for (dup = 0; dup < i; dup++) {
1769                         if (!strcmp(globbuf.gl_pathv[i], globbuf.gl_pathv[dup])) {
1770                                 duplicate = 1;
1771                                 break;
1772                         }
1773                 }
1774                 if (duplicate) {
1775                         continue;
1776                 }
1777                 tmpdoc = NULL;
1778                 tmpdoc = ast_xml_open(globbuf.gl_pathv[i]);
1779                 if (!tmpdoc) {
1780                         ast_log(LOG_ERROR, "Could not open XML documentation at '%s'\n", globbuf.gl_pathv[i]);
1781                         continue;
1782                 }
1783                 /* Get doc root node and check if it starts with '<docs>' */
1784                 root_node = ast_xml_get_root(tmpdoc);
1785                 if (!root_node) {
1786                         ast_log(LOG_ERROR, "Error getting documentation root node");
1787                         ast_xml_close(tmpdoc);
1788                         continue;
1789                 }
1790                 /* Check root node name for malformed xmls. */
1791                 if (strcmp(ast_xml_node_get_name(root_node), "docs")) {
1792                         ast_log(LOG_ERROR, "Documentation file is not well formed!\n");
1793                         ast_xml_close(tmpdoc);
1794                         continue;
1795                 }
1796                 doc_tree = ast_calloc(1, sizeof(*doc_tree));
1797                 if (!doc_tree) {
1798                         ast_log(LOG_ERROR, "Unable to allocate documentation_tree structure!\n");
1799                         ast_xml_close(tmpdoc);
1800                         continue;
1801                 }
1802                 doc_tree->doc = tmpdoc;
1803                 doc_tree->filename = ast_strdup(globbuf.gl_pathv[i]);
1804                 AST_RWLIST_INSERT_TAIL(&xmldoc_tree, doc_tree, entry);
1805         }
1806         AST_RWLIST_UNLOCK(&xmldoc_tree);
1807
1808         globfree(&globbuf);
1809
1810         return 0;
1811 }
1812
1813 #endif /* AST_XML_DOCS */
1814
1815