64 lines
2.1 KiB
TypeScript
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;
|
|
}
|