ssml-builder-js 2.14.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/{chunk-UZSCXPC5.mjs → chunk-F2EMU3HM.mjs} +2 -2
- package/dist/{chunk-QFIBPCO4.mjs → chunk-HI74FTKY.mjs} +197 -21
- package/dist/chunk-HI74FTKY.mjs.map +1 -0
- package/dist/{chunk-AQ55MOPU.mjs → chunk-NKLZGITR.mjs} +354 -177
- package/dist/chunk-NKLZGITR.mjs.map +1 -0
- package/dist/core.d.mts +67 -17
- package/dist/core.d.ts +67 -17
- package/dist/core.js +353 -174
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +5 -1
- package/dist/elements.js +70 -20
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-CUXRwSw0.d.mts → index.d-DR5Qz43p.d.mts} +37 -2
- package/dist/{index.d-CUXRwSw0.d.ts → index.d-DR5Qz43p.d.ts} +37 -2
- package/dist/index.d.mts +162 -19
- package/dist/index.d.ts +162 -19
- package/dist/index.js +2060 -1128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +752 -174
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +11 -2
- package/dist/react.d.ts +11 -2
- package/dist/react.js +147 -32
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +79 -14
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AQ55MOPU.mjs.map +0 -1
- package/dist/chunk-QFIBPCO4.mjs.map +0 -1
- /package/dist/{chunk-UZSCXPC5.mjs.map → chunk-F2EMU3HM.mjs.map} +0 -0
package/dist/core.js
CHANGED
|
@@ -36,12 +36,14 @@ __export(core_exports, {
|
|
|
36
36
|
fromPlainTextToSsml: () => fromPlainTextToSsml,
|
|
37
37
|
getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
|
|
38
38
|
getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
|
|
39
|
+
getSsmlSourceMap: () => getSsmlSourceMap,
|
|
39
40
|
isValidAzureAudioDuration: () => isValidAzureAudioDuration,
|
|
40
41
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
41
42
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
42
43
|
parseSsml: () => parseSsml,
|
|
43
44
|
splitSsmlDocument: () => splitSsmlDocument,
|
|
44
45
|
validateAzureSsml: () => validateAzureSsml,
|
|
46
|
+
validateAzureSsmlChunks: () => validateAzureSsmlChunks,
|
|
45
47
|
validateSsml: () => validateSsml,
|
|
46
48
|
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
47
49
|
});
|
|
@@ -974,6 +976,236 @@ function buildPartialSsml(textOrOptions, context) {
|
|
|
974
976
|
return serializePartialSsml(textOrOptions.text, textOrOptions);
|
|
975
977
|
}
|
|
976
978
|
|
|
979
|
+
// packages/ssml-core/src/textNodes.ts
|
|
980
|
+
function decodeXmlText(value) {
|
|
981
|
+
return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
|
|
982
|
+
if (entity === "&") return "&";
|
|
983
|
+
if (entity === "'") return "'";
|
|
984
|
+
if (entity === ">") return ">";
|
|
985
|
+
if (entity === "<") return "<";
|
|
986
|
+
if (entity === """) return '"';
|
|
987
|
+
const hexadecimal = entity.toLowerCase().startsWith("&#x");
|
|
988
|
+
const digits = entity.slice(hexadecimal ? 3 : 2, -1);
|
|
989
|
+
return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function encodeXmlText(value) {
|
|
993
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
994
|
+
}
|
|
995
|
+
function decodeXmlAttribute(value) {
|
|
996
|
+
return decodeXmlText(value);
|
|
997
|
+
}
|
|
998
|
+
function findTagEnd(source, start) {
|
|
999
|
+
let quote = "";
|
|
1000
|
+
for (let index = start; index < source.length; index += 1) {
|
|
1001
|
+
const character = source[index];
|
|
1002
|
+
if (quote) {
|
|
1003
|
+
if (character === quote) quote = "";
|
|
1004
|
+
} else if (character === '"' || character === "'") {
|
|
1005
|
+
quote = character;
|
|
1006
|
+
} else if (character === ">") {
|
|
1007
|
+
return index;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return source.length - 1;
|
|
1011
|
+
}
|
|
1012
|
+
function readTagName(tag) {
|
|
1013
|
+
const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
|
|
1014
|
+
return match?.[1];
|
|
1015
|
+
}
|
|
1016
|
+
function readTagAttributes(tag, name) {
|
|
1017
|
+
const attributes = {};
|
|
1018
|
+
const nameStart = tag.indexOf(name);
|
|
1019
|
+
const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
|
|
1020
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
1021
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
1022
|
+
attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
|
|
1023
|
+
}
|
|
1024
|
+
return attributes;
|
|
1025
|
+
}
|
|
1026
|
+
function collectTextNodes(source) {
|
|
1027
|
+
const nodes = [];
|
|
1028
|
+
const elements = [];
|
|
1029
|
+
let index = 0;
|
|
1030
|
+
const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
|
|
1031
|
+
if (!rawText) return;
|
|
1032
|
+
const path = elements.map((element) => element.name);
|
|
1033
|
+
const parent = elements[elements.length - 1];
|
|
1034
|
+
nodes.push({
|
|
1035
|
+
context: {
|
|
1036
|
+
ancestorTags: path.slice(0, -1),
|
|
1037
|
+
parentAttributes: { ...parent?.attributes ?? {} },
|
|
1038
|
+
parentTag: parent?.name ?? "",
|
|
1039
|
+
path
|
|
1040
|
+
},
|
|
1041
|
+
decodedText: decodeXmlText(rawText),
|
|
1042
|
+
end,
|
|
1043
|
+
sourceEnd,
|
|
1044
|
+
sourceStart,
|
|
1045
|
+
start
|
|
1046
|
+
});
|
|
1047
|
+
};
|
|
1048
|
+
while (index < source.length) {
|
|
1049
|
+
if (source[index] !== "<") {
|
|
1050
|
+
const nextTag = source.indexOf("<", index);
|
|
1051
|
+
const end2 = nextTag === -1 ? source.length : nextTag;
|
|
1052
|
+
addText(index, end2, source.slice(index, end2));
|
|
1053
|
+
index = end2;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (source.startsWith("<!--", index)) {
|
|
1057
|
+
const end2 = source.indexOf("-->", index + 4);
|
|
1058
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (source.startsWith("<![CDATA[", index)) {
|
|
1062
|
+
const contentStart = index + 9;
|
|
1063
|
+
const end2 = source.indexOf("]]>", contentStart);
|
|
1064
|
+
const contentEnd = end2 === -1 ? source.length : end2;
|
|
1065
|
+
addText(
|
|
1066
|
+
contentStart,
|
|
1067
|
+
contentEnd,
|
|
1068
|
+
source.slice(contentStart, contentEnd),
|
|
1069
|
+
index,
|
|
1070
|
+
end2 === -1 ? source.length : end2 + 3
|
|
1071
|
+
);
|
|
1072
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (source.startsWith("<?", index)) {
|
|
1076
|
+
const end2 = source.indexOf("?>", index + 2);
|
|
1077
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (source.startsWith("</", index)) {
|
|
1081
|
+
const end2 = findTagEnd(source, index + 2);
|
|
1082
|
+
elements.pop();
|
|
1083
|
+
index = end2 + 1;
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
const end = findTagEnd(source, index + 1);
|
|
1087
|
+
const tag = source.slice(index, end + 1);
|
|
1088
|
+
const name = readTagName(tag);
|
|
1089
|
+
if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
|
|
1090
|
+
index = end + 1;
|
|
1091
|
+
}
|
|
1092
|
+
return nodes;
|
|
1093
|
+
}
|
|
1094
|
+
function collectSourceMap(source) {
|
|
1095
|
+
const segments = [];
|
|
1096
|
+
const markers = [];
|
|
1097
|
+
const elements = [];
|
|
1098
|
+
let textOffset = 0;
|
|
1099
|
+
let index = 0;
|
|
1100
|
+
const textParts = [];
|
|
1101
|
+
const addText = (value) => {
|
|
1102
|
+
if (!value) return;
|
|
1103
|
+
const parent = elements[elements.length - 1];
|
|
1104
|
+
if (parent) parent.nextChildIndex += 1;
|
|
1105
|
+
const sourceNodePath = parent?.path ?? ["speak"];
|
|
1106
|
+
const start = textOffset;
|
|
1107
|
+
textOffset += value.length;
|
|
1108
|
+
textParts.push(value);
|
|
1109
|
+
segments.push({ text: value, range: { start, end: textOffset }, sourceNodePath: [...sourceNodePath] });
|
|
1110
|
+
};
|
|
1111
|
+
while (index < source.length) {
|
|
1112
|
+
if (source[index] !== "<") {
|
|
1113
|
+
const end2 = source.indexOf("<", index);
|
|
1114
|
+
const textEnd = end2 === -1 ? source.length : end2;
|
|
1115
|
+
addText(decodeXmlText(source.slice(index, textEnd)));
|
|
1116
|
+
index = textEnd;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (source.startsWith("<!--", index)) {
|
|
1120
|
+
const end2 = source.indexOf("-->", index + 4);
|
|
1121
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (source.startsWith("<![CDATA[", index)) {
|
|
1125
|
+
const contentStart = index + 9;
|
|
1126
|
+
const end2 = source.indexOf("]]>", contentStart);
|
|
1127
|
+
const contentEnd = end2 === -1 ? source.length : end2;
|
|
1128
|
+
addText(source.slice(contentStart, contentEnd));
|
|
1129
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (source.startsWith("<?", index)) {
|
|
1133
|
+
const end2 = source.indexOf("?>", index + 2);
|
|
1134
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
const end = findTagEnd(source, index + 1);
|
|
1138
|
+
const rawTag = source.slice(index, end + 1);
|
|
1139
|
+
if (rawTag.startsWith("</")) {
|
|
1140
|
+
elements.pop();
|
|
1141
|
+
index = end + 1;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
const name = readTagName(rawTag);
|
|
1145
|
+
if (!name) {
|
|
1146
|
+
index = end + 1;
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
const parent = elements[elements.length - 1];
|
|
1150
|
+
const childIndex = parent?.nextChildIndex ?? 0;
|
|
1151
|
+
if (parent) parent.nextChildIndex += 1;
|
|
1152
|
+
const path = parent ? [...parent.path, `${name}[${childIndex}]`] : [name];
|
|
1153
|
+
const attributes = readTagAttributes(rawTag, name);
|
|
1154
|
+
const normalizedName = name.toLowerCase();
|
|
1155
|
+
if (normalizedName === "mark" || normalizedName === "bookmark") {
|
|
1156
|
+
const markerName = attributes[normalizedName === "mark" ? "name" : "mark"];
|
|
1157
|
+
if (markerName) {
|
|
1158
|
+
markers.push({
|
|
1159
|
+
kind: normalizedName,
|
|
1160
|
+
name: markerName,
|
|
1161
|
+
originalTextRange: { start: textOffset, end: textOffset },
|
|
1162
|
+
sourceNodePath: [...path]
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (!/\/\s*>$/.test(rawTag)) elements.push({ name, path, nextChildIndex: 0 });
|
|
1167
|
+
index = end + 1;
|
|
1168
|
+
}
|
|
1169
|
+
return { text: textParts.join(""), segments, markers };
|
|
1170
|
+
}
|
|
1171
|
+
function getSsmlSourceMap(ssml) {
|
|
1172
|
+
parseSsml(ssml);
|
|
1173
|
+
return collectSourceMap(ssml);
|
|
1174
|
+
}
|
|
1175
|
+
function extractSsmlText(ssml) {
|
|
1176
|
+
parseSsml(ssml);
|
|
1177
|
+
return collectTextNodes(ssml).map((node) => node.decodedText);
|
|
1178
|
+
}
|
|
1179
|
+
async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
1180
|
+
parseSsml(ssml);
|
|
1181
|
+
const nodes = collectTextNodes(ssml);
|
|
1182
|
+
const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
|
|
1183
|
+
const replacements = await Promise.all(
|
|
1184
|
+
nodes.map(async (node) => {
|
|
1185
|
+
const context = {
|
|
1186
|
+
ancestorTags: [...node.context.ancestorTags],
|
|
1187
|
+
parentAttributes: { ...node.context.parentAttributes },
|
|
1188
|
+
parentTag: node.context.parentTag,
|
|
1189
|
+
path: [...node.context.path]
|
|
1190
|
+
};
|
|
1191
|
+
const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
|
|
1192
|
+
if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
|
|
1193
|
+
const transformed = await transform(node.decodedText, context);
|
|
1194
|
+
if (typeof transformed !== "string") {
|
|
1195
|
+
throw new TypeError("SSML text node transform must return a string");
|
|
1196
|
+
}
|
|
1197
|
+
return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
|
|
1198
|
+
})
|
|
1199
|
+
);
|
|
1200
|
+
let result = "";
|
|
1201
|
+
let cursor = 0;
|
|
1202
|
+
nodes.forEach((node, nodeIndex) => {
|
|
1203
|
+
result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
|
|
1204
|
+
cursor = node.sourceEnd;
|
|
1205
|
+
});
|
|
1206
|
+
return result + ssml.slice(cursor);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
977
1209
|
// packages/ssml-core/src/split.ts
|
|
978
1210
|
var DEFAULT_MAX_LENGTH = 1e4;
|
|
979
1211
|
function cloneElement(element, children) {
|
|
@@ -1105,7 +1337,7 @@ function findSourceNodePath(nodes, targetOffset) {
|
|
|
1105
1337
|
});
|
|
1106
1338
|
return foundPath ?? firstPath;
|
|
1107
1339
|
}
|
|
1108
|
-
function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
|
|
1340
|
+
function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio, sourceMap, includeEndMarkers) {
|
|
1109
1341
|
const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
|
|
1110
1342
|
const text = nodes.map(textFromNode).join("");
|
|
1111
1343
|
const marks = [];
|
|
@@ -1121,7 +1353,23 @@ function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, re
|
|
|
1121
1353
|
hasBackgroundAudio: chunkNodes.some(
|
|
1122
1354
|
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1123
1355
|
),
|
|
1124
|
-
sourceNodePath: findSourceNodePath(document.children ?? [], textStart)
|
|
1356
|
+
sourceNodePath: findSourceNodePath(document.children ?? [], textStart),
|
|
1357
|
+
sourceTextSegments: sourceMap.segments.filter(({ range }) => range.end > textStart && range.start < textStart + text.length).map((segment) => {
|
|
1358
|
+
const start = Math.max(segment.range.start, textStart);
|
|
1359
|
+
const end = Math.min(segment.range.end, textStart + text.length);
|
|
1360
|
+
return {
|
|
1361
|
+
text: segment.text.slice(start - segment.range.start, end - segment.range.start),
|
|
1362
|
+
range: { start, end },
|
|
1363
|
+
sourceNodePath: [...segment.sourceNodePath]
|
|
1364
|
+
};
|
|
1365
|
+
}),
|
|
1366
|
+
sourceMarkers: sourceMap.markers.filter(
|
|
1367
|
+
({ originalTextRange }) => originalTextRange.start >= textStart && (originalTextRange.start < textStart + text.length || includeEndMarkers && originalTextRange.start === textStart + text.length)
|
|
1368
|
+
).map((marker) => ({
|
|
1369
|
+
...marker,
|
|
1370
|
+
originalTextRange: { ...marker.originalTextRange },
|
|
1371
|
+
sourceNodePath: [...marker.sourceNodePath]
|
|
1372
|
+
}))
|
|
1125
1373
|
};
|
|
1126
1374
|
}
|
|
1127
1375
|
function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
@@ -1131,11 +1379,12 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
|
1131
1379
|
throw new RangeError("maxLength must be a positive integer");
|
|
1132
1380
|
}
|
|
1133
1381
|
const document = parseSsml(ssml);
|
|
1382
|
+
const sourceMap = getSsmlSourceMap(ssml);
|
|
1134
1383
|
const backgroundAudio = (document.children ?? []).find(
|
|
1135
1384
|
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1136
1385
|
);
|
|
1137
1386
|
if (ssml.length <= resolvedMaxLength) {
|
|
1138
|
-
return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
|
|
1387
|
+
return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true, sourceMap, true)];
|
|
1139
1388
|
}
|
|
1140
1389
|
const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
|
|
1141
1390
|
const plainDocumentLength = documentWithChildren(document, []).length;
|
|
@@ -1159,7 +1408,16 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
|
1159
1408
|
}
|
|
1160
1409
|
if (group.length > 0) chunks.push(group);
|
|
1161
1410
|
if (chunks.length === 0) {
|
|
1162
|
-
const result = createChunk(
|
|
1411
|
+
const result = createChunk(
|
|
1412
|
+
document,
|
|
1413
|
+
[],
|
|
1414
|
+
0,
|
|
1415
|
+
0,
|
|
1416
|
+
backgroundAudio,
|
|
1417
|
+
resolvedOptions.replicateBackgroundAudio ?? false,
|
|
1418
|
+
sourceMap,
|
|
1419
|
+
true
|
|
1420
|
+
);
|
|
1163
1421
|
if (result.ssml.length > resolvedMaxLength) {
|
|
1164
1422
|
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1165
1423
|
}
|
|
@@ -1173,7 +1431,9 @@ function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
|
1173
1431
|
chunkIndex,
|
|
1174
1432
|
textStart,
|
|
1175
1433
|
backgroundAudio,
|
|
1176
|
-
resolvedOptions.replicateBackgroundAudio ?? false
|
|
1434
|
+
resolvedOptions.replicateBackgroundAudio ?? false,
|
|
1435
|
+
sourceMap,
|
|
1436
|
+
chunkIndex === chunks.length - 1
|
|
1177
1437
|
);
|
|
1178
1438
|
textStart = result.originalTextRange.end;
|
|
1179
1439
|
return result;
|
|
@@ -1196,155 +1456,6 @@ function validateSsml(xmlString) {
|
|
|
1196
1456
|
}
|
|
1197
1457
|
}
|
|
1198
1458
|
|
|
1199
|
-
// packages/ssml-core/src/textNodes.ts
|
|
1200
|
-
function decodeXmlText(value) {
|
|
1201
|
-
return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
|
|
1202
|
-
if (entity === "&") return "&";
|
|
1203
|
-
if (entity === "'") return "'";
|
|
1204
|
-
if (entity === ">") return ">";
|
|
1205
|
-
if (entity === "<") return "<";
|
|
1206
|
-
if (entity === """) return '"';
|
|
1207
|
-
const hexadecimal = entity.toLowerCase().startsWith("&#x");
|
|
1208
|
-
const digits = entity.slice(hexadecimal ? 3 : 2, -1);
|
|
1209
|
-
return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
function encodeXmlText(value) {
|
|
1213
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1214
|
-
}
|
|
1215
|
-
function decodeXmlAttribute(value) {
|
|
1216
|
-
return decodeXmlText(value);
|
|
1217
|
-
}
|
|
1218
|
-
function findTagEnd(source, start) {
|
|
1219
|
-
let quote = "";
|
|
1220
|
-
for (let index = start; index < source.length; index += 1) {
|
|
1221
|
-
const character = source[index];
|
|
1222
|
-
if (quote) {
|
|
1223
|
-
if (character === quote) quote = "";
|
|
1224
|
-
} else if (character === '"' || character === "'") {
|
|
1225
|
-
quote = character;
|
|
1226
|
-
} else if (character === ">") {
|
|
1227
|
-
return index;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
return source.length - 1;
|
|
1231
|
-
}
|
|
1232
|
-
function readTagName(tag) {
|
|
1233
|
-
const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
|
|
1234
|
-
return match?.[1];
|
|
1235
|
-
}
|
|
1236
|
-
function readTagAttributes(tag, name) {
|
|
1237
|
-
const attributes = {};
|
|
1238
|
-
const nameStart = tag.indexOf(name);
|
|
1239
|
-
const attributeSource = tag.slice(nameStart + name.length, tag.length - 1).replace(/\/\s*$/, "");
|
|
1240
|
-
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
1241
|
-
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
1242
|
-
attributes[match[1].toLowerCase()] = decodeXmlAttribute(match[3]);
|
|
1243
|
-
}
|
|
1244
|
-
return attributes;
|
|
1245
|
-
}
|
|
1246
|
-
function collectTextNodes(source) {
|
|
1247
|
-
const nodes = [];
|
|
1248
|
-
const elements = [];
|
|
1249
|
-
let index = 0;
|
|
1250
|
-
const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
|
|
1251
|
-
if (!rawText) return;
|
|
1252
|
-
const path = elements.map((element) => element.name);
|
|
1253
|
-
const parent = elements[elements.length - 1];
|
|
1254
|
-
nodes.push({
|
|
1255
|
-
context: {
|
|
1256
|
-
ancestorTags: path.slice(0, -1),
|
|
1257
|
-
parentAttributes: { ...parent?.attributes ?? {} },
|
|
1258
|
-
parentTag: parent?.name ?? "",
|
|
1259
|
-
path
|
|
1260
|
-
},
|
|
1261
|
-
decodedText: decodeXmlText(rawText),
|
|
1262
|
-
end,
|
|
1263
|
-
sourceEnd,
|
|
1264
|
-
sourceStart,
|
|
1265
|
-
start
|
|
1266
|
-
});
|
|
1267
|
-
};
|
|
1268
|
-
while (index < source.length) {
|
|
1269
|
-
if (source[index] !== "<") {
|
|
1270
|
-
const nextTag = source.indexOf("<", index);
|
|
1271
|
-
const end2 = nextTag === -1 ? source.length : nextTag;
|
|
1272
|
-
addText(index, end2, source.slice(index, end2));
|
|
1273
|
-
index = end2;
|
|
1274
|
-
continue;
|
|
1275
|
-
}
|
|
1276
|
-
if (source.startsWith("<!--", index)) {
|
|
1277
|
-
const end2 = source.indexOf("-->", index + 4);
|
|
1278
|
-
index = end2 === -1 ? source.length : end2 + 3;
|
|
1279
|
-
continue;
|
|
1280
|
-
}
|
|
1281
|
-
if (source.startsWith("<![CDATA[", index)) {
|
|
1282
|
-
const contentStart = index + 9;
|
|
1283
|
-
const end2 = source.indexOf("]]>", contentStart);
|
|
1284
|
-
const contentEnd = end2 === -1 ? source.length : end2;
|
|
1285
|
-
addText(
|
|
1286
|
-
contentStart,
|
|
1287
|
-
contentEnd,
|
|
1288
|
-
source.slice(contentStart, contentEnd),
|
|
1289
|
-
index,
|
|
1290
|
-
end2 === -1 ? source.length : end2 + 3
|
|
1291
|
-
);
|
|
1292
|
-
index = end2 === -1 ? source.length : end2 + 3;
|
|
1293
|
-
continue;
|
|
1294
|
-
}
|
|
1295
|
-
if (source.startsWith("<?", index)) {
|
|
1296
|
-
const end2 = source.indexOf("?>", index + 2);
|
|
1297
|
-
index = end2 === -1 ? source.length : end2 + 2;
|
|
1298
|
-
continue;
|
|
1299
|
-
}
|
|
1300
|
-
if (source.startsWith("</", index)) {
|
|
1301
|
-
const end2 = findTagEnd(source, index + 2);
|
|
1302
|
-
elements.pop();
|
|
1303
|
-
index = end2 + 1;
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
const end = findTagEnd(source, index + 1);
|
|
1307
|
-
const tag = source.slice(index, end + 1);
|
|
1308
|
-
const name = readTagName(tag);
|
|
1309
|
-
if (name && !/\/\s*>$/.test(tag)) elements.push({ attributes: readTagAttributes(tag, name), name });
|
|
1310
|
-
index = end + 1;
|
|
1311
|
-
}
|
|
1312
|
-
return nodes;
|
|
1313
|
-
}
|
|
1314
|
-
function extractSsmlText(ssml) {
|
|
1315
|
-
parseSsml(ssml);
|
|
1316
|
-
return collectTextNodes(ssml).map((node) => node.decodedText);
|
|
1317
|
-
}
|
|
1318
|
-
async function mapSsmlTextNodes(ssml, transform, options = {}) {
|
|
1319
|
-
parseSsml(ssml);
|
|
1320
|
-
const nodes = collectTextNodes(ssml);
|
|
1321
|
-
const skipTags = new Set((options.skipTags ?? ["phoneme", "say-as", "sub"]).map((tag) => tag.toLowerCase()));
|
|
1322
|
-
const replacements = await Promise.all(
|
|
1323
|
-
nodes.map(async (node) => {
|
|
1324
|
-
const context = {
|
|
1325
|
-
ancestorTags: [...node.context.ancestorTags],
|
|
1326
|
-
parentAttributes: { ...node.context.parentAttributes },
|
|
1327
|
-
parentTag: node.context.parentTag,
|
|
1328
|
-
path: [...node.context.path]
|
|
1329
|
-
};
|
|
1330
|
-
const shouldTransform = !skipTags.has(context.parentTag.toLowerCase()) && (options.filter?.(context) ?? true);
|
|
1331
|
-
if (!shouldTransform) return ssml.slice(node.sourceStart, node.sourceEnd);
|
|
1332
|
-
const transformed = await transform(node.decodedText, context);
|
|
1333
|
-
if (typeof transformed !== "string") {
|
|
1334
|
-
throw new TypeError("SSML text node transform must return a string");
|
|
1335
|
-
}
|
|
1336
|
-
return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
|
|
1337
|
-
})
|
|
1338
|
-
);
|
|
1339
|
-
let result = "";
|
|
1340
|
-
let cursor = 0;
|
|
1341
|
-
nodes.forEach((node, nodeIndex) => {
|
|
1342
|
-
result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
|
|
1343
|
-
cursor = node.sourceEnd;
|
|
1344
|
-
});
|
|
1345
|
-
return result + ssml.slice(cursor);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
1459
|
// packages/ssml-core/src/migration.ts
|
|
1349
1460
|
var DEFAULT_TRANSLATION_SKIP_TAGS = ["phoneme", "say-as", "sayAs", "sub"];
|
|
1350
1461
|
function elementName2(element) {
|
|
@@ -1765,34 +1876,51 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
|
|
|
1765
1876
|
const inFlight = /* @__PURE__ */ new Map();
|
|
1766
1877
|
const waiters = [];
|
|
1767
1878
|
let active = 0;
|
|
1768
|
-
const
|
|
1879
|
+
const configuredSignal = options.signal ?? new AbortController().signal;
|
|
1880
|
+
const acquire = async (signal) => {
|
|
1881
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1769
1882
|
if (active < concurrency) {
|
|
1770
1883
|
active += 1;
|
|
1771
1884
|
return;
|
|
1772
1885
|
}
|
|
1773
|
-
await new Promise((resolve) =>
|
|
1886
|
+
await new Promise((resolve, reject) => {
|
|
1887
|
+
let waiter;
|
|
1888
|
+
const abortHandler = () => {
|
|
1889
|
+
const index = waiters.indexOf(waiter);
|
|
1890
|
+
if (index >= 0) waiters.splice(index, 1);
|
|
1891
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1892
|
+
reject(new Error("URL validation was aborted."));
|
|
1893
|
+
};
|
|
1894
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1895
|
+
waiter = () => {
|
|
1896
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1897
|
+
resolve();
|
|
1898
|
+
};
|
|
1899
|
+
waiters.push(waiter);
|
|
1900
|
+
});
|
|
1774
1901
|
active += 1;
|
|
1775
1902
|
};
|
|
1776
1903
|
const release = () => {
|
|
1777
1904
|
active -= 1;
|
|
1778
1905
|
waiters.shift()?.();
|
|
1779
1906
|
};
|
|
1780
|
-
const check = async (url, context) => {
|
|
1781
|
-
if (
|
|
1782
|
-
const
|
|
1907
|
+
const check = async (url, context, signal = configuredSignal) => {
|
|
1908
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1909
|
+
const key = `${context.tag}:${context.attribute}:${url}`;
|
|
1910
|
+
const cached = cache.get(key);
|
|
1783
1911
|
if (cached !== void 0) return cached;
|
|
1784
|
-
const existing = inFlight.get(
|
|
1912
|
+
const existing = inFlight.get(key);
|
|
1785
1913
|
if (existing) return existing;
|
|
1786
1914
|
const promise = (async () => {
|
|
1787
|
-
await acquire();
|
|
1915
|
+
await acquire(signal);
|
|
1788
1916
|
try {
|
|
1789
|
-
if (
|
|
1790
|
-
const validation = Promise.resolve(validator(url, context));
|
|
1917
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1918
|
+
const validation = Promise.resolve(validator(url, context, signal));
|
|
1791
1919
|
let timer;
|
|
1792
1920
|
let abortHandler;
|
|
1793
1921
|
const cancellation = new Promise((_resolve, reject) => {
|
|
1794
1922
|
abortHandler = () => reject(new Error("URL validation was aborted."));
|
|
1795
|
-
|
|
1923
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1796
1924
|
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
1797
1925
|
timer = setTimeout(
|
|
1798
1926
|
() => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
|
|
@@ -1802,24 +1930,24 @@ function createAzureUrlValidatorRunner(validator, options = {}) {
|
|
|
1802
1930
|
});
|
|
1803
1931
|
try {
|
|
1804
1932
|
const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
|
|
1805
|
-
cache.set(
|
|
1933
|
+
cache.set(key, result);
|
|
1806
1934
|
return result;
|
|
1807
1935
|
} finally {
|
|
1808
1936
|
if (timer) clearTimeout(timer);
|
|
1809
|
-
if (abortHandler)
|
|
1937
|
+
if (abortHandler) signal.removeEventListener("abort", abortHandler);
|
|
1810
1938
|
}
|
|
1811
1939
|
} finally {
|
|
1812
1940
|
release();
|
|
1813
1941
|
}
|
|
1814
1942
|
})();
|
|
1815
|
-
inFlight.set(
|
|
1943
|
+
inFlight.set(key, promise);
|
|
1816
1944
|
try {
|
|
1817
1945
|
return await promise;
|
|
1818
1946
|
} finally {
|
|
1819
|
-
inFlight.delete(
|
|
1947
|
+
inFlight.delete(key);
|
|
1820
1948
|
}
|
|
1821
1949
|
};
|
|
1822
|
-
return (url, context) => check(url, context);
|
|
1950
|
+
return (url, context, signal) => check(url, context, signal ?? configuredSignal);
|
|
1823
1951
|
}
|
|
1824
1952
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1825
1953
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
@@ -1932,6 +2060,7 @@ function tokenizeElements(source) {
|
|
|
1932
2060
|
if (parent) parent.childElementCount += 1;
|
|
1933
2061
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1934
2062
|
const tokenName = nameMatch[1];
|
|
2063
|
+
const path = parent ? [...parent.path, `${tokenName}[${childElementIndex ?? 0}]`] : [tokenName];
|
|
1935
2064
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1936
2065
|
tokens.push({
|
|
1937
2066
|
attributes,
|
|
@@ -1942,12 +2071,14 @@ function tokenizeElements(source) {
|
|
|
1942
2071
|
parentName: parent?.name,
|
|
1943
2072
|
parentVoiceName,
|
|
1944
2073
|
selfClosing,
|
|
1945
|
-
start
|
|
2074
|
+
start,
|
|
2075
|
+
path
|
|
1946
2076
|
});
|
|
1947
2077
|
if (!selfClosing) {
|
|
1948
2078
|
openElements.push({
|
|
1949
2079
|
childElementCount: 0,
|
|
1950
2080
|
name: tokenName,
|
|
2081
|
+
path,
|
|
1951
2082
|
voiceName: tokenVoiceName
|
|
1952
2083
|
});
|
|
1953
2084
|
}
|
|
@@ -1960,13 +2091,14 @@ function location(source, offset) {
|
|
|
1960
2091
|
const line = before.split("\n").length;
|
|
1961
2092
|
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
1962
2093
|
}
|
|
1963
|
-
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
|
|
2094
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code, metadata = {}) {
|
|
1964
2095
|
diagnostics.push({
|
|
1965
2096
|
...location(source, offset),
|
|
1966
2097
|
message,
|
|
1967
2098
|
severity,
|
|
1968
2099
|
source: "ssml-static-validator",
|
|
1969
|
-
...code ? { code } : {}
|
|
2100
|
+
...code ? { code } : {},
|
|
2101
|
+
...metadata
|
|
1970
2102
|
});
|
|
1971
2103
|
}
|
|
1972
2104
|
function isSupportedProsodyRate(value) {
|
|
@@ -2439,9 +2571,11 @@ function validateAzureSsmlStatic(ssml, options = {}) {
|
|
|
2439
2571
|
for (const token of tokens) {
|
|
2440
2572
|
const tokenName = token.name.toLowerCase();
|
|
2441
2573
|
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
2574
|
+
const tokenDiagnosticStart = diagnostics.length;
|
|
2442
2575
|
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
2443
2576
|
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
2444
2577
|
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
2578
|
+
annotateTokenDiagnostics(diagnostics, tokenDiagnosticStart, token, options, tokenVoiceName);
|
|
2445
2579
|
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
2446
2580
|
addDiagnostic(
|
|
2447
2581
|
diagnostics,
|
|
@@ -2463,18 +2597,37 @@ function urlAttributes(token) {
|
|
|
2463
2597
|
return value === void 0 ? [] : [{ attribute, value }];
|
|
2464
2598
|
});
|
|
2465
2599
|
}
|
|
2600
|
+
function annotateTokenDiagnostics(diagnostics, startIndex, token, options, voiceName) {
|
|
2601
|
+
const attributes = [...token.attributes.keys()];
|
|
2602
|
+
for (const diagnostic of diagnostics.slice(startIndex)) {
|
|
2603
|
+
const attributeName = attributes.find(
|
|
2604
|
+
(attribute) => new RegExp(`(?:<[^> ]+\\s+|")${attribute}(?:"|>|\\s)`, "i").test(diagnostic.message)
|
|
2605
|
+
);
|
|
2606
|
+
const nodePath = options.sourceNodePath ? [...options.sourceNodePath] : [...token.path];
|
|
2607
|
+
Object.assign(diagnostic, {
|
|
2608
|
+
range: { start: token.start, end: token.end + 1 },
|
|
2609
|
+
tagName: token.name,
|
|
2610
|
+
...attributeName ? { attributeName } : {},
|
|
2611
|
+
...voiceName ? { voiceName } : {},
|
|
2612
|
+
...options.chunkIndex !== void 0 ? { chunkIndex: options.chunkIndex } : {},
|
|
2613
|
+
nodePath,
|
|
2614
|
+
targetNodePath: [...token.path]
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2466
2618
|
function validateAzureSsml(ssml, options = {}) {
|
|
2467
2619
|
const diagnostics = validateAzureSsmlStatic(ssml, options);
|
|
2468
2620
|
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2469
2621
|
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
2470
2622
|
const runnerOptions = options.urlValidation ?? {};
|
|
2471
|
-
const boundedValidator = createAzureUrlValidatorRunner(validator, {
|
|
2623
|
+
const boundedValidator = options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
|
|
2472
2624
|
...runnerOptions,
|
|
2473
2625
|
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
2474
2626
|
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
2475
2627
|
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
2476
2628
|
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
2477
2629
|
});
|
|
2630
|
+
const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
|
|
2478
2631
|
let tokens;
|
|
2479
2632
|
try {
|
|
2480
2633
|
tokens = tokenizeElements(ssml);
|
|
@@ -2484,30 +2637,54 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2484
2637
|
const checks = tokens.flatMap(
|
|
2485
2638
|
(token) => urlAttributes(token).map(async ({ attribute, value }) => {
|
|
2486
2639
|
try {
|
|
2487
|
-
const result = await boundedValidator(
|
|
2640
|
+
const result = await boundedValidator(
|
|
2641
|
+
value,
|
|
2642
|
+
{ tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
|
|
2643
|
+
validationSignal
|
|
2644
|
+
);
|
|
2488
2645
|
const valid = typeof result === "boolean" ? result : result.valid;
|
|
2489
2646
|
if (!valid) {
|
|
2490
2647
|
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
2648
|
+
const diagnosticStart = diagnostics.length;
|
|
2491
2649
|
addDiagnostic(
|
|
2492
2650
|
diagnostics,
|
|
2493
2651
|
ssml,
|
|
2494
2652
|
token.start,
|
|
2495
2653
|
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
2496
2654
|
);
|
|
2655
|
+
annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
2497
2656
|
}
|
|
2498
2657
|
} catch (error) {
|
|
2499
2658
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2659
|
+
const diagnosticStart = diagnostics.length;
|
|
2500
2660
|
addDiagnostic(
|
|
2501
2661
|
diagnostics,
|
|
2502
2662
|
ssml,
|
|
2503
2663
|
token.start,
|
|
2504
2664
|
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
2505
2665
|
);
|
|
2666
|
+
annotateTokenDiagnostics(diagnostics, diagnosticStart, token, options, token.parentVoiceName);
|
|
2506
2667
|
}
|
|
2507
2668
|
})
|
|
2508
2669
|
);
|
|
2509
2670
|
return Promise.all(checks).then(() => diagnostics);
|
|
2510
2671
|
}
|
|
2672
|
+
async function validateAzureSsmlChunks(chunks, options = {}) {
|
|
2673
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2674
|
+
const sharedOptions = validator ? {
|
|
2675
|
+
...options,
|
|
2676
|
+
urlValidatorRunner: options.urlValidatorRunner ?? createAzureUrlValidatorRunner(validator, {
|
|
2677
|
+
...options.urlValidation ?? {},
|
|
2678
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
2679
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
2680
|
+
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
2681
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
2682
|
+
})
|
|
2683
|
+
} : options;
|
|
2684
|
+
return Promise.all(
|
|
2685
|
+
chunks.map((chunk, chunkIndex) => Promise.resolve(validateAzureSsml(chunk, { ...sharedOptions, chunkIndex })))
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2511
2688
|
|
|
2512
2689
|
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2513
2690
|
var AZURE_VOICE_CATALOG_METADATA = {
|
|
@@ -2536,12 +2713,14 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
|
2536
2713
|
fromPlainTextToSsml,
|
|
2537
2714
|
getAzureVoiceCatalogMetadata,
|
|
2538
2715
|
getBuiltInVoiceCatalogMetadata,
|
|
2716
|
+
getSsmlSourceMap,
|
|
2539
2717
|
isValidAzureAudioDuration,
|
|
2540
2718
|
mapSsmlTextNodes,
|
|
2541
2719
|
normalizeAzureLanguage,
|
|
2542
2720
|
parseSsml,
|
|
2543
2721
|
splitSsmlDocument,
|
|
2544
2722
|
validateAzureSsml,
|
|
2723
|
+
validateAzureSsmlChunks,
|
|
2545
2724
|
validateSsml,
|
|
2546
2725
|
validateSsmlStructureIntegrity
|
|
2547
2726
|
});
|