@@ -603,16 +603,6 @@ static void RemoveChar(char *p) | |||
memset(p, ' ', utf8_in(&c, p)); | |||
} | |||
static MNEM_TAB xml_char_mnemonics[] = { | |||
{ "gt", '>' }, | |||
{ "lt", 0xe000 + '<' }, // private usage area, to avoid confusion with XML tag | |||
{ "amp", '&' }, | |||
{ "quot", '"' }, | |||
{ "nbsp", ' ' }, | |||
{ "apos", '\'' }, | |||
{ NULL, -1 } | |||
}; | |||
int ReadClause(Translator *tr, char *buf, short *charix, int *charix_top, int n_buf, int *tone_type, char *voice_change) | |||
{ | |||
/* Find the end of the current clause. | |||
@@ -641,9 +631,7 @@ int ReadClause(Translator *tr, char *buf, short *charix, int *charix_top, int n_ | |||
int phoneme_mode = 0; | |||
int n_xml_buf; | |||
int terminator; | |||
int found; | |||
bool any_alnum = false; | |||
bool self_closing; | |||
int punct_data = 0; | |||
bool is_end_clause; | |||
int announced_punctuation = 0; | |||
@@ -728,22 +716,10 @@ int ReadClause(Translator *tr, char *buf, short *charix, int *charix_top, int n_ | |||
c2 = GetC(); | |||
sprintf(ungot_string, "%s%c%c", &xml_buf2[0], c1, c2); | |||
int found = -1; | |||
if (c1 == ';') { | |||
if (xml_buf2[0] == '#') { | |||
// character code number | |||
if (xml_buf2[1] == 'x') | |||
found = sscanf(&xml_buf2[2], "%x", (unsigned int *)(&c1)); | |||
else | |||
found = sscanf(&xml_buf2[1], "%d", &c1); | |||
} else { | |||
if ((found = LookupMnem(xml_char_mnemonics, xml_buf2)) != -1) { | |||
c1 = found; | |||
if (c2 == 0) | |||
c2 = ' '; | |||
} | |||
} | |||
} else | |||
found = -1; | |||
found = ParseSsmlReference(xml_buf2, &c1, &c2); | |||
} | |||
if (found <= 0) { | |||
ungot_string_ix = 0; | |||
@@ -754,12 +730,7 @@ int ReadClause(Translator *tr, char *buf, short *charix, int *charix_top, int n_ | |||
if ((c1 <= 0x20) && ((sayas_mode == SAYAS_SINGLE_CHARS) || (sayas_mode == SAYAS_KEY))) | |||
c1 += 0xe000; // move into unicode private usage area | |||
} else if ((c1 == '<') && (ssml_ignore_l_angle != '<')) { | |||
if ((c2 == '!') || (c2 == '?')) { | |||
// a comment, ignore until closing '<' (or <?xml tag ) | |||
while (!Eof() && (c1 != '>')) | |||
c1 = GetC(); | |||
c2 = ' '; | |||
} else if ((c2 == '/') || iswalpha(c2)) { | |||
if ((c2 == '/') || iswalpha(c2) || c2 == '!' || c2 == '?') { | |||
// check for space in the output buffer for embedded commands produced by the SSML tag | |||
if (ix > (n_buf - 20)) { | |||
// Perhaps not enough room, end the clause before the SSML tag | |||
@@ -780,14 +751,7 @@ int ReadClause(Translator *tr, char *buf, short *charix, int *charix_top, int n_ | |||
xml_buf[n_xml_buf] = 0; | |||
c2 = ' '; | |||
self_closing = false; | |||
if (xml_buf[n_xml_buf-1] == '/') { | |||
// a self-closing tag | |||
xml_buf[n_xml_buf-1] = ' '; | |||
self_closing = true; | |||
} | |||
terminator = ProcessSsmlTag(xml_buf, buf, &ix, n_buf, self_closing, xmlbase, &audio_text, current_voice_id, &base_voice, base_voice_variant_name, &ignore_text, &clear_skipping_text, &sayas_mode, &sayas_start, ssml_stack, &n_ssml_stack, &n_param_stack, (int *)speech_parameters); | |||
terminator = ProcessSsmlTag(xml_buf, buf, &ix, n_buf, xmlbase, &audio_text, current_voice_id, &base_voice, base_voice_variant_name, &ignore_text, &clear_skipping_text, &sayas_mode, &sayas_start, ssml_stack, &n_ssml_stack, &n_param_stack, (int *)speech_parameters); | |||
if (terminator != 0) { | |||
buf[ix] = ' '; |
@@ -549,7 +549,7 @@ static void SetProsodyParameter(int param_type, wchar_t *attr1, PARAM_STACK *sp, | |||
} | |||
} | |||
int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, bool self_closing, const char *xmlbase, bool *audio_text, char *current_voice_id, espeak_VOICE *base_voice, char *base_voice_variant_name, bool *ignore_text, bool *clear_skipping_text, int *sayas_mode, int *sayas_start, SSML_STACK *ssml_stack, int *n_ssml_stack, int *n_param_stack, int *speech_parameters) | |||
int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, const char *xmlbase, bool *audio_text, char *current_voice_id, espeak_VOICE *base_voice, char *base_voice_variant_name, bool *ignore_text, bool *clear_skipping_text, int *sayas_mode, int *sayas_start, SSML_STACK *ssml_stack, int *n_ssml_stack, int *n_param_stack, int *speech_parameters) | |||
{ | |||
// xml_buf is the tag and attributes with a zero terminator in place of the original '>' | |||
// returns a clause terminator value. | |||
@@ -574,9 +574,23 @@ int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, boo | |||
PARAM_STACK *sp; | |||
SSML_STACK *ssml_sp; | |||
// don't process comments and xml declarations | |||
if (wcsncmp(xml_buf, (wchar_t *) "!--", 3) == 0 || wcsncmp(xml_buf, (wchar_t *) "?xml", 4) == 0) { | |||
return 0; | |||
} | |||
// these tags have no effect if they are self-closing, eg. <voice /> | |||
static char ignore_if_self_closing[] = { 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0 }; | |||
bool self_closing = false; | |||
int len; | |||
len = wcslen(xml_buf); | |||
if (xml_buf[len - 1] == '/') { | |||
// a self-closing tag | |||
xml_buf[len - 1] = ' '; | |||
self_closing = true; | |||
} | |||
static const MNEM_TAB mnem_phoneme_alphabet[] = { | |||
{ "espeak", 1 }, | |||
{ NULL, -1 } | |||
@@ -935,3 +949,38 @@ int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, boo | |||
} | |||
return 0; | |||
} | |||
static MNEM_TAB xml_entity_mnemonics[] = { | |||
{ "gt", '>' }, | |||
{ "lt", 0xe000 + '<' }, // private usage area, to avoid confusion with XML tag | |||
{ "amp", '&' }, | |||
{ "quot", '"' }, | |||
{ "nbsp", ' ' }, | |||
{ "apos", '\'' }, | |||
{ NULL, -1 } | |||
}; | |||
int ParseSsmlReference(char *ref, int *c1, int *c2) { | |||
// Check if buffer *ref contains an XML character or entity reference | |||
// if found, set *c1 to the replacement char | |||
// change *c2 for entity references | |||
// returns >= 0 on success | |||
if (ref[0] == '#') { | |||
// character reference | |||
if (ref[1] == 'x') | |||
return sscanf(&ref[2], "%x", c1); | |||
else | |||
return sscanf(&ref[1], "%d", c1); | |||
} else { | |||
// entity reference | |||
int found; | |||
if ((found = LookupMnem(xml_entity_mnemonics, ref)) != -1) { | |||
*c1 = found; | |||
if (*c2 == 0) | |||
*c2 = ' '; | |||
return found; | |||
} | |||
} | |||
return -1; | |||
} |
@@ -66,7 +66,6 @@ int ProcessSsmlTag(wchar_t *xml_buf, | |||
char *outbuf, | |||
int *outix, | |||
int n_outbuf, | |||
bool self_closing, | |||
const char *xmlbase, | |||
bool *audio_text, | |||
char *current_voice_id, | |||
@@ -81,6 +80,10 @@ int ProcessSsmlTag(wchar_t *xml_buf, | |||
int *n_param_stack, | |||
int *speech_parameters); | |||
int ParseSsmlReference(char *ref, | |||
int *c1, | |||
int *c2); | |||
#ifdef __cplusplus | |||
} | |||
#endif |
@@ -458,7 +458,6 @@ extern short echo_buf[N_ECHO_BUF]; | |||
void SynthesizeInit(void); | |||
int Generate(PHONEME_LIST *phoneme_list, int *n_ph, bool resume); | |||
void MakeWave2(PHONEME_LIST *p, int n_ph); | |||
int SpeakNextClause(int control); | |||
void SetSpeed(int control); | |||
void SetEmbedded(int control, int value); |
@@ -261,8 +261,6 @@ extern "C" | |||
// match 1 pre 2 post 0 - use common phoneme string | |||
// match 1 pre 2 post 3 0 - empty phoneme string | |||
typedef const char *constcharptr; | |||
// used to mark words with the source[] buffer | |||
typedef struct { | |||
unsigned int flags; |
@@ -2,12 +2,21 @@ | |||
test_ssml() { | |||
INPUT=$1 | |||
if [ "$2" = "punct" ] | |||
then | |||
PARAMETERS="--punct -x" | |||
else | |||
PARAMETERS="-v en-US --ipa=2" | |||
fi | |||
echo "testing ${INPUT}" | |||
cp $(dirname $INPUT)/$(basename ${INPUT%.*}).expected expected.txt | |||
ESPEAK_DATA_PATH=`pwd` LD_LIBRARY_PATH=src:${LD_LIBRARY_PATH} \ | |||
src/espeak-ng -m -q -v en-US --ipa=2 -f ${INPUT} > actual.txt | |||
src/espeak-ng -m -q $PARAMETERS -f ${INPUT} > actual.txt | |||
diff expected.txt actual.txt || exit 1 | |||
} | |||
for i in `ls tests/ssml/*.ssml` ; do test_ssml $i ; done | |||
for i in `ls tests/ssml/*.ssml` ; do test_ssml $i; done | |||
for i in `ls tests/ssml/*.ssml2` ; do test_ssml $i punct; done | |||
@@ -0,0 +1,4 @@ | |||
l'EsDan_: gr'eIt@D,an_: 'amp@s,and t'Ik_: kw'oUts | |||
b'i: b'i: | |||
z'Ed z'Ed | |||
@@ -0,0 +1,9 @@ | |||
<!-- SSML reference test | |||
Entity references < > & ' and " should be replaced in the buffer | |||
Character references like A are translated to Unicode (65 = 'A') | |||
See https://www.tutorialspoint.com/xml/xml_syntax.htm for example | |||
--> | |||
<speak>< > & ' "</speak> | |||
<speak>B B</speak> | |||
<speak>z z</speak> |