ssml-builder-js 2.11.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/core.mjs CHANGED
@@ -11,10 +11,11 @@ import {
11
11
  mapSsmlTextNodes,
12
12
  normalizeAzureLanguage,
13
13
  parseSsml,
14
+ splitSsmlDocument,
14
15
  validateAzureSsml,
15
16
  validateSsml,
16
17
  validateSsmlStructureIntegrity
17
- } from "./chunk-SNRI43QJ.mjs";
18
+ } from "./chunk-25LOR4AJ.mjs";
18
19
  import "./chunk-6S5ODO6A.mjs";
19
20
  export {
20
21
  areAzureLanguagesEquivalent,
@@ -29,6 +30,7 @@ export {
29
30
  mapSsmlTextNodes,
30
31
  normalizeAzureLanguage,
31
32
  parseSsml,
33
+ splitSsmlDocument,
32
34
  validateAzureSsml,
33
35
  validateSsml,
34
36
  validateSsmlStructureIntegrity
package/dist/elements.js CHANGED
@@ -1133,6 +1133,7 @@ function tokenizeElements(source) {
1133
1133
  attributes,
1134
1134
  childElementIndex,
1135
1135
  end,
1136
+ depth: openElements.length + 1,
1136
1137
  name: tokenName,
1137
1138
  parentName: parent?.name,
1138
1139
  parentVoiceName,
@@ -1313,13 +1314,18 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
1313
1314
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
1314
1315
  return;
1315
1316
  }
1317
+ if (parsed.username || parsed.password)
1318
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
1316
1319
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
1317
1320
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
1318
1321
  const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
1319
1322
  try {
1320
- return new URL(allowedOrigin).origin === parsed.origin;
1323
+ const configured = new URL(allowedOrigin);
1324
+ if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
1325
+ return false;
1326
+ return configured.origin === parsed.origin;
1321
1327
  } catch {
1322
- return allowedOrigin === parsed.origin;
1328
+ return false;
1323
1329
  }
1324
1330
  }) ?? false;
1325
1331
  if (options.allowedAudioOrigins && !isAllowedOrigin)
@@ -1511,7 +1517,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1511
1517
  addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
1512
1518
  }
1513
1519
  }
1514
- function validateAzureSsml(ssml, options = {}) {
1520
+ function validateAzureSsmlStatic(ssml, options = {}) {
1515
1521
  const diagnostics = [];
1516
1522
  if (typeof ssml !== "string") {
1517
1523
  return [
@@ -1527,6 +1533,9 @@ function validateAzureSsml(ssml, options = {}) {
1527
1533
  const maxLength = options.maxLength ?? 1e4;
1528
1534
  if (ssml.length > maxLength)
1529
1535
  addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
1536
+ if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
1537
+ addDiagnostic(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
1538
+ }
1530
1539
  try {
1531
1540
  parseSsml(ssml);
1532
1541
  } catch (error) {
@@ -1536,6 +1545,18 @@ function validateAzureSsml(ssml, options = {}) {
1536
1545
  return diagnostics;
1537
1546
  }
1538
1547
  const tokens = tokenizeElements(ssml);
1548
+ if (options.maxXmlDepth !== void 0) {
1549
+ for (const token of tokens) {
1550
+ if (token.depth > options.maxXmlDepth) {
1551
+ addDiagnostic(
1552
+ diagnostics,
1553
+ ssml,
1554
+ token.start,
1555
+ `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
1556
+ );
1557
+ }
1558
+ }
1559
+ }
1539
1560
  const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
1540
1561
  const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
1541
1562
  const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
@@ -1620,6 +1641,51 @@ function validateAzureSsml(ssml, options = {}) {
1620
1641
  }
1621
1642
  return diagnostics;
1622
1643
  }
1644
+ function urlAttributes(token) {
1645
+ const tag = canonicalTagName(token.name);
1646
+ const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
1647
+ return attributes.flatMap((attribute) => {
1648
+ const value = attr(token, attribute);
1649
+ return value === void 0 ? [] : [{ attribute, value }];
1650
+ });
1651
+ }
1652
+ function validateAzureSsml(ssml, options = {}) {
1653
+ const diagnostics = validateAzureSsmlStatic(ssml, options);
1654
+ const validator = options.urlValidator ?? options.customUrlValidator;
1655
+ if (!validator || typeof ssml !== "string") return diagnostics;
1656
+ let tokens;
1657
+ try {
1658
+ tokens = tokenizeElements(ssml);
1659
+ } catch {
1660
+ return diagnostics;
1661
+ }
1662
+ const checks = tokens.flatMap(
1663
+ (token) => urlAttributes(token).map(async ({ attribute, value }) => {
1664
+ try {
1665
+ const result = await validator(value, { tag: token.name, attribute });
1666
+ const valid = typeof result === "boolean" ? result : result.valid;
1667
+ if (!valid) {
1668
+ const reason = typeof result === "boolean" ? void 0 : result.reason;
1669
+ addDiagnostic(
1670
+ diagnostics,
1671
+ ssml,
1672
+ token.start,
1673
+ `<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
1674
+ );
1675
+ }
1676
+ } catch (error) {
1677
+ const reason = error instanceof Error ? error.message : String(error);
1678
+ addDiagnostic(
1679
+ diagnostics,
1680
+ ssml,
1681
+ token.start,
1682
+ `<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
1683
+ );
1684
+ }
1685
+ })
1686
+ );
1687
+ return Promise.all(checks).then(() => diagnostics);
1688
+ }
1623
1689
  var AZURE_VOICE_CATALOG_METADATA = {
1624
1690
  apiVersion: "2025-10-01",
1625
1691
  generatedAt: "2026-08-28T00:00:00.000Z",
@@ -3557,6 +3623,42 @@ var SSML_HOVER_COPY = {
3557
3623
  parameters: {
3558
3624
  value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
3559
3625
  }
3626
+ },
3627
+ "mstts:dialog": { title: "\u30C0\u30A4\u30A2\u30ED\u30B0", description: "\u8907\u6570\u306E\u8A71\u8005\u30BF\u30FC\u30F3\u3092\u307E\u3068\u3081\u307E\u3059\u3002", parameters: {} },
3628
+ "mstts:turn": {
3629
+ title: "\u8A71\u8005\u30BF\u30FC\u30F3",
3630
+ description: "\u30C0\u30A4\u30A2\u30ED\u30B0\u5185\u306E\u8A71\u8005\u3068\u767A\u8A71\u5185\u5BB9\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
3631
+ parameters: {
3632
+ voice: { title: "voice", description: "\u3053\u306E\u30BF\u30FC\u30F3\u3067\u4F7F\u7528\u3059\u308B Azure \u97F3\u58F0\u540D\u3002" },
3633
+ speaker: { title: "speaker", description: "\u8A71\u8005\u8B58\u5225\u5B50\u3002" }
3634
+ }
3635
+ },
3636
+ "mstts:backgroundaudio": {
3637
+ title: "\u80CC\u666F\u97F3\u58F0",
3638
+ description: "\u5408\u6210\u97F3\u58F0\u306E\u80CC\u5F8C\u3067\u518D\u751F\u3059\u308B\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
3639
+ parameters: { src: { title: "src", description: "\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u306E HTTPS URL\u3002" } }
3640
+ },
3641
+ "mstts:ttsembedding": {
3642
+ title: "\u97F3\u58F0\u57CB\u3081\u8FBC\u307F",
3643
+ description: "\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0\u307E\u305F\u306F\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB\u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u57CB\u3081\u8FBC\u307F\u307E\u3059\u3002",
3644
+ parameters: { speakerProfileId: { title: "speakerProfileId", description: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID\u3002" } }
3645
+ },
3646
+ "mstts:embedding": {
3647
+ title: "\u57CB\u3081\u8FBC\u307F",
3648
+ description: "\u8A71\u8005\u57CB\u3081\u8FBC\u307F\u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
3649
+ parameters: {
3650
+ id: { title: "id", description: "\u57CB\u3081\u8FBC\u307F ID\u3002" },
3651
+ speakerProfileId: { title: "speakerProfileId", description: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID\u3002" }
3652
+ }
3653
+ },
3654
+ "mstts:voiceconversion": {
3655
+ title: "\u97F3\u58F0\u5909\u63DB",
3656
+ description: "\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0\u306E\u5909\u63DB\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
3657
+ parameters: {
3658
+ url: { title: "url", description: "\u97F3\u58F0\u5909\u63DB\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB URL\u3002" },
3659
+ profile: { title: "profile", description: "\u5909\u63DB\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB\u3002" },
3660
+ speakerProfileId: { title: "speakerProfileId", description: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID\u3002" }
3661
+ }
3560
3662
  }
3561
3663
  }
3562
3664
  },
@@ -3721,6 +3823,42 @@ var SSML_HOVER_COPY = {
3721
3823
  parameters: {
3722
3824
  value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
3723
3825
  }
3826
+ },
3827
+ "mstts:dialog": { title: "Dialog", description: "Groups multiple speaker turns.", parameters: {} },
3828
+ "mstts:turn": {
3829
+ title: "Dialog turn",
3830
+ description: "Specifies a speaker and utterance within a dialog.",
3831
+ parameters: {
3832
+ voice: { title: "voice", description: "The Azure voice used by this turn." },
3833
+ speaker: { title: "speaker", description: "The speaker identifier." }
3834
+ }
3835
+ },
3836
+ "mstts:backgroundaudio": {
3837
+ title: "Background audio",
3838
+ description: "Specifies an audio file played behind synthesized speech.",
3839
+ parameters: { src: { title: "src", description: "The HTTPS URL of the audio file." } }
3840
+ },
3841
+ "mstts:ttsembedding": {
3842
+ title: "TTS embedding",
3843
+ description: "Embeds custom voice or speaker profile metadata.",
3844
+ parameters: { speakerProfileId: { title: "speakerProfileId", description: "The speaker profile ID." } }
3845
+ },
3846
+ "mstts:embedding": {
3847
+ title: "Embedding",
3848
+ description: "Specifies speaker embedding metadata.",
3849
+ parameters: {
3850
+ id: { title: "id", description: "The embedding ID." },
3851
+ speakerProfileId: { title: "speakerProfileId", description: "The speaker profile ID." }
3852
+ }
3853
+ },
3854
+ "mstts:voiceconversion": {
3855
+ title: "Voice conversion",
3856
+ description: "Specifies custom voice conversion metadata.",
3857
+ parameters: {
3858
+ url: { title: "url", description: "The voice conversion profile URL." },
3859
+ profile: { title: "profile", description: "The conversion profile." },
3860
+ speakerProfileId: { title: "speakerProfileId", description: "The speaker profile ID." }
3861
+ }
3724
3862
  }
3725
3863
  }
3726
3864
  }