diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/Makefile | 4 | ||||
-rw-r--r-- | main/asterisk.c | 6 | ||||
-rw-r--r-- | main/config.c | 18 | ||||
-rw-r--r-- | main/features.c | 141 | ||||
-rw-r--r-- | main/pbx.c | 2826 | ||||
-rw-r--r-- | main/term.c | 64 | ||||
-rw-r--r-- | main/xml.c | 195 |
7 files changed, 2789 insertions, 465 deletions
diff --git a/main/Makefile b/main/Makefile index b1ef7ef77..cab6f9b9a 100644 --- a/main/Makefile +++ b/main/Makefile @@ -28,7 +28,7 @@ OBJS= tcptls.o io.o sched.o logger.o frame.o loader.o config.o channel.o \ cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o \ strcompat.o threadstorage.o dial.o event.o adsistub.o audiohook.o \ astobj2.o hashtab.o global_datastores.o version.o \ - features.o taskprocessor.o timing.o datastore.o + features.o taskprocessor.o timing.o datastore.o xml.o # we need to link in the objects statically, not as a library, because # otherwise modules will not have them available if none of the static @@ -41,7 +41,7 @@ OBJS+=say.o AST_LIBS += $(SSL_LIB) AST_LIBS += $(BKTR_LIB) - +AST_LIBS += $(LIBXML2_LIB) ifeq ($(POLL_AVAILABLE),) OBJS+=poll.o diff --git a/main/asterisk.c b/main/asterisk.c index 73e27c229..239acd7ff 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -117,6 +117,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/devicestate.h" #include "asterisk/module.h" #include "asterisk/dsp.h" +#include "asterisk/xml.h" #include "asterisk/buildinfo.h" #include "asterisk/doxyref.h" /* Doxygen documentation */ @@ -3322,6 +3323,11 @@ int main(int argc, char *argv[]) exit(1); } +#ifdef AST_XML_DOCS + /* Load XML documentation. */ + ast_load_documentation(); +#endif + if (load_modules(1)) { /* Load modules, pre-load only */ printf("%s", term_quit()); exit(1); diff --git a/main/config.c b/main/config.c index a382a7a80..b3abe2730 100644 --- a/main/config.c +++ b/main/config.c @@ -39,24 +39,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define AST_INCLUDE_GLOB 1 -#ifdef AST_INCLUDE_GLOB -/* glob compat stuff - eventually this should go in compat.h or some - * header in include/asterisk/ - */ -#if defined(__Darwin__) || defined(__CYGWIN__) -#define GLOB_ABORTED GLOB_ABEND -#endif - -#include <glob.h> - -#ifdef SOLARIS -#define MY_GLOB_FLAGS GLOB_NOCHECK -#else -#define MY_GLOB_FLAGS (GLOB_NOMAGIC|GLOB_BRACE) -#endif - -#endif - #include "asterisk/config.h" #include "asterisk/cli.h" #include "asterisk/lock.h" diff --git a/main/features.c b/main/features.c index 82b85669c..adf5086aa 100644 --- a/main/features.c +++ b/main/features.c @@ -56,6 +56,98 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/global_datastores.h" #include "asterisk/astobj2.h" +/*** DOCUMENTATION + <application name="Bridge" language="en_US"> + <synopsis> + Bridge two channels. + </synopsis> + <syntax> + <parameter name="channel" required="true"> + <para>The current channel is bridged to the specified <replaceable>channel</replaceable>.</para> + </parameter> + <parameter name="options"> + <optionlist> + <option name="p"> + <para>Play a courtesy tone to <replaceable>channel</replaceable>.</para> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>Allows the ability to bridge two channels via the dialplan.</para> + <para>This application sets the following channel variable upon completion:</para> + <variablelist> + <variable name="BRIDGERESULT"> + <para>The result of the bridge attempt as a text string.</para> + <value name="SUCCESS" /> + <value name="FAILURE" /> + <value name="LOOP" /> + <value name="NONEXISTENT" /> + <value name="INCOMPATIBLE" /> + </variable> + </variablelist> + </description> + </application> + <application name="ParkedCall" language="en_US"> + <synopsis> + Answer a parked call. + </synopsis> + <syntax> + <parameter name="exten" required="true" /> + </syntax> + <description> + <para>Used to connect to a parked call. This application is always + registered internally and does not need to be explicitly added + into the dialplan, although you should include the <literal>parkedcalls</literal> + context. If no extension is provided, then the first available + parked call will be acquired.</para> + </description> + </application> + <application name="Park" language="en_US"> + <synopsis> + Park yourself. + </synopsis> + <syntax> + <parameter name="timeout"> + <para>A custom parking timeout for this parked call.</para> + </parameter> + <parameter name="return_context"> + <para>The context to return the call to after it times out.</para> + </parameter> + <parameter name="return_exten"> + <para>The extension to return the call to after it times out.</para> + </parameter> + <parameter name="return_priority"> + <para>The priority to return the call to after it times out.</para> + </parameter> + <parameter name="options"> + <para>A list of options for this parked call.</para> + <optionlist> + <option name="r"> + <para>Send ringing instead of MOH to the parked call.</para> + </option> + <option name="R"> + <para>Randomize the selection of a parking space.</para> + </option> + <option name="s"> + <para>Silence announcement of the parking space number.</para> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>Used to park yourself (typically in combination with a supervised + transfer to know the parking space). This application is always + registered internally and does not need to be explicitly added + into the dialplan, although you should include the <literal>parkedcalls</literal> + context (or the context specified in <filename>features.conf</filename>).</para> + <para>If you set the <variable>PARKINGEXTEN</variable> variable to an extension in your + parking context, Park() will park the call on that extension, unless + it already exists. In that case, execution will continue at next priority.</para> + </description> + </application> + ***/ + #define DEFAULT_PARK_TIME 45000 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 2000 @@ -150,41 +242,8 @@ static unsigned int atxfercallbackretries; static char *registrar = "features"; /*!< Registrar for operations */ /* module and CLI command definitions */ -static char *synopsis = "Answer a parked call"; - -static char *descrip = "ParkedCall(exten): " -"Used to connect to a parked call. This application is always\n" -"registered internally and does not need to be explicitly added\n" -"into the dialplan, although you should include the 'parkedcalls'\n" -"context. If no extension is provided, then the first available\n" -"parked call will be acquired.\n"; - static char *parkcall = PARK_APP_NAME; -static char *synopsis2 = "Park yourself"; - -static char *descrip2 = -" Park([timeout,[return_context,[return_exten,[return_priority,[options]]]]]):" -"Used to park yourself (typically in combination with a supervised\n" -"transfer to know the parking space). This application is always\n" -"registered internally and does not need to be explicitly added\n" -"into the dialplan, although you should include the 'parkedcalls'\n" -"context (or the context specified in features.conf).\n\n" -"If you set the PARKINGEXTEN variable to an extension in your\n" -"parking context, Park() will park the call on that extension, unless\n" -"it already exists. In that case, execution will continue at next\n" -"priority.\n" -" This application can accept arguments as well.\n" -" timeout - A custom parking timeout for this parked call.\n" -" return_context - The context to return the call to after it times out.\n" -" return_exten - The extension to return the call to after it times out.\n" -" return_priority - The priority to return the call to after it times out.\n" -" options - A list of options for this parked call. Valid options are:\n" -" 'r' - Send ringing instead of MOH to the parked call.\n" -" 'R' - Randomize the selection of a parking space.\n" -" 's' - Silence announcement of the parking space number.\n" -""; - static struct ast_app *monitor_app = NULL; static int monitor_ok = 1; @@ -3962,16 +4021,6 @@ int ast_pickup_call(struct ast_channel *chan) } static char *app_bridge = "Bridge"; -static char *bridge_synopsis = "Bridge two channels"; -static char *bridge_descrip = -"Usage: Bridge(channel[,options])\n" -" Allows the ability to bridge two channels via the dialplan.\n" -"The current channel is bridged to the specified 'channel'.\n" -" Options:\n" -" p - Play a courtesy tone to 'channel'.\n" -"This application sets the following channel variable upon completion:\n" -" BRIDGERESULT The result of the bridge attempt as a text string, one of\n" -" SUCCESS | FAILURE | LOOP | NONEXISTENT | INCOMPATIBLE\n"; enum { BRIDGE_OPT_PLAYTONE = (1 << 0), @@ -4113,7 +4162,7 @@ int ast_features_init(void) { int res; - ast_register_application2(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip, NULL); + ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL); parkinglots = ao2_container_alloc(7, parkinglot_hash_cb, parkinglot_cmp_cb); @@ -4121,9 +4170,9 @@ int ast_features_init(void) return res; ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL); - res = ast_register_application2(parkedcall, park_exec, synopsis, descrip, NULL); + res = ast_register_application2(parkedcall, park_exec, NULL, NULL, NULL); if (!res) - res = ast_register_application2(parkcall, park_call_exec, synopsis2, descrip2, NULL); + res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL); if (!res) { ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls"); ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park, "Park a channel", mandescr_park); diff --git a/main/pbx.c b/main/pbx.c index b86cb38ce..0df8b1377 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -12,7 +12,7 @@ * channels for your use. * * This program is free software, distributed under the terms of - * the GNU General Public License Version 2. See the LICENSE file +* the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ @@ -59,12 +59,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/musiconhold.h" #include "asterisk/app.h" #include "asterisk/devicestate.h" -#include "asterisk/stringfields.h" #include "asterisk/event.h" #include "asterisk/hashtab.h" #include "asterisk/module.h" #include "asterisk/indications.h" #include "asterisk/taskprocessor.h" +#include "asterisk/xml.h" /*! * \note I M P O R T A N T : @@ -87,6 +87,541 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") * */ +/*** DOCUMENTATION + <application name="Answer" language="en_US"> + <synopsis> + Answer a channel if ringing. + </synopsis> + <syntax> + <parameter name="delay"> + <para>Asterisk will wait this number of milliseconds before returning to + the dialplan after answering the call.</para> + </parameter> + </syntax> + <description> + <para>If the call has not been answered, this application will + answer it. Otherwise, it has no effect on the call.</para> + </description> + </application> + <application name="BackGround" language="en_US"> + <synopsis> + Play an audio file while waiting for digits of an extension to go to. + </synopsis> + <syntax> + <parameter name="filenames" required="true" argsep="&"> + <argument name="filename1" required="true" /> + <argument name="filename2" multiple="true" /> + </parameter> + <parameter name="options"> + <optionlist> + <option name="s"> + <para>Causes the playback of the message to be skipped + if the channel is not in the <literal>up</literal> state (i.e. it + hasn't been answered yet). If this happens, the + application will return immediately.</para> + </option> + <option name="n"> + <para>Don't answer the channel before playing the files.</para> + </option> + <option name="m"> + <para>Only break if a digit hit matches a one digit + extension in the destination context.</para> + </option> + </optionlist> + </parameter> + <parameter name="langoverride"> + <para>Explicitly specifies which language to attempt to use for the requested sound files.</para> + </parameter> + <parameter name="context"> + <para>This is the dialplan context that this application will use when exiting + to a dialed extension.</para> + </parameter> + </syntax> + <description> + <para>This application will play the given list of files <emphasis>(do not put extension)</emphasis> + while waiting for an extension to be dialed by the calling channel. To continue waiting + for digits after this application has finished playing files, the <literal>WaitExten</literal> + application should be used.</para> + <para>If one of the requested sound files does not exist, call processing will be terminated.</para> + <para>This application sets the following channel variable upon completion:</para> + <variablelist> + <variable name="BACKGROUNDSTATUS"> + <para>The status of the background attempt as a text string.</para> + <value name="SUCCESS" /> + <value name="FAILED" /> + </variable> + </variablelist> + </description> + <see-also> + <ref type="application">Playback</ref> + </see-also> + </application> + <application name="Busy" language="en_US"> + <synopsis> + Indicate the Busy condition. + </synopsis> + <syntax> + <parameter name="timeout"> + <para>If specified, the calling channel will be hung up after the specified number of seconds. + Otherwise, this application will wait until the calling channel hangs up.</para> + </parameter> + </syntax> + <description> + <para>This application will indicate the busy condition to the calling channel.</para> + </description> + </application> + <application name="Congestion" language="en_US"> + <synopsis> + Indicate the Congestion condition. + </synopsis> + <syntax> + <parameter name="timeout"> + <para>If specified, the calling channel will be hung up after the specified number of seconds. + Otherwise, this application will wait until the calling channel hangs up.</para> + </parameter> + </syntax> + <description> + <para>This application will indicate the congestion condition to the calling channel.</para> + </description> + </application> + <application name="ExecIfTime" language="en_US"> + <synopsis> + Conditional application execution based on the current time. + </synopsis> + <syntax argsep="?"> + <parameter name="day_condition" required="true"> + <argument name="times" required="true" /> + <argument name="weekdays" required="true" /> + <argument name="mdays" required="true" /> + <argument name="months" required="true" /> + </parameter> + <parameter name="appname" required="true" hasparams="optional"> + <argument name="appargs" required="true" /> + </parameter> + </syntax> + <description> + <para>This application will execute the specified dialplan application, with optional + arguments, if the current time matches the given time specification.</para> + </description> + </application> + <application name="Goto" language="en_US"> + <synopsis> + Jump to a particular priority, extension, or context. + </synopsis> + <syntax> + <parameter name="context" /> + <parameter name="extensions" /> + <parameter name="priority" required="true" /> + </syntax> + <description> + <para>This application will set the current context, extension, and priority in the channel structure. + After it completes, the pbx engine will continue dialplan execution at the specified location. + If no specific <replaceable>extension</replaceable>, or <replaceable>extension</replaceable> and + <replaceable>context</replaceable>, are specified, then this application will + just set the specified <replaceable>priority</replaceable> of the current extension.</para> + <para>At least a <replaceable>priority</replaceable> is required as an argument, or the goto will + return a <literal>-1</literal>, and the channel and call will be terminated.</para> + <para>If the location that is put into the channel information is bogus, and asterisk cannot + find that location in the dialplan, then the execution engine will try to find and execute the code in + the <literal>i</literal> (invalid) extension in the current context. If that does not exist, it will try to execute the + <literal>h</literal> extension. If either or neither the <literal>h</literal> or <literal>i</literal> extensions + have been defined, the channel is hung up, and the execution of instructions on the channel is terminated. + What this means is that, for example, you specify a context that does not exist, then + it will not be possible to find the <literal>h</literal> or <literal>i</literal> extensions, + and the call will terminate!</para> + </description> + </application> + <application name="GotoIf" language="en_US"> + <synopsis> + Conditional goto. + </synopsis> + <syntax argsep="?"> + <parameter name="condition" required="true" /> + <parameter name="destination" required="true" argsep=":"> + <argument name="labeliftrue"> + <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.</para> + </argument> + <argument name="labeliffalse"> + <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.</para> + </argument> + </parameter> + </syntax> + <description> + <para>This application will set the current context, extension, and priority in the channel structure + based on the evaluation of the given condition. After this application completes, the + pbx engine will continue dialplan execution at the specified location in the dialplan. + The labels are specified with the same syntax as used within the Goto application. + If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the + next instruction. If the target location is bogus, and does not exist, the execution engine will try + to find and execute the code in the <literal>i</literal> (invalid) extension in the current context. + If that does not exist, it will try to execute the <literal>h</literal> extension. + If either or neither the <literal>h</literal> or <literal>i</literal> extensions have been defined, + the channel is hung up, and the execution of instructions on the channel is terminated. + Remember that this command can set the current context, and if the context specified + does not exist, then it will not be able to find any 'h' or 'i' extensions there, and + the channel and call will both be terminated!.</para> + </description> + </application> + <application name="GotoIfTime" language="en_US"> + <synopsis> + Conditional Goto based on the current time. + </synopsis> + <syntax argsep="?"> + <parameter name="condition" required="true"> + <argument name="times" required="true" /> + <argument name="weekdays" required="true" /> + <argument name="mdays" required="true" /> + <argument name="months" required="true" /> + </parameter> + <parameter name="destination" required="true" argsep=":"> + <argument name="labeliftrue" /> + <argument name="labeliffalse" /> + </parameter> + </syntax> + <description> + <para>This application will set the context, extension, and priority in the channel structure + based on the evaluation of the given time specification. After this application completes, + the pbx engine will continue dialplan execution at the specified location in the dialplan. + If the current time is within the given time specification, the channel will continue at + <replaceable>labeliftrue</replaceable>. Otherwise the channel will continue at <replaceable>labeliffalse</replaceable>. + If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next + instruction. If the target jump location is bogus, the same actions would be taken as for <literal>Goto</literal>. + Further information on the time specification can be found in examples + illustrating how to do time-based context includes in the dialplan.</para> + </description> + </application> + <application name="ImportVar" language="en_US"> + <synopsis> + Import a variable from a channel into a new variable. + </synopsis> + <syntax argsep="="> + <parameter name="newvar" required="true" /> + <parameter name="vardata" required="true"> + <argument name="channelname" required="true" /> + <argument name="variable" required="true" /> + </parameter> + </syntax> + <description> + <para>This application imports a <replaceable>variable</replaceable> from the specified + <replaceable>channel</replaceable> (as opposed to the current one) and stores it as a variable + (<replaceable>newvar</replaceable>) in the current channel (the channel that is calling this + application). Variables created by this application have the same inheritance properties as those + created with the <literal>Set</literal> application.</para> + </description> + <see-also> + <ref type="application">Set</ref> + </see-also> + </application> + <application name="Hangup" language="en_US"> + <synopsis> + Hang up the calling channel. + </synopsis> + <syntax> + <parameter name="causecode"> + <para>If a <replaceable>causecode</replaceable> is given the channel's + hangup cause will be set to the given value.</para> + </parameter> + </syntax> + <description> + <para>This application will hang up the calling channel.</para> + </description> + </application> + <application name="Incomplete" language="en_US"> + <synopsis> + Returns AST_PBX_INCOMPLETE value. + </synopsis> + <syntax> + <parameter name="n"> + <para>If specified, then Incomplete will not attempt to answer the channel first.</para> + <note><para>Most channel types need to be in Answer state in order to receive DTMF.</para></note> + </parameter> + </syntax> + <description> + <para>Signals the PBX routines that the previous matched extension is incomplete + and that further input should be allowed before matching can be considered + to be complete. Can be used within a pattern match when certain criteria warrants + a longer match.</para> + + </description> + </application> + <application name="KeepAlive" language="en_US"> + <synopsis> + Returns AST_PBX_KEEPALIVE value. + </synopsis> + <syntax /> + <description> + <para>This application is chiefly meant for internal use with Gosubs. Please do not run + it alone from the dialplan!</para> + </description> + </application> + <application name="NoOp" language="en_US"> + <synopsis> + Do Nothing (No Operation). + </synopsis> + <syntax> + <parameter name="text"> + <para>Any text provided can be viewed at the Asterisk CLI.</para> + </parameter> + </syntax> + <description> + <para>This application does nothing. However, it is useful for debugging purposes.</para> + <para>This method can be used to see the evaluations of variables or functions without having any effect.</para> + </description> + <see-also> + <ref type="application">Verbose</ref> + </see-also> + </application> + <application name="Proceeding" language="en_US"> + <synopsis> + Indicate proceeding. + </synopsis> + <syntax /> + <description> + <para>This application will request that a proceeding message be provided to the calling channel.</para> + </description> + </application> + <application name="Progress" language="en_US"> + <synopsis> + Indicate progress. + </synopsis> + <syntax /> + <description> + <para>This application will request that in-band progress information be provided to the calling channel.</para> + </description> + </application> + <application name="RaiseException" language="en_US"> + <synopsis> + Handle an exceptional condition. + </synopsis> + <syntax> + <parameter name="reason" required="true" /> + </syntax> + <description> + <para>This application will jump to the <literal>e</literal> extension in the current context, setting the + dialplan function EXCEPTION(). If the <literal>e</literal> extension does not exist, the call will hangup.</para> + </description> + </application> + <application name="ResetCDR" language="en_US"> + <synopsis> + Resets the Call Data Record. + </synopsis> + <syntax> + <parameter name="options"> + <optionlist> + <option name="w"> + <para>Store the current CDR record before resetting it.</para> + </option> + <option name="a"> + <para>Store any stacked records.</para> + </option> + <option name="v"> + <para>Save CDR variables.</para> + </option> + <option name="e"> + <para>Enable CDR only (negate effects of NoCDR).</para> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>This application causes the Call Data Record to be reset.</para> + </description> + </application> + <application name="Ringing" language="en_US"> + <synopsis> + Indicate ringing tone. + </synopsis> + <syntax /> + <description> + <para>This application will request that the channel indicate a ringing tone to the user.</para> + </description> + </application> + <application name="SayAlpha" language="en_US"> + <synopsis> + Say Alpha. + </synopsis> + <syntax> + <parameter name="string" required="true" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the letters of the + given <replaceable>string</replaceable>.</para> + </description> + </application> + <application name="SayDigits" language="en_US"> + <synopsis> + Say Digits. + </synopsis> + <syntax> + <parameter name="digits" required="true" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the digits of + the given number. This will use the language that is currently set for the channel.</para> + </description> + </application> + <application name="SayNumber" language="en_US"> + <synopsis> + Say Number. + </synopsis> + <syntax> + <parameter name="digits" required="true" /> + <parameter name="gender" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the given <replaceable>digits</replaceable>. + Optionally, a <replaceable>gender</replaceable> may be specified. This will use the language that is currently + set for the channel. See the LANGUAGE() function for more information on setting the language for the channel.</para> + </description> + </application> + <application name="SayPhonetic" language="en_US"> + <synopsis> + Say Phonetic. + </synopsis> + <syntax> + <parameter name="string" required="true" /> + </syntax> + <description> + <para>This application will play the sounds from the phonetic alphabet that correspond to the + letters in the given <replaceable>string</replaceable>.</para> + </description> + </application> + <application name="Set" language="en_US"> + <synopsis> + Set channel variable or function value. + </synopsis> + <syntax argsep="="> + <parameter name="name" required="true" /> + <parameter name="value" required="true" /> + </syntax> + <description> + <para>This function can be used to set the value of channel variables or dialplan functions. + When setting variables, if the variable name is prefixed with <literal>_</literal>, + the variable will be inherited into channels created from the current channel. + If the variable name is prefixed with <literal>__</literal>, the variable will be + inherited into channels created from the current channel and all children channels.</para> + <note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have + a <literal>[compat]</literal> category, and you have <literal>app_set = 1.6</literal> under that,then + the behavior of this app changes, and does not strip surrounding quotes from the right hand side as + it did previously in 1.4. The <literal>app_set = 1.6</literal> is only inserted if <literal>make samples</literal> + is executed, or if users insert this by hand into the <filename>asterisk.conf</filename> file. + The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar) + were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly + protect separators and quotes in various database access strings has been greatly + reduced by these changes.</para></note> + </description> + </application> + <application name="MSet" language="en_US"> + <synopsis> + Set channel variable(s) or function value(s). + </synopsis> + <syntax> + <parameter name="set1" required="true" argsep="="> + <argument name="name1" required="true" /> + <argument name="value1" required="true" /> + </parameter> + <parameter name="set2" multiple="true" argsep="="> + <argument name="name2" required="true" /> + <argument name="value2" required="true" /> + </parameter> + </syntax> + <description> + <para>This function can be used to set the value of channel variables or dialplan functions. + When setting variables, if the variable name is prefixed with <literal>_</literal>, + the variable will be inherited into channels created from the current channel + If the variable name is prefixed with <literal>__</literal>, the variable will be + inherited into channels created from the current channel and all children channels. + MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus + prone to doing things that you may not expect. For example, it strips surrounding + double-quotes from the right-hand side (value). If you need to put a separator + character (comma or vert-bar), you will need to escape them by inserting a backslash + before them. Avoid its use if possible.</para> + </description> + </application> + <application name="SetAMAFlags" language="en_US"> + <synopsis> + Set the AMA Flags. + </synopsis> + <syntax> + <parameter name="flag" /> + </syntax> + <description> + <para>This application will set the channel's AMA Flags for billing purposes.</para> + </description> + </application> + <application name="Wait" language="en_US"> + <synopsis> + Waits for some time. + </synopsis> + <syntax> + <parameter name="seconds" required="true"> + <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the + application to wait for 1.5 seconds.</para> + </parameter> + </syntax> + <description> + <para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para> + </description> + </application> + <application name="WaitExten" language="en_US"> + <synopsis> + Waits for an extension to be entered. + </synopsis> + <syntax> + <parameter name="seconds"> + <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the + application to wait for 1.5 seconds.</para> + </parameter> + <parameter name="options"> + <optionlist> + <option name="m"> + <para>Provide music on hold to the caller while waiting for an extension.</para> + <argument name="x"> + <para>Specify the class for music on hold.</para> + </argument> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>This application waits for the user to enter a new extension for a specified number + of <replaceable>seconds</replaceable>.</para> + </description> + <see-also> + <ref type="application">Playback</ref> + <ref type="application">Background</ref> + </see-also> + </application> + <function name="EXCEPTION" language="en_US"> + <synopsis> + Retrieve the details of the current dialplan exception. + </synopsis> + <syntax> + <parameter name="field" required="true"> + <para>The following fields are available for retrieval:</para> + <enumlist> + <enum name="reason"> + <para>INVALID, ERROR, RESPONSETIMEOUT, ABSOLUTETIMEOUT, or custom + value set by the RaiseException() application</para> + </enum> + <enum name="context"> + <para>The context executing when the exception occurred.</para> + </enum> + <enum name="exten"> + <para>The extension executing when the exception occurred.</para> + </enum> + <enum name="priority"> + <para>The numeric priority executing when the exception occurred.</para> + </enum> + </enumlist> + </parameter> + </syntax> + <description> + <para>Retrieve the details (specified <replaceable>field</replaceable>) of the current dialplan exception.</para> + </description> + </function> + ***/ + #ifdef LOW_MEMORY #define EXT_DATA_SIZE 256 #else @@ -220,12 +755,17 @@ struct ast_context { char name[0]; /*!< Name of the context */ }; - /*! \brief ast_app: A registered application */ struct ast_app { int (*execute)(struct ast_channel *chan, void *data); - const char *synopsis; /*!< Synopsis text for 'show applications' */ - const char *description; /*!< Description (help text) for 'show application <name>' */ + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(synopsis); /*!< Synopsis text for 'show applications' */ + AST_STRING_FIELD(description); /*!< Description (help text) for 'show application <name>' */ + AST_STRING_FIELD(syntax); /*!< Syntax text for 'core show applications' */ + AST_STRING_FIELD(arguments); /*!< Arguments description */ + AST_STRING_FIELD(seealso); /*!< See also */ + ); + enum ast_doc_src docsrc;/*!< Where the documentation come from. */ AST_RWLIST_ENTRY(ast_app) list; /*!< Next app in list */ struct ast_module *module; /*!< Module this app belongs to */ char name[0]; /*!< Name of the application */ @@ -281,6 +821,42 @@ struct pbx_exception { int priority; /*!< Priority associated with this exception */ }; +#ifdef AST_XML_DOCS +/*! \brief Default documentation language. */ +static const char default_documentation_language[] = "en_US"; + +/*! \brief Number of columns to print when showing the XML documentation with a + * 'core show application/function *' CLI command. Used in text wrapping.*/ +static const int xmldoc_text_columns = 74; + +/*! \brief This is a value that we will use to let the wrapping mechanism move the cursor + * backward and forward xmldoc_max_diff positions before cutting the middle of a + * word, trying to find a space or a \n. */ +static const int xmldoc_max_diff = 5; + +/*! \brief XML documentation language. */ +static char documentation_language[6]; + +/*! \brief XML documentation tree */ +struct documentation_tree { + char *filename; /*!< XML document filename. */ + struct ast_xml_doc *doc; /*!< Open document pointer. */ + AST_RWLIST_ENTRY(documentation_tree) entry; +}; + +/*! + * \brief Container of documentation trees + * + * \note A RWLIST is a sufficient container type to use here for now. + * However, some changes will need to be made to implement ref counting + * if reload support is added in the future. + */ +static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree); +#endif + +/*! \brief Maximum number of characters needed for a color escape sequence, plus a null char */ +#define MAX_ESCAPE_CHARS 23 + static int pbx_builtin_answer(struct ast_channel *, void *); static int pbx_builtin_goto(struct ast_channel *, void *); static int pbx_builtin_hangup(struct ast_channel *, void *); @@ -326,6 +902,9 @@ static unsigned int hashtab_hash_extens(const void *obj); static unsigned int hashtab_hash_priority(const void *obj); static unsigned int hashtab_hash_labels(const void *obj); static void __ast_internal_context_destroy( struct ast_context *con); +#ifdef AST_XML_DOCS +static char *xmldoc_colorization(const char *bwinput); +#endif /* a func for qsort to use to sort a char array */ static int compare_char(const void *a, const void *b) @@ -433,296 +1012,38 @@ static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); static struct pbx_builtin { char name[AST_MAX_APP]; int (*execute)(struct ast_channel *chan, void *data); - char *synopsis; - char *description; } builtins[] = { /* These applications are built into the PBX core and do not need separate modules */ - { "Answer", pbx_builtin_answer, - "Answer a channel if ringing", - " Answer([delay]): If the call has not been answered, this application will\n" - "answer it. Otherwise, it has no effect on the call. If a delay is specified,\n" - "Asterisk will wait this number of milliseconds before returning to\n" - "the dialplan after answering the call.\n" - }, - - { "BackGround", pbx_builtin_background, - "Play an audio file while waiting for digits of an extension to go to.", - " Background(filename1[&filename2...][,options[,langoverride][,context]]):\n" - "This application will play the given list of files (do not put extension)\n" - "while waiting for an extension to be dialed by the calling channel. To\n" - "continue waiting for digits after this application has finished playing\n" - "files, the WaitExten application should be used. The 'langoverride' option\n" - "explicitly specifies which language to attempt to use for the requested sound\n" - "files. If a 'context' is specified, this is the dialplan context that this\n" - "application will use when exiting to a dialed extension." - " If one of the requested sound files does not exist, call processing will be\n" - "terminated.\n" - " Options:\n" - " s - Causes the playback of the message to be skipped\n" - " if the channel is not in the 'up' state (i.e. it\n" - " hasn't been answered yet). If this happens, the\n" - " application will return immediately.\n" - " n - Don't answer the channel before playing the files.\n" - " m - Only break if a digit hit matches a one digit\n" - " extension in the destination context.\n" - "This application sets the following channel variable upon completion:\n" - " BACKGROUNDSTATUS The status of the background attempt as a text string, one of\n" - " SUCCESS | FAILED\n" - "See Also: Playback (application) -- Play sound file(s) to the channel,\n" - " that cannot be interrupted\n" - }, - - { "Busy", pbx_builtin_busy, - "Indicate the Busy condition", - " Busy([timeout]): This application will indicate the busy condition to\n" - "the calling channel. If the optional timeout is specified, the calling channel\n" - "will be hung up after the specified number of seconds. Otherwise, this\n" - "application will wait until the calling channel hangs up.\n" - }, - - { "Congestion", pbx_builtin_congestion, - "Indicate the Congestion condition", - " Congestion([timeout]): This application will indicate the congestion\n" - "condition to the calling channel. If the optional timeout is specified, the\n" - "calling channel will be hung up after the specified number of seconds.\n" - "Otherwise, this application will wait until the calling channel hangs up.\n" - }, - - { "ExecIfTime", pbx_builtin_execiftime, - "Conditional application execution based on the current time", - " ExecIfTime(<times>,<weekdays>,<mdays>,<months>?appname[(appargs)]):\n" - "This application will execute the specified dialplan application, with optional\n" - "arguments, if the current time matches the given time specification.\n" - }, - - { "Goto", pbx_builtin_goto, - "Jump to a particular priority, extension, or context", - " Goto([[context,]extension,]priority): This application will set the current\n" - "context, extension, and priority in the channel structure. After it completes, the\n" - "pbx engine will continue dialplan execution at the specified location.\n" - "If no specific extension, or extension and context, are specified, then this\n" - "application will just set the specified priority of the current extension.\n" - " At least a priority is required as an argument, or the goto will return a -1,\n" - "and the channel and call will be terminated.\n" - " If the location that is put into the channel information is bogus, and asterisk cannot\n" - "find that location in the dialplan,\n" - "then the execution engine will try to find and execute the code in the 'i' (invalid)\n" - "extension in the current context. If that does not exist, it will try to execute the\n" - "'h' extension. If either or neither the 'h' or 'i' extensions have been defined, the\n" - "channel is hung up, and the execution of instructions on the channel is terminated.\n" - "What this means is that, for example, you specify a context that does not exist, then\n" - "it will not be possible to find the 'h' or 'i' extensions, and the call will terminate!\n" - }, - - { "GotoIf", pbx_builtin_gotoif, - "Conditional goto", - " GotoIf(condition?[labeliftrue]:[labeliffalse]): This application will set the current\n" - "context, extension, and priority in the channel structure based on the evaluation of\n" - "the given condition. After this application completes, the\n" - "pbx engine will continue dialplan execution at the specified location in the dialplan.\n" - "The channel will continue at\n" - "'labeliftrue' if the condition is true, or 'labeliffalse' if the condition is\n" - "false. The labels are specified with the same syntax as used within the Goto\n" - "application. If the label chosen by the condition is omitted, no jump is\n" - "performed, and the execution passes to the next instruction.\n" - "If the target location is bogus, and does not exist, the execution engine will try \n" - "to find and execute the code in the 'i' (invalid)\n" - "extension in the current context. If that does not exist, it will try to execute the\n" - "'h' extension. If either or neither the 'h' or 'i' extensions have been defined, the\n" - "channel is hung up, and the execution of instructions on the channel is terminated.\n" - "Remember that this command can set the current context, and if the context specified\n" - "does not exist, then it will not be able to find any 'h' or 'i' extensions there, and\n" - "the channel and call will both be terminated!\n" - }, - - { "GotoIfTime", pbx_builtin_gotoiftime, - "Conditional Goto based on the current time", - " GotoIfTime(<times>,<weekdays>,<mdays>,<months>?[labeliftrue]:[labeliffalse]):\n" - "This application will set the context, extension, and priority in the channel structure\n" - "based on the evaluation of the given time specification. After this application completes,\n" - "the pbx engine will continue dialplan execution at the specified location in the dialplan.\n" - "If the current time is within the given time specification, the channel will continue at\n" - "'labeliftrue'. Otherwise the channel will continue at 'labeliffalse'. If the label chosen\n" - "by the condition is omitted, no jump is performed, and execution passes to the next\n" - "instruction. If the target jump location is bogus, the same actions would be taken as for\n" - "Goto.\n" - "Further information on the time specification can be found in examples\n" - "illustrating how to do time-based context includes in the dialplan.\n" - }, - - { "ImportVar", pbx_builtin_importvar, - "Import a variable from a channel into a new variable", - " ImportVar(newvar=channelname,variable): This application imports a variable\n" - "from the specified channel (as opposed to the current one) and stores it as\n" - "a variable in the current channel (the channel that is calling this\n" - "application). Variables created by this application have the same inheritance\n" - "properties as those created with the Set application. See the documentation for\n" - "Set for more information.\n" - }, - - { "Hangup", pbx_builtin_hangup, - "Hang up the calling channel", - " Hangup([causecode]): This application will hang up the calling channel.\n" - "If a causecode is given the channel's hangup cause will be set to the given\n" - "value.\n" - }, - - { "Incomplete", pbx_builtin_incomplete, - "returns AST_PBX_INCOMPLETE value", - " Incomplete([n]): Signals the PBX routines that the previous matched extension\n" - "is incomplete and that further input should be allowed before matching can\n" - "be considered to be complete. Can be used within a pattern match when\n" - "certain criteria warrants a longer match.\n" - " If the 'n' option is specified, then Incomplete will not attempt to answer\n" - "the channel first. Note that most channel types need to be in Answer state\n" - "in order to receive DTMF.\n" - }, - - { "KeepAlive", pbx_builtin_keepalive, - "returns AST_PBX_KEEPALIVE value", - " KeepAlive(): This application is chiefly meant for internal use with Gosubs.\n" - "Please do not run it alone from the dialplan!\n" - }, - - { "NoOp", pbx_builtin_noop, - "Do Nothing (No Operation)", - " NoOp(): This application does nothing. However, it is useful for debugging\n" - "purposes. Any text that is provided as arguments to this application can be\n" - "viewed at the Asterisk CLI. This method can be used to see the evaluations of\n" - "variables or functions without having any effect. Alternatively, see the\n" - "Verbose() application for finer grain control of output at custom verbose levels.\n" - }, - - { "Proceeding", pbx_builtin_proceeding, - "Indicate proceeding", - " Proceeding(): This application will request that a proceeding message\n" - "be provided to the calling channel.\n" - }, - - { "Progress", pbx_builtin_progress, - "Indicate progress", - " Progress(): This application will request that in-band progress information\n" - "be provided to the calling channel.\n" - }, - - { "RaiseException", pbx_builtin_raise_exception, - "Handle an exceptional condition", - " RaiseException(<reason>): This application will jump to the \"e\" extension\n" - "in the current context, setting the dialplan function EXCEPTION(). If the \"e\"\n" - "extension does not exist, the call will hangup.\n" - }, - - { "ResetCDR", pbx_builtin_resetcdr, - "Resets the Call Data Record", - " ResetCDR([options]): This application causes the Call Data Record to be\n" - "reset.\n" - " Options:\n" - " w -- Store the current CDR record before resetting it.\n" - " a -- Store any stacked records.\n" - " v -- Save CDR variables.\n" - " e -- Enable CDR only (negate effects of NoCDR).\n" - }, - - { "Ringing", pbx_builtin_ringing, - "Indicate ringing tone", - " Ringing(): This application will request that the channel indicate a ringing\n" - "tone to the user.\n" - }, - - { "SayAlpha", pbx_builtin_saycharacters, - "Say Alpha", - " SayAlpha(string): This application will play the sounds that correspond to\n" - "the letters of the given string.\n" - }, - - { "SayDigits", pbx_builtin_saydigits, - "Say Digits", - " SayDigits(digits): This application will play the sounds that correspond\n" - "to the digits of the given number. This will use the language that is currently\n" - "set for the channel. See the LANGUAGE function for more information on setting\n" - "the language for the channel.\n" - }, - - { "SayNumber", pbx_builtin_saynumber, - "Say Number", - " SayNumber(digits[,gender]): This application will play the sounds that\n" - "correspond to the given number. Optionally, a gender may be specified.\n" - "This will use the language that is currently set for the channel. See the\n" - "LANGUAGE function for more information on setting the language for the channel.\n" - }, - - { "SayPhonetic", pbx_builtin_sayphonetic, - "Say Phonetic", - " SayPhonetic(string): This application will play the sounds from the phonetic\n" - "alphabet that correspond to the letters in the given string.\n" - }, - - { "Set", pbx_builtin_setvar, - "Set channel variable or function value", - " Set(name=value)\n" - "This function can be used to set the value of channel variables or dialplan\n" - "functions. When setting variables, if the variable name is prefixed with _,\n" - "the variable will be inherited into channels created from the current\n" - "channel. If the variable name is prefixed with __, the variable will be\n" - "inherited into channels created from the current channel and all children\n" - "channels.\n" - "Compatibility note: If (and only if), in /etc/asterisk/asterisk.conf, you have a [compat]\n" - "category, and you have app_set = 1.6 under that, then the behavior of this\n" - "app changes, and does not strip surrounding quotes from the right hand side\n" - "as it did previously in 1.4. The app_set = 1.6 is only inserted if 'make samples'\n" - "is executed, or if users insert this by hand into the asterisk.conf file.\n" - "/nThe advantages of not stripping out quoting, and not caring about the\n" - "separator characters (comma and vertical bar) were sufficient to make these\n" - "changes in 1.6. Confusion about how many backslashes would be needed to properly\n" - "protect separators and quotes in various database access strings has been greatly\n" - "reduced by these changes.\n" - }, - - { "MSet", pbx_builtin_setvar_multiple, - "Set channel variable(s) or function value(s)", - " MSet(name1=value1,name2=value2,...)\n" - "This function can be used to set the value of channel variables or dialplan\n" - "functions. When setting variables, if the variable name is prefixed with _,\n" - "the variable will be inherited into channels created from the current\n" - "channel. If the variable name is prefixed with __, the variable will be\n" - "inherited into channels created from the current channel and all children\n" - "channels.\n\n" - "MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus\n" - "prone to doing things that you may not expect. For example, it strips surrounding\n" - "double-quotes from the right-hand side (value). If you need to put a separator\n" - "character (comma or vert-bar), you will need to escape them by inserting a backslash\n" - "before them. Avoid its use if possible.\n" - }, - - { "SetAMAFlags", pbx_builtin_setamaflags, - "Set the AMA Flags", - " SetAMAFlags([flag]): This application will set the channel's AMA Flags for\n" - " billing purposes.\n" - }, - - { "Wait", pbx_builtin_wait, - "Waits for some time", - " Wait(seconds): This application waits for a specified number of seconds.\n" - "Then, dialplan execution will continue at the next priority.\n" - " Note that the seconds can be passed with fractions of a second. For example,\n" - "'1.5' will ask the application to wait for 1.5 seconds.\n" - }, - - { "WaitExten", pbx_builtin_waitexten, - "Waits for an extension to be entered", - " WaitExten([seconds][,options]): This application waits for the user to enter\n" - "a new extension for a specified number of seconds.\n" - " Note that the seconds can be passed with fractions of a second. For example,\n" - "'1.5' will ask the application to wait for 1.5 seconds.\n" - " Options:\n" - " m[(x)] - Provide music on hold to the caller while waiting for an extension.\n" - " Optionally, specify the class for music on hold within parenthesis.\n" - "See Also: Playback(application), Background(application).\n" - }, - + { "Answer", pbx_builtin_answer }, + { "BackGround", pbx_builtin_background }, + { "Busy", pbx_builtin_busy }, + { "Congestion", pbx_builtin_congestion }, + { "ExecIfTime", pbx_builtin_execiftime }, + { "Goto", pbx_builtin_goto }, + { "GotoIf", pbx_builtin_gotoif }, + { "GotoIfTime", pbx_builtin_gotoiftime }, + { "ImportVar", pbx_builtin_importvar }, + { "Hangup", pbx_builtin_hangup }, + { "Incomplete", pbx_builtin_incomplete }, + { "KeepAlive", pbx_builtin_keepalive }, + { "NoOp", pbx_builtin_noop }, + { "Proceeding", pbx_builtin_proceeding }, + { "Progress", pbx_builtin_progress }, + { "RaiseException", pbx_builtin_raise_exception }, + { "ResetCDR", pbx_builtin_resetcdr }, + { "Ringing", pbx_builtin_ringing }, + { "SayAlpha", pbx_builtin_saycharacters }, + { "SayDigits", pbx_builtin_saydigits }, + { "SayNumber", pbx_builtin_saynumber }, + { "SayPhonetic", pbx_builtin_sayphonetic }, + { "Set", pbx_builtin_setvar }, + { "MSet", pbx_builtin_setvar_multiple }, + { "SetAMAFlags", pbx_builtin_setamaflags }, + { "Wait", pbx_builtin_wait }, + { "WaitExten", pbx_builtin_waitexten } }; static struct ast_context *contexts; @@ -2618,15 +2939,6 @@ static int acf_exception_read(struct ast_channel *chan, const char *name, char * static struct ast_custom_function exception_function = { .name = "EXCEPTION", - .synopsis = "Retrieve the details of the current dialplan exception", - .desc = -"The following fields are available for retrieval:\n" -" reason INVALID, ERROR, RESPONSETIMEOUT, ABSOLUTETIMEOUT, or custom\n" -" value set by the RaiseException() application\n" -" context The context executing when the exception occurred\n" -" exten The extension executing when the exception occurred\n" -" priority The numeric priority executing when the exception occurred\n", - .syntax = "EXCEPTION(<field>)", .read = acf_exception_read, }; @@ -2673,10 +2985,10 @@ static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_c { struct ast_custom_function *acf; /* Maximum number of characters added by terminal coloring is 22 */ - char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40]; - char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; - char stxtitle[40], *syntax = NULL; - int synopsis_size, description_size, syntax_size; + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; + char stxtitle[40], *syntax = NULL, *arguments = NULL; + int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; char *ret = NULL; int which = 0; int wordlen; @@ -2703,49 +3015,75 @@ static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_c return ret; } - if (a->argc < 4) + if (a->argc < 4) { return CLI_SHOWUSAGE; + } if (!(acf = ast_custom_function_find(a->argv[3]))) { ast_cli(a->fd, "No function by that name registered.\n"); return CLI_FAILURE; + } + syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + MAX_ESCAPE_CHARS; + if (!(syntax = ast_malloc(syntax_size))) { + ast_cli(a->fd, "Memory allocation failure!\n"); + return CLI_FAILURE; } - if (acf->synopsis) - synopsis_size = strlen(acf->synopsis) + 23; - else - synopsis_size = strlen("Not available") + 23; - synopsis = alloca(synopsis_size); + snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); + term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); + term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); +#ifdef AST_XML_DOCS + if (acf->docsrc == AST_XML_DOC) { + arguments = xmldoc_colorization(S_OR(acf->arguments, "Not available")); + synopsis = xmldoc_colorization(S_OR(acf->synopsis, "Not available")); + description = xmldoc_colorization(S_OR(acf->desc, "Not available")); + seealso = xmldoc_colorization(S_OR(acf->seealso, "Not available")); + } else +#endif + { + synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + MAX_ESCAPE_CHARS; + synopsis = ast_malloc(synopsis_size); - if (acf->desc) - description_size = strlen(acf->desc) + 23; - else - description_size = strlen("Not available") + 23; - description = alloca(description_size); + description_size = strlen(S_OR(acf->desc, "Not Available")) + MAX_ESCAPE_CHARS; + description = ast_malloc(description_size); - if (acf->syntax) - syntax_size = strlen(acf->syntax) + 23; - else - syntax_size = strlen("Not available") + 23; - syntax = alloca(syntax_size); + arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + MAX_ESCAPE_CHARS; + arguments = ast_malloc(arguments_size); - snprintf(info, 64 + AST_MAX_APP, "\n -= Info about function '%s' =- \n\n", acf->name); - term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); - term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); - term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); - term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); - term_color(syntax, - acf->syntax ? acf->syntax : "Not available", - COLOR_CYAN, 0, syntax_size); - term_color(synopsis, - acf->synopsis ? acf->synopsis : "Not available", - COLOR_CYAN, 0, synopsis_size); - term_color(description, - acf->desc ? acf->desc : "Not available", - COLOR_CYAN, 0, description_size); - - ast_cli(a->fd,"%s%s%s\n\n%s%s\n\n%s%s\n", infotitle, stxtitle, syntax, syntitle, synopsis, destitle, description); + seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + MAX_ESCAPE_CHARS; + seealso = ast_malloc(seealso_size); + + /* check allocated memory. */ + if (!synopsis || !description || !arguments || !seealso) { + ast_free(synopsis); + ast_free(description); + ast_free(arguments); + ast_free(seealso); + ast_free(syntax); + return CLI_FAILURE; + } + + term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); + term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); + term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); + term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); + } + + ast_cli(a->fd,"%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", + infotitle, syntitle, synopsis, destitle, description, + stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); + + ast_free(arguments); + ast_free(synopsis); + ast_free(description); + ast_free(seealso); + ast_free(syntax); return CLI_SUCCESS; } @@ -2772,22 +3110,1533 @@ int ast_custom_function_unregister(struct ast_custom_function *acf) return -1; AST_RWLIST_WRLOCK(&acf_root); - if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) + if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) { + if (cur->docsrc == AST_XML_DOC) { + ast_string_field_free_memory(acf); + } ast_verb(2, "Unregistered custom function %s\n", cur->name); + } AST_RWLIST_UNLOCK(&acf_root); return cur ? 0 : -1; } +#ifdef AST_XML_DOCS +static const struct strcolorized_tags { + const char *init; /*!< Replace initial tag with this string. */ + const char *end; /*!< Replace end tag with this string. */ + const int colorfg; /*!< Foreground color. */ + const char *inittag; /*!< Initial tag description. */ + const char *endtag; /*!< Ending tag description. */ +} colorized_tags[] = { + { "<", ">", COLOR_GREEN, "<replaceable>", "</replaceable>" }, + { "\'", "\'", COLOR_BLUE, "<literal>", "</literal>" }, + { "*", "*", COLOR_RED, "<emphasis>", "</emphasis>" }, + { "\"", "\"", COLOR_YELLOW, "<filename>", "</filename>" }, + { "\"", "\"", COLOR_CYAN, "<directory>", "</directory>" }, + { "${", "}", COLOR_GREEN, "<variable>", "</variable>" }, + { "", "", COLOR_BLUE, "<value>", "</value>" }, + { "", "", COLOR_BLUE, "<enum>", "</enum>" }, + { "\'", "\'", COLOR_GRAY, "<astcli>", "</astcli>" }, + + /* Special tags */ + { "", "", COLOR_YELLOW, "<note>", "</note>" }, + { "", "", COLOR_RED, "<warning>", "</warning>" } +}; + +static const struct strspecial_tags { + const char *tagname; /*!< Special tag name. */ + const char *init; /*!< Print this at the beginning. */ + const char *end; /*!< Print this at the end. */ +} special_tags[] = { + { "note", "<note>NOTE:</note> ", "" }, + { "warning", "<warning>WARNING!!!:</warning> ", "" } +}; + +/*! \internal + * \brief Calculate the space in bytes used by a format string + * that will be passed to a sprintf function. + * \param postbr The format string to use to calculate the length. + * \retval The postbr length. + */ +static int xmldoc_postbrlen(const char *postbr) +{ + int postbrreallen = 0, i; + size_t postbrlen; + + if (!postbr) { + return 0; + } + postbrlen = strlen(postbr); + for (i = 0; i < postbrlen; i++) { + if (postbr[i] == '\t') { + postbrreallen += 8 - (postbrreallen % 8); + } else { + postbrreallen++; + } + } + return postbrreallen; +} + +/*! \internal + * \brief Setup postbr to be used while wrapping the text. + * Add to postbr array all the spaces and tabs at the beginning of text. + * \param postbr output array. + * \param len text array length. + * \param text Text with format string before the actual string. + */ +static void xmldoc_setpostbr(char *postbr, size_t len, const char *text) +{ + int c, postbrlen = 0; + + if (!text) { + return; + } + + for (c = 0; c < len; c++) { + if (text[c] == '\t' || text[c] == ' ') { + postbr[postbrlen++] = text[c]; + } else { + break; + } + } + postbr[postbrlen] = '\0'; +} + +/*! \internal + * \brief Try to find a space or a break in text starting at currentpost + * and moving at most maxdiff positions. + * Helper for xmldoc_string_wrap(). + * \param text Input string where it will search. + * \param currentpos Current position within text. + * \param maxdiff Not move more than maxdiff inside text. + * \retval 1 if a space or break is found inside text while moving. + * \retval 0 if no space or break is found. + */ +static int xmldoc_wait_nextspace(const char *text, int currentpos, int maxdiff) +{ + int i, textlen; + + if (!text) { + return 0; + } + + textlen = strlen(text); + for (i = currentpos; i < textlen; i++) { + if (text[i] == ESC) { + /* Move to the end of the escape sequence */ + while (i < textlen && text[i] != 'm') { + i++; + } + } else if (text[i] == ' ' || text[i] == '\n') { + /* Found the next space or linefeed */ + return 1; + } else if (i - currentpos > maxdiff) { + /* We have looked the max distance and didn't find it */ + return 0; + } + } + + /* Reached the end and did not find it */ + + return 0; +} + +/*! \internal + * \brief Helper function for xmldoc_string_wrap(). + * Try to found a space or a break inside text moving backward + * not more than maxdiff positions. + * \param text The input string where to search for a space. + * \param currentpos The current cursor position. + * \param maxdiff The max number of positions to move within text. + * \retval 0 If no space is found (Notice that text[currentpos] is not a space or a break) + * \retval > 0 If a space or a break is found, and the result is the position relative to + * currentpos. + */ +static int xmldoc_foundspace_backward(const char *text, int currentpos, int maxdiff) +{ + int i; + + for (i = currentpos; i > 0; i--) { + if (text[i] == ' ' || text[i] == '\n') { + return (currentpos - i); + } else if (text[i] == 'm' && (text[i - 1] >= '0' || text[i - 1] <= '9')) { + /* give up, we found the end of a possible ESC sequence. */ + return 0; + } else if (currentpos - i > maxdiff) { + /* give up, we can't move anymore. */ + return 0; + } + } + + /* we found the beginning of the text */ + + return 0; +} + +/*! \internal + * \brief Justify a text to a number of columns. + * \param text Input text to be justified. + * \param columns Number of columns to preserve in the text. + * \param maxdiff Try to not cut a word when goinf down. + * \retval NULL on error. + * \retval The wrapped text. + */ +static char *xmldoc_string_wrap(const char *text, int columns, int maxdiff) +{ + struct ast_str *tmp; + char *ret, postbr[160]; + int count = 1, i, backspace, needtobreak = 0, colmax, textlen; + + /* sanity check */ + if (!text || columns <= 0 || maxdiff < 0) { + ast_log(LOG_WARNING, "Passing wrong arguments while trying to wrap the text\n"); + return NULL; + } + + tmp = ast_str_create(strlen(text) * 3); + + if (!tmp) { + return NULL; + } + + /* Check for blanks and tabs and put them in postbr. */ + xmldoc_setpostbr(postbr, sizeof(postbr), text); + colmax = columns - xmldoc_postbrlen(postbr); + + textlen = strlen(text); + for (i = 0; i < textlen; i++) { + if (needtobreak || !(count % colmax)) { + if (text[i] == ' ') { + ast_str_append(&tmp, 0, "\n%s", postbr); + needtobreak = 0; + count = 1; + } else if (text[i] != '\n') { + needtobreak = 1; + if (xmldoc_wait_nextspace(text, i, maxdiff)) { + /* wait for the next space */ + ast_str_append(&tmp, 0, "%c", text[i]); + continue; + } + /* Try to look backwards */ + backspace = xmldoc_foundspace_backward(text, i, maxdiff); + if (backspace) { + needtobreak = 1; + tmp->used -= backspace; + tmp->str[tmp->used] = '\0'; + i -= backspace + 1; + continue; + } + ast_str_append(&tmp, 0, "\n%s", postbr); + needtobreak = 0; + count = 1; + } + /* skip blanks after a \n */ + while (text[i] == ' ') { + i++; + } + } + if (text[i] == '\n') { + xmldoc_setpostbr(postbr, sizeof(postbr), &text[i] + 1); + colmax = columns - xmldoc_postbrlen(postbr); + needtobreak = 0; + count = 1; + } + if (text[i] == ESC) { + /* Ignore Escape sequences. */ + do { + ast_str_append(&tmp, 0, "%c", text[i]); + i++; + } while (i < textlen && text[i] != 'm'); + } else { + count++; + } + ast_str_append(&tmp, 0, "%c", text[i]); + } + + ret = ast_strdup(tmp->str); + ast_free(tmp); + + return ret; +} + +/*! \internal + * \brief Colorize the xmldoc output. + * \param bwinput Not colorized input. + * \retval NULL on error. + * \retval New malloced buffer colorized. + */ +static char *xmldoc_colorization(const char *bwinput) +{ + struct ast_str *colorized; + char *wrapped = NULL; + int i, c, len, colorsection; + char *tmp; + size_t bwinputlen; + static const int base_fg = COLOR_CYAN; + + if (!bwinput) { + return NULL; + } + + bwinputlen = strlen(bwinput); + + if (!(colorized = ast_str_create(256))) { + return NULL; + } + + ast_term_color_code(&colorized, base_fg, 0); + if (!colorized) { + return NULL; + } + + for (i = 0; i < bwinputlen; i++) { + colorsection = 0; + /* Check if we are at the beginning of a tag to be colorized. */ + for (c = 0; c < ARRAY_LEN(colorized_tags); c++) { + if (strncasecmp(bwinput + i, colorized_tags[c].inittag, strlen(colorized_tags[c].inittag))) { + continue; + } + + if (!(tmp = strcasestr(bwinput + i + strlen(colorized_tags[c].inittag), colorized_tags[c].endtag))) { + continue; + } + + len = tmp - (bwinput + i + strlen(colorized_tags[c].inittag)); + + /* Setup color */ + ast_term_color_code(&colorized, colorized_tags[c].colorfg, 0); + if (!colorized) { + return NULL; + } + + /* copy initial string replace */ + ast_str_append(&colorized, 0, "%s", colorized_tags[c].init); + if (!colorized) { + return NULL; + } + { + char buf[len + 1]; + ast_copy_string(buf, bwinput + i + strlen(colorized_tags[c].inittag), sizeof(buf)); + ast_str_append(&colorized, 0, "%s", buf); + } + if (!colorized) { + return NULL; + } + + /* copy the ending string replace */ + ast_str_append(&colorized, 0, "%s", colorized_tags[c].end); + if (!colorized) { + return NULL; + } + + /* Continue with the last color. */ + ast_term_color_code(&colorized, base_fg, 0); + if (!colorized) { + return NULL; + } + + i += len + strlen(colorized_tags[c].endtag) + strlen(colorized_tags[c].inittag) - 1; + colorsection = 1; + break; + } + + if (!colorsection) { + ast_str_append(&colorized, 0, "%c", bwinput[i]); + if (!colorized) { + return NULL; + } + } + } + + ast_term_color_code(&colorized, COLOR_BRWHITE, 0); + if (!colorized) { + return NULL; + } + + /* Wrap the text, notice that string wrap will avoid cutting an ESC sequence. */ + wrapped = xmldoc_string_wrap(colorized->str, xmldoc_text_columns, xmldoc_max_diff); + + ast_free(colorized); + + return wrapped; +} + +/*! \internal + * \brief Cleanup spaces and tabs after a \n + * \param text String to be cleaned up. + * \param output buffer (not already allocated). + * \param lastspaces Remove last spaces in the string. + */ +static void xmldoc_string_cleanup(const char *text, struct ast_str **output, int lastspaces) +{ + int i; + size_t textlen; + + if (!text) { + *output = NULL; + return; + } + + textlen = strlen(text); + + *output = ast_str_create(textlen); + if (!(*output)) { + ast_log(LOG_ERROR, "Problem allocating output buffer\n"); + return; + } + + for (i = 0; i < textlen; i++) { + if (text[i] == '\n' || text[i] == '\r') { + /* remove spaces/tabs/\n after a \n. */ + while (text[i + 1] == '\t' || text[i + 1] == '\r' || text[i + 1] == '\n') { + i++; + } + ast_str_append(output, 0, " "); + continue; + } else { + ast_str_append(output, 0, "%c", text[i]); + } + } + + /* remove last spaces (we dont want always to remove the trailing spaces). */ + if (lastspaces) { + ast_str_trim_blanks(*output); + } +} + +/*! \internal + * \brief Get the application/function node for 'name' application/function with language 'language' + * if we don't find any, get the first application with 'name' no matter which language with. + * \param type 'application', 'function', ... + * \param name Application or Function name. + * \param language Try to get this language (if not found try with en_US) + * \retval NULL on error. + * \retval A node of type ast_xml_node. + */ +static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *language) +{ + struct ast_xml_node *node = NULL; + struct documentation_tree *doctree; + const char *lang; + + AST_RWLIST_RDLOCK(&xmldoc_tree); + AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { + /* the core xml documents have priority over thirdparty document. */ + node = ast_xml_get_root(doctree->doc); + while ((node = ast_xml_find_element(node, type, "name", name))) { + /* Check language */ + lang = ast_xml_get_attribute(node, "language"); + if (lang && !strcmp(lang, language)) { + ast_xml_free_attr(lang); + break; + } else if (lang) { + ast_xml_free_attr(lang); + } + } + + if (node && ast_xml_node_get_children(node)) { + break; + } + + /* We didn't find the application documentation for the specified language, + so, try to load documentation for any language */ + node = ast_xml_get_root(doctree->doc); + if (ast_xml_node_get_children(node)) { + if ((node = ast_xml_find_element(ast_xml_node_get_children(node), type, "name", name))) { + break; + } + } + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + return node; +} + +/*! \internal + * \brief Helper function used to build the syntax, it allocates the needed buffer (or reallocates it), + * and based on the reverse value it makes use of fmt to print the parameter list inside the + * realloced buffer (syntax). + * \param reverse We are going backwards while generating the syntax? + * \param len Current length of 'syntax' buffer. + * \param syntax Output buffer for the concatenated values. + * \param fmt A format string that will be used in a sprintf call. + */ +static __attribute__((format(printf,4,5))) void xmldoc_reverse_helper(int reverse, int *len, char **syntax, const char *fmt, ...) +{ + int totlen, tmpfmtlen; + char *tmpfmt, tmp; + va_list ap; + + va_start(ap, fmt); + if (ast_vasprintf(&tmpfmt, fmt, ap) < 0) { + va_end(ap); + return; + } + va_end(ap); + + tmpfmtlen = strlen(tmpfmt); + totlen = *len + tmpfmtlen + 1; + + *syntax = ast_realloc(*syntax, totlen); + + if (!*syntax) { + ast_free(tmpfmt); + return; + } + + if (reverse) { + memmove(*syntax + tmpfmtlen, *syntax, *len); + /* Save this char, it will be overwritten by the \0 of strcpy. */ + tmp = (*syntax)[0]; + strcpy(*syntax, tmpfmt); + /* Restore the already saved char. */ + (*syntax)[tmpfmtlen] = tmp; + (*syntax)[totlen - 1] = '\0'; + } else { + strcpy(*syntax + *len, tmpfmt); + } + + *len = totlen - 1; + ast_free(tmpfmt); +} + +/*! \internal + * \brief Check if the passed node has <argument> tags inside it. + * \param node Root node to search argument elements. + * \retval 1 If a <argument> element is found inside 'node'. + * \retval 0 If no <argument> is found inside 'node'. + */ +static int xmldoc_has_arguments(struct ast_xml_node *fixnode) +{ + struct ast_xml_node *node = fixnode; + + for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { + return 1; + } + } + return 0; +} + +/*! \internal + * \brief Build the syntax for a specified starting node. + * \param rootnode A pointer to the ast_xml root node. + * \param rootname Name of the application, function, option, etc. to build the syntax. + * \param childname The name of each parameter node. + * \param printparenthesis Boolean if we must print parenthesis if not parameters are found in the rootnode. + * \param printrootname Boolean if we must print the rootname before the syntax and parenthesis at the begining/end. + * \retval NULL on error. + * \retval An ast_malloc'ed string with the syntax generated. + */ +static char *xmldoc_get_syntax(struct ast_xml_node *rootnode, const char *rootname, const char *childname, int printparenthesis, int printrootname) +{ +#define GOTONEXT(__rev, __a) (__rev ? ast_xml_node_get_prev(__a) : ast_xml_node_get_next(__a)) +#define ISLAST(__rev, __a) (__rev == 1 ? (ast_xml_node_get_prev(__a) ? 0 : 1) : (ast_xml_node_get_next(__a) ? 0 : 1)) +#define MP(__a) ((multiple ? __a : "")) + struct ast_xml_node *node = NULL, *firstparam = NULL, *lastparam = NULL; + const char *paramtype, *multipletype, *paramname, *attrargsep, *parenthesis, *argname; + int reverse, required, paramcount = 0, openbrackets = 0, len = 0, hasparams=0; + int reqfinode = 0, reqlanode = 0, optmidnode = 0, prnparenthesis; + char *syntax = NULL, *argsep; + int paramnamemalloc, multiple; + + if (ast_strlen_zero(rootname) || ast_strlen_zero(childname)) { + ast_log(LOG_WARNING, "Tried to look in XML tree with faulty rootname or childname while creating a syntax.\n"); + return NULL; + } + + if (!rootnode || !ast_xml_node_get_children(rootnode)) { + /* If the rootnode field is not found, at least print name. */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + + /* Get the argument separator from the root node attribute name 'argsep', if not found + defaults to ','. */ + attrargsep = ast_xml_get_attribute(rootnode, "argsep"); + if (attrargsep) { + argsep = ast_strdupa(attrargsep); + ast_xml_free_attr(attrargsep); + } else { + argsep = ast_strdupa(","); + } + + /* Get order of evaluation. */ + for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + required = 0; + hasparams = 1; + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (ast_true(paramtype)) { + required = 1; + } + ast_xml_free_attr(paramtype); + } + + lastparam = node; + reqlanode = required; + + if (!firstparam) { + /* first parameter node */ + firstparam = node; + reqfinode = required; + } + } + + if (!hasparams) { + /* This application, function, option, etc, doesn't have any params. */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + + if (reqfinode && reqlanode) { + /* check midnode */ + for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + if (node != firstparam && node != lastparam) { + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (!ast_true(paramtype)) { + optmidnode = 1; + break; + } + ast_xml_free_attr(paramtype); + } + } + } + } + + if ((!reqfinode && reqlanode) || (reqfinode && reqlanode && optmidnode)) { + reverse = 1; + node = lastparam; + } else { + reverse = 0; + node = firstparam; + } + + /* init syntax string. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, + (printrootname ? (printrootname == 2 ? ")]" : ")"): "")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), + (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); + } + + for (; node; node = GOTONEXT(reverse, node)) { + if (strcasecmp(ast_xml_node_get_name(node), childname)) { + continue; + } + + /* Get the argument name, if it is not the leaf, go inside that parameter. */ + if (xmldoc_has_arguments(node)) { + parenthesis = ast_xml_get_attribute(node, "hasparams"); + prnparenthesis = 0; + if (parenthesis) { + prnparenthesis = ast_true(parenthesis); + if (!strcasecmp(parenthesis, "optional")) { + prnparenthesis = 2; + } + ast_xml_free_attr(parenthesis); + } + argname = ast_xml_get_attribute(node, "name"); + if (argname) { + paramname = xmldoc_get_syntax(node, argname, "argument", prnparenthesis, prnparenthesis); + ast_xml_free_attr(argname); + paramnamemalloc = 1; + } else { + /* Malformed XML, print **UNKOWN** */ + paramname = ast_strdup("**unknown**"); + } + paramnamemalloc = 1; + } else { + paramnamemalloc = 0; + paramname = ast_xml_get_attribute(node, "name"); + if (!paramname) { + ast_log(LOG_WARNING, "Malformed XML %s: no %s name\n", rootname, childname); + if (syntax) { + /* Free already allocated syntax */ + ast_free(syntax); + } + /* to give up is ok? */ + ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); + return syntax; + } + } + + /* Defaults to 'false'. */ + multiple = 0; + if ((multipletype = ast_xml_get_attribute(node, "multiple"))) { + if (ast_true(multipletype)) { + multiple = 1; + } + ast_xml_free_attr(multipletype); + } + + required = 0; /* Defaults to 'false'. */ + if ((paramtype = ast_xml_get_attribute(node, "required"))) { + if (ast_true(paramtype)) { + required = 1; + } + ast_xml_free_attr(paramtype); + } + + /* build syntax core. */ + + if (required) { + /* First parameter */ + if (!paramcount) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s", paramname, MP("["), MP(argsep), MP("...]")); + } else { + /* Time to close open brackets. */ + while (openbrackets > 0) { + xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); + openbrackets--; + } + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", paramname, argsep); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", argsep, paramname); + } + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s", MP("["), MP(argsep), MP("...]")); + } + } else { + /* First parameter */ + if (!paramcount) { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]", paramname, MP("["), MP(argsep), MP("...]")); + } else { + if (ISLAST(reverse, node)) { + /* This is the last parameter. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]%s", paramname, + MP("["), MP(argsep), MP("...]"), argsep); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s[%s%s%s%s]", argsep, paramname, + MP("["), MP(argsep), MP("...]")); + } + } else { + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s%s]", paramname, argsep, + MP("["), MP(argsep), MP("...]")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s%s", argsep, paramname, + MP("["), MP(argsep), MP("...]")); + } + openbrackets++; + } + } + } + if (paramnamemalloc) { + ast_free((char *) paramname); + } else { + ast_xml_free_attr(paramname); + } + + paramcount++; + } + + /* Time to close open brackets. */ + while (openbrackets > 0) { + xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); + openbrackets--; + } + + /* close syntax string. */ + if (reverse) { + xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), + (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); + } else { + xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")") : "")); + } + + return syntax; +#undef ISLAST +#undef GOTONEXT +#undef MP +} + +/*! \internal + * \brief Get the syntax for a specified application or function. + * \param type Application or Function ? + * \param name Name of the application or function. + * \retval NULL on error. + * \retval The generated syntax in a ast_malloc'ed string. + */ +static char *xmldoc_build_syntax(const char *type, const char *name) +{ + struct ast_xml_node *node; + char *syntax = NULL; + + node = xmldoc_get_node(type, name, documentation_language); + if (!node) { + return NULL; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) { + break; + } + } + + if (node) { + syntax = xmldoc_get_syntax(node, name, "parameter", 1, 1); + } + return syntax; +} + +/*! \internal + * \brief Parse a <para> element. + * \param node The <para> element pointer. + * \param tabs Added this string before the content of the <para> element. + * \param posttabs Added this string after the content of the <para> element. + * \param buffer This must be an already allocated ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 1 If 'node' is a named 'para'. + * \retval 2 If data is appended in buffer. + * \retval 0 on error. + */ +static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer) +{ + const char *tmptext; + struct ast_xml_node *tmp; + int ret = 0; + struct ast_str *tmpstr; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + if (strcasecmp(ast_xml_node_get_name(node), "para")) { + return ret; + } + + ast_str_append(buffer, 0, "%s", tabs); + + ret = 1; + + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* Get the text inside the <para> element and append it to buffer. */ + tmptext = ast_xml_get_text(tmp); + if (tmptext) { + /* Strip \n etc. */ + xmldoc_string_cleanup(tmptext, &tmpstr, 0); + ast_xml_free_text(tmptext); + if (tmpstr) { + if (strcasecmp(ast_xml_node_get_name(tmp), "text")) { + ast_str_append(buffer, 0, "<%s>%s</%s>", ast_xml_node_get_name(tmp), + tmpstr->str, ast_xml_node_get_name(tmp)); + } else { + ast_str_append(buffer, 0, "%s", tmpstr->str); + } + ast_free(tmpstr); + ret = 2; + } + } + } + + ast_str_append(buffer, 0, "%s", posttabs); + + return ret; +} + +/*! \internal + * \brief Parse special elements defined in 'struct special_tags' special elements must have a <para> element inside them. + * \param fixnode special tag node pointer. + * \param tabs put tabs before printing the node content. + * \param posttabs put posttabs after printing node content. + * \param buffer Output buffer, the special tags will be appended here. + * \retval 0 if no special element is parsed. + * \retval 1 if a special element is parsed (data is appended to buffer). + * \retval 2 if a special element is parsed and also a <para> element is parsed inside the specialtag. + */ +static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + int ret = 0, i, count = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + for (i = 0; i < ARRAY_LEN(special_tags); i++) { + if (strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) { + continue; + } + + ret = 1; + /* This is a special tag. */ + + /* concat data */ + if (!ast_strlen_zero(special_tags[i].init)) { + ast_str_append(buffer, 0, "%s%s", tabs, special_tags[i].init); + } + + /* parse <para> elements inside special tags. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + /* first <para> just print it without tabs at the begining. */ + if (xmldoc_parse_para(node, (!count ? "" : tabs), posttabs, buffer) == 2) { + ret = 2; + } + } + + if (!ast_strlen_zero(special_tags[i].end)) { + ast_str_append(buffer, 0, "%s%s", special_tags[i].end, posttabs); + } + + break; + } + + return ret; +} + +/*! \internal + * \brief Parse an <argument> element from the xml documentation. + * \param fixnode Pointer to the 'argument' xml node. + * \param insideparameter If we are parsing an <argument> inside a <parameter>. + * \param paramtabs pre tabs if we are inside a parameter element. + * \param tabs What to be printed before the argument name. + * \param buffer Output buffer to put values found inside the <argument> element. + * \retval 1 If there is content inside the argument. + * \retval 0 If the argument element is not parsed, or there is no content inside it. + */ +static int xmldoc_parse_argument(struct ast_xml_node *fixnode, int insideparameter, const char *paramtabs, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + const char *argname; + int count=0, ret = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + /* Print the argument names */ + argname = ast_xml_get_attribute(node, "name"); + if (!argname) { + return 0; + } + ast_str_append(buffer, 0, "%s%s%s", tabs, argname, (insideparameter ? "\n" : "")); + ast_xml_free_attr(argname); + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (xmldoc_parse_para(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { + count++; + ret = 1; + } else if (xmldoc_parse_specialtags(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { + count++; + ret = 1; + } + } + + return ret; +} + +/*! \internal + * \brief Parse a <variable> node inside a <variablelist> node. + * \param node The variable node to parse. + * \param tabs A string to be appended at the begining of the output that will be stored + * in buffer. + * \param buffer This must be an already created ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 0 if no data is appended. + * \retval 1 if data is appended. + */ +static int xmldoc_parse_variable(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *tmp; + const char *valname; + const char *tmptext; + struct ast_str *cleanstr; + int ret = 0, printedpara=0; + + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + if (xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer)) { + printedpara = 1; + continue; + } else if (xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer)) { + printedpara = 1; + continue; + } + + if (strcasecmp(ast_xml_node_get_name(tmp), "value")) { + continue; + } + + /* Parse a <value> tag only. */ + if (!printedpara) { + ast_str_append(buffer, 0, "\n"); + printedpara = 1; + } + /* Parse each <value name='valuename'>desciption</value> */ + valname = ast_xml_get_attribute(tmp, "name"); + if (valname) { + ret = 1; + ast_str_append(buffer, 0, "%s<value>%s</value>", tabs, valname); + ast_xml_free_attr(valname); + } + tmptext = ast_xml_get_text(tmp); + /* Check inside this node for any explanation about its meaning. */ + if (tmptext) { + /* Cleanup text. */ + xmldoc_string_cleanup(tmptext, &cleanstr, 1); + ast_xml_free_text(tmptext); + if (cleanstr && cleanstr->used > 0) { + ast_str_append(buffer, 0, ":%s", cleanstr->str); + } + ast_free(cleanstr); + } + ast_str_append(buffer, 0, "\n"); + } + + return ret; +} + +/*! \internal + * \brief Parse a <variablelist> node and put all the output inside 'buffer'. + * \param node The variablelist node pointer. + * \param tabs A string to be appended at the begining of the output that will be stored + * in buffer. + * \param buffer This must be an already created ast_str. It will be used + * to store the result (if already has something it will be appended to the current + * string). + * \retval 1 If a <variablelist> element is parsed. + * \retval 0 On error. + */ +static int xmldoc_parse_variablelist(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *tmp; + const char *varname; + char *vartabs; + int ret = 0; + + if (!node || !ast_xml_node_get_children(node)) { + return ret; + } + + if (strcasecmp(ast_xml_node_get_name(node), "variablelist")) { + return ret; + } + + /* use this spacing (add 4 spaces) inside a variablelist node. */ + ast_asprintf(&vartabs, "%s ", tabs); + if (!vartabs) { + return ret; + } + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* We can have a <para> element inside the variable list */ + if ((xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer))) { + ret = 1; + continue; + } else if ((xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer))) { + ret = 1; + continue; + } + + if (!strcasecmp(ast_xml_node_get_name(tmp), "variable")) { + /* Store the variable name in buffer. */ + varname = ast_xml_get_attribute(tmp, "name"); + if (varname) { + ast_str_append(buffer, 0, "%s<variable>%s</variable>: ", tabs, varname); + ast_xml_free_attr(varname); + /* Parse the <variable> possible values. */ + xmldoc_parse_variable(tmp, vartabs, buffer); + ret = 1; + } + } + } + + ast_free(vartabs); + + return ret; +} + +/*! \internal + * \brief Parse the <see-also> node content. + * \param type 'application' or 'function'. + * \param name Application or functions name. + * \retval NULL on error. + * \retval Content of the see-also node. + */ +static char *xmldoc_build_seealso(const char *type, const char *name) +{ + struct ast_str *outputstr; + char *output; + struct ast_xml_node *node; + const char *typename; + const char *content; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + return NULL; + } + + /* get the application/function root node. */ + node = xmldoc_get_node(type, name, documentation_language); + if (!node || !ast_xml_node_get_children(node)) { + return NULL; + } + + /* Find the <see-also> node. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "see-also")) { + break; + } + } + + if (!node || !ast_xml_node_get_children(node)) { + /* we couldnt find a <see-also> node. */ + return NULL; + } + + /* prepare the output string. */ + outputstr = ast_str_create(128); + if (!outputstr) { + return NULL; + } + + /* get into the <see-also> node. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), "ref")) { + continue; + } + + /* parse the <ref> node. 'type' attribute is required. */ + typename = ast_xml_get_attribute(node, "type"); + if (!typename) { + continue; + } + content = ast_xml_get_text(node); + if (!content) { + ast_xml_free_attr(typename); + continue; + } + if (!strcasecmp(typename, "application") || !strcasecmp(typename, "function")) { + ast_str_append(&outputstr, 0, "%s: Type <astcli>core show %s %s</astcli> for more info.\n", + content, typename, content); + } else if (!strcasecmp(typename, "astcli")) { + ast_str_append(&outputstr, 0, "%s: Type <astcli>help %s</astcli> for more info.\n", content, content); + } else if (!strcasecmp(typename, "link")) { + ast_str_append(&outputstr, 0, "%s\n", content); + } else if (!strcasecmp(typename, "manpage")) { + ast_str_append(&outputstr, 0, "ManPage: %s\n", content); + } + ast_xml_free_text(content); + } + + output = ast_strdup(outputstr->str); + ast_free(outputstr); + + return output; +} + +/*! \internal + * \brief Parse a <enum> node. + * \brief fixnode An ast_xml_node pointer to the <enum> node. + * \bried buffer The output buffer. + * \retval 0 if content is not found inside the enum element (data is not appended to buffer). + * \retval 1 if content is found and data is appended to buffer. + */ +static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + int ret = 0; + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if ((xmldoc_parse_para(node, (ret ? tabs : " - "), "\n", buffer))) { + ret = 1; + } else if ((xmldoc_parse_specialtags(node, (ret ? tabs : " - "), "\n", buffer))) { + ret = 1; + } + } + return ret; +} + +/*! \internal + * \brief Parse a <enumlist> node. + * \param fixnode As ast_xml pointer to the <enumlist> node. + * \param buffer The ast_str output buffer. + * \retval 0 if no <enumlist> node was parsed. + * \retval 1 if a <enumlist> node was parsed. + */ +static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node = fixnode; + const char *enumname; + int ret = 0; + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (strcasecmp(ast_xml_node_get_name(node), "enum")) { + continue; + } + + enumname = ast_xml_get_attribute(node, "name"); + if (enumname) { + ast_str_append(buffer, 0, "%s<enum>%s</enum>", tabs, enumname); + ast_xml_free_attr(enumname); + + /* parse only enum elements inside a enumlist node. */ + if ((xmldoc_parse_enum(node, tabs, buffer))) { + ret = 1; + } else { + ast_str_append(buffer, 0, "\n"); + } + } + } + return ret; +} + +/*! \internal + * \brief Parse an <option> node. + * \param fixnode An ast_xml pointer to the <option> node. + * \param tabs A string to be appended at the begining of each line being added to the + * buffer string. + * \param buffer The output buffer. + * \retval 0 if no option node is parsed. + * \retval 1 if an option node is parsed. + */ +static int xmldoc_parse_option(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node; + int ret = 0; + char *optiontabs; + + ast_asprintf(&optiontabs, "%s ", tabs); + if (!optiontabs) { + return ret; + } + for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { + /* if this is the first data appended to buffer, print a \n*/ + if (!ret && ast_xml_node_get_children(node)) { + /* print \n */ + ast_str_append(buffer, 0, "\n"); + } + if (xmldoc_parse_argument(node, 0, NULL, optiontabs, buffer)) { + ret = 1; + } + continue; + } + + if (xmldoc_parse_para(node, (ret ? tabs : ""), "\n", buffer)) { + ret = 1; + } else if (xmldoc_parse_specialtags(node, (ret ? tabs : ""), "\n", buffer)) { + ret = 1; + } + + xmldoc_parse_variablelist(node, optiontabs, buffer); + + xmldoc_parse_enumlist(node, optiontabs, buffer); + } + ast_free(optiontabs); + + return ret; +} + +/*! \internal + * \brief Parse an <optionlist> element from the xml documentation. + * \param fixnode Pointer to the optionlist xml node. + * \param tabs A string to be appended at the begining of each line being added to the + * buffer string. + * \param buffer Output buffer to put what is inside the optionlist tag. + */ +static void xmldoc_parse_optionlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + struct ast_xml_node *node; + const char *optname; + char *optionsyntax; + + for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { + /* Start appending every option tag. */ + if (strcasecmp(ast_xml_node_get_name(node), "option")) { + continue; + } + + /* Get the option name. */ + optname = ast_xml_get_attribute(node, "name"); + if (!optname) { + continue; + } + + optionsyntax = xmldoc_get_syntax(node, optname, "argument", 0, 1); + if (!optionsyntax) { + continue; + } + + ast_str_append(buffer, 0, "%s%s: ", tabs, optionsyntax); + + if (!xmldoc_parse_option(node, tabs, buffer)) { + ast_str_append(buffer, 0, "\n"); + } + } +} + +/*! \internal + * \brief Parse a 'parameter' tag inside a syntax element. + * \param fixnode A pointer to the 'parameter' xml node. + * \param tabs A string to be appended at the beginning of each line being printed inside + * 'buffer'. + * \param buffer String buffer to put values found inside the parameter element. + */ +static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) +{ + const char *paramname; + struct ast_xml_node *node = fixnode; + int hasarguments, printed = 0; + char *internaltabs; + + if (strcasecmp(ast_xml_node_get_name(node), "parameter")) { + return; + } + + hasarguments = xmldoc_has_arguments(node); + if (!(paramname = ast_xml_get_attribute(node, "name"))) { + /* parameter MUST have an attribute name. */ + return; + } + + ast_asprintf(&internaltabs, "%s ", tabs); + if (!internaltabs) { + return; + } + + if (!hasarguments) { + ast_str_append(buffer, 0, "%s\n", paramname); + ast_xml_free_attr(paramname); + printed = 1; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "optionlist")) { + xmldoc_parse_optionlist(node, internaltabs, buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) { + xmldoc_parse_enumlist(node, internaltabs, buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { + xmldoc_parse_argument(node, 1, internaltabs, (!hasarguments ? " " : ""), buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "para")) { + if (!printed) { + ast_str_append(buffer, 0, "%s\n", paramname); + ast_xml_free_attr(paramname); + printed = 1; + } + xmldoc_parse_para(node, internaltabs, "\n", buffer); + continue; + } else if ((xmldoc_parse_specialtags(node, internaltabs, "\n", buffer))) { + continue; + } + } + ast_free(internaltabs); +} + +/*! \internal + * \brief Generate the [arguments] tag based on type of node 'application' or + * 'function' and name. + * \param type 'application' or 'function' ? + * \param name Name of the application or function to build the 'arguments' tag. + * \retval NULL on error. + * \retval Output buffer with the [arguments] tag content. + */ +static char *xmldoc_build_arguments(const char *type, const char *name) +{ + struct ast_xml_node *node; + struct ast_str *ret = ast_str_create(128); + char *retstr = NULL; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + return NULL; + } + + node = xmldoc_get_node(type, name, documentation_language); + + if (!node || !ast_xml_node_get_children(node)) { + return NULL; + } + + /* Find the syntax field. */ + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) { + break; + } + } + + if (!node || !ast_xml_node_get_children(node)) { + /* We couldn't find the syntax node. */ + return NULL; + } + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + xmldoc_parse_parameter(node, "", &ret); + } + + if (ret->used > 0) { + /* remove last '\n' */ + if (ret->str[ret->used - 1] == '\n') { + ret->str[ret->used - 1] = '\0'; + ret->used--; + } + retstr = ast_strdup(ret->str); + } + ast_free(ret); + + return retstr; +} + +/*! \internal + * \brief Return the string within a node formatted with <para> and <variablelist> elements. + * \param node Parent node where content resides. + * \param raw If set, return the node's content without further processing. + * \param raw_wrap Wrap raw text. + * \retval NULL on error + * \retval Node content on success. + */ +static struct ast_str *xmldoc_get_formatted(struct ast_xml_node *node, int raw_output, int raw_wrap) +{ + struct ast_xml_node *tmp; + const char *notcleanret, *tmpstr; + struct ast_str *ret = ast_str_create(128); + + if (raw_output) { + notcleanret = ast_xml_get_text(node); + tmpstr = notcleanret; + xmldoc_string_cleanup(ast_skip_blanks(notcleanret), &ret, 0); + ast_xml_free_text(tmpstr); + } else { + for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { + /* if found, parse a <para> element. */ + if (xmldoc_parse_para(tmp, "", "\n", &ret)) { + continue; + } else if (xmldoc_parse_specialtags(tmp, "", "\n", &ret)) { + continue; + } + /* if found, parse a <variablelist> element. */ + xmldoc_parse_variablelist(tmp, "", &ret); + xmldoc_parse_enumlist(tmp, " ", &ret); + } + /* remove last '\n' */ + /* XXX Don't modify ast_str internals manually */ + if (ret->str[ret->used-1] == '\n') { + ret->str[ret->used-1] = '\0'; + ret->used--; + } + } + return ret; +} + +/*! \internal + * \brief Get the content of a field (synopsis, description, etc) from an asterisk document tree + * \param type Type of element (application, function, ...). + * \param name Name of element (Dial, Echo, Playback, ...). + * \param var Name of field to return (synopsis, description, etc). + * \param raw Field only contains text, no other elements inside it. + * \retval NULL On error. + * \retval Field text content on success. + */ +static char *xmldoc_build_field(const char *type, const char *name, const char *var, int raw) +{ + struct ast_xml_node *node; + char *ret = NULL; + struct ast_str *formatted; + + if (ast_strlen_zero(type) || ast_strlen_zero(name)) { + ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n"); + return ret; + } + + node = xmldoc_get_node(type, name, documentation_language); + + if (!node) { + ast_log(LOG_WARNING, "Counldn't find %s %s in XML documentation\n", type, name); + return ret; + } + + node = ast_xml_find_element(ast_xml_node_get_children(node), var, NULL, NULL); + + if (!node || !ast_xml_node_get_children(node)) { + ast_log(LOG_DEBUG, "Cannot find variable '%s' in tree '%s'\n", name, var); + return ret; + } + + formatted = xmldoc_get_formatted(node, raw, raw); + if (formatted->used > 0) { + ret = ast_strdup(formatted->str); + } + ast_free(formatted); + + return ret; +} +#endif /* AST_XML_DOCS */ + +/*! \internal + * \brief Retrieve the XML documentation of a specified ast_custom_function, + * and populate ast_custom_function string fields. + * \param acf ast_custom_function structure with empty 'desc' and 'synopsis' + * but with a function 'name'. + * \retval -1 On error. + * \retval 0 On succes. + */ +static int acf_retrieve_docs(struct ast_custom_function *acf) +{ +#ifdef AST_XML_DOCS + char *tmpxml; + + /* Let's try to find it in the Documentation XML */ + if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) { + return 0; + } + + if (ast_string_field_init(acf, 128)) { + return -1; + } + + /* load synopsis */ + tmpxml = xmldoc_build_field("function", acf->name, "synopsis", 1); + ast_string_field_set(acf, synopsis, tmpxml); + ast_free(tmpxml); + + /* load description */ + tmpxml = xmldoc_build_field("function", acf->name, "description", 0); + ast_string_field_set(acf, desc, tmpxml); + ast_free(tmpxml); + + /* load syntax */ + tmpxml = xmldoc_build_syntax("function", acf->name); + ast_string_field_set(acf, syntax, tmpxml); + ast_free(tmpxml); + + /* load arguments */ + tmpxml = xmldoc_build_arguments("function", acf->name); + ast_string_field_set(acf, arguments, tmpxml); + ast_free(tmpxml); + + /* load seealso */ + tmpxml = xmldoc_build_seealso("function", acf->name); + ast_string_field_set(acf, seealso, tmpxml); + ast_free(tmpxml); + + acf->docsrc = AST_XML_DOC; +#endif + + return 0; +} + int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod) { struct ast_custom_function *cur; char tmps[80]; - if (!acf) + if (!acf) { return -1; + } acf->mod = mod; + acf->docsrc = AST_STATIC_DOC; + + if (acf_retrieve_docs(acf)) { + return -1; + } AST_RWLIST_WRLOCK(&acf_root); @@ -2807,8 +4656,10 @@ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_m } } AST_RWLIST_TRAVERSE_SAFE_END; - if (!cur) + + if (!cur) { AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist); + } AST_RWLIST_UNLOCK(&acf_root); @@ -4478,6 +6329,9 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel struct ast_app *tmp, *cur = NULL; char tmps[80]; int length, res; +#ifdef AST_XML_DOCS + char *tmpxml; +#endif AST_RWLIST_WRLOCK(&apps); AST_RWLIST_TRAVERSE(&apps, tmp, list) { @@ -4496,10 +6350,50 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel return -1; } + if (ast_string_field_init(tmp, 128)) { + ast_free(tmp); + return -1; + } + +#ifdef AST_XML_DOCS + /* Try to lookup the docs in our XML documentation database */ + if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) { + /* load synopsis */ + tmpxml = xmldoc_build_field("application", app, "synopsis", 1); + ast_string_field_set(tmp, synopsis, tmpxml); + ast_free(tmpxml); + + /* load description */ + tmpxml = xmldoc_build_field("application", app, "description", 0); + ast_string_field_set(tmp, description, tmpxml); + ast_free(tmpxml); + + /* load syntax */ + tmpxml = xmldoc_build_syntax("application", app); + ast_string_field_set(tmp, syntax, tmpxml); + ast_free(tmpxml); + + /* load arguments */ + tmpxml = xmldoc_build_arguments("application", app); + ast_string_field_set(tmp, arguments, tmpxml); + ast_free(tmpxml); + + /* load seealso */ + tmpxml = xmldoc_build_seealso("application", app); + ast_string_field_set(tmp, seealso, tmpxml); + ast_free(tmpxml); + tmp->docsrc = AST_XML_DOC; + } else { +#endif + ast_string_field_set(tmp, synopsis, synopsis); + ast_string_field_set(tmp, description, description); + tmp->docsrc = AST_STATIC_DOC; +#ifdef AST_XML_DOCS + } +#endif + strcpy(tmp->name, app); tmp->execute = execute; - tmp->synopsis = synopsis; - tmp->description = description; tmp->module = mod; /* Store in alphabetical order */ @@ -4520,6 +6414,123 @@ int ast_register_application2(const char *app, int (*execute)(struct ast_channel return 0; } +#ifdef AST_XML_DOCS +/*! \brief Close and unload XML documentation. */ +static void xmldoc_unload_documentation(void) +{ + struct documentation_tree *doctree; + + AST_RWLIST_WRLOCK(&xmldoc_tree); + while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) { + ast_free(doctree->filename); + ast_xml_close(doctree->doc); + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + ast_xml_finish(); +} + +int ast_load_documentation(void) +{ + struct ast_xml_node *root_node; + struct ast_xml_doc *tmpdoc; + struct documentation_tree *doc_tree; + char *xmlpattern; + struct ast_config *cfg = NULL; + struct ast_variable *var = NULL; + struct ast_flags cnfflags = { 0 }; + int globret, i, dup, duplicate; + glob_t globbuf; + + /* setup default XML documentation language */ + snprintf(documentation_language, sizeof(documentation_language), default_documentation_language); + + if (!(cfg = ast_config_load2("asterisk.conf", "" /* core can't reload */, cnfflags))) { + ast_log(LOG_ERROR, "No asterisk.conf? That cannot be good.\n"); + } + + for (var = ast_variable_browse(cfg, "options"); var; var = var->next) { + if (!strcasecmp(var->name, "documentation_language")) { + if (!ast_strlen_zero(var->value)) { + snprintf(documentation_language, sizeof(documentation_language), "%s", var->value); + } + } + } + ast_config_destroy(cfg); + + /* initialize the XML library. */ + ast_xml_init(); + + /* register function to be run when asterisk finish. */ + ast_register_atexit(xmldoc_unload_documentation); + + /* Get every *-LANG.xml file inside $(ASTDATADIR)/documentation */ + ast_asprintf(&xmlpattern, "%s/documentation{/thirdparty/,/}*-{%s,%.2s_??,%s}.xml", ast_config_AST_DATA_DIR, + documentation_language, documentation_language, default_documentation_language); + globbuf.gl_offs = 0; /* initialize it to silence gcc */ + globret = glob(xmlpattern, MY_GLOB_FLAGS | GLOB_BRACE, NULL, &globbuf); + if (globret == GLOB_NOSPACE) { + ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Not enough memory\n", xmlpattern); + ast_free(xmlpattern); + return 1; + } else if (globret == GLOB_ABORTED) { + ast_log(LOG_WARNING, "Glob Expansion of pattern '%s' failed: Read error\n", xmlpattern); + ast_free(xmlpattern); + return 1; + } + ast_free(xmlpattern); + + AST_RWLIST_WRLOCK(&xmldoc_tree); + /* loop over expanded files */ + for (i = 0; i < globbuf.gl_pathc; i++) { + /* check for duplicates (if we already [try to] open the same file. */ + duplicate = 0; + for (dup = 0; dup < i; dup++) { + if (!strcmp(globbuf.gl_pathv[i], globbuf.gl_pathv[dup])) { + duplicate = 1; + break; + } + } + if (duplicate) { + continue; + } + tmpdoc = NULL; + tmpdoc = ast_xml_open(globbuf.gl_pathv[i]); + if (!tmpdoc) { + ast_log(LOG_ERROR, "Could not open XML documentation at '%s'\n", globbuf.gl_pathv[i]); + continue; + } + /* Get doc root node and check if it starts with '<docs>' */ + root_node = ast_xml_get_root(tmpdoc); + if (!root_node) { + ast_log(LOG_ERROR, "Error getting documentation root node"); + ast_xml_close(tmpdoc); + continue; + } + /* Check root node name for malformed xmls. */ + if (strcmp(ast_xml_node_get_name(root_node), "docs")) { + ast_log(LOG_ERROR, "Documentation file is not well formed!\n"); + ast_xml_close(tmpdoc); + continue; + } + doc_tree = ast_calloc(1, sizeof(*doc_tree)); + if (!doc_tree) { + ast_log(LOG_ERROR, "Unable to allocate documentation_tree structure!\n"); + ast_xml_close(tmpdoc); + continue; + } + doc_tree->doc = tmpdoc; + doc_tree->filename = ast_strdup(globbuf.gl_pathv[i]); + AST_RWLIST_INSERT_TAIL(&xmldoc_tree, doc_tree, entry); + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + globfree(&globbuf); + + return 0; +} +#endif /* AST_XML_DOCS */ + /* * Append to the list. We don't have a tail pointer because we need * to scan the list anyways to check for duplicates during insertion. @@ -4553,6 +6564,78 @@ void ast_unregister_switch(struct ast_switch *sw) * Help for CLI commands ... */ +static void print_app_docs(struct ast_app *aa, int fd) +{ + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], stxtitle[40], argtitle[40]; + char seealsotitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *syntax = NULL, *arguments = NULL; + char *seealso = NULL; + int syntax_size, synopsis_size, description_size, arguments_size, seealso_size; + + snprintf(info, sizeof(info), "\n -= Info about application '%s' =- \n\n", aa->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); + + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); + term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); + +#ifdef AST_XML_DOCS + if (aa->docsrc == AST_XML_DOC) { + description = xmldoc_colorization(S_OR(aa->description, "Not available")); + arguments = xmldoc_colorization(S_OR(aa->arguments, "Not available")); + synopsis = xmldoc_colorization(S_OR(aa->synopsis, "Not available")); + seealso = xmldoc_colorization(S_OR(aa->seealso, "Not available")); + + if (!synopsis || !description || !arguments || !seealso) { + goto return_cleanup; + } + } else +#endif + { + synopsis_size = strlen(S_OR(aa->synopsis, "Not Available")) + MAX_ESCAPE_CHARS; + synopsis = ast_malloc(synopsis_size); + + description_size = strlen(S_OR(aa->description, "Not Available")) + MAX_ESCAPE_CHARS; + description = ast_malloc(description_size); + + arguments_size = strlen(S_OR(aa->arguments, "Not Available")) + MAX_ESCAPE_CHARS; + arguments = ast_malloc(arguments_size); + + seealso_size = strlen(S_OR(aa->seealso, "Not Available")) + MAX_ESCAPE_CHARS; + seealso = ast_malloc(seealso_size); + + if (!synopsis || !description || !arguments || !seealso) { + goto return_cleanup; + } + + term_color(synopsis, S_OR(aa->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); + term_color(description, S_OR(aa->description, "Not available"), COLOR_CYAN, 0, description_size); + term_color(arguments, S_OR(aa->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); + term_color(seealso, S_OR(aa->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); + } + + /* Handle the syntax the same for both XML and raw docs */ + syntax_size = strlen(S_OR(aa->syntax, "Not Available")) + MAX_ESCAPE_CHARS; + if (!(syntax = ast_malloc(syntax_size))) { + goto return_cleanup; + } + term_color(syntax, S_OR(aa->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); + + ast_cli(fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", + infotitle, syntitle, synopsis, destitle, description, + stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); + +return_cleanup: + ast_free(description); + ast_free(arguments); + ast_free(synopsis); + ast_free(seealso); + ast_free(syntax); +} + /* * \brief 'show application' CLI command implementation function... */ @@ -4591,58 +6674,22 @@ static char *handle_show_application(struct ast_cli_entry *e, int cmd, struct as return ret; } - if (a->argc < 4) + if (a->argc < 4) { return CLI_SHOWUSAGE; + } - /* ... go through all applications ... */ AST_RWLIST_RDLOCK(&apps); AST_RWLIST_TRAVERSE(&apps, aa, list) { - /* ... compare this application name with all arguments given - * to 'show application' command ... */ + /* Check for each app that was supplied as an argument */ for (app = 3; app < a->argc; app++) { - if (!strcasecmp(aa->name, a->argv[app])) { - /* Maximum number of characters added by terminal coloring is 22 */ - char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40]; - char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; - int synopsis_size, description_size; - - no_registered_app = 0; + if (strcasecmp(aa->name, a->argv[app])) { + continue; + } - if (aa->synopsis) - synopsis_size = strlen(aa->synopsis) + 23; - else - synopsis_size = strlen("Not available") + 23; - synopsis = alloca(synopsis_size); + /* We found it! */ + no_registered_app = 0; - if (aa->description) - description_size = strlen(aa->description) + 23; - else - description_size = strlen("Not available") + 23; - description = alloca(description_size); - - if (synopsis && description) { - snprintf(info, 64 + AST_MAX_APP, "\n -= Info about application '%s' =- \n\n", aa->name); - term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); - term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); - term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); - term_color(synopsis, - aa->synopsis ? aa->synopsis : "Not available", - COLOR_CYAN, 0, synopsis_size); - term_color(description, - aa->description ? aa->description : "Not available", - COLOR_CYAN, 0, description_size); - - ast_cli(a->fd,"%s%s%s\n\n%s%s\n", infotitle, syntitle, synopsis, destitle, description); - } else { - /* ... one of our applications, show info ...*/ - ast_cli(a->fd,"\n -= Info about application '%s' =- \n\n" - "[Synopsis]\n %s\n\n" - "[Description]\n%s\n", - aa->name, - aa->synopsis ? aa->synopsis : "Not available", - aa->description ? aa->description : "Not available"); - } - } + print_app_docs(aa, a->fd); } } AST_RWLIST_UNLOCK(&apps); @@ -5744,6 +7791,11 @@ int ast_unregister_application(const char *app) unreference_cached_app(tmp); AST_RWLIST_REMOVE_CURRENT(list); ast_verb(2, "Unregistered application '%s'\n", tmp->name); +#ifdef AST_XML_DOCS + if (tmp->docsrc == AST_XML_DOC) { + ast_string_field_free_memory(tmp); + } +#endif ast_free(tmp); break; } @@ -8534,12 +10586,12 @@ int load_pbx(void) /* Register builtin applications */ for (x = 0; x < sizeof(builtins) / sizeof(struct pbx_builtin); x++) { ast_verb(1, "[%s]\n", builtins[x].name); - if (ast_register_application2(builtins[x].name, builtins[x].execute, builtins[x].synopsis, builtins[x].description, NULL)) { + if (ast_register_application2(builtins[x].name, builtins[x].execute, NULL, NULL, NULL)) { ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name); return -1; } } - + /* Register manager application */ ast_manager_register2("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan, "List dialplan", mandescr_show_dialplan); diff --git a/main/term.c b/main/term.c index d12135f24..7f38ee5ce 100644 --- a/main/term.c +++ b/main/term.c @@ -199,32 +199,72 @@ char *term_color(char *outbuf, const char *inbuf, int fgcolor, int bgcolor, int return outbuf; } -char *term_color_code(char *outbuf, int fgcolor, int bgcolor, int maxout) +static void check_fgcolor(int *fgcolor, int *attr) { - int attr = 0; - if ((!vt100compat) || (!fgcolor)) { - *outbuf = '\0'; - return outbuf; + if (*fgcolor & 128) { + *attr = ast_opt_light_background ? 0 : ATTR_BRIGHT; + *fgcolor &= ~128; + } + + if (ast_opt_light_background) { + *fgcolor = opposite(*fgcolor); } +} - if (fgcolor & 128) { - attr = ast_opt_light_background ? 0 : ATTR_BRIGHT; - fgcolor &= ~128; +static void check_bgcolor(int *bgcolor) +{ + if (*bgcolor) { + *bgcolor &= ~128; } +} - if (ast_opt_light_background) { - fgcolor = opposite(fgcolor); +static int check_colors_allowed(int fgcolor) +{ + return (!vt100compat || !fgcolor) ? 0 : 1; +} + +int ast_term_color_code(struct ast_str **str, int fgcolor, int bgcolor) +{ + int attr = 0; + + if (!check_colors_allowed(fgcolor)) { + return -1; } - if (bgcolor) { - bgcolor &= ~128; + check_fgcolor(&fgcolor, &attr); + check_bgcolor(&bgcolor); + + if (ast_opt_force_black_background) { + ast_str_append(str, 0, "%c[%d;%d;%dm", ESC, attr, fgcolor, COLOR_BLACK + 10); + } else if (bgcolor) { + ast_str_append(str, 0, "%c[%d;%d;%dm", ESC, attr, fgcolor, bgcolor + 10); + } else { + ast_str_append(str, 0, "%c[%d;%dm", ESC, attr, fgcolor); } + return 0; +} + +char *term_color_code(char *outbuf, int fgcolor, int bgcolor, int maxout) +{ + int attr = 0; + + if (!check_colors_allowed(fgcolor)) { + *outbuf = '\0'; + return outbuf; + } + + check_fgcolor(&fgcolor, &attr); + check_bgcolor(&bgcolor); + if (ast_opt_force_black_background) { + snprintf(outbuf, maxout, "%c[%d;%d;%dm", ESC, attr, fgcolor, COLOR_BLACK + 10); + } else if (bgcolor) { snprintf(outbuf, maxout, "%c[%d;%d;%dm", ESC, attr, fgcolor, bgcolor + 10); } else { snprintf(outbuf, maxout, "%c[%d;%dm", ESC, attr, fgcolor); } + return outbuf; } diff --git a/main/xml.c b/main/xml.c new file mode 100644 index 000000000..9a0c66d90 --- /dev/null +++ b/main/xml.c @@ -0,0 +1,195 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2008, Eliel C. Sardanons (LU1ALY) <eliels@gmail.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief XML abstraction layer + * + * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com> + */ + +#include "asterisk.h" +#include "asterisk/xml.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#if defined(HAVE_LIBXML2) +#include <libxml/parser.h> +#include <libxml/tree.h> +/* libxml2 ast_xml implementation. */ + + +int ast_xml_init(void) +{ + LIBXML_TEST_VERSION + + return 0; +} + +int ast_xml_finish(void) +{ + xmlCleanupParser(); + + return 0; +} + +struct ast_xml_doc *ast_xml_open(char *filename) +{ + xmlDoc *doc; + + if (!filename) { + return NULL; + } + + doc = xmlReadFile(filename, NULL, XML_PARSE_RECOVER); + + return (struct ast_xml_doc *) doc; +} + + +void ast_xml_close(struct ast_xml_doc *doc) +{ + if (!doc) { + return; + } + + xmlFreeDoc((xmlDoc *) doc); + doc = NULL; +} + + +struct ast_xml_node *ast_xml_get_root(struct ast_xml_doc *doc) +{ + xmlNode *root_node; + + if (!doc) { + return NULL; + } + + root_node = xmlDocGetRootElement((xmlDoc *) doc); + + return (struct ast_xml_node *) root_node; +} + +void ast_xml_free_node(struct ast_xml_node *node) +{ + if (!node) { + return; + } + + xmlFreeNode((xmlNode *) node); + node = NULL; +} + +void ast_xml_free_attr(const char *attribute) +{ + if (attribute) { + xmlFree((char *) attribute); + } +} + +void ast_xml_free_text(const char *text) +{ + if (text) { + xmlFree((char *) text); + } +} + +const char *ast_xml_get_attribute(struct ast_xml_node *node, const char *attrname) +{ + xmlChar *attrvalue; + + if (!node) { + return NULL; + } + + if (!attrname) { + return NULL; + } + + attrvalue = xmlGetProp((xmlNode *) node, (xmlChar *) attrname); + + return (const char *) attrvalue; +} + +struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue) +{ + struct ast_xml_node *cur; + const char *attr; + + if (!root_node) { + return NULL; + } + + for (cur = root_node; cur; cur = ast_xml_node_get_next(cur)) { + /* Check if the name matchs */ + if (strcmp(ast_xml_node_get_name(cur), name)) { + continue; + } + /* We need to check for a specific attribute name? */ + if (!attrname || !attrvalue) { + return cur; + } + /* Get the attribute, we need to compare it. */ + if ((attr = ast_xml_get_attribute(cur, attrname))) { + /* does attribute name/value matches? */ + if (!strcmp(attr, attrvalue)) { + ast_xml_free_attr(attr); + return cur; + } + ast_xml_free_attr(attr); + } + } + + return NULL; +} + +const char *ast_xml_get_text(struct ast_xml_node *node) +{ + if (!node) { + return NULL; + } + + return (const char *) xmlNodeGetContent((xmlNode *) node); +} + +const char *ast_xml_node_get_name(struct ast_xml_node *node) +{ + return (const char *) ((xmlNode *) node)->name; +} + +struct ast_xml_node *ast_xml_node_get_children(struct ast_xml_node *node) +{ + return (struct ast_xml_node *) ((xmlNode *) node)->children; +} + +struct ast_xml_node *ast_xml_node_get_next(struct ast_xml_node *node) +{ + return (struct ast_xml_node *) ((xmlNode *) node)->next; +} + +struct ast_xml_node *ast_xml_node_get_prev(struct ast_xml_node *node) +{ + return (struct ast_xml_node *) ((xmlNode *) node)->prev; +} + +struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node) +{ + return (struct ast_xml_node *) ((xmlNode *) node)->parent; +} + +#endif /* defined(HAVE_LIBXML2) */ + |