type TTSMark = { offset: number; name: string; text: string; }; export const parseSSMLMarks = (ssml: string) => { ssml = ssml.replace(/]*>/i, ''); ssml = ssml.replace(/<\/speak>/i, ''); const markRegex = //g; let plainText = ''; const marks: TTSMark[] = []; let match; while ((match = markRegex.exec(ssml)) !== null) { const markTagEndIndex = markRegex.lastIndex; const nextMarkIndex = ssml.indexOf(']+>/g, '') .replace(/\r\n/g, ' ') .replace(/\r/g, ' ') .replace(/\n/g, ' ') .trimStart(); plainText += cleanedChunk; const offset = plainText.length - cleanedChunk.length; const markName = match[1]!; marks.push({ offset, name: markName, text: cleanedChunk }); } return { plainText, marks }; }; export const findSSMLMark = (charIndex: number, marks: TTSMark[]) => { let left = 0; let right = marks.length - 1; let result: TTSMark | null = null; while (left <= right) { const mid = Math.floor((left + right) / 2); const mark = marks[mid]!; if (mark.offset <= charIndex) { result = mark; left = mid + 1; } else { right = mid - 1; } } return result; }; export const parseSSMLLang = (ssml: string): string | null => { const match = ssml.match(/xml:lang\s*=\s*"([^"]+)"/); return match ? match[1]! : null; };