ssml-builder-js 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -2
- package/dist/{chunk-GTARZC43.mjs → chunk-D6ITDU3A.mjs} +120 -40
- package/dist/chunk-D6ITDU3A.mjs.map +1 -0
- package/dist/{chunk-GF4QZHLJ.mjs → chunk-QJTVHANI.mjs} +1 -1
- package/dist/chunk-QJTVHANI.mjs.map +1 -0
- package/dist/core.d.mts +23 -1
- package/dist/core.d.ts +23 -1
- package/dist/core.js +121 -39
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +5 -1
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +121 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-GF4QZHLJ.mjs.map +0 -1
- package/dist/chunk-GTARZC43.mjs.map +0 -1
package/dist/core.d.mts
CHANGED
|
@@ -172,23 +172,45 @@ declare function extractSsmlText(ssml: string): string[];
|
|
|
172
172
|
declare function mapSsmlTextNodes(ssml: string, transform: (text: string, context: SsmlTextNodeContext) => string | Promise<string>, options?: MapSsmlTextNodesOptions): Promise<string>;
|
|
173
173
|
|
|
174
174
|
type SsmlDiagnosticSeverity = "error" | "warning";
|
|
175
|
+
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch";
|
|
175
176
|
interface SsmlDiagnostic {
|
|
177
|
+
code?: AzureDiagnosticCode;
|
|
176
178
|
line: number;
|
|
177
179
|
column: number;
|
|
178
180
|
message: string;
|
|
179
181
|
severity: SsmlDiagnosticSeverity;
|
|
180
182
|
}
|
|
183
|
+
interface AzureVoiceDefinition {
|
|
184
|
+
name: string;
|
|
185
|
+
locale: string;
|
|
186
|
+
secondaryLocales?: readonly string[];
|
|
187
|
+
styles?: readonly string[];
|
|
188
|
+
}
|
|
189
|
+
/** Alias retained for consumers that prefer the metadata terminology. */
|
|
190
|
+
interface AzureVoiceMetadata extends AzureVoiceDefinition {
|
|
191
|
+
}
|
|
181
192
|
interface AzureValidationOptions {
|
|
182
193
|
allowedAudioOrigins?: readonly string[];
|
|
183
194
|
allowExternalAudio?: boolean;
|
|
184
195
|
allowHttpAudio?: boolean;
|
|
185
196
|
customVoiceStyleMap?: Record<string, readonly string[]>;
|
|
197
|
+
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
198
|
+
languageAliases?: Record<string, string | readonly string[]>;
|
|
186
199
|
maxLength?: number;
|
|
200
|
+
normalizeLanguage?: (lang: string) => string;
|
|
187
201
|
unknownVoicePolicy?: "error" | "warn" | "ignore";
|
|
202
|
+
unsupportedStylePolicy?: "error" | "warn" | "ignore";
|
|
188
203
|
validateNestedVoices?: boolean;
|
|
204
|
+
voiceCatalog?: readonly AzureVoiceDefinition[];
|
|
205
|
+
voiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
189
206
|
}
|
|
207
|
+
type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
|
|
190
208
|
/** @deprecated Use AzureValidationOptions instead. */
|
|
191
209
|
type AzureSsmlValidationOptions = AzureValidationOptions;
|
|
210
|
+
/** Normalizes an Azure language tag using BCP 47 and the configured aliases. */
|
|
211
|
+
declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
|
|
212
|
+
/** Compares two Azure language tags after BCP 47 and alias normalization. */
|
|
213
|
+
declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
|
|
192
214
|
declare function validateAzureSsml(ssml: string, options?: AzureValidationOptions): SsmlDiagnostic[];
|
|
193
215
|
|
|
194
|
-
export { type AudioElement, type AzureSsmlValidationOptions, type AzureValidationOptions, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml };
|
|
216
|
+
export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
|
package/dist/core.d.ts
CHANGED
|
@@ -172,23 +172,45 @@ declare function extractSsmlText(ssml: string): string[];
|
|
|
172
172
|
declare function mapSsmlTextNodes(ssml: string, transform: (text: string, context: SsmlTextNodeContext) => string | Promise<string>, options?: MapSsmlTextNodesOptions): Promise<string>;
|
|
173
173
|
|
|
174
174
|
type SsmlDiagnosticSeverity = "error" | "warning";
|
|
175
|
+
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch";
|
|
175
176
|
interface SsmlDiagnostic {
|
|
177
|
+
code?: AzureDiagnosticCode;
|
|
176
178
|
line: number;
|
|
177
179
|
column: number;
|
|
178
180
|
message: string;
|
|
179
181
|
severity: SsmlDiagnosticSeverity;
|
|
180
182
|
}
|
|
183
|
+
interface AzureVoiceDefinition {
|
|
184
|
+
name: string;
|
|
185
|
+
locale: string;
|
|
186
|
+
secondaryLocales?: readonly string[];
|
|
187
|
+
styles?: readonly string[];
|
|
188
|
+
}
|
|
189
|
+
/** Alias retained for consumers that prefer the metadata terminology. */
|
|
190
|
+
interface AzureVoiceMetadata extends AzureVoiceDefinition {
|
|
191
|
+
}
|
|
181
192
|
interface AzureValidationOptions {
|
|
182
193
|
allowedAudioOrigins?: readonly string[];
|
|
183
194
|
allowExternalAudio?: boolean;
|
|
184
195
|
allowHttpAudio?: boolean;
|
|
185
196
|
customVoiceStyleMap?: Record<string, readonly string[]>;
|
|
197
|
+
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
198
|
+
languageAliases?: Record<string, string | readonly string[]>;
|
|
186
199
|
maxLength?: number;
|
|
200
|
+
normalizeLanguage?: (lang: string) => string;
|
|
187
201
|
unknownVoicePolicy?: "error" | "warn" | "ignore";
|
|
202
|
+
unsupportedStylePolicy?: "error" | "warn" | "ignore";
|
|
188
203
|
validateNestedVoices?: boolean;
|
|
204
|
+
voiceCatalog?: readonly AzureVoiceDefinition[];
|
|
205
|
+
voiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
189
206
|
}
|
|
207
|
+
type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
|
|
190
208
|
/** @deprecated Use AzureValidationOptions instead. */
|
|
191
209
|
type AzureSsmlValidationOptions = AzureValidationOptions;
|
|
210
|
+
/** Normalizes an Azure language tag using BCP 47 and the configured aliases. */
|
|
211
|
+
declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
|
|
212
|
+
/** Compares two Azure language tags after BCP 47 and alias normalization. */
|
|
213
|
+
declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
|
|
192
214
|
declare function validateAzureSsml(ssml: string, options?: AzureValidationOptions): SsmlDiagnostic[];
|
|
193
215
|
|
|
194
|
-
export { type AudioElement, type AzureSsmlValidationOptions, type AzureValidationOptions, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml };
|
|
216
|
+
export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
|
package/dist/core.js
CHANGED
|
@@ -27,10 +27,12 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
27
27
|
// src/core.ts
|
|
28
28
|
var core_exports = {};
|
|
29
29
|
__export(core_exports, {
|
|
30
|
+
areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
|
|
30
31
|
buildPartialSsml: () => buildPartialSsml,
|
|
31
32
|
buildSsml: () => buildSsml,
|
|
32
33
|
extractSsmlText: () => extractSsmlText,
|
|
33
34
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
35
|
+
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
34
36
|
parseSsml: () => parseSsml,
|
|
35
37
|
validateAzureSsml: () => validateAzureSsml,
|
|
36
38
|
validateSsml: () => validateSsml
|
|
@@ -1227,51 +1229,123 @@ function location(source, offset) {
|
|
|
1227
1229
|
const line = before.split("\n").length;
|
|
1228
1230
|
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
1229
1231
|
}
|
|
1230
|
-
function addDiagnostic(diagnostics, source, offset, message, severity = "error") {
|
|
1231
|
-
diagnostics.push({ ...location(source, offset), message, severity });
|
|
1232
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
|
|
1233
|
+
diagnostics.push({ ...location(source, offset), message, severity, ...code ? { code } : {} });
|
|
1232
1234
|
}
|
|
1233
1235
|
function attr(token, name) {
|
|
1234
1236
|
return token.attributes.get(name.toLowerCase());
|
|
1235
1237
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1238
|
+
var ADDITIONAL_VOICE_DEFINITIONS = [
|
|
1239
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
|
|
1240
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1241
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1242
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1243
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1244
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1245
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" }
|
|
1246
|
+
];
|
|
1247
|
+
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1248
|
+
"zh-CN": ["zh-Hans"],
|
|
1249
|
+
"zh-TW": ["zh-Hant"]
|
|
1250
|
+
};
|
|
1251
|
+
function canonicalLanguageTag(language) {
|
|
1252
|
+
const trimmed = language.trim();
|
|
1253
|
+
if (!trimmed) return "";
|
|
1254
|
+
try {
|
|
1255
|
+
return new Intl.Locale(trimmed).toString().toLowerCase();
|
|
1256
|
+
} catch {
|
|
1257
|
+
return trimmed.toLowerCase();
|
|
1245
1258
|
}
|
|
1246
|
-
return map;
|
|
1247
1259
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1260
|
+
function createLanguageNormalizer(options) {
|
|
1261
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
1262
|
+
const addAliasGroup = (canonical, values) => {
|
|
1263
|
+
const normalizedCanonical = canonicalLanguageTag(canonical);
|
|
1264
|
+
if (!normalizedCanonical) return;
|
|
1265
|
+
aliases.set(normalizedCanonical, normalizedCanonical);
|
|
1266
|
+
for (const value of values) {
|
|
1267
|
+
const normalizedValue = canonicalLanguageTag(value);
|
|
1268
|
+
if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
|
|
1272
|
+
for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
|
|
1273
|
+
addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
|
|
1274
|
+
return (language) => {
|
|
1275
|
+
const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
|
|
1276
|
+
const normalized = canonicalLanguageTag(customValue);
|
|
1277
|
+
return aliases.get(normalized) ?? normalized;
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function normalizeAzureLanguage(language, options = {}) {
|
|
1281
|
+
return createLanguageNormalizer(options)(language);
|
|
1282
|
+
}
|
|
1283
|
+
function areAzureLanguagesEquivalent(first, second, options = {}) {
|
|
1284
|
+
const normalize = createLanguageNormalizer(options);
|
|
1285
|
+
const normalizedFirst = normalize(first);
|
|
1286
|
+
const normalizedSecond = normalize(second);
|
|
1287
|
+
if (normalizedFirst === normalizedSecond) return true;
|
|
1288
|
+
return normalizedFirst === languagePart(normalizedFirst) && languagePart(normalizedFirst) === languagePart(normalizedSecond);
|
|
1251
1289
|
}
|
|
1252
1290
|
function voiceLocalePrefix(voiceName) {
|
|
1253
1291
|
const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
|
|
1254
1292
|
if (!match?.groups) return void 0;
|
|
1293
|
+
const tag = `${match.groups.language}-${match.groups.region}`;
|
|
1255
1294
|
return {
|
|
1256
1295
|
language: match.groups.language.toLowerCase(),
|
|
1257
|
-
region: match.groups.region.toLowerCase()
|
|
1296
|
+
region: match.groups.region.toLowerCase(),
|
|
1297
|
+
tag
|
|
1258
1298
|
};
|
|
1259
1299
|
}
|
|
1260
|
-
function
|
|
1261
|
-
const match = /^(?<language>[A-Za-z]{2,3})(?:-(?<region>[A-Za-z]{2}|\d{3}))?(?:-|$)/.exec(language.trim());
|
|
1262
|
-
if (!match?.groups) return void 0;
|
|
1300
|
+
function definitionFromStyleMap(voiceName, styles) {
|
|
1263
1301
|
return {
|
|
1264
|
-
|
|
1265
|
-
|
|
1302
|
+
name: voiceName,
|
|
1303
|
+
locale: voiceLocalePrefix(voiceName)?.tag ?? "",
|
|
1304
|
+
styles
|
|
1266
1305
|
};
|
|
1267
1306
|
}
|
|
1268
|
-
function
|
|
1269
|
-
const
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1307
|
+
function normalizeVoiceCatalog(options) {
|
|
1308
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
1309
|
+
for (const [name, styles] of Object.entries(EXPRESS_AS_STYLES)) {
|
|
1310
|
+
definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
|
|
1311
|
+
}
|
|
1312
|
+
for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1313
|
+
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1314
|
+
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1315
|
+
for (const definition of options.customVoiceDefinitions ?? [])
|
|
1316
|
+
definitions.set(definition.name.toLowerCase(), definition);
|
|
1317
|
+
for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
|
|
1318
|
+
const key = voiceName.toLowerCase();
|
|
1319
|
+
const current = definitions.get(key);
|
|
1320
|
+
definitions.set(key, {
|
|
1321
|
+
...current ?? definitionFromStyleMap(voiceName, styles),
|
|
1322
|
+
name: current?.name ?? voiceName,
|
|
1323
|
+
styles: styles.map((style) => style.toLowerCase())
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
return definitions;
|
|
1327
|
+
}
|
|
1328
|
+
function diagnosticSeverity(policy) {
|
|
1329
|
+
if (policy === "ignore") return void 0;
|
|
1330
|
+
return policy === "error" ? "error" : "warning";
|
|
1273
1331
|
}
|
|
1274
|
-
function
|
|
1332
|
+
function languagePart(language) {
|
|
1333
|
+
try {
|
|
1334
|
+
return new Intl.Locale(language).language.toLowerCase();
|
|
1335
|
+
} catch {
|
|
1336
|
+
return language.split("-")[0]?.toLowerCase() ?? "";
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
|
|
1340
|
+
const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
|
|
1341
|
+
if (candidateLanguages.length === 0 || !language.trim()) return void 0;
|
|
1342
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
1343
|
+
const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
|
|
1344
|
+
if (normalizedCandidates.includes(normalizedLanguage)) return true;
|
|
1345
|
+
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1346
|
+
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1347
|
+
}
|
|
1348
|
+
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1275
1349
|
const name = token.name.toLowerCase();
|
|
1276
1350
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1277
1351
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
@@ -1313,17 +1387,19 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceSt
|
|
|
1313
1387
|
const role = attr(token, "role");
|
|
1314
1388
|
if (role && !ALLOWED_ROLES.has(role))
|
|
1315
1389
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
1316
|
-
const
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1390
|
+
const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
|
|
1391
|
+
const supportedStyles = definition?.styles;
|
|
1392
|
+
const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
|
|
1393
|
+
if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
|
|
1319
1394
|
addDiagnostic(
|
|
1320
1395
|
diagnostics,
|
|
1321
1396
|
source,
|
|
1322
1397
|
token.start,
|
|
1323
1398
|
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
1324
|
-
severity
|
|
1399
|
+
severity,
|
|
1400
|
+
"azure-unsupported-style"
|
|
1325
1401
|
);
|
|
1326
|
-
if (style && voiceName && !
|
|
1402
|
+
if (style && voiceName && !definition && severity)
|
|
1327
1403
|
addDiagnostic(
|
|
1328
1404
|
diagnostics,
|
|
1329
1405
|
source,
|
|
@@ -1428,41 +1504,47 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1428
1504
|
"Azure SSML requires at least one <voice> element under <speak>."
|
|
1429
1505
|
);
|
|
1430
1506
|
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
1431
|
-
const
|
|
1507
|
+
const voiceCatalog = normalizeVoiceCatalog(options);
|
|
1508
|
+
const normalizeLanguage = createLanguageNormalizer(options);
|
|
1432
1509
|
const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
1433
1510
|
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
1434
1511
|
for (const token of voicesToValidate) {
|
|
1435
1512
|
const name = attr(token, "name")?.trim();
|
|
1436
1513
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
1437
|
-
|
|
1514
|
+
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
1515
|
+
if (name && !definition && policySeverity)
|
|
1438
1516
|
addDiagnostic(
|
|
1439
1517
|
diagnostics,
|
|
1440
1518
|
ssml,
|
|
1441
1519
|
token.start,
|
|
1442
|
-
`Unknown voice "${name}" is not registered in the voice
|
|
1443
|
-
policySeverity
|
|
1520
|
+
`Unknown voice "${name}" is not registered in the voice catalog.`,
|
|
1521
|
+
policySeverity,
|
|
1522
|
+
"azure-unknown-voice"
|
|
1444
1523
|
);
|
|
1445
|
-
if (name && language &&
|
|
1524
|
+
if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
|
|
1446
1525
|
addDiagnostic(
|
|
1447
1526
|
diagnostics,
|
|
1448
1527
|
ssml,
|
|
1449
1528
|
token.start,
|
|
1450
1529
|
`Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
|
|
1451
|
-
"warning"
|
|
1530
|
+
"warning",
|
|
1531
|
+
"azure-locale-mismatch"
|
|
1452
1532
|
);
|
|
1453
1533
|
}
|
|
1454
1534
|
for (const token of tokens) {
|
|
1455
1535
|
const tokenVoiceName = options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1456
|
-
validateElement(token, ssml, diagnostics, tokenVoiceName, options,
|
|
1536
|
+
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
1457
1537
|
}
|
|
1458
1538
|
return diagnostics;
|
|
1459
1539
|
}
|
|
1460
1540
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1461
1541
|
0 && (module.exports = {
|
|
1542
|
+
areAzureLanguagesEquivalent,
|
|
1462
1543
|
buildPartialSsml,
|
|
1463
1544
|
buildSsml,
|
|
1464
1545
|
extractSsmlText,
|
|
1465
1546
|
mapSsmlTextNodes,
|
|
1547
|
+
normalizeAzureLanguage,
|
|
1466
1548
|
parseSsml,
|
|
1467
1549
|
validateAzureSsml,
|
|
1468
1550
|
validateSsml
|