[Asterisk-code-review] func_sayfiles: Retrieve say file names (asterisk[18])

George Joseph asteriskteam at digium.com
Fri Sep 10 11:45:42 CDT 2021


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/16467 )

Change subject: func_sayfiles: Retrieve say file names
......................................................................

func_sayfiles: Retrieve say file names

Up until now, all of the logic used to translate
arguments to the Say applications has been
directly coupled to playback, preventing other
modules from using this logic.

This refactors code in say.c and adds a SAYFILES
function that can be used to retrieve the file
names that would be played. These can then be
used in other applications or for other purposes.

Additionally, a SayMoney application and a SayOrdinal
application are added. Both SayOrdinal and SayNumber
are also expanded to support integers greater than
one billion.

ASTERISK-29531

Change-Id: If9718c89353b8e153d84add3cc4637b79585db19
---
A doc/CHANGES-staging/say.txt
A funcs/func_sayfiles.c
M include/asterisk/say.h
M main/channel.c
M main/pbx_builtins.c
M main/say.c
6 files changed, 1,056 insertions(+), 95 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved; Approved for Submit



diff --git a/doc/CHANGES-staging/say.txt b/doc/CHANGES-staging/say.txt
new file mode 100644
index 0000000..115ceea
--- /dev/null
+++ b/doc/CHANGES-staging/say.txt
@@ -0,0 +1,7 @@
+Subject: say.c
+
+Adds SAYFILES function to retrieve the file names that would
+be played by corresponding Say applications, such as
+SayDigits, SayAlpha, etc.
+
+Additionally adds SayMoney and SayOrdinal applications.
diff --git a/funcs/func_sayfiles.c b/funcs/func_sayfiles.c
new file mode 100644
index 0000000..81f3259
--- /dev/null
+++ b/funcs/func_sayfiles.c
@@ -0,0 +1,396 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert <asterisk at phreaknet.org>
+ *
+ * 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 Returns files played by Say applications
+ *
+ * \author Naveen Albert <asterisk at phreaknet.org>
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/say.h"
+#include "asterisk/lock.h"
+#include "asterisk/localtime.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/conversions.h"
+
+/*** DOCUMENTATION
+	<function name="SAYFILES" language="en_US">
+		<synopsis>
+			Returns the ampersand-delimited file names that would be played by the Say applications (e.g. SayAlpha, SayDigits).
+		</synopsis>
+		<syntax>
+			<parameter name="value" required="true">
+				<para>The value to be translated to filenames.</para>
+			</parameter>
+			<parameter name="type">
+				<para>Say application type.</para>
+				<enumlist>
+					<enum name="alpha">
+						<para>Files played by SayAlpha(). Default if none is specified.</para>
+					</enum>
+					<enum name="digits">
+						<para>Files played by SayDigits().</para>
+					</enum>
+					<enum name="money">
+						<para>Files played by SayMoney(). Currently supported for English and US dollars only.</para>
+					</enum>
+					<enum name="number">
+						<para>Files played by SayNumber(). Currently supported for English only.</para>
+					</enum>
+					<enum name="ordinal">
+						<para>Files played by SayOrdinal(). Currently supported for English only.</para>
+					</enum>
+					<enum name="phonetic">
+						<para>Files played by SayPhonetic().</para>
+					</enum>
+				</enumlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Returns the files that would be played by a Say application. These filenames could then be
+			passed directly into Playback, BackGround, Read, Queue, or any application which supports
+			playback of multiple ampersand-delimited files.</para>
+			<example title="Read using the number 123">
+			 same => n,Read(response,${SAYFILES(123,number)})
+			</example>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
+			<ref type="application">SayPhonetic</ref>
+		</see-also>
+	</function>
+ ***/
+static int sayfile_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *value, *type, *files;
+	const char *lang;
+	struct ast_str *filenames = NULL;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(value);
+		AST_APP_ARG(type);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "SAYFILES requires an argument\n");
+		return 0;
+	}
+
+	AST_STANDARD_APP_ARGS(args, data);
+
+	value = args.value;
+	type = (ast_strlen_zero(args.type) ? "alpha" : args.type);
+	lang = (chan ? ast_channel_language(chan) : "en"); /* No chan for unit tests */
+
+	if (!strcmp(type, "alpha")) {
+		filenames = ast_get_character_str(value, lang, AST_SAY_CASE_NONE);
+	} else if (!strcmp(type, "phonetic")) {
+		filenames = ast_get_phonetic_str(value, lang);
+	} else if (!strcmp(type, "digits")) {
+		filenames = ast_get_digit_str(value, lang);
+	} else if (!strcmp(type, "number")) {
+		int num;
+		if (ast_str_to_int(value, &num)) {
+			ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
+		} else {
+			filenames = ast_get_number_str(num, lang);
+		}
+	} else if (!strcmp(type, "ordinal")) {
+		int num;
+		if (ast_str_to_int(value, &num)) {
+			ast_log(LOG_WARNING, "Invalid numeric argument: %s\n", value);
+		} else {
+			filenames = ast_get_ordinal_str(num, lang);
+		}
+	} else if (!strcmp(type, "money")) {
+		filenames = ast_get_money_str(value, lang);
+	} else {
+		ast_log(LOG_WARNING, "Invalid say type specified: %s\n", type);
+	}
+
+	if (!filenames) {
+		return -1;
+	}
+
+	files = ast_str_buffer(filenames);
+	snprintf(buf, len, "%s", files);
+	ast_free(filenames);
+
+	return 0;
+}
+
+static struct ast_custom_function sayfiles = {
+	.name = "SAYFILES",
+	.read = sayfile_exec,
+};
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_SAYFILES_function)
+{
+	enum ast_test_result_state res = AST_TEST_PASS;
+	struct ast_str *expr, *result;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_SAYFILES_function";
+		info->category = "/funcs/func_sayfiles/";
+		info->summary = "Test SAYFILES function substitution";
+		info->description =
+			"Executes a series of variable substitutions using the SAYFILES function and ensures that the expected results are received.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Testing SAYFILES() substitution ...\n");
+
+	if (!(expr = ast_str_create(16))) {
+		return AST_TEST_FAIL;
+	}
+	if (!(result = ast_str_create(16))) {
+		ast_free(expr);
+		return AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(hi Th3re,alpha)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "letters/h&letters/i&letters/space&letters/t&letters/h&digits/3&letters/r&letters/e") != 0) {
+		ast_test_status_update(test, "SAYFILES(hi Th3re,alpha) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(phreak,phonetic)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "phonetic/p_p&phonetic/h_p&phonetic/r_p&phonetic/e_p&phonetic/a_p&phonetic/k_p") != 0) {
+		ast_test_status_update(test, "SAYFILES(phreak,phonetic) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(35,digits)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/3&digits/5") != 0) {
+		ast_test_status_update(test, "SAYFILES(35,digits) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(35,number)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/30&digits/5") != 0) {
+		ast_test_status_update(test, "SAYFILES(35,number) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(747,number)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/7&digits/hundred&digits/40&digits/7") != 0) {
+		ast_test_status_update(test, "SAYFILES(747,number) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1042,number)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/2") != 0) {
+		ast_test_status_update(test, "SAYFILES(1042,number) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(0,number)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/0") != 0) {
+		ast_test_status_update(test, "SAYFILES(0,digits) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(2001000001,number)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/1") != 0) {
+		ast_test_status_update(test, "SAYFILES(2001000001,number) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(7,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/h-7") != 0) {
+		ast_test_status_update(test, "SAYFILES(7,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(35,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/30&digits/h-5") != 0) {
+		ast_test_status_update(test, "SAYFILES(35,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1042,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&digits/thousand&digits/40&digits/h-2") != 0) {
+		ast_test_status_update(test, "SAYFILES(1042,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(11042,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/11&digits/thousand&digits/40&digits/h-2") != 0) {
+		ast_test_status_update(test, "SAYFILES(11042,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(40000,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/40&digits/h-thousand") != 0) {
+		ast_test_status_update(test, "SAYFILES(40000,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(43638,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/40&digits/3&digits/thousand&digits/6&digits/hundred&digits/30&digits/h-8") != 0) {
+		ast_test_status_update(test, "SAYFILES(43638,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1000000,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&digits/h-million") != 0) {
+		ast_test_status_update(test, "SAYFILES(1000000,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1000001,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&digits/million&digits/h-1") != 0) {
+		ast_test_status_update(test, "SAYFILES(1000001,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(2001000001,ordinal)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/2&digits/billion&digits/1&digits/million&digits/h-1") != 0) {
+		ast_test_status_update(test, "SAYFILES(2001000001,ordinal) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(0,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/0&cents") != 0) {
+		ast_test_status_update(test, "SAYFILES(0,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(0.01,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&cent") != 0) {
+		ast_test_status_update(test, "SAYFILES(0.01,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(0.42,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/40&digits/2&cents") != 0) {
+		ast_test_status_update(test, "SAYFILES(0.42,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1.00,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar") != 0) {
+		ast_test_status_update(test, "SAYFILES(1.00,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(1.42,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&letters/dollar_&and&digits/40&digits/2&cents") != 0) {
+		ast_test_status_update(test, "SAYFILES(1.42,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(2.00,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/2&dollars") != 0) {
+		ast_test_status_update(test, "SAYFILES(2.00,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_str_set(&expr, 0, "${SAYFILES(2.42,money)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/2&dollars&and&digits/40&digits/2&cents") != 0) {
+		ast_test_status_update(test, "SAYFILES(2.42,money) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
+	ast_free(expr);
+	ast_free(result);
+
+	return res;
+}
+#endif
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(test_SAYFILES_function);
+	return ast_custom_function_unregister(&sayfiles);
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(test_SAYFILES_function);
+	return ast_custom_function_register(&sayfiles);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Say application files");
diff --git a/include/asterisk/say.h b/include/asterisk/say.h
index a4aa90c..5e55b0f 100644
--- a/include/asterisk/say.h
+++ b/include/asterisk/say.h
@@ -86,6 +86,25 @@
 SAY_EXTERN int (* ast_say_number_full)(struct ast_channel *chan, int num, const char *ints, const char *lang, const char *options, int audiofd, int ctrlfd) SAY_INIT(ast_say_number_full);
 
 /*!
+ * \brief says an ordinal number
+ * \param chan channel to say them number on
+ * \param num ordinal number to say on the channel
+ * \param ints which dtmf to interrupt on
+ * \param lang language to speak the number
+ * \param options set to 'f' for female, 'm' for male, 'c' for commune, 'n' for neuter
+ * \details
+ * Vocally says an ordinal number on a given channel
+ * \retval 0 on success
+ * \retval DTMF digit on interrupt
+ * \retval -1 on failure
+ */
+int ast_say_ordinal(struct ast_channel *chan, int num,
+	const char *ints, const char *lang, const char *options);
+
+/*! \brief Same as \ref ast_say_number() with audiofd for received audio and returns 1 on ctrlfd being readable */
+SAY_EXTERN int (* ast_say_ordinal_full)(struct ast_channel *chan, int num, const char *ints, const char *lang, const char *options, int audiofd, int ctrlfd) SAY_INIT(ast_say_ordinal_full);
+
+/*!
  * \brief says an enumeration
  * \param chan channel to say them enumeration on
  * \param num number to say on the channel
@@ -143,6 +162,14 @@
 SAY_EXTERN int (* ast_say_digit_str_full)(struct ast_channel *chan, const char *num, const char *ints, const char *lang, int audiofd, int ctrlfd) SAY_INIT(ast_say_digit_str_full);
 
 /*! \brief
+ * function to pronounce monetary amounts
+ */
+int ast_say_money_str(struct ast_channel *chan, const char *num,
+	const char *ints, const char *lang);
+
+SAY_EXTERN int (* ast_say_money_str_full)(struct ast_channel *chan, const char *num, const char *ints, const char *lang, int audiofd, int ctrlfd) SAY_INIT(ast_say_money_str_full);
+
+/*! \brief
  * the generic 'say' routine, with the first chars in the string
  * defining the format to use
  */
@@ -184,6 +211,79 @@
 
 int ast_say_counted_adjective(struct ast_channel *chan, int num, const char *adjective, const char *gender);
 
+/*!
+ * \brief Returns an ast_str of files for SayAlpha playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ * \param sensitivity Case sensitivity
+ *
+ * Computes the list of files to be played by SayAlpha.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_character_str(const char *str, const char *lang, enum ast_say_case_sensitivity sensitivity);
+
+/*!
+ * \brief Returns an ast_str of files for SayPhonetic playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayPhonetic.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_phonetic_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayDigits playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayDigits.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_digit_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayMoney playback.
+ *
+ * \param str Text to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayMoney.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_money_str(const char *str, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayNumber playback.
+ *
+ * \param num Integer to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayNumber.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_number_str(int num, const char *lang);
+
+/*!
+ * \brief Returns an ast_str of files for SayOrdinal playback.
+ *
+ * \param num Integer to be translated to the corresponding audio files.
+ * \param lang Channel language
+ *
+ * Computes the list of files to be played by SayOrdinal.
+ *
+ * \retval ampersand-separated string of Asterisk sound files that can be played back.
+ */
+struct ast_str* ast_get_ordinal_str(int num, const char *lang);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/main/channel.c b/main/channel.c
index 9e33fb2..9104ef8 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -8257,6 +8257,12 @@
 	return ast_say_number_full(chan, num, ints, language, options, -1, -1);
 }
 
+int ast_say_ordinal(struct ast_channel *chan, int num,
+	const char *ints, const char *language, const char *options)
+{
+	return ast_say_ordinal_full(chan, num, ints, language, options, -1, -1);
+}
+
 int ast_say_enumeration(struct ast_channel *chan, int num,
 	const char *ints, const char *language, const char *options)
 {
@@ -8275,6 +8281,12 @@
 	return ast_say_digit_str_full(chan, str, ints, lang, -1, -1);
 }
 
+int ast_say_money_str(struct ast_channel *chan, const char *str,
+	const char *ints, const char *lang)
+{
+	return ast_say_money_str_full(chan, str, ints, lang, -1, -1);
+}
+
 int ast_say_character_str(struct ast_channel *chan, const char *str,
 	const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity)
 {
diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c
index e370338..eae4c6b 100644
--- a/main/pbx_builtins.c
+++ b/main/pbx_builtins.c
@@ -37,6 +37,7 @@
 #include "asterisk/say.h"
 #include "asterisk/app.h"
 #include "asterisk/module.h"
+#include "asterisk/conversions.h"
 #include "pbx_private.h"
 
 /*** DOCUMENTATION
@@ -440,9 +441,12 @@
 		</description>
 		<see-also>
 			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
 			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
 			<ref type="application">SayPhonetic</ref>
 			<ref type="function">CHANNEL</ref>
+			<ref type="function">SAYFILES</ref>
 		</see-also>
 	</application>
 	<application name="SayAlphaCase" language="en_US">
@@ -481,7 +485,9 @@
 		</description>
 		<see-also>
 			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
 			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
 			<ref type="application">SayPhonetic</ref>
 			<ref type="application">SayAlpha</ref>
 			<ref type="function">CHANNEL</ref>
@@ -503,9 +509,35 @@
 		</description>
 		<see-also>
 			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayMoney</ref>
 			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
 			<ref type="application">SayPhonetic</ref>
 			<ref type="function">CHANNEL</ref>
+			<ref type="function">SAYFILES</ref>
+		</see-also>
+	</application>
+	<application name="SayMoney" language="en_US">
+		<synopsis>
+			Say Money.
+		</synopsis>
+		<syntax>
+			<parameter name="dollars" required="true" />
+		</syntax>
+		<description>
+			<para>This application will play the currency sounds for the given floating point number
+			in the current language. Currently only English and US Dollars is supported.
+			If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true'
+			(case insensitive), then this application will react to DTMF in the same way as
+			<literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="function">CHANNEL</ref>
+			<ref type="function">SAYFILES</ref>
 		</see-also>
 	</application>
 	<application name="SayNumber" language="en_US">
@@ -527,8 +559,37 @@
 		<see-also>
 			<ref type="application">SayAlpha</ref>
 			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
 			<ref type="application">SayPhonetic</ref>
 			<ref type="function">CHANNEL</ref>
+			<ref type="function">SAYFILES</ref>
+		</see-also>
+	</application>
+	<application name="SayOrdinal" language="en_US">
+		<synopsis>
+			Say Ordinal Number.
+		</synopsis>
+		<syntax>
+			<parameter name="digits" required="true" />
+			<parameter name="gender" />
+		</syntax>
+		<description>
+			<para>This application will play the ordinal sounds that correspond to the given
+			<replaceable>digits</replaceable> (e.g. 1st, 42nd). Currently only English is supported.</para>
+			<para>Optionally, a <replaceable>gender</replaceable> may be
+			specified. This will use the language that is currently set for the channel. See the CHANNEL()
+			function for more information on setting the language for the channel. If the channel variable
+			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+			application will react to DTMF in the same way as <literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="function">CHANNEL</ref>
+			<ref type="function">SAYFILES</ref>
 		</see-also>
 	</application>
 	<application name="SayPhonetic" language="en_US">
@@ -547,7 +608,10 @@
 		<see-also>
 			<ref type="application">SayAlpha</ref>
 			<ref type="application">SayDigits</ref>
+			<ref type="application">SayMoney</ref>
 			<ref type="application">SayNumber</ref>
+			<ref type="application">SayOrdinal</ref>
+			<ref type="function">SAYFILES</ref>
 		</see-also>
 	</application>
 	<application name="SetAMAFlags" language="en_US">
@@ -1283,7 +1347,7 @@
 	ast_copy_string(tmp, data, sizeof(tmp));
 	strsep(&number, ",");
 
-	if (sscanf(tmp, "%d", &number_val) != 1) {
+	if (ast_str_to_int(tmp, &number_val)) {
 		ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp);
 		return 0;
 	}
@@ -1306,6 +1370,53 @@
 	return interrupt ? res : 0;
 }
 
+static int pbx_builtin_sayordinal(struct ast_channel *chan, const char *data)
+{
+	char tmp[256];
+	char *number = tmp;
+	int number_val;
+	char *options;
+	int res;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "SayOrdinal requires an argument (number)\n");
+		return -1;
+	}
+	ast_copy_string(tmp, data, sizeof(tmp));
+	strsep(&number, ",");
+
+	if (ast_str_to_int(tmp, &number_val)) {
+		ast_log(LOG_WARNING, "argument '%s' to SayOrdinal could not be parsed as a number.\n", tmp);
+		return 0;
+	}
+
+	options = strsep(&number, ",");
+	if (options) {
+		if ( strcasecmp(options, "f") && strcasecmp(options, "m") &&
+			strcasecmp(options, "c") && strcasecmp(options, "n") ) {
+			ast_log(LOG_WARNING, "SayOrdinal gender option is either 'f', 'm', 'c' or 'n'\n");
+			return -1;
+		}
+	}
+
+	res = ast_say_ordinal(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options);
+
+	if (res < 0 && !ast_check_hangup_locked(chan)) {
+		ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp);
+	}
+
+	return interrupt ? res : 0;
+}
+
 static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data)
 {
 	int res = 0;
@@ -1326,6 +1437,26 @@
 	return res;
 }
 
+static int pbx_builtin_saymoney(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (data) {
+		res = ast_say_money_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+	}
+
+	return res;
+}
+
 static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data)
 {
 	int res = 0;
@@ -1483,7 +1614,9 @@
 	{ "SayAlpha",       pbx_builtin_saycharacters },
 	{ "SayAlphaCase",   pbx_builtin_saycharacters_case },
 	{ "SayDigits",      pbx_builtin_saydigits },
+	{ "SayMoney",       pbx_builtin_saymoney },
 	{ "SayNumber",      pbx_builtin_saynumber },
+	{ "SayOrdinal",     pbx_builtin_sayordinal },
 	{ "SayPhonetic",    pbx_builtin_sayphonetic },
 	{ "SetAMAFlags",    pbx_builtin_setamaflags },
 	{ "Wait",           pbx_builtin_wait },
diff --git a/main/say.c b/main/say.c
index 0a37091..009ee8f 100644
--- a/main/say.c
+++ b/main/say.c
@@ -29,6 +29,8 @@
  *						Next Generation Networks (NGN).
  * \note 2007-03-20 : Support for Thai added by Dome C. <dome at tel.co.th>,
  *						IP Crossing Co., Ltd.
+ * \note 2021-07-26 : Refactoring to separate string buildup and playback
+ *						by Naveen Albert <asterisk at phreaknet.org>
  */
 
 /*** MODULEINFO
@@ -58,9 +60,7 @@
 /* Forward declaration */
 static int wait_file(struct ast_channel *chan, const char *ints, const char *file, const char *lang);
 
-
-static int say_character_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity, int audiofd, int ctrlfd)
-{
+struct ast_str* ast_get_character_str(const char *str, const char *lang, enum ast_say_case_sensitivity sensitivity) {
 	const char *fn;
 	char fnbuf[10], asciibuf[20] = "letters/ascii";
 	char ltr;
@@ -69,6 +69,12 @@
 	int upper = 0;
 	int lower = 0;
 
+	struct ast_str *filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
 	while (str[num] && !res) {
 		fn = NULL;
 		switch (str[num]) {
@@ -154,14 +160,7 @@
 		}
 		if ((fn && ast_fileexists(fn, NULL, lang) > 0) ||
 			(snprintf(asciibuf + 13, sizeof(asciibuf) - 13, "%d", str[num]) > 0 && ast_fileexists(asciibuf, NULL, lang) > 0 && (fn = asciibuf))) {
-			res = ast_streamfile(chan, fn, lang);
-			if (!res) {
-				if ((audiofd  > -1) && (ctrlfd > -1))
-					res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
-				else
-					res = ast_waitstream(chan, ints);
-			}
-			ast_stopstream(chan);
+			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
 		}
 		if (upper || lower) {
 			continue;
@@ -169,18 +168,56 @@
 		num++;
 	}
 
+	return filenames;
+}
+
+static int say_filenames(struct ast_channel *chan, const char *ints, const char *lang, int audiofd, int ctrlfd, struct ast_str *filenames)
+{
+	int res = 0;
+	char *files;
+	const char *fn;
+
+	if (!filenames) {
+		return -1;
+	}
+	files = ast_str_buffer(filenames);
+
+	while ((fn = strsep(&files, "&"))) {
+		res = ast_streamfile(chan, fn, lang);
+		if (!res) {
+			if ((audiofd  > -1) && (ctrlfd > -1))
+				res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
+			else
+				res = ast_waitstream(chan, ints);
+		}
+		ast_stopstream(chan);
+	}
+
+	ast_free(filenames);
+
 	return res;
 }
 
-static int say_phonetic_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+static int say_character_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, enum ast_say_case_sensitivity sensitivity, int audiofd, int ctrlfd)
+{
+	struct ast_str *filenames = ast_get_character_str(str, lang, sensitivity);
+	return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+struct ast_str* ast_get_phonetic_str(const char *str, const char *lang)
 {
 	const char *fn;
 	char fnbuf[256];
 	char ltr;
 	int num = 0;
-	int res = 0;
 
-	while (str[num] && !res) {
+	struct ast_str *filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
+	while (str[num]) {
 		fn = NULL;
 		switch (str[num]) {
 		case ('*'):
@@ -237,29 +274,33 @@
 			fn = fnbuf;
 		}
 		if (fn && ast_fileexists(fn, NULL, lang) > 0) {
-			res = ast_streamfile(chan, fn, lang);
-			if (!res) {
-				if ((audiofd  > -1) && (ctrlfd > -1))
-					res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
-				else
-					res = ast_waitstream(chan, ints);
-			}
-			ast_stopstream(chan);
+			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
 		}
 		num++;
 	}
 
-	return res;
+	return filenames;
 }
 
-static int say_digit_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+static int say_phonetic_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+	struct ast_str *filenames = ast_get_phonetic_str(str, lang);
+	return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+struct ast_str* ast_get_digit_str(const char *str, const char *lang)
 {
 	const char *fn;
 	char fnbuf[256];
 	int num = 0;
-	int res = 0;
 
-	while (str[num] && !res) {
+	struct ast_str *filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
+	while (str[num]) {
 		fn = NULL;
 		switch (str[num]) {
 		case ('*'):
@@ -287,19 +328,340 @@
 			break;
 		}
 		if (fn && ast_fileexists(fn, NULL, lang) > 0) {
-			res = ast_streamfile(chan, fn, lang);
-			if (!res) {
-				if ((audiofd  > -1) && (ctrlfd > -1))
-					res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
-				else
-					res = ast_waitstream(chan, ints);
-			}
-			ast_stopstream(chan);
+			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
 		}
 		num++;
 	}
 
-	return res;
+	return filenames;
+}
+
+static int say_digit_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+	struct ast_str *filenames = ast_get_digit_str(str, lang);
+	return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+static struct ast_str* ast_get_money_en_dollars_str(const char *str, const char *lang)
+{
+	const char *fnr;
+
+	double dollars = 0;
+	int amt, cents;
+	struct ast_str *fnrecurse = NULL;
+
+	struct ast_str *filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
+	if (sscanf(str, "%30lf", &dollars) != 1) {
+		amt = 0;
+	} else { /* convert everything to cents */
+		amt = dollars * 100;
+	}
+
+	/* Just the cents after the dollar decimal point */
+	cents = amt - (((int) dollars) * 100);
+	ast_debug(1, "Cents is %d, amount is %d\n", cents, amt);
+
+	if (amt >= 100) {
+		fnrecurse = ast_get_number_str((amt / 100), lang);
+		if (!fnrecurse) {
+			ast_log(LOG_WARNING, "Couldn't get string for dollars\n");
+		} else {
+			fnr = ast_str_buffer(fnrecurse);
+			ast_str_append(&filenames, 0, "%s", fnr);
+		}
+
+		/* If this is it, end on a down pitch, otherwise up pitch */
+		if (amt < 200) {
+			ast_str_append(&filenames, 0, "&%s", (cents > 0) ? "letters/dollar_" : "letters/dollar");
+		} else {
+			ast_str_append(&filenames, 0, "&%s", "dollars");
+		}
+
+		/* If dollars and cents, add "and" in the middle */
+		if (cents > 0) {
+			ast_str_append(&filenames, 0, "&%s", "and");
+		}
+	}
+
+	if (cents > 0) {
+		fnrecurse = ast_get_number_str(cents, lang);
+		if (!fnrecurse) {
+			ast_log(LOG_ERROR, "Couldn't get string for cents\n");
+		} else {
+			fnr = ast_str_buffer(fnrecurse);
+			ast_str_append(&filenames, 0, (amt < 100 ? "%s" : "&%s"), fnr);
+		}
+		ast_str_append(&filenames, 0, "&%s", (cents == 1) ? "cent" : "cents");
+	} else if (amt == 0) {
+		fnrecurse = ast_get_digit_str("0", lang);
+		if (!fnrecurse) {
+			ast_log(LOG_ERROR, "Couldn't get string for cents\n");
+		} else {
+			fnr = ast_str_buffer(fnrecurse);
+			ast_str_append(&filenames, 0, "%s", fnr);
+		}
+		ast_str_append(&filenames, 0, "&%s", "cents");
+	}
+
+	if (fnrecurse) {
+		ast_free(fnrecurse);
+	}
+
+	return filenames;
+}
+
+/*! \brief  ast_get_money_str: call language-specific functions */
+struct ast_str* ast_get_money_str(const char *str, const char *lang)
+{
+	if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+		return ast_get_money_en_dollars_str(str, lang);
+	}
+
+	ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to US Dollars\n", lang);
+	/* Default to english */
+	return ast_get_money_en_dollars_str(str, lang);
+}
+
+static int say_money_str_full(struct ast_channel *chan, const char *str, const char *ints, const char *lang, int audiofd, int ctrlfd)
+{
+	struct ast_str *filenames = ast_get_money_str(str, lang);
+	return say_filenames(chan, ints, lang, audiofd, ctrlfd, filenames);
+}
+
+static struct ast_str* get_number_str_en(int num, const char *lang)
+{
+	const char *fnr;
+	int loops = 0;
+
+	int res = 0;
+	int playh = 0;
+	char fn[256] = "";
+
+	struct ast_str *filenames;
+
+	if (!num) {
+		return ast_get_digit_str("0", lang);
+	}
+
+	filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
+	while (!res && (num || playh)) {
+		if (num < 0) {
+			ast_copy_string(fn, "digits/minus", sizeof(fn));
+			if ( num > INT_MIN ) {
+				num = -num;
+			} else {
+				num = 0;
+			}
+		} else if (playh) {
+			ast_copy_string(fn, "digits/hundred", sizeof(fn));
+			playh = 0;
+		} else if (num < 20) {
+			snprintf(fn, sizeof(fn), "digits/%d", num);
+			num = 0;
+		} else if (num < 100) {
+			snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
+			num %= 10;
+		} else {
+			if (num < 1000){
+				snprintf(fn, sizeof(fn), "digits/%d", (num/100));
+				playh++;
+				num %= 100;
+			} else {
+				struct ast_str *fnrecurse = NULL;
+				if (num < 1000000) { /* 1,000,000 */
+					fnrecurse = get_number_str_en((num / 1000), lang);
+					if (!fnrecurse) {
+						ast_log(LOG_ERROR, "Couldn't get string for num\n");
+					} else {
+						fnr = ast_str_buffer(fnrecurse);
+						ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+					}
+					num %= 1000;
+					snprintf(fn, sizeof(fn), "&digits/thousand");
+				} else {
+					if (num < 1000000000) {	/* 1,000,000,000 */
+						fnrecurse = get_number_str_en((num / 1000000), lang);
+						if (!fnrecurse) {
+							ast_log(LOG_ERROR, "Couldn't get string for num\n");
+						} else {
+							fnr = ast_str_buffer(fnrecurse);
+							ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+						}
+						num %= 1000000;
+						ast_copy_string(fn, "&digits/million", sizeof(fn));
+					} else {
+						if (num < INT_MAX) {
+							fnrecurse = get_number_str_en((num / 1000000000), lang);
+							if (!fnrecurse) {
+								ast_log(LOG_ERROR, "Couldn't get string for num\n");
+							} else {
+								fnr = ast_str_buffer(fnrecurse);
+								ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+							}
+							num %= 1000000000;
+							ast_copy_string(fn, "&digits/billion", sizeof(fn));
+						} else {
+							ast_log(LOG_WARNING, "Number '%d' is too big for me\n", num);
+							res = -1;
+						}
+					}
+				}
+				if (fnrecurse) {
+					ast_free(fnrecurse);
+				}
+				/* we already decided whether or not to add an &, don't add another one immediately */
+				loops = 0;
+			}
+		}
+		if (!res) {
+			ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fn);
+			loops++;
+		}
+	}
+
+	return filenames;
+}
+
+/*! \brief  ast_get_number_str: call language-specific functions */
+struct ast_str* ast_get_number_str(int num, const char *lang)
+{
+	if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+		return get_number_str_en(num, lang);
+	}
+
+	ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to English\n", lang);
+	/* Default to english */
+	return get_number_str_en(num, lang);
+}
+
+static struct ast_str* get_ordinal_str_en(int num, const char *lang)
+{
+	const char *fnr;
+	int loops = 0;
+
+	int res = 0;
+	int playh = 0;
+	char fn[256] = "";
+
+	struct ast_str *filenames;
+
+	if (!num) {
+		num = 0;
+	}
+
+	filenames = ast_str_create(20);
+	if (!filenames) {
+		return NULL;
+	}
+	ast_str_reset(filenames);
+
+	while (!res && (num || playh)) {
+		if (num < 0) {
+			ast_copy_string(fn, "digits/minus", sizeof(fn));
+			if ( num > INT_MIN ) {
+				num = -num;
+			} else {
+				num = 0;
+			}
+		} else if (playh) {
+			ast_copy_string(fn, (num % 100 == 0) ? "digits/h-hundred" : "digits/hundred", sizeof(fn));
+			playh = 0;
+		} else if (num < 20) {
+			if (num > 0) {
+				snprintf(fn, sizeof(fn), "digits/h-%d", num);
+			} else {
+				ast_log(LOG_ERROR, "Unsupported ordinal number: %d\n", num);
+			}
+			num = 0;
+		} else if (num < 100) {
+			int base = (num / 10) * 10;
+			if (base != num) {
+				snprintf(fn, sizeof(fn), "digits/%d", base);
+			} else {
+				snprintf(fn, sizeof(fn), "digits/h-%d", base);
+			}
+			num %= 10;
+		} else {
+			if (num < 1000){
+				snprintf(fn, sizeof(fn), "digits/%d", (num/100));
+				playh++;
+				num %= 100;
+			} else {
+				struct ast_str *fnrecurse = NULL;
+				if (num < 1000000) { /* 1,000,000 */
+					fnrecurse = get_number_str_en((num / 1000), lang);
+					if (!fnrecurse) {
+						ast_log(LOG_ERROR, "Couldn't get string for num\n");
+					} else {
+						fnr = ast_str_buffer(fnrecurse);
+						ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+					}
+					num %= 1000;
+					snprintf(fn, sizeof(fn), (num % 1000 == 0) ? "&digits/h-thousand" : "&digits/thousand");
+				} else {
+					if (num < 1000000000) {	/* 1,000,000,000 */
+						fnrecurse = get_number_str_en((num / 1000000), lang);
+						if (!fnrecurse) {
+							ast_log(LOG_ERROR, "Couldn't get string for num\n");
+						} else {
+							fnr = ast_str_buffer(fnrecurse);
+							ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+						}
+						num %= 1000000;
+						ast_copy_string(fn, (num % 1000000 == 0) ? "&digits/h-million" : "&digits/million", sizeof(fn));
+					} else {
+						if (num < INT_MAX) {
+							fnrecurse = get_number_str_en((num / 1000000000), lang);
+							if (!fnrecurse) {
+								ast_log(LOG_ERROR, "Couldn't get string for num\n");
+							} else {
+								fnr = ast_str_buffer(fnrecurse);
+								ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fnr);
+							}
+							num %= 1000000000;
+							ast_copy_string(fn, (num % 1000000000 == 0) ? "&digits/h-billion" : "&digits/billion", sizeof(fn));
+						} else {
+							ast_log(LOG_WARNING, "Number '%d' is too big for me\n", num);
+							res = -1;
+						}
+					}
+				}
+				if (fnrecurse) {
+					ast_free(fnrecurse);
+				}
+				/* we already decided whether or not to add an &, don't add another one immediately */
+				loops = 0;
+			}
+		}
+		if (!res) {
+			ast_str_append(&filenames, 0, (loops == 0 ? "%s" : "&%s"), fn);
+			loops++;
+		}
+	}
+
+	return filenames;
+}
+
+/*! \brief  ast_get_ordinal_str: call language-specific functions */
+struct ast_str* ast_get_ordinal_str(int num, const char *lang)
+{
+	if (!strncasecmp(lang, "en", 2)) { /* English syntax */
+		return get_ordinal_str_en(num, lang);
+	}
+
+	ast_log(LOG_WARNING, "Language %s not currently supported, defaulting to English\n", lang);
+	/* Default to english */
+	return get_ordinal_str_en(num, lang);
 }
 
 /* Forward declarations */
@@ -540,66 +902,15 @@
 	\note This is the default syntax, if no other syntax defined in this file is used */
 static int ast_say_number_full_en(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd)
 {
-	int res = 0;
-	int playh = 0;
-	char fn[256] = "";
-	if (!num)
-		return ast_say_digits_full(chan, 0, ints, language, audiofd, ctrlfd);
+	struct ast_str *filenames = ast_get_number_str(num, language);
+	return say_filenames(chan, ints, language, audiofd, ctrlfd, filenames);
+}
 
-	while (!res && (num || playh)) {
-		if (num < 0) {
-			ast_copy_string(fn, "digits/minus", sizeof(fn));
-			if ( num > INT_MIN ) {
-				num = -num;
-			} else {
-				num = 0;
-			}
-		} else if (playh) {
-			ast_copy_string(fn, "digits/hundred", sizeof(fn));
-			playh = 0;
-		} else if (num < 20) {
-			snprintf(fn, sizeof(fn), "digits/%d", num);
-			num = 0;
-		} else if (num < 100) {
-			snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
-			num %= 10;
-		} else {
-			if (num < 1000){
-				snprintf(fn, sizeof(fn), "digits/%d", (num/100));
-				playh++;
-				num %= 100;
-			} else {
-				if (num < 1000000) { /* 1,000,000 */
-					res = ast_say_number_full_en(chan, num / 1000, ints, language, audiofd, ctrlfd);
-					if (res)
-						return res;
-					num %= 1000;
-					snprintf(fn, sizeof(fn), "digits/thousand");
-				} else {
-					if (num < 1000000000) {	/* 1,000,000,000 */
-						res = ast_say_number_full_en(chan, num / 1000000, ints, language, audiofd, ctrlfd);
-						if (res)
-							return res;
-						num %= 1000000;
-						ast_copy_string(fn, "digits/million", sizeof(fn));
-					} else {
-						ast_debug(1, "Number '%d' is too big for me\n", num);
-						res = -1;
-					}
-				}
-			}
-		}
-		if (!res) {
-			if (!ast_streamfile(chan, fn, language)) {
-				if ((audiofd  > -1) && (ctrlfd > -1))
-					res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
-				else
-					res = ast_waitstream(chan, ints);
-			}
-			ast_stopstream(chan);
-		}
-	}
-	return res;
+/*! \brief  say_ordinal_full */
+static int say_ordinal_full(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd)
+{
+	struct ast_str *filenames = ast_get_ordinal_str(num, language);
+	return say_filenames(chan, ints, language, audiofd, ctrlfd, filenames);
 }
 
 static int exp10_int(int power)
@@ -9410,8 +9721,10 @@
 static void __attribute__((constructor)) __say_init(void)
 {
 	ast_say_number_full = say_number_full;
+	ast_say_ordinal_full = say_ordinal_full;
 	ast_say_enumeration_full = say_enumeration_full;
 	ast_say_digit_str_full = say_digit_str_full;
+	ast_say_money_str_full = say_money_str_full;
 	ast_say_character_str_full = say_character_str_full;
 	ast_say_phonetic_str_full = say_phonetic_str_full;
 	ast_say_datetime = say_datetime;

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/16467
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 18
Gerrit-Change-Id: If9718c89353b8e153d84add3cc4637b79585db19
Gerrit-Change-Number: 16467
Gerrit-PatchSet: 2
Gerrit-Owner: N A <mail at interlinked.x10host.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210910/9397eff6/attachment-0001.html>


More information about the asterisk-code-review mailing list