ssml-builder-js 2.12.0 → 2.13.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/index.js CHANGED
@@ -51,11 +51,14 @@ __export(src_exports, {
51
51
  getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
52
52
  isValidAzureAudioDuration: () => isValidAzureAudioDuration,
53
53
  mapSsmlTextNodes: () => mapSsmlTextNodes,
54
+ mergeSynthesisResults: () => mergeSynthesisResults,
54
55
  normalizeAzureLanguage: () => normalizeAzureLanguage,
55
56
  parseSsml: () => parseSsml,
56
57
  splitSsmlDocument: () => splitSsmlDocument,
57
58
  synthesizeSpeech: () => synthesizeSpeech,
58
59
  synthesizeSsml: () => synthesizeSsml,
60
+ synthesizeSsmlChunks: () => synthesizeSsmlChunks,
61
+ synthesizeSsmlSafe: () => synthesizeSsmlSafe,
59
62
  validateAzureSsml: () => validateAzureSsml,
60
63
  validateSsml: () => validateSsml,
61
64
  validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
@@ -1061,30 +1064,109 @@ function splitNode(document, node, maxLength, context = []) {
1061
1064
  flush();
1062
1065
  return parts;
1063
1066
  }
1064
- function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH) {
1065
- if (!Number.isInteger(maxLength) || maxLength <= 0) {
1067
+ function textFromNode(node) {
1068
+ if (typeof node === "string") return node;
1069
+ if (node.type === "text") return node.value;
1070
+ return (node.children ?? []).map(textFromNode).join("");
1071
+ }
1072
+ function collectMarks(node, marks) {
1073
+ if (typeof node === "string" || node.type === "text") return;
1074
+ if (node.type === "mark" && node.name) marks.push(node.name);
1075
+ if (node.type === "bookmark" && node.mark) marks.push(node.mark);
1076
+ for (const child of node.children ?? []) collectMarks(child, marks);
1077
+ }
1078
+ function collectInheritedContext(nodes) {
1079
+ const context = {};
1080
+ const visit = (node) => {
1081
+ if (typeof node === "string" || node.type === "text") return;
1082
+ if (context.voice === void 0 && node.type === "voice" && node.name) context.voice = node.name;
1083
+ if (context.lang === void 0 && node.type === "lang" && node.lang) context.lang = node.lang;
1084
+ if (context.prosody === void 0 && node.type === "prosody") {
1085
+ const prosody = {};
1086
+ for (const [key, value] of Object.entries(node.attributes ?? {})) prosody[key] = String(value);
1087
+ for (const key of ["rate", "pitch", "volume", "contour", "range"]) {
1088
+ const value = node[key];
1089
+ if (value !== void 0) prosody[key] = String(value);
1090
+ }
1091
+ if (Object.keys(prosody).length > 0) context.prosody = prosody;
1092
+ }
1093
+ for (const child of node.children ?? []) visit(child);
1094
+ };
1095
+ nodes.forEach(visit);
1096
+ return context;
1097
+ }
1098
+ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
1099
+ const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
1100
+ const text = nodes.map(textFromNode).join("");
1101
+ const marks = [];
1102
+ for (const node of nodes) collectMarks(node, marks);
1103
+ const inheritedContext = collectInheritedContext(nodes);
1104
+ if (inheritedContext.lang === void 0 && document.lang) inheritedContext.lang = document.lang;
1105
+ return {
1106
+ chunkIndex,
1107
+ ssml: documentWithChildren(document, chunkNodes),
1108
+ originalTextRange: { start: textStart, end: textStart + text.length },
1109
+ inheritedContext,
1110
+ containedMarks: marks,
1111
+ hasBackgroundAudio: chunkNodes.some(
1112
+ (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1113
+ )
1114
+ };
1115
+ }
1116
+ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
1117
+ const resolvedMaxLength = typeof maxLength === "number" ? maxLength : maxLength.maxLength ?? DEFAULT_MAX_LENGTH;
1118
+ const resolvedOptions = typeof maxLength === "number" ? options : maxLength;
1119
+ if (!Number.isInteger(resolvedMaxLength) || resolvedMaxLength <= 0) {
1066
1120
  throw new RangeError("maxLength must be a positive integer");
1067
1121
  }
1068
1122
  const document = parseSsml(ssml);
1069
- if (ssml.length <= maxLength) return [ssml];
1070
- const children = document.children ?? [];
1071
- const splitChildren = children.flatMap((child) => splitNode(document, child, maxLength));
1123
+ const backgroundAudio = (document.children ?? []).find(
1124
+ (node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
1125
+ );
1126
+ if (ssml.length <= resolvedMaxLength) {
1127
+ return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
1128
+ }
1129
+ const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
1130
+ const plainDocumentLength = documentWithChildren(document, []).length;
1131
+ const backgroundDocumentLength = backgroundAudio ? documentWithChildren(document, [backgroundAudio]).length : plainDocumentLength;
1132
+ const backgroundOverhead = Math.max(0, backgroundDocumentLength - plainDocumentLength);
1133
+ const contentMaxLength = Math.max(1, resolvedMaxLength - backgroundOverhead);
1134
+ const splitChildren = contentChildren.flatMap((child) => splitNode(document, child, contentMaxLength));
1072
1135
  const chunks = [];
1073
1136
  let group = [];
1074
1137
  for (const child of splitChildren) {
1075
1138
  const candidate = [...group, child];
1076
- if (documentWithChildren(document, candidate).length <= maxLength) {
1139
+ if (documentWithChildren(document, candidate).length <= contentMaxLength) {
1077
1140
  group = candidate;
1078
1141
  continue;
1079
1142
  }
1080
1143
  if (group.length > 0) chunks.push(group);
1081
1144
  group = [child];
1082
- if (documentWithChildren(document, group).length > maxLength) {
1145
+ if (documentWithChildren(document, group).length > contentMaxLength) {
1083
1146
  throw new RangeError("maxLength is too small to contain the SSML document wrapper");
1084
1147
  }
1085
1148
  }
1086
1149
  if (group.length > 0) chunks.push(group);
1087
- return chunks.map((chunk) => documentWithChildren(document, chunk));
1150
+ if (chunks.length === 0) {
1151
+ const result = createChunk(document, [], 0, 0, backgroundAudio, resolvedOptions.replicateBackgroundAudio ?? false);
1152
+ if (result.ssml.length > resolvedMaxLength) {
1153
+ throw new RangeError("maxLength is too small to contain the SSML document wrapper");
1154
+ }
1155
+ return [result];
1156
+ }
1157
+ let textStart = 0;
1158
+ return chunks.map((chunk, chunkIndex) => {
1159
+ const result = createChunk(
1160
+ document,
1161
+ chunk,
1162
+ chunkIndex,
1163
+ textStart,
1164
+ backgroundAudio,
1165
+ resolvedOptions.replicateBackgroundAudio ?? false
1166
+ );
1167
+ textStart = result.originalTextRange.end;
1168
+ return result;
1169
+ });
1088
1170
  }
1089
1171
 
1090
1172
  // packages/ssml-core/src/validation.ts
@@ -2175,7 +2257,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
2175
2257
  addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
2176
2258
  }
2177
2259
  }
2178
- function validateAzureSsml(ssml, options = {}) {
2260
+ function validateAzureSsmlStatic(ssml, options = {}) {
2179
2261
  const diagnostics = [];
2180
2262
  if (typeof ssml !== "string") {
2181
2263
  return [
@@ -2299,6 +2381,51 @@ function validateAzureSsml(ssml, options = {}) {
2299
2381
  }
2300
2382
  return diagnostics;
2301
2383
  }
2384
+ function urlAttributes(token) {
2385
+ const tag = canonicalTagName(token.name);
2386
+ const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
2387
+ return attributes.flatMap((attribute) => {
2388
+ const value = attr(token, attribute);
2389
+ return value === void 0 ? [] : [{ attribute, value }];
2390
+ });
2391
+ }
2392
+ function validateAzureSsml(ssml, options = {}) {
2393
+ const diagnostics = validateAzureSsmlStatic(ssml, options);
2394
+ const validator = options.urlValidator ?? options.customUrlValidator;
2395
+ if (!validator || typeof ssml !== "string") return diagnostics;
2396
+ let tokens;
2397
+ try {
2398
+ tokens = tokenizeElements(ssml);
2399
+ } catch {
2400
+ return diagnostics;
2401
+ }
2402
+ const checks = tokens.flatMap(
2403
+ (token) => urlAttributes(token).map(async ({ attribute, value }) => {
2404
+ try {
2405
+ const result = await validator(value, { tag: token.name, attribute });
2406
+ const valid = typeof result === "boolean" ? result : result.valid;
2407
+ if (!valid) {
2408
+ const reason = typeof result === "boolean" ? void 0 : result.reason;
2409
+ addDiagnostic(
2410
+ diagnostics,
2411
+ ssml,
2412
+ token.start,
2413
+ `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
2414
+ );
2415
+ }
2416
+ } catch (error) {
2417
+ const reason = error instanceof Error ? error.message : String(error);
2418
+ addDiagnostic(
2419
+ diagnostics,
2420
+ ssml,
2421
+ token.start,
2422
+ `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
2423
+ );
2424
+ }
2425
+ })
2426
+ );
2427
+ return Promise.all(checks).then(() => diagnostics);
2428
+ }
2302
2429
 
2303
2430
  // packages/ssml-core/src/generated/azureVoiceCatalog.ts
2304
2431
  var AZURE_VOICE_CATALOG_METADATA = {
@@ -2485,12 +2612,23 @@ async function synthesizeSsml(ssml, config) {
2485
2612
  ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
2486
2613
  );
2487
2614
  const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
2615
+ const requestId = result.resultId;
2616
+ const addSourceMetadata = (event) => ({
2617
+ ...event,
2618
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2619
+ ...requestId ? { requestId } : {}
2620
+ });
2621
+ const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
2622
+ const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
2623
+ const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
2488
2624
  resolve({
2489
2625
  audioData: result.audioData,
2490
2626
  durationMs,
2491
- ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
2492
- ...visemes.length > 0 ? { visemes } : {},
2493
- ...bookmarks.length > 0 ? { bookmarks } : {}
2627
+ ...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
2628
+ ...requestId ? { requestId } : {},
2629
+ ...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
2630
+ ...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
2631
+ ...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
2494
2632
  });
2495
2633
  };
2496
2634
  try {
@@ -2510,30 +2648,1591 @@ async function synthesizeSsml(ssml, config) {
2510
2648
  }
2511
2649
  });
2512
2650
  }
2651
+ async function synthesizeSsmlChunks(chunks, config) {
2652
+ const results = [];
2653
+ const totalChunks = chunks.length;
2654
+ for (const [index, chunk] of chunks.entries()) {
2655
+ const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
2656
+ const result = await synthesizeSsml(input.ssml, {
2657
+ ...config,
2658
+ ...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
2659
+ onProgress: void 0
2660
+ });
2661
+ results.push(result);
2662
+ config.onProgress?.({
2663
+ currentChunk: index + 1,
2664
+ totalChunks,
2665
+ percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100)
2666
+ });
2667
+ }
2668
+ return mergeSynthesisResults(results);
2669
+ }
2670
+ function mergeSynthesisResults(results) {
2671
+ const audioLength = results.reduce((total, result) => total + result.audioData.byteLength, 0);
2672
+ const audioData = new Uint8Array(audioLength);
2673
+ const boundaries = [];
2674
+ const visemes = [];
2675
+ const bookmarks = [];
2676
+ let byteOffset = 0;
2677
+ let durationOffset = 0;
2678
+ for (const result of results) {
2679
+ audioData.set(new Uint8Array(result.audioData), byteOffset);
2680
+ byteOffset += result.audioData.byteLength;
2681
+ const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
2682
+ for (const boundary of chunkBoundaries) {
2683
+ const textRange = boundary.textRange ?? result.textRange;
2684
+ const requestId = boundary.requestId ?? result.requestId;
2685
+ boundaries.push({
2686
+ ...boundary,
2687
+ audioOffsetMs: boundary.audioOffsetMs + durationOffset,
2688
+ ...textRange ? { textRange: { ...textRange } } : {},
2689
+ ...requestId ? { requestId } : {}
2690
+ });
2691
+ }
2692
+ for (const viseme of result.visemes ?? []) {
2693
+ const textRange = viseme.textRange ?? result.textRange;
2694
+ const requestId = viseme.requestId ?? result.requestId;
2695
+ visemes.push({
2696
+ ...viseme,
2697
+ audioOffsetMs: viseme.audioOffsetMs + durationOffset,
2698
+ ...textRange ? { textRange: { ...textRange } } : {},
2699
+ ...requestId ? { requestId } : {}
2700
+ });
2701
+ }
2702
+ for (const bookmark of result.bookmarks ?? []) {
2703
+ const textRange = bookmark.textRange ?? result.textRange;
2704
+ const requestId = bookmark.requestId ?? result.requestId;
2705
+ bookmarks.push({
2706
+ ...bookmark,
2707
+ audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
2708
+ ...textRange ? { textRange: { ...textRange } } : {},
2709
+ ...requestId ? { requestId } : {}
2710
+ });
2711
+ }
2712
+ durationOffset += Math.max(0, result.durationMs);
2713
+ }
2714
+ return {
2715
+ audioData: audioData.buffer,
2716
+ durationMs: durationOffset,
2717
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
2718
+ ...visemes.length > 0 ? { visemes } : {},
2719
+ ...bookmarks.length > 0 ? { bookmarks } : {},
2720
+ ...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
2721
+ ...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
2722
+ };
2723
+ }
2513
2724
  async function synthesizeSpeech(ssml, config) {
2514
2725
  return (await synthesizeSsml(ssml, config)).audioData;
2515
2726
  }
2516
2727
 
2517
- // packages/azure-tts-client/src/client.ts
2518
- var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
2519
- var _options;
2520
- var AzureTtsClient = class {
2521
- constructor(options) {
2522
- __privateAdd(this, _options);
2523
- __privateSet(this, _options, options);
2728
+ // packages/ssml-core/dist/index.mjs
2729
+ var __typeError2 = (msg) => {
2730
+ throw TypeError(msg);
2731
+ };
2732
+ var __accessCheck2 = (obj, member, msg) => member.has(obj) || __typeError2("Cannot " + msg);
2733
+ var __privateGet2 = (obj, member, getter) => (__accessCheck2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
2734
+ var __privateAdd2 = (obj, member, value) => member.has(obj) ? __typeError2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2735
+ var __privateSet2 = (obj, member, value, setter) => (__accessCheck2(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
2736
+ var SYNTHESIS_NAMESPACE2 = "http://www.w3.org/2001/10/synthesis";
2737
+ var MSTTS_NAMESPACE2 = "http://www.w3.org/2001/mstts";
2738
+ var MAX_NESTING_DEPTH2 = 1e3;
2739
+ var SSML_TAGS2 = {
2740
+ SPEAK: "speak",
2741
+ VOICE: "voice",
2742
+ PROSODY: "prosody",
2743
+ BREAK: "break",
2744
+ EXPRESS_AS: "express-as",
2745
+ EXPRESS_AS_CAMEL: "expressAs",
2746
+ MSTTS_EXPRESS_AS: "mstts:express-as",
2747
+ SAY_AS: "say-as",
2748
+ SAY_AS_CAMEL: "sayAs",
2749
+ PHONEME: "phoneme",
2750
+ EMPHASIS: "emphasis",
2751
+ AUDIO: "audio",
2752
+ SUB: "sub",
2753
+ LANG: "lang",
2754
+ MARK: "mark",
2755
+ BOOKMARK: "bookmark",
2756
+ LEXICON: "lexicon",
2757
+ PARAGRAPH: "p",
2758
+ SENTENCE: "s",
2759
+ WORD: "w",
2760
+ MSTTS_SILENCE: "mstts:silence",
2761
+ SILENCE: "silence",
2762
+ MSTTS_VISEME: "mstts:viseme",
2763
+ VISEME: "viseme",
2764
+ MSTTS_AUDIO_DURATION: "mstts:audioduration",
2765
+ MSTTS_DIALOG: "mstts:dialog",
2766
+ MSTTS_TURN: "mstts:turn",
2767
+ MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
2768
+ MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
2769
+ MSTTS_EMBEDDING: "mstts:embedding",
2770
+ MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
2771
+ };
2772
+ var SSML_ATTRS2 = {
2773
+ VERSION: "version",
2774
+ XMLNS: "xmlns",
2775
+ XML_LANG: "xml:lang",
2776
+ LANG: "lang",
2777
+ MSTTS_XMLNS: "xmlns:mstts",
2778
+ NAME: "name",
2779
+ VOICE: "voice",
2780
+ SPEAKER: "speaker",
2781
+ EFFECT: "effect",
2782
+ RATE: "rate",
2783
+ PITCH: "pitch",
2784
+ VOLUME: "volume",
2785
+ CONTOUR: "contour",
2786
+ RANGE: "range",
2787
+ TIME: "time",
2788
+ STRENGTH: "strength",
2789
+ STYLE: "style",
2790
+ STYLE_DEGREE: "styledegree",
2791
+ STYLE_DEGREE_CAMEL: "styleDegree",
2792
+ STYLE_DEGREE_HYPHEN: "style-degree",
2793
+ ROLE: "role",
2794
+ INTERPRET_AS: "interpret-as",
2795
+ FORMAT: "format",
2796
+ DETAIL: "detail",
2797
+ ALPHABET: "alphabet",
2798
+ PH: "ph",
2799
+ LEVEL: "level",
2800
+ SRC: "src",
2801
+ DESC: "desc",
2802
+ CLIP_BEGIN: "clipBegin",
2803
+ CLIP_END: "clipEnd",
2804
+ SPEED: "speed",
2805
+ REPEAT_COUNT: "repeatCount",
2806
+ REPEAT_DURATION: "repeatDuration",
2807
+ SOUND_LEVEL: "soundLevel",
2808
+ ALIAS: "alias",
2809
+ MARK: "mark",
2810
+ URI: "uri",
2811
+ ID: "id",
2812
+ MODEL: "model",
2813
+ PROFILE: "profile",
2814
+ URL: "url",
2815
+ SPEAKER_PROFILE_ID: "speakerProfileId",
2816
+ TYPE: "type",
2817
+ VALUE: "value",
2818
+ FADE_IN: "fadein",
2819
+ FADE_OUT: "fadeout"
2820
+ };
2821
+ var XML_ENTITIES2 = {
2822
+ amp: "&",
2823
+ apos: "'",
2824
+ gt: ">",
2825
+ lt: "<",
2826
+ quot: '"'
2827
+ };
2828
+ function hasOwn2(object, property) {
2829
+ return Object.getOwnPropertyDescriptor(object, property) !== void 0;
2830
+ }
2831
+ function setAttribute2(attributes, name, value) {
2832
+ Object.defineProperty(attributes, name, {
2833
+ configurable: true,
2834
+ enumerable: true,
2835
+ value,
2836
+ writable: true
2837
+ });
2838
+ }
2839
+ function decodeEntity2(entity) {
2840
+ const namedValue = hasOwn2(XML_ENTITIES2, entity) ? XML_ENTITIES2[entity] : void 0;
2841
+ if (namedValue !== void 0) {
2842
+ return namedValue;
2524
2843
  }
2525
- async synthesize(ssml) {
2526
- const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
2527
- const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
2528
- __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
2529
- const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
2530
- return synthesizeSpeech(ssml, config);
2844
+ const isHexadecimal = entity.startsWith("#x") || entity.startsWith("#X");
2845
+ const isDecimal = entity.startsWith("#");
2846
+ if (!isHexadecimal && !isDecimal) {
2847
+ throw new Error(`Unknown XML entity: &${entity};`);
2531
2848
  }
2532
- async synthesizeSsml(ssml) {
2533
- const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
2534
- const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
2535
- __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
2536
- return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
2849
+ const digits = entity.slice(isHexadecimal ? 2 : 1);
2850
+ const codePoint = Number.parseInt(digits, isHexadecimal ? 16 : 10);
2851
+ if (!digits || !Number.isInteger(codePoint) || codePoint < 0 || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343 || codePoint < 32 && ![9, 10, 13].includes(codePoint)) {
2852
+ throw new Error(`Invalid XML character reference: &${entity};`);
2853
+ }
2854
+ return String.fromCodePoint(codePoint);
2855
+ }
2856
+ function decodeXmlEntities2(value) {
2857
+ let result = "";
2858
+ let start = 0;
2859
+ while (true) {
2860
+ const ampersand = value.indexOf("&", start);
2861
+ if (ampersand === -1) {
2862
+ return result + value.slice(start);
2863
+ }
2864
+ result += value.slice(start, ampersand);
2865
+ const semicolon = value.indexOf(";", ampersand + 1);
2866
+ if (semicolon === -1) {
2867
+ throw new Error("Unterminated XML entity reference");
2868
+ }
2869
+ result += decodeEntity2(value.slice(ampersand + 1, semicolon));
2870
+ start = semicolon + 1;
2871
+ }
2872
+ }
2873
+ function isXmlNameStart2(value) {
2874
+ return value !== void 0 && /[A-Za-z_]/.test(value);
2875
+ }
2876
+ function isXmlNameCharacter2(value) {
2877
+ return value !== void 0 && /[A-Za-z0-9_.:-]/.test(value);
2878
+ }
2879
+ function isXmlWhitespace2(value) {
2880
+ return value === " " || value === " " || value === "\r" || value === "\n";
2881
+ }
2882
+ function removeStandardNamespaceAttributes2(attributes) {
2883
+ if (attributes[SSML_ATTRS2.XMLNS] === SYNTHESIS_NAMESPACE2) {
2884
+ delete attributes[SSML_ATTRS2.XMLNS];
2885
+ }
2886
+ if (attributes[SSML_ATTRS2.MSTTS_XMLNS] === MSTTS_NAMESPACE2) {
2887
+ delete attributes[SSML_ATTRS2.MSTTS_XMLNS];
2888
+ }
2889
+ }
2890
+ var _index2;
2891
+ var XmlParser2 = class {
2892
+ constructor(source) {
2893
+ __privateAdd2(this, _index2, 0);
2894
+ this.source = source;
2895
+ }
2896
+ parse() {
2897
+ if (this.source.charCodeAt(0) === 65279) {
2898
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2899
+ }
2900
+ this.skipMisc();
2901
+ if (__privateGet2(this, _index2) >= this.source.length) {
2902
+ this.fail("SSML input is empty");
2903
+ }
2904
+ if (this.source[__privateGet2(this, _index2)] !== "<") {
2905
+ this.fail("SSML input must start with an XML element");
2906
+ }
2907
+ const root = this.parseElement(0);
2908
+ this.skipMisc();
2909
+ if (__privateGet2(this, _index2) !== this.source.length) {
2910
+ this.fail("Unexpected content after the root XML element");
2911
+ }
2912
+ return root;
2913
+ }
2914
+ parseElement(depth) {
2915
+ if (depth > MAX_NESTING_DEPTH2) {
2916
+ this.fail("XML nesting depth exceeds the supported limit");
2917
+ }
2918
+ this.expect("<");
2919
+ if (this.source[__privateGet2(this, _index2)] === "/") {
2920
+ this.fail("Unexpected closing XML element");
2921
+ }
2922
+ const name = this.parseName();
2923
+ const { attributes, selfClosing } = this.parseStartTag();
2924
+ if (selfClosing) {
2925
+ return { name, attributes, children: [] };
2926
+ }
2927
+ const children = [];
2928
+ while (__privateGet2(this, _index2) < this.source.length) {
2929
+ if (this.source.startsWith("</", __privateGet2(this, _index2))) {
2930
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
2931
+ const closingName = this.parseName();
2932
+ this.skipWhitespace();
2933
+ this.expect(">");
2934
+ if (closingName !== name) {
2935
+ this.fail(`Mismatched closing element: expected </${name}> but found </${closingName}>`);
2936
+ }
2937
+ return { name, attributes, children };
2938
+ }
2939
+ if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
2940
+ this.skipComment();
2941
+ continue;
2942
+ }
2943
+ if (this.source.startsWith("<![CDATA[", __privateGet2(this, _index2))) {
2944
+ this.appendText(children, this.parseCdata());
2945
+ continue;
2946
+ }
2947
+ if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
2948
+ this.skipProcessingInstruction();
2949
+ continue;
2950
+ }
2951
+ if (this.source.startsWith("<!", __privateGet2(this, _index2))) {
2952
+ this.fail("Unsupported XML declaration inside an element");
2953
+ }
2954
+ if (this.source[__privateGet2(this, _index2)] === "<") {
2955
+ children.push(this.parseElement(depth + 1));
2956
+ } else {
2957
+ this.appendText(children, this.parseText());
2958
+ }
2959
+ }
2960
+ this.fail(`Unclosed XML element: <${name}>`);
2961
+ }
2962
+ parseStartTag() {
2963
+ const attributes = {};
2964
+ while (__privateGet2(this, _index2) < this.source.length) {
2965
+ this.skipWhitespace();
2966
+ if (this.source.startsWith("/>", __privateGet2(this, _index2))) {
2967
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 2);
2968
+ return { attributes, selfClosing: true };
2969
+ }
2970
+ if (this.source[__privateGet2(this, _index2)] === ">") {
2971
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2972
+ return { attributes, selfClosing: false };
2973
+ }
2974
+ const name = this.parseName();
2975
+ this.skipWhitespace();
2976
+ this.expect("=");
2977
+ this.skipWhitespace();
2978
+ const quote = this.source[__privateGet2(this, _index2)];
2979
+ if (quote !== '"' && quote !== "'") {
2980
+ this.fail(`XML attribute ${name} must use a quoted value`);
2981
+ }
2982
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2983
+ const valueStart = __privateGet2(this, _index2);
2984
+ while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== quote) {
2985
+ if (this.source[__privateGet2(this, _index2)] === "<") {
2986
+ this.fail(`Invalid "<" in XML attribute ${name}`);
2987
+ }
2988
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2989
+ }
2990
+ if (__privateGet2(this, _index2) >= this.source.length) {
2991
+ this.fail(`Unclosed XML attribute ${name}`);
2992
+ }
2993
+ const value = decodeXmlEntities2(this.source.slice(valueStart, __privateGet2(this, _index2)));
2994
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
2995
+ if (hasOwn2(attributes, name)) {
2996
+ this.fail(`Duplicate XML attribute: ${name}`);
2997
+ }
2998
+ setAttribute2(attributes, name, value);
2999
+ }
3000
+ this.fail("Unclosed XML start tag");
3001
+ }
3002
+ parseText() {
3003
+ const start = __privateGet2(this, _index2);
3004
+ while (__privateGet2(this, _index2) < this.source.length && this.source[__privateGet2(this, _index2)] !== "<") {
3005
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3006
+ }
3007
+ const value = this.source.slice(start, __privateGet2(this, _index2));
3008
+ if (value.includes("]]>")) {
3009
+ this.fail("CDATA termination is not valid in ordinary XML text");
3010
+ }
3011
+ return decodeXmlEntities2(value);
3012
+ }
3013
+ parseCdata() {
3014
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<![CDATA[".length);
3015
+ const end = this.source.indexOf("]]>", __privateGet2(this, _index2));
3016
+ if (end === -1) {
3017
+ this.fail("Unclosed XML CDATA section");
3018
+ }
3019
+ const value = this.source.slice(__privateGet2(this, _index2), end);
3020
+ __privateSet2(this, _index2, end + 3);
3021
+ return value;
3022
+ }
3023
+ skipComment() {
3024
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<!--".length);
3025
+ const end = this.source.indexOf("-->", __privateGet2(this, _index2));
3026
+ if (end === -1) {
3027
+ this.fail("Unclosed XML comment");
3028
+ }
3029
+ if (this.source.slice(__privateGet2(this, _index2), end).includes("--")) {
3030
+ this.fail("XML comments cannot contain consecutive hyphens");
3031
+ }
3032
+ __privateSet2(this, _index2, end + 3);
3033
+ }
3034
+ skipProcessingInstruction() {
3035
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + "<?".length);
3036
+ this.parseName();
3037
+ const end = this.source.indexOf("?>", __privateGet2(this, _index2));
3038
+ if (end === -1) {
3039
+ this.fail("Unclosed XML processing instruction");
3040
+ }
3041
+ __privateSet2(this, _index2, end + 2);
3042
+ }
3043
+ skipMisc() {
3044
+ while (__privateGet2(this, _index2) < this.source.length) {
3045
+ this.skipWhitespace();
3046
+ if (this.source.startsWith("<!--", __privateGet2(this, _index2))) {
3047
+ this.skipComment();
3048
+ continue;
3049
+ }
3050
+ if (this.source.startsWith("<?", __privateGet2(this, _index2))) {
3051
+ this.skipProcessingInstruction();
3052
+ continue;
3053
+ }
3054
+ if (this.source.startsWith("<!DOCTYPE", __privateGet2(this, _index2))) {
3055
+ this.fail("DOCTYPE declarations are not supported");
3056
+ }
3057
+ break;
3058
+ }
3059
+ }
3060
+ parseName() {
3061
+ const first = this.source[__privateGet2(this, _index2)];
3062
+ if (!isXmlNameStart2(first)) {
3063
+ this.fail("Invalid XML name");
3064
+ }
3065
+ const start = __privateGet2(this, _index2);
3066
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3067
+ while (isXmlNameCharacter2(this.source[__privateGet2(this, _index2)])) {
3068
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3069
+ }
3070
+ return this.source.slice(start, __privateGet2(this, _index2));
3071
+ }
3072
+ appendText(children, value) {
3073
+ if (!value) {
3074
+ return;
3075
+ }
3076
+ const previous = children[children.length - 1];
3077
+ if (typeof previous === "string") {
3078
+ children[children.length - 1] = previous + value;
3079
+ } else {
3080
+ children.push(value);
3081
+ }
3082
+ }
3083
+ skipWhitespace() {
3084
+ while (isXmlWhitespace2(this.source[__privateGet2(this, _index2)])) {
3085
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + 1);
3086
+ }
3087
+ }
3088
+ expect(value) {
3089
+ if (!this.source.startsWith(value, __privateGet2(this, _index2))) {
3090
+ this.fail(`Expected "${value}"`);
3091
+ }
3092
+ __privateSet2(this, _index2, __privateGet2(this, _index2) + value.length);
3093
+ }
3094
+ fail(message) {
3095
+ throw new Error(`${message} at position ${__privateGet2(this, _index2)}`);
3096
+ }
3097
+ };
3098
+ _index2 = /* @__PURE__ */ new WeakMap();
3099
+ function readAttribute2(attributes, ...names) {
3100
+ let found = false;
3101
+ let value;
3102
+ for (const name of names) {
3103
+ if (hasOwn2(attributes, name)) {
3104
+ if (!found) {
3105
+ value = String(attributes[name]);
3106
+ found = true;
3107
+ }
3108
+ delete attributes[name];
3109
+ }
3110
+ }
3111
+ return value;
3112
+ }
3113
+ function getElementAttributes2(node) {
3114
+ const attributes = { ...node.attributes };
3115
+ removeStandardNamespaceAttributes2(attributes);
3116
+ return attributes;
3117
+ }
3118
+ function finishElement2(element, node, attributes) {
3119
+ if (node.children.length > 0) {
3120
+ element.children = node.children.map(convertNode2);
3121
+ }
3122
+ if (Object.keys(attributes).length > 0) {
3123
+ element.attributes = attributes;
3124
+ }
3125
+ return element;
3126
+ }
3127
+ function convertElement2(node) {
3128
+ const attributes = getElementAttributes2(node);
3129
+ switch (node.name) {
3130
+ case SSML_TAGS2.VOICE: {
3131
+ const element = { type: SSML_TAGS2.VOICE };
3132
+ const name = readAttribute2(attributes, SSML_ATTRS2.NAME);
3133
+ const effect = readAttribute2(attributes, SSML_ATTRS2.EFFECT);
3134
+ if (name !== void 0) element.name = name;
3135
+ if (effect !== void 0) element.effect = effect;
3136
+ return finishElement2(element, node, attributes);
3137
+ }
3138
+ case SSML_TAGS2.PROSODY: {
3139
+ const element = { type: SSML_TAGS2.PROSODY };
3140
+ const rate = readAttribute2(attributes, SSML_ATTRS2.RATE);
3141
+ const pitch = readAttribute2(attributes, SSML_ATTRS2.PITCH);
3142
+ const volume = readAttribute2(attributes, SSML_ATTRS2.VOLUME);
3143
+ const contour = readAttribute2(attributes, SSML_ATTRS2.CONTOUR);
3144
+ const range = readAttribute2(attributes, SSML_ATTRS2.RANGE);
3145
+ if (rate !== void 0) element.rate = rate;
3146
+ if (pitch !== void 0) element.pitch = pitch;
3147
+ if (volume !== void 0) element.volume = volume;
3148
+ if (contour !== void 0) element.contour = contour;
3149
+ if (range !== void 0) element.range = range;
3150
+ return finishElement2(element, node, attributes);
3151
+ }
3152
+ case SSML_TAGS2.BREAK: {
3153
+ const element = { type: SSML_TAGS2.BREAK };
3154
+ const time = readAttribute2(attributes, SSML_ATTRS2.TIME);
3155
+ const strength = readAttribute2(attributes, SSML_ATTRS2.STRENGTH);
3156
+ if (time !== void 0) element.time = time;
3157
+ if (strength !== void 0) element.strength = strength;
3158
+ return finishElement2(element, node, attributes);
3159
+ }
3160
+ case SSML_TAGS2.EXPRESS_AS:
3161
+ case SSML_TAGS2.EXPRESS_AS_CAMEL:
3162
+ case SSML_TAGS2.MSTTS_EXPRESS_AS: {
3163
+ const element = { type: node.name };
3164
+ const style = readAttribute2(attributes, SSML_ATTRS2.STYLE);
3165
+ const styleDegree = readAttribute2(
3166
+ attributes,
3167
+ SSML_ATTRS2.STYLE_DEGREE,
3168
+ SSML_ATTRS2.STYLE_DEGREE_CAMEL,
3169
+ SSML_ATTRS2.STYLE_DEGREE_HYPHEN
3170
+ );
3171
+ const role = readAttribute2(attributes, SSML_ATTRS2.ROLE);
3172
+ if (style !== void 0) element.style = style;
3173
+ if (styleDegree !== void 0) element.styleDegree = styleDegree;
3174
+ if (role !== void 0) element.role = role;
3175
+ return finishElement2(element, node, attributes);
3176
+ }
3177
+ case SSML_TAGS2.SAY_AS:
3178
+ case SSML_TAGS2.SAY_AS_CAMEL: {
3179
+ const element = { type: node.name };
3180
+ const interpretAs = readAttribute2(attributes, SSML_ATTRS2.INTERPRET_AS);
3181
+ const format = readAttribute2(attributes, SSML_ATTRS2.FORMAT);
3182
+ const detail = readAttribute2(attributes, SSML_ATTRS2.DETAIL);
3183
+ if (interpretAs !== void 0) element.interpretAs = interpretAs;
3184
+ if (format !== void 0) element.format = format;
3185
+ if (detail !== void 0) element.detail = detail;
3186
+ return finishElement2(element, node, attributes);
3187
+ }
3188
+ case SSML_TAGS2.PHONEME: {
3189
+ const element = { type: SSML_TAGS2.PHONEME };
3190
+ const alphabet = readAttribute2(attributes, SSML_ATTRS2.ALPHABET);
3191
+ const ph = readAttribute2(attributes, SSML_ATTRS2.PH);
3192
+ if (alphabet !== void 0) element.alphabet = alphabet;
3193
+ if (ph !== void 0) element.ph = ph;
3194
+ return finishElement2(element, node, attributes);
3195
+ }
3196
+ case SSML_TAGS2.EMPHASIS: {
3197
+ const element = { type: SSML_TAGS2.EMPHASIS };
3198
+ const level = readAttribute2(attributes, SSML_ATTRS2.LEVEL);
3199
+ if (level !== void 0) element.level = level;
3200
+ return finishElement2(element, node, attributes);
3201
+ }
3202
+ case SSML_TAGS2.AUDIO: {
3203
+ const element = { type: SSML_TAGS2.AUDIO };
3204
+ const src = readAttribute2(attributes, SSML_ATTRS2.SRC);
3205
+ const desc = readAttribute2(attributes, SSML_ATTRS2.DESC);
3206
+ const clipBegin = readAttribute2(attributes, SSML_ATTRS2.CLIP_BEGIN);
3207
+ const clipEnd = readAttribute2(attributes, SSML_ATTRS2.CLIP_END);
3208
+ const speed = readAttribute2(attributes, SSML_ATTRS2.SPEED);
3209
+ const repeatCount = readAttribute2(attributes, SSML_ATTRS2.REPEAT_COUNT);
3210
+ const repeatDuration = readAttribute2(attributes, SSML_ATTRS2.REPEAT_DURATION);
3211
+ const soundLevel = readAttribute2(attributes, SSML_ATTRS2.SOUND_LEVEL);
3212
+ if (src !== void 0) element.src = src;
3213
+ if (desc !== void 0) element.desc = desc;
3214
+ if (clipBegin !== void 0) element.clipBegin = clipBegin;
3215
+ if (clipEnd !== void 0) element.clipEnd = clipEnd;
3216
+ if (speed !== void 0) element.speed = speed;
3217
+ if (repeatCount !== void 0) element.repeatCount = repeatCount;
3218
+ if (repeatDuration !== void 0) element.repeatDuration = repeatDuration;
3219
+ if (soundLevel !== void 0) element.soundLevel = soundLevel;
3220
+ return finishElement2(element, node, attributes);
3221
+ }
3222
+ case SSML_TAGS2.SUB: {
3223
+ const element = { type: SSML_TAGS2.SUB };
3224
+ const alias = readAttribute2(attributes, SSML_ATTRS2.ALIAS);
3225
+ if (alias !== void 0) element.alias = alias;
3226
+ return finishElement2(element, node, attributes);
3227
+ }
3228
+ case SSML_TAGS2.LANG: {
3229
+ const element = { type: SSML_TAGS2.LANG };
3230
+ const lang = readAttribute2(attributes, SSML_ATTRS2.XML_LANG, SSML_ATTRS2.LANG);
3231
+ if (lang !== void 0) element.lang = lang;
3232
+ return finishElement2(element, node, attributes);
3233
+ }
3234
+ case SSML_TAGS2.MARK: {
3235
+ const element = { type: SSML_TAGS2.MARK };
3236
+ const name = readAttribute2(attributes, SSML_ATTRS2.NAME);
3237
+ if (name !== void 0) element.name = name;
3238
+ return finishElement2(element, node, attributes);
3239
+ }
3240
+ case SSML_TAGS2.BOOKMARK: {
3241
+ const element = { type: SSML_TAGS2.BOOKMARK };
3242
+ const mark = readAttribute2(attributes, SSML_ATTRS2.MARK);
3243
+ if (mark !== void 0) element.mark = mark;
3244
+ return finishElement2(element, node, attributes);
3245
+ }
3246
+ case SSML_TAGS2.LEXICON: {
3247
+ const element = { type: SSML_TAGS2.LEXICON };
3248
+ const uri = readAttribute2(attributes, SSML_ATTRS2.URI);
3249
+ if (uri !== void 0) element.uri = uri;
3250
+ return finishElement2(element, node, attributes);
3251
+ }
3252
+ case SSML_TAGS2.PARAGRAPH: {
3253
+ const element = { type: SSML_TAGS2.PARAGRAPH };
3254
+ return finishElement2(element, node, attributes);
3255
+ }
3256
+ case SSML_TAGS2.SENTENCE: {
3257
+ const element = { type: SSML_TAGS2.SENTENCE };
3258
+ return finishElement2(element, node, attributes);
3259
+ }
3260
+ case SSML_TAGS2.WORD: {
3261
+ const element = { type: SSML_TAGS2.WORD };
3262
+ return finishElement2(element, node, attributes);
3263
+ }
3264
+ case SSML_TAGS2.MSTTS_SILENCE:
3265
+ case SSML_TAGS2.SILENCE: {
3266
+ const element = {
3267
+ type: node.name === SSML_TAGS2.MSTTS_SILENCE ? SSML_TAGS2.MSTTS_SILENCE : SSML_TAGS2.SILENCE
3268
+ };
3269
+ const typeValue = readAttribute2(attributes, SSML_ATTRS2.TYPE);
3270
+ const value = readAttribute2(attributes, SSML_ATTRS2.VALUE);
3271
+ if (typeValue !== void 0) element.typeValue = typeValue;
3272
+ if (value !== void 0) element.value = value;
3273
+ return finishElement2(element, node, attributes);
3274
+ }
3275
+ case SSML_TAGS2.MSTTS_VISEME:
3276
+ case SSML_TAGS2.VISEME: {
3277
+ const element = {
3278
+ type: node.name === SSML_TAGS2.MSTTS_VISEME ? SSML_TAGS2.MSTTS_VISEME : SSML_TAGS2.VISEME
3279
+ };
3280
+ const typeValue = readAttribute2(attributes, SSML_ATTRS2.TYPE);
3281
+ if (typeValue !== void 0) element.typeValue = typeValue;
3282
+ return finishElement2(element, node, attributes);
3283
+ }
3284
+ case SSML_TAGS2.MSTTS_AUDIO_DURATION: {
3285
+ const element = { type: SSML_TAGS2.MSTTS_AUDIO_DURATION };
3286
+ const value = readAttribute2(attributes, SSML_ATTRS2.VALUE);
3287
+ if (value !== void 0) element.value = value;
3288
+ return finishElement2(element, node, attributes);
3289
+ }
3290
+ case SSML_TAGS2.MSTTS_DIALOG: {
3291
+ const element = { type: SSML_TAGS2.MSTTS_DIALOG };
3292
+ return finishElement2(element, node, attributes);
3293
+ }
3294
+ case SSML_TAGS2.MSTTS_TURN: {
3295
+ const element = { type: SSML_TAGS2.MSTTS_TURN };
3296
+ const voice = readAttribute2(attributes, SSML_ATTRS2.VOICE);
3297
+ const speaker = readAttribute2(attributes, SSML_ATTRS2.SPEAKER);
3298
+ if (voice !== void 0) element.voice = voice;
3299
+ if (speaker !== void 0) element.speaker = speaker;
3300
+ return finishElement2(element, node, attributes);
3301
+ }
3302
+ case SSML_TAGS2.MSTTS_BACKGROUND_AUDIO: {
3303
+ const element = { type: SSML_TAGS2.MSTTS_BACKGROUND_AUDIO };
3304
+ const src = readAttribute2(attributes, SSML_ATTRS2.SRC);
3305
+ const volume = readAttribute2(attributes, SSML_ATTRS2.VOLUME);
3306
+ const fadeIn = readAttribute2(attributes, SSML_ATTRS2.FADE_IN);
3307
+ const fadeOut = readAttribute2(attributes, SSML_ATTRS2.FADE_OUT);
3308
+ if (src !== void 0) element.src = src;
3309
+ if (volume !== void 0) element.volume = volume;
3310
+ if (fadeIn !== void 0) element.fadeIn = fadeIn;
3311
+ if (fadeOut !== void 0) element.fadeOut = fadeOut;
3312
+ return finishElement2(element, node, attributes);
3313
+ }
3314
+ case SSML_TAGS2.MSTTS_TTS_EMBEDDING: {
3315
+ const element = { type: SSML_TAGS2.MSTTS_TTS_EMBEDDING };
3316
+ const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
3317
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
3318
+ return finishElement2(element, node, attributes);
3319
+ }
3320
+ case SSML_TAGS2.MSTTS_EMBEDDING: {
3321
+ const element = { type: SSML_TAGS2.MSTTS_EMBEDDING };
3322
+ const id = readAttribute2(attributes, SSML_ATTRS2.ID);
3323
+ const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
3324
+ if (id !== void 0) element.id = id;
3325
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
3326
+ return finishElement2(element, node, attributes);
3327
+ }
3328
+ case SSML_TAGS2.MSTTS_VOICE_CONVERSION: {
3329
+ const element = { type: SSML_TAGS2.MSTTS_VOICE_CONVERSION };
3330
+ const url = readAttribute2(attributes, SSML_ATTRS2.URL);
3331
+ const profile = readAttribute2(attributes, SSML_ATTRS2.PROFILE);
3332
+ const speakerProfileId = readAttribute2(attributes, SSML_ATTRS2.SPEAKER_PROFILE_ID);
3333
+ if (url !== void 0) element.url = url;
3334
+ if (profile !== void 0) element.profile = profile;
3335
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
3336
+ return finishElement2(element, node, attributes);
3337
+ }
3338
+ default: {
3339
+ const element = {
3340
+ name: node.name,
3341
+ type: "custom"
3342
+ };
3343
+ return finishElement2(element, node, attributes);
3344
+ }
3345
+ }
3346
+ }
3347
+ function convertNode2(node) {
3348
+ return typeof node === "string" ? node : convertElement2(node);
3349
+ }
3350
+ function parseSsml2(xmlString) {
3351
+ if (typeof xmlString !== "string") {
3352
+ throw new TypeError("SSML input must be a string");
3353
+ }
3354
+ const root = new XmlParser2(xmlString).parse();
3355
+ if (root.name !== SSML_TAGS2.SPEAK) {
3356
+ throw new Error(`SSML root element must be <${SSML_TAGS2.SPEAK}>, found <${root.name}>`);
3357
+ }
3358
+ const attributes = { ...root.attributes };
3359
+ const version = readAttribute2(attributes, SSML_ATTRS2.VERSION);
3360
+ const lang = readAttribute2(attributes, SSML_ATTRS2.XML_LANG, SSML_ATTRS2.LANG);
3361
+ if (version === void 0) {
3362
+ throw new Error(`SSML <${SSML_TAGS2.SPEAK}> element is missing the "${SSML_ATTRS2.VERSION}" attribute`);
3363
+ }
3364
+ if (lang === void 0) {
3365
+ throw new Error(`SSML <${SSML_TAGS2.SPEAK}> element is missing the "${SSML_ATTRS2.XML_LANG}" attribute`);
3366
+ }
3367
+ removeStandardNamespaceAttributes2(attributes);
3368
+ const document = {
3369
+ children: root.children.map(convertNode2),
3370
+ lang,
3371
+ type: SSML_TAGS2.SPEAK,
3372
+ version
3373
+ };
3374
+ if (Object.keys(attributes).length > 0) {
3375
+ document.attributes = attributes;
3376
+ }
3377
+ return document;
3378
+ }
3379
+ var AZURE_VOICE_DEFINITIONS2 = [
3380
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3381
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
3382
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
3383
+ {
3384
+ name: "en-US-GuyNeural",
3385
+ locale: "en-US",
3386
+ styles: [
3387
+ "angry",
3388
+ "cheerful",
3389
+ "excited",
3390
+ "friendly",
3391
+ "hopeful",
3392
+ "newscast",
3393
+ "sad",
3394
+ "shouting",
3395
+ "terrified",
3396
+ "unfriendly",
3397
+ "whispering"
3398
+ ]
3399
+ },
3400
+ {
3401
+ name: "en-US-JennyMultilingualNeural",
3402
+ locale: "en-US",
3403
+ styles: [
3404
+ "cheerful",
3405
+ "empathetic",
3406
+ "excited",
3407
+ "friendly",
3408
+ "hopeful",
3409
+ "sad",
3410
+ "shouting",
3411
+ "terrified",
3412
+ "unfriendly",
3413
+ "whispering"
3414
+ ]
3415
+ },
3416
+ {
3417
+ name: "en-US-JennyNeural",
3418
+ locale: "en-US",
3419
+ styles: [
3420
+ "assistant",
3421
+ "chat",
3422
+ "customerservice",
3423
+ "newscast",
3424
+ "cheerful",
3425
+ "empathetic",
3426
+ "excited",
3427
+ "friendly",
3428
+ "hopeful",
3429
+ "sad",
3430
+ "shouting",
3431
+ "terrified",
3432
+ "unfriendly",
3433
+ "whispering"
3434
+ ]
3435
+ },
3436
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
3437
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
3438
+ { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
3439
+ { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
3440
+ { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
3441
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3442
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
3443
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
3444
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
3445
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
3446
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
3447
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
3448
+ { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
3449
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" },
3450
+ { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
3451
+ {
3452
+ name: "ru-RU-SvetlanaNeural",
3453
+ locale: "ru-RU",
3454
+ styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
3455
+ },
3456
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
3457
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
3458
+ {
3459
+ name: "zh-CN-XiaoxiaoNeural",
3460
+ locale: "zh-CN",
3461
+ styles: [
3462
+ "assistant",
3463
+ "chat",
3464
+ "customerservice",
3465
+ "newscast",
3466
+ "cheerful",
3467
+ "empathetic",
3468
+ "excited",
3469
+ "friendly",
3470
+ "hopeful",
3471
+ "sad",
3472
+ "terrified",
3473
+ "whispering",
3474
+ "poetry-reading",
3475
+ "sports_commentary",
3476
+ "sports_commentary_excited",
3477
+ "story"
3478
+ ]
3479
+ },
3480
+ {
3481
+ name: "zh-CN-YunxiNeural",
3482
+ locale: "zh-CN",
3483
+ styles: [
3484
+ "narration-relaxed",
3485
+ "embarrassed",
3486
+ "fearful",
3487
+ "sad",
3488
+ "disgruntled",
3489
+ "serious",
3490
+ "angry",
3491
+ "depressed",
3492
+ "chat",
3493
+ "cheerful",
3494
+ "assistant"
3495
+ ]
3496
+ },
3497
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
3498
+ ];
3499
+ var ALLOWED_BREAK_STRENGTHS2 = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
3500
+ var ALLOWED_SAY_AS2 = /* @__PURE__ */ new Set([
3501
+ "characters",
3502
+ "spell-out",
3503
+ "cardinal",
3504
+ "ordinal",
3505
+ "number",
3506
+ "date",
3507
+ "time",
3508
+ "telephone",
3509
+ "fraction",
3510
+ "address",
3511
+ "name",
3512
+ "currency",
3513
+ "number_digit"
3514
+ ]);
3515
+ var ALLOWED_ROLES2 = /* @__PURE__ */ new Set([
3516
+ "Girl",
3517
+ "Boy",
3518
+ "YoungAdultFemale",
3519
+ "YoungAdultMale",
3520
+ "OlderAdultFemale",
3521
+ "OlderAdultMale",
3522
+ "SeniorFemale",
3523
+ "SeniorMale"
3524
+ ]);
3525
+ var ALLOWED_EMPHASIS_LEVELS2 = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
3526
+ var ALLOWED_SILENCE_TYPES2 = /* @__PURE__ */ new Set([
3527
+ "Leading",
3528
+ "Tailing",
3529
+ "Sentenceboundary",
3530
+ "Comma",
3531
+ "Semicolon",
3532
+ "Enumerationcomma"
3533
+ ]);
3534
+ var ALLOWED_VISEME_TYPES2 = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
3535
+ var DEFAULT_PREVIEW_TAGS2 = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
3536
+ function featureStatusForTag2(name, options) {
3537
+ const tagName = canonicalTagName2(name);
3538
+ const configured = Object.entries(options.tagStatuses ?? {}).find(
3539
+ ([candidate]) => canonicalTagName2(candidate) === tagName
3540
+ )?.[1];
3541
+ if (configured) return configured;
3542
+ if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS2]).some((candidate) => canonicalTagName2(candidate) === tagName))
3543
+ return "preview";
3544
+ if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName2(candidate) === tagName)) return "deprecated";
3545
+ return void 0;
3546
+ }
3547
+ function decodeAttribute2(value) {
3548
+ return value.replace(
3549
+ /&(?:amp|apos|gt|lt|quot);/gi,
3550
+ (entity) => ({ "&amp;": "&", "&apos;": "'", "&gt;": ">", "&lt;": "<", "&quot;": '"' })[entity.toLowerCase()] ?? entity
3551
+ );
3552
+ }
3553
+ function findTagEnd22(source, start) {
3554
+ let quote = "";
3555
+ for (let index = start; index < source.length; index += 1) {
3556
+ const character = source[index];
3557
+ if (quote) {
3558
+ if (character === quote) quote = "";
3559
+ } else if (character === '"' || character === "'") quote = character;
3560
+ else if (character === ">") return index;
3561
+ }
3562
+ return source.length - 1;
3563
+ }
3564
+ function tokenizeElements2(source) {
3565
+ const tokens = [];
3566
+ const openElements = [];
3567
+ let index = 0;
3568
+ while (index < source.length) {
3569
+ const start = source.indexOf("<", index);
3570
+ if (start === -1) break;
3571
+ if (source.startsWith("<!--", start)) {
3572
+ const end2 = source.indexOf("-->", start + 4);
3573
+ index = end2 === -1 ? source.length : end2 + 3;
3574
+ continue;
3575
+ }
3576
+ if (source.startsWith("<![CDATA[", start)) {
3577
+ const end2 = source.indexOf("]]>", start + 9);
3578
+ index = end2 === -1 ? source.length : end2 + 3;
3579
+ continue;
3580
+ }
3581
+ if (source.startsWith("<?", start)) {
3582
+ const end2 = source.indexOf("?>", start + 2);
3583
+ index = end2 === -1 ? source.length : end2 + 2;
3584
+ continue;
3585
+ }
3586
+ const end = findTagEnd22(source, start + 1);
3587
+ const raw = source.slice(start, end + 1);
3588
+ if (raw.startsWith("</")) {
3589
+ openElements.pop();
3590
+ index = end + 1;
3591
+ continue;
3592
+ }
3593
+ const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
3594
+ if (!nameMatch?.[1]) {
3595
+ index = end + 1;
3596
+ continue;
3597
+ }
3598
+ const attributes = /* @__PURE__ */ new Map();
3599
+ const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
3600
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
3601
+ for (const match of attributeSource.matchAll(attributePattern)) {
3602
+ attributes.set(match[1].toLowerCase(), decodeAttribute2(match[3]));
3603
+ }
3604
+ const selfClosing = /\/\s*>$/.test(raw);
3605
+ const parent = openElements[openElements.length - 1];
3606
+ const childElementIndex = parent?.childElementCount;
3607
+ if (parent) parent.childElementCount += 1;
3608
+ const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
3609
+ const tokenName = nameMatch[1];
3610
+ const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
3611
+ tokens.push({
3612
+ attributes,
3613
+ childElementIndex,
3614
+ end,
3615
+ depth: openElements.length + 1,
3616
+ name: tokenName,
3617
+ parentName: parent?.name,
3618
+ parentVoiceName,
3619
+ selfClosing,
3620
+ start
3621
+ });
3622
+ if (!selfClosing) {
3623
+ openElements.push({
3624
+ childElementCount: 0,
3625
+ name: tokenName,
3626
+ voiceName: tokenVoiceName
3627
+ });
3628
+ }
3629
+ index = end + 1;
3630
+ }
3631
+ return tokens;
3632
+ }
3633
+ function location2(source, offset) {
3634
+ const before = source.slice(0, Math.max(0, offset));
3635
+ const line = before.split("\n").length;
3636
+ return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
3637
+ }
3638
+ function addDiagnostic2(diagnostics, source, offset, message, severity = "error", code) {
3639
+ diagnostics.push({
3640
+ ...location2(source, offset),
3641
+ message,
3642
+ severity,
3643
+ source: "ssml-static-validator",
3644
+ ...code ? { code } : {}
3645
+ });
3646
+ }
3647
+ function isSupportedProsodyRate2(value) {
3648
+ const trimmed = value.trim();
3649
+ if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
3650
+ const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
3651
+ if (!multiplier) return false;
3652
+ const numericValue = Number(multiplier[1]);
3653
+ return numericValue >= 0.5 && numericValue <= 2;
3654
+ }
3655
+ function isValidAzureAudioDuration2(value) {
3656
+ const trimmed = value.trim();
3657
+ const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
3658
+ if (numeric) return Number(numeric[1]) > 0;
3659
+ const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
3660
+ if (!clock) return false;
3661
+ return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
3662
+ }
3663
+ function isValidAzureBackgroundAudioDuration2(value) {
3664
+ const match = /^(\d+)$/.exec(value.trim());
3665
+ if (!match) return false;
3666
+ const milliseconds = Number(match[1]);
3667
+ return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
3668
+ }
3669
+ function attr2(token, name) {
3670
+ return token.attributes.get(name.toLowerCase());
3671
+ }
3672
+ var DEFAULT_LANGUAGE_ALIASES2 = {
3673
+ "zh-CN": ["zh-Hans"],
3674
+ "zh-TW": ["zh-Hant"]
3675
+ };
3676
+ function canonicalLanguageTag2(language) {
3677
+ const trimmed = language.trim();
3678
+ if (!trimmed) return "";
3679
+ try {
3680
+ return new Intl.Locale(trimmed).toString().toLowerCase();
3681
+ } catch {
3682
+ return trimmed.toLowerCase();
3683
+ }
3684
+ }
3685
+ function createLanguageNormalizer2(options) {
3686
+ const aliases = /* @__PURE__ */ new Map();
3687
+ const addAliasGroup = (canonical, values) => {
3688
+ const normalizedCanonical = canonicalLanguageTag2(canonical);
3689
+ if (!normalizedCanonical) return;
3690
+ aliases.set(normalizedCanonical, normalizedCanonical);
3691
+ for (const value of values) {
3692
+ const normalizedValue = canonicalLanguageTag2(value);
3693
+ if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
3694
+ }
3695
+ };
3696
+ for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES2)) addAliasGroup(canonical, values);
3697
+ for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
3698
+ addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
3699
+ return (language) => {
3700
+ const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
3701
+ const normalized = canonicalLanguageTag2(customValue);
3702
+ return aliases.get(normalized) ?? normalized;
3703
+ };
3704
+ }
3705
+ function voiceLocalePrefix2(voiceName) {
3706
+ const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
3707
+ if (!match?.groups) return void 0;
3708
+ const tag = `${match.groups.language}-${match.groups.region}`;
3709
+ return {
3710
+ language: match.groups.language.toLowerCase(),
3711
+ region: match.groups.region.toLowerCase(),
3712
+ tag
3713
+ };
3714
+ }
3715
+ function definitionFromStyleMap2(voiceName, styles) {
3716
+ return {
3717
+ name: voiceName,
3718
+ locale: voiceLocalePrefix2(voiceName)?.tag ?? "",
3719
+ styles
3720
+ };
3721
+ }
3722
+ function normalizeVoiceCatalog2(options) {
3723
+ const definitions = /* @__PURE__ */ new Map();
3724
+ for (const definition of AZURE_VOICE_DEFINITIONS2) definitions.set(definition.name.toLowerCase(), definition);
3725
+ for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
3726
+ for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
3727
+ for (const definition of options.customVoiceDefinitions ?? [])
3728
+ definitions.set(definition.name.toLowerCase(), definition);
3729
+ for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
3730
+ const key = voiceName.toLowerCase();
3731
+ const current = definitions.get(key);
3732
+ definitions.set(key, {
3733
+ ...current ?? definitionFromStyleMap2(voiceName, styles),
3734
+ name: current?.name ?? voiceName,
3735
+ styles: styles.map((style) => style.toLowerCase())
3736
+ });
3737
+ }
3738
+ return definitions;
3739
+ }
3740
+ function diagnosticSeverity2(policy) {
3741
+ if (policy === "ignore") return void 0;
3742
+ return policy === "error" ? "error" : "warning";
3743
+ }
3744
+ function languagePart2(language) {
3745
+ try {
3746
+ return new Intl.Locale(language).language.toLowerCase();
3747
+ } catch {
3748
+ return language.split("-")[0]?.toLowerCase() ?? "";
3749
+ }
3750
+ }
3751
+ function definitionMatchesLanguage2(definition, voiceName, language, normalizeLanguage) {
3752
+ const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix2(voiceName)?.tag ?? ""];
3753
+ if (candidateLanguages.length === 0 || !language.trim()) return void 0;
3754
+ const normalizedLanguage = normalizeLanguage(language);
3755
+ const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
3756
+ if (normalizedCandidates.includes(normalizedLanguage)) return true;
3757
+ if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
3758
+ return normalizedLanguage === languagePart2(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart2(candidate) === normalizedLanguage) : false;
3759
+ }
3760
+ function canonicalTagName2(name) {
3761
+ const normalized = name.toLowerCase();
3762
+ if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
3763
+ if (normalized === "sayas") return "say-as";
3764
+ return normalized;
3765
+ }
3766
+ function validateVoiceFeatureMatrix2(token, source, diagnostics, voiceName, definition) {
3767
+ if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
3768
+ return;
3769
+ const tagName = canonicalTagName2(token.name);
3770
+ const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName2));
3771
+ const supportedTags = definition.supportedTags?.map(canonicalTagName2);
3772
+ if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
3773
+ addDiagnostic2(
3774
+ diagnostics,
3775
+ source,
3776
+ token.start,
3777
+ `Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
3778
+ "error",
3779
+ "azure-unsupported-tag-for-voice"
3780
+ );
3781
+ }
3782
+ }
3783
+ function validateAudioSource2(token, source, diagnostics, options, elementName2) {
3784
+ const src = attr2(token, "src");
3785
+ if (!src) {
3786
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
3787
+ return;
3788
+ }
3789
+ let parsed;
3790
+ try {
3791
+ parsed = new URL(src);
3792
+ } catch {
3793
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
3794
+ return;
3795
+ }
3796
+ if (parsed.username || parsed.password)
3797
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
3798
+ if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
3799
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
3800
+ const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
3801
+ try {
3802
+ const configured = new URL(allowedOrigin);
3803
+ if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
3804
+ return false;
3805
+ return configured.origin === parsed.origin;
3806
+ } catch {
3807
+ return false;
3808
+ }
3809
+ }) ?? false;
3810
+ if (options.allowedAudioOrigins && !isAllowedOrigin)
3811
+ addDiagnostic2(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
3812
+ else if (!isAllowedOrigin && !options.allowExternalAudio)
3813
+ addDiagnostic2(
3814
+ diagnostics,
3815
+ source,
3816
+ token.start,
3817
+ `<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
3818
+ );
3819
+ }
3820
+ function validateElement2(token, source, diagnostics, voiceName, options, voiceCatalog) {
3821
+ const name = token.name.toLowerCase();
3822
+ const tagStatus = featureStatusForTag2(token.name, options);
3823
+ if (tagStatus === "preview")
3824
+ addDiagnostic2(
3825
+ diagnostics,
3826
+ source,
3827
+ token.start,
3828
+ `<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
3829
+ "warning",
3830
+ "azure-preview-tag"
3831
+ );
3832
+ if (tagStatus === "deprecated")
3833
+ addDiagnostic2(
3834
+ diagnostics,
3835
+ source,
3836
+ token.start,
3837
+ `<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
3838
+ "info",
3839
+ "azure-deprecated-tag"
3840
+ );
3841
+ if (name === "voice" && !attr2(token, "name")?.trim())
3842
+ addDiagnostic2(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
3843
+ if (name === "break") {
3844
+ const time = attr2(token, "time");
3845
+ const strength = attr2(token, "strength");
3846
+ if (!time && !strength)
3847
+ addDiagnostic2(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
3848
+ if (time && strength)
3849
+ addDiagnostic2(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
3850
+ if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
3851
+ addDiagnostic2(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
3852
+ if (strength && !ALLOWED_BREAK_STRENGTHS2.has(strength))
3853
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
3854
+ }
3855
+ if (name === "prosody") {
3856
+ const rate = attr2(token, "rate");
3857
+ const pitch = attr2(token, "pitch");
3858
+ const volume = attr2(token, "volume");
3859
+ if (rate && !isSupportedProsodyRate2(rate))
3860
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
3861
+ if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
3862
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
3863
+ if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
3864
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
3865
+ }
3866
+ if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
3867
+ const style = attr2(token, "style");
3868
+ if (!style?.trim())
3869
+ addDiagnostic2(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
3870
+ const degree = attr2(token, "styledegree") ?? attr2(token, "style-degree");
3871
+ if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
3872
+ addDiagnostic2(
3873
+ diagnostics,
3874
+ source,
3875
+ token.start,
3876
+ "<mstts:express-as styledegree> must be a number between 0.01 and 2."
3877
+ );
3878
+ const role = attr2(token, "role");
3879
+ if (role && !ALLOWED_ROLES2.has(role))
3880
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
3881
+ const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
3882
+ const supportedStyles = definition?.styles;
3883
+ const severity = diagnosticSeverity2(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
3884
+ if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
3885
+ addDiagnostic2(
3886
+ diagnostics,
3887
+ source,
3888
+ token.start,
3889
+ `Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
3890
+ severity,
3891
+ "azure-unsupported-style"
3892
+ );
3893
+ if (style && voiceName && !definition && severity)
3894
+ addDiagnostic2(
3895
+ diagnostics,
3896
+ source,
3897
+ token.start,
3898
+ `Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
3899
+ severity
3900
+ );
3901
+ }
3902
+ if (name === "say-as" || name === "sayas") {
3903
+ const interpretAs = attr2(token, "interpret-as");
3904
+ if (!interpretAs || !ALLOWED_SAY_AS2.has(interpretAs))
3905
+ addDiagnostic2(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
3906
+ }
3907
+ if (name === "phoneme" && (!attr2(token, "alphabet") || !attr2(token, "ph")))
3908
+ addDiagnostic2(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
3909
+ if (name === "emphasis" && attr2(token, "level") && !ALLOWED_EMPHASIS_LEVELS2.has(attr2(token, "level") ?? ""))
3910
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr2(token, "level")}".`);
3911
+ if (name === "sub" && !attr2(token, "alias")?.trim())
3912
+ addDiagnostic2(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
3913
+ if (name === "lang" && !attr2(token, "xml:lang")?.trim() && !attr2(token, "lang")?.trim())
3914
+ addDiagnostic2(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
3915
+ if (name === "mark" && !attr2(token, "name")?.trim())
3916
+ addDiagnostic2(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
3917
+ if (name === "bookmark" && !attr2(token, "mark")?.trim())
3918
+ addDiagnostic2(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
3919
+ if (name === "lexicon") {
3920
+ const uri = attr2(token, "uri");
3921
+ if (!uri) addDiagnostic2(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
3922
+ else {
3923
+ try {
3924
+ const parsed = new URL(uri);
3925
+ if (parsed.protocol !== "https:")
3926
+ addDiagnostic2(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
3927
+ } catch {
3928
+ addDiagnostic2(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
3929
+ }
3930
+ }
3931
+ }
3932
+ if (name === "mstts:silence") {
3933
+ const type = attr2(token, "type");
3934
+ const value = attr2(token, "value");
3935
+ if (!type || !ALLOWED_SILENCE_TYPES2.has(type))
3936
+ addDiagnostic2(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
3937
+ if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
3938
+ addDiagnostic2(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
3939
+ }
3940
+ if (name === "mstts:audioduration") {
3941
+ const value = attr2(token, "value");
3942
+ if (!value || !isValidAzureAudioDuration2(value))
3943
+ addDiagnostic2(
3944
+ diagnostics,
3945
+ source,
3946
+ token.start,
3947
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
3948
+ );
3949
+ if (!token.selfClosing)
3950
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
3951
+ }
3952
+ if (name === "mstts:viseme") {
3953
+ const type = attr2(token, "type");
3954
+ if (!type || !ALLOWED_VISEME_TYPES2.has(type))
3955
+ addDiagnostic2(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
3956
+ }
3957
+ if (name === "audio") {
3958
+ validateAudioSource2(token, source, diagnostics, options, "audio");
3959
+ }
3960
+ if (name === "mstts:turn") {
3961
+ if (!attr2(token, "voice")?.trim() && !attr2(token, "speaker")?.trim())
3962
+ addDiagnostic2(
3963
+ diagnostics,
3964
+ source,
3965
+ token.start,
3966
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
3967
+ );
3968
+ if (token.parentName?.toLowerCase() !== "mstts:dialog")
3969
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
3970
+ }
3971
+ if (name === "mstts:backgroundaudio") {
3972
+ validateAudioSource2(token, source, diagnostics, options, "mstts:backgroundaudio");
3973
+ const volume = attr2(token, "volume");
3974
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
3975
+ addDiagnostic2(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
3976
+ for (const [attribute, value] of [
3977
+ ["fadein", attr2(token, "fadein")],
3978
+ ["fadeout", attr2(token, "fadeout")]
3979
+ ]) {
3980
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration2(value))
3981
+ addDiagnostic2(
3982
+ diagnostics,
3983
+ source,
3984
+ token.start,
3985
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
3986
+ );
3987
+ }
3988
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
3989
+ addDiagnostic2(
3990
+ diagnostics,
3991
+ source,
3992
+ token.start,
3993
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
3994
+ );
3995
+ if (!token.selfClosing)
3996
+ addDiagnostic2(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
3997
+ }
3998
+ }
3999
+ function validateAzureSsmlStatic2(ssml, options = {}) {
4000
+ const diagnostics = [];
4001
+ if (typeof ssml !== "string") {
4002
+ return [
4003
+ {
4004
+ line: 1,
4005
+ column: 1,
4006
+ message: "SSML input must be a string",
4007
+ severity: "error",
4008
+ source: "ssml-static-validator"
4009
+ }
4010
+ ];
4011
+ }
4012
+ const maxLength = options.maxLength ?? 1e4;
4013
+ if (ssml.length > maxLength)
4014
+ addDiagnostic2(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
4015
+ if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
4016
+ addDiagnostic2(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
4017
+ }
4018
+ try {
4019
+ parseSsml2(ssml);
4020
+ } catch (error) {
4021
+ const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
4022
+ const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
4023
+ addDiagnostic2(diagnostics, ssml, match ? Number(match[1]) : 0, message);
4024
+ return diagnostics;
4025
+ }
4026
+ const tokens = tokenizeElements2(ssml);
4027
+ if (options.maxXmlDepth !== void 0) {
4028
+ for (const token of tokens) {
4029
+ if (token.depth > options.maxXmlDepth) {
4030
+ addDiagnostic2(
4031
+ diagnostics,
4032
+ ssml,
4033
+ token.start,
4034
+ `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
4035
+ );
4036
+ }
4037
+ }
4038
+ }
4039
+ const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
4040
+ const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
4041
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
4042
+ for (const [index, token] of backgroundAudioTokens.entries()) {
4043
+ if (index > 0)
4044
+ addDiagnostic2(
4045
+ diagnostics,
4046
+ ssml,
4047
+ token.start,
4048
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
4049
+ );
4050
+ }
4051
+ if (!speak || voices.length === 0)
4052
+ addDiagnostic2(
4053
+ diagnostics,
4054
+ ssml,
4055
+ speak?.start ?? 0,
4056
+ "Azure SSML requires at least one <voice> element under <speak>."
4057
+ );
4058
+ const voiceName = voices[0] ? attr2(voices[0], "name") : void 0;
4059
+ const voiceCatalog = normalizeVoiceCatalog2(options);
4060
+ const normalizeLanguage = createLanguageNormalizer2(options);
4061
+ const policySeverity = diagnosticSeverity2(options.unknownVoicePolicy ?? "warn");
4062
+ const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
4063
+ for (const token of voicesToValidate) {
4064
+ const name = attr2(token, "name")?.trim();
4065
+ const language = attr2(token, "xml:lang")?.trim() || (speak ? attr2(speak, "xml:lang")?.trim() : void 0);
4066
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
4067
+ if (name && definition?.status === "preview")
4068
+ addDiagnostic2(
4069
+ diagnostics,
4070
+ ssml,
4071
+ token.start,
4072
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
4073
+ "warning",
4074
+ "azure-preview-voice"
4075
+ );
4076
+ if (name && definition?.status === "deprecated")
4077
+ addDiagnostic2(
4078
+ diagnostics,
4079
+ ssml,
4080
+ token.start,
4081
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
4082
+ "info",
4083
+ "azure-deprecated-voice"
4084
+ );
4085
+ if (name && !definition && policySeverity)
4086
+ addDiagnostic2(
4087
+ diagnostics,
4088
+ ssml,
4089
+ token.start,
4090
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
4091
+ policySeverity,
4092
+ "azure-unknown-voice"
4093
+ );
4094
+ if (name && language && definitionMatchesLanguage2(definition, name, language, normalizeLanguage) === false)
4095
+ addDiagnostic2(
4096
+ diagnostics,
4097
+ ssml,
4098
+ token.start,
4099
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
4100
+ "warning",
4101
+ "azure-locale-mismatch"
4102
+ );
4103
+ }
4104
+ for (const token of tokens) {
4105
+ const tokenName = token.name.toLowerCase();
4106
+ const tokenVoiceName = tokenName === "voice" ? attr2(token, "name")?.trim() : tokenName === "mstts:turn" ? attr2(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
4107
+ validateElement2(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
4108
+ const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
4109
+ validateVoiceFeatureMatrix2(token, ssml, diagnostics, tokenVoiceName, definition);
4110
+ if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
4111
+ addDiagnostic2(
4112
+ diagnostics,
4113
+ ssml,
4114
+ token.start,
4115
+ `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
4116
+ "error",
4117
+ "azure-unsupported-model-for-voice"
4118
+ );
4119
+ }
4120
+ }
4121
+ return diagnostics;
4122
+ }
4123
+ function urlAttributes2(token) {
4124
+ const tag = canonicalTagName2(token.name);
4125
+ const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
4126
+ return attributes.flatMap((attribute) => {
4127
+ const value = attr2(token, attribute);
4128
+ return value === void 0 ? [] : [{ attribute, value }];
4129
+ });
4130
+ }
4131
+ function validateAzureSsml2(ssml, options = {}) {
4132
+ const diagnostics = validateAzureSsmlStatic2(ssml, options);
4133
+ const validator = options.urlValidator ?? options.customUrlValidator;
4134
+ if (!validator || typeof ssml !== "string") return diagnostics;
4135
+ let tokens;
4136
+ try {
4137
+ tokens = tokenizeElements2(ssml);
4138
+ } catch {
4139
+ return diagnostics;
4140
+ }
4141
+ const checks = tokens.flatMap(
4142
+ (token) => urlAttributes2(token).map(async ({ attribute, value }) => {
4143
+ try {
4144
+ const result = await validator(value, { tag: token.name, attribute });
4145
+ const valid = typeof result === "boolean" ? result : result.valid;
4146
+ if (!valid) {
4147
+ const reason = typeof result === "boolean" ? void 0 : result.reason;
4148
+ addDiagnostic2(
4149
+ diagnostics,
4150
+ ssml,
4151
+ token.start,
4152
+ `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
4153
+ );
4154
+ }
4155
+ } catch (error) {
4156
+ const reason = error instanceof Error ? error.message : String(error);
4157
+ addDiagnostic2(
4158
+ diagnostics,
4159
+ ssml,
4160
+ token.start,
4161
+ `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
4162
+ );
4163
+ }
4164
+ })
4165
+ );
4166
+ return Promise.all(checks).then(() => diagnostics);
4167
+ }
4168
+ var AZURE_VOICE_CATALOG_METADATA2 = {
4169
+ apiVersion: "2025-10-01",
4170
+ generatedAt: "2026-08-28T00:00:00.000Z",
4171
+ regions: [],
4172
+ voiceCount: AZURE_VOICE_DEFINITIONS2.length
4173
+ };
4174
+
4175
+ // packages/azure-tts-client/src/safe.ts
4176
+ async function synthesizeSsmlSafe(client, ssml, options = {}) {
4177
+ const validationOptions = options.validation ?? options;
4178
+ const diagnostics = await Promise.resolve(validateAzureSsml2(ssml, validationOptions));
4179
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
4180
+ if (errors.length > 0) {
4181
+ return {
4182
+ ok: false,
4183
+ success: false,
4184
+ status: "validation-error",
4185
+ error: {
4186
+ kind: "validation",
4187
+ message: "SSML validation failed; the Azure Speech API was not called.",
4188
+ diagnostics: errors
4189
+ }
4190
+ };
4191
+ }
4192
+ try {
4193
+ return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
4194
+ } catch (error) {
4195
+ const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
4196
+ return { ok: false, success: false, status: "azure-api-error", error: azureError };
4197
+ }
4198
+ }
4199
+
4200
+ // packages/azure-tts-client/src/client.ts
4201
+ var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
4202
+ var _options;
4203
+ var AzureTtsClient = class {
4204
+ constructor(options) {
4205
+ __privateAdd(this, _options);
4206
+ __privateSet(this, _options, options);
4207
+ }
4208
+ async synthesize(ssml) {
4209
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4210
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4211
+ __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4212
+ const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
4213
+ return synthesizeSpeech(ssml, config);
4214
+ }
4215
+ async synthesizeSsml(ssml) {
4216
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4217
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4218
+ __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
4219
+ return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
4220
+ }
4221
+ async synthesizeChunks(chunks, options = {}) {
4222
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
4223
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
4224
+ return synthesizeSsmlChunks(chunks, {
4225
+ endpoint,
4226
+ region,
4227
+ subscriptionKey,
4228
+ outputFormat,
4229
+ signal,
4230
+ timeoutMs,
4231
+ onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
4232
+ });
4233
+ }
4234
+ async synthesizeSsmlSafe(ssml, options = {}) {
4235
+ return synthesizeSsmlSafe(this, ssml, options);
2537
4236
  }
2538
4237
  };
2539
4238
  _options = new WeakMap();
@@ -2631,11 +4330,14 @@ async function fetchAzureVoiceCatalog(options) {
2631
4330
  getBuiltInVoiceCatalogMetadata,
2632
4331
  isValidAzureAudioDuration,
2633
4332
  mapSsmlTextNodes,
4333
+ mergeSynthesisResults,
2634
4334
  normalizeAzureLanguage,
2635
4335
  parseSsml,
2636
4336
  splitSsmlDocument,
2637
4337
  synthesizeSpeech,
2638
4338
  synthesizeSsml,
4339
+ synthesizeSsmlChunks,
4340
+ synthesizeSsmlSafe,
2639
4341
  validateAzureSsml,
2640
4342
  validateSsml,
2641
4343
  validateSsmlStructureIntegrity