ssml-builder-js 2.8.0 → 2.9.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 +95 -17
- package/dist/{chunk-TKD3PU5Y.mjs → chunk-7LCSH4SZ.mjs} +610 -124
- package/dist/chunk-7LCSH4SZ.mjs.map +1 -0
- package/dist/{chunk-4RQETJUI.mjs → chunk-JNJVTEL6.mjs} +939 -10
- package/dist/chunk-JNJVTEL6.mjs.map +1 -0
- package/dist/core.d.mts +97 -3
- package/dist/core.d.ts +97 -3
- package/dist/core.js +616 -124
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +15 -3
- package/dist/elements.d.mts +10 -1
- package/dist/elements.d.ts +10 -1
- package/dist/elements.js +1245 -15
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +316 -9
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +616 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +47 -6
- package/dist/react.d.ts +47 -6
- package/dist/react.js +1219 -25
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +280 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +4 -3
- package/dist/chunk-4RQETJUI.mjs.map +0 -1
- package/dist/chunk-TKD3PU5Y.mjs.map +0 -1
package/dist/elements.js
CHANGED
|
@@ -74,7 +74,14 @@ var SSML_TAGS = {
|
|
|
74
74
|
MSTTS_SILENCE: "mstts:silence",
|
|
75
75
|
SILENCE: "silence",
|
|
76
76
|
MSTTS_VISEME: "mstts:viseme",
|
|
77
|
-
VISEME: "viseme"
|
|
77
|
+
VISEME: "viseme",
|
|
78
|
+
MSTTS_AUDIO_DURATION: "mstts:audioduration",
|
|
79
|
+
MSTTS_DIALOG: "mstts:dialog",
|
|
80
|
+
MSTTS_TURN: "mstts:turn",
|
|
81
|
+
MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
|
|
82
|
+
MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
|
|
83
|
+
MSTTS_EMBEDDING: "mstts:embedding",
|
|
84
|
+
MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
|
|
78
85
|
};
|
|
79
86
|
var SSML_ATTRS = {
|
|
80
87
|
VERSION: "version",
|
|
@@ -83,6 +90,7 @@ var SSML_ATTRS = {
|
|
|
83
90
|
LANG: "lang",
|
|
84
91
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
85
92
|
NAME: "name",
|
|
93
|
+
VOICE: "voice",
|
|
86
94
|
EFFECT: "effect",
|
|
87
95
|
RATE: "rate",
|
|
88
96
|
PITCH: "pitch",
|
|
@@ -114,7 +122,9 @@ var SSML_ATTRS = {
|
|
|
114
122
|
MARK: "mark",
|
|
115
123
|
URI: "uri",
|
|
116
124
|
TYPE: "type",
|
|
117
|
-
VALUE: "value"
|
|
125
|
+
VALUE: "value",
|
|
126
|
+
FADE_IN: "fadein",
|
|
127
|
+
FADE_OUT: "fadeout"
|
|
118
128
|
};
|
|
119
129
|
function escapeText(value) {
|
|
120
130
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -201,6 +211,23 @@ function getAttributes(element) {
|
|
|
201
211
|
case SSML_TAGS.VISEME:
|
|
202
212
|
addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
|
|
203
213
|
break;
|
|
214
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
215
|
+
addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
|
|
216
|
+
break;
|
|
217
|
+
case SSML_TAGS.MSTTS_TURN:
|
|
218
|
+
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
219
|
+
break;
|
|
220
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
221
|
+
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
222
|
+
addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
|
|
223
|
+
addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
|
|
224
|
+
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
225
|
+
break;
|
|
226
|
+
case SSML_TAGS.MSTTS_DIALOG:
|
|
227
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
228
|
+
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
229
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
230
|
+
break;
|
|
204
231
|
case SSML_TAGS.PARAGRAPH:
|
|
205
232
|
case SSML_TAGS.SENTENCE:
|
|
206
233
|
case SSML_TAGS.WORD:
|
|
@@ -225,6 +252,8 @@ function getTagName(element) {
|
|
|
225
252
|
case SSML_TAGS.VISEME:
|
|
226
253
|
case SSML_TAGS.MSTTS_VISEME:
|
|
227
254
|
return SSML_TAGS.MSTTS_VISEME;
|
|
255
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION:
|
|
256
|
+
return SSML_TAGS.MSTTS_AUDIO_DURATION;
|
|
228
257
|
case "element":
|
|
229
258
|
case "custom":
|
|
230
259
|
return element.name;
|
|
@@ -757,6 +786,46 @@ function convertElement(node) {
|
|
|
757
786
|
if (typeValue !== void 0) element.typeValue = typeValue;
|
|
758
787
|
return finishElement(element, node, attributes);
|
|
759
788
|
}
|
|
789
|
+
case SSML_TAGS.MSTTS_AUDIO_DURATION: {
|
|
790
|
+
const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
|
|
791
|
+
const value = readAttribute(attributes, SSML_ATTRS.VALUE);
|
|
792
|
+
if (value !== void 0) element.value = value;
|
|
793
|
+
return finishElement(element, node, attributes);
|
|
794
|
+
}
|
|
795
|
+
case SSML_TAGS.MSTTS_DIALOG: {
|
|
796
|
+
const element = { type: SSML_TAGS.MSTTS_DIALOG };
|
|
797
|
+
return finishElement(element, node, attributes);
|
|
798
|
+
}
|
|
799
|
+
case SSML_TAGS.MSTTS_TURN: {
|
|
800
|
+
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
801
|
+
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
802
|
+
if (voice !== void 0) element.voice = voice;
|
|
803
|
+
return finishElement(element, node, attributes);
|
|
804
|
+
}
|
|
805
|
+
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
806
|
+
const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
|
|
807
|
+
const src = readAttribute(attributes, SSML_ATTRS.SRC);
|
|
808
|
+
const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
|
|
809
|
+
const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
|
|
810
|
+
const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
|
|
811
|
+
if (src !== void 0) element.src = src;
|
|
812
|
+
if (volume !== void 0) element.volume = volume;
|
|
813
|
+
if (fadeIn !== void 0) element.fadeIn = fadeIn;
|
|
814
|
+
if (fadeOut !== void 0) element.fadeOut = fadeOut;
|
|
815
|
+
return finishElement(element, node, attributes);
|
|
816
|
+
}
|
|
817
|
+
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
818
|
+
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
819
|
+
return finishElement(element, node, attributes);
|
|
820
|
+
}
|
|
821
|
+
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
822
|
+
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
823
|
+
return finishElement(element, node, attributes);
|
|
824
|
+
}
|
|
825
|
+
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
826
|
+
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
827
|
+
return finishElement(element, node, attributes);
|
|
828
|
+
}
|
|
760
829
|
default: {
|
|
761
830
|
const element = {
|
|
762
831
|
name: node.name,
|
|
@@ -798,6 +867,644 @@ function parseSsml(xmlString) {
|
|
|
798
867
|
}
|
|
799
868
|
return document2;
|
|
800
869
|
}
|
|
870
|
+
var AZURE_VOICE_DEFINITIONS = [
|
|
871
|
+
{ name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
872
|
+
{ name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
|
|
873
|
+
{ name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
|
|
874
|
+
{
|
|
875
|
+
name: "en-US-GuyNeural",
|
|
876
|
+
locale: "en-US",
|
|
877
|
+
styles: [
|
|
878
|
+
"angry",
|
|
879
|
+
"cheerful",
|
|
880
|
+
"excited",
|
|
881
|
+
"friendly",
|
|
882
|
+
"hopeful",
|
|
883
|
+
"newscast",
|
|
884
|
+
"sad",
|
|
885
|
+
"shouting",
|
|
886
|
+
"terrified",
|
|
887
|
+
"unfriendly",
|
|
888
|
+
"whispering"
|
|
889
|
+
]
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: "en-US-JennyMultilingualNeural",
|
|
893
|
+
locale: "en-US",
|
|
894
|
+
styles: [
|
|
895
|
+
"cheerful",
|
|
896
|
+
"empathetic",
|
|
897
|
+
"excited",
|
|
898
|
+
"friendly",
|
|
899
|
+
"hopeful",
|
|
900
|
+
"sad",
|
|
901
|
+
"shouting",
|
|
902
|
+
"terrified",
|
|
903
|
+
"unfriendly",
|
|
904
|
+
"whispering"
|
|
905
|
+
]
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
name: "en-US-JennyNeural",
|
|
909
|
+
locale: "en-US",
|
|
910
|
+
styles: [
|
|
911
|
+
"assistant",
|
|
912
|
+
"chat",
|
|
913
|
+
"customerservice",
|
|
914
|
+
"newscast",
|
|
915
|
+
"cheerful",
|
|
916
|
+
"empathetic",
|
|
917
|
+
"excited",
|
|
918
|
+
"friendly",
|
|
919
|
+
"hopeful",
|
|
920
|
+
"sad",
|
|
921
|
+
"shouting",
|
|
922
|
+
"terrified",
|
|
923
|
+
"unfriendly",
|
|
924
|
+
"whispering"
|
|
925
|
+
]
|
|
926
|
+
},
|
|
927
|
+
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
928
|
+
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
929
|
+
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
930
|
+
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
931
|
+
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
932
|
+
{ name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
|
|
933
|
+
{ name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
|
|
934
|
+
{ name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
|
|
935
|
+
{ name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
|
|
936
|
+
{ name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
|
|
937
|
+
{ name: "ms-MY-YasminNeural", locale: "ms-MY" },
|
|
938
|
+
{ name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
|
|
939
|
+
{
|
|
940
|
+
name: "ru-RU-SvetlanaNeural",
|
|
941
|
+
locale: "ru-RU",
|
|
942
|
+
styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
943
|
+
},
|
|
944
|
+
{ name: "th-TH-PremwadeeNeural", locale: "th-TH" },
|
|
945
|
+
{ name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
|
|
946
|
+
{
|
|
947
|
+
name: "zh-CN-XiaoxiaoNeural",
|
|
948
|
+
locale: "zh-CN",
|
|
949
|
+
styles: [
|
|
950
|
+
"assistant",
|
|
951
|
+
"chat",
|
|
952
|
+
"customerservice",
|
|
953
|
+
"newscast",
|
|
954
|
+
"cheerful",
|
|
955
|
+
"empathetic",
|
|
956
|
+
"excited",
|
|
957
|
+
"friendly",
|
|
958
|
+
"hopeful",
|
|
959
|
+
"sad",
|
|
960
|
+
"terrified",
|
|
961
|
+
"whispering",
|
|
962
|
+
"poetry-reading",
|
|
963
|
+
"sports_commentary",
|
|
964
|
+
"sports_commentary_excited",
|
|
965
|
+
"story"
|
|
966
|
+
]
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: "zh-CN-YunxiNeural",
|
|
970
|
+
locale: "zh-CN",
|
|
971
|
+
styles: [
|
|
972
|
+
"narration-relaxed",
|
|
973
|
+
"embarrassed",
|
|
974
|
+
"fearful",
|
|
975
|
+
"sad",
|
|
976
|
+
"disgruntled",
|
|
977
|
+
"serious",
|
|
978
|
+
"angry",
|
|
979
|
+
"depressed",
|
|
980
|
+
"chat",
|
|
981
|
+
"cheerful",
|
|
982
|
+
"assistant"
|
|
983
|
+
]
|
|
984
|
+
},
|
|
985
|
+
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
986
|
+
];
|
|
987
|
+
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
988
|
+
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
989
|
+
"characters",
|
|
990
|
+
"spell-out",
|
|
991
|
+
"cardinal",
|
|
992
|
+
"ordinal",
|
|
993
|
+
"number",
|
|
994
|
+
"date",
|
|
995
|
+
"time",
|
|
996
|
+
"telephone",
|
|
997
|
+
"fraction",
|
|
998
|
+
"address",
|
|
999
|
+
"name",
|
|
1000
|
+
"currency",
|
|
1001
|
+
"number_digit"
|
|
1002
|
+
]);
|
|
1003
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set([
|
|
1004
|
+
"Girl",
|
|
1005
|
+
"Boy",
|
|
1006
|
+
"YoungAdultFemale",
|
|
1007
|
+
"YoungAdultMale",
|
|
1008
|
+
"OlderAdultFemale",
|
|
1009
|
+
"OlderAdultMale",
|
|
1010
|
+
"SeniorFemale",
|
|
1011
|
+
"SeniorMale"
|
|
1012
|
+
]);
|
|
1013
|
+
var ALLOWED_EMPHASIS_LEVELS = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
|
|
1014
|
+
var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
1015
|
+
"Leading",
|
|
1016
|
+
"Tailing",
|
|
1017
|
+
"Sentenceboundary",
|
|
1018
|
+
"Comma",
|
|
1019
|
+
"Semicolon",
|
|
1020
|
+
"Enumerationcomma"
|
|
1021
|
+
]);
|
|
1022
|
+
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
1023
|
+
function decodeAttribute(value) {
|
|
1024
|
+
return value.replace(
|
|
1025
|
+
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
1026
|
+
(entity) => ({ "&": "&", "'": "'", ">": ">", "<": "<", """: '"' })[entity.toLowerCase()] ?? entity
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
function findTagEnd2(source, start) {
|
|
1030
|
+
let quote = "";
|
|
1031
|
+
for (let index = start; index < source.length; index += 1) {
|
|
1032
|
+
const character = source[index];
|
|
1033
|
+
if (quote) {
|
|
1034
|
+
if (character === quote) quote = "";
|
|
1035
|
+
} else if (character === '"' || character === "'") quote = character;
|
|
1036
|
+
else if (character === ">") return index;
|
|
1037
|
+
}
|
|
1038
|
+
return source.length - 1;
|
|
1039
|
+
}
|
|
1040
|
+
function tokenizeElements(source) {
|
|
1041
|
+
const tokens = [];
|
|
1042
|
+
const openElements = [];
|
|
1043
|
+
let index = 0;
|
|
1044
|
+
while (index < source.length) {
|
|
1045
|
+
const start = source.indexOf("<", index);
|
|
1046
|
+
if (start === -1) break;
|
|
1047
|
+
if (source.startsWith("<!--", start)) {
|
|
1048
|
+
const end2 = source.indexOf("-->", start + 4);
|
|
1049
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (source.startsWith("<![CDATA[", start)) {
|
|
1053
|
+
const end2 = source.indexOf("]]>", start + 9);
|
|
1054
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (source.startsWith("<?", start)) {
|
|
1058
|
+
const end2 = source.indexOf("?>", start + 2);
|
|
1059
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
const end = findTagEnd2(source, start + 1);
|
|
1063
|
+
const raw = source.slice(start, end + 1);
|
|
1064
|
+
if (raw.startsWith("</")) {
|
|
1065
|
+
openElements.pop();
|
|
1066
|
+
index = end + 1;
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
|
|
1070
|
+
if (!nameMatch?.[1]) {
|
|
1071
|
+
index = end + 1;
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
1075
|
+
const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
|
|
1076
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
1077
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
1078
|
+
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1079
|
+
}
|
|
1080
|
+
const selfClosing = /\/\s*>$/.test(raw);
|
|
1081
|
+
const parent = openElements[openElements.length - 1];
|
|
1082
|
+
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1083
|
+
const tokenName = nameMatch[1];
|
|
1084
|
+
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1085
|
+
tokens.push({
|
|
1086
|
+
attributes,
|
|
1087
|
+
end,
|
|
1088
|
+
name: tokenName,
|
|
1089
|
+
parentName: parent?.name,
|
|
1090
|
+
parentVoiceName,
|
|
1091
|
+
selfClosing,
|
|
1092
|
+
start
|
|
1093
|
+
});
|
|
1094
|
+
if (!selfClosing) {
|
|
1095
|
+
openElements.push({
|
|
1096
|
+
name: tokenName,
|
|
1097
|
+
voiceName: tokenVoiceName
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
index = end + 1;
|
|
1101
|
+
}
|
|
1102
|
+
return tokens;
|
|
1103
|
+
}
|
|
1104
|
+
function location(source, offset) {
|
|
1105
|
+
const before = source.slice(0, Math.max(0, offset));
|
|
1106
|
+
const line = before.split("\n").length;
|
|
1107
|
+
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
1108
|
+
}
|
|
1109
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error", code2) {
|
|
1110
|
+
diagnostics.push({
|
|
1111
|
+
...location(source, offset),
|
|
1112
|
+
message,
|
|
1113
|
+
severity,
|
|
1114
|
+
source: "ssml-static-validator",
|
|
1115
|
+
...code2 ? { code: code2 } : {}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
function isSupportedProsodyRate(value) {
|
|
1119
|
+
const trimmed = value.trim();
|
|
1120
|
+
if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
|
|
1121
|
+
const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
|
|
1122
|
+
if (!multiplier) return false;
|
|
1123
|
+
const numericValue = Number(multiplier[1]);
|
|
1124
|
+
return numericValue >= 0.5 && numericValue <= 2;
|
|
1125
|
+
}
|
|
1126
|
+
function isValidAzureAudioDuration(value) {
|
|
1127
|
+
const trimmed = value.trim();
|
|
1128
|
+
const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
|
|
1129
|
+
if (numeric) return Number(numeric[1]) > 0;
|
|
1130
|
+
const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
|
|
1131
|
+
if (!clock) return false;
|
|
1132
|
+
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1133
|
+
}
|
|
1134
|
+
function attr(token, name) {
|
|
1135
|
+
return token.attributes.get(name.toLowerCase());
|
|
1136
|
+
}
|
|
1137
|
+
var DEFAULT_LANGUAGE_ALIASES = {
|
|
1138
|
+
"zh-CN": ["zh-Hans"],
|
|
1139
|
+
"zh-TW": ["zh-Hant"]
|
|
1140
|
+
};
|
|
1141
|
+
function canonicalLanguageTag(language) {
|
|
1142
|
+
const trimmed = language.trim();
|
|
1143
|
+
if (!trimmed) return "";
|
|
1144
|
+
try {
|
|
1145
|
+
return new Intl.Locale(trimmed).toString().toLowerCase();
|
|
1146
|
+
} catch {
|
|
1147
|
+
return trimmed.toLowerCase();
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function createLanguageNormalizer(options) {
|
|
1151
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
1152
|
+
const addAliasGroup = (canonical, values) => {
|
|
1153
|
+
const normalizedCanonical = canonicalLanguageTag(canonical);
|
|
1154
|
+
if (!normalizedCanonical) return;
|
|
1155
|
+
aliases.set(normalizedCanonical, normalizedCanonical);
|
|
1156
|
+
for (const value of values) {
|
|
1157
|
+
const normalizedValue = canonicalLanguageTag(value);
|
|
1158
|
+
if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
|
|
1162
|
+
for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
|
|
1163
|
+
addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
|
|
1164
|
+
return (language) => {
|
|
1165
|
+
const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
|
|
1166
|
+
const normalized = canonicalLanguageTag(customValue);
|
|
1167
|
+
return aliases.get(normalized) ?? normalized;
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function voiceLocalePrefix(voiceName) {
|
|
1171
|
+
const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
|
|
1172
|
+
if (!match?.groups) return void 0;
|
|
1173
|
+
const tag = `${match.groups.language}-${match.groups.region}`;
|
|
1174
|
+
return {
|
|
1175
|
+
language: match.groups.language.toLowerCase(),
|
|
1176
|
+
region: match.groups.region.toLowerCase(),
|
|
1177
|
+
tag
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
function definitionFromStyleMap(voiceName, styles) {
|
|
1181
|
+
return {
|
|
1182
|
+
name: voiceName,
|
|
1183
|
+
locale: voiceLocalePrefix(voiceName)?.tag ?? "",
|
|
1184
|
+
styles
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function normalizeVoiceCatalog(options) {
|
|
1188
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
1189
|
+
for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
|
|
1190
|
+
for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1191
|
+
for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
|
|
1192
|
+
for (const definition of options.customVoiceDefinitions ?? [])
|
|
1193
|
+
definitions.set(definition.name.toLowerCase(), definition);
|
|
1194
|
+
for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
|
|
1195
|
+
const key = voiceName.toLowerCase();
|
|
1196
|
+
const current = definitions.get(key);
|
|
1197
|
+
definitions.set(key, {
|
|
1198
|
+
...current ?? definitionFromStyleMap(voiceName, styles),
|
|
1199
|
+
name: current?.name ?? voiceName,
|
|
1200
|
+
styles: styles.map((style) => style.toLowerCase())
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return definitions;
|
|
1204
|
+
}
|
|
1205
|
+
function diagnosticSeverity(policy) {
|
|
1206
|
+
if (policy === "ignore") return void 0;
|
|
1207
|
+
return policy === "error" ? "error" : "warning";
|
|
1208
|
+
}
|
|
1209
|
+
function languagePart(language) {
|
|
1210
|
+
try {
|
|
1211
|
+
return new Intl.Locale(language).language.toLowerCase();
|
|
1212
|
+
} catch {
|
|
1213
|
+
return language.split("-")[0]?.toLowerCase() ?? "";
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
|
|
1217
|
+
const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
|
|
1218
|
+
if (candidateLanguages.length === 0 || !language.trim()) return void 0;
|
|
1219
|
+
const normalizedLanguage = normalizeLanguage(language);
|
|
1220
|
+
const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
|
|
1221
|
+
if (normalizedCandidates.includes(normalizedLanguage)) return true;
|
|
1222
|
+
if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
|
|
1223
|
+
return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
|
|
1224
|
+
}
|
|
1225
|
+
function canonicalTagName(name) {
|
|
1226
|
+
const normalized = name.toLowerCase();
|
|
1227
|
+
if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
|
|
1228
|
+
if (normalized === "sayas") return "say-as";
|
|
1229
|
+
return normalized;
|
|
1230
|
+
}
|
|
1231
|
+
function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
|
|
1232
|
+
if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
|
|
1233
|
+
return;
|
|
1234
|
+
const tagName = canonicalTagName(token.name);
|
|
1235
|
+
const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
|
|
1236
|
+
const supportedTags = definition.supportedTags?.map(canonicalTagName);
|
|
1237
|
+
if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
|
|
1238
|
+
addDiagnostic(
|
|
1239
|
+
diagnostics,
|
|
1240
|
+
source,
|
|
1241
|
+
token.start,
|
|
1242
|
+
`Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
|
|
1243
|
+
"error",
|
|
1244
|
+
"azure-unsupported-tag-for-voice"
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function validateAudioSource(token, source, diagnostics, options, elementName2) {
|
|
1249
|
+
const src = attr(token, "src");
|
|
1250
|
+
if (!src) {
|
|
1251
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
let parsed;
|
|
1255
|
+
try {
|
|
1256
|
+
parsed = new URL(src);
|
|
1257
|
+
} catch {
|
|
1258
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1262
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1263
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1264
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1265
|
+
else if (!options.allowExternalAudio)
|
|
1266
|
+
addDiagnostic(
|
|
1267
|
+
diagnostics,
|
|
1268
|
+
source,
|
|
1269
|
+
token.start,
|
|
1270
|
+
`<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1274
|
+
const name = token.name.toLowerCase();
|
|
1275
|
+
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1276
|
+
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
1277
|
+
if (name === "break") {
|
|
1278
|
+
const time = attr(token, "time");
|
|
1279
|
+
const strength = attr(token, "strength");
|
|
1280
|
+
if (!time && !strength)
|
|
1281
|
+
addDiagnostic(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
|
|
1282
|
+
if (time && strength)
|
|
1283
|
+
addDiagnostic(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
|
|
1284
|
+
if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
|
|
1285
|
+
addDiagnostic(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
|
|
1286
|
+
if (strength && !ALLOWED_BREAK_STRENGTHS.has(strength))
|
|
1287
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
|
|
1288
|
+
}
|
|
1289
|
+
if (name === "prosody") {
|
|
1290
|
+
const rate = attr(token, "rate");
|
|
1291
|
+
const pitch = attr(token, "pitch");
|
|
1292
|
+
const volume = attr(token, "volume");
|
|
1293
|
+
if (rate && !isSupportedProsodyRate(rate))
|
|
1294
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
|
|
1295
|
+
if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
|
|
1296
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
|
|
1297
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
|
|
1298
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
|
|
1299
|
+
}
|
|
1300
|
+
if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
|
|
1301
|
+
const style = attr(token, "style");
|
|
1302
|
+
if (!style?.trim())
|
|
1303
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
|
|
1304
|
+
const degree = attr(token, "styledegree") ?? attr(token, "style-degree");
|
|
1305
|
+
if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
|
|
1306
|
+
addDiagnostic(
|
|
1307
|
+
diagnostics,
|
|
1308
|
+
source,
|
|
1309
|
+
token.start,
|
|
1310
|
+
"<mstts:express-as styledegree> must be a number between 0.01 and 2."
|
|
1311
|
+
);
|
|
1312
|
+
const role = attr(token, "role");
|
|
1313
|
+
if (role && !ALLOWED_ROLES.has(role))
|
|
1314
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
1315
|
+
const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
|
|
1316
|
+
const supportedStyles = definition?.styles;
|
|
1317
|
+
const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
|
|
1318
|
+
if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
|
|
1319
|
+
addDiagnostic(
|
|
1320
|
+
diagnostics,
|
|
1321
|
+
source,
|
|
1322
|
+
token.start,
|
|
1323
|
+
`Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
|
|
1324
|
+
severity,
|
|
1325
|
+
"azure-unsupported-style"
|
|
1326
|
+
);
|
|
1327
|
+
if (style && voiceName && !definition && severity)
|
|
1328
|
+
addDiagnostic(
|
|
1329
|
+
diagnostics,
|
|
1330
|
+
source,
|
|
1331
|
+
token.start,
|
|
1332
|
+
`Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
|
|
1333
|
+
severity
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
if (name === "say-as" || name === "sayas") {
|
|
1337
|
+
const interpretAs = attr(token, "interpret-as");
|
|
1338
|
+
if (!interpretAs || !ALLOWED_SAY_AS.has(interpretAs))
|
|
1339
|
+
addDiagnostic(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
|
|
1340
|
+
}
|
|
1341
|
+
if (name === "phoneme" && (!attr(token, "alphabet") || !attr(token, "ph")))
|
|
1342
|
+
addDiagnostic(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
|
|
1343
|
+
if (name === "emphasis" && attr(token, "level") && !ALLOWED_EMPHASIS_LEVELS.has(attr(token, "level") ?? ""))
|
|
1344
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr(token, "level")}".`);
|
|
1345
|
+
if (name === "sub" && !attr(token, "alias")?.trim())
|
|
1346
|
+
addDiagnostic(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
|
|
1347
|
+
if (name === "lang" && !attr(token, "xml:lang")?.trim() && !attr(token, "lang")?.trim())
|
|
1348
|
+
addDiagnostic(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
|
|
1349
|
+
if (name === "mark" && !attr(token, "name")?.trim())
|
|
1350
|
+
addDiagnostic(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
|
|
1351
|
+
if (name === "bookmark" && !attr(token, "mark")?.trim())
|
|
1352
|
+
addDiagnostic(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
|
|
1353
|
+
if (name === "lexicon") {
|
|
1354
|
+
const uri = attr(token, "uri");
|
|
1355
|
+
if (!uri) addDiagnostic(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
|
|
1356
|
+
else {
|
|
1357
|
+
try {
|
|
1358
|
+
const parsed = new URL(uri);
|
|
1359
|
+
if (parsed.protocol !== "https:")
|
|
1360
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
|
|
1361
|
+
} catch {
|
|
1362
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (name === "mstts:silence") {
|
|
1367
|
+
const type = attr(token, "type");
|
|
1368
|
+
const value = attr(token, "value");
|
|
1369
|
+
if (!type || !ALLOWED_SILENCE_TYPES.has(type))
|
|
1370
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
|
|
1371
|
+
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
1372
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
1373
|
+
}
|
|
1374
|
+
if (name === "mstts:audioduration") {
|
|
1375
|
+
const value = attr(token, "value");
|
|
1376
|
+
if (!value || !isValidAzureAudioDuration(value))
|
|
1377
|
+
addDiagnostic(
|
|
1378
|
+
diagnostics,
|
|
1379
|
+
source,
|
|
1380
|
+
token.start,
|
|
1381
|
+
'<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
|
|
1382
|
+
);
|
|
1383
|
+
if (!token.selfClosing)
|
|
1384
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
|
|
1385
|
+
}
|
|
1386
|
+
if (name === "mstts:viseme") {
|
|
1387
|
+
const type = attr(token, "type");
|
|
1388
|
+
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
1389
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1390
|
+
}
|
|
1391
|
+
if (name === "audio") {
|
|
1392
|
+
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1393
|
+
}
|
|
1394
|
+
if (name === "mstts:turn") {
|
|
1395
|
+
if (!attr(token, "voice")?.trim())
|
|
1396
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
|
|
1397
|
+
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1398
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1399
|
+
}
|
|
1400
|
+
if (name === "mstts:backgroundaudio") {
|
|
1401
|
+
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1402
|
+
const volume = attr(token, "volume");
|
|
1403
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
|
|
1404
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1405
|
+
for (const [attribute, value] of [
|
|
1406
|
+
["fadein", attr(token, "fadein")],
|
|
1407
|
+
["fadeout", attr(token, "fadeout")]
|
|
1408
|
+
]) {
|
|
1409
|
+
if (value && !isValidAzureAudioDuration(value))
|
|
1410
|
+
addDiagnostic(
|
|
1411
|
+
diagnostics,
|
|
1412
|
+
source,
|
|
1413
|
+
token.start,
|
|
1414
|
+
`<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
if (!token.selfClosing)
|
|
1418
|
+
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function validateAzureSsml(ssml, options = {}) {
|
|
1422
|
+
const diagnostics = [];
|
|
1423
|
+
if (typeof ssml !== "string") {
|
|
1424
|
+
return [
|
|
1425
|
+
{
|
|
1426
|
+
line: 1,
|
|
1427
|
+
column: 1,
|
|
1428
|
+
message: "SSML input must be a string",
|
|
1429
|
+
severity: "error",
|
|
1430
|
+
source: "ssml-static-validator"
|
|
1431
|
+
}
|
|
1432
|
+
];
|
|
1433
|
+
}
|
|
1434
|
+
const maxLength = options.maxLength ?? 1e4;
|
|
1435
|
+
if (ssml.length > maxLength)
|
|
1436
|
+
addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
1437
|
+
try {
|
|
1438
|
+
parseSsml(ssml);
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
|
|
1441
|
+
const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
|
|
1442
|
+
addDiagnostic(diagnostics, ssml, match ? Number(match[1]) : 0, message);
|
|
1443
|
+
return diagnostics;
|
|
1444
|
+
}
|
|
1445
|
+
const tokens = tokenizeElements(ssml);
|
|
1446
|
+
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1447
|
+
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
1448
|
+
if (!speak || voices.length === 0)
|
|
1449
|
+
addDiagnostic(
|
|
1450
|
+
diagnostics,
|
|
1451
|
+
ssml,
|
|
1452
|
+
speak?.start ?? 0,
|
|
1453
|
+
"Azure SSML requires at least one <voice> element under <speak>."
|
|
1454
|
+
);
|
|
1455
|
+
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
1456
|
+
const voiceCatalog = normalizeVoiceCatalog(options);
|
|
1457
|
+
const normalizeLanguage = createLanguageNormalizer(options);
|
|
1458
|
+
const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
|
|
1459
|
+
const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
|
|
1460
|
+
for (const token of voicesToValidate) {
|
|
1461
|
+
const name = attr(token, "name")?.trim();
|
|
1462
|
+
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
1463
|
+
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
1464
|
+
if (name && !definition && policySeverity)
|
|
1465
|
+
addDiagnostic(
|
|
1466
|
+
diagnostics,
|
|
1467
|
+
ssml,
|
|
1468
|
+
token.start,
|
|
1469
|
+
`Unknown voice "${name}" is not registered in the voice catalog.`,
|
|
1470
|
+
policySeverity,
|
|
1471
|
+
"azure-unknown-voice"
|
|
1472
|
+
);
|
|
1473
|
+
if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
|
|
1474
|
+
addDiagnostic(
|
|
1475
|
+
diagnostics,
|
|
1476
|
+
ssml,
|
|
1477
|
+
token.start,
|
|
1478
|
+
`Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
|
|
1479
|
+
"warning",
|
|
1480
|
+
"azure-locale-mismatch"
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
for (const token of tokens) {
|
|
1484
|
+
const tokenName = token.name.toLowerCase();
|
|
1485
|
+
const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
|
|
1486
|
+
validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
|
|
1487
|
+
const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
|
|
1488
|
+
validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
|
|
1489
|
+
if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
|
|
1490
|
+
addDiagnostic(
|
|
1491
|
+
diagnostics,
|
|
1492
|
+
ssml,
|
|
1493
|
+
token.start,
|
|
1494
|
+
`Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
|
|
1495
|
+
"error",
|
|
1496
|
+
"azure-unsupported-model-for-voice"
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return diagnostics;
|
|
1501
|
+
}
|
|
1502
|
+
var AZURE_VOICE_CATALOG_METADATA = {
|
|
1503
|
+
apiVersion: "2025-10-01",
|
|
1504
|
+
generatedAt: "2026-08-28T00:00:00.000Z",
|
|
1505
|
+
regions: [],
|
|
1506
|
+
voiceCount: AZURE_VOICE_DEFINITIONS.length
|
|
1507
|
+
};
|
|
801
1508
|
|
|
802
1509
|
// packages/ssml-editor-react/src/clearSsmlDocument.ts
|
|
803
1510
|
function getDocumentChildren(document2) {
|
|
@@ -858,6 +1565,7 @@ var INTRINSICALLY_EMPTY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
858
1565
|
"lexicon",
|
|
859
1566
|
"mark",
|
|
860
1567
|
"mstts:silence",
|
|
1568
|
+
"mstts:audioduration",
|
|
861
1569
|
"mstts:viseme",
|
|
862
1570
|
"silence",
|
|
863
1571
|
"viseme"
|
|
@@ -1516,6 +2224,8 @@ function updateEditableText(document2, value) {
|
|
|
1516
2224
|
// packages/ssml-editor-react/src/constants/ssmlPresets.ts
|
|
1517
2225
|
var ssmlPresets_exports = {};
|
|
1518
2226
|
__export(ssmlPresets_exports, {
|
|
2227
|
+
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
2228
|
+
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
1519
2229
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
1520
2230
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
1521
2231
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -1548,6 +2258,103 @@ __export(ssmlPresets_exports, {
|
|
|
1548
2258
|
getExpressAsStyleCategory: () => getExpressAsStyleCategory,
|
|
1549
2259
|
resolveExpressAsStyles: () => resolveExpressAsStyles
|
|
1550
2260
|
});
|
|
2261
|
+
|
|
2262
|
+
// packages/ssml-editor-react/src/constants/azureVoiceStyleMap.generated.ts
|
|
2263
|
+
var AZURE_VOICE_STYLE_MAP = {
|
|
2264
|
+
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
2265
|
+
"de-DE-KatjaNeural": ["cheerful", "sad"],
|
|
2266
|
+
"en-US-AndrewNeural": ["empathetic", "relieved"],
|
|
2267
|
+
"en-US-GuyNeural": [
|
|
2268
|
+
"angry",
|
|
2269
|
+
"cheerful",
|
|
2270
|
+
"excited",
|
|
2271
|
+
"friendly",
|
|
2272
|
+
"hopeful",
|
|
2273
|
+
"newscast",
|
|
2274
|
+
"sad",
|
|
2275
|
+
"shouting",
|
|
2276
|
+
"terrified",
|
|
2277
|
+
"unfriendly",
|
|
2278
|
+
"whispering"
|
|
2279
|
+
],
|
|
2280
|
+
"en-US-JennyMultilingualNeural": [
|
|
2281
|
+
"cheerful",
|
|
2282
|
+
"empathetic",
|
|
2283
|
+
"excited",
|
|
2284
|
+
"friendly",
|
|
2285
|
+
"hopeful",
|
|
2286
|
+
"sad",
|
|
2287
|
+
"shouting",
|
|
2288
|
+
"terrified",
|
|
2289
|
+
"unfriendly",
|
|
2290
|
+
"whispering"
|
|
2291
|
+
],
|
|
2292
|
+
"en-US-JennyNeural": [
|
|
2293
|
+
"assistant",
|
|
2294
|
+
"chat",
|
|
2295
|
+
"customerservice",
|
|
2296
|
+
"newscast",
|
|
2297
|
+
"cheerful",
|
|
2298
|
+
"empathetic",
|
|
2299
|
+
"excited",
|
|
2300
|
+
"friendly",
|
|
2301
|
+
"hopeful",
|
|
2302
|
+
"sad",
|
|
2303
|
+
"shouting",
|
|
2304
|
+
"terrified",
|
|
2305
|
+
"unfriendly",
|
|
2306
|
+
"whispering"
|
|
2307
|
+
],
|
|
2308
|
+
"es-ES-ElviraNeural": [],
|
|
2309
|
+
"fil-PH-AngeloNeural": [],
|
|
2310
|
+
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
2311
|
+
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
2312
|
+
"id-ID-GadisNeural": [],
|
|
2313
|
+
"it-IT-ElsaNeural": ["cheerful", "sad"],
|
|
2314
|
+
"ja-JP-KeitaNeural": ["chat"],
|
|
2315
|
+
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
2316
|
+
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
2317
|
+
"ko-KR-SunHiNeural": ["cheerful", "sad"],
|
|
2318
|
+
"ms-MY-YasminNeural": [],
|
|
2319
|
+
"pt-BR-FranciscaNeural": ["calm"],
|
|
2320
|
+
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
|
|
2321
|
+
"th-TH-PremwadeeNeural": [],
|
|
2322
|
+
"vi-VN-HoaiMyNeural": [],
|
|
2323
|
+
"zh-CN-XiaoxiaoNeural": [
|
|
2324
|
+
"assistant",
|
|
2325
|
+
"chat",
|
|
2326
|
+
"customerservice",
|
|
2327
|
+
"newscast",
|
|
2328
|
+
"cheerful",
|
|
2329
|
+
"empathetic",
|
|
2330
|
+
"excited",
|
|
2331
|
+
"friendly",
|
|
2332
|
+
"hopeful",
|
|
2333
|
+
"sad",
|
|
2334
|
+
"terrified",
|
|
2335
|
+
"whispering",
|
|
2336
|
+
"poetry-reading",
|
|
2337
|
+
"sports_commentary",
|
|
2338
|
+
"sports_commentary_excited",
|
|
2339
|
+
"story"
|
|
2340
|
+
],
|
|
2341
|
+
"zh-CN-YunxiNeural": [
|
|
2342
|
+
"narration-relaxed",
|
|
2343
|
+
"embarrassed",
|
|
2344
|
+
"fearful",
|
|
2345
|
+
"sad",
|
|
2346
|
+
"disgruntled",
|
|
2347
|
+
"serious",
|
|
2348
|
+
"angry",
|
|
2349
|
+
"depressed",
|
|
2350
|
+
"chat",
|
|
2351
|
+
"cheerful",
|
|
2352
|
+
"assistant"
|
|
2353
|
+
],
|
|
2354
|
+
"zh-TW-HsiaoChenNeural": []
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
// packages/ssml-editor-react/src/constants/ssmlPresets.ts
|
|
1551
2358
|
var SSML_PRESETS = [
|
|
1552
2359
|
{
|
|
1553
2360
|
id: "basic",
|
|
@@ -1647,7 +2454,7 @@ function getExpressAsStyleCategory(style) {
|
|
|
1647
2454
|
}
|
|
1648
2455
|
return "other";
|
|
1649
2456
|
}
|
|
1650
|
-
var
|
|
2457
|
+
var LEGACY_VOICE_STYLE_MAP = {
|
|
1651
2458
|
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
1652
2459
|
"ja-JP-KeitaNeural": ["chat"],
|
|
1653
2460
|
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
@@ -1733,6 +2540,7 @@ var VOICE_STYLE_MAP = {
|
|
|
1733
2540
|
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
1734
2541
|
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1735
2542
|
};
|
|
2543
|
+
var VOICE_STYLE_MAP = Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
|
|
1736
2544
|
var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
|
|
1737
2545
|
Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
|
|
1738
2546
|
);
|
|
@@ -1775,6 +2583,7 @@ var SAY_AS_PRESETS = [
|
|
|
1775
2583
|
];
|
|
1776
2584
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
1777
2585
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
2586
|
+
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
1778
2587
|
var SILENCE_TYPE_PRESETS = [
|
|
1779
2588
|
"Leading",
|
|
1780
2589
|
"Tailing",
|
|
@@ -1783,6 +2592,11 @@ var SILENCE_TYPE_PRESETS = [
|
|
|
1783
2592
|
"Semicolon",
|
|
1784
2593
|
"Enumerationcomma"
|
|
1785
2594
|
];
|
|
2595
|
+
var AUDIO_DURATION_DESCRIPTIONS = {
|
|
2596
|
+
"5s": { ja: "5\u79D2", en: "5 seconds" },
|
|
2597
|
+
"10s": { ja: "10\u79D2", en: "10 seconds" },
|
|
2598
|
+
"30s": { ja: "30\u79D2", en: "30 seconds" }
|
|
2599
|
+
};
|
|
1786
2600
|
var PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"];
|
|
1787
2601
|
var VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"];
|
|
1788
2602
|
var SSML_ATTRIBUTE_PRESETS = {
|
|
@@ -1826,6 +2640,14 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
1826
2640
|
type: SILENCE_TYPE_PRESETS,
|
|
1827
2641
|
value: SILENCE_VALUE_PRESETS
|
|
1828
2642
|
},
|
|
2643
|
+
"mstts:audioduration": {
|
|
2644
|
+
value: AUDIO_DURATION_PRESETS
|
|
2645
|
+
},
|
|
2646
|
+
"mstts:backgroundaudio": {
|
|
2647
|
+
volume: PROSODY_VOLUME_PRESETS,
|
|
2648
|
+
fadein: AUDIO_DURATION_PRESETS,
|
|
2649
|
+
fadeout: AUDIO_DURATION_PRESETS
|
|
2650
|
+
},
|
|
1829
2651
|
silence: {
|
|
1830
2652
|
type: SILENCE_TYPE_PRESETS,
|
|
1831
2653
|
value: SILENCE_VALUE_PRESETS
|
|
@@ -2162,7 +2984,7 @@ var SILENCE_VALUE_DESCRIPTIONS = {
|
|
|
2162
2984
|
};
|
|
2163
2985
|
|
|
2164
2986
|
// packages/ssml-editor-react/src/ssmlContext.ts
|
|
2165
|
-
function
|
|
2987
|
+
function findTagEnd3(source, start, limit) {
|
|
2166
2988
|
let quote;
|
|
2167
2989
|
for (let index = start + 1; index < limit; index += 1) {
|
|
2168
2990
|
const character = source[index];
|
|
@@ -2212,7 +3034,7 @@ function findActiveSsmlTags(source, offset) {
|
|
|
2212
3034
|
index = end;
|
|
2213
3035
|
continue;
|
|
2214
3036
|
}
|
|
2215
|
-
const tagEnd =
|
|
3037
|
+
const tagEnd = findTagEnd3(source, tagStart, source.length);
|
|
2216
3038
|
const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
|
|
2217
3039
|
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
2218
3040
|
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
@@ -2255,7 +3077,7 @@ function findSsmlVoiceContext(source, offset) {
|
|
|
2255
3077
|
index = end === -1 || end + 2 > limit ? limit : end + 2;
|
|
2256
3078
|
continue;
|
|
2257
3079
|
}
|
|
2258
|
-
const tagEnd =
|
|
3080
|
+
const tagEnd = findTagEnd3(source, tagStart, limit);
|
|
2259
3081
|
if (tagEnd === -1) {
|
|
2260
3082
|
break;
|
|
2261
3083
|
}
|
|
@@ -2313,6 +3135,28 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
2313
3135
|
label: "mstts:express-as",
|
|
2314
3136
|
insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`
|
|
2315
3137
|
},
|
|
3138
|
+
{
|
|
3139
|
+
label: "mstts:audioduration",
|
|
3140
|
+
insertText: '<mstts:audioduration value="10s" />'
|
|
3141
|
+
},
|
|
3142
|
+
{
|
|
3143
|
+
label: "mstts:dialog",
|
|
3144
|
+
insertText: `<mstts:dialog>
|
|
3145
|
+
<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
|
|
3146
|
+
</mstts:dialog>`
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
label: "mstts:turn",
|
|
3150
|
+
insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
label: "mstts:backgroundaudio",
|
|
3154
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
|
|
3155
|
+
},
|
|
3156
|
+
{
|
|
3157
|
+
label: "mstts:ttsembedding",
|
|
3158
|
+
insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
|
|
3159
|
+
},
|
|
2316
3160
|
{
|
|
2317
3161
|
label: "sub",
|
|
2318
3162
|
insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
|
|
@@ -2580,6 +3424,13 @@ var SSML_HOVER_COPY = {
|
|
|
2580
3424
|
parameters: {
|
|
2581
3425
|
type: { title: "type", description: "\u30D3\u30BC\u30FC\u30E0\u30A4\u30D9\u30F3\u30C8\u306E\u5F62\u5F0F\u3002" }
|
|
2582
3426
|
}
|
|
3427
|
+
},
|
|
3428
|
+
"mstts:audioduration": {
|
|
3429
|
+
title: "\u97F3\u58F0\u9577",
|
|
3430
|
+
description: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
3431
|
+
parameters: {
|
|
3432
|
+
value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
|
|
3433
|
+
}
|
|
2583
3434
|
}
|
|
2584
3435
|
}
|
|
2585
3436
|
},
|
|
@@ -2737,6 +3588,13 @@ var SSML_HOVER_COPY = {
|
|
|
2737
3588
|
parameters: {
|
|
2738
3589
|
type: { title: "type", description: "The viseme event format." }
|
|
2739
3590
|
}
|
|
3591
|
+
},
|
|
3592
|
+
"mstts:audioduration": {
|
|
3593
|
+
title: "Audio duration",
|
|
3594
|
+
description: "Sets the target duration of synthesized audio.",
|
|
3595
|
+
parameters: {
|
|
3596
|
+
value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
|
|
3597
|
+
}
|
|
2740
3598
|
}
|
|
2741
3599
|
}
|
|
2742
3600
|
}
|
|
@@ -3033,6 +3891,74 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
3033
3891
|
values: VISEME_TYPE_PRESETS2
|
|
3034
3892
|
}
|
|
3035
3893
|
]
|
|
3894
|
+
},
|
|
3895
|
+
{
|
|
3896
|
+
name: "mstts:audioduration",
|
|
3897
|
+
description: "Sets the target duration of synthesized audio.",
|
|
3898
|
+
parameters: [
|
|
3899
|
+
{
|
|
3900
|
+
name: "value",
|
|
3901
|
+
description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
|
|
3902
|
+
example: "10s"
|
|
3903
|
+
}
|
|
3904
|
+
]
|
|
3905
|
+
},
|
|
3906
|
+
{
|
|
3907
|
+
name: "mstts:dialog",
|
|
3908
|
+
description: "Groups multiple Azure dialog turns that can use different voices.",
|
|
3909
|
+
parameters: []
|
|
3910
|
+
},
|
|
3911
|
+
{
|
|
3912
|
+
name: "mstts:turn",
|
|
3913
|
+
description: "Adds one dialog turn using the required Azure voice name.",
|
|
3914
|
+
parameters: [
|
|
3915
|
+
{
|
|
3916
|
+
name: "voice",
|
|
3917
|
+
description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
|
|
3918
|
+
example: "en-US-JennyNeural"
|
|
3919
|
+
}
|
|
3920
|
+
]
|
|
3921
|
+
},
|
|
3922
|
+
{
|
|
3923
|
+
name: "mstts:backgroundaudio",
|
|
3924
|
+
description: "Plays background audio while speech is synthesized.",
|
|
3925
|
+
parameters: [
|
|
3926
|
+
{
|
|
3927
|
+
name: "src",
|
|
3928
|
+
description: "An absolute HTTP(S) URL for the background audio file.",
|
|
3929
|
+
example: "https://example.com/music.mp3"
|
|
3930
|
+
},
|
|
3931
|
+
{
|
|
3932
|
+
name: "volume",
|
|
3933
|
+
description: "The background audio volume, for example `-3dB` or `medium`.",
|
|
3934
|
+
example: "-3dB"
|
|
3935
|
+
},
|
|
3936
|
+
{
|
|
3937
|
+
name: "fadein",
|
|
3938
|
+
description: "The fade-in duration, for example `1s`.",
|
|
3939
|
+
example: "1s"
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
name: "fadeout",
|
|
3943
|
+
description: "The fade-out duration, for example `500ms`.",
|
|
3944
|
+
example: "500ms"
|
|
3945
|
+
}
|
|
3946
|
+
]
|
|
3947
|
+
},
|
|
3948
|
+
{
|
|
3949
|
+
name: "mstts:ttsembedding",
|
|
3950
|
+
description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
|
|
3951
|
+
parameters: []
|
|
3952
|
+
},
|
|
3953
|
+
{
|
|
3954
|
+
name: "mstts:embedding",
|
|
3955
|
+
description: "Specifies embedding metadata for custom voice scenarios.",
|
|
3956
|
+
parameters: []
|
|
3957
|
+
},
|
|
3958
|
+
{
|
|
3959
|
+
name: "mstts:voiceconversion",
|
|
3960
|
+
description: "Specifies voice conversion metadata for custom voice scenarios.",
|
|
3961
|
+
parameters: []
|
|
3036
3962
|
}
|
|
3037
3963
|
];
|
|
3038
3964
|
var definitionsByName = /* @__PURE__ */ new Map();
|
|
@@ -3091,7 +4017,7 @@ function toRange(source, token) {
|
|
|
3091
4017
|
function containsOffset(token, offset) {
|
|
3092
4018
|
return offset >= token.start && offset < token.end;
|
|
3093
4019
|
}
|
|
3094
|
-
function
|
|
4020
|
+
function findTagEnd4(source, start) {
|
|
3095
4021
|
let quote;
|
|
3096
4022
|
for (let index = start; index < source.length; index += 1) {
|
|
3097
4023
|
const character = source[index];
|
|
@@ -3222,7 +4148,7 @@ function findTagAtOffset(source, offset) {
|
|
|
3222
4148
|
searchStart = tokenEnd2;
|
|
3223
4149
|
continue;
|
|
3224
4150
|
}
|
|
3225
|
-
const tagEnd =
|
|
4151
|
+
const tagEnd = findTagEnd4(source, start + 1);
|
|
3226
4152
|
const contentEnd = tagEnd ?? source.length;
|
|
3227
4153
|
const tokenEnd = tagEnd === void 0 ? source.length : tagEnd + 1;
|
|
3228
4154
|
if (offset < tokenEnd) {
|
|
@@ -3574,13 +4500,34 @@ var SSML_INSERTIONS = [
|
|
|
3574
4500
|
suffix: "",
|
|
3575
4501
|
mode: "insert"
|
|
3576
4502
|
})
|
|
4503
|
+
},
|
|
4504
|
+
{
|
|
4505
|
+
id: "mstts:audioduration",
|
|
4506
|
+
icon: "\u25F7",
|
|
4507
|
+
tagName: "mstts:audioduration",
|
|
4508
|
+
selfClosing: true,
|
|
4509
|
+
labels: { ja: "\u97F3\u58F0\u9577", en: "Audio duration" },
|
|
4510
|
+
descriptions: {
|
|
4511
|
+
ja: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
4512
|
+
en: "Sets the target duration of synthesized audio."
|
|
4513
|
+
},
|
|
4514
|
+
parameterDescription: {
|
|
4515
|
+
ja: "\u76EE\u6A19\u6642\u9593\u3092\u9078\u629E\u3057\u307E\u3059\u3002",
|
|
4516
|
+
en: "Selects the target duration."
|
|
4517
|
+
},
|
|
4518
|
+
options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
|
|
4519
|
+
createTemplate: (value) => ({
|
|
4520
|
+
prefix: `<mstts:audioduration value="${value}"/>`,
|
|
4521
|
+
suffix: "",
|
|
4522
|
+
mode: "insert"
|
|
4523
|
+
})
|
|
3577
4524
|
}
|
|
3578
4525
|
];
|
|
3579
4526
|
var DEFAULT_INSERTION_GROUPS = [
|
|
3580
4527
|
{
|
|
3581
4528
|
id: "pauses",
|
|
3582
4529
|
labels: { ja: "\u9593\u30FB\u7121\u97F3", en: "Pauses" },
|
|
3583
|
-
insertionIds: ["break", "mstts:silence"]
|
|
4530
|
+
insertionIds: ["break", "mstts:silence", "mstts:audioduration"]
|
|
3584
4531
|
},
|
|
3585
4532
|
{
|
|
3586
4533
|
id: "prosody",
|
|
@@ -3817,6 +4764,60 @@ section[data-ssml-editor] {
|
|
|
3817
4764
|
border-radius: 0.25rem;
|
|
3818
4765
|
overflow: visible;
|
|
3819
4766
|
}
|
|
4767
|
+
[data-ssml-editor] .ssml-editor-visual {
|
|
4768
|
+
display: grid;
|
|
4769
|
+
gap: 0.75rem;
|
|
4770
|
+
min-height: 8rem;
|
|
4771
|
+
padding: 0.75rem;
|
|
4772
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
4773
|
+
border-radius: 0.25rem;
|
|
4774
|
+
}
|
|
4775
|
+
[data-ssml-editor] .ssml-editor-visual-layout {
|
|
4776
|
+
display: grid;
|
|
4777
|
+
grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
|
|
4778
|
+
gap: 1rem;
|
|
4779
|
+
}
|
|
4780
|
+
[data-ssml-editor] .ssml-editor-visual-tree ul {
|
|
4781
|
+
margin: 0;
|
|
4782
|
+
padding-left: 1.25rem;
|
|
4783
|
+
}
|
|
4784
|
+
[data-ssml-editor] .ssml-editor-visual button {
|
|
4785
|
+
padding: 0.35rem 0.5rem;
|
|
4786
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
4787
|
+
border-radius: 0.25rem;
|
|
4788
|
+
color: var(--ssml-editor-color);
|
|
4789
|
+
background: var(--ssml-editor-control-bg);
|
|
4790
|
+
cursor: pointer;
|
|
4791
|
+
}
|
|
4792
|
+
[data-ssml-editor] .ssml-editor-visual button[data-selected="true"] {
|
|
4793
|
+
border-color: var(--ssml-editor-active-border);
|
|
4794
|
+
background: var(--ssml-editor-active-bg);
|
|
4795
|
+
}
|
|
4796
|
+
[data-ssml-editor] .ssml-editor-visual textarea {
|
|
4797
|
+
box-sizing: border-box;
|
|
4798
|
+
width: 100%;
|
|
4799
|
+
min-height: 6rem;
|
|
4800
|
+
padding: 0.5rem;
|
|
4801
|
+
color: var(--ssml-editor-color);
|
|
4802
|
+
background: var(--ssml-editor-control-bg);
|
|
4803
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
4804
|
+
border-radius: 0.25rem;
|
|
4805
|
+
font: inherit;
|
|
4806
|
+
}
|
|
4807
|
+
[data-ssml-editor] .ssml-editor-visual-actions,
|
|
4808
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb {
|
|
4809
|
+
display: flex;
|
|
4810
|
+
flex-wrap: wrap;
|
|
4811
|
+
gap: 0.4rem;
|
|
4812
|
+
align-items: center;
|
|
4813
|
+
}
|
|
4814
|
+
[data-ssml-editor] .ssml-editor-visual-errors {
|
|
4815
|
+
padding: 0.5rem;
|
|
4816
|
+
color: #b91c1c;
|
|
4817
|
+
background: #fef2f2;
|
|
4818
|
+
border: 1px solid #b91c1c;
|
|
4819
|
+
border-radius: 0.25rem;
|
|
4820
|
+
}
|
|
3820
4821
|
`.trim();
|
|
3821
4822
|
function injectStyles() {
|
|
3822
4823
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
|
|
@@ -3830,6 +4831,55 @@ function injectStyles() {
|
|
|
3830
4831
|
function isDarkTheme(theme) {
|
|
3831
4832
|
return theme === "vs-dark" || theme.toLowerCase().includes("dark");
|
|
3832
4833
|
}
|
|
4834
|
+
function isElement(node) {
|
|
4835
|
+
return typeof node !== "string" && node.type !== "text";
|
|
4836
|
+
}
|
|
4837
|
+
function visualElementName(element) {
|
|
4838
|
+
return element.type === "custom" || element.type === "element" ? element.name : element.type;
|
|
4839
|
+
}
|
|
4840
|
+
function collectVisualTextLeaves(nodes, path = [], ancestors = []) {
|
|
4841
|
+
return nodes.flatMap((node, index) => {
|
|
4842
|
+
const currentPath = [...path, index];
|
|
4843
|
+
if (typeof node === "string") return [{ path: currentPath, value: node, ancestors }];
|
|
4844
|
+
if (node.type === "text") return [{ path: currentPath, value: node.value, ancestors }];
|
|
4845
|
+
return collectVisualTextLeaves(node.children ?? [], currentPath, [...ancestors, visualElementName(node)]);
|
|
4846
|
+
});
|
|
4847
|
+
}
|
|
4848
|
+
function updateVisualNodes(nodes, path, update) {
|
|
4849
|
+
if (path.length === 0) return nodes;
|
|
4850
|
+
const [index, ...rest] = path;
|
|
4851
|
+
return nodes.flatMap((node, nodeIndex) => {
|
|
4852
|
+
if (nodeIndex !== index) return [node];
|
|
4853
|
+
if (rest.length === 0) {
|
|
4854
|
+
const next = update(node);
|
|
4855
|
+
return Array.isArray(next) ? next : [next];
|
|
4856
|
+
}
|
|
4857
|
+
if (!isElement(node)) return [node];
|
|
4858
|
+
return [{ ...node, children: updateVisualNodes(node.children ?? [], rest, update) }];
|
|
4859
|
+
});
|
|
4860
|
+
}
|
|
4861
|
+
function updateVisualText(document2, path, value) {
|
|
4862
|
+
return { ...document2, children: updateVisualNodes(document2.children ?? [], path, () => value) };
|
|
4863
|
+
}
|
|
4864
|
+
function wrapVisualText(document2, path, start, end, type, attributes) {
|
|
4865
|
+
return {
|
|
4866
|
+
...document2,
|
|
4867
|
+
children: updateVisualNodes(document2.children ?? [], path, (node) => {
|
|
4868
|
+
const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
|
|
4869
|
+
if (!value || start === end) return node;
|
|
4870
|
+
if (type === "break") {
|
|
4871
|
+
return [value.slice(0, start), { type, attributes, children: [] }, value.slice(start)].filter(
|
|
4872
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
4873
|
+
);
|
|
4874
|
+
}
|
|
4875
|
+
const selected = value.slice(start, end);
|
|
4876
|
+
const wrapper = { type, attributes, children: [selected] };
|
|
4877
|
+
return [value.slice(0, start), wrapper, value.slice(end)].filter(
|
|
4878
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
4879
|
+
);
|
|
4880
|
+
})
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
3833
4883
|
function getMenuPosition(trigger, menu) {
|
|
3834
4884
|
const bounds = trigger.getBoundingClientRect();
|
|
3835
4885
|
const margin = 8;
|
|
@@ -3851,6 +4901,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3851
4901
|
this.toolbarActions = null;
|
|
3852
4902
|
this.display = null;
|
|
3853
4903
|
this.editorContainer = null;
|
|
4904
|
+
this.visualContainer = null;
|
|
3854
4905
|
this.helpPanel = null;
|
|
3855
4906
|
this.openMenu = null;
|
|
3856
4907
|
this.openMenuTrigger = null;
|
|
@@ -3864,6 +4915,8 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3864
4915
|
this.documentState = null;
|
|
3865
4916
|
this.decorationsVisible = false;
|
|
3866
4917
|
this.helpOpen = false;
|
|
4918
|
+
this.visualSelectedPath = null;
|
|
4919
|
+
this.visualSelection = { start: 0, end: 0 };
|
|
3867
4920
|
this.handleDocumentPointerDown = (event) => {
|
|
3868
4921
|
const target = event.target;
|
|
3869
4922
|
if (this.openMenu && target instanceof Node && !this.openMenu.contains(target) && !this.openMenuTrigger?.contains(target)) {
|
|
@@ -3905,6 +4958,12 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3905
4958
|
set locale(locale) {
|
|
3906
4959
|
this.setAttribute("locale", locale);
|
|
3907
4960
|
}
|
|
4961
|
+
get editMode() {
|
|
4962
|
+
return this.getAttribute("edit-mode") === "visual" ? "visual" : "code";
|
|
4963
|
+
}
|
|
4964
|
+
set editMode(mode) {
|
|
4965
|
+
this.setAttribute("edit-mode", mode);
|
|
4966
|
+
}
|
|
3908
4967
|
prepareDocument(value) {
|
|
3909
4968
|
try {
|
|
3910
4969
|
this.documentState = parseSsml(value);
|
|
@@ -3947,6 +5006,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3947
5006
|
}
|
|
3948
5007
|
}
|
|
3949
5008
|
}
|
|
5009
|
+
this.renderVisualEditor();
|
|
3950
5010
|
return;
|
|
3951
5011
|
}
|
|
3952
5012
|
if (name === "theme" && this.monaco) {
|
|
@@ -3964,6 +5024,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3964
5024
|
this.renderHelp();
|
|
3965
5025
|
return;
|
|
3966
5026
|
}
|
|
5027
|
+
if (name === "edit-mode") {
|
|
5028
|
+
this.updateEditMode();
|
|
5029
|
+
}
|
|
3967
5030
|
if (name === "show-decorations") {
|
|
3968
5031
|
this.decorationsVisible = newValue !== null;
|
|
3969
5032
|
this.updateDecorations();
|
|
@@ -3988,7 +5051,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3988
5051
|
display.dataset.ssmlEditorDisplay = "";
|
|
3989
5052
|
const editorContainer = document.createElement("div");
|
|
3990
5053
|
editorContainer.className = "ssml-editor-editor";
|
|
3991
|
-
|
|
5054
|
+
const visualContainer = document.createElement("div");
|
|
5055
|
+
visualContainer.className = "ssml-editor-visual";
|
|
5056
|
+
display.append(editorContainer, visualContainer);
|
|
3992
5057
|
root.append(toolbar, display);
|
|
3993
5058
|
this.replaceChildren(root);
|
|
3994
5059
|
this.root = root;
|
|
@@ -3996,8 +5061,11 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3996
5061
|
this.toolbarActions = toolbarActions;
|
|
3997
5062
|
this.display = display;
|
|
3998
5063
|
this.editorContainer = editorContainer;
|
|
5064
|
+
this.visualContainer = visualContainer;
|
|
3999
5065
|
this.renderToolbar();
|
|
4000
5066
|
this.renderHelp();
|
|
5067
|
+
this.updateEditMode();
|
|
5068
|
+
this.renderVisualEditor();
|
|
4001
5069
|
}
|
|
4002
5070
|
renderToolbar() {
|
|
4003
5071
|
const toolbar = this.toolbar;
|
|
@@ -4041,10 +5109,10 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
4041
5109
|
for (const id of toolbarIds) {
|
|
4042
5110
|
const group = groupByButtonId.get(id);
|
|
4043
5111
|
if (previousGroup !== void 0 && group !== previousGroup) {
|
|
4044
|
-
const
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
toolbarActions.append(
|
|
5112
|
+
const separator2 = document.createElement("span");
|
|
5113
|
+
separator2.className = "ssml-editor-toolbar-separator";
|
|
5114
|
+
separator2.setAttribute("aria-hidden", "true");
|
|
5115
|
+
toolbarActions.append(separator2);
|
|
4048
5116
|
}
|
|
4049
5117
|
previousGroup = group;
|
|
4050
5118
|
const insertion = insertionById.get(id);
|
|
@@ -4056,8 +5124,24 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
4056
5124
|
toolbarActions.append(this.createActionButton(id));
|
|
4057
5125
|
}
|
|
4058
5126
|
}
|
|
5127
|
+
const separator = document.createElement("span");
|
|
5128
|
+
separator.className = "ssml-editor-toolbar-separator";
|
|
5129
|
+
separator.setAttribute("aria-hidden", "true");
|
|
5130
|
+
toolbarActions.append(separator, this.createModeButton("visual", "Visual"), this.createModeButton("code", "Code"));
|
|
4059
5131
|
this.updateActiveButtons();
|
|
4060
5132
|
}
|
|
5133
|
+
createModeButton(mode, label) {
|
|
5134
|
+
const button = document.createElement("button");
|
|
5135
|
+
button.type = "button";
|
|
5136
|
+
button.className = "ssml-editor-toolbar-button";
|
|
5137
|
+
button.dataset.ssmlEditorButton = `edit-mode-${mode}`;
|
|
5138
|
+
button.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
5139
|
+
button.textContent = label;
|
|
5140
|
+
button.addEventListener("click", () => {
|
|
5141
|
+
this.editMode = mode;
|
|
5142
|
+
});
|
|
5143
|
+
return button;
|
|
5144
|
+
}
|
|
4061
5145
|
createActionButton(id) {
|
|
4062
5146
|
const copy = EDITOR_COPY[this.locale];
|
|
4063
5147
|
const labels = {
|
|
@@ -4437,6 +5521,150 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
4437
5521
|
this.root.dataset.theme = isDarkTheme(this.theme) ? "dark" : "light";
|
|
4438
5522
|
}
|
|
4439
5523
|
}
|
|
5524
|
+
updateEditMode() {
|
|
5525
|
+
if (this.editorContainer && this.visualContainer) {
|
|
5526
|
+
const visual = this.editMode === "visual";
|
|
5527
|
+
this.editorContainer.hidden = visual;
|
|
5528
|
+
this.visualContainer.hidden = !visual;
|
|
5529
|
+
}
|
|
5530
|
+
for (const mode of ["visual", "code"]) {
|
|
5531
|
+
const button = this.toolbarActions?.querySelector(
|
|
5532
|
+
`[data-ssml-editor-button="edit-mode-${mode}"]`
|
|
5533
|
+
);
|
|
5534
|
+
button?.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
5535
|
+
button?.toggleAttribute("data-active", this.editMode === mode);
|
|
5536
|
+
}
|
|
5537
|
+
this.renderVisualEditor();
|
|
5538
|
+
}
|
|
5539
|
+
renderVisualEditor() {
|
|
5540
|
+
const container = this.visualContainer;
|
|
5541
|
+
if (!container) return;
|
|
5542
|
+
container.replaceChildren();
|
|
5543
|
+
if (!this.documentState) {
|
|
5544
|
+
const error = document.createElement("p");
|
|
5545
|
+
error.className = "ssml-editor-visual-errors";
|
|
5546
|
+
error.textContent = "SSML syntax must be valid before visual editing is available.";
|
|
5547
|
+
container.append(error);
|
|
5548
|
+
return;
|
|
5549
|
+
}
|
|
5550
|
+
const documentState = this.documentState;
|
|
5551
|
+
const leaves = collectVisualTextLeaves(documentState.children ?? []);
|
|
5552
|
+
const selectedLeaf = leaves.find((leaf) => leaf.path.join(".") === this.visualSelectedPath?.join(".")) ?? leaves[0];
|
|
5553
|
+
const breadcrumb = document.createElement("div");
|
|
5554
|
+
breadcrumb.className = "ssml-editor-visual-breadcrumb";
|
|
5555
|
+
breadcrumb.textContent = `<speak>${selectedLeaf ? ` / ${selectedLeaf.ancestors.map((name) => `<${name}>`).join(" / ")}` : ""}`;
|
|
5556
|
+
const clear = document.createElement("button");
|
|
5557
|
+
clear.type = "button";
|
|
5558
|
+
clear.textContent = "Clear parent";
|
|
5559
|
+
clear.disabled = !this.visualSelectedPath;
|
|
5560
|
+
clear.addEventListener("click", () => {
|
|
5561
|
+
this.visualSelectedPath = null;
|
|
5562
|
+
this.renderVisualEditor();
|
|
5563
|
+
});
|
|
5564
|
+
breadcrumb.append(clear);
|
|
5565
|
+
container.append(breadcrumb);
|
|
5566
|
+
const diagnostics = validateAzureSsml(buildSsml(documentState));
|
|
5567
|
+
if (diagnostics.length > 0) {
|
|
5568
|
+
const errors = document.createElement("div");
|
|
5569
|
+
errors.className = "ssml-editor-visual-errors";
|
|
5570
|
+
errors.setAttribute("role", "alert");
|
|
5571
|
+
for (const diagnostic of diagnostics) {
|
|
5572
|
+
const message = document.createElement("div");
|
|
5573
|
+
message.textContent = diagnostic.message;
|
|
5574
|
+
errors.append(message);
|
|
5575
|
+
}
|
|
5576
|
+
container.append(errors);
|
|
5577
|
+
}
|
|
5578
|
+
const layout = document.createElement("div");
|
|
5579
|
+
layout.className = "ssml-editor-visual-layout";
|
|
5580
|
+
const tree = document.createElement("nav");
|
|
5581
|
+
tree.className = "ssml-editor-visual-tree";
|
|
5582
|
+
tree.setAttribute("aria-label", "SSML structure tree");
|
|
5583
|
+
tree.append(document.createTextNode("Structure"));
|
|
5584
|
+
const treeList = document.createElement("ul");
|
|
5585
|
+
const renderTree = (nodes, parent, parentPath = []) => {
|
|
5586
|
+
nodes.forEach((node, index) => {
|
|
5587
|
+
if (!isElement(node)) return;
|
|
5588
|
+
const path = [...parentPath, index];
|
|
5589
|
+
const item = document.createElement("li");
|
|
5590
|
+
const button = document.createElement("button");
|
|
5591
|
+
button.type = "button";
|
|
5592
|
+
button.textContent = `<${visualElementName(node)}>`;
|
|
5593
|
+
button.dataset.selected = String(path.join(".") === this.visualSelectedPath?.join("."));
|
|
5594
|
+
button.addEventListener("click", () => {
|
|
5595
|
+
this.visualSelectedPath = path;
|
|
5596
|
+
this.renderVisualEditor();
|
|
5597
|
+
});
|
|
5598
|
+
item.append(button);
|
|
5599
|
+
if ((node.children ?? []).some(isElement)) {
|
|
5600
|
+
const childList = document.createElement("ul");
|
|
5601
|
+
renderTree(node.children ?? [], childList, path);
|
|
5602
|
+
item.append(childList);
|
|
5603
|
+
}
|
|
5604
|
+
parent.append(item);
|
|
5605
|
+
});
|
|
5606
|
+
};
|
|
5607
|
+
renderTree(documentState.children ?? [], treeList);
|
|
5608
|
+
tree.append(treeList);
|
|
5609
|
+
layout.append(tree);
|
|
5610
|
+
const form = document.createElement("div");
|
|
5611
|
+
form.className = "ssml-editor-visual-form";
|
|
5612
|
+
if (selectedLeaf) {
|
|
5613
|
+
const label = document.createElement("label");
|
|
5614
|
+
label.append(document.createTextNode("Text"));
|
|
5615
|
+
const textarea = document.createElement("textarea");
|
|
5616
|
+
textarea.value = selectedLeaf.value;
|
|
5617
|
+
textarea.readOnly = this.readonly;
|
|
5618
|
+
textarea.addEventListener("select", () => {
|
|
5619
|
+
this.visualSelection = { start: textarea.selectionStart, end: textarea.selectionEnd };
|
|
5620
|
+
});
|
|
5621
|
+
textarea.addEventListener("input", () => {
|
|
5622
|
+
if (!this.readonly) this.replaceDocument(updateVisualText(documentState, selectedLeaf.path, textarea.value));
|
|
5623
|
+
});
|
|
5624
|
+
label.append(textarea);
|
|
5625
|
+
form.append(label);
|
|
5626
|
+
const actions = document.createElement("div");
|
|
5627
|
+
actions.className = "ssml-editor-visual-actions";
|
|
5628
|
+
const wrappers = [
|
|
5629
|
+
["Rate", "prosody", { rate: "slow" }],
|
|
5630
|
+
["Pitch", "prosody", { pitch: "high" }],
|
|
5631
|
+
["Emotion", "mstts:express-as", { style: "cheerful" }],
|
|
5632
|
+
["Pause", "break", { time: "500ms" }],
|
|
5633
|
+
["Pronunciation", "phoneme", { alphabet: "ipa", ph: selectedLeaf.value }]
|
|
5634
|
+
];
|
|
5635
|
+
for (const [labelText, type, attributes] of wrappers) {
|
|
5636
|
+
const button = document.createElement("button");
|
|
5637
|
+
button.type = "button";
|
|
5638
|
+
button.textContent = labelText;
|
|
5639
|
+
button.disabled = this.readonly;
|
|
5640
|
+
button.addEventListener("click", () => {
|
|
5641
|
+
const start = this.visualSelection.start === this.visualSelection.end ? 0 : this.visualSelection.start;
|
|
5642
|
+
const end = this.visualSelection.start === this.visualSelection.end ? selectedLeaf.value.length : this.visualSelection.end;
|
|
5643
|
+
this.replaceDocument(wrapVisualText(documentState, selectedLeaf.path, start, end, type, attributes));
|
|
5644
|
+
this.renderVisualEditor();
|
|
5645
|
+
});
|
|
5646
|
+
actions.append(button);
|
|
5647
|
+
}
|
|
5648
|
+
const preview = document.createElement("button");
|
|
5649
|
+
preview.type = "button";
|
|
5650
|
+
preview.textContent = "Preview selection";
|
|
5651
|
+
preview.addEventListener("click", () => {
|
|
5652
|
+
this.dispatchEvent(
|
|
5653
|
+
new CustomEvent("preview-selection", {
|
|
5654
|
+
detail: { ssml: buildSsml({ ...documentState, children: [selectedLeaf.value] }) },
|
|
5655
|
+
bubbles: true,
|
|
5656
|
+
composed: true
|
|
5657
|
+
})
|
|
5658
|
+
);
|
|
5659
|
+
});
|
|
5660
|
+
actions.append(preview);
|
|
5661
|
+
form.append(actions);
|
|
5662
|
+
} else {
|
|
5663
|
+
form.textContent = "Select an element or text node to edit it.";
|
|
5664
|
+
}
|
|
5665
|
+
layout.append(form);
|
|
5666
|
+
container.append(layout);
|
|
5667
|
+
}
|
|
4440
5668
|
updateActiveButtons() {
|
|
4441
5669
|
const editor = this.editor;
|
|
4442
5670
|
const model = this.model;
|
|
@@ -4464,6 +5692,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
4464
5692
|
return;
|
|
4465
5693
|
}
|
|
4466
5694
|
const model = monacoModule.editor.createModel(this.prepareDocument(this.value), "xml");
|
|
5695
|
+
this.renderVisualEditor();
|
|
4467
5696
|
const editor = monacoModule.editor.create(container, {
|
|
4468
5697
|
model,
|
|
4469
5698
|
theme: this.theme,
|
|
@@ -4562,7 +5791,8 @@ SsmlEditorElement.observedAttributes = [
|
|
|
4562
5791
|
"locale",
|
|
4563
5792
|
"show-toolbar",
|
|
4564
5793
|
"show-toolbar-labels",
|
|
4565
|
-
"show-decorations"
|
|
5794
|
+
"show-decorations",
|
|
5795
|
+
"edit-mode"
|
|
4566
5796
|
];
|
|
4567
5797
|
|
|
4568
5798
|
// packages/ssml-editor-elements/src/index.ts
|