ssml-builder-js 2.2.0 → 2.3.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 +4 -2
- package/dist/{chunk-2XJER2BG.mjs → chunk-NPZ44CHU.mjs} +161 -134
- package/dist/chunk-NPZ44CHU.mjs.map +1 -0
- package/dist/{chunk-I3GP7OJU.mjs → chunk-PSN6XAFF.mjs} +8 -2
- package/dist/chunk-PSN6XAFF.mjs.map +1 -0
- package/dist/core.js +7 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js +7 -1
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +25 -1
- package/dist/react.d.ts +25 -1
- package/dist/react.js +365 -15
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +333 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2XJER2BG.mjs.map +0 -1
- package/dist/chunk-I3GP7OJU.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -137,7 +137,7 @@ const parsed = parseSsml(ssml);
|
|
|
137
137
|
|
|
138
138
|
## `ssml-editor-react` の利用方法
|
|
139
139
|
|
|
140
|
-
`SsmlEditor` は `SsmlDocument` を受け取り、ツールバーと本文の表示エリアだけを表示するシンプルなコンポーネントです。ツールバーから選択範囲の速度、音量、ピッチなどの設定、元に戻す・やり直す操作ができます。音声の選択と表示はアプリ側で行います。本文の編集には Monaco Editor を使用し、変更時に SSML の構文を検証します。構文エラーはエディター上のマーカーとエラーメッセージで表示されます。XML のタグ名やパラメータへホバーすると SSML の説明を確認できます。テキストを選択すると、選択文字数と試聴を行うフローティングアクションが表示されます。`showDecorations` が有効な場合、`break` と `prosody` のタグに間やピッチ変化を示すインラインバッジが表示され、Monaco のインライン装飾も有効になります。生成された SSML は `onSsmlChange` で受け取り、アプリ側で自由に表示できます。`SsmlEditorRef` を `ref` に渡すと、全体、選択範囲、または現在行の SSML を取得できます。画面表示は日本語(デフォルト)と英語に対応しています。
|
|
140
|
+
`SsmlEditor` は `SsmlDocument` を受け取り、ツールバーと本文の表示エリアだけを表示するシンプルなコンポーネントです。ツールバーから選択範囲の速度、音量、ピッチなどの設定、元に戻す・やり直す操作ができます。音声の選択と表示はアプリ側で行います。本文の編集には Monaco Editor を使用し、変更時に SSML の構文を検証します。構文エラーはエディター上のマーカーとエラーメッセージで表示されます。XML のタグ名やパラメータへホバーすると SSML の説明を確認できます。テキストを選択すると、選択文字数と試聴を行うフローティングアクションが表示されます。`enableCodeLens`(デフォルトは `true`)が有効な場合、`prosody` と `break` タグの上に属性編集やタグ操作の CodeLens が表示されます。`showDecorations` が有効な場合、`break` と `prosody` のタグに間やピッチ変化を示すインラインバッジが表示され、Monaco のインライン装飾も有効になります。生成された SSML は `onSsmlChange` で受け取り、アプリ側で自由に表示できます。`SsmlEditorRef` を `ref` に渡すと、全体、選択範囲、または現在行の SSML を取得できます。画面表示は日本語(デフォルト)と英語に対応しています。
|
|
141
141
|
|
|
142
142
|
`<mstts:express-as>` の `style` 属性補完と標準の感情メニューは、カーソルまたは選択範囲を囲む最内の `<voice name="...">` が対応するスタイルだけを表示します。音声名が未指定の場合は全候補を表示し、登録済みでスタイル非対応の音声や未登録の音声では候補がないことを表示します。`emotionStyles` を指定した場合、感情メニューではその値と登録済み音声の対応スタイルの共通部分を使用します。
|
|
143
143
|
|
|
@@ -182,6 +182,7 @@ export function App() {
|
|
|
182
182
|
- `showToolbarIcons`: ツールバーのアイコン表示(デフォルトは `true`)
|
|
183
183
|
- `showToolbarLabels`: ツールバーの文字による説明表示(デフォルトは `false`)。省略時はアイコンにホバーすると説明が表示されます
|
|
184
184
|
- `showDecorations`: 本文中のインライン装飾(バッジや Inlay Hints)の表示(デフォルトは `false`)。ツールバーの「装飾」スイッチで表示・非表示を切り替えられます
|
|
185
|
+
- `enableCodeLens`: `prosody` と `break` タグの CodeLens クイックコントローラーを表示するか(デフォルトは `true`)
|
|
185
186
|
- `buttonVisibility`: ツールバーボタンごとの表示設定。`help`、`break`、`emphasis`、`rate`、`pitch`、`volume`、`emotion`、`say-as`、`lang`、`mstts:silence`、`undo`、`redo`、`clearAll`、`format`、`decorations`、カスタム挿入 ID を指定でき、未指定のボタンは表示されます
|
|
186
187
|
- `editorOptions` / `settings`: Monaco の設定。`height`、`minHeight`、`readOnly`、`theme`(`system` / `light` / `dark`)、`fontSize`、`wordWrap`、`lineNumbers`、`minimap`、`automaticLayout` を指定できます。これらは同名のトップレベル props でも指定できます
|
|
187
188
|
- `loadingFallback`: Monaco の読み込み中に表示する React ノード
|
|
@@ -426,7 +427,7 @@ Typed representations are available for elements such as `voice`, `prosody`, `br
|
|
|
426
427
|
|
|
427
428
|
## Using `ssml-editor-react`
|
|
428
429
|
|
|
429
|
-
`SsmlEditor` accepts an `SsmlDocument` and renders only a toolbar and text display area. The toolbar applies rate, volume, and pitch settings to the selection and provides undo and redo actions. The application is responsible for selecting and displaying the voice. Monaco Editor is used for text editing, and SSML syntax is validated whenever the text changes. Syntax errors are shown with editor markers and an error message. Hovering over XML tag names or parameters shows SSML descriptions. Selecting text displays a floating action bar with the character count and preview action. When `showDecorations` is enabled, inline badges for pause and pitch changes are rendered next to `break` and `prosody` tags, and Monaco inline decorations are enabled. Generated SSML is provided through `onSsmlChange` so the application can display it wherever it needs. Pass an `SsmlEditorRef` through `ref` to retrieve full, selected, or current-line SSML, and use `onSelectionChange` to observe selection text and state. The UI supports Japanese (the default) and English.
|
|
430
|
+
`SsmlEditor` accepts an `SsmlDocument` and renders only a toolbar and text display area. The toolbar applies rate, volume, and pitch settings to the selection and provides undo and redo actions. The application is responsible for selecting and displaying the voice. Monaco Editor is used for text editing, and SSML syntax is validated whenever the text changes. Syntax errors are shown with editor markers and an error message. Hovering over XML tag names or parameters shows SSML descriptions. Selecting text displays a floating action bar with the character count and preview action. When `enableCodeLens` is enabled (the default), CodeLens quick controls for editing and unwrapping `prosody` tags and editing or deleting `break` tags are shown above those tags. When `showDecorations` is enabled, inline badges for pause and pitch changes are rendered next to `break` and `prosody` tags, and Monaco inline decorations are enabled. Generated SSML is provided through `onSsmlChange` so the application can display it wherever it needs. Pass an `SsmlEditorRef` through `ref` to retrieve full, selected, or current-line SSML, and use `onSelectionChange` to observe selection text and state. The UI supports Japanese (the default) and English.
|
|
430
431
|
|
|
431
432
|
Completion for the `<mstts:express-as>` `style` attribute and the built-in Emotion menu only show styles supported by the innermost `<voice name="...">` around the cursor or selection. When no voice name is available, all candidates are shown; a registered voice without supported styles or an explicitly unregistered voice displays an empty-state message. When `emotionStyles` is supplied, the Emotion menu uses its intersection with the registered voice's supported styles.
|
|
432
433
|
|
|
@@ -471,6 +472,7 @@ export function App() {
|
|
|
471
472
|
- `showToolbarIcons`: Whether to show toolbar icons (defaults to `true`)
|
|
472
473
|
- `showToolbarLabels`: Whether to show text labels on the toolbar (defaults to `false`); when omitted, hover over an icon to see its description
|
|
473
474
|
- `showDecorations`: Whether inline decorations such as badges and inlay hints are shown in the text (defaults to `false`); use the **Decorations** toolbar switch to toggle them at runtime
|
|
475
|
+
- `enableCodeLens`: Whether CodeLens quick controls for `prosody` and `break` tags are shown (defaults to `true`)
|
|
474
476
|
- `buttonVisibility`: Per-toolbar-button visibility settings for `help`, `break`, `emphasis`, `rate`, `pitch`, `volume`, `emotion`, `say-as`, `lang`, `mstts:silence`, `undo`, `redo`, `clearAll`, `format`, `decorations`, and custom insertion IDs; unspecified buttons are shown
|
|
475
477
|
- `editorOptions` / `settings`: Monaco settings for `height`, `minHeight`, `readOnly`, `theme` (`system` / `light` / `dark`), `fontSize`, `wordWrap`, `lineNumbers`, `minimap`, and `automaticLayout`. The same settings can also be supplied as top-level props
|
|
476
478
|
- `loadingFallback`: A React node displayed while Monaco is loading
|
|
@@ -375,6 +375,154 @@ var SSML_HOVER_COPY = {
|
|
|
375
375
|
}
|
|
376
376
|
};
|
|
377
377
|
|
|
378
|
+
// packages/ssml-editor-react/src/ssmlContext.ts
|
|
379
|
+
function findTagEnd(source, start, limit) {
|
|
380
|
+
let quote;
|
|
381
|
+
for (let index = start + 1; index < limit; index += 1) {
|
|
382
|
+
const character = source[index];
|
|
383
|
+
if (quote !== void 0) {
|
|
384
|
+
if (character === quote) {
|
|
385
|
+
quote = void 0;
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (character === '"' || character === "'") {
|
|
390
|
+
quote = character;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (character === ">") {
|
|
394
|
+
return index;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return -1;
|
|
398
|
+
}
|
|
399
|
+
function getVoiceName(tag) {
|
|
400
|
+
return tag.match(/\bname\s*=\s*(["'])([\s\S]*?)\1/i)?.[2];
|
|
401
|
+
}
|
|
402
|
+
function escapeXmlAttribute(value) {
|
|
403
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'");
|
|
404
|
+
}
|
|
405
|
+
function updateTagAttribute(text, tagRange, attributeName, newValue) {
|
|
406
|
+
const start = Math.max(0, Math.min(tagRange.start, text.length));
|
|
407
|
+
const end = Math.max(start, Math.min(tagRange.end, text.length));
|
|
408
|
+
const tag = text.slice(start, end);
|
|
409
|
+
const escapedAttributeName = attributeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
410
|
+
const attributePattern = new RegExp(`(\\b${escapedAttributeName}\\s*=\\s*)(["'])([\\s\\S]*?)\\2`, "i");
|
|
411
|
+
const escapedValue = escapeXmlAttribute(newValue);
|
|
412
|
+
const match = tag.match(attributePattern);
|
|
413
|
+
if (match?.index !== void 0) {
|
|
414
|
+
const valueStart = match.index + match[0].indexOf(match[2]) + 1;
|
|
415
|
+
const valueEnd = valueStart + (match[3]?.length ?? 0);
|
|
416
|
+
return `${text.slice(0, start)}${tag.slice(0, valueStart)}${escapedValue}${tag.slice(valueEnd)}${text.slice(end)}`;
|
|
417
|
+
}
|
|
418
|
+
const insertionIndex = tag.endsWith("/>") ? tag.length - 2 : tag.length - 1;
|
|
419
|
+
const attribute = ` ${attributeName}="${escapedValue}"`;
|
|
420
|
+
return `${text.slice(0, start)}${tag.slice(0, insertionIndex)}${attribute}${tag.slice(insertionIndex)}${text.slice(end)}`;
|
|
421
|
+
}
|
|
422
|
+
function closeElement(stack, name) {
|
|
423
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
424
|
+
if (stack[index]?.name === name) {
|
|
425
|
+
stack.splice(index);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function findActiveSsmlTags(source, offset) {
|
|
431
|
+
const limit = Math.max(0, Math.min(offset, source.length));
|
|
432
|
+
const stack = [];
|
|
433
|
+
let index = 0;
|
|
434
|
+
while (index < source.length) {
|
|
435
|
+
const tagStart = source.indexOf("<", index);
|
|
436
|
+
if (tagStart === -1 || tagStart > limit) {
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
const nonContentEnd = source.startsWith("<!--", tagStart) ? source.indexOf("-->", tagStart + 4) : source.startsWith("<![CDATA[", tagStart) ? source.indexOf("]]>", tagStart + 9) : source.startsWith("<?", tagStart) ? source.indexOf("?>", tagStart + 2) : void 0;
|
|
440
|
+
if (nonContentEnd !== void 0) {
|
|
441
|
+
const delimiterLength = source.startsWith("<?", tagStart) ? 2 : 3;
|
|
442
|
+
const end = nonContentEnd === -1 ? source.length : nonContentEnd + delimiterLength;
|
|
443
|
+
if (limit < end) {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
index = end;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const tagEnd = findTagEnd(source, tagStart, source.length);
|
|
450
|
+
const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
|
|
451
|
+
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
452
|
+
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
453
|
+
if (tagEnd === -1 || limit <= tagEnd) {
|
|
454
|
+
if (openingMatch?.[1]) {
|
|
455
|
+
stack.push({ name: openingMatch[1].toLowerCase() });
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
if (closingMatch?.[1]) {
|
|
460
|
+
closeElement(stack, closingMatch[1].toLowerCase());
|
|
461
|
+
} else if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
|
|
462
|
+
stack.push({ name: openingMatch[1].toLowerCase() });
|
|
463
|
+
}
|
|
464
|
+
index = tagEnd + 1;
|
|
465
|
+
}
|
|
466
|
+
return new Set(stack.map(({ name }) => name));
|
|
467
|
+
}
|
|
468
|
+
function findSsmlVoiceContext(source, offset) {
|
|
469
|
+
const limit = Math.max(0, Math.min(offset, source.length));
|
|
470
|
+
const stack = [];
|
|
471
|
+
let index = 0;
|
|
472
|
+
while (index < limit) {
|
|
473
|
+
const tagStart = source.indexOf("<", index);
|
|
474
|
+
if (tagStart === -1 || tagStart >= limit) {
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
if (source.startsWith("<!--", tagStart)) {
|
|
478
|
+
const end = source.indexOf("-->", tagStart + 4);
|
|
479
|
+
index = end === -1 || end + 3 > limit ? limit : end + 3;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (source.startsWith("<![CDATA[", tagStart)) {
|
|
483
|
+
const end = source.indexOf("]]>", tagStart + 9);
|
|
484
|
+
index = end === -1 || end + 3 > limit ? limit : end + 3;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (source.startsWith("<?", tagStart)) {
|
|
488
|
+
const end = source.indexOf("?>", tagStart + 2);
|
|
489
|
+
index = end === -1 || end + 2 > limit ? limit : end + 2;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const tagEnd = findTagEnd(source, tagStart, limit);
|
|
493
|
+
if (tagEnd === -1) {
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
const tag = source.slice(tagStart, tagEnd + 1);
|
|
497
|
+
if (tag.startsWith("<!")) {
|
|
498
|
+
index = tagEnd + 1;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
502
|
+
if (closingMatch?.[1]) {
|
|
503
|
+
closeElement(stack, closingMatch[1].toLowerCase());
|
|
504
|
+
index = tagEnd + 1;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
508
|
+
if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
|
|
509
|
+
const name = openingMatch[1].toLowerCase();
|
|
510
|
+
stack.push({
|
|
511
|
+
name,
|
|
512
|
+
...name === "voice" ? { voiceName: getVoiceName(tag) } : {}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
index = tagEnd + 1;
|
|
516
|
+
}
|
|
517
|
+
for (let stackIndex = stack.length - 1; stackIndex >= 0; stackIndex -= 1) {
|
|
518
|
+
const element = stack[stackIndex];
|
|
519
|
+
if (element?.name === "voice") {
|
|
520
|
+
return element.voiceName === void 0 ? {} : { voiceName: element.voiceName };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return void 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
378
526
|
// packages/ssml-editor-react/src/constants/ssmlPresets.ts
|
|
379
527
|
var ssmlPresets_exports = {};
|
|
380
528
|
__export(ssmlPresets_exports, {
|
|
@@ -1091,6 +1239,7 @@ var SSML_ATTRS = {
|
|
|
1091
1239
|
STYLE: "style",
|
|
1092
1240
|
STYLE_DEGREE: "styledegree",
|
|
1093
1241
|
STYLE_DEGREE_CAMEL: "styleDegree",
|
|
1242
|
+
STYLE_DEGREE_HYPHEN: "style-degree",
|
|
1094
1243
|
ROLE: "role",
|
|
1095
1244
|
INTERPRET_AS: "interpret-as",
|
|
1096
1245
|
FORMAT: "format",
|
|
@@ -1634,7 +1783,12 @@ function convertElement(node) {
|
|
|
1634
1783
|
case SSML_TAGS.MSTTS_EXPRESS_AS: {
|
|
1635
1784
|
const element = { type: node.name };
|
|
1636
1785
|
const style = readAttribute(attributes, SSML_ATTRS.STYLE);
|
|
1637
|
-
const styleDegree = readAttribute(
|
|
1786
|
+
const styleDegree = readAttribute(
|
|
1787
|
+
attributes,
|
|
1788
|
+
SSML_ATTRS.STYLE_DEGREE,
|
|
1789
|
+
SSML_ATTRS.STYLE_DEGREE_CAMEL,
|
|
1790
|
+
SSML_ATTRS.STYLE_DEGREE_HYPHEN
|
|
1791
|
+
);
|
|
1638
1792
|
const role = readAttribute(attributes, SSML_ATTRS.ROLE);
|
|
1639
1793
|
if (style !== void 0) element.style = style;
|
|
1640
1794
|
if (styleDegree !== void 0) element.styleDegree = styleDegree;
|
|
@@ -1991,7 +2145,7 @@ function validateEntityReferences(value) {
|
|
|
1991
2145
|
index = semicolon + 1;
|
|
1992
2146
|
}
|
|
1993
2147
|
}
|
|
1994
|
-
function
|
|
2148
|
+
function findTagEnd2(source, start, hasInternalSubset = false) {
|
|
1995
2149
|
let quote;
|
|
1996
2150
|
let subsetDepth = 0;
|
|
1997
2151
|
for (let index = start + 1; index < source.length; index += 1) {
|
|
@@ -2164,7 +2318,7 @@ function parseXml(source) {
|
|
|
2164
2318
|
continue;
|
|
2165
2319
|
}
|
|
2166
2320
|
if (source.startsWith("</", index)) {
|
|
2167
|
-
const end2 =
|
|
2321
|
+
const end2 = findTagEnd2(source, index);
|
|
2168
2322
|
const name2 = parseClosingTag(source, index, end2);
|
|
2169
2323
|
const element = currentElement(stack);
|
|
2170
2324
|
if (element === void 0 || element.name !== name2) {
|
|
@@ -2176,7 +2330,7 @@ function parseXml(source) {
|
|
|
2176
2330
|
continue;
|
|
2177
2331
|
}
|
|
2178
2332
|
if (source.startsWith("<!", index)) {
|
|
2179
|
-
const end2 =
|
|
2333
|
+
const end2 = findTagEnd2(source, index, true);
|
|
2180
2334
|
parseDeclaration(source, index, end2);
|
|
2181
2335
|
appendNode2(currentElement(stack)?.children ?? children, {
|
|
2182
2336
|
kind: "declaration",
|
|
@@ -2185,7 +2339,7 @@ function parseXml(source) {
|
|
|
2185
2339
|
index = end2 + 1;
|
|
2186
2340
|
continue;
|
|
2187
2341
|
}
|
|
2188
|
-
const end =
|
|
2342
|
+
const end = findTagEnd2(source, index);
|
|
2189
2343
|
const { name, selfClosing } = parseStartTag(source, index, end);
|
|
2190
2344
|
if (stack.length === 0) {
|
|
2191
2345
|
if (root !== void 0) {
|
|
@@ -2624,134 +2778,6 @@ function createSsmlInsertionEdit(source, startOffset, endOffset, template, eol =
|
|
|
2624
2778
|
};
|
|
2625
2779
|
}
|
|
2626
2780
|
|
|
2627
|
-
// packages/ssml-editor-react/src/ssmlContext.ts
|
|
2628
|
-
function findTagEnd2(source, start, limit) {
|
|
2629
|
-
let quote;
|
|
2630
|
-
for (let index = start + 1; index < limit; index += 1) {
|
|
2631
|
-
const character = source[index];
|
|
2632
|
-
if (quote !== void 0) {
|
|
2633
|
-
if (character === quote) {
|
|
2634
|
-
quote = void 0;
|
|
2635
|
-
}
|
|
2636
|
-
continue;
|
|
2637
|
-
}
|
|
2638
|
-
if (character === '"' || character === "'") {
|
|
2639
|
-
quote = character;
|
|
2640
|
-
continue;
|
|
2641
|
-
}
|
|
2642
|
-
if (character === ">") {
|
|
2643
|
-
return index;
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
return -1;
|
|
2647
|
-
}
|
|
2648
|
-
function getVoiceName(tag) {
|
|
2649
|
-
return tag.match(/\bname\s*=\s*(["'])([\s\S]*?)\1/i)?.[2];
|
|
2650
|
-
}
|
|
2651
|
-
function closeElement(stack, name) {
|
|
2652
|
-
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
2653
|
-
if (stack[index]?.name === name) {
|
|
2654
|
-
stack.splice(index);
|
|
2655
|
-
return;
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
function findActiveSsmlTags(source, offset) {
|
|
2660
|
-
const limit = Math.max(0, Math.min(offset, source.length));
|
|
2661
|
-
const stack = [];
|
|
2662
|
-
let index = 0;
|
|
2663
|
-
while (index < source.length) {
|
|
2664
|
-
const tagStart = source.indexOf("<", index);
|
|
2665
|
-
if (tagStart === -1 || tagStart > limit) {
|
|
2666
|
-
break;
|
|
2667
|
-
}
|
|
2668
|
-
const nonContentEnd = source.startsWith("<!--", tagStart) ? source.indexOf("-->", tagStart + 4) : source.startsWith("<![CDATA[", tagStart) ? source.indexOf("]]>", tagStart + 9) : source.startsWith("<?", tagStart) ? source.indexOf("?>", tagStart + 2) : void 0;
|
|
2669
|
-
if (nonContentEnd !== void 0) {
|
|
2670
|
-
const delimiterLength = source.startsWith("<?", tagStart) ? 2 : 3;
|
|
2671
|
-
const end = nonContentEnd === -1 ? source.length : nonContentEnd + delimiterLength;
|
|
2672
|
-
if (limit < end) {
|
|
2673
|
-
break;
|
|
2674
|
-
}
|
|
2675
|
-
index = end;
|
|
2676
|
-
continue;
|
|
2677
|
-
}
|
|
2678
|
-
const tagEnd = findTagEnd2(source, tagStart, source.length);
|
|
2679
|
-
const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
|
|
2680
|
-
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
2681
|
-
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
2682
|
-
if (tagEnd === -1 || limit <= tagEnd) {
|
|
2683
|
-
if (openingMatch?.[1]) {
|
|
2684
|
-
stack.push({ name: openingMatch[1].toLowerCase() });
|
|
2685
|
-
}
|
|
2686
|
-
break;
|
|
2687
|
-
}
|
|
2688
|
-
if (closingMatch?.[1]) {
|
|
2689
|
-
closeElement(stack, closingMatch[1].toLowerCase());
|
|
2690
|
-
} else if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
|
|
2691
|
-
stack.push({ name: openingMatch[1].toLowerCase() });
|
|
2692
|
-
}
|
|
2693
|
-
index = tagEnd + 1;
|
|
2694
|
-
}
|
|
2695
|
-
return new Set(stack.map(({ name }) => name));
|
|
2696
|
-
}
|
|
2697
|
-
function findSsmlVoiceContext(source, offset) {
|
|
2698
|
-
const limit = Math.max(0, Math.min(offset, source.length));
|
|
2699
|
-
const stack = [];
|
|
2700
|
-
let index = 0;
|
|
2701
|
-
while (index < limit) {
|
|
2702
|
-
const tagStart = source.indexOf("<", index);
|
|
2703
|
-
if (tagStart === -1 || tagStart >= limit) {
|
|
2704
|
-
break;
|
|
2705
|
-
}
|
|
2706
|
-
if (source.startsWith("<!--", tagStart)) {
|
|
2707
|
-
const end = source.indexOf("-->", tagStart + 4);
|
|
2708
|
-
index = end === -1 || end + 3 > limit ? limit : end + 3;
|
|
2709
|
-
continue;
|
|
2710
|
-
}
|
|
2711
|
-
if (source.startsWith("<![CDATA[", tagStart)) {
|
|
2712
|
-
const end = source.indexOf("]]>", tagStart + 9);
|
|
2713
|
-
index = end === -1 || end + 3 > limit ? limit : end + 3;
|
|
2714
|
-
continue;
|
|
2715
|
-
}
|
|
2716
|
-
if (source.startsWith("<?", tagStart)) {
|
|
2717
|
-
const end = source.indexOf("?>", tagStart + 2);
|
|
2718
|
-
index = end === -1 || end + 2 > limit ? limit : end + 2;
|
|
2719
|
-
continue;
|
|
2720
|
-
}
|
|
2721
|
-
const tagEnd = findTagEnd2(source, tagStart, limit);
|
|
2722
|
-
if (tagEnd === -1) {
|
|
2723
|
-
break;
|
|
2724
|
-
}
|
|
2725
|
-
const tag = source.slice(tagStart, tagEnd + 1);
|
|
2726
|
-
if (tag.startsWith("<!")) {
|
|
2727
|
-
index = tagEnd + 1;
|
|
2728
|
-
continue;
|
|
2729
|
-
}
|
|
2730
|
-
const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
2731
|
-
if (closingMatch?.[1]) {
|
|
2732
|
-
closeElement(stack, closingMatch[1].toLowerCase());
|
|
2733
|
-
index = tagEnd + 1;
|
|
2734
|
-
continue;
|
|
2735
|
-
}
|
|
2736
|
-
const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
|
|
2737
|
-
if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
|
|
2738
|
-
const name = openingMatch[1].toLowerCase();
|
|
2739
|
-
stack.push({
|
|
2740
|
-
name,
|
|
2741
|
-
...name === "voice" ? { voiceName: getVoiceName(tag) } : {}
|
|
2742
|
-
});
|
|
2743
|
-
}
|
|
2744
|
-
index = tagEnd + 1;
|
|
2745
|
-
}
|
|
2746
|
-
for (let stackIndex = stack.length - 1; stackIndex >= 0; stackIndex -= 1) {
|
|
2747
|
-
const element = stack[stackIndex];
|
|
2748
|
-
if (element?.name === "voice") {
|
|
2749
|
-
return element.voiceName === void 0 ? {} : { voiceName: element.voiceName };
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
return void 0;
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
2781
|
// packages/ssml-editor-react/src/ssmlCompletion.ts
|
|
2756
2782
|
var SSML_ATTRIBUTE_VALUE_PATTERN = /<([\w:-]+)\s+[^>]*?\b([\w:-]+)=["']([^"']*)$/i;
|
|
2757
2783
|
var EXPRESS_AS_TAG_NAMES = /* @__PURE__ */ new Set(["mstts:express-as", "express-as", "expressas"]);
|
|
@@ -3477,10 +3503,11 @@ export {
|
|
|
3477
3503
|
getEditableText,
|
|
3478
3504
|
updateEditableText,
|
|
3479
3505
|
createSsmlInsertionEdit,
|
|
3506
|
+
updateTagAttribute,
|
|
3480
3507
|
findActiveSsmlTags,
|
|
3481
3508
|
findSsmlVoiceContext,
|
|
3482
3509
|
registerSsmlCompletionProvider,
|
|
3483
3510
|
findSsmlHoverTarget,
|
|
3484
3511
|
formatSsmlHover
|
|
3485
3512
|
};
|
|
3486
|
-
//# sourceMappingURL=chunk-
|
|
3513
|
+
//# sourceMappingURL=chunk-NPZ44CHU.mjs.map
|