Add REPLACE & PASSTHRU functions, overhaul of func_strings, fix API docs for the ast_get_encoded_* functions.

* Add REPLACE function, which searches a given variable for a set of
   characters and replaces each with a given character.
 * Add PASSTHRU function, which passes a literal string back, like a NoOp for
   functions.  Intent is to be able to specify a literal string to another
   function that takes a variable name as an argument.
 * Let the array manipulation functions work with dialplan functions, in
   addition to variables.  This allows the array manipulation functions to
   modify ASTDB and ODBC backends, assuming the func_odbc configuration has
   both read and write functions.
(closes issue #15223)
 Reported by: ajohnson
Patches: 
       20091112__issue15223.diff.txt uploaded by tilghman (license 14)
 Tested by: lmadsen, tilghman


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@230994 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Tilghman Lesher
2009-11-24 04:58:44 +00:00
parent 606276ec48
commit 0bccc4fbe6
3 changed files with 205 additions and 86 deletions

View File

@@ -146,8 +146,13 @@ Dialplan Functions
mode=multirow. If rowlimit is set, then additional rows may be retrieved mode=multirow. If rowlimit is set, then additional rows may be retrieved
from the same query by using the name of the function which retrieved the from the same query by using the name of the function which retrieved the
first row as an argument to ODBC_FETCH(). first row as an argument to ODBC_FETCH().
* Added JABBER_RECEIVE, which permits receiving XMPP messages from the * Added JABBER_RECEIVE, which permits receiving XMPP messages from the
dialplan. This function returns the content of the received message. dialplan. This function returns the content of the received message.
* Added REPLACE, which searches a given variable name for a set of characters,
then either replaces them with a single character or deletes them.
* Added PASSTHRU, which literally passes the same argument back as its return
value. The intent is to be able to use a literal string argument to
functions that currently require a variable name as an argument.
Dialplan Variables Dialplan Variables
------------------ ------------------

View File

@@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/localtime.h" #include "asterisk/localtime.h"
AST_THREADSTORAGE(result_buf); AST_THREADSTORAGE(result_buf);
AST_THREADSTORAGE(tmp_buf);
/*** DOCUMENTATION /*** DOCUMENTATION
<function name="FIELDQTY" language="en_US"> <function name="FIELDQTY" language="en_US">
@@ -91,6 +92,37 @@ AST_THREADSTORAGE(result_buf);
<literal>\</literal></para></note> <literal>\</literal></para></note>
</description> </description>
</function> </function>
<function name="REPLACE" language="en_US">
<synopsis>
Replace a set of characters in a given string with another character.
</synopsis>
<syntax>
<parameter name="varname" required="true" />
<parameter name="find-chars" required="true" />
<parameter name="replace-char" required="false" />
</syntax>
<description>
<para>Iterates through a string replacing all the <replaceable>find-chars</replaceable> with
<replaceable>replace-char</replaceable>. <replaceable>replace-char</replaceable> may be either
empty or contain one character. If empty, all <replaceable>find-chars</replaceable> will be
deleted from the output.</para>
<note><para>The replacement only occurs in the output. The original variable is not
altered.</para></note>
</description>
</function>
<function name="PASSTHRU" language="en_US">
<synopsis>
Pass the given argument back as a value.
</synopsis>
<syntax>
<parameter name="string" required="false" />
</syntax>
<description>
<para>Literally returns the given <replaceable>string</replaceable>. The intent is to permit
other dialplan functions which take a variable name as an argument to be able to take a literal
string, instead.</para>
</description>
</function>
<function name="REGEX" language="en_US"> <function name="REGEX" language="en_US">
<synopsis> <synopsis>
Check string against a regular expression. Check string against a regular expression.
@@ -363,7 +395,7 @@ static int function_fieldqty_helper(struct ast_channel *chan, const char *cmd,
char *parse, char *buf, struct ast_str **sbuf, ssize_t len) char *parse, char *buf, struct ast_str **sbuf, ssize_t len)
{ {
char *varsubst; char *varsubst;
struct ast_str *str = ast_str_create(16); struct ast_str *str = ast_str_thread_get(&result_buf, 16);
int fieldcount = 0; int fieldcount = 0;
AST_DECLARE_APP_ARGS(args, AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(varname); AST_APP_ARG(varname);
@@ -401,7 +433,6 @@ static int function_fieldqty_helper(struct ast_channel *chan, const char *cmd,
snprintf(buf, len, "%d", fieldcount); snprintf(buf, len, "%d", fieldcount);
} }
ast_free(str);
return 0; return 0;
} }
@@ -430,16 +461,19 @@ static int listfilter(struct ast_channel *chan, const char *cmd, char *parse, ch
AST_APP_ARG(delimiter); AST_APP_ARG(delimiter);
AST_APP_ARG(fieldvalue); AST_APP_ARG(fieldvalue);
); );
const char *orig_list, *ptr; const char *ptr;
struct ast_str *orig_list = ast_str_thread_get(&tmp_buf, 16);
const char *begin, *cur, *next; const char *begin, *cur, *next;
int dlen, flen, first = 1; int dlen, flen, first = 1;
struct ast_str *result, **result_ptr = &result; struct ast_str *result, **result_ptr = &result;
char *delim; char *delim, *varsubst;
AST_STANDARD_APP_ARGS(args, parse); AST_STANDARD_APP_ARGS(args, parse);
if (buf) { if (buf) {
result = ast_str_thread_get(&result_buf, 16); if (!(result = ast_str_thread_get(&result_buf, 16))) {
return -1;
}
} else { } else {
/* Place the result directly into the output buffer */ /* Place the result directly into the output buffer */
result_ptr = bufstr; result_ptr = bufstr;
@@ -450,11 +484,15 @@ static int listfilter(struct ast_channel *chan, const char *cmd, char *parse, ch
return -1; return -1;
} }
varsubst = alloca(strlen(args.listname) + 4);
sprintf(varsubst, "${%s}", args.listname);
/* If we don't lock the channel, the variable could disappear out from underneath us. */ /* If we don't lock the channel, the variable could disappear out from underneath us. */
if (chan) { if (chan) {
ast_channel_lock(chan); ast_channel_lock(chan);
} }
if (!(orig_list = pbx_builtin_getvar_helper(chan, args.listname))) { ast_str_substitute_variables(&orig_list, 0, chan, varsubst);
if (ast_str_strlen(orig_list)) {
ast_log(LOG_ERROR, "List variable '%s' not found\n", args.listname); ast_log(LOG_ERROR, "List variable '%s' not found\n", args.listname);
if (chan) { if (chan) {
ast_channel_unlock(chan); ast_channel_unlock(chan);
@@ -463,11 +501,11 @@ static int listfilter(struct ast_channel *chan, const char *cmd, char *parse, ch
} }
/* If the string isn't there, just copy out the string and be done with it. */ /* If the string isn't there, just copy out the string and be done with it. */
if (!(ptr = strstr(orig_list, args.fieldvalue))) { if (!(ptr = strstr(ast_str_buffer(orig_list), args.fieldvalue))) {
if (buf) { if (buf) {
ast_copy_string(buf, orig_list, len); ast_copy_string(buf, ast_str_buffer(orig_list), len);
} else { } else {
ast_str_set(result_ptr, len, "%s", orig_list); ast_str_set(result_ptr, len, "%s", ast_str_buffer(orig_list));
} }
if (chan) { if (chan) {
ast_channel_unlock(chan); ast_channel_unlock(chan);
@@ -489,10 +527,10 @@ static int listfilter(struct ast_channel *chan, const char *cmd, char *parse, ch
ast_str_reset(result); ast_str_reset(result);
/* Enough space for any result */ /* Enough space for any result */
if (len > -1) { if (len > -1) {
ast_str_make_space(result_ptr, len ? len : strlen(orig_list) + 1); ast_str_make_space(result_ptr, len ? len : ast_str_strlen(orig_list) + 1);
} }
begin = orig_list; begin = ast_str_buffer(orig_list);
next = strstr(begin, delim); next = strstr(begin, delim);
do { do {
@@ -609,6 +647,76 @@ static struct ast_custom_function filter_function = {
.read = filter, .read = filter,
}; };
static int replace(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
{
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(varname);
AST_APP_ARG(find);
AST_APP_ARG(replace);
);
char *strptr, *varsubst;
struct ast_str *str = ast_str_thread_get(&result_buf, 16);
char find[256]; /* Only 256 characters possible */
char replace[2] = "";
size_t unused;
AST_STANDARD_APP_ARGS(args, data);
if (!str) {
return -1;
}
if (args.argc < 2) {
ast_log(LOG_ERROR, "Usage: %s(<varname>,<search-chars>[,<replace-char>])\n", cmd);
return -1;
}
/* Decode escapes */
ast_get_encoded_str(args.find, find, sizeof(find));
ast_get_encoded_char(args.replace, replace, &unused);
if (ast_strlen_zero(find) || ast_strlen_zero(args.varname)) {
ast_log(LOG_ERROR, "The characters to search for and the variable name must not be empty.\n");
return -1;
}
varsubst = alloca(strlen(args.varname) + 4);
sprintf(varsubst, "${%s}", args.varname);
ast_str_substitute_variables(&str, 0, chan, varsubst);
if (!ast_str_strlen(str)) {
/* Blank, nothing to replace */
return -1;
}
ast_debug(3, "String to search: (%s)\n", ast_str_buffer(str));
ast_debug(3, "Characters to find: (%s)\n", find);
ast_debug(3, "Character to replace with: (%s)\n", replace);
for (strptr = ast_str_buffer(str); *strptr; strptr++) {
/* buf is already a mutable buffer, so we construct the result
* directly there */
if (strchr(find, *strptr)) {
if (ast_strlen_zero(replace)) {
/* Remove character */
strcpy(strptr, strptr + 1); /* SAFE */
strptr--;
} else {
/* Replace character */
*strptr = *replace;
}
}
}
ast_str_set(buf, len, "%s", ast_str_buffer(str));
return 0;
}
static struct ast_custom_function replace_function = {
.name = "REPLACE",
.read2 = replace,
};
static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf, static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf,
size_t len) size_t len)
{ {
@@ -1169,138 +1277,131 @@ static struct ast_custom_function tolower_function = {
.read2 = string_tolower2, .read2 = string_tolower2,
}; };
static int array_remove(struct ast_channel *chan, const char *cmd, char *var, char *buf, size_t len, int beginning) static int shift_pop(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
{ {
const char *tmp; #define beginning (cmd[0] == 'S') /* SHIFT */
char *after, *before; char *after, delimiter[2] = ",", *varsubst;
char *(*search_func)(const char *s, int c) = beginning ? strchr : strrchr; size_t unused;
struct ast_str *before = ast_str_thread_get(&result_buf, 16);
char *(*search_func)(const char *s, int c) = (beginning ? strchr : strrchr);
AST_DECLARE_APP_ARGS(args, AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(var); AST_APP_ARG(var);
AST_APP_ARG(delimiter); AST_APP_ARG(delimiter);
); );
if (!chan) { if (!before) {
ast_log(LOG_WARNING, "%s requires a channel\n", cmd);
return -1; return -1;
} }
AST_STANDARD_APP_ARGS(args, var); AST_STANDARD_APP_ARGS(args, data);
if (ast_strlen_zero(args.var)) { if (ast_strlen_zero(args.var)) {
ast_log(LOG_WARNING, "%s requires a channel variable name\n", cmd); ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
return -1; return -1;
} }
if (args.delimiter && strlen(args.delimiter) != 1) { varsubst = alloca(strlen(args.var) + 4);
ast_log(LOG_WARNING, "%s delimeters should be a single character\n", cmd); sprintf(varsubst, "${%s}", args.var);
ast_str_substitute_variables(&before, 0, chan, varsubst);
if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
ast_get_encoded_char(args.delimiter, delimiter, &unused);
}
if (!ast_str_strlen(before)) {
/* Nothing to pop */
return -1; return -1;
} }
ast_channel_lock(chan); if (!(after = search_func(ast_str_buffer(before), delimiter[0]))) {
if (ast_strlen_zero(tmp = pbx_builtin_getvar_helper(chan, args.var))) { /* Only one entry in array */
ast_channel_unlock(chan); ast_str_set(buf, len, "%s", ast_str_buffer(before));
return 0;
}
before = ast_strdupa(tmp);
ast_channel_unlock(chan);
/* Only one entry in array */
if (!(after = search_func(before, S_OR(args.delimiter, ",")[0]))) {
ast_copy_string(buf, before, len);
pbx_builtin_setvar_helper(chan, args.var, ""); pbx_builtin_setvar_helper(chan, args.var, "");
} else { } else {
*after++ = '\0'; *after++ = '\0';
ast_copy_string(buf, beginning ? before : after, len); ast_str_set(buf, len, "%s", beginning ? ast_str_buffer(before) : after);
pbx_builtin_setvar_helper(chan, args.var, beginning ? after : before); pbx_builtin_setvar_helper(chan, args.var, beginning ? after : ast_str_buffer(before));
} }
return 0; return 0;
#undef beginning
} }
static int shift(struct ast_channel *chan, const char *cmd, char *var, char *buf, size_t len)
{
return array_remove(chan, cmd, var, buf, len, 1);
}
static struct ast_custom_function shift_function = { static struct ast_custom_function shift_function = {
.name = "SHIFT", .name = "SHIFT",
.read = shift, .read2 = shift_pop,
}; };
static int pop(struct ast_channel *chan, const char *cmd, char *var, char *buf, size_t len)
{
return array_remove(chan, cmd, var, buf, len, 0);
}
static struct ast_custom_function pop_function = { static struct ast_custom_function pop_function = {
.name = "POP", .name = "POP",
.read = pop, .read2 = shift_pop,
}; };
static int array_insert(struct ast_channel *chan, const char *cmd, char *var, const char *val, int beginning) static int unshift_push(struct ast_channel *chan, const char *cmd, char *data, const char *new_value)
{ {
const char *tmp; #define beginning (cmd[0] == 'U') /* UNSHIFT */
struct ast_str *buf; char delimiter[2] = ",", *varsubst;
size_t unused;
struct ast_str *buf, *previous_value;
AST_DECLARE_APP_ARGS(args, AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(var); AST_APP_ARG(var);
AST_APP_ARG(delimiter); AST_APP_ARG(delimiter);
); );
if (!chan) { if (!(buf = ast_str_thread_get(&result_buf, 16)) ||
ast_log(LOG_WARNING, "%s requires a channel\n", cmd); !(previous_value = ast_str_thread_get(&tmp_buf, 16))) {
return -1; return -1;
} }
AST_STANDARD_APP_ARGS(args, var); AST_STANDARD_APP_ARGS(args, data);
if (ast_strlen_zero(args.var) || ast_strlen_zero(val)) { if (ast_strlen_zero(args.var)) {
ast_log(LOG_WARNING, "%s requires a variable, and at least one value\n", cmd); ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
return -1; return -1;
} }
if (args.delimiter && strlen(args.delimiter) != 1) { if (args.argc > 1 && !ast_strlen_zero(args.delimiter)) {
ast_log(LOG_WARNING, "%s delimeters should be a single character\n", cmd); ast_get_encoded_char(args.delimiter, delimiter, &unused);
return -1;
} }
if (!(buf = ast_str_create(32))) { varsubst = alloca(strlen(args.var) + 4);
ast_log(LOG_ERROR, "Unable to allocate memory for buffer!\n"); sprintf(varsubst, "${%s}", args.var);
return -1; ast_str_substitute_variables(&previous_value, 0, chan, varsubst);
}
ast_channel_lock(chan); if (!ast_str_strlen(previous_value)) {
if (!(tmp = pbx_builtin_getvar_helper(chan, args.var))) { ast_str_set(&buf, 0, "%s", new_value);
ast_str_set(&buf, 0, "%s", val);
} else { } else {
ast_str_append(&buf, 0, "%s%s%s", beginning ? val : tmp, S_OR(args.delimiter, ","), beginning ? tmp : val); ast_str_set(&buf, 0, "%s%c%s",
beginning ? new_value : ast_str_buffer(previous_value),
delimiter[0],
beginning ? ast_str_buffer(previous_value) : new_value);
} }
ast_channel_unlock(chan);
pbx_builtin_setvar_helper(chan, args.var, ast_str_buffer(buf)); pbx_builtin_setvar_helper(chan, args.var, ast_str_buffer(buf));
ast_free(buf);
return 0; return 0;
} #undef beginning
static int push(struct ast_channel *chan, const char *cmd, char *var, const char *val)
{
return array_insert(chan, cmd, var, val, 0);
} }
static struct ast_custom_function push_function = { static struct ast_custom_function push_function = {
.name = "PUSH", .name = "PUSH",
.write = push, .write = unshift_push,
}; };
static int unshift(struct ast_channel *chan, const char *cmd, char *var, const char *val)
{
return array_insert(chan, cmd, var, val, 1);
}
static struct ast_custom_function unshift_function = { static struct ast_custom_function unshift_function = {
.name = "UNSHIFT", .name = "UNSHIFT",
.write = unshift, .write = unshift_push,
};
static int passthru(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
{
ast_str_set(buf, len, "%s", data);
return 0;
}
static struct ast_custom_function passthru_function = {
.name = "PASSTHRU",
.read2 = passthru,
}; };
static int unload_module(void) static int unload_module(void)
@@ -1309,6 +1410,7 @@ static int unload_module(void)
res |= ast_custom_function_unregister(&fieldqty_function); res |= ast_custom_function_unregister(&fieldqty_function);
res |= ast_custom_function_unregister(&filter_function); res |= ast_custom_function_unregister(&filter_function);
res |= ast_custom_function_unregister(&replace_function);
res |= ast_custom_function_unregister(&listfilter_function); res |= ast_custom_function_unregister(&listfilter_function);
res |= ast_custom_function_unregister(&regex_function); res |= ast_custom_function_unregister(&regex_function);
res |= ast_custom_function_unregister(&array_function); res |= ast_custom_function_unregister(&array_function);
@@ -1328,6 +1430,7 @@ static int unload_module(void)
res |= ast_custom_function_unregister(&pop_function); res |= ast_custom_function_unregister(&pop_function);
res |= ast_custom_function_unregister(&push_function); res |= ast_custom_function_unregister(&push_function);
res |= ast_custom_function_unregister(&unshift_function); res |= ast_custom_function_unregister(&unshift_function);
res |= ast_custom_function_unregister(&passthru_function);
return res; return res;
} }
@@ -1338,6 +1441,7 @@ static int load_module(void)
res |= ast_custom_function_register(&fieldqty_function); res |= ast_custom_function_register(&fieldqty_function);
res |= ast_custom_function_register(&filter_function); res |= ast_custom_function_register(&filter_function);
res |= ast_custom_function_register(&replace_function);
res |= ast_custom_function_register(&listfilter_function); res |= ast_custom_function_register(&listfilter_function);
res |= ast_custom_function_register(&regex_function); res |= ast_custom_function_register(&regex_function);
res |= ast_custom_function_register(&array_function); res |= ast_custom_function_register(&array_function);
@@ -1357,6 +1461,7 @@ static int load_module(void)
res |= ast_custom_function_register(&pop_function); res |= ast_custom_function_register(&pop_function);
res |= ast_custom_function_register(&push_function); res |= ast_custom_function_register(&push_function);
res |= ast_custom_function_register(&unshift_function); res |= ast_custom_function_register(&unshift_function);
res |= ast_custom_function_register(&passthru_function);
return res; return res;
} }

View File

@@ -555,12 +555,21 @@ int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect,
/*! \brief Allow to record message and have a review option */ /*! \brief Allow to record message and have a review option */
int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path); int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path);
/*! \brief Decode an encoded control or extended ASCII character /*!\brief Decode an encoded control or extended ASCII character
\return Returns a pointer to the result string * \param[in] stream String to decode
*/ * \param[out] result Decoded character
* \param[out] consumed Number of characters used in stream to encode the character
* \retval -1 Stream is of zero length
* \retval 0 Success
*/
int ast_get_encoded_char(const char *stream, char *result, size_t *consumed); int ast_get_encoded_char(const char *stream, char *result, size_t *consumed);
/*! \brief Decode a stream of encoded control or extended ASCII characters */ /*!\brief Decode a stream of encoded control or extended ASCII characters
* \param[in] stream Encoded string
* \param[out] result Decoded string
* \param[in] result_len Maximum size of the result buffer
* \return A pointer to the result string
*/
char *ast_get_encoded_str(const char *stream, char *result, size_t result_len); char *ast_get_encoded_str(const char *stream, char *result, size_t result_len);
/*! \brief Decode a stream of encoded control or extended ASCII characters */ /*! \brief Decode a stream of encoded control or extended ASCII characters */