ssml-builder-js 2.5.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/dist/elements.mjs CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  registerSsmlCompletionProvider,
34
34
  resolveExpressAsStyles,
35
35
  updateEditableText
36
- } from "./chunk-ALFYHSCR.mjs";
36
+ } from "./chunk-QJTVHANI.mjs";
37
37
  import "./chunk-6S5ODO6A.mjs";
38
38
 
39
39
  // packages/ssml-editor-react/src/ssmlInsertions.ts
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { AudioElement, AzureSsmlValidationOptions, AzureValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.mjs';
1
+ export { AudioElement, AzureDiagnosticCode, AzureLanguageNormalizationOptions, AzureSsmlValidationOptions, AzureValidationOptions, AzureVoiceDefinition, AzureVoiceMetadata, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml } from './core.mjs';
2
2
 
3
3
  interface TtsConfig {
4
4
  signal?: AbortSignal;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AudioElement, AzureSsmlValidationOptions, AzureValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.js';
1
+ export { AudioElement, AzureDiagnosticCode, AzureLanguageNormalizationOptions, AzureSsmlValidationOptions, AzureValidationOptions, AzureVoiceDefinition, AzureVoiceMetadata, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MapSsmlTextNodesOptions, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml } from './core.js';
2
2
 
3
3
  interface TtsConfig {
4
4
  signal?: AbortSignal;
package/dist/index.js CHANGED
@@ -40,10 +40,12 @@ __export(src_exports, {
40
40
  AzureTtsClient: () => AzureTtsClient,
41
41
  AzureTtsError: () => AzureTtsError,
42
42
  AzureTtsSdkError: () => AzureTtsSdkError,
43
+ areAzureLanguagesEquivalent: () => areAzureLanguagesEquivalent,
43
44
  buildPartialSsml: () => buildPartialSsml,
44
45
  buildSsml: () => buildSsml,
45
46
  extractSsmlText: () => extractSsmlText,
46
47
  mapSsmlTextNodes: () => mapSsmlTextNodes,
48
+ normalizeAzureLanguage: () => normalizeAzureLanguage,
47
49
  parseSsml: () => parseSsml,
48
50
  synthesizeSpeech: () => synthesizeSpeech,
49
51
  validateAzureSsml: () => validateAzureSsml,
@@ -1241,29 +1243,123 @@ function location(source, offset) {
1241
1243
  const line = before.split("\n").length;
1242
1244
  return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
1243
1245
  }
1244
- function addDiagnostic(diagnostics, source, offset, message, severity = "error") {
1245
- diagnostics.push({ ...location(source, offset), message, severity });
1246
+ function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
1247
+ diagnostics.push({ ...location(source, offset), message, severity, ...code ? { code } : {} });
1246
1248
  }
1247
1249
  function attr(token, name) {
1248
1250
  return token.attributes.get(name.toLowerCase());
1249
1251
  }
1250
- function normalizeVoiceStyleMap(customVoiceStyleMap) {
1251
- const map = new Map(
1252
- Object.entries(EXPRESS_AS_STYLES).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
1253
- );
1254
- for (const [voiceName, styles] of Object.entries(customVoiceStyleMap ?? {})) {
1255
- map.set(
1256
- voiceName.toLowerCase(),
1257
- styles.map((style) => style.toLowerCase())
1258
- );
1252
+ var ADDITIONAL_VOICE_DEFINITIONS = [
1253
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
1254
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
1255
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
1256
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1257
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
1258
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
1259
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" }
1260
+ ];
1261
+ var DEFAULT_LANGUAGE_ALIASES = {
1262
+ "zh-CN": ["zh-Hans"],
1263
+ "zh-TW": ["zh-Hant"]
1264
+ };
1265
+ function canonicalLanguageTag(language) {
1266
+ const trimmed = language.trim();
1267
+ if (!trimmed) return "";
1268
+ try {
1269
+ return new Intl.Locale(trimmed).toString().toLowerCase();
1270
+ } catch {
1271
+ return trimmed.toLowerCase();
1259
1272
  }
1260
- return map;
1273
+ }
1274
+ function createLanguageNormalizer(options) {
1275
+ const aliases = /* @__PURE__ */ new Map();
1276
+ const addAliasGroup = (canonical, values) => {
1277
+ const normalizedCanonical = canonicalLanguageTag(canonical);
1278
+ if (!normalizedCanonical) return;
1279
+ aliases.set(normalizedCanonical, normalizedCanonical);
1280
+ for (const value of values) {
1281
+ const normalizedValue = canonicalLanguageTag(value);
1282
+ if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
1283
+ }
1284
+ };
1285
+ for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
1286
+ for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
1287
+ addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
1288
+ return (language) => {
1289
+ const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
1290
+ const normalized = canonicalLanguageTag(customValue);
1291
+ return aliases.get(normalized) ?? normalized;
1292
+ };
1293
+ }
1294
+ function normalizeAzureLanguage(language, options = {}) {
1295
+ return createLanguageNormalizer(options)(language);
1296
+ }
1297
+ function areAzureLanguagesEquivalent(first, second, options = {}) {
1298
+ const normalize = createLanguageNormalizer(options);
1299
+ const normalizedFirst = normalize(first);
1300
+ const normalizedSecond = normalize(second);
1301
+ if (normalizedFirst === normalizedSecond) return true;
1302
+ return normalizedFirst === languagePart(normalizedFirst) && languagePart(normalizedFirst) === languagePart(normalizedSecond);
1303
+ }
1304
+ function voiceLocalePrefix(voiceName) {
1305
+ const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
1306
+ if (!match?.groups) return void 0;
1307
+ const tag = `${match.groups.language}-${match.groups.region}`;
1308
+ return {
1309
+ language: match.groups.language.toLowerCase(),
1310
+ region: match.groups.region.toLowerCase(),
1311
+ tag
1312
+ };
1313
+ }
1314
+ function definitionFromStyleMap(voiceName, styles) {
1315
+ return {
1316
+ name: voiceName,
1317
+ locale: voiceLocalePrefix(voiceName)?.tag ?? "",
1318
+ styles
1319
+ };
1320
+ }
1321
+ function normalizeVoiceCatalog(options) {
1322
+ const definitions = /* @__PURE__ */ new Map();
1323
+ for (const [name, styles] of Object.entries(EXPRESS_AS_STYLES)) {
1324
+ definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
1325
+ }
1326
+ for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
1327
+ for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
1328
+ for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
1329
+ for (const definition of options.customVoiceDefinitions ?? [])
1330
+ definitions.set(definition.name.toLowerCase(), definition);
1331
+ for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
1332
+ const key = voiceName.toLowerCase();
1333
+ const current = definitions.get(key);
1334
+ definitions.set(key, {
1335
+ ...current ?? definitionFromStyleMap(voiceName, styles),
1336
+ name: current?.name ?? voiceName,
1337
+ styles: styles.map((style) => style.toLowerCase())
1338
+ });
1339
+ }
1340
+ return definitions;
1261
1341
  }
1262
1342
  function diagnosticSeverity(policy) {
1263
1343
  if (policy === "ignore") return void 0;
1264
1344
  return policy === "error" ? "error" : "warning";
1265
1345
  }
1266
- function validateElement(token, source, diagnostics, voiceName, options, voiceStyleMap) {
1346
+ function languagePart(language) {
1347
+ try {
1348
+ return new Intl.Locale(language).language.toLowerCase();
1349
+ } catch {
1350
+ return language.split("-")[0]?.toLowerCase() ?? "";
1351
+ }
1352
+ }
1353
+ function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
1354
+ const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
1355
+ if (candidateLanguages.length === 0 || !language.trim()) return void 0;
1356
+ const normalizedLanguage = normalizeLanguage(language);
1357
+ const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
1358
+ if (normalizedCandidates.includes(normalizedLanguage)) return true;
1359
+ if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
1360
+ return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
1361
+ }
1362
+ function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
1267
1363
  const name = token.name.toLowerCase();
1268
1364
  if (name === "voice" && !attr(token, "name")?.trim())
1269
1365
  addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
@@ -1305,17 +1401,19 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceSt
1305
1401
  const role = attr(token, "role");
1306
1402
  if (role && !ALLOWED_ROLES.has(role))
1307
1403
  addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
1308
- const supportedStyles = voiceName ? voiceStyleMap.get(voiceName.toLowerCase()) : void 0;
1309
- const severity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
1310
- if (style && supportedStyles && !supportedStyles.includes(style.toLowerCase()) && severity)
1404
+ const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
1405
+ const supportedStyles = definition?.styles;
1406
+ const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
1407
+ if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
1311
1408
  addDiagnostic(
1312
1409
  diagnostics,
1313
1410
  source,
1314
1411
  token.start,
1315
1412
  `Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
1316
- severity
1413
+ severity,
1414
+ "azure-unsupported-style"
1317
1415
  );
1318
- if (style && voiceName && !supportedStyles && severity)
1416
+ if (style && voiceName && !definition && severity)
1319
1417
  addDiagnostic(
1320
1418
  diagnostics,
1321
1419
  source,
@@ -1420,23 +1518,36 @@ function validateAzureSsml(ssml, options = {}) {
1420
1518
  "Azure SSML requires at least one <voice> element under <speak>."
1421
1519
  );
1422
1520
  const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
1423
- const voiceStyleMap = normalizeVoiceStyleMap(options.customVoiceStyleMap);
1521
+ const voiceCatalog = normalizeVoiceCatalog(options);
1522
+ const normalizeLanguage = createLanguageNormalizer(options);
1424
1523
  const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
1425
1524
  const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
1426
1525
  for (const token of voicesToValidate) {
1427
1526
  const name = attr(token, "name")?.trim();
1428
- if (name && !voiceStyleMap.has(name.toLowerCase()) && policySeverity)
1527
+ const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
1528
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
1529
+ if (name && !definition && policySeverity)
1530
+ addDiagnostic(
1531
+ diagnostics,
1532
+ ssml,
1533
+ token.start,
1534
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
1535
+ policySeverity,
1536
+ "azure-unknown-voice"
1537
+ );
1538
+ if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
1429
1539
  addDiagnostic(
1430
1540
  diagnostics,
1431
1541
  ssml,
1432
1542
  token.start,
1433
- `Unknown voice "${name}" is not registered in the voice style map.`,
1434
- policySeverity
1543
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
1544
+ "warning",
1545
+ "azure-locale-mismatch"
1435
1546
  );
1436
1547
  }
1437
1548
  for (const token of tokens) {
1438
1549
  const tokenVoiceName = options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
1439
- validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceStyleMap);
1550
+ validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
1440
1551
  }
1441
1552
  return diagnostics;
1442
1553
  }
@@ -1627,10 +1738,12 @@ _options = new WeakMap();
1627
1738
  AzureTtsClient,
1628
1739
  AzureTtsError,
1629
1740
  AzureTtsSdkError,
1741
+ areAzureLanguagesEquivalent,
1630
1742
  buildPartialSsml,
1631
1743
  buildSsml,
1632
1744
  extractSsmlText,
1633
1745
  mapSsmlTextNodes,
1746
+ normalizeAzureLanguage,
1634
1747
  parseSsml,
1635
1748
  synthesizeSpeech,
1636
1749
  validateAzureSsml,