/*
* Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "svox_ssml_parser.h"
#include <utils/Log.h>
#include <cutils/jstring.h>
#include <string.h>
#include <utils/String16.h>
#define SSML_PITCH_XLOW "50"
#define SSML_PITCH_LOW "75"
#define SSML_PITCH_MEDIUM "100"
#define SSML_PITCH_HIGH "150"
#define SSML_PITCH_XHIGH "200"
#define SSML_RATE_XSLOW "30"
#define SSML_RATE_SLOW "60"
#define SSML_RATE_MEDIUM "100"
#define SSML_RATE_FAST "250"
#define SSML_RATE_XFAST "500"
#define SSML_VOLUME_SILENT "0"
#define SSML_VOLUME_XLOW "25"
#define SSML_VOLUME_LOW "70"
#define SSML_VOLUME_MEDIUM "120"
#define SSML_VOLUME_LOUD "300"
#define SSML_VOLUME_XLOUD "450"
#define SSML_BREAK_NONE "0ms"
#define SSML_BREAK_XWEAK "100ms"
#define SSML_BREAK_WEAK "300ms"
#define SSML_BREAK_MEDIUM "600ms"
#define SSML_BREAK_STRONG "1s"
#define SSML_BREAK_XSTRONG "3s"
extern int cnvIpaToXsampa(const char16_t* ipaString, size_t ipaStringSize, char** outXsampaString);
extern char * createPhonemeString( const char * xsampa, int length );
SvoxSsmlParser::SvoxSsmlParser() : m_isInBreak(0), m_appendix(NULL), m_docLanguage(NULL)
{
mParser = XML_ParserCreate("UTF-8");
if (mParser)
{
XML_SetElementHandler(mParser, starttagHandler, endtagHandler);
XML_SetCharacterDataHandler(mParser, textHandler);
XML_SetUserData(mParser, (void*)this);
m_datasize = 512;
m_data = new char[m_datasize];
m_data[0] = '\0';
}
}
SvoxSsmlParser::~SvoxSsmlParser()
{
if (mParser)
XML_ParserFree(mParser);
if (m_data)
delete [] m_data;
if (m_appendix)
delete [] m_appendix;
if (m_docLanguage)
delete [] m_docLanguage;
}
int SvoxSsmlParser::initSuccessful()
{
return (mParser && m_data);
}
int SvoxSsmlParser::parseDocument(const char* ssmldoc, int isFinal)
{
int doclen = (int)strlen(ssmldoc) + 1;
int status = XML_Parse(mParser, ssmldoc, doclen, isFinal);
if (status == XML_STATUS_ERROR)
{
/* Note: for some reason Expat almost always complains about invalid tokens, even when document is well formed */
ALOGI("Parser error at line %d: %s\n", (int)XML_GetCurrentLineNumber(mParser), XML_ErrorString(XML_GetErrorCode(mParser)));
}
return status;
}
char* SvoxSsmlParser::getParsedDocument()
{
return m_data;
}
char* SvoxSsmlParser::getParsedDocumentLanguage()
{
return m_docLanguage;
}
void SvoxSsmlParser::starttagHandler(void* data, const XML_Char* element, const XML_Char** attributes)
{
((SvoxSsmlParser*)data)->startElement(element, attributes);
}
void SvoxSsmlParser::startElement(const XML_Char* element, const XML_Char** attributes)
{
if (strcmp(element, "speak") == 0)
{
if (strlen(m_data) > 0)
{
/* we have old data, get rid of it and reallocate memory */
delete m_data;
m_data = NULL;
m_datasize = 512;
m_data = new char[m_datasize];
if (!m_data)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
/* the only attribute supported in the speak tag is xml:lang, all others are ignored */
for (int i = 0; attributes[i]; i += 2)
{
if (strcmp(attributes[i], "xml:lang") == 0)
{
if (!m_docLanguage)
{
m_docLanguage = new char[strlen(attributes[i+1])+1];
}
strcpy(m_docLanguage, attributes[i+1]);
break;
}
}
}
else if (strcmp(element, "p") == 0) /* currently no attributes are supported for <p> */
{
if (strlen(m_data) + 4 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "<p>");
}
else if (strcmp(element, "s") == 0) /* currently no attributes are supported for <s> */
{
if (strlen(m_data) + 4 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "<s>");
}
else if (strcmp(element, "phoneme") == 0) /* only ipa and xsampa alphabets are supported */
{
int alpha = 1; /* set to 1 if alphabet is ipa */
int tagComplete = 0; /* set to 1 if phoneme tag has already been added */
char16_t* ph = NULL;
char* xsampastr = NULL;
size_t phsize = 0;
size_t xsampasize = 0;
for (int i = 0; attributes[i]; i += 2)
{
if (strcmp(attributes[i], "alphabet") == 0)
{
if (strcmp(attributes[i+1], "xsampa") == 0)
{
alpha = 0;
}
}
if (strcmp(attributes[i], "ph") == 0)
{
ph = new char16_t[strlen8to16(attributes[i+1]) + 1];
ph = strdup8to16(attributes[i+1], &phsize);
}
}
if (!ph)
{
/* error, no phonetic string */
ALOGE("Error: bad SSML syntax, ph attribute not supplied.");
return;
}
if (alpha)
{
/* need to convert phoneme string to xsampa */
xsampasize = cnvIpaToXsampa(ph, phsize, &xsampastr);
delete [] ph;
if (!xsampastr)
{
ALOGE("Error: failed to allocate memory for IPA string conversion");
return;
}
}
else
{
xsampastr = strndup16to8(ph, phsize);
xsampasize = strlen(xsampastr);
delete [] ph;
}
/* split XSAMPA string into multiple phonemes if needed */
if (strstr(xsampastr, " ") || strstr(xsampastr, "#")) /* check again to see if we have multiple words */
{
char* phonstr = createPhonemeString(xsampastr, strlen(xsampastr) + 1);
free(xsampastr);
xsampastr = NULL;
xsampastr = (char*)malloc(strlen(phonstr) + 1);
strcpy(xsampastr, phonstr);
free(phonstr);
phonstr = NULL;
tagComplete = 1;
}
if (tagComplete)
{
if (strlen(m_data) + strlen(xsampastr) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!");
free(xsampastr);
return;
}
}
}
else
{
if (strlen(m_data) + strlen(xsampastr) + 17 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!");
free(xsampastr);
return;
}
}
strcat(m_data, "<phoneme ph='");
}
strcat(m_data, xsampastr);
free(xsampastr);
if (!tagComplete)
{
if (strlen(m_data) + 4 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "'/>");
}
m_isInBreak = 1; /* set flag to indicate any text between open and close tag is to be discarded */
}
else if (strcmp(element, "break") == 0)
{
if (strlen(m_data) + 17 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "<break time='");
char* time = NULL;
for (int i = 0; attributes[i]; i += 2)
{
if (strcmp(attributes[i], "time") == 0)
{
time = new char[strlen(attributes[i+1]) + 1];
if (!time)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strcpy(time, attributes[i+1]);
}
else if (strcmp(attributes[i], "strength") == 0 && !time)
{
time = convertBreakStrengthToTime(attributes[i+1]);
}
}
if (!time)
{
time = new char[6];
if (!time)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strcpy(time, SSML_BREAK_WEAK); /* if no time or strength attributes are specified, default to weak break */
}
if (strlen(m_data) + strlen(time) + 4 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, time);
strcat(m_data, "'/>");
m_isInBreak = 1; /* set flag to indicate any text between open and close tag is to be discarded */
}
else if (strcmp(element, "prosody") == 0) /* only pitch, rate and volume attributes are supported */
{
for (int i = 0; attributes[i]; i += 2)
{
if (strcmp(attributes[i], "pitch") == 0)
{
char* svoxpitch = convertToSvoxPitch(attributes[i+1]);
if (!svoxpitch)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
if (!svoxpitch)
{
svoxpitch = new char[4];
if (!svoxpitch)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strcpy(svoxpitch, "100");
}
char* pitch = new char[17 + strlen(svoxpitch)];
if (!pitch)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
sprintf(pitch, "<pitch level='%s'>", svoxpitch);
if (strlen(m_data) + strlen(pitch) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, pitch);
if (!m_appendix)
{
m_appendix = new char[30];
m_appendix[0] = '\0';
}
strcat(m_appendix, "</pitch>");
delete [] svoxpitch;
delete [] pitch;
}
else if (strcmp(attributes[i], "rate") == 0)
{
char* svoxrate = convertToSvoxRate(attributes[i+1]);
if (!svoxrate)
{
svoxrate = new char[4];
if (!svoxrate)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strcpy(svoxrate, "100");
}
char* rate = new char[17 + strlen(svoxrate)];
if (!rate)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
sprintf(rate, "<speed level='%s'>", svoxrate);
if (strlen(m_data) + strlen(rate) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, rate);
if (!m_appendix)
{
m_appendix = new char[30];
if (!m_appendix)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
m_appendix[0] = '\0';
}
strcat(m_appendix, "</speed>");
delete [] svoxrate;
delete [] rate;
}
else if (strcmp(attributes[i], "volume") == 0)
{
char* svoxvol = convertToSvoxVolume(attributes[i+1]);
if (!svoxvol)
{
svoxvol = new char[4];
if (!svoxvol)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strcpy(svoxvol, "100");
}
char* volume = new char[18 + strlen(svoxvol)];
if (!volume)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
sprintf(volume, "<volume level='%s'>", svoxvol);
if (strlen(m_data) + strlen(volume) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, volume);
if (!m_appendix)
{
m_appendix = new char[30];
m_appendix[0] = '\0';
}
strcat(m_appendix, "</volume>");
delete [] svoxvol;
delete [] volume;
}
}
}
else if (strcmp(element, "audio") == 0) /* only 16kHz 16bit wav files are supported as src */
{
if (strlen(m_data) + 17 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "<usesig file='");
for (int i = 0; attributes[i]; i += 2)
{
if (strcmp(attributes[i], "src") == 0)
{
if (strlen(m_data) + strlen(attributes[i+1]) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, attributes[i+1]);
}
}
strcat(m_data, "'>");
}
}
void SvoxSsmlParser::endtagHandler(void* data, const XML_Char* element)
{
((SvoxSsmlParser*)data)->endElement(element);
}
void SvoxSsmlParser::endElement(const XML_Char* element)
{
if (strcmp(element, "speak") == 0)
{
/* do nothing */
}
else if (strcmp(element, "p") == 0)
{
if (strlen(m_data) + 5 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "</p>");
}
else if (strcmp(element, "s") == 0)
{
if (strlen(m_data) + 5 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "</s>");
}
else if (strcmp(element, "phoneme") == 0)
{
m_isInBreak = 0; /* indicate we are no longer in phoneme tag */
}
else if (strcmp(element, "break") == 0)
{
m_isInBreak = 0; /* indicate we are no longer in break tag */
}
else if (strcmp(element, "prosody") == 0)
{
if (m_appendix)
{
if (strlen(m_data) + strlen(m_appendix) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, m_appendix);
delete [] m_appendix;
m_appendix = NULL;
}
}
else if (strcmp(element, "audio") == 0)
{
if (strlen(m_data) + 10 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, "</usesig>");
}
}
void SvoxSsmlParser::textHandler(void* data, const XML_Char* text, int length)
{
((SvoxSsmlParser*)data)->textElement(text, length);
}
void SvoxSsmlParser::textElement(const XML_Char* text, int length)
{
if (m_isInBreak)
{
return; /* handles the case when someone has added text inside the break or phoneme tag - this text is thrown away */
}
char* content = new char[length + 1];
if (!content)
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
strncpy(content, text, length);
content[length] = '\0';
if (strlen(m_data) + strlen(content) + 1 > (size_t)m_datasize)
{
if (!growDataSize(100))
{
ALOGE("Error: failed to allocate memory for string!\n");
return;
}
}
strcat(m_data, content);
delete [] content;
}
/**
convertToSvoxPitch
Converts SSML pitch labels to SVOX pitch levels
*/
char* SvoxSsmlParser::convertToSvoxPitch(const char* value)
{
char* converted = NULL;
if (strcmp(value, "x-low") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_XLOW);
}
else if (strcmp(value, "low") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_LOW);
}
else if (strcmp(value, "medium") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_MEDIUM);
}
else if (strcmp(value, "default") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_MEDIUM);
}
else if (strcmp(value, "high") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_HIGH);
}
else if (strcmp(value, "x-high") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_PITCH_XHIGH);
}
return converted;
}
/**
convertToSvoxRate
Converts SSML rate labels to SVOX speed levels
*/
char* SvoxSsmlParser::convertToSvoxRate(const char* value)
{
char* converted = NULL;
if (strcmp(value, "x-slow") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_XSLOW);
}
else if (strcmp(value, "slow") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_SLOW);
}
else if (strcmp(value, "medium") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_MEDIUM);
}
else if (strcmp(value, "default") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_MEDIUM);
}
else if (strcmp(value, "fast") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_FAST);
}
else if (strcmp(value, "x-fast") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_RATE_XFAST);
}
return converted;
}
/**
convertToSvoxVolume
Converts SSML volume labels to SVOX volume levels
*/
char* SvoxSsmlParser::convertToSvoxVolume(const char* value)
{
char* converted = NULL;
if (strcmp(value, "silent") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_SILENT);
}
else if (strcmp(value, "x-low") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_XLOW);
}
else if (strcmp(value, "low") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_LOW);
}
else if (strcmp(value, "medium") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_MEDIUM);
}
else if (strcmp(value, "default") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_MEDIUM);
}
else if (strcmp(value, "loud") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_LOUD);
}
else if (strcmp(value, "x-loud") == 0)
{
converted = new char[4];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_VOLUME_XLOUD);
}
return converted;
}
/**
convertBreakStrengthToTime
Converts SSML break strength labels to SVOX break time
*/
char* SvoxSsmlParser::convertBreakStrengthToTime(const char* value)
{
char* converted = NULL;
if (strcmp(value, "none") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_NONE);
}
else if (strcmp(value, "x-weak") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_XWEAK);
}
else if (strcmp(value, "weak") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_WEAK);
}
else if (strcmp(value, "medium") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_MEDIUM);
}
else if (strcmp(value, "strong") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_STRONG);
}
else if (strcmp(value, "x-strong") == 0)
{
converted = new char[6];
if (!converted)
{
ALOGE("Error: failed to allocate memory for string!\n");
return NULL;
}
strcpy(converted, SSML_BREAK_XSTRONG);
}
return converted;
}
/**
growDataSize
Increases the size of the internal text storage member
*/
int SvoxSsmlParser::growDataSize(int sizeToGrow)
{
char* tmp = new char[m_datasize];
if (!tmp)
return 0;
strcpy(tmp, m_data);
delete [] m_data;
m_data = NULL;
m_data = new char[m_datasize + sizeToGrow];
if (!m_data)
{
m_data = tmp;
return 0;
}
m_datasize += sizeToGrow;
strcpy(m_data, tmp);
delete [] tmp;
tmp = NULL;
return 1;
}