ssml-builder-js 2.6.0 → 2.8.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 +60 -4
- package/dist/{chunk-GF4QZHLJ.mjs → chunk-4RQETJUI.mjs} +1 -1
- package/dist/chunk-4RQETJUI.mjs.map +1 -0
- package/dist/{chunk-GTARZC43.mjs → chunk-TKD3PU5Y.mjs} +146 -43
- package/dist/chunk-TKD3PU5Y.mjs.map +1 -0
- package/dist/core.d.mts +25 -1
- package/dist/core.d.ts +25 -1
- package/dist/core.js +147 -42
- 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 +147 -42
- 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.ts
CHANGED
|
@@ -172,23 +172,47 @@ 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 SsmlDiagnosticSource = "ssml-static-validator";
|
|
176
|
+
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch";
|
|
175
177
|
interface SsmlDiagnostic {
|
|
178
|
+
code?: AzureDiagnosticCode;
|
|
176
179
|
line: number;
|
|
177
180
|
column: number;
|
|
178
181
|
message: string;
|
|
179
182
|
severity: SsmlDiagnosticSeverity;
|
|
183
|
+
source: SsmlDiagnosticSource;
|
|
184
|
+
}
|
|
185
|
+
interface AzureVoiceDefinition {
|
|
186
|
+
name: string;
|
|
187
|
+
locale: string;
|
|
188
|
+
secondaryLocales?: readonly string[];
|
|
189
|
+
styles?: readonly string[];
|
|
190
|
+
}
|
|
191
|
+
/** Alias retained for consumers that prefer the metadata terminology. */
|
|
192
|
+
interface AzureVoiceMetadata extends AzureVoiceDefinition {
|
|
180
193
|
}
|
|
181
194
|
interface AzureValidationOptions {
|
|
182
195
|
allowedAudioOrigins?: readonly string[];
|
|
183
196
|
allowExternalAudio?: boolean;
|
|
184
197
|
allowHttpAudio?: boolean;
|
|
185
198
|
customVoiceStyleMap?: Record<string, readonly string[]>;
|
|
199
|
+
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
200
|
+
languageAliases?: Record<string, string | readonly string[]>;
|
|
186
201
|
maxLength?: number;
|
|
202
|
+
normalizeLanguage?: (lang: string) => string;
|
|
187
203
|
unknownVoicePolicy?: "error" | "warn" | "ignore";
|
|
204
|
+
unsupportedStylePolicy?: "error" | "warn" | "ignore";
|
|
188
205
|
validateNestedVoices?: boolean;
|
|
206
|
+
voiceCatalog?: readonly AzureVoiceDefinition[];
|
|
207
|
+
voiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
189
208
|
}
|
|
209
|
+
type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
|
|
190
210
|
/** @deprecated Use AzureValidationOptions instead. */
|
|
191
211
|
type AzureSsmlValidationOptions = AzureValidationOptions;
|
|
212
|
+
/** Normalizes an Azure language tag using BCP 47 and the configured aliases. */
|
|
213
|
+
declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
|
|
214
|
+
/** Compares two Azure language tags after BCP 47 and alias normalization. */
|
|
215
|
+
declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
|
|
192
216
|
declare function validateAzureSsml(ssml: string, options?: AzureValidationOptions): SsmlDiagnostic[];
|
|
193
217
|
|
|
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 };
|
|
218
|
+
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 SsmlDiagnosticSource, 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
|
|
@@ -1130,7 +1132,8 @@ var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
|
1130
1132
|
"fraction",
|
|
1131
1133
|
"address",
|
|
1132
1134
|
"name",
|
|
1133
|
-
"currency"
|
|
1135
|
+
"currency",
|
|
1136
|
+
"number_digit"
|
|
1134
1137
|
]);
|
|
1135
1138
|
var ALLOWED_ROLES = /* @__PURE__ */ new Set([
|
|
1136
1139
|
"Girl",
|
|
@@ -1227,51 +1230,137 @@ function location(source, offset) {
|
|
|
1227
1230
|
const line = before.split("\n").length;
|
|
1228
1231
|
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
1229
1232
|
}
|
|
1230
|
-
function addDiagnostic(diagnostics, source, offset, message, severity = "error") {
|
|
1231
|
-
diagnostics.push({
|
|
1233
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
|
|
1234
|
+
diagnostics.push({
|
|
1235
|
+
...location(source, offset),
|
|
1236
|
+
message,
|
|
1237
|
+
severity,
|
|
1238
|
+
source: "ssml-static-validator",
|
|
1239
|
+
...code ? { code } : {}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
function isSupportedProsodyRate(value) {
|
|
1243
|
+
const trimmed = value.trim();
|
|
1244
|
+
if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
|
|
1245
|
+
const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
|
|
1246
|
+
if (!multiplier) return false;
|
|
1247
|
+
const numericValue = Number(multiplier[1]);
|
|
1248
|
+
return numericValue >= 0.5 && numericValue <= 2;
|
|
1232
1249
|
}
|
|
1233
1250
|
function attr(token, name) {
|
|
1234
1251
|
return token.attributes.get(name.toLowerCase());
|
|
1235
1252
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1253
|
+
var ADDITIONAL_VOICE_DEFINITIONS = [
|
|
1254
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
|
|
1255
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1256
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
1257
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1258
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
1259
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
1260
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" }
|
|
1261
|
+
];
|
|
1262
|
+
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1263
|
+
"zh-CN": ["zh-Hans"],
|
|
1264
|
+
"zh-TW": ["zh-Hant"]
|
|
1265
|
+
};
|
|
1266
|
+
function canonicalLanguageTag(language) {
|
|
1267
|
+
const trimmed = language.trim();
|
|
1268
|
+
if (!trimmed) return "";
|
|
1269
|
+
try {
|
|
1270
|
+
return new Intl.Locale(trimmed).toString().toLowerCase();
|
|
1271
|
+
} catch {
|
|
1272
|
+
return trimmed.toLowerCase();
|
|
1245
1273
|
}
|
|
1246
|
-
return map;
|
|
1247
1274
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1275
|
+
function createLanguageNormalizer(options) {
|
|
1276
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
1277
|
+
const addAliasGroup = (canonical, values) => {
|
|
1278
|
+
const normalizedCanonical = canonicalLanguageTag(canonical);
|
|
1279
|
+
if (!normalizedCanonical) return;
|
|
1280
|
+
aliases.set(normalizedCanonical, normalizedCanonical);
|
|
1281
|
+
for (const value of values) {
|
|
1282
|
+
const normalizedValue = canonicalLanguageTag(value);
|
|
1283
|
+
if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
|
|
1287
|
+
for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
|
|
1288
|
+
addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
|
|
1289
|
+
return (language) => {
|
|
1290
|
+
const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
|
|
1291
|
+
const normalized = canonicalLanguageTag(customValue);
|
|
1292
|
+
return aliases.get(normalized) ?? normalized;
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function normalizeAzureLanguage(language, options = {}) {
|
|
1296
|
+
return createLanguageNormalizer(options)(language);
|
|
1297
|
+
}
|
|
1298
|
+
function areAzureLanguagesEquivalent(first, second, options = {}) {
|
|
1299
|
+
const normalize = createLanguageNormalizer(options);
|
|
1300
|
+
const normalizedFirst = normalize(first);
|
|
1301
|
+
const normalizedSecond = normalize(second);
|
|
1302
|
+
if (normalizedFirst === normalizedSecond) return true;
|
|
1303
|
+
return normalizedFirst === languagePart(normalizedFirst) && languagePart(normalizedFirst) === languagePart(normalizedSecond);
|
|
1251
1304
|
}
|
|
1252
1305
|
function voiceLocalePrefix(voiceName) {
|
|
1253
1306
|
const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
|
|
1254
1307
|
if (!match?.groups) return void 0;
|
|
1308
|
+
const tag = `${match.groups.language}-${match.groups.region}`;
|
|
1255
1309
|
return {
|
|
1256
1310
|
language: match.groups.language.toLowerCase(),
|
|
1257
|
-
region: match.groups.region.toLowerCase()
|
|
1311
|
+
region: match.groups.region.toLowerCase(),
|
|
1312
|
+
tag
|
|
1258
1313
|
};
|
|
1259
1314
|
}
|
|
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;
|
|
1315
|
+
function definitionFromStyleMap(voiceName, styles) {
|
|
1263
1316
|
return {
|
|
1264
|
-
|
|
1265
|
-
|
|
1317
|
+
name: voiceName,
|
|
1318
|
+
locale: voiceLocalePrefix(voiceName)?.tag ?? "",
|
|
1319
|
+
styles
|
|
1266
1320
|
};
|
|
1267
1321
|
}
|
|
1268
|
-
function
|
|
1269
|
-
const
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1322
|
+
function normalizeVoiceCatalog(options) {
|
|
1323
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
1324
|
+
for (const [name, styles] of Object.entries(EXPRESS_AS_STYLES)) {
|
|
1325
|
+
definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
|
|
1326
|
+
}
|
|
1327
|
+
for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1328
|
+
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1329
|
+
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1330
|
+
for (const definition of options.customVoiceDefinitions ?? [])
|
|
1331
|
+
definitions.set(definition.name.toLowerCase(), definition);
|
|
1332
|
+
for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
|
|
1333
|
+
const key = voiceName.toLowerCase();
|
|
1334
|
+
const current = definitions.get(key);
|
|
1335
|
+
definitions.set(key, {
|
|
1336
|
+
...current ?? definitionFromStyleMap(voiceName, styles),
|
|
1337
|
+
name: current?.name ?? voiceName,
|
|
1338
|
+
styles: styles.map((style) => style.toLowerCase())
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
return definitions;
|
|
1342
|
+
}
|
|
1343
|
+
function diagnosticSeverity(policy) {
|
|
1344
|
+
if (policy === "ignore") return void 0;
|
|
1345
|
+
return policy === "error" ? "error" : "warning";
|
|
1346
|
+
}
|
|
1347
|
+
function languagePart(language) {
|
|
1348
|
+
try {
|
|
1349
|
+
return new Intl.Locale(language).language.toLowerCase();
|
|
1350
|
+
} catch {
|
|
1351
|
+
return language.split("-")[0]?.toLowerCase() ?? "";
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
|
|
1355
|
+
const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
|
|
1356
|
+
if (candidateLanguages.length === 0 || !language.trim()) return void 0;
|
|
1357
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
1358
|
+
const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
|
|
1359
|
+
if (normalizedCandidates.includes(normalizedLanguage)) return true;
|
|
1360
|
+
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1361
|
+
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1273
1362
|
}
|
|
1274
|
-
function validateElement(token, source, diagnostics, voiceName, options,
|
|
1363
|
+
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1275
1364
|
const name = token.name.toLowerCase();
|
|
1276
1365
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1277
1366
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
@@ -1291,7 +1380,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceSt
|
|
|
1291
1380
|
const rate = attr(token, "rate");
|
|
1292
1381
|
const pitch = attr(token, "pitch");
|
|
1293
1382
|
const volume = attr(token, "volume");
|
|
1294
|
-
if (rate &&
|
|
1383
|
+
if (rate && !isSupportedProsodyRate(rate))
|
|
1295
1384
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
|
|
1296
1385
|
if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
|
|
1297
1386
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
|
|
@@ -1313,17 +1402,19 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceSt
|
|
|
1313
1402
|
const role = attr(token, "role");
|
|
1314
1403
|
if (role && !ALLOWED_ROLES.has(role))
|
|
1315
1404
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
1316
|
-
const
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1405
|
+
const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
|
|
1406
|
+
const supportedStyles = definition?.styles;
|
|
1407
|
+
const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
|
|
1408
|
+
if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
|
|
1319
1409
|
addDiagnostic(
|
|
1320
1410
|
diagnostics,
|
|
1321
1411
|
source,
|
|
1322
1412
|
token.start,
|
|
1323
1413
|
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
1324
|
-
severity
|
|
1414
|
+
severity,
|
|
1415
|
+
"azure-unsupported-style"
|
|
1325
1416
|
);
|
|
1326
|
-
if (style && voiceName && !
|
|
1417
|
+
if (style && voiceName && !definition && severity)
|
|
1327
1418
|
addDiagnostic(
|
|
1328
1419
|
diagnostics,
|
|
1329
1420
|
source,
|
|
@@ -1404,7 +1495,15 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceSt
|
|
|
1404
1495
|
function validateAzureSsml(ssml, options = {}) {
|
|
1405
1496
|
const diagnostics = [];
|
|
1406
1497
|
if (typeof ssml !== "string") {
|
|
1407
|
-
return [
|
|
1498
|
+
return [
|
|
1499
|
+
{
|
|
1500
|
+
line: 1,
|
|
1501
|
+
column: 1,
|
|
1502
|
+
message: "SSML input must be a string",
|
|
1503
|
+
severity: "error",
|
|
1504
|
+
source: "ssml-static-validator"
|
|
1505
|
+
}
|
|
1506
|
+
];
|
|
1408
1507
|
}
|
|
1409
1508
|
const maxLength = options.maxLength ?? 1e4;
|
|
1410
1509
|
if (ssml.length > maxLength)
|
|
@@ -1428,41 +1527,47 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1428
1527
|
"Azure SSML requires at least one <voice> element under <speak>."
|
|
1429
1528
|
);
|
|
1430
1529
|
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
1431
|
-
const
|
|
1530
|
+
const voiceCatalog = normalizeVoiceCatalog(options);
|
|
1531
|
+
const normalizeLanguage = createLanguageNormalizer(options);
|
|
1432
1532
|
const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
1433
1533
|
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
1434
1534
|
for (const token of voicesToValidate) {
|
|
1435
1535
|
const name = attr(token, "name")?.trim();
|
|
1436
1536
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
1437
|
-
|
|
1537
|
+
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
1538
|
+
if (name && !definition && policySeverity)
|
|
1438
1539
|
addDiagnostic(
|
|
1439
1540
|
diagnostics,
|
|
1440
1541
|
ssml,
|
|
1441
1542
|
token.start,
|
|
1442
|
-
`Unknown voice "${name}" is not registered in the voice
|
|
1443
|
-
policySeverity
|
|
1543
|
+
`Unknown voice "${name}" is not registered in the voice catalog.`,
|
|
1544
|
+
policySeverity,
|
|
1545
|
+
"azure-unknown-voice"
|
|
1444
1546
|
);
|
|
1445
|
-
if (name && language &&
|
|
1547
|
+
if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
|
|
1446
1548
|
addDiagnostic(
|
|
1447
1549
|
diagnostics,
|
|
1448
1550
|
ssml,
|
|
1449
1551
|
token.start,
|
|
1450
1552
|
`Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
|
|
1451
|
-
"warning"
|
|
1553
|
+
"warning",
|
|
1554
|
+
"azure-locale-mismatch"
|
|
1452
1555
|
);
|
|
1453
1556
|
}
|
|
1454
1557
|
for (const token of tokens) {
|
|
1455
1558
|
const tokenVoiceName = options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1456
|
-
validateElement(token, ssml, diagnostics, tokenVoiceName, options,
|
|
1559
|
+
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
1457
1560
|
}
|
|
1458
1561
|
return diagnostics;
|
|
1459
1562
|
}
|
|
1460
1563
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1461
1564
|
0 && (module.exports = {
|
|
1565
|
+
areAzureLanguagesEquivalent,
|
|
1462
1566
|
buildPartialSsml,
|
|
1463
1567
|
buildSsml,
|
|
1464
1568
|
extractSsmlText,
|
|
1465
1569
|
mapSsmlTextNodes,
|
|
1570
|
+
normalizeAzureLanguage,
|
|
1466
1571
|
parseSsml,
|
|
1467
1572
|
validateAzureSsml,
|
|
1468
1573
|
validateSsml
|