From f266faaff78b9f71a739ffead91c09ec5f8afc07 Mon Sep 17 00:00:00 2001 From: donovanmalisch23-alt <228235407+donovanmalisch23-alt@users.noreply.github.com> Date: Mon, 3 Aug 2026 11:04:45 +0000 Subject: [PATCH] espeak: escape XML-special characters in single-character mode When speakup echoes a single character, espeakup wraps it in SSML: %c If the character is one of XML's five special characters (< > & ' "), the raw byte is injected straight into the markup, producing ill-formed SSML. espeak-ng then misparses the element, which surfaces as a spurious high-pitched "ringing" whenever one of these characters is spoken. Escape each special character to its corresponding XML entity (< > & ' ") so the markup stays well-formed. Non-printable characters (< 0x20 or > 0x7e) are no longer wrapped in SSML either; they fall through to the existing raw-synthesis fallback so they cannot corrupt the surrounding element either. The space and ordinary-printable-character paths are unchanged. --- src/espeak.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/espeak.c b/src/espeak.c index 60cc996..c9ac908 100644 --- a/src/espeak.c +++ b/src/espeak.c @@ -302,15 +302,39 @@ static espeak_ERROR speak_text(struct synth_t *s) synth_mode |= espeakSSML; if (espeakup_mode == ESPEAKUP_MODE_SPEAKUP && (s->len == 1)) { - char *buf; + char *buf = NULL; int n; - if (s->buf[0] == ' ') + unsigned char c = s->buf[0]; + if (c == ' ') n = asprintf(&buf, " "); - else - n = asprintf(&buf, - "%c", - s->buf[0]); + else if (c < 0x20 || c > 0x7e) { + /* Not a printable character; do not embed it in SSML, as + * that would produce invalid markup. Fall through to the + * raw-synthesis path below. */ + n = -1; + } else { + /* Escape characters that are special in XML/SSML so the + * resulting markup stays well-formed; otherwise espeak-ng + * misparses the element, which is the source of + * the spurious high-pitched "ringing" on these characters. */ + const char *entity = NULL; + switch (c) { + case '<': entity = "<"; break; + case '>': entity = ">"; break; + case '&': entity = "&"; break; + case '\'': entity = "'"; break; + case '"': entity = """; break; + } + if (entity) + n = asprintf(&buf, + "%s", + entity); + else + n = asprintf(&buf, + "%c", + c); + } if (n == -1) { /* D'oh. Not much to do on allocation failure. * Perhaps espeak will happen to say the character */