The XML parser is based on the code in ntdll/actctx.c.
Used by Diablo IV's screen reader.
From: Shaun Ren sren@codeweavers.com
Based on the XML parser in ntdll/actctx.c. --- dlls/sapi/xml.c | 420 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 418 insertions(+), 2 deletions(-)
diff --git a/dlls/sapi/xml.c b/dlls/sapi/xml.c index f61864c3254..442537fef4c 100644 --- a/dlls/sapi/xml.c +++ b/dlls/sapi/xml.c @@ -3,6 +3,13 @@ * * Copyright 2025 Shaun Ren for CodeWeavers * + * Based on ntdll/actctx.c + * Copyright 2004 Jon Griffiths + * Copyright 2007 Eric Pouech + * Copyright 2007 Jacek Caban for CodeWeavers + * Copyright 2007 Alexandre Julliard + * Copyright 2013 Nikolay Sivov + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -18,11 +25,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <assert.h> + #define COBJMACROS
#include "objbase.h"
#include "sapiddk.h" +#include "sperror.h"
#include "wine/debug.h"
@@ -30,9 +40,415 @@
WINE_DEFAULT_DEBUG_CHANNEL(sapi);
+ +#define MAX_NAMESPACES 64 + +typedef struct +{ + const WCHAR *ptr; + unsigned int len; +} xmlstr_t; + +struct xml_elem +{ + xmlstr_t name; + xmlstr_t ns; + int ns_pos; +}; + +struct xml_attr +{ + xmlstr_t name; + xmlstr_t value; +}; + +struct xml_parser +{ + const WCHAR *ptr; + const WCHAR *end; + struct xml_attr namespaces[MAX_NAMESPACES]; + int ns_pos; + BOOL error; +}; + +static const xmlstr_t empty_xmlstr; + +static const WCHAR ssml_ns[] = L"http://www.w3.org/2001/10/synthesis"; + +static inline const char *debugstr_xmlstr(const xmlstr_t *str) +{ + return debugstr_wn(str->ptr, str->len); +} + +static inline BOOL xmlstr_eq(const xmlstr_t* xmlstr, const WCHAR *str) +{ + return !wcsncmp(xmlstr->ptr, str, xmlstr->len) && !str[xmlstr->len]; +} + +static inline BOOL xml_elem_eq(const struct xml_elem *elem, const WCHAR *ns, const WCHAR *name) +{ + if (!xmlstr_eq(&elem->name, name)) return FALSE; + return xmlstr_eq(&elem->ns, ns); +} + +static BOOL xml_name_eq(const struct xml_elem *elem1, const struct xml_elem *elem2) +{ + return (elem1->name.len == elem2->name.len && + elem1->ns.len == elem2->ns.len && + !wcsncmp(elem1->name.ptr, elem2->name.ptr, elem1->name.len) && + !wcsncmp(elem1->ns.ptr, elem2->ns.ptr, elem1->ns.len)); +} + +static BOOL set_error(struct xml_parser *parser) +{ + parser->error = TRUE; + return FALSE; +} + +static BOOL is_xmlns_attr(const struct xml_attr *attr) +{ + const int len = wcslen(L"xmlns"); + if (attr->name.len < len) return FALSE; + if (wcsncmp(attr->name.ptr, L"xmlns", len)) return FALSE; + return (attr->name.len == len || attr->name.ptr[len] == ':'); +} + +static void push_xmlns(struct xml_parser *parser, const struct xml_attr *attr) +{ + const int len = wcslen(L"xmlns"); + struct xml_attr *ns; + + if (parser->ns_pos == MAX_NAMESPACES - 1) + { + FIXME("Too many namespaces.\n"); + set_error(parser); + return; + } + ns = &parser->namespaces[parser->ns_pos++]; + ns->value = attr->value; + if (attr->name.len > len) + { + ns->name.ptr = attr->name.ptr + len + 1; + ns->name.len = attr->name.len - len - 1; + } + else ns->name = empty_xmlstr; +} + +static xmlstr_t find_xmlns(struct xml_parser *parser, const xmlstr_t *name) +{ + int i; + + for (i = parser->ns_pos - 1; i >= 0; i--) + { + if (parser->namespaces[i].name.len == name->len && + !wcsncmp(parser->namespaces[i].name.ptr, name->ptr, name->len)) + return parser->namespaces[i].value; + } + if (parser->ns_pos) WARN("Namespace %s not found.\n", debugstr_xmlstr(name)); + return empty_xmlstr; +} + +static void skip_xml_spaces(struct xml_parser *parser) +{ + while (parser->ptr < parser->end && isxmlspace(*parser->ptr)) + parser->ptr++; +} + +static BOOL next_xml_attr(struct xml_parser *parser, struct xml_attr *attr, BOOL *end) +{ + const WCHAR *ptr; + WCHAR quote; + + *end = FALSE; + + if (parser->error) return FALSE; + + skip_xml_spaces(parser); + + if (parser->ptr == parser->end) + return set_error(parser); + + if (*parser->ptr == '/') + { + parser->ptr++; + if (parser->ptr == parser->end || *parser->ptr != '>') + return set_error(parser); + + parser->ptr++; + *end = TRUE; + return FALSE; + } + + if (*parser->ptr == '>') + { + parser->ptr++; + return FALSE; + } + + ptr = parser->ptr; + while (ptr < parser->end && *ptr != '=' && *ptr != '>' && !isxmlspace(*ptr)) ptr++; + + if (ptr == parser->end) + return set_error(parser); + + attr->name.ptr = parser->ptr; + attr->name.len = ptr-parser->ptr; + parser->ptr = ptr; + + /* skip spaces before '=' */ + while (ptr < parser->end && *ptr != '=' && isxmlspace(*ptr)) ptr++; + if (ptr == parser->end || *ptr != '=') + return set_error(parser); + + /* skip '=' itself */ + ptr++; + if (ptr == parser->end) + return set_error(parser); + + /* skip spaces after '=' */ + while (ptr < parser->end && *ptr != '"' && *ptr != ''' && isxmlspace(*ptr)) ptr++; + + if (ptr == parser->end || (*ptr != '"' && *ptr != ''')) return set_error(parser); + + quote = *ptr++; + attr->value.ptr = ptr; + if (ptr == parser->end) + return set_error(parser); + + while (ptr < parser->end && *ptr != quote) ptr++; + if (ptr == parser->end) + { + parser->ptr = parser->end; + return set_error(parser); + } + + attr->value.len = ptr - attr->value.ptr; + parser->ptr = ptr + 1; + if (parser->ptr != parser->end) return TRUE; + + return set_error(parser); +} + +static void read_xml_elem(struct xml_parser *parser, struct xml_elem *elem) +{ + const WCHAR *ptr = parser->ptr; + + elem->ns = empty_xmlstr; + elem->name.ptr = ptr; + while (ptr < parser->end && !isxmlspace(*ptr) && *ptr != '>' && *ptr != '/') + { + if (*ptr == ':') + { + elem->ns.ptr = elem->name.ptr; + elem->ns.len = ptr - elem->ns.ptr; + elem->name.ptr = ptr + 1; + } + ptr++; + } + elem->name.len = ptr - elem->name.ptr; + parser->ptr = ptr; +} + +static inline BOOL is_special_xml_markup(const struct xml_elem *elem) +{ + return *elem->name.ptr == '!' || *elem->name.ptr == '?'; +} + +static BOOL skip_special_xml_markup(struct xml_parser *parser, struct xml_elem *elem) +{ + const WCHAR *ptr; + + if (parser->error) return FALSE; + + if (elem->name.len > 1 && elem->name.ptr[0] == '!' && elem->name.ptr[1] == '[') + { + /* <![...]]> */ + for (ptr = parser->ptr; ptr < parser->end - 3; ptr++) + { + if (ptr[0] == ']' && ptr[1] == ']' && ptr[2] == '>') + { + parser->ptr = ptr + 3; + return TRUE; + } + } + } + else if (xmlstr_eq(&elem->name, L"!--")) + { + /* <!--...--> */ + for (ptr = parser->ptr; ptr < parser->end - 2; ptr++) + { + if (ptr[0] == '-' && ptr[1] == '-' && ptr[2] == '>') + { + parser->ptr = ptr + 3; + return TRUE; + } + } + } + else if (*elem->name.ptr == '!') + { + /* <!...> */ + for (ptr = parser->ptr; ptr < parser->end; ptr++) + { + if (*ptr == '>') + { + parser->ptr = ptr + 1; + return TRUE; + } + } + } + else if (*elem->name.ptr == '?') + { + /* <?...?> */ + for (ptr = parser->ptr; ptr < parser->end - 1; ptr++) + { + if (ptr[0] == '?' && ptr[1] == '>') + { + parser->ptr = ptr + 2; + return TRUE; + } + } + } + + return FALSE; +} + +static BOOL next_xml_elem(struct xml_parser *parser, struct xml_elem *elem, const struct xml_elem *parent) +{ + const WCHAR *ptr; + struct xml_attr attr; + BOOL end = FALSE; + + parser->ns_pos = parent->ns_pos; /* restore namespace stack to parent state */ + + if (parser->error) return FALSE; + + skip_xml_spaces(parser); + + if (parser->ptr == parser->end || *parser->ptr != '<') + return set_error(parser); + parser->ptr++; + + /* check for element terminating the parent element */ + if (parser->ptr < parser->end && *parser->ptr == '/') + { + parser->ptr++; + read_xml_elem(parser, elem); + elem->ns = find_xmlns(parser, &elem->ns); + if (!xml_name_eq(elem, parent)) + { + ERR("Wrong closing element %s for %s.\n", + debugstr_xmlstr(&elem->name), debugstr_xmlstr(&parent->name)); + return set_error(parser); + } + skip_xml_spaces(parser); + if (parser->ptr == parser->end || *parser->ptr++ != '>') + return set_error(parser); + return FALSE; + } + + read_xml_elem(parser, elem); + if (!elem->name.len) + return set_error(parser); + + if (!is_special_xml_markup(elem)) + { + /* parse namespace attributes */ + ptr = parser->ptr; + while (next_xml_attr(parser, &attr, &end)) + { + if (is_xmlns_attr(&attr)) push_xmlns(parser, &attr); + } + parser->ptr = ptr; + elem->ns = find_xmlns(parser, &elem->ns); + elem->ns_pos = parser->ns_pos; + } + + if (parser->ptr != parser->end) return TRUE; + else return set_error(parser); +} + +static HRESULT parse_ssml_speak_elem(struct xml_parser *parser, SPVSTATE *state) +{ + return E_NOTIMPL; +} + +static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVSTATE *state, SPVTEXTFRAG **frag_list) +{ + struct xml_parser parser = {0}; + struct xml_elem parent = {0}; + struct xml_elem elem; + HRESULT hr; + + parser.ptr = contents; + parser.end = end; + + /* Default SSML namespace. */ + parser.namespaces[0].name = empty_xmlstr; + parser.namespaces[0].value.ptr = ssml_ns; + parser.namespaces[0].value.len = wcslen(ssml_ns); + parser.ns_pos = 1; + + parent.ns = parser.namespaces[0].value; + parent.ns_pos = 1; + + for (;;) + { + if (!next_xml_elem(&parser, &elem, &parent)) + return SPERR_UNSUPPORTED_FORMAT; + + if (!is_special_xml_markup(&elem)) + break; + if (!skip_special_xml_markup(&parser, &elem)) + return SPERR_UNSUPPORTED_FORMAT; + } + + if (!xml_elem_eq(&elem, ssml_ns, L"speak")) + return SPERR_UNSUPPORTED_FORMAT; + if (FAILED(hr = parse_ssml_speak_elem(&parser, state))) + return hr; + + if (next_xml_elem(&parser, &elem, &parent)) + { + ERR("Unexpected element %s after <speak>.\n", debugstr_xmlstr(&elem.name)); + return SPERR_UNSUPPORTED_FORMAT; + } + + if (parser.error) + return SPERR_UNSUPPORTED_FORMAT; + + return S_OK; +} + HRESULT parse_sapi_xml(const WCHAR *contents, DWORD parse_flag, BOOL persist, SPVSTATE *global_state, SPVTEXTFRAG **frag_list) { - FIXME("(%p, %#lx, %d, %p, %p): stub.\n", contents, parse_flag, persist, global_state, frag_list); - return E_NOTIMPL; + SPVSTATE state = *global_state; + const WCHAR *end; + HRESULT hr; + + TRACE("(%p, %#lx, %d, %p, %p).\n", contents, parse_flag, persist, global_state, frag_list); + + if (parse_flag == SPF_PARSE_SAPI) + { + FIXME("SAPI XML parsing is not implemented.\n"); + return E_NOTIMPL; + } + + assert(!state.pPhoneIds); + assert(!state.Context.pCategory && !state.Context.pBefore && !state.Context.pAfter); + + end = contents + wcslen(contents); + + if (FAILED(hr = parse_ssml_contents(contents, end, &state, frag_list))) + return hr; + + if (persist) + { + assert(!state.pPhoneIds); + assert(!state.Context.pCategory && !state.Context.pBefore && !state.Context.pAfter); + + *global_state = state; + } + + return S_OK; }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 6 +- dlls/sapi/xml.c | 135 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 8 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index 49d312fed70..f355ff7c98d 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -998,13 +998,13 @@ static void test_spvoice_ssml(void) reset_engine_params(&test_engine);
hr = ISpVoice_Speak(voice, bad_text1, SPF_IS_XML | SPF_PARSE_SSML, NULL); - todo_wine ok(hr == SPERR_UNSUPPORTED_FORMAT, "got %#lx.\n", hr); + ok(hr == SPERR_UNSUPPORTED_FORMAT, "got %#lx.\n", hr);
hr = ISpVoice_Speak(voice, bad_text2, SPF_IS_XML | SPF_PARSE_SSML, NULL); - todo_wine ok(hr == SPERR_UNSUPPORTED_FORMAT, "got %#lx.\n", hr); + ok(hr == SPERR_UNSUPPORTED_FORMAT, "got %#lx.\n", hr);
hr = ISpVoice_Speak(voice, bad_text3, SPF_IS_XML | SPF_PARSE_SSML, NULL); - todo_wine ok(hr == SPERR_UNSUPPORTED_FORMAT || broken(hr == S_OK) /* win7 */, "got %#lx.\n", hr); + ok(hr == SPERR_UNSUPPORTED_FORMAT || broken(hr == S_OK) /* win7 */, "got %#lx.\n", hr);
reset_engine_params(&test_engine);
diff --git a/dlls/sapi/xml.c b/dlls/sapi/xml.c index 442537fef4c..7ed7505f355 100644 --- a/dlls/sapi/xml.c +++ b/dlls/sapi/xml.c @@ -80,11 +80,32 @@ static inline const char *debugstr_xmlstr(const xmlstr_t *str) return debugstr_wn(str->ptr, str->len); }
+static WCHAR *xmlstrcpyW(WCHAR *dst, const xmlstr_t* src) +{ + memcpy(dst, src->ptr, src->len * sizeof(WCHAR)); + dst[src->len] = 0; + return dst; +} + +static WCHAR *xmlstrdupW(const xmlstr_t* str) +{ + WCHAR *strW; + + if (!(strW = malloc((str->len + 1) * sizeof(WCHAR)))) + return NULL; + return xmlstrcpyW(strW, str); +} + static inline BOOL xmlstr_eq(const xmlstr_t* xmlstr, const WCHAR *str) { return !wcsncmp(xmlstr->ptr, str, xmlstr->len) && !str[xmlstr->len]; }
+static inline BOOL xml_attr_eq(const struct xml_attr *attr, const WCHAR *name) +{ + return xmlstr_eq(&attr->name, name); +} + static inline BOOL xml_elem_eq(const struct xml_elem *elem, const WCHAR *ns, const WCHAR *name) { if (!xmlstr_eq(&elem->name, name)) return FALSE; @@ -367,11 +388,118 @@ static BOOL next_xml_elem(struct xml_parser *parser, struct xml_elem *elem, cons else return set_error(parser); }
-static HRESULT parse_ssml_speak_elem(struct xml_parser *parser, SPVSTATE *state) +static BOOL next_text_or_xml_elem(struct xml_parser *parser, BOOL *is_text, struct xml_elem *elem, + const struct xml_elem *parent) { + if (parser->error) return FALSE; + + while (parser->ptr < parser->end) + { + if (*parser->ptr == '<') + { + *is_text = FALSE; + + if (!next_xml_elem(parser, elem, parent)) + return FALSE; + + if (!is_special_xml_markup(elem)) + return TRUE; + else if (!skip_special_xml_markup(parser, elem)) + return set_error(parser); + } + else + { + *is_text = TRUE; + return TRUE; + } + } + return FALSE; +} + +static HRESULT add_sapi_text_fragment(struct xml_parser *parser, const SPVSTATE *state) +{ + FIXME("stub.\n"); return E_NOTIMPL; }
+static HRESULT parse_ssml_elems(struct xml_parser *parser, const SPVSTATE *state, const struct xml_elem *parent) +{ + struct xml_elem elem; + BOOL is_text; + HRESULT hr = S_OK; + + while (SUCCEEDED(hr) && next_text_or_xml_elem(parser, &is_text, &elem, parent)) + { + if (is_text) + { + hr = add_sapi_text_fragment(parser, state); + } + else + { + FIXME("Unknown element %s.\n", debugstr_xmlstr(&elem.name)); + hr = E_NOTIMPL; + } + } + + if (SUCCEEDED(hr) && parser->error) + return SPERR_UNSUPPORTED_FORMAT; + return hr; +} + +static HRESULT parse_ssml_speak_elem(struct xml_parser *parser, const struct xml_elem *parent, SPVSTATE *state) +{ + struct xml_attr attr; + BOOL end = FALSE; + BOOL has_version = FALSE; + LCID lcid = 0; + + while (next_xml_attr(parser, &attr, &end)) + { + if (xml_attr_eq(&attr, L"version")) + { + if (!xmlstr_eq(&attr.value, L"1.0")) + { + ERR("Invalid SSML version %s.\n", debugstr_xmlstr(&attr.value)); + return SPERR_UNSUPPORTED_FORMAT; + } + + has_version = TRUE; + } + else if (xml_attr_eq(&attr, L"xml:lang")) + { + WCHAR *lang = xmlstrdupW(&attr.value); + + if (!lang) return E_OUTOFMEMORY; + lcid = LocaleNameToLCID(lang, 0); + free(lang); + + if (!lcid) + { + ERR("Invalid xml:lang %s.\n", debugstr_xmlstr(&attr.value)); + return SPERR_UNSUPPORTED_FORMAT; + } + } + } + + if (!has_version) + { + ERR("Missing version attribute.\n"); + return SPERR_UNSUPPORTED_FORMAT; + } + if (!lcid) + { + ERR("Missing xml:lang attribute.\n"); + return SPERR_UNSUPPORTED_FORMAT; + } + + if (end) return S_OK; + + state->eAction = SPVA_Speak; + state->LangID = LANGIDFROMLCID(lcid); + + return parse_ssml_elems(parser, state, parent); +} + static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVSTATE *state, SPVTEXTFRAG **frag_list) { struct xml_parser parser = {0}; @@ -404,7 +532,7 @@ static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVS
if (!xml_elem_eq(&elem, ssml_ns, L"speak")) return SPERR_UNSUPPORTED_FORMAT; - if (FAILED(hr = parse_ssml_speak_elem(&parser, state))) + if (FAILED(hr = parse_ssml_speak_elem(&parser, &elem, state))) return hr;
if (next_xml_elem(&parser, &elem, &parent)) @@ -413,9 +541,6 @@ static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVS return SPERR_UNSUPPORTED_FORMAT; }
- if (parser.error) - return SPERR_UNSUPPORTED_FORMAT; - return S_OK; }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 48 +++++++++++++++++++------------------------ dlls/sapi/xml.c | 44 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 30 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index f355ff7c98d..dbb8736f76e 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -958,42 +958,36 @@ static void test_spvoice_ssml(void) reset_engine_params(&test_engine);
hr = ISpVoice_Speak(voice, text1, SPF_IS_XML | SPF_PARSE_SSML, NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); - - if (test_engine.frag_count == 1) { - check_frag_text(0, L"text1"); - - check_frag_state_field(0, eAction, SPVA_Speak, "%d"); - ok(test_engine.frags[0].State.LangID == 0x409 || broken(test_engine.frags[0].State.LangID == 0) /* win7 */, - "got %#hx.\n", test_engine.frags[0].State.LangID); - check_frag_state_field(0, EmphAdj, 0, "%ld"); - check_frag_state_field(0, RateAdj, 0, "%ld"); - check_frag_state_field(0, Volume, 100, "%lu"); - check_frag_state_field(0, PitchAdj.MiddleAdj, 0, "%ld"); - check_frag_state_field(0, PitchAdj.RangeAdj, 0, "%ld"); - check_frag_state_field(0, SilenceMSecs, 0, "%lu"); - check_frag_state_field(0, ePartOfSpeech, SPPS_Unknown, "%#x"); - } + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); + check_frag_text(0, L"text1"); + + check_frag_state_field(0, eAction, SPVA_Speak, "%d"); + ok(test_engine.frags[0].State.LangID == 0x409 || broken(test_engine.frags[0].State.LangID == 0) /* win7 */, + "got %#hx.\n", test_engine.frags[0].State.LangID); + check_frag_state_field(0, EmphAdj, 0, "%ld"); + check_frag_state_field(0, RateAdj, 0, "%ld"); + check_frag_state_field(0, Volume, 100, "%lu"); + check_frag_state_field(0, PitchAdj.MiddleAdj, 0, "%ld"); + check_frag_state_field(0, PitchAdj.RangeAdj, 0, "%ld"); + check_frag_state_field(0, SilenceMSecs, 0, "%lu"); + check_frag_state_field(0, ePartOfSpeech, SPPS_Unknown, "%#x");
reset_engine_params(&test_engine);
/* SSML autodetection when SPF_PARSE_SSML is not specified. */ hr = ISpVoice_Speak(voice, text1, SPF_IS_XML, NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); - - if (test_engine.frag_count == 1) - check_frag_text(0, L"text1"); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); + check_frag_text(0, L"text1");
reset_engine_params(&test_engine);
/* XML and SSML autodetection when SPF_IS_XML is not specified. */ hr = ISpVoice_Speak(voice, text1, SPF_DEFAULT, NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); - if (test_engine.frag_count == 1) - check_frag_text(0, L"text1"); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); + check_frag_text(0, L"text1");
reset_engine_params(&test_engine);
@@ -1009,7 +1003,7 @@ static void test_spvoice_ssml(void) reset_engine_params(&test_engine);
hr = ISpVoice_Speak(voice, text2, SPF_IS_XML | SPF_PARSE_SSML, NULL); - todo_wine ok(hr == S_OK || broken(hr == SPERR_UNSUPPORTED_FORMAT) /* win7 */, "got %#lx.\n", hr); + ok(hr == S_OK || broken(hr == SPERR_UNSUPPORTED_FORMAT) /* win7 */, "got %#lx.\n", hr);
if (hr == S_OK) { ok(test_engine.frag_count == 1, "got %Iu.\n", test_engine.frag_count); diff --git a/dlls/sapi/xml.c b/dlls/sapi/xml.c index 7ed7505f355..518a687a5e8 100644 --- a/dlls/sapi/xml.c +++ b/dlls/sapi/xml.c @@ -64,11 +64,14 @@ struct xml_attr
struct xml_parser { + const WCHAR *base; const WCHAR *ptr; const WCHAR *end; struct xml_attr namespaces[MAX_NAMESPACES]; int ns_pos; BOOL error; + + SPVTEXTFRAG *tail_frag; };
static const xmlstr_t empty_xmlstr; @@ -418,8 +421,40 @@ static BOOL next_text_or_xml_elem(struct xml_parser *parser, BOOL *is_text, stru
static HRESULT add_sapi_text_fragment(struct xml_parser *parser, const SPVSTATE *state) { - FIXME("stub.\n"); - return E_NOTIMPL; + const WCHAR *text, *ptr; + SPVTEXTFRAG *frag; + size_t len; + WCHAR *buf; + + if (parser->error) + return SPERR_UNSUPPORTED_FORMAT; + + text = parser->ptr; + + for (ptr = parser->ptr; ptr < parser->end; ptr++) if (*ptr == '<') break; + len = ptr - parser->ptr; + parser->ptr = ptr; + + if (!len) + return S_OK; + + if (!(frag = malloc(sizeof(*frag) + (len + 1) * sizeof(WCHAR)))) + return E_OUTOFMEMORY; + + buf = (WCHAR *)(frag + 1); + memcpy(buf, text, len * sizeof(WCHAR)); + buf[len] = 0; + + frag->pNext = NULL; + frag->State = *state; + frag->pTextStart = buf; + frag->ulTextLen = len; + frag->ulTextSrcOffset = text - parser->base; + + parser->tail_frag->pNext = frag; + parser->tail_frag = frag; + + return S_OK; }
static HRESULT parse_ssml_elems(struct xml_parser *parser, const SPVSTATE *state, const struct xml_elem *parent) @@ -504,11 +539,13 @@ static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVS { struct xml_parser parser = {0}; struct xml_elem parent = {0}; + SPVTEXTFRAG head_frag = {0}; struct xml_elem elem; HRESULT hr;
- parser.ptr = contents; + parser.base = parser.ptr = contents; parser.end = end; + parser.tail_frag = &head_frag;
/* Default SSML namespace. */ parser.namespaces[0].name = empty_xmlstr; @@ -541,6 +578,7 @@ static HRESULT parse_ssml_contents(const WCHAR *contents, const WCHAR *end, SPVS return SPERR_UNSUPPORTED_FORMAT; }
+ *frag_list = head_frag.pNext; return S_OK; }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/tests/tts.c | 4 ++-- dlls/sapi/xml.c | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/dlls/sapi/tests/tts.c b/dlls/sapi/tests/tts.c index dbb8736f76e..569ee918164 100644 --- a/dlls/sapi/tests/tts.c +++ b/dlls/sapi/tests/tts.c @@ -1015,8 +1015,8 @@ static void test_spvoice_ssml(void) reset_engine_params(&test_engine);
hr = ISpVoice_Speak(voice, text3, SPF_IS_XML, NULL); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - todo_wine ok(test_engine.frag_count == 7 || broken(test_engine.frag_count == 1) /* win7 */, + ok(hr == S_OK, "got %#lx.\n", hr); + ok(test_engine.frag_count == 7 || broken(test_engine.frag_count == 1) /* win7 */, "got %Iu.\n", test_engine.frag_count);
if (test_engine.frag_count == 7) { diff --git a/dlls/sapi/xml.c b/dlls/sapi/xml.c index 518a687a5e8..aad32462272 100644 --- a/dlls/sapi/xml.c +++ b/dlls/sapi/xml.c @@ -459,8 +459,9 @@ static HRESULT add_sapi_text_fragment(struct xml_parser *parser, const SPVSTATE
static HRESULT parse_ssml_elems(struct xml_parser *parser, const SPVSTATE *state, const struct xml_elem *parent) { + struct xml_attr attr; struct xml_elem elem; - BOOL is_text; + BOOL is_text, end; HRESULT hr = S_OK;
while (SUCCEEDED(hr) && next_text_or_xml_elem(parser, &is_text, &elem, parent)) @@ -469,6 +470,12 @@ static HRESULT parse_ssml_elems(struct xml_parser *parser, const SPVSTATE *state { hr = add_sapi_text_fragment(parser, state); } + else if (xml_elem_eq(&elem, ssml_ns, L"p") || xml_elem_eq(&elem, ssml_ns, L"s")) + { + while (next_xml_attr(parser, &attr, &end)) ; + if (end) continue; + hr = parse_ssml_elems(parser, state, &elem); + } else { FIXME("Unknown element %s.\n", debugstr_xmlstr(&elem.name));
This merge request was approved by Huw Davies.