| @@ -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> | |||