@echogarden/text-segmentation 0.7.0 → 0.8.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.
Files changed (34) hide show
  1. package/dist/exports/Exports.d.ts +3 -0
  2. package/dist/exports/Exports.d.ts.map +1 -0
  3. package/dist/exports/Exports.js +3 -0
  4. package/dist/exports/Exports.js.map +1 -0
  5. package/dist/{exports → segmentation}/TextSegmentation.d.ts +0 -1
  6. package/dist/segmentation/TextSegmentation.d.ts.map +1 -0
  7. package/dist/segmentation/TextSegmentation.js +575 -0
  8. package/dist/segmentation/TextSegmentation.js.map +1 -0
  9. package/dist/segmentation/WordSequence.d.ts.map +1 -0
  10. package/dist/segmentation/WordSequence.js.map +1 -0
  11. package/dist/tests/Test.js +2 -2
  12. package/dist/tests/Test.js.map +1 -1
  13. package/dist/utilities/Timer.d.ts +7 -3
  14. package/dist/utilities/Timer.d.ts.map +1 -1
  15. package/dist/utilities/Timer.js +41 -39
  16. package/dist/utilities/Timer.js.map +1 -1
  17. package/package.json +4 -4
  18. package/src/exports/Exports.ts +2 -0
  19. package/src/segmentation/TextSegmentation.ts +743 -0
  20. package/src/tests/Test.ts +2 -2
  21. package/src/utilities/Timer.ts +50 -48
  22. package/dist/Test.d.ts +0 -2
  23. package/dist/Test.d.ts.map +0 -1
  24. package/dist/Test.js +0 -120
  25. package/dist/Test.js.map +0 -1
  26. package/dist/exports/TextSegmentation.d.ts.map +0 -1
  27. package/dist/exports/TextSegmentation.js +0 -369
  28. package/dist/exports/TextSegmentation.js.map +0 -1
  29. package/dist/exports/WordSequence.d.ts.map +0 -1
  30. package/dist/exports/WordSequence.js.map +0 -1
  31. package/src/exports/TextSegmentation.ts +0 -527
  32. /package/dist/{exports → segmentation}/WordSequence.d.ts +0 -0
  33. /package/dist/{exports → segmentation}/WordSequence.js +0 -0
  34. /package/src/{exports → segmentation}/WordSequence.ts +0 -0
package/src/tests/Test.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { addMissingPunctuationWordsToWordSequence, SegmentationOptions, SegmentationResult, segmentText, segmentWordSequence, splitToWords, WordSequence } from "../exports/TextSegmentation.js"
2
- import { Timer } from "../utilities/Timer.js"
1
+ import { addMissingPunctuationWordsToWordSequence, SegmentationOptions, SegmentationResult, segmentText, segmentWordSequence, splitToWords, WordSequence } from '../exports/Exports.js'
2
+ import { Timer } from '../utilities/Timer.js'
3
3
 
4
4
  const log = console.log
5
5
 
@@ -1,95 +1,97 @@
1
- import { roundToDigits } from "./Utilities.js"
1
+ export class Timer {
2
+ private readonly logger: TimerLogger
2
3
 
3
- declare const chrome: any
4
- declare const process: any
4
+ private startTime = 0
5
5
 
6
- export class Timer {
7
- startTime = 0
6
+ constructor(logger?: TimerLogger) {
7
+ if (logger) {
8
+ this.logger = logger
9
+ } else {
10
+ this.logger = console.log
11
+ }
8
12
 
9
- constructor() {
10
13
  this.restart()
11
14
  }
12
15
 
13
- restart() {
16
+ // Resets the timer to the current time.
17
+ restart(): void {
14
18
  this.startTime = Timer.currentTime
15
19
  }
16
20
 
21
+ // Elapsed time in milliseconds (monotonic where supported).
17
22
  get elapsedTime(): number {
18
- // Elapsed time (milliseconds)
19
23
  return Timer.currentTime - this.startTime
20
24
  }
21
25
 
26
+ // Elapsed time in seconds.
22
27
  get elapsedTimeSeconds(): number {
23
- // Elapsed time (seconds)
24
28
  return this.elapsedTime / 1000
25
29
  }
26
30
 
31
+ // Returns elapsed ms and restarts the timer.
27
32
  getElapsedTimeAndRestart(): number {
28
- const elapsedTime = this.elapsedTime
33
+ const elapsed = this.elapsedTime
29
34
  this.restart()
30
35
 
31
- return elapsedTime
36
+ return elapsed
32
37
  }
33
38
 
39
+ // Logs elapsed time (in ms) and restarts the timer.
34
40
  logAndRestart(title: string, timePrecision = 3): number {
35
- const elapsedTime = this.elapsedTime
36
-
37
- //
38
- const message = `${title}: ${roundToDigits(elapsedTime, timePrecision)}ms`
39
-
40
- console.log(message)
41
- //
42
-
41
+ const elapsedMs = this.elapsedTime
42
+ this.logger(`${title}: ${roundToDigits(elapsedMs, timePrecision)}ms`)
43
43
  this.restart()
44
44
 
45
- return elapsedTime
45
+ return elapsedMs
46
46
  }
47
47
 
48
+ // Current high-resolution timestamp in milliseconds since Unix epoch.
48
49
  static get currentTime(): number {
49
- if (!this.timestampFunc) {
50
- this.createGlobalTimestampFunction()
51
- }
52
-
53
50
  return this.timestampFunc()
54
51
  }
55
52
 
53
+ // Current timestamp in microseconds (integer).
56
54
  static get microsecondTimestamp(): number {
57
55
  return Math.floor(Timer.currentTime * 1000)
58
56
  }
59
57
 
60
- private static createGlobalTimestampFunction() {
61
- if (typeof process === 'object' && typeof process.hrtime === 'function') {
62
- let baseTimestamp = 0
58
+ // Clock setup
59
+ private static timestampFunc: () => number = Timer.createTimestampFunction()
63
60
 
64
- this.timestampFunc = () => {
65
- const nodeTimeStamp = process.hrtime()
66
- const millisecondTime = (nodeTimeStamp[0] * 1000) + (nodeTimeStamp[1] / 1000000)
61
+ private static createTimestampFunction(): () => number {
62
+ const g = globalThis as any
67
63
 
68
- return baseTimestamp + millisecondTime
69
- }
64
+ // 1. Modern standard: performance.now() (Browsers & Node 16+)
65
+ if (typeof g.performance === 'object' && typeof g.performance.now === 'function') {
66
+ const timeOrigin =
67
+ g.performance.timeOrigin ?? (Date.now() - g.performance.now())
70
68
 
71
- baseTimestamp = Date.now() - this.timestampFunc()
69
+ return () => timeOrigin + g.performance.now()
72
70
  }
73
- else if (typeof chrome === 'object' && chrome.Interval) {
74
- const baseTimestamp = Date.now()
75
71
 
76
- const chromeIntervalObject = new chrome.Interval()
77
- chromeIntervalObject.start()
72
+ // 2. Node.js high resolution timer (BigInt variant, Node 10.4+)
73
+ if (typeof g.process === 'object' && typeof g.process.hrtime === 'function') {
74
+ const startNs = g.process.hrtime.bigint()
78
75
 
79
- this.timestampFunc = () => baseTimestamp + chromeIntervalObject.microseconds() / 1000
80
- }
81
- else if (typeof performance === 'object' && performance.now) {
82
- const baseTimestamp = Date.now() - performance.now()
76
+ const epochBaseMs = Date.now() - (Number(startNs) / 1e6)
83
77
 
84
- this.timestampFunc = () => baseTimestamp + performance.now()
85
- }
86
- else if (Date.now) {
87
- this.timestampFunc = () => Date.now()
78
+ return () =>
79
+ epochBaseMs + Number(g.process.hrtime.bigint()) / 1e6
88
80
  }
89
- else {
90
- this.timestampFunc = () => (new Date()).getTime()
81
+
82
+ // 3. Last-resort fallback (non-monotonic)
83
+ if (typeof Date.now === 'function') {
84
+ return () => Date.now()
91
85
  }
86
+
87
+ return () => new Date().getTime()
92
88
  }
89
+ }
90
+
91
+ export function roundToDigits(value: number, digits: number): number {
92
+ const factor = 10 ** digits
93
93
 
94
- private static timestampFunc: () => number
94
+ return Math.round(value * factor) / factor
95
95
  }
96
+
97
+ type TimerLogger = (msg: string) => void
package/dist/Test.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=Test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Test.d.ts","sourceRoot":"","sources":["../src/Test.ts"],"names":[],"mappings":""}
package/dist/Test.js DELETED
@@ -1,120 +0,0 @@
1
- import { addMissingPunctuationWordsToWordSequence, segmentText, segmentWordSequence, splitToWords, WordSequence } from "./exports/TextSegmentation.js";
2
- import { Timer } from "./utilities/Timer.js";
3
- const log = console.log;
4
- async function test1() {
5
- //const text = 'Hello! 1. Say who? 2. How are you? This is v2.0 that good. I have 2 344 234ms C# is good games'
6
- const text = 'the 6v34abc6g6 😊😊 -43.45 x -2445.65rty the';
7
- //const text = 'Hello world! 23rd? 123% 3/4/7 Привет мир! 你好世界!'
8
- const options = {
9
- language: 'en'
10
- };
11
- const wordSequence = await splitToWords(text, options);
12
- console.log(JSON.stringify(wordSequence.wordArray));
13
- const segmentedText = await segmentWordSequence(wordSequence);
14
- const x = 0;
15
- }
16
- async function test2() {
17
- const { readFileSync, writeFileSync } = await import('fs');
18
- const text = readFileSync('test-data/Japanese3.txt', 'utf-8');
19
- const timer = new Timer();
20
- let result;
21
- for (let i = 0; i < 5; i++) {
22
- result = await segmentText(text, {
23
- language: 'en',
24
- customSuppressions: [],
25
- enableEastAsianPostprocessing: true
26
- });
27
- timer.logAndRestart(`Total execution time`);
28
- }
29
- log('');
30
- //
31
- let segmentedText = '';
32
- for (let sentenceIndex = 0; sentenceIndex < result.sentences.length; sentenceIndex++) {
33
- const sentence = result.sentences[sentenceIndex];
34
- const phrases = sentence.phrases;
35
- for (let phraseIndex = 0; phraseIndex < phrases.length; phraseIndex++) {
36
- const phrase = phrases[phraseIndex];
37
- segmentedText += phrase.words.wordArray.join(' | ');
38
- if (phraseIndex < phrases.length - 1) {
39
- segmentedText += `\n${'-'.repeat(100)}\n`;
40
- }
41
- }
42
- if (sentenceIndex < result.sentences.length - 1) {
43
- segmentedText += `\n${'='.repeat(100)} \n`;
44
- }
45
- }
46
- writeFileSync('out/segmented.txt', segmentedText);
47
- }
48
- async function test3() {
49
- const text = ` Hello, how are you today ?? `;
50
- const words = await splitToWords(text);
51
- const nonPunctuationWordEntries = words.nonPunctuationEntries;
52
- const nonPunctuationWordSequence = new WordSequence();
53
- nonPunctuationWordSequence.entries = nonPunctuationWordEntries;
54
- const extendedWords = addMissingPunctuationWordsToWordSequence(nonPunctuationWordSequence, text);
55
- const x = 0;
56
- }
57
- async function test4() {
58
- const text = `
59
- Hello 12/43 yo good-go bobo_baba man!
60
- 2.4a, 5.6 x&y x'v·y v‧z x·y·5
61
- abc123 23+42.534 645
62
- 年代主演兩123部電影系列後
63
- 2004年-12月,公园被国家旅游局评定为国家4A级旅游景区.
64
- ah'f.bf5.c.d.
65
- -345.45%
66
-
67
- Hello 1/ how are you?
68
- 76.54af's567
69
- in an 8.2x8x3 grid
70
-
71
- 5343.234$
72
-
73
- Hello World. How are you?
74
-
75
- x·y·z
76
- 756.534-54
77
-
78
- This is 23 GB.
79
-
80
- That’s great if you want to cram as many of your friends’ genomes 'cause that's not.
81
-
82
- I like C# and C++ languages!
83
-
84
- Price is $60 or 60$
85
-
86
- `;
87
- const result = (await splitToWords(text)).wordArray;
88
- log(result.join(' | '));
89
- }
90
- async function test5() {
91
- const text1 = `
92
- A.B.C.D.E.
93
-
94
- This A.I. techonology, tells the Dr. goodbye and nothing else. Yes.
95
-
96
- ただ
97
-
98
-
99
-
100
- 街の
101
- `;
102
- const text2 = `
103
-
104
-
105
- Hello World. Yo!
106
- \t
107
-
108
- gooooogoo
109
-
110
- , Hi. BOBO.
111
- `;
112
- const wordSequence = await splitToWords(text2, { language: 'en' });
113
- const result = await segmentWordSequence(wordSequence);
114
- const sentencesText = result.sentences.map(x => x.text);
115
- log(sentencesText.join(' \n '));
116
- }
117
- //test1()
118
- //test2()
119
- test5();
120
- //# sourceMappingURL=Test.js.map
package/dist/Test.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"Test.js","sourceRoot":"","sources":["../src/Test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"TextSegmentation.d.ts","sourceRoot":"","sources":["../../src/exports/TextSegmentation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAOhE,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,+BAI5E;AAED,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,+BAA+B,+BAoL9G;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,yBAmF7E;AAmDD,wBAAgB,wCAAwC,CAAC,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM;;;EAsDtG;AA+ED,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,YAAY,CAAA;IACnB,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,qBAAqB,EAAE,KAAK,EAAE,CAAA;CAC9B;AAED,qBAAa,YAAY;IACxB,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,YAAY,CAAA;IAEnB,YAAY,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAGhD;IAED,IAAI,IAAI,WAEP;IAED,IAAI,SAAS,IAAI,KAAK,CAKrB;CACD;AAED,qBAAa,QAAS,SAAQ,YAAY;IACzC,OAAO,EAAE,MAAM,EAAE,CAAK;CACtB;AAED,qBAAa,MAAO,SAAQ,YAAY;CACvC;AAED,MAAM,WAAW,KAAK;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,6BAA6B,CAAC,EAAE,OAAO,CAAA;CACvC;AAED,eAAO,MAAM,0BAA0B,EAAE,mBAIxC,CAAA;AAED,MAAM,WAAW,+BAA+B;IAC/C,mCAAmC,EAAE,OAAO,CAAA;CAC5C;AAED,eAAO,MAAM,sCAAsC,EAAE,+BAEpD,CAAA"}
@@ -1,369 +0,0 @@
1
- import { buildWordOrNumberPattern as buildWordSplitterPattern, phraseSeparatorRegExp, sentenceSeparatorTrailingPunctuationCharacterRegExp, sentenceSeparatorCharacterRegExp, whitespacePatternRegExp, letterPatternGlobalRegExp } from '../patterns/Patterns.js';
2
- import { cldrSuppressions, additionalSuppressions, leadingApostropheContractionSuppressions, nounSuppressions, tldSuppressions } from '../patterns/Suppressions.js';
3
- import { eastAsianCharRangesRegExp } from '../patterns/EastAsianCharacterPatterns.js';
4
- import { WordSequence } from './WordSequence.js';
5
- import { getShortLanguageCode } from '../utilities/Utilities.js';
6
- export { WordSequence } from './WordSequence.js';
7
- import { buildRegExp } from 'regexp-composer';
8
- ////////////////////////////////////////////////////////////////////////////////////////////////
9
- // Exported methods
10
- ////////////////////////////////////////////////////////////////////////////////////////////////
11
- export async function segmentText(text, options) {
12
- const wordSequence = await splitToWords(text, options);
13
- return segmentWordSequence(wordSequence);
14
- }
15
- export async function segmentWordSequence(wordSequence, options) {
16
- options = { ...defaultWordSequenceSegmentationOptions, ...options };
17
- const segmentWordRanges = [];
18
- {
19
- let segmentStartWordOffset = 0;
20
- let nonWhitespaceWordSeen = false;
21
- let newlineSeenInCurrentSegment = false;
22
- for (let wordIndex = 0; wordIndex < wordSequence.length; wordIndex++) {
23
- const word = wordSequence.getWordAt(wordIndex);
24
- if (!newlineSeenInCurrentSegment && nonWhitespaceWordSeen && word.endsWith('\n')) {
25
- newlineSeenInCurrentSegment = true;
26
- }
27
- if (newlineSeenInCurrentSegment) {
28
- const nextWord = wordIndex < wordSequence.length - 1 ? wordSequence.getWordAt(wordIndex + 1) : '';
29
- if (nextWord === '' || !whitespacePatternRegExp.test(nextWord)) {
30
- segmentWordRanges.push({ start: segmentStartWordOffset, end: wordIndex + 1 });
31
- segmentStartWordOffset = wordIndex + 1;
32
- newlineSeenInCurrentSegment = false;
33
- }
34
- }
35
- if (nonWhitespaceWordSeen === false) {
36
- nonWhitespaceWordSeen = !whitespacePatternRegExp.test(word);
37
- }
38
- }
39
- if (segmentStartWordOffset < wordSequence.length) {
40
- segmentWordRanges.push({ start: segmentStartWordOffset, end: wordSequence.length });
41
- }
42
- }
43
- const sentenceWordRanges = [];
44
- const segmentSentenceRanges = [];
45
- {
46
- const minimumSentenceLetterCount = 2;
47
- for (let segmentIndex = 0; segmentIndex < segmentWordRanges.length; segmentIndex++) {
48
- const segmentWordRange = segmentWordRanges[segmentIndex];
49
- const segmentStartWordIndex = segmentWordRange.start;
50
- const segmentEndWordIndex = segmentWordRange.end;
51
- segmentSentenceRanges.push({ start: sentenceWordRanges.length, end: sentenceWordRanges.length });
52
- let nonWhitespaceWordSeen = false;
53
- let sentenceStartWordOffset = segmentStartWordIndex;
54
- let currentSentenceLetterCount = 0;
55
- for (let wordIndex = segmentStartWordIndex; wordIndex < segmentEndWordIndex; wordIndex++) {
56
- const word = wordSequence.getWordAt(wordIndex);
57
- if (nonWhitespaceWordSeen === false) {
58
- nonWhitespaceWordSeen = !whitespacePatternRegExp.test(word);
59
- }
60
- if (currentSentenceLetterCount < minimumSentenceLetterCount) {
61
- const matches = word.matchAll(letterPatternGlobalRegExp);
62
- for (const _ of matches) {
63
- currentSentenceLetterCount += 1;
64
- if (currentSentenceLetterCount >= minimumSentenceLetterCount) {
65
- break;
66
- }
67
- }
68
- }
69
- if (nonWhitespaceWordSeen && currentSentenceLetterCount >= minimumSentenceLetterCount && sentenceSeparatorCharacterRegExp.test(word)) {
70
- let trailingSequenceEndIndex = wordIndex;
71
- while (trailingSequenceEndIndex < segmentEndWordIndex) {
72
- const trailingWord = wordSequence.getWordAt(trailingSequenceEndIndex);
73
- if (sentenceSeparatorTrailingPunctuationCharacterRegExp.test(trailingWord)) {
74
- trailingSequenceEndIndex++;
75
- }
76
- else {
77
- break;
78
- }
79
- }
80
- sentenceWordRanges.push({
81
- start: sentenceStartWordOffset,
82
- end: trailingSequenceEndIndex
83
- });
84
- segmentSentenceRanges[segmentSentenceRanges.length - 1].end += 1;
85
- sentenceStartWordOffset = trailingSequenceEndIndex;
86
- currentSentenceLetterCount = 0;
87
- wordIndex = trailingSequenceEndIndex - 1;
88
- }
89
- }
90
- if (sentenceStartWordOffset < segmentEndWordIndex) {
91
- sentenceWordRanges.push({ start: sentenceStartWordOffset, end: segmentEndWordIndex });
92
- segmentSentenceRanges[segmentSentenceRanges.length - 1].end += 1;
93
- }
94
- }
95
- }
96
- const sentences = [];
97
- for (const wordRange of sentenceWordRanges) {
98
- const sentenceWordSequence = wordSequence.slice(wordRange.start, wordRange.end);
99
- sentences.push(new Sentence(wordRange, sentenceWordSequence));
100
- }
101
- for (const sentence of sentences) {
102
- const phraseWordRanges = [];
103
- let sentenceEndWordOffset = sentence.wordRange.end;
104
- let phraseStartWordOffset = sentence.wordRange.start;
105
- for (let wordIndex = phraseStartWordOffset; wordIndex < sentenceEndWordOffset; wordIndex++) {
106
- const currentWord = wordSequence.getWordAt(wordIndex);
107
- const isCurrentWordPhraseSeparator = phraseSeparatorRegExp.test(currentWord) &&
108
- (!options.requireSpaceAfterColonsOrSemicolons ||
109
- (currentWord !== ':' && currentWord !== ';') ||
110
- whitespacePatternRegExp.test(wordSequence.getWordAt(wordIndex + 1)));
111
- if (isCurrentWordPhraseSeparator) {
112
- let whitespaceSeenOnce = false;
113
- while (wordIndex < sentenceEndWordOffset - 1) {
114
- const nextWord = wordSequence.getWordAt(wordIndex + 1);
115
- if (!sentenceSeparatorTrailingPunctuationCharacterRegExp.test(nextWord)) {
116
- break;
117
- }
118
- if (whitespacePatternRegExp.test(nextWord)) {
119
- whitespaceSeenOnce = true;
120
- }
121
- if (nextWord === '"' && whitespaceSeenOnce) {
122
- break;
123
- }
124
- wordIndex += 1;
125
- }
126
- phraseWordRanges.push({
127
- start: phraseStartWordOffset,
128
- end: wordIndex + 1
129
- });
130
- phraseStartWordOffset = wordIndex + 1;
131
- }
132
- }
133
- if (phraseStartWordOffset < sentenceEndWordOffset) {
134
- phraseWordRanges.push({ start: phraseStartWordOffset, end: sentenceEndWordOffset });
135
- }
136
- for (const wordRange of phraseWordRanges) {
137
- const phraseWordSequence = wordSequence.slice(wordRange.start, wordRange.end);
138
- sentence.phrases.push(new Phrase(wordRange, phraseWordSequence));
139
- }
140
- }
141
- const result = {
142
- words: wordSequence,
143
- sentences,
144
- segmentSentenceRanges,
145
- };
146
- return result;
147
- }
148
- const cachedWordSplitterRegExps = new Map();
149
- export async function splitToWords(text, options) {
150
- if (!options) {
151
- options = {};
152
- }
153
- options = { ...defaultSegmentationOptions, ...options };
154
- if (options.language) {
155
- options.language = getShortLanguageCode(options.language);
156
- }
157
- const optionsAsJson = JSON.stringify(options);
158
- let wordSplitterRegExp = cachedWordSplitterRegExps.get(optionsAsJson);
159
- if (!wordSplitterRegExp) {
160
- wordSplitterRegExp = buildWordSplitterRegExpForOptions(options);
161
- cachedWordSplitterRegExps.set(optionsAsJson, wordSplitterRegExp);
162
- }
163
- let wordSequence = new WordSequence();
164
- function addPunctuationWordsBetween(startOffset, endOffset) {
165
- const punctuationWordSubstring = text.substring(startOffset, endOffset);
166
- let charOffset = startOffset;
167
- let punctuationWordStartOffset = startOffset;
168
- function addPunctuationWordIfNeeded() {
169
- if (charOffset > punctuationWordStartOffset) {
170
- const wordText = text.substring(punctuationWordStartOffset, charOffset);
171
- wordSequence.addWord(wordText, punctuationWordStartOffset, true);
172
- punctuationWordStartOffset = charOffset;
173
- }
174
- }
175
- for (const char of punctuationWordSubstring) {
176
- if (char === ' ') {
177
- charOffset += 1;
178
- continue;
179
- }
180
- addPunctuationWordIfNeeded();
181
- charOffset += char.length;
182
- addPunctuationWordIfNeeded();
183
- }
184
- addPunctuationWordIfNeeded();
185
- }
186
- const wordMatches = text.matchAll(wordSplitterRegExp);
187
- let lastMatchEndOffset = 0;
188
- if (wordMatches) {
189
- for (const match of wordMatches) {
190
- const offsets = match.indices[0];
191
- const matchStartOffset = offsets[0];
192
- const matchEndOffset = offsets[1];
193
- if (matchStartOffset > lastMatchEndOffset) {
194
- addPunctuationWordsBetween(lastMatchEndOffset, matchStartOffset);
195
- }
196
- const wordText = text.substring(matchStartOffset, matchEndOffset);
197
- wordSequence.addWord(wordText, matchStartOffset, false);
198
- lastMatchEndOffset = matchEndOffset;
199
- }
200
- addPunctuationWordsBetween(lastMatchEndOffset, text.length);
201
- }
202
- if (options.enableEastAsianPostprocessing) {
203
- wordSequence = await postprocessEastAsianWords(text, wordSequence);
204
- }
205
- return wordSequence;
206
- }
207
- async function postprocessEastAsianWords(containingText, wordSequence) {
208
- const icuSegmentation = await getIcuSegmentation();
209
- if (icuSegmentation === undefined) {
210
- return wordSequence;
211
- }
212
- let icuInitialized = false;
213
- const newWordSequence = new WordSequence();
214
- for (let wordIndex = 0; wordIndex < wordSequence.length; wordIndex++) {
215
- const wordEntry = wordSequence.entries[wordIndex];
216
- const wordStartOffset = wordEntry.startOffset;
217
- const word = wordSequence.getWordAt(wordIndex);
218
- if (eastAsianCharRangesRegExp.test(word)) {
219
- if (!icuInitialized) {
220
- await icuSegmentation.initialize();
221
- icuInitialized = true;
222
- }
223
- const wordBreaks = [...icuSegmentation.createWordBreakIterator(word)];
224
- for (let i = 0; i < wordBreaks.length - 1; i++) {
225
- const subwordStartOffset = wordStartOffset + wordBreaks[i];
226
- const subwordEndOffset = wordStartOffset + wordBreaks[i + 1];
227
- const subwordText = containingText.substring(subwordStartOffset, subwordEndOffset);
228
- newWordSequence.addWord(subwordText, subwordStartOffset, false);
229
- }
230
- }
231
- else {
232
- const wordText = containingText.substring(wordEntry.startOffset, wordEntry.endOffset);
233
- newWordSequence.addWord(wordText, wordEntry.startOffset, wordEntry.isPunctuation);
234
- }
235
- }
236
- return newWordSequence;
237
- }
238
- // Add any missing punctuation words to a word sequence
239
- export function addMissingPunctuationWordsToWordSequence(wordSequence, sourceText) {
240
- const originalWordsReverseMapping = new Map();
241
- const wordSequenceWithPunctuation = new WordSequence();
242
- function addWordEntriesForTextSlice(textSlice, initialCharOffset) {
243
- let charOffset = initialCharOffset;
244
- // Add entry for every codepoint (this will correctly treat characters beyond BMP)
245
- for (const char of textSlice) {
246
- const charEndOffset = charOffset + char.length;
247
- const lastEntry = wordSequenceWithPunctuation.lastEntry;
248
- if (char === ' ' && lastEntry && lastEntry.isPunctuation && lastEntry.text[0] === ' ') {
249
- wordSequenceWithPunctuation.lastEntry.text += ' ';
250
- wordSequenceWithPunctuation.lastEntry.endOffset = charEndOffset;
251
- }
252
- else {
253
- const wordText = sourceText.substring(charOffset, charEndOffset);
254
- wordSequenceWithPunctuation.addWord(wordText, charOffset, true);
255
- }
256
- charOffset = charEndOffset;
257
- }
258
- }
259
- for (let wordIndex = 0; wordIndex < wordSequence.length; wordIndex++) {
260
- const wordEntry = wordSequence.getEntryAt(wordIndex);
261
- const wordStartOffset = wordEntry.startOffset;
262
- const previousWordEndOffset = wordIndex > 0 ? wordSequence.entries[wordIndex - 1].endOffset : 0;
263
- // Add entries for any punctuation characters between the current and previous word (or start)
264
- if (previousWordEndOffset !== wordStartOffset) {
265
- const textSlice = sourceText.substring(previousWordEndOffset, wordStartOffset);
266
- addWordEntriesForTextSlice(textSlice, previousWordEndOffset);
267
- }
268
- wordSequenceWithPunctuation.entries.push(wordEntry);
269
- originalWordsReverseMapping.set(wordSequenceWithPunctuation.length - 1, wordIndex);
270
- // If last word, add entries for any trailing punctuation characters
271
- if (wordIndex === wordSequence.length - 1) {
272
- if (sourceText.length !== wordEntry.endOffset) {
273
- const textSlice = sourceText.substring(wordEntry.endOffset, sourceText.length);
274
- addWordEntriesForTextSlice(textSlice, wordEntry.endOffset);
275
- }
276
- }
277
- }
278
- return { wordSequenceWithPunctuation, originalWordsReverseMapping };
279
- }
280
- function getPunctuationRanges(wordSequence, text) {
281
- const punctuationRanges = [];
282
- const wordEntries = wordSequence.entries;
283
- if (wordEntries[0].startOffset > 0) {
284
- punctuationRanges.push({ start: 0, end: wordEntries[0].startOffset });
285
- }
286
- for (let i = 0; i < wordEntries.length; i++) {
287
- const entry = wordEntries[i];
288
- const previousEndOffset = wordEntries[i - 1]?.endOffset ?? 0;
289
- if (entry.startOffset > previousEndOffset) {
290
- punctuationRanges.push({ start: previousEndOffset, end: entry.startOffset });
291
- }
292
- if (entry.isPunctuation) {
293
- punctuationRanges.push({ start: entry.startOffset, end: entry.endOffset });
294
- }
295
- }
296
- {
297
- const lastEndOffset = wordEntries[wordEntries.length - 1]?.endOffset;
298
- if (lastEndOffset && lastEndOffset < text.length) {
299
- punctuationRanges.push({ start: lastEndOffset, end: text.length });
300
- }
301
- }
302
- return punctuationRanges;
303
- }
304
- ////////////////////////////////////////////////////////////////////////////////////////////////
305
- // Helper methods
306
- ////////////////////////////////////////////////////////////////////////////////////////////////
307
- function buildWordSplitterRegExpForOptions(options) {
308
- const cldrSuppressionsForLang = cldrSuppressions[options.language ?? ''] ?? [];
309
- const extendedSuppressionsForLang = additionalSuppressions[options.language ?? ''] ?? [];
310
- const contractionSuppressionsForLang = leadingApostropheContractionSuppressions[options.language ?? ''] ?? [];
311
- const contractionSuppressionsForLangWithSingleQuote = contractionSuppressionsForLang.map(str => str.replaceAll(`'`, `’`));
312
- const customSuppressions = options.customSuppressions ?? [];
313
- let suppressions = [
314
- ...customSuppressions,
315
- ...cldrSuppressionsForLang,
316
- ...extendedSuppressionsForLang,
317
- ...contractionSuppressionsForLang,
318
- ...contractionSuppressionsForLangWithSingleQuote,
319
- ...nounSuppressions,
320
- ...tldSuppressions,
321
- ];
322
- const wordPattern = buildWordSplitterPattern([
323
- ...suppressions,
324
- ...suppressions.map(word => word.toLocaleLowerCase()),
325
- ...suppressions.map(word => word.toLocaleUpperCase()),
326
- ]);
327
- const wordSplitterRegExp = buildRegExp(wordPattern, { global: true });
328
- return wordSplitterRegExp;
329
- }
330
- async function getIcuSegmentation() {
331
- try {
332
- const icuSegmentation = await import('@echogarden/icu-segmentation-wasm');
333
- return icuSegmentation;
334
- }
335
- catch {
336
- return undefined;
337
- }
338
- }
339
- export class TextFragment {
340
- wordRange;
341
- words;
342
- constructor(wordRange, words) {
343
- this.wordRange = wordRange;
344
- this.words = words;
345
- }
346
- get text() {
347
- return this.words.text;
348
- }
349
- get charRange() {
350
- return {
351
- start: this.words.firstEntry.startOffset,
352
- end: this.words.lastEntry.endOffset
353
- };
354
- }
355
- }
356
- export class Sentence extends TextFragment {
357
- phrases = [];
358
- }
359
- export class Phrase extends TextFragment {
360
- }
361
- export const defaultSegmentationOptions = {
362
- language: '',
363
- customSuppressions: [],
364
- enableEastAsianPostprocessing: true,
365
- };
366
- export const defaultWordSequenceSegmentationOptions = {
367
- requireSpaceAfterColonsOrSemicolons: true
368
- };
369
- //# sourceMappingURL=TextSegmentation.js.map