Files
Jyotisha/frontend/src/lib/sentence-split.ts
T

64 lines
2.1 KiB
TypeScript

/**
* Split after sentence punctuation and keep the punctuation on the left piece.
*
* Replaces lookbehind splits. Those are syntax errors on Safari before 16.4,
* so this file must not grow one. A newline inside `punctuation`
* is a kept boundary. `splitOnNewlines` instead discards `\n+` runs. Do not
* combine that option with a newline in `punctuation`.
*/
export type SentenceSplitOptions = {
/** Drop whitespace after the kept punctuation. Same as a trailing `\s*`. */
consumeFollowingWhitespace?: boolean;
/** Discard `\n+` even when the newline is not after punctuation. */
splitOnNewlines?: boolean;
};
function isJsWhitespace(char: string): boolean {
return char.length === 1 && /\s/u.test(char);
}
export function splitAfterSentencePunctuation(
text: string,
punctuation: string,
options?: SentenceSplitOptions,
): string[] {
const consumeFollowingWhitespace = options?.consumeFollowingWhitespace === true;
const splitOnNewlines = options?.splitOnNewlines === true;
const marks = new Set(punctuation);
const parts: string[] = [];
let start = 0;
let index = 0;
while (index < text.length) {
const char = text.charAt(index);
if (marks.has(char)) {
const boundaryEnd = index + 1;
let delimiterEnd = boundaryEnd;
if (consumeFollowingWhitespace) {
while (delimiterEnd < text.length && isJsWhitespace(text.charAt(delimiterEnd))) {
delimiterEnd += 1;
}
}
// A zero-width match at the end of the string does not add a trailing empty piece.
if (delimiterEnd === text.length && delimiterEnd === boundaryEnd) break;
parts.push(text.slice(start, boundaryEnd));
start = delimiterEnd;
index = delimiterEnd;
continue;
}
if (splitOnNewlines && char === "\n") {
let newlineEnd = index + 1;
while (newlineEnd < text.length && text.charAt(newlineEnd) === "\n") newlineEnd += 1;
parts.push(text.slice(start, index));
start = newlineEnd;
index = newlineEnd;
continue;
}
index += 1;
}
parts.push(text.slice(start));
return parts;
}