bitaboom 2.1.0 → 2.2.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 +18 -0
- package/dist/index.d.ts +37 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -42,10 +42,22 @@ const plain = removeMarkdownFormatting('**Bold** _italic_ [link](https://example
|
|
|
42
42
|
console.log(plain); // "Bold italic link"
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### High-performance Arabic preformatting
|
|
46
|
+
|
|
47
|
+
If you need to normalize messy Arabic/OCR text at scale (spacing, punctuation, brackets, ellipses, references), use the single-pass preformatter:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { preformatArabicText } from 'bitaboom';
|
|
51
|
+
|
|
52
|
+
preformatArabicText('بِسْمِ اللَّهِ ( الرَّحْمَنِ ) 127 / 11 قَالَ ...');
|
|
53
|
+
preformatArabicText(['صفحة 1 ...', 'صفحة 2 ...']); // batch mode
|
|
54
|
+
```
|
|
55
|
+
|
|
45
56
|
## Feature highlights
|
|
46
57
|
|
|
47
58
|
- **Arabic-first matching** – build diacritic-insensitive regular expressions, collapse tatweel, score Arabic content density, and replace Urdu glyphs.
|
|
48
59
|
- **Rich typography normalisers** – more than 30 helpers to fix punctuation spacing, quotes, brackets, ellipses, smart quotes, uppercase detection, and whitespace quirks.
|
|
60
|
+
- **Single-pass Arabic preformatter** – `preformatArabicText` consolidates the common formatting pipeline and is optimized for large datasets.
|
|
49
61
|
- **Sanitisation pipelines** – strip references, URLs, part markers, markdown decorations, escaped spaces, or numbers in bilingual text.
|
|
50
62
|
- **Parsing helpers** – validate JSON-ish blobs, split search queries by quotes, balance parentheses/quotes, and expand page range strings.
|
|
51
63
|
- **Transliteration polish** – normalise common Arabic prefixes (`al-`, `wa-`, `bi-`), dedupe apostrophes, replace salutations with ﷺ, and extract initials from transliterated names.
|
|
@@ -151,6 +163,12 @@ All modules are exported from `src/index.ts`. Functions are grouped below by fea
|
|
|
151
163
|
| `normalizeTransliteratedEnglish` | Combine prefix removal + diacritic stripping. |
|
|
152
164
|
| `extractInitials` | Extract the first letters from up to two words (after normalisation). |
|
|
153
165
|
|
|
166
|
+
### Preformatting pipeline (`src/preformat.ts`)
|
|
167
|
+
|
|
168
|
+
| Function | Description |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `preformatArabicText` | High-performance Arabic preformatting pipeline (single-pass, optimized for large datasets). Accepts a single string or an array of strings. |
|
|
171
|
+
|
|
154
172
|
## Build & development
|
|
155
173
|
|
|
156
174
|
| Task | Command |
|
package/dist/index.d.ts
CHANGED
|
@@ -171,6 +171,15 @@ type MakeRegexOptions = {
|
|
|
171
171
|
* rx.test('اَنا إلى الآفاق'); // true
|
|
172
172
|
*/
|
|
173
173
|
declare const makeDiacriticInsensitiveRegex: (needle: string, opts?: MakeRegexOptions) => RegExp;
|
|
174
|
+
/**
|
|
175
|
+
* Remove simple HTML/XML-like tags from a string.
|
|
176
|
+
*
|
|
177
|
+
* This is intentionally lightweight and does not attempt to parse HTML; it simply drops
|
|
178
|
+
* substrings that look like `<...>`.
|
|
179
|
+
*
|
|
180
|
+
* @param content Input string
|
|
181
|
+
* @returns String with tags removed
|
|
182
|
+
*/
|
|
174
183
|
declare const removeAllTags: (content: string) => string;
|
|
175
184
|
//#endregion
|
|
176
185
|
//#region src/constants.d.ts
|
|
@@ -181,6 +190,9 @@ declare const PATTERN_ENDS_WITH_PUNCTUATION: RegExp;
|
|
|
181
190
|
/**
|
|
182
191
|
* Adds line breaks after punctuation marks such as periods, exclamation points, and question marks.
|
|
183
192
|
* Example: 'Text.' becomes 'Text.\n'.
|
|
193
|
+
*
|
|
194
|
+
* Note: For the full preformatting pipeline in one pass (significantly faster and more memory-friendly
|
|
195
|
+
* on very large inputs), use `preformatArabicText` from `src/preformat.ts`.
|
|
184
196
|
* @param {string} text - The input text containing punctuation.
|
|
185
197
|
* @returns {string} - The modified text with line breaks added after punctuation.
|
|
186
198
|
*/
|
|
@@ -494,6 +506,30 @@ declare const isBalanced: (str: string) => boolean;
|
|
|
494
506
|
*/
|
|
495
507
|
declare const parsePageRanges: (pageInput: string) => number[];
|
|
496
508
|
//#endregion
|
|
509
|
+
//#region src/preformat.d.ts
|
|
510
|
+
/**
|
|
511
|
+
* Hyperoptimized Arabic text preformatting entry point.
|
|
512
|
+
*
|
|
513
|
+
* The implementation lives in `src/preformat-core.ts` to keep the public surface
|
|
514
|
+
* area small while allowing internal benchmarking (buffer vs concat builders).
|
|
515
|
+
*
|
|
516
|
+
* @module preformat
|
|
517
|
+
*/
|
|
518
|
+
type PreformatArabicText = {
|
|
519
|
+
(text: string): string;
|
|
520
|
+
(texts: string[]): string[];
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
* High-performance Arabic preformatting pipeline.
|
|
524
|
+
*
|
|
525
|
+
* Consolidates common formatting steps (spacing, punctuation normalization, reference formatting,
|
|
526
|
+
* bracket/quote cleanup, ellipsis condensation, newline normalization) into a single-pass formatter.
|
|
527
|
+
*
|
|
528
|
+
* @param text Input string or an array of strings
|
|
529
|
+
* @returns Preformatted string or array of strings (matching input shape)
|
|
530
|
+
*/
|
|
531
|
+
declare const preformatArabicText: PreformatArabicText;
|
|
532
|
+
//#endregion
|
|
497
533
|
//#region src/sanitization.d.ts
|
|
498
534
|
/**
|
|
499
535
|
* Removes various symbols, part references, and numerical markers from the text.
|
|
@@ -685,5 +721,5 @@ declare const normalizeTransliteratedEnglish: (text: string) => string;
|
|
|
685
721
|
*/
|
|
686
722
|
declare const extractInitials: (fullName: string) => string;
|
|
687
723
|
//#endregion
|
|
688
|
-
export { MakeRegexOptions, PATTERN_ENDS_WITH_PUNCTUATION, addSpaceBeforeAndAfterPunctuation, addSpaceBetweenArabicTextAndNumbers, applySmartQuotes, arabicNumeralToNumber, cleanExtremeArabicUnderscores, cleanLiteralNewLines, cleanMultilines, cleanSpacesBeforePeriod, cleanSymbolsAndPartReferences, cleanTrailingPageNumbers, condenseAsterisks, condenseColons, condenseDashes, condenseEllipsis, condensePeriods, condenseUnderscores, convertUrduSymbolsToArabic, doubleToSingleBrackets, ensureSpaceBeforeBrackets, ensureSpaceBeforeQuotes, escapeRegex, extractInitials, findLastPunctuation, fixBracketTypos, fixCurlyBraces, fixMismatchedQuotationMarks, fixTrailingWow, formatStringBySentence, getArabicScore, hasWordInSingleLine, insertLineBreaksAfterPunctuation, isAllUppercase, isBalanced, isJsonStructureValid, isOnlyPunctuation, makeDiacriticInsensitive, makeDiacriticInsensitiveRegex, normalize, normalizeArabicPrefixesToAl, normalizeDoubleApostrophes, normalizeJsonSyntax, normalizeSlashInReferences, normalizeSpaces, normalizeTransliteratedEnglish, parsePageRanges, reduceMultilineBreaksToDouble, reduceMultilineBreaksToSingle, removeAllTags, removeArabicPrefixes, removeDeathYear, removeMarkdownFormatting, removeNonIndexSignatures, removeNumbersAndDashes, removeRedundantPunctuation, removeSingleDigitReferences, removeSingularCodes, removeSolitaryArabicLetters, removeSpaceInsideBrackets, removeUrls, replaceDoubleBracketsWithArrows, replaceEnglishPunctuationWithArabic, replaceLineBreaksWithSpaces, replaceSalutationsWithSymbol, splitByQuotes, stripAllDigits, stripBoldStyling, stripItalicsStyling, stripStyling, toTitleCase, trimSpaceInsideQuotes, truncate, truncateMiddle, unescapeSpaces };
|
|
724
|
+
export { MakeRegexOptions, PATTERN_ENDS_WITH_PUNCTUATION, addSpaceBeforeAndAfterPunctuation, addSpaceBetweenArabicTextAndNumbers, applySmartQuotes, arabicNumeralToNumber, cleanExtremeArabicUnderscores, cleanLiteralNewLines, cleanMultilines, cleanSpacesBeforePeriod, cleanSymbolsAndPartReferences, cleanTrailingPageNumbers, condenseAsterisks, condenseColons, condenseDashes, condenseEllipsis, condensePeriods, condenseUnderscores, convertUrduSymbolsToArabic, doubleToSingleBrackets, ensureSpaceBeforeBrackets, ensureSpaceBeforeQuotes, escapeRegex, extractInitials, findLastPunctuation, fixBracketTypos, fixCurlyBraces, fixMismatchedQuotationMarks, fixTrailingWow, formatStringBySentence, getArabicScore, hasWordInSingleLine, insertLineBreaksAfterPunctuation, isAllUppercase, isBalanced, isJsonStructureValid, isOnlyPunctuation, makeDiacriticInsensitive, makeDiacriticInsensitiveRegex, normalize, normalizeArabicPrefixesToAl, normalizeDoubleApostrophes, normalizeJsonSyntax, normalizeSlashInReferences, normalizeSpaces, normalizeTransliteratedEnglish, parsePageRanges, preformatArabicText, reduceMultilineBreaksToDouble, reduceMultilineBreaksToSingle, removeAllTags, removeArabicPrefixes, removeDeathYear, removeMarkdownFormatting, removeNonIndexSignatures, removeNumbersAndDashes, removeRedundantPunctuation, removeSingleDigitReferences, removeSingularCodes, removeSolitaryArabicLetters, removeSpaceInsideBrackets, removeUrls, replaceDoubleBracketsWithArrows, replaceEnglishPunctuationWithArabic, replaceLineBreaksWithSpaces, replaceSalutationsWithSymbol, splitByQuotes, stripAllDigits, stripBoldStyling, stripItalicsStyling, stripStyling, toTitleCase, trimSpaceInsideQuotes, truncate, truncateMiddle, unescapeSpaces };
|
|
689
725
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
const e=/[.!?؟؛]$/,t=e=>parseInt(e.replace(/[\u0660-\u0669]/g,e=>(e.charCodeAt(0)-1632).toString()),10),n=e=>e.replace(/(?<!\d ?ه|اه)ـ(?=\r?$)|^ـ(?!اهـ)/gm,``),r=e=>e.replace(/ھ/g,`ه`).replace(/ی/g,`ي`),i=e=>{if(!e)return 0;let t=/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/g,n=/[0-9\u0660-\u0669\u06F0-\u06F9]/g,r=/[^\s0-9\u0660-\u0669\u06F0-\u06F9]/g,i=e.replace(n,``),a=i.match(t)||[],o=i.match(r)||[];return o.length===0?0:a.length/o.length},a=t=>{for(let n=t.length-1;n>=0;n--)if(e.test(t[n]))return n;return-1},o=e=>e.replace(/ و /g,` و`),s=e=>e.replace(/([\u0600-\u06FF]+)(\d+)/g,`$1 $2`),c=e=>e.replace(/(?<![0-9] ?)-|(?<=[\u0600-\u06FF])\s?\d\s?(?=[\u0600-\u06FF])/g,` `).replace(/(?<=[\u0600-\u06FF]\s)(\d+\s)+\d+(?=(\s[\u0600-\u06FF]|$))/g,` `),l=e=>e.replace(/[[({][\u0621-\u064A\u0660-\u0669][\])}]/g,``),u=e=>e.replace(/(^| )[\u0621-\u064A]( |$)/g,` `),d=e=>e.replace(/\?|؟\./g,`؟`).replace(/(;|؛)\s*(\1\s*)*/g,`؛`).replace(/,|-،/g,`،`),f=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),p=(e,t={})=>{let{equivalences:n={alif:!0,taMarbutahHa:!0,alifMaqsurahYa:!0},allowTatweel:r=!0,ignoreDiacritics:i=!0,flexWhitespace:a=!0,flags:o=`u`}=t;if(e.length>5e3)throw Error(`makeDiacriticInsensitiveRegex: needle too long`);let s=e=>{switch(e){case`ا`:case`أ`:case`إ`:case`آ`:return n.alif?`[اأإآ]`:`ا`;case`ة`:case`ه`:return n.taMarbutahHa?`[هة]`:f(e);case`ى`:case`ي`:return n.alifMaqsurahYa?`[ىي]`:f(e);default:return f(e)}},c=`${i?`[\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06ED]*`:``}${r?`\\u0640*`:``}`,l=``;for(let t of Array.from(e))/\s/.test(t)?l+=a?`\\s+`:`\\s*`:l+=`${s(t)}${c}`;return new RegExp(l,o)},
|
|
1
|
+
const e=/[.!?؟؛]$/,t=e=>parseInt(e.replace(/[\u0660-\u0669]/g,e=>(e.charCodeAt(0)-1632).toString()),10),n=e=>e.replace(/(?<!\d ?ه|اه)ـ(?=\r?$)|^ـ(?!اهـ)/gm,``),r=e=>e.replace(/ھ/g,`ه`).replace(/ی/g,`ي`),i=e=>{if(!e)return 0;let t=/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/g,n=/[0-9\u0660-\u0669\u06F0-\u06F9]/g,r=/[^\s0-9\u0660-\u0669\u06F0-\u06F9]/g,i=e.replace(n,``),a=i.match(t)||[],o=i.match(r)||[];return o.length===0?0:a.length/o.length},a=t=>{for(let n=t.length-1;n>=0;n--)if(e.test(t[n]))return n;return-1},o=e=>e.replace(/ و /g,` و`),s=e=>e.replace(/([\u0600-\u06FF]+)(\d+)/g,`$1 $2`),c=e=>e.replace(/(?<![0-9] ?)-|(?<=[\u0600-\u06FF])\s?\d\s?(?=[\u0600-\u06FF])/g,` `).replace(/(?<=[\u0600-\u06FF]\s)(\d+\s)+\d+(?=(\s[\u0600-\u06FF]|$))/g,` `),l=e=>e.replace(/[[({][\u0621-\u064A\u0660-\u0669][\])}]/g,``),u=e=>e.replace(/(^| )[\u0621-\u064A]( |$)/g,` `),d=e=>e.replace(/\?|؟\./g,`؟`).replace(/(;|؛)\s*(\1\s*)*/g,`؛`).replace(/,|-،/g,`،`),f=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),p=(e,t={})=>{let{equivalences:n={alif:!0,taMarbutahHa:!0,alifMaqsurahYa:!0},allowTatweel:r=!0,ignoreDiacritics:i=!0,flexWhitespace:a=!0,flags:o=`u`}=t;if(e.length>5e3)throw Error(`makeDiacriticInsensitiveRegex: needle too long`);let s=e=>{switch(e){case`ا`:case`أ`:case`إ`:case`آ`:return n.alif?`[اأإآ]`:`ا`;case`ة`:case`ه`:return n.taMarbutahHa?`[هة]`:f(e);case`ى`:case`ي`:return n.alifMaqsurahYa?`[ىي]`:f(e);default:return f(e)}},c=`${i?`[\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06ED]*`:``}${r?`\\u0640*`:``}`,l=``;for(let t of Array.from(e))/\s/.test(t)?l+=a?`\\s+`:`\\s*`:l+=`${s(t)}${c}`;return new RegExp(l,o)},ee=e=>e.replace(/<[^>]*>/g,``),te=e=>e.replace(/([.?!؟])/g,`$1
|
|
2
2
|
`).replace(/\n\s+/g,`
|
|
3
|
-
`).trim(),
|
|
4
|
-
`),
|
|
3
|
+
`).trim(),ne=e=>e.replace(/( ?)([.!?,،؟;؛])((?![ '”“)"\]\n])|(?=\s{2,}))/g,`$1$2 `).replace(/\s([.!?,،؟;؛])\s*([ '”“)"\]\n])/g,`$1$2`).replace(/([^\s\w\d'”“)"\]]+)\s+([.!?,،؟;؛])|([.!?,،؟;؛])\s+$/g,`$1$2$3`).replace(/(?<=\D)( ?: ?)(?!(\d+:)|(:\d+))|(?<=\d) ?: ?(?=\D)|(?<=\D) ?: ?(?=\d)/g,`: `),re=e=>e.replace(/[“”]/g,`"`).replace(/"([^"]*)"/g,`“$1”`).replace(/^”/g,`“`),ie=e=>e.replace(/\\n|\r/g,`
|
|
4
|
+
`),ae=e=>e.replace(/^ +| +$/gm,``),oe=e=>/^\s*\S+\s*$/gm.test(e),se=e=>/^[\u0020-\u002f\u003a-\u0040\u005b-\u0060\u007b-\u007e0-9٠-٩]+$/.test(e),ce=e=>e.replace(/\s+([.؟!,،؛:?])/g,`$1`),le=e=>e.replace(/(\*\s*)+/g,`*`),ue=e=>e.replace(/[.-]?:[.-]?/g,`:`),de=e=>e.replace(/-{2,}/g,`-`),fe=e=>e.replace(/\.{2,}/g,`…`),pe=e=>e.replace(/(\n\s*){3,}/g,`
|
|
5
5
|
|
|
6
|
-
`),
|
|
7
|
-
`),
|
|
6
|
+
`),me=e=>e.replace(/(\n\s*){2,}/g,`
|
|
7
|
+
`),he=e=>e.replace(/\. +\./g,`.`),ge=e=>e.replace(/ـ{2,}/g,`ـ`).replace(/_+/g,`_`),m=e=>e.replace(/(\(|\)){2,}|(\[|\]){2,}/g,`$1$2`),h=e=>e.replace(/(\S) *(\([^)]*\))/g,`$1 $2`),g=e=>e.replace(/(\S) *(«[^»]*»)/g,`$1 $2`),_=e=>e.replace(/\(«|\( \(/g,`«`).replace(/»\)|\) \)/g,`»`).replace(/\)([0-9\u0660-\u0669]+)\)/g,`($1)`).replace(/\)([0-9\u0660-\u0669]+)\(/g,`($1)`),v=e=>{let t=e;return t=t.replace(/\(([^(){}]+)\}/g,`{$1}`),t.replace(/\{([^(){}]+)\)/g,`{$1}`)},y=e=>e.replace(/«([^»)]+)\)/g,`«$1»`).replace(/\(([^()]+)»/g,`«$1»`).replace(/«([^»]+)(?=\s*$|$)/g,`«$1»`),b=e=>{let t=/^\((?:\d+|۱|۲|۳|۴|۵|۶|۷|۸|۹)\)\s/,n=[],r=e.split(`
|
|
8
8
|
`),i=``;return r.forEach(e=>{let r=e.trim(),a=t.test(r),o=/^\(\d+\/\d+\)/.test(r);if(a&&!o)i&&=(n.push(i.trim()),``),n.push(r);else{i+=`${r} `;let e=i.trim().slice(-1);/[.!؟]/.test(e)&&(n.push(i.trim()),i=``)}}),i&&n.push(i.trim()),n.join(`
|
|
9
|
-
`)},
|
|
9
|
+
`)},x=e=>{let t=e.replace(/[^\p{L}]/gu,``);return t.length===0?!1:t===t.toUpperCase()},S=e=>e.replace(/(\d+)\s?\/\s?(\d+)/g,`$1/$2`),C=e=>e.replace(/[ \t]+/g,` `),w=e=>e.replace(/([؟!])[.،]/g,`$1`),T=e=>e.replace(/([[(])\s*(.*?)\s*([\])])/g,`$1$2$3`),E=e=>e.replace(/\(\(\s?/g,`«`).replace(/\s?\)\)/g,`»`),D=e=>e.normalize(`NFKD`).replace(/[\u0300-\u036f]/g,``).trim(),O=e=>{let t={𝑎:`I`,𝑨:`g`,𝘼:`!`,𝑏:`J`,𝑩:`h`,𝘽:`?`,𝑐:`K`,𝑪:`i`,𝑑:`L`,𝑫:`j`,𝘿:`,`,𝑒:`M`,𝑬:`k`,𝙀:`.`,𝑓:`N`,𝑭:`l`,𝑔:`O`,𝑮:`m`,𝑯:`n`,𝑖:`Q`,𝑰:`o`,𝑗:`R`,𝑱:`p`,𝑘:`S`,𝑲:`q`,𝑙:`T`,𝑳:`r`,𝙇:`-`,𝑚:`U`,𝑴:`s`,𝑛:`V`,𝑵:`t`,𝑜:`W`,𝑶:`u`,𝑝:`X`,𝑷:`v`,𝑞:`Y`,𝑸:`w`,𝑟:`Z`,𝑹:`x`,𝑆:`A`,𝑺:`y`,𝑇:`B`,𝑻:`z`,𝑢:`a`,𝑈:`C`,𝑣:`b`,𝑉:`D`,𝑤:`c`,𝑊:`E`,𝑥:`d`,𝑋:`F`,𝑦:`e`,𝑌:`G`,𝑧:`f`,𝑍:`H`,"":`P`};return e.replace(/[\uD835\uDC62-\uD835\uDC7B\uD835\uDC46-\uD835\uDC5F\u{1D63C}-\u{1D647}]/gu,e=>t[e]||e)},k=e=>O(D(e)),A=e=>e.toLowerCase().split(` `).map(e=>{if(e.length===0)return e;let t=e.match(/\p{L}/u);if(!t||t.index===void 0)return e;let n=t.index;return e.slice(0,n)+e.charAt(n).toUpperCase()+e.slice(n+1)}).join(` `),j=e=>e.replace(/([“”"]|«) *(.*?) *([“”"]|»)/g,`$1$2$3`),M=e=>{let t=e.replace(/(\b\d+\b)(?=:)/g,`"$1"`);return t=t.replace(/:\s*'([^']+)'/g,`: "$1"`),t=t.replace(/:\s*"([^"]+)"/g,`: "$1"`),JSON.stringify(JSON.parse(t))},N=e=>/^{(\s*(\d+|'[^']*'|"[^"]*")\s*:\s*('|")[^'"]*\3\s*,)*(?:\s*(\d+|'[^']*'|"[^"]*")\s*:\s*('|")[^'"]*\5\s*)}$/.test(e.trim()),P=e=>(e.match(/(?:[^\s"]+|"(.*?)")+/g)||[]).map(e=>e.startsWith(`"`)?e.slice(1,-1):e),F=e=>{let t=0;for(let n of e)n===`"`&&t++;return t%2==0},I={"(":`)`,"[":`]`,"{":`}`},L=new Set([`(`,`[`,`{`]),_e=new Set([`)`,`]`,`}`]),ve=e=>{let t=[];for(let n of e)if(L.has(n))t.push(n);else if(_e.has(n)){let e=t.pop();if(!e||I[e]!==n)return!1}return t.length===0},ye=e=>F(e)&&ve(e),be=e=>{if(e.includes(`-`)){let[t,n]=e.split(`-`).map(Number);if(t>n)throw Error(`Start page cannot be greater than end page`);return Array.from({length:n-t+1},(e,n)=>t+n)}else return e.split(`,`).map(Number)},R=new Uint8Array(65536),z=(e,t)=>{for(let n=0;n<e.length;n++)R[e.charCodeAt(n)]|=t};z(` `,1),z(`
|
|
10
|
+
\r`,64);for(let e=48;e<=57;e++)R[e]|=16;for(let e=1536;e<=1791;e++)R[e]|=32;for(let e=1872;e<=1919;e++)R[e]|=32;z(`.!?,:;`,194),z(`،؛؟`,194),z(`([{"'«“`,68),z(`)]}"'»”`,74),z(`-_*و/`,64);const B=8221,V=1608,H=1548,U=1563,W=1567,G=1600,K=8230,q=e=>e===32||e===9||e===10||e===13;var xe=class{buffer;length;constructor(e){this.buffer=new Uint16Array(Math.max(16,e)),this.length=0}last(){return this.length>0?this.buffer[this.length-1]:0}secondLast(){return this.length>1?this.buffer[this.length-2]:0}push(e){this.ensureCapacity(1),this.buffer[this.length]=e,this.length++}pop(){this.length>0&&this.length--}ensureCapacity(e){let t=this.length+e;if(t<=this.buffer.length)return;let n=this.buffer.length*2;n<t&&(n=t);let r=new Uint16Array(n);r.set(this.buffer.subarray(0,this.length)),this.buffer=r}toStringTrimmed(){let e=0,t=this.length;for(;e<t&&q(this.buffer[e]);)e++;for(;t>e&&q(this.buffer[t-1]);)t--;if(t<=e)return``;let n=32768,r=``;for(let i=e;i<t;i+=n){let e=this.buffer.subarray(i,Math.min(t,i+n));r+=String.fromCharCode(...e)}return r}};const Se=e=>{if(!e)return``;let t=``,n=e.length,r=0,i=0,a=0;for(;r<n;){let o=e.charCodeAt(r),s=o,c=R[o];if(c&1){i++,r++;continue}if(c&64){if(o===10||o===13){for(i=0,a!==10&&(t+=`
|
|
11
|
+
`,a=10),r++;r<n;){let t=e.charCodeAt(r);if(t===32||t===9||t===10||t===13)r++;else break}continue}if(o===63)o=W;else if(o===59)o=U;else if(o===44)o=H;else if(o===58){(a===46||a===45)&&(t=t.slice(0,-1),a=t.charCodeAt(t.length-1)||0);let n=e.charCodeAt(r+1);(n===46||n===45)&&r++}else if(o===40&&e.charCodeAt(r+1)===40)o=171,r++;else if(o===41&&e.charCodeAt(r+1)===41)o=187,r++;else if(o===46){if(a===K){r++;continue}if(a===46){t=t.slice(0,-1)+`…`,a=K,r++;continue}}else if(o===V&&a===32){for(t+=`و`,a=V,r++;r<n;){let t=e.charCodeAt(r);if(t===32||t===9)r++;else break}continue}else if(o===G&&a===G||o===95&&a===95||o===45&&a===45||o===42&&a===42){r++;continue}if((a===W||a===33)&&(o===46||o===H)){r++;continue}}if(i>0){let s=!0,c=R[o];if(c&2&&(s=!1),R[a]&4&&(s=!1),o===47){let t=0;for(let i=r+1;i<n;i++){let n=e.charCodeAt(i);if(n!==32&&n!==9&&n!==10&&n!==13){t=n;break}}R[a]&16&&R[t]&16&&(s=!1)}a===47&&c&16&&t.length>=2&&R[t.charCodeAt(t.length-2)]&16&&(s=!1),s&&(t+=` `,a=32),i=0}let l=R[o];l&16&&R[a]&32&&(t+=` `,a=32),l&4&&a!==32&&a!==10&&!(R[a]&4)&&a!==0&&(t+=` `,a=32),R[a]&128&&(l&13||o===32||o===10||o===13||l&8||o===34||o===39||o===187||o===B||o===a||(a===W||a===33)&&(o===46||o===H)||(t+=` `,a=32)),o===s?t+=e[r]:t+=String.fromCharCode(o),a=o,r++}return t.trim()},Ce=e=>{if(!e)return``;let t=e.length,n=0,r=new xe(t+(t>>3)+64),i=0,a=0;for(;n<t;){let o=e.charCodeAt(n),s=o,c=R[o];if(c&1){i++,n++;continue}if(c&64){if(o===10||o===13){for(i=0,a!==10&&(r.push(10),a=10),n++;n<t;){let t=e.charCodeAt(n);if(t===32||t===9||t===10||t===13)n++;else break}continue}if(o===63)o=W;else if(o===59)o=U;else if(o===44)o=H;else if(o===58){(a===46||a===45)&&(r.pop(),a=r.last());let t=e.charCodeAt(n+1);(t===46||t===45)&&n++}else if(o===40&&e.charCodeAt(n+1)===40)o=171,n++;else if(o===41&&e.charCodeAt(n+1)===41)o=187,n++;else if(o===46){if(a===K){n++;continue}if(a===46){r.pop(),r.push(K),a=K,n++;continue}}else if(o===V&&a===32){for(r.push(V),a=V,n++;n<t;){let t=e.charCodeAt(n);if(t===32||t===9)n++;else break}continue}else if(o===G&&a===G||o===95&&a===95||o===45&&a===45||o===42&&a===42){n++;continue}if((a===W||a===33)&&(o===46||o===H)){n++;continue}}if(i>0){let s=!0,c=R[o];if(c&2&&(s=!1),R[a]&4&&(s=!1),o===47){let r=0;for(let i=n+1;i<t;i++){let t=e.charCodeAt(i);if(t!==32&&t!==9&&t!==10&&t!==13){r=t;break}}R[a]&16&&R[r]&16&&(s=!1)}a===47&&c&16&&r.length>=2&&R[r.secondLast()]&16&&(s=!1),s&&(r.push(32),a=32),i=0}let l=R[o];l&16&&R[a]&32&&(r.push(32),a=32),l&4&&a!==32&&a!==10&&!(R[a]&4)&&a!==0&&(r.push(32),a=32),R[a]&128&&(l&13||o===32||o===10||o===13||l&8||o===34||o===39||o===187||o===B||o===a||(a===W||a===33)&&(o===46||o===H)||(r.push(32),a=32)),o===s?r.push(s):r.push(o),a=o,n++}return r.toStringTrimmed()},J=e=>Se(e),we=e=>Ce(e),Y=e=>{let t=process.env.BITABOOM_PREFORMAT_BUILDER;return t===`concat`?J(e):t===`buffer`?we(e):J(e)},Te=e=>Array.isArray(e)?e.map(Y):Y(e),Ee=e=>e.replace(/ *\(?:\d+(?:\/\d+){0,2}\)? *| *\[\d+(?:\/\d+)?\] *| *«\d+» *|\d+\/\d+(?:\/\d+)?|[،§{}؍﴿﴾<>;_؟»«:!،؛[\]…ـ¬.\\/*()"]/g,` `),De=e=>e.replace(/-\[\d+\]-/g,``),Oe=e=>e.replace(/\s+/g,` `),ke=e=>e.replace(/[0-9]/g,``),Ae=e=>e.replace(/\[(d)\.\s*\d{1,4}[hH]\]\s*|\((d)\.\s*\d{1,4}[hH]\)\s*/g,``),je=e=>e.replace(/[\d-]/g,``),Me=e=>e.replace(/\(\d{1}\)|\[\d{1}\]|«\d»/g,``),Ne=e=>e.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g,``),Pe=e=>e.replace(/\*\*([^*]+)\*\*/g,`$1`).replace(/__([^_]+)__/g,`$1`).replace(/\*([^*]+)\*/g,`$1`).replace(/_([^_]+)_/g,`$1`).replace(/~~([^~]+)~~/g,`$1`).replace(/^\s*>\s?/gm,``).replace(/!\[[^\]]*]\([^)]*\)/g,``).replace(/\[([^\]]+)]\([^)]*\)/g,`$1`).replace(/^#+\s*/gm,``).replace(/^\s*[-*+]\s+/gm,``).replace(/^\s*\d+\.\s+/gm,``).replace(/`/gm,``),Fe=(e,t=150)=>e.length>t?`${e.substring(0,t-1)}…`:e,Ie=(e,t=50,n)=>{if(e.length<=t)return e;let r=Math.max(3,Math.floor(t/3)),i=n??r,a=t-1-i;return a<1?`${e.substring(0,t-1)}…`:`${e.substring(0,a)}…${e.substring(e.length-i)}`},Le=e=>e.replace(/\\ /g,` `).trim(),X=[[`ا`,`آ`,`أ`,`إ`],[`ة`,`ه`],[`ى`,`ي`]],Re=e=>{for(let t of X)if(t.includes(e))return`[${t.map(e=>f(e)).join(``)}]`;return f(e)},ze=e=>e.normalize(`NFC`).replace(/[\u200C\u200D]/g,``).replace(/\s+/g,` `).trim(),Be=e=>{let t=ze(e);return Array.from(t).map(e=>Re(e)+`[ًٌٍَُِّْ]*`).join(``)},Ve=e=>e.replace(/(\b|\W)(Al |Al-|Ar-|As-|Adh-|Ad-|Ats-|Ath |Ath-|Az |Az-|az-|adh-|as-|ar-)/g,`$1al-`).replace(/(\b|\W)(Ash-S|ash-S)/g,`$1al-S`).replace(/al- (.+?)\b/g,`al-$1`),He=e=>e.replace(/ʿʿ/g,`ʿ`).replace(/ʾʾ/g,`ʾ`),Ue=e=>e.replace(/\(peace be upon him\)|(Messenger of (Allah|Allāh)|Messenger|Prophet|Mu[hḥ]ammad) *\((s[^)]*m|peace[^)]*him|May[^)]*him|may[^)]*him)\)*/gi,`$1 ﷺ`).replace(/,\s*ﷺ\s*,/g,` ﷺ`),Z=e=>e.normalize(`NFKD`).replace(/[\u0300-\u036f]/g,``).replace(/`|ʾ|ʿ|-/g,``),Q=e=>C(e.replace(/(\bal-|\bli-|\bbi-|\bfī|\bwa[-\s]+|\bl-|\bliʿl|\Bʿalá|\Bʿan|\bb\.)/gi,``)),$=e=>Z(Q(e)),We=e=>$(e).trim().split(/[ -]/).slice(0,2).map(e=>e.charAt(0).toUpperCase()).join(``);export{e as PATTERN_ENDS_WITH_PUNCTUATION,ne as addSpaceBeforeAndAfterPunctuation,s as addSpaceBetweenArabicTextAndNumbers,re as applySmartQuotes,t as arabicNumeralToNumber,n as cleanExtremeArabicUnderscores,ie as cleanLiteralNewLines,ae as cleanMultilines,ce as cleanSpacesBeforePeriod,Ee as cleanSymbolsAndPartReferences,De as cleanTrailingPageNumbers,le as condenseAsterisks,ue as condenseColons,de as condenseDashes,fe as condenseEllipsis,he as condensePeriods,ge as condenseUnderscores,r as convertUrduSymbolsToArabic,m as doubleToSingleBrackets,h as ensureSpaceBeforeBrackets,g as ensureSpaceBeforeQuotes,f as escapeRegex,We as extractInitials,a as findLastPunctuation,_ as fixBracketTypos,v as fixCurlyBraces,y as fixMismatchedQuotationMarks,o as fixTrailingWow,b as formatStringBySentence,i as getArabicScore,oe as hasWordInSingleLine,te as insertLineBreaksAfterPunctuation,x as isAllUppercase,ye as isBalanced,N as isJsonStructureValid,se as isOnlyPunctuation,Be as makeDiacriticInsensitive,p as makeDiacriticInsensitiveRegex,Z as normalize,Ve as normalizeArabicPrefixesToAl,He as normalizeDoubleApostrophes,M as normalizeJsonSyntax,S as normalizeSlashInReferences,C as normalizeSpaces,$ as normalizeTransliteratedEnglish,be as parsePageRanges,Te as preformatArabicText,pe as reduceMultilineBreaksToDouble,me as reduceMultilineBreaksToSingle,ee as removeAllTags,Q as removeArabicPrefixes,Ae as removeDeathYear,Pe as removeMarkdownFormatting,c as removeNonIndexSignatures,je as removeNumbersAndDashes,w as removeRedundantPunctuation,Me as removeSingleDigitReferences,l as removeSingularCodes,u as removeSolitaryArabicLetters,T as removeSpaceInsideBrackets,Ne as removeUrls,E as replaceDoubleBracketsWithArrows,d as replaceEnglishPunctuationWithArabic,Oe as replaceLineBreaksWithSpaces,Ue as replaceSalutationsWithSymbol,P as splitByQuotes,ke as stripAllDigits,D as stripBoldStyling,O as stripItalicsStyling,k as stripStyling,A as toTitleCase,j as trimSpaceInsideQuotes,Fe as truncate,Ie as truncateMiddle,Le as unescapeSpaces};
|
|
10
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DIACRITICS_CLASS","sentences: string[]","italicMap: Record<string, string>","stack: string[]","EQUIV_GROUPS: string[][]"],"sources":["../src/constants.ts","../src/arabic.ts","../src/cleaning.ts","../src/formatting.ts","../src/parsing.ts","../src/sanitization.ts","../src/transliteration.ts"],"sourcesContent":["/** Matches text ending with common punctuation marks */\nexport const PATTERN_ENDS_WITH_PUNCTUATION = /[.!?؟؛]$/;\n","import { PATTERN_ENDS_WITH_PUNCTUATION } from './constants';\n\n/**\n * Converts Arabic-Indic numerals (٠-٩) to a JavaScript number.\n *\n * This function finds all Arabic-Indic digits in the input string and converts them\n * to their corresponding Arabic (Western) digits, then parses the result as an integer.\n *\n * Arabic-Indic digits mapping:\n * - ٠ → 0, ١ → 1, ٢ → 2, ٣ → 3, ٤ → 4\n * - ٥ → 5, ٦ → 6, ٧ → 7, ٨ → 8, ٩ → 9\n *\n * @param arabic - The string containing Arabic-Indic numerals to convert\n * @returns The parsed integer value of the converted numerals\n *\n * @example\n * ```typescript\n * arabicNumeralToNumber(\"١٢٣\"); // returns 123\n * arabicNumeralToNumber(\"٥٠\"); // returns 50\n * arabicNumeralToNumber(\"abc١٢٣xyz\"); // returns 123 (non-digits ignored)\n * arabicNumeralToNumber(\"\"); // returns NaN\n * ```\n *\n * Returns NaN if no valid Arabic-Indic digits are found\n */\nexport const arabicNumeralToNumber = (arabic: string) => {\n return parseInt(\n arabic.replace(/[\\u0660-\\u0669]/g, (c) => (c.charCodeAt(0) - 0x0660).toString()),\n 10,\n );\n};\n\n/**\n * Removes extreme Arabic underscores (ـ) that appear at the beginning or end of a line or in text.\n * Does not affect Hijri dates (e.g., 1424هـ) or specific Arabic terms.\n * Example: \"ـThis is a textـ\" will be changed to \"This is a text\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with extreme underscores removed.\n */\nexport const cleanExtremeArabicUnderscores = (text: string) => {\n return text.replace(/(?<!\\d ?ه|اه)ـ(?=\\r?$)|^ـ(?!اهـ)/gm, '');\n};\n\n/**\n * Converts Urdu symbols to their Arabic equivalents.\n * Example: 'ھذا' will be changed to 'هذا', 'ی' to 'ي'.\n * @param {string} text - The input text containing Urdu symbols.\n * @returns {string} - The modified text with Urdu symbols converted to Arabic symbols.\n */\nexport const convertUrduSymbolsToArabic = (text: string) => {\n return text.replace(/ھ/g, 'ه').replace(/ی/g, 'ي');\n};\n\n/**\n * Calculates the proportion of Arabic characters in text relative to total non-whitespace, non-digit characters.\n * Digits (ASCII and Arabic-Indic variants) are excluded from both numerator and denominator.\n * @param text - The input text to analyze\n * @returns A decimal between 0-1 representing the Arabic character ratio (0 = no Arabic, 1 = all Arabic)\n */\nexport const getArabicScore = (text: string) => {\n if (!text) {\n return 0;\n }\n // Arabic letters (letters/ranges only)\n const arabicLettersPattern = /[\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/g;\n // ASCII digits + Arabic-Indic digits + Extended Arabic-Indic digits\n const allDigitPattern = /[0-9\\u0660-\\u0669\\u06F0-\\u06F9]/g;\n // Counted characters exclude whitespace and all listed digits\n const countedCharsPattern = /[^\\s0-9\\u0660-\\u0669\\u06F0-\\u06F9]/g;\n const cleaned = text.replace(allDigitPattern, '');\n const arabicMatches = cleaned.match(arabicLettersPattern) || [];\n const totalMatches = cleaned.match(countedCharsPattern) || [];\n return totalMatches.length === 0 ? 0 : arabicMatches.length / totalMatches.length;\n};\n\n/**\n * Finds the position of the last punctuation character in a string\n *\n * @param text - The text to search through\n * @returns The index of the last punctuation character, or -1 if none found\n *\n * @example\n * ```typescript\n * const text = \"Hello world! How are you?\";\n * const lastPuncIndex = findLastPunctuation(text);\n * // Result: 24 (position of the last '?')\n *\n * const noPuncText = \"Hello world\";\n * const notFound = findLastPunctuation(noPuncText);\n * // Result: -1 (no punctuation found)\n * ```\n */\nexport const findLastPunctuation = (text: string) => {\n for (let i = text.length - 1; i >= 0; i--) {\n if (PATTERN_ENDS_WITH_PUNCTUATION.test(text[i])) {\n return i;\n }\n }\n\n return -1;\n};\n\n/**\n * Fixes the trailing \"و\" (wow) in phrases such as \"عليكم و رحمة\" to \"عليكم ورحمة\".\n * This function attempts to correct phrases where \"و\" appears unnecessarily, particularly in greetings.\n * Example: 'السلام عليكم و رحمة' will be changed to 'السلام عليكم ورحمة'.\n * @param {string} text - The input text containing the \"و\" character.\n * @returns {string} - The modified text with unnecessary trailing \"و\" characters corrected.\n */\nexport const fixTrailingWow = (text: string) => {\n return text.replace(/ و /g, ' و');\n};\n\n/**\n * Inserts a space between Arabic text and numbers.\n * Example: 'الآية37' will be changed to 'الآية 37'.\n * @param {string} text - The input text containing Arabic text followed by numbers.\n * @returns {string} - The modified text with spaces inserted between Arabic text and numbers.\n */\nexport const addSpaceBetweenArabicTextAndNumbers = (text: string) => {\n return text.replace(/([\\u0600-\\u06FF]+)(\\d+)/g, '$1 $2');\n};\n\n/**\n * Removes single-digit numbers surrounded by Arabic text. Also removes dashes (-) not followed by a number.\n * For example, removes '3' from 'وهب 3 وقال' but does not remove '121' from 'لوحه 121 الجرح'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with non-index numbers and dashes removed.\n */\nexport const removeNonIndexSignatures = (text: string) => {\n return text\n .replace(/(?<![0-9] ?)-|(?<=[\\u0600-\\u06FF])\\s?\\d\\s?(?=[\\u0600-\\u06FF])/g, ' ')\n .replace(/(?<=[\\u0600-\\u06FF]\\s)(\\d+\\s)+\\d+(?=(\\s[\\u0600-\\u06FF]|$))/g, ' ');\n};\n\n/**\n * Removes characters enclosed in square brackets [] or parentheses () if they are Arabic letters or Arabic-Indic numerals.\n * Example: '[س]' or '(س)' will be removed.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with singular codes removed.\n */\nexport const removeSingularCodes = (text: string) => {\n return text.replace(/[[({][\\u0621-\\u064A\\u0660-\\u0669][\\])}]/g, '');\n};\n\n/**\n * Removes solitary Arabic letters unless they are the 'ha' letter, which is used in Hijri years.\n * Example: \"ب ا الكلمات ت\" will be changed to \"ا الكلمات\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with solitary Arabic letters removed.\n */\nexport const removeSolitaryArabicLetters = (text: string) => {\n return text.replace(/(^| )[\\u0621-\\u064A]( |$)/g, ' ');\n};\n\n/**\n * Replaces English punctuation (question mark and semicolon) with their Arabic equivalents.\n * Example: '?' will be replaced with '؟', and ';' with '؛'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with English punctuation replaced by Arabic punctuation.\n */\nexport const replaceEnglishPunctuationWithArabic = (text: string) => {\n return text\n .replace(/\\?|؟\\./g, '؟')\n .replace(/(;|؛)\\s*(\\1\\s*)*/g, '؛')\n .replace(/,|-،/g, '،');\n};\n","/** Character class for Arabic diacritics (tashkīl/harakāt). */\nconst DIACRITICS_CLASS = '[\\\\u0610-\\\\u061A\\\\u064B-\\\\u065F\\\\u0670\\\\u06D6-\\\\u06ED]';\n/** Tatweel (kashīda) class. */\nconst TATWEEL_CLASS = '\\\\u0640';\n\n/**\n * Escape a string so it can be safely embedded into a RegExp source.\n *\n * @param s Any string\n * @returns Escaped string\n */\nexport const escapeRegex = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Optional equivalence toggles for {@link makeDiacriticInsensitiveRegex}. */\ntype EquivOptions = {\n /** Treat ا/أ/إ/آ as equivalent. @default true */\n alif?: boolean;\n /** Treat ة/ه as equivalent. @default true */\n taMarbutahHa?: boolean;\n /** Treat ى/ي as equivalent. @default true */\n alifMaqsurahYa?: boolean;\n};\n\n/** Options for {@link makeDiacriticInsensitiveRegex}. */\nexport type MakeRegexOptions = {\n /**\n * Character equivalences to allow.\n * @default { alif: true, taMarbutahHa: true, alifMaqsurahYa: true }\n */\n equivalences?: EquivOptions;\n\n /**\n * Allow tatweel between letters (tolerate decorative elongation).\n * @default true\n */\n allowTatweel?: boolean;\n\n /**\n * Ignore diacritics by inserting a `DIACRITICS_CLASS*` after each letter.\n * @default true\n */\n ignoreDiacritics?: boolean;\n\n /**\n * Treat any whitespace in the needle as `\\s+` for flexible matching.\n * @default true\n */\n flexWhitespace?: boolean;\n\n /**\n * RegExp flags to use.\n * @default 'u'\n */\n flags?: string;\n};\n\n/**\n * Build a **diacritic-insensitive**, **tatweel-tolerant** RegExp for Arabic text matching.\n *\n * Features:\n * - Optional character equivalences: ا~أ~إ~آ, ة~ه, ى~ي.\n * - Optional tolerance for tatweel between characters.\n * - Optional diacritic-insensitivity (by inserting a diacritics class after each char).\n * - Optional flexible whitespace (needle whitespace becomes `\\s+`).\n *\n * @param needle The Arabic text to match\n * @param opts See {@link MakeRegexOptions}\n * @returns A `RegExp` matching the needle with the desired tolerances\n *\n * @example\n * const rx = makeDiacriticInsensitiveRegex('أنا إلى الآفاق');\n * rx.test('انا الي الافاق'); // true\n * rx.test('اَنا إلى الآفاق'); // true\n */\nexport const makeDiacriticInsensitiveRegex = (needle: string, opts: MakeRegexOptions = {}): RegExp => {\n const {\n equivalences = { alif: true, taMarbutahHa: true, alifMaqsurahYa: true },\n allowTatweel = true,\n ignoreDiacritics = true,\n flexWhitespace = true,\n flags = 'u',\n } = opts;\n\n // Safety guard against extremely large inputs causing excessive pattern sizes\n if (needle.length > 5000) {\n throw new Error('makeDiacriticInsensitiveRegex: needle too long');\n }\n\n const charClass = (ch: string): string => {\n switch (ch) {\n case 'ا':\n case 'أ':\n case 'إ':\n case 'آ':\n return equivalences.alif ? '[اأإآ]' : 'ا';\n case 'ة':\n case 'ه':\n return equivalences.taMarbutahHa ? '[هة]' : escapeRegex(ch);\n case 'ى':\n case 'ي':\n return equivalences.alifMaqsurahYa ? '[ىي]' : escapeRegex(ch);\n default:\n return escapeRegex(ch);\n }\n };\n\n const after = `${ignoreDiacritics ? `${DIACRITICS_CLASS}*` : ''}${allowTatweel ? `${TATWEEL_CLASS}*` : ''}`;\n\n let pattern = '';\n for (const ch of Array.from(needle)) {\n if (/\\s/.test(ch)) {\n pattern += flexWhitespace ? '\\\\s+' : '\\\\s*';\n } else {\n pattern += `${charClass(ch)}${after}`;\n }\n }\n\n return new RegExp(pattern, flags);\n};\n\nexport const removeAllTags = (content: string) => content.replace(/<[^>]*>/g, '');\n","/**\n * Adds line breaks after punctuation marks such as periods, exclamation points, and question marks.\n * Example: 'Text.' becomes 'Text.\\n'.\n * @param {string} text - The input text containing punctuation.\n * @returns {string} - The modified text with line breaks added after punctuation.\n */\nexport const insertLineBreaksAfterPunctuation = (text: string) => {\n // Define the punctuation marks that should trigger a new line\n const punctuation = /([.?!؟])/g;\n\n // Replace occurrences of punctuation marks followed by a space with the punctuation mark, a newline, and the space\n const formattedText = text.replace(punctuation, '$1\\n').replace(/\\n\\s+/g, '\\n').trim();\n\n return formattedText;\n};\n\n/**\n * Adds spaces before and after punctuation, except for certain cases like quoted text or ayah references.\n * Example: 'Text,word' becomes 'Text, word'.\n * @param {string} text - The input text containing punctuation.\n * @returns {string} - The modified text with spaces added before and after punctuation.\n */\nexport const addSpaceBeforeAndAfterPunctuation = (text: string) => {\n return text\n .replace(/( ?)([.!?,،؟;؛])((?![ '”“)\"\\]\\n])|(?=\\s{2,}))/g, '$1$2 ')\n .replace(/\\s([.!?,،؟;؛])\\s*([ '”“)\"\\]\\n])/g, '$1$2')\n .replace(/([^\\s\\w\\d'”“)\"\\]]+)\\s+([.!?,،؟;؛])|([.!?,،؟;؛])\\s+$/g, '$1$2$3')\n .replace(/(?<=\\D)( ?: ?)(?!(\\d+:)|(:\\d+))|(?<=\\d) ?: ?(?=\\D)|(?<=\\D) ?: ?(?=\\d)/g, ': ');\n};\n\n/**\n * Turns regular double quotes surrounding a body of text into smart quotes.\n * Also fixes incorrect starting quotes by ensuring the string starts with an opening quote if needed.\n * Example: 'The \"quick brown\" fox' becomes 'The “quick brown” fox'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with smart quotes applied.\n */\nexport const applySmartQuotes = (text: string) => {\n return text\n .replace(/[“”]/g, '\"')\n .replace(/\"([^\"]*)\"/g, '“$1”')\n .replace(/^”/g, '“');\n};\n\n/**\n * Replaces literal new line characters (\\n) and carriage returns (\\r) with actual line breaks.\n * Example: 'A\\\\nB' becomes 'A\\nB'.\n * @param {string} text - The input text containing literal new lines.\n * @returns {string} - The modified text with actual line breaks.\n */\nexport const cleanLiteralNewLines = (text: string) => {\n return text.replace(/\\\\n|\\r/g, '\\n');\n};\n\n/**\n * Removes trailing spaces from each line in a multiline string.\n * Example: \" This is a line \\nAnother line \" becomes \"This is a line\\nAnother line\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with trailing spaces removed.\n */\nexport const cleanMultilines = (text: string) => {\n return text.replace(/^ +| +$/gm, '');\n};\n\n/**\n * Detects if a word is by itself in a line.\n * @param text The text to check.\n * @returns true if there exists a word in any of the lines in the text that is by itself.\n */\nexport const hasWordInSingleLine = (text: string): boolean => {\n return /^\\s*\\S+\\s*$/gm.test(text);\n};\n\n/**\n * Checks if the input string consists of only punctuation characters.\n * @param {string} text - The input text to check.\n * @returns {boolean} - Returns true if the string contains only punctuation, false otherwise.\n */\nexport const isOnlyPunctuation = (text: string): boolean => {\n const regex = /^[\\u0020-\\u002f\\u003a-\\u0040\\u005b-\\u0060\\u007b-\\u007e0-9٠-٩]+$/;\n return regex.test(text);\n};\n\n/**\n * Cleans unnecessary spaces before punctuation marks such as periods, commas, and question marks.\n * Example: 'This is a sentence , with extra space .' becomes 'This is a sentence, with extra space.'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with cleaned spaces before punctuation.\n */\nexport const cleanSpacesBeforePeriod = (text: string) => {\n return text.replace(/\\s+([.؟!,،؛:?])/g, '$1');\n};\n\n/**\n * Condenses multiple asterisks (*) into a single one.\n * Example: '***' becomes '*'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed asterisks.\n */\nexport const condenseAsterisks = (text: string) => {\n return text.replace(/(\\*\\s*)+/g, '*');\n};\n\n/**\n * Replaces occurrences of colons surrounded by periods (e.g., '.:.' or ':') with a single colon.\n * Example: 'This.:. is a test' becomes 'This: is a test'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed colons.\n */\nexport const condenseColons = (text: string) => {\n return text.replace(/[.-]?:[.-]?/g, ':');\n};\n\n/**\n * Condenses two or more dashes (--) into a single dash (-).\n * Example: 'This is some ---- text' becomes 'This is some - text'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed dashes.\n */\nexport const condenseDashes = (text: string) => {\n return text.replace(/-{2,}/g, '-');\n};\n\n/**\n * Replaces sequences of two or more periods (e.g., '...') with an ellipsis character (…).\n * Example: 'This is a test...' becomes 'This is a test…'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with ellipses condensed.\n */\nexport const condenseEllipsis = (text: string) => {\n return text.replace(/\\.{2,}/g, '…');\n};\n\n/**\n * Reduces multiple consecutive line breaks (3 or more) to exactly 2 line breaks.\n * Example: 'This is line 1\\n\\n\\n\\nThis is line 2' becomes 'This is line 1\\n\\nThis is line 2'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed line breaks.\n */\nexport const reduceMultilineBreaksToDouble = (text: string) => {\n return text.replace(/(\\n\\s*){3,}/g, '\\n\\n');\n};\n\n/**\n * Reduces multiple consecutive line breaks (2 or more) to exactly 1 line break.\n * Example: 'This is line 1\\n\\nThis is line 2' becomes 'This is line 1\\nThis is line 2'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed line breaks.\n */\nexport const reduceMultilineBreaksToSingle = (text: string) => {\n return text.replace(/(\\n\\s*){2,}/g, '\\n');\n};\n\n/**\n * Condenses multiple periods separated by spaces (e.g., '. . .') into a single period.\n * Example: 'This . . . is a test' becomes 'This. is a test'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed periods.\n */\nexport const condensePeriods = (text: string) => {\n return text.replace(/\\. +\\./g, '.');\n};\n\n/**\n * Condenses multiple underscores (__) or Arabic Tatweel characters (ـــــ) into a single underscore or Tatweel.\n * Example: 'This is ـــ some text __' becomes 'This is ـ some text _'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed underscores.\n */\nexport const condenseUnderscores = (text: string) => {\n return text.replace(/ـ{2,}/g, 'ـ').replace(/_+/g, '_');\n};\n\n/**\n * Replaces double parentheses or brackets with single ones.\n * Example: '((text))' becomes '(text)'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed brackets.\n */\nexport const doubleToSingleBrackets = (text: string) => {\n return text.replace(/(\\(|\\)){2,}|(\\[|\\]){2,}/g, '$1$2');\n};\n\n/**\n * Ensures at most 1 space exists before any word before brackets.\n * Adds a space if there isn't one, or reduces multiple spaces to one.\n * @param {string} text - The input text to modify\n * @returns {string} - The modified text with proper spacing before brackets\n */\nexport const ensureSpaceBeforeBrackets = (text: string) => {\n return text.replace(/(\\S) *(\\([^)]*\\))/g, '$1 $2');\n};\n\n/**\n * Ensures at most 1 space exists before any word before Arabic quotation marks.\n * Adds a space if there isn't one, or reduces multiple spaces to one.\n * @param {string} text - The input text to modify\n * @returns {string} - The modified text with proper spacing before Arabic quotes\n */\nexport const ensureSpaceBeforeQuotes = (text: string) => {\n return text.replace(/(\\S) *(«[^»]*»)/g, '$1 $2');\n};\n\n/**\n * Fixes common bracket and quotation mark typos in text\n * Corrects malformed patterns like \"(«\", \"»)\", and misplaced digits in brackets\n * @param text - Input text that may contain bracket typos\n * @returns Text with corrected bracket and quotation mark combinations\n */\nexport const fixBracketTypos = (text: string) => {\n return (\n text\n .replace(/\\(«|\\( \\(/g, '«')\n .replace(/»\\)|\\) \\)/g, '»')\n // Fix \")digit)\" pattern to \"(digit)\"\n .replace(/\\)([0-9\\u0660-\\u0669]+)\\)/g, '($1)')\n // Fix \")digit(\" pattern to \"(digit)\"\n .replace(/\\)([0-9\\u0660-\\u0669]+)\\(/g, '($1)')\n );\n};\n\n/**\n * Fixes mismatched curly braces by converting incorrect bracket/brace combinations\n * to proper curly braces { }\n * @param text - Input text that may contain mismatched curly braces\n * @returns Text with corrected curly brace pairs\n */\nexport const fixCurlyBraces = (text: string) => {\n // Process each mismatch type separately to avoid interference\n let result = text;\n\n // Fix ( content } to { content }\n result = result.replace(/\\(([^(){}]+)\\}/g, '{$1}');\n\n // Fix { content ) to { content }\n return result.replace(/\\{([^(){}]+)\\)/g, '{$1}');\n};\n\n/**\n * Fixes mismatched quotation marks in Arabic text by converting various\n * incorrect bracket/quote combinations to proper Arabic quotation marks (« »)\n * @param text - Input text that may contain mismatched quotation marks\n * @returns Text with corrected Arabic quotation marks\n */\nexport const fixMismatchedQuotationMarks = (text: string) => {\n return (\n text\n // Matches mismatched quotation marks: « followed by content and closed with )\n .replace(/«([^»)]+)\\)/g, '«$1»')\n // Fix reverse mismatched ( content » to « content »\n .replace(/\\(([^()]+)»/g, '«$1»')\n // Matches any unclosed « quotation marks at end of content\n .replace(/«([^»]+)(?=\\s*$|$)/g, '«$1»')\n );\n};\n\n/**\n * Formats a multiline string by joining sentences and maintaining footnotes on their own lines.\n * Footnotes are identified by Arabic and English numerals.\n * Example: 'Sentence one.\\n(1) A footnote.\\nSentence two.' remains the same, while regular sentences are joined.\n * @param {string} input - The input text containing sentences and footnotes.\n * @returns {string} - The formatted text.\n */\nexport const formatStringBySentence = (input: string) => {\n const footnoteRegex = /^\\((?:\\d+|۱|۲|۳|۴|۵|۶|۷|۸|۹)\\)\\s/;\n const sentences: string[] = [];\n const lines = input.split('\\n');\n let currentSentence = '';\n\n lines.forEach((line) => {\n const trimmedLine = line.trim();\n const isFootnote = footnoteRegex.test(trimmedLine);\n const isNumber = /^\\(\\d+\\/\\d+\\)/.test(trimmedLine);\n\n if (isFootnote && !isNumber) {\n if (currentSentence) {\n sentences.push(currentSentence.trim());\n currentSentence = '';\n }\n sentences.push(trimmedLine);\n } else {\n currentSentence += `${trimmedLine} `;\n const lastChar = currentSentence.trim().slice(-1);\n if (/[.!؟]/.test(lastChar)) {\n sentences.push(currentSentence.trim());\n currentSentence = '';\n }\n }\n });\n\n // Add any remaining text to the output\n if (currentSentence) {\n sentences.push(currentSentence.trim());\n }\n\n return sentences.join('\\n');\n};\n\n/**\n * Detects if text is entirely in uppercase letters\n * @param text - The text to check\n * @returns true if all alphabetic characters are uppercase, false otherwise\n */\nexport const isAllUppercase = (text: string) => {\n // Remove non-letter characters (including numbers, punctuation, spaces)\n // \\p{L} matches any Unicode letter character\n const lettersOnly = text.replace(/[^\\p{L}]/gu, '');\n\n // If there are no letter characters, return false\n if (lettersOnly.length === 0) {\n return false;\n }\n\n return lettersOnly === lettersOnly.toUpperCase();\n};\n\n/**\n * Removes unnecessary spaces around slashes in references.\n * Example: '127 / 11' becomes '127/11'.\n * @param {string} text - The input text containing references.\n * @returns {string} - The modified text with spaces removed around slashes.\n */\nexport const normalizeSlashInReferences = (text: string) => {\n return text.replace(/(\\d+)\\s?\\/\\s?(\\d+)/g, '$1/$2');\n};\n\n/**\n * Reduces multiple spaces or tabs to a single space.\n * Example: 'This is a text' becomes 'This is a text'.\n * @param {string} text - The input text containing extra spaces.\n * @returns {string} - The modified text with reduced spaces.\n */\nexport const normalizeSpaces = (text: string) => {\n return text.replace(/[ \\t]+/g, ' ');\n};\n\n/**\n * Removes redundant punctuation marks that follow Arabic question marks or exclamation marks.\n * This function cleans up text by removing periods (.) or Arabic commas (،) that immediately\n * follow Arabic question marks (؟) or exclamation marks (!), as they are considered redundant\n * in proper Arabic punctuation.\n *\n * @param text - The Arabic text to clean up\n * @returns The text with redundant punctuation removed\n *\n * @example\n * ```typescript\n * removeRedundantPunctuation('كيف حالك؟.') // Returns: 'كيف حالك؟'\n * removeRedundantPunctuation('ممتاز!،') // Returns: 'ممتاز!'\n * removeRedundantPunctuation('هذا جيد.') // Returns: 'هذا جيد.' (unchanged)\n * ```\n */\nexport const removeRedundantPunctuation = (text: string) => {\n return text.replace(/([؟!])[.،]/g, '$1');\n};\n\n/**\n * Removes spaces inside brackets, parentheses, or square brackets.\n * Example: '( a b )' becomes '(a b)'.\n * @param {string} text - The input text with spaces inside brackets.\n * @returns {string} - The modified text with spaces removed inside brackets.\n */\nexport const removeSpaceInsideBrackets = (text: string) => {\n return text.replace(/([[(])\\s*(.*?)\\s*([\\])])/g, '$1$2$3');\n};\n\n/**\n * Replaces double parentheses single a single arrow variation.\n * Example: '((text))' becomes '«text»'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed brackets.\n */\nexport const replaceDoubleBracketsWithArrows = (text: string) => {\n return text.replace(/\\(\\(\\s?/g, '«').replace(/\\s?\\)\\)/g, '»');\n};\n\n/**\n * Removes bold styling from text by normalizing the string and removing stylistic characters.\n * @param {string} text - The input text containing bold characters.\n * @returns {string} - The modified text with bold styling removed.\n */\nexport const stripBoldStyling = (text: string) => {\n // Normalize the string to NFKD form\n const normalizedString = text.normalize('NFKD');\n\n // Remove combining marks (diacritics) and stylistic characters from the string\n return normalizedString.replace(/[\\u0300-\\u036f]/g, '').trim();\n};\n\n/**\n * Removes italicized characters by replacing italic Unicode characters with their normal counterparts.\n * Example: '𝘼𝘽𝘾' becomes 'ABC'.\n * @param {string} text - The input text containing italicized characters.\n * @returns {string} - The modified text with italics removed.\n */\nexport const stripItalicsStyling = (text: string) => {\n const italicMap: Record<string, string> = {\n '\\uD835\\uDC4E': 'I',\n '\\uD835\\uDC68': 'g',\n '\\u{1D63C}': '!',\n '\\uD835\\uDC4F': 'J',\n '\\uD835\\uDC69': 'h',\n '\\u{1D63D}': '?',\n '\\uD835\\uDC50': 'K',\n '\\uD835\\uDC6A': 'i',\n '\\uD835\\uDC51': 'L',\n '\\uD835\\uDC6B': 'j',\n '\\u{1D63F}': ',',\n '\\uD835\\uDC52': 'M',\n '\\uD835\\uDC6C': 'k',\n '\\u{1D640}': '.',\n '\\uD835\\uDC53': 'N',\n '\\uD835\\uDC6D': 'l',\n '\\uD835\\uDC54': 'O',\n '\\uD835\\uDC6E': 'm',\n '\\uD835\\uDC6F': 'n',\n '\\uD835\\uDC56': 'Q',\n '\\uD835\\uDC70': 'o',\n '\\uD835\\uDC57': 'R',\n '\\uD835\\uDC71': 'p',\n '\\uD835\\uDC58': 'S',\n '\\uD835\\uDC72': 'q',\n '\\uD835\\uDC59': 'T',\n '\\uD835\\uDC73': 'r',\n '\\u{1D647}': '-',\n '\\uD835\\uDC5A': 'U',\n '\\uD835\\uDC74': 's',\n '\\uD835\\uDC5B': 'V',\n '\\uD835\\uDC75': 't',\n '\\uD835\\uDC5C': 'W',\n '\\uD835\\uDC76': 'u',\n '\\uD835\\uDC5D': 'X',\n '\\uD835\\uDC77': 'v',\n '\\uD835\\uDC5E': 'Y',\n '\\uD835\\uDC78': 'w',\n '\\uD835\\uDC5F': 'Z',\n '\\uD835\\uDC79': 'x',\n '\\uD835\\uDC46': 'A',\n '\\uD835\\uDC7A': 'y',\n '\\uD835\\uDC47': 'B',\n '\\uD835\\uDC7B': 'z',\n '\\uD835\\uDC62': 'a',\n '\\uD835\\uDC48': 'C',\n '\\uD835\\uDC63': 'b',\n '\\uD835\\uDC49': 'D',\n '\\uD835\\uDC64': 'c',\n '\\uD835\\uDC4A': 'E',\n '\\uD835\\uDC65': 'd',\n '\\uD835\\uDC4B': 'F',\n '\\uD835\\uDC66': 'e',\n '\\uD835\\uDC4C': 'G',\n '\\uD835\\uDC67': 'f',\n '\\uD835\\uDC4D': 'H',\n '\\uD835\\uDC55': 'P',\n };\n\n return text.replace(/[\\uD835\\uDC62-\\uD835\\uDC7B\\uD835\\uDC46-\\uD835\\uDC5F\\u{1D63C}-\\u{1D647}]/gu, (match) => {\n return italicMap[match] || match;\n });\n};\n\n/**\n * Removes all bold and italic styling from the input text.\n * @param {string} text - The input text to remove styling from.\n * @returns {string} - The modified text with all styling removed.\n */\nexport const stripStyling = (text: string) => {\n return stripItalicsStyling(stripBoldStyling(text));\n};\n\n/**\n * Converts a string to title case (first letter of each word capitalized)\n * @param str - The input string to convert\n * @returns String with each word's first letter capitalized\n */\nexport const toTitleCase = (str: string) => {\n return str\n .toLowerCase()\n .split(' ')\n .map((word) => {\n if (word.length === 0) return word;\n // Find the first Unicode letter in the chunk\n const match = word.match(/\\p{L}/u);\n if (!match || match.index === undefined) return word;\n const i = match.index;\n return word.slice(0, i) + word.charAt(i).toUpperCase() + word.slice(i + 1);\n })\n .join(' ');\n};\n\n/**\n * Removes unnecessary spaces inside quotes.\n * Example: '“ Text ”' becomes '“Text”'.\n * @param {string} text - The input text with spaces inside quotes.\n * @returns {string} - The modified text with spaces removed inside quotes.\n */\nexport const trimSpaceInsideQuotes = (text: string) => {\n return text.replace(/([“”\"]|«) *(.*?) *([“”\"]|»)/g, '$1$2$3');\n};\n","/**\n * Converts a string that resembles JSON but with numeric keys and single-quoted values\n * into valid JSON format. This function replaces numeric keys with quoted numeric keys\n * and ensures all values are double-quoted as required by JSON.\n *\n * @param {string} str - The input string that needs to be fixed into valid JSON.\n * @returns {string} - A valid JSON string.\n *\n * @example\n * const result = normalizeJsonSyntax(\"{10: 'abc', 20: 'def'}\");\n * console.log(result); // '{\"10\": \"abc\", \"20\": \"def\"}'\n */\nexport const normalizeJsonSyntax = (str: string) => {\n let input = str.replace(/(\\b\\d+\\b)(?=:)/g, '\"$1\"');\n input = input.replace(/:\\s*'([^']+)'/g, ': \"$1\"');\n input = input.replace(/:\\s*\"([^\"]+)\"/g, ': \"$1\"');\n\n return JSON.stringify(JSON.parse(input));\n};\n\n/**\n * Checks if a given string resembles a JSON object with numeric or quoted keys and values\n * that are single or double quoted. This is useful for detecting malformed JSON-like\n * structures that can be fixed by the `normalizeJsonSyntax` function.\n *\n * @param {string} str - The input string to check.\n * @returns {boolean} - Returns true if the string is JSON-like, false otherwise.\n *\n * @example\n * const result = isJsonStructureValid(\"{10: 'abc', 'key': 'value'}\");\n * console.log(result); // true\n */\nexport const isJsonStructureValid = (str: string) => {\n // Checks for a pattern with numeric keys or quoted keys and values in quotes\n const jsonLikePattern =\n /^{(\\s*(\\d+|'[^']*'|\"[^\"]*\")\\s*:\\s*('|\")[^'\"]*\\3\\s*,)*(?:\\s*(\\d+|'[^']*'|\"[^\"]*\")\\s*:\\s*('|\")[^'\"]*\\5\\s*)}$/;\n return jsonLikePattern.test(str.trim());\n};\n\n/**\n * Splits a string by spaces and quoted substrings.\n *\n * This function takes an input string and splits it into parts where substrings\n * enclosed in double quotes are treated as a single part. Other substrings\n * separated by spaces are split normally.\n *\n * @param {string} query - The input string to be split.\n * @returns {string[]} An array of strings, with quoted substrings kept intact.\n *\n * @example\n * const result = splitByQuotes('\"This is\" \"a part of the\" \"string and\"');\n * console.log(result); // [\"This is\", \"a part of the\", \"string and\"]\n */\nexport const splitByQuotes = (query: string): string[] => {\n const regex = /(?:[^\\s\"]+|\"(.*?)\")+/g;\n return (query.match(regex) || []).map((s: string) => (s.startsWith('\"') ? s.slice(1, -1) : s));\n};\n\n/**\n * Checks if all double quotes in a string are balanced (even count).\n * A string has balanced quotes if every opening quote has a corresponding closing quote.\n *\n * @param str - The string to check for balanced quotes\n * @returns True if quotes are balanced (even count), false otherwise\n *\n * @example\n * ```typescript\n * areQuotesBalanced('Hello \"world\"') // Returns: true\n * areQuotesBalanced('Hello \"world') // Returns: false\n * areQuotesBalanced('No quotes') // Returns: true\n * ```\n */\nconst areQuotesBalanced = (str: string) => {\n let quoteCount = 0;\n for (const char of str) {\n if (char === '\"') {\n quoteCount++;\n }\n }\n return quoteCount % 2 === 0;\n};\n\nconst brackets = { '(': ')', '[': ']', '{': '}' };\nconst openBrackets = new Set(['(', '[', '{']);\nconst closeBrackets = new Set([')', ']', '}']);\n\n/**\n * Checks if all brackets in a string are properly balanced and matched.\n * This function validates that every opening bracket has a corresponding closing bracket\n * in the correct order and of the matching type.\n *\n * Supported bracket types: parentheses (), square brackets [], curly braces {}\n *\n * @param str - The string to check for balanced brackets\n * @returns True if all brackets are properly balanced and matched, false otherwise\n *\n * @example\n * ```typescript\n * areBracketsBalanced('(hello [world])') // Returns: true\n * areBracketsBalanced('(hello [world)') // Returns: false (mismatched)\n * areBracketsBalanced('((hello))') // Returns: true\n * areBracketsBalanced('(hello') // Returns: false (unclosed)\n * ```\n */\n\nconst areBracketsBalanced = (str: string) => {\n const stack: string[] = [];\n\n for (const char of str) {\n if (openBrackets.has(char)) {\n stack.push(char);\n } else if (closeBrackets.has(char)) {\n const lastOpen = stack.pop();\n if (!lastOpen || brackets[lastOpen as keyof typeof brackets] !== char) {\n return false;\n }\n }\n }\n\n return stack.length === 0;\n};\n\n/**\n * Checks if both quotes and brackets are balanced in a string.\n * This function combines quote balance checking and bracket balance checking\n * to ensure the entire string has properly balanced punctuation.\n *\n * A string is considered balanced when:\n * - All double quotes have matching pairs (even count)\n * - All brackets (parentheses, square brackets, curly braces) are properly matched and nested\n *\n * @param str - The string to check for balanced quotes and brackets\n * @returns True if both quotes and brackets are balanced, false otherwise\n *\n * @example\n * ```typescript\n * isBalanced('He said \"Hello (world)!\"') // Returns: true\n * isBalanced('He said \"Hello (world!\"') // Returns: false (unbalanced quote)\n * isBalanced('He said \"Hello (world)\"') // Returns: false (unbalanced quote)\n * isBalanced('Hello (world) [test]') // Returns: true\n * ```\n */\nexport const isBalanced = (str: string) => {\n return areQuotesBalanced(str) && areBracketsBalanced(str);\n};\n\n/**\n * Parses page input string into array of page numbers, supporting ranges and lists\n * @param pageInput - Page specification string (e.g., \"1-5\" or \"1,3,5\")\n * @returns Array of page numbers\n * @throws Error when start page exceeds end page in range\n */\nexport const parsePageRanges = (pageInput: string): number[] => {\n if (pageInput.includes('-')) {\n const [start, end] = pageInput.split('-').map(Number);\n\n if (start > end) {\n throw new Error('Start page cannot be greater than end page');\n }\n\n return Array.from({ length: end - start + 1 }, (_, i) => start + i);\n } else {\n return pageInput.split(',').map(Number);\n }\n};\n","import { escapeRegex } from './cleaning';\n\n/**\n * Removes various symbols, part references, and numerical markers from the text.\n * Example: '(1) (2/3)' becomes ''.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with symbols and part references removed.\n */\nexport const cleanSymbolsAndPartReferences = (text: string) => {\n return text.replace(\n / *\\(?:\\d+(?:\\/\\d+){0,2}\\)? *| *\\[\\d+(?:\\/\\d+)?\\] *| *«\\d+» *|\\d+\\/\\d+(?:\\/\\d+)?|[،§{}؍﴿﴾<>;_؟»«:!،؛[\\]…ـ¬.\\\\/*()\"]/g,\n ' ',\n );\n};\n\n/**\n * Removes trailing page numbers formatted as '-[46]-' from the text.\n * Example: 'This is some -[46]- text' becomes 'This is some text'.\n * @param {string} text - The input text with trailing page numbers.\n * @returns {string} - The modified text with page numbers removed.\n */\nexport const cleanTrailingPageNumbers = (text: string) => {\n return text.replace(/-\\[\\d+\\]-/g, '');\n};\n\n/**\n * Replaces consecutive line breaks and whitespace characters with a single space.\n * Example: 'a\\nb' becomes 'a b'.\n * @param {string} text - The input text containing line breaks or multiple spaces.\n * @returns {string} - The modified text with spaces.\n */\nexport const replaceLineBreaksWithSpaces = (text: string) => {\n return text.replace(/\\s+/g, ' ');\n};\n\n/**\n * Removes all numeric digits from the text.\n * Example: 'abc123' becomes 'abc'.\n * @param {string} text - The input text containing digits.\n * @returns {string} - The modified text with digits removed.\n */\nexport const stripAllDigits = (text: string) => {\n return text.replace(/[0-9]/g, '');\n};\n\n/**\n * Removes death year references like \"(d. 390H)\" and \"[d. 100h]\" from the text.\n * Example: 'Sufyān ibn ‘Uyaynah (d. 198h)' becomes 'Sufyān ibn ‘Uyaynah'.\n * @param {string} text - The input text containing death year references.\n * @returns {string} - The modified text with death years removed.\n */\nexport const removeDeathYear = (text: string) => {\n return text.replace(/\\[(d)\\.\\s*\\d{1,4}[hH]\\]\\s*|\\((d)\\.\\s*\\d{1,4}[hH]\\)\\s*/g, '');\n};\n\n/**\n * Removes numeric digits and dashes from the text.\n * Example: 'ABC 123-Xyz' becomes 'ABC Xyz'.\n * @param {string} text - The input text containing digits and dashes.\n * @returns {string} - The modified text with numbers and dashes removed.\n */\nexport const removeNumbersAndDashes = (text: string) => {\n return text.replace(/[\\d-]/g, '');\n};\n\n/**\n * Removes single digit references like (1), «2», [3] from the text.\n * Example: 'Ref (1), Ref «2», Ref [3]' becomes 'Ref , Ref , Ref '.\n * @param {string} text - The input text containing single digit references.\n * @returns {string} - The modified text with single digit references removed.\n */\nexport const removeSingleDigitReferences = (text: string) => {\n return text.replace(/\\(\\d{1}\\)|\\[\\d{1}\\]|«\\d»/g, '');\n};\n\n/**\n * Removes URLs from the text.\n * Example: 'Visit https://example.com' becomes 'Visit '.\n * @param {string} text - The input text containing URLs.\n * @returns {string} - The modified text with URLs removed.\n */\nexport const removeUrls = (text: string) => {\n return text.replace(\n /https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g,\n '',\n );\n};\n\n/**\n * Removes common Markdown formatting syntax from text\n * @param text - The input text containing Markdown formatting\n * @returns Text with Markdown formatting removed (bold, italics, headers, lists, backticks)\n */\nexport const removeMarkdownFormatting = (text: string) => {\n return (\n text\n // Remove bold first (**text**) - must come before italics\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n // Remove bold with underscores (__text__)\n .replace(/__([^_]+)__/g, '$1')\n // Remove italics (*text*)\n .replace(/\\*([^*]+)\\*/g, '$1')\n // Remove italics with underscores (_text_)\n .replace(/_([^_]+)_/g, '$1')\n // Remove strikethrough (~~text~~)\n .replace(/~~([^~]+)~~/g, '$1')\n // Remove blockquotes\n .replace(/^\\s*>\\s?/gm, '')\n // Remove images \n .replace(/!\\[[^\\]]*]\\([^)]*\\)/g, '')\n // Convert links [text](url) -> text\n .replace(/\\[([^\\]]+)]\\([^)]*\\)/g, '$1')\n // Remove headers (# ## ### etc.)\n .replace(/^#+\\s*/gm, '')\n // Remove unordered list markers (- * +)\n .replace(/^\\s*[-*+]\\s+/gm, '')\n // Remove ordered list markers (1. 2. etc.)\n .replace(/^\\s*\\d+\\.\\s+/gm, '')\n // Remove backticks\n .replace(/`/gm, '')\n );\n};\n\n/**\n * Truncates a string to a specified length, adding an ellipsis if truncated.\n *\n * @param val - The string to truncate\n * @param n - Maximum length of the string (default: 150)\n * @returns The truncated string with ellipsis if needed, otherwise the original string\n *\n * @example\n * ```javascript\n * truncate('The quick brown fox jumps over the lazy dog', 20);\n * // Output: 'The quick brown fox…'\n *\n * truncate('Short text', 50);\n * // Output: 'Short text'\n * ```\n */\nexport const truncate = (val: string, n = 150): string => (val.length > n ? `${val.substring(0, n - 1)}…` : val);\n\n/**\n * Truncates a string from the middle, preserving both the beginning and end portions.\n *\n * @param text - The string to truncate\n * @param maxLength - Maximum length of the resulting string (default: 50)\n * @param endLength - Number of characters to preserve at the end (default: 1/3 of maxLength, minimum 3)\n * @returns The truncated string with ellipsis in the middle if needed, otherwise the original string\n *\n * @example\n * ```javascript\n * truncateMiddle('The quick brown fox jumps right over the lazy dog', 20);\n * // Output: 'The quick bro…zy dog'\n *\n * truncateMiddle('The quick brown fox jumps right over the lazy dog', 25, 8);\n * // Output: 'The quick brown …lazy dog'\n *\n * truncateMiddle('Short text', 50);\n * // Output: 'Short text'\n * ```\n */\nexport const truncateMiddle = (text: string, maxLength: number = 50, endLength?: number) => {\n if (text.length <= maxLength) {\n return text;\n }\n\n // Default end length is roughly 1/3 of max length, minimum 3 characters\n const defaultEndLength = Math.max(3, Math.floor(maxLength / 3));\n const actualEndLength = endLength ?? defaultEndLength;\n\n // Reserve space for the ellipsis character (1 char)\n const availableLength = maxLength - 1;\n\n // Calculate start length (remaining space after end portion)\n const startLength = availableLength - actualEndLength;\n\n // Ensure we have at least some characters at the start\n if (startLength < 1) {\n // If we can't fit both start and end, just truncate normally\n return `${text.substring(0, maxLength - 1)}…`;\n }\n\n const startPortion = text.substring(0, startLength);\n const endPortion = text.substring(text.length - actualEndLength);\n\n return `${startPortion}…${endPortion}`;\n};\n\n/**\n * Unescapes backslash-escaped spaces and trims whitespace from both ends.\n * Commonly used to clean file paths that have been escaped when pasted into terminals.\n *\n * @param input - The string to unescape and clean\n * @returns The cleaned string with escaped spaces converted to regular spaces and trimmed\n *\n * @example\n * ```javascript\n * unescapeSpaces('My\\\\ Folder\\\\ Name');\n * // Output: 'My Folder Name'\n *\n * unescapeSpaces(' /path/to/My\\\\ Document.txt ');\n * // Output: '/path/to/My Document.txt'\n *\n * unescapeSpaces('regular text');\n * // Output: 'regular text'\n * ```\n */\nexport const unescapeSpaces = (input: string) => input.replace(/\\\\ /g, ' ').trim();\n\n/**\n * Arabic diacritics (Tashkeel/Harakat).\n */\nconst DIACRITICS_CLASS = '[\\u064B\\u064C\\u064D\\u064E\\u064F\\u0650\\u0651\\u0652]';\n\n/**\n * Groups of equivalent Arabic characters — any character in a group should match\n * any other character in the same group.\n */\nconst EQUIV_GROUPS: string[][] = [\n ['\\u0627', '\\u0622', '\\u0623', '\\u0625'], // ا, آ, أ, إ\n ['\\u0629', '\\u0647'], // ة <-> ه\n ['\\u0649', '\\u064A'], // ى <-> ي\n];\n\n/**\n * Return an escaped character class representing all equivalents for the given character.\n *\n * If the character belongs to one of the predefined equivalence groups (e.g. ا/آ/أ/إ),\n * the returned class will match any member of that group. Otherwise, the original\n * character is simply escaped for safe inclusion in a regular expression.\n *\n * @param ch - A single character to expand into its equivalence class\n * @returns A RegExp-safe string representing the character (and its equivalents when applicable)\n */\nconst getEquivClass = (ch: string): string => {\n for (const group of EQUIV_GROUPS) {\n if (group.includes(ch)) {\n // join the group's members into a character class\n return `[${group.map((c) => escapeRegex(c)).join('')}]`;\n }\n }\n // not in equivalence groups -> return escaped character\n return escapeRegex(ch);\n};\n\n/** Small safe normalization: NFC, remove ZWJ/ZWNJ, collapse spaces. */\nconst normalizeArabicLight = (str: string) => {\n return str\n .normalize('NFC')\n .replace(/[\\u200C\\u200D]/g, '') // remove ZWJ/ZWNJ\n .replace(/\\s+/g, ' ')\n .trim();\n};\n\n/**\n * Creates a diacritic-insensitive regex pattern for Arabic text matching.\n * Normalizes text, handles character equivalences (ا/آ/أ/إ, ة/ه, ى/ي),\n * and makes each character tolerant of Arabic diacritics (Tashkeel/Harakat)\n * @param text - Input Arabic text to make diacritic-insensitive\n * @returns Regex pattern string that matches the text with or without diacritics and character variants\n */\nexport const makeDiacriticInsensitive = (text: string) => {\n const diacriticsMatcher = `${DIACRITICS_CLASS}*`;\n const norm = normalizeArabicLight(text);\n // Use Array.from to iterate grapheme-safe over the string (works fine for Arabic letters)\n return Array.from(norm)\n .map((ch) => getEquivClass(ch) + diacriticsMatcher)\n .join('');\n};\n","import { normalizeSpaces } from './formatting';\n\n/**\n * Replaces common Arabic prefixes (like 'Al-', 'Ar-', 'Ash-', etc.) with 'al-' in the text.\n * Handles different variations of prefixes such as Ash- and Al- but not when the second word\n * does not start with 'S'.\n * Example: 'Ash-Shafiee' becomes 'al-Shafiee'.\n *\n * @param {string} text - The input text containing Arabic prefixes.\n * @returns {string} - The modified text with standardized 'al-' prefixes.\n */\nexport const normalizeArabicPrefixesToAl = (text: string) => {\n return text\n .replace(/(\\b|\\W)(Al |Al-|Ar-|As-|Adh-|Ad-|Ats-|Ath |Ath-|Az |Az-|az-|adh-|as-|ar-)/g, '$1al-')\n .replace(/(\\b|\\W)(Ash-S|ash-S)/g, '$1al-S')\n .replace(/al- (.+?)\\b/g, 'al-$1');\n};\n\n/**\n * Removes double occurrences of Arabic apostrophes such as ʿʿ or ʾʾ in the text.\n * Example: 'ʿulamāʾʾ' becomes 'ʿulamāʾ'.\n *\n * @param {string} text - The input text containing double apostrophes.\n * @returns {string} - The modified text with condensed apostrophes.\n */\nexport const normalizeDoubleApostrophes = (text: string) => {\n return text.replace(/ʿʿ/g, 'ʿ').replace(/ʾʾ/g, 'ʾ');\n};\n\n/**\n * Replaces common salutations such as \"sallahu alayhi wasallam\" with \"ﷺ\" in the text.\n * It also handles variations of the salutation phrase, including 'peace and blessings be upon him'.\n * Example: 'Then Muḥammad (sallahu alayhi wasallam)' becomes 'Then Muḥammad ﷺ'.\n *\n * @param {string} text - The input text containing salutations.\n * @returns {string} - The modified text with salutations replaced.\n */\nexport const replaceSalutationsWithSymbol = (text: string) => {\n return text\n .replace(\n /\\(peace be upon him\\)|(Messenger of (Allah|Allāh)|Messenger|Prophet|Mu[hḥ]ammad) *\\((s[^)]*m|peace[^)]*him|May[^)]*him|may[^)]*him)\\)*/gi,\n '$1 ﷺ',\n )\n .replace(/,\\s*ﷺ\\s*,/g, ' ﷺ');\n};\n\n/**\n * Normalizes the text by removing diacritics, apostrophes, and dashes.\n * Example: 'Al-Jadwal' becomes 'AlJadwal'.\n *\n * @param {string} input - The input text to normalize.\n * @returns {string} - The normalized text.\n */\nexport const normalize = (input: string) => {\n return input\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/`|ʾ|ʿ|-/g, '');\n};\n\n/**\n * Strips common Arabic prefixes like 'al-', 'bi-', 'fī', 'wa-', etc. from the beginning of words.\n * Example: 'al-Bukhari' becomes 'Bukhari'.\n *\n * @param {string} text - The input text containing Arabic prefixes.\n * @returns {string} - The modified text with prefixes stripped.\n */\nexport const removeArabicPrefixes = (text: string) => {\n return normalizeSpaces(text.replace(/(\\bal-|\\bli-|\\bbi-|\\bfī|\\bwa[-\\s]+|\\bl-|\\bliʿl|\\Bʿalá|\\Bʿan|\\bb\\.)/gi, ''));\n};\n\n/**\n * Simplifies English transliterations by removing diacritics, apostrophes, and common prefixes.\n * Example: 'Al-Jadwal' becomes 'Jadwal', and 'āḍġḥīṣṭū' becomes 'adghistu'.\n *\n * @param {string} text - The input text to simplify.\n * @returns {string} - The simplified text.\n */\nexport const normalizeTransliteratedEnglish = (text: string) => normalize(removeArabicPrefixes(text));\n\n/**\n * Extracts the initials from the input string, typically used for names or titles.\n * Example: 'Nayl al-Awtar' becomes 'NA'.\n *\n * @param {string} text - The input text to extract initials from.\n * @returns {string} - The extracted initials.\n */\nexport const extractInitials = (fullName: string) => {\n const initials = normalizeTransliteratedEnglish(fullName)\n .trim()\n .split(/[ -]/)\n .slice(0, 2)\n .map((word) => {\n return word.charAt(0).toUpperCase();\n })\n .join('');\n return initials;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DIACRITICS_CLASS","sentences: string[]","italicMap: Record<string, string>","stack: string[]","currentFlags","preformatArabicText: PreformatArabicText","EQUIV_GROUPS: string[][]"],"sources":["../src/constants.ts","../src/arabic.ts","../src/cleaning.ts","../src/formatting.ts","../src/parsing.ts","../src/preformat-core.ts","../src/preformat.ts","../src/sanitization.ts","../src/transliteration.ts"],"sourcesContent":["/** Matches text ending with common punctuation marks */\nexport const PATTERN_ENDS_WITH_PUNCTUATION = /[.!?؟؛]$/;\n","import { PATTERN_ENDS_WITH_PUNCTUATION } from './constants';\n\n/**\n * Converts Arabic-Indic numerals (٠-٩) to a JavaScript number.\n *\n * This function finds all Arabic-Indic digits in the input string and converts them\n * to their corresponding Arabic (Western) digits, then parses the result as an integer.\n *\n * Arabic-Indic digits mapping:\n * - ٠ → 0, ١ → 1, ٢ → 2, ٣ → 3, ٤ → 4\n * - ٥ → 5, ٦ → 6, ٧ → 7, ٨ → 8, ٩ → 9\n *\n * @param arabic - The string containing Arabic-Indic numerals to convert\n * @returns The parsed integer value of the converted numerals\n *\n * @example\n * ```typescript\n * arabicNumeralToNumber(\"١٢٣\"); // returns 123\n * arabicNumeralToNumber(\"٥٠\"); // returns 50\n * arabicNumeralToNumber(\"abc١٢٣xyz\"); // returns 123 (non-digits ignored)\n * arabicNumeralToNumber(\"\"); // returns NaN\n * ```\n *\n * Returns NaN if no valid Arabic-Indic digits are found\n */\nexport const arabicNumeralToNumber = (arabic: string) => {\n return parseInt(\n arabic.replace(/[\\u0660-\\u0669]/g, (c) => (c.charCodeAt(0) - 0x0660).toString()),\n 10,\n );\n};\n\n/**\n * Removes extreme Arabic underscores (ـ) that appear at the beginning or end of a line or in text.\n * Does not affect Hijri dates (e.g., 1424هـ) or specific Arabic terms.\n * Example: \"ـThis is a textـ\" will be changed to \"This is a text\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with extreme underscores removed.\n */\nexport const cleanExtremeArabicUnderscores = (text: string) => {\n return text.replace(/(?<!\\d ?ه|اه)ـ(?=\\r?$)|^ـ(?!اهـ)/gm, '');\n};\n\n/**\n * Converts Urdu symbols to their Arabic equivalents.\n * Example: 'ھذا' will be changed to 'هذا', 'ی' to 'ي'.\n * @param {string} text - The input text containing Urdu symbols.\n * @returns {string} - The modified text with Urdu symbols converted to Arabic symbols.\n */\nexport const convertUrduSymbolsToArabic = (text: string) => {\n return text.replace(/ھ/g, 'ه').replace(/ی/g, 'ي');\n};\n\n/**\n * Calculates the proportion of Arabic characters in text relative to total non-whitespace, non-digit characters.\n * Digits (ASCII and Arabic-Indic variants) are excluded from both numerator and denominator.\n * @param text - The input text to analyze\n * @returns A decimal between 0-1 representing the Arabic character ratio (0 = no Arabic, 1 = all Arabic)\n */\nexport const getArabicScore = (text: string) => {\n if (!text) {\n return 0;\n }\n // Arabic letters (letters/ranges only)\n const arabicLettersPattern = /[\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/g;\n // ASCII digits + Arabic-Indic digits + Extended Arabic-Indic digits\n const allDigitPattern = /[0-9\\u0660-\\u0669\\u06F0-\\u06F9]/g;\n // Counted characters exclude whitespace and all listed digits\n const countedCharsPattern = /[^\\s0-9\\u0660-\\u0669\\u06F0-\\u06F9]/g;\n const cleaned = text.replace(allDigitPattern, '');\n const arabicMatches = cleaned.match(arabicLettersPattern) || [];\n const totalMatches = cleaned.match(countedCharsPattern) || [];\n return totalMatches.length === 0 ? 0 : arabicMatches.length / totalMatches.length;\n};\n\n/**\n * Finds the position of the last punctuation character in a string\n *\n * @param text - The text to search through\n * @returns The index of the last punctuation character, or -1 if none found\n *\n * @example\n * ```typescript\n * const text = \"Hello world! How are you?\";\n * const lastPuncIndex = findLastPunctuation(text);\n * // Result: 24 (position of the last '?')\n *\n * const noPuncText = \"Hello world\";\n * const notFound = findLastPunctuation(noPuncText);\n * // Result: -1 (no punctuation found)\n * ```\n */\nexport const findLastPunctuation = (text: string) => {\n for (let i = text.length - 1; i >= 0; i--) {\n if (PATTERN_ENDS_WITH_PUNCTUATION.test(text[i])) {\n return i;\n }\n }\n\n return -1;\n};\n\n/**\n * Fixes the trailing \"و\" (wow) in phrases such as \"عليكم و رحمة\" to \"عليكم ورحمة\".\n * This function attempts to correct phrases where \"و\" appears unnecessarily, particularly in greetings.\n * Example: 'السلام عليكم و رحمة' will be changed to 'السلام عليكم ورحمة'.\n * @param {string} text - The input text containing the \"و\" character.\n * @returns {string} - The modified text with unnecessary trailing \"و\" characters corrected.\n */\nexport const fixTrailingWow = (text: string) => {\n return text.replace(/ و /g, ' و');\n};\n\n/**\n * Inserts a space between Arabic text and numbers.\n * Example: 'الآية37' will be changed to 'الآية 37'.\n * @param {string} text - The input text containing Arabic text followed by numbers.\n * @returns {string} - The modified text with spaces inserted between Arabic text and numbers.\n */\nexport const addSpaceBetweenArabicTextAndNumbers = (text: string) => {\n return text.replace(/([\\u0600-\\u06FF]+)(\\d+)/g, '$1 $2');\n};\n\n/**\n * Removes single-digit numbers surrounded by Arabic text. Also removes dashes (-) not followed by a number.\n * For example, removes '3' from 'وهب 3 وقال' but does not remove '121' from 'لوحه 121 الجرح'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with non-index numbers and dashes removed.\n */\nexport const removeNonIndexSignatures = (text: string) => {\n return text\n .replace(/(?<![0-9] ?)-|(?<=[\\u0600-\\u06FF])\\s?\\d\\s?(?=[\\u0600-\\u06FF])/g, ' ')\n .replace(/(?<=[\\u0600-\\u06FF]\\s)(\\d+\\s)+\\d+(?=(\\s[\\u0600-\\u06FF]|$))/g, ' ');\n};\n\n/**\n * Removes characters enclosed in square brackets [] or parentheses () if they are Arabic letters or Arabic-Indic numerals.\n * Example: '[س]' or '(س)' will be removed.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with singular codes removed.\n */\nexport const removeSingularCodes = (text: string) => {\n return text.replace(/[[({][\\u0621-\\u064A\\u0660-\\u0669][\\])}]/g, '');\n};\n\n/**\n * Removes solitary Arabic letters unless they are the 'ha' letter, which is used in Hijri years.\n * Example: \"ب ا الكلمات ت\" will be changed to \"ا الكلمات\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with solitary Arabic letters removed.\n */\nexport const removeSolitaryArabicLetters = (text: string) => {\n return text.replace(/(^| )[\\u0621-\\u064A]( |$)/g, ' ');\n};\n\n/**\n * Replaces English punctuation (question mark and semicolon) with their Arabic equivalents.\n * Example: '?' will be replaced with '؟', and ';' with '؛'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with English punctuation replaced by Arabic punctuation.\n */\nexport const replaceEnglishPunctuationWithArabic = (text: string) => {\n return text\n .replace(/\\?|؟\\./g, '؟')\n .replace(/(;|؛)\\s*(\\1\\s*)*/g, '؛')\n .replace(/,|-،/g, '،');\n};\n","/** Character class for Arabic diacritics (tashkīl/harakāt). */\nconst DIACRITICS_CLASS = '[\\\\u0610-\\\\u061A\\\\u064B-\\\\u065F\\\\u0670\\\\u06D6-\\\\u06ED]';\n/** Tatweel (kashīda) class. */\nconst TATWEEL_CLASS = '\\\\u0640';\n\n/**\n * Escape a string so it can be safely embedded into a RegExp source.\n *\n * @param s Any string\n * @returns Escaped string\n */\nexport const escapeRegex = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** Optional equivalence toggles for {@link makeDiacriticInsensitiveRegex}. */\ntype EquivOptions = {\n /** Treat ا/أ/إ/آ as equivalent. @default true */\n alif?: boolean;\n /** Treat ة/ه as equivalent. @default true */\n taMarbutahHa?: boolean;\n /** Treat ى/ي as equivalent. @default true */\n alifMaqsurahYa?: boolean;\n};\n\n/** Options for {@link makeDiacriticInsensitiveRegex}. */\nexport type MakeRegexOptions = {\n /**\n * Character equivalences to allow.\n * @default { alif: true, taMarbutahHa: true, alifMaqsurahYa: true }\n */\n equivalences?: EquivOptions;\n\n /**\n * Allow tatweel between letters (tolerate decorative elongation).\n * @default true\n */\n allowTatweel?: boolean;\n\n /**\n * Ignore diacritics by inserting a `DIACRITICS_CLASS*` after each letter.\n * @default true\n */\n ignoreDiacritics?: boolean;\n\n /**\n * Treat any whitespace in the needle as `\\s+` for flexible matching.\n * @default true\n */\n flexWhitespace?: boolean;\n\n /**\n * RegExp flags to use.\n * @default 'u'\n */\n flags?: string;\n};\n\n/**\n * Build a **diacritic-insensitive**, **tatweel-tolerant** RegExp for Arabic text matching.\n *\n * Features:\n * - Optional character equivalences: ا~أ~إ~آ, ة~ه, ى~ي.\n * - Optional tolerance for tatweel between characters.\n * - Optional diacritic-insensitivity (by inserting a diacritics class after each char).\n * - Optional flexible whitespace (needle whitespace becomes `\\s+`).\n *\n * @param needle The Arabic text to match\n * @param opts See {@link MakeRegexOptions}\n * @returns A `RegExp` matching the needle with the desired tolerances\n *\n * @example\n * const rx = makeDiacriticInsensitiveRegex('أنا إلى الآفاق');\n * rx.test('انا الي الافاق'); // true\n * rx.test('اَنا إلى الآفاق'); // true\n */\nexport const makeDiacriticInsensitiveRegex = (needle: string, opts: MakeRegexOptions = {}): RegExp => {\n const {\n equivalences = { alif: true, taMarbutahHa: true, alifMaqsurahYa: true },\n allowTatweel = true,\n ignoreDiacritics = true,\n flexWhitespace = true,\n flags = 'u',\n } = opts;\n\n // Safety guard against extremely large inputs causing excessive pattern sizes\n if (needle.length > 5000) {\n throw new Error('makeDiacriticInsensitiveRegex: needle too long');\n }\n\n const charClass = (ch: string): string => {\n switch (ch) {\n case 'ا':\n case 'أ':\n case 'إ':\n case 'آ':\n return equivalences.alif ? '[اأإآ]' : 'ا';\n case 'ة':\n case 'ه':\n return equivalences.taMarbutahHa ? '[هة]' : escapeRegex(ch);\n case 'ى':\n case 'ي':\n return equivalences.alifMaqsurahYa ? '[ىي]' : escapeRegex(ch);\n default:\n return escapeRegex(ch);\n }\n };\n\n const after = `${ignoreDiacritics ? `${DIACRITICS_CLASS}*` : ''}${allowTatweel ? `${TATWEEL_CLASS}*` : ''}`;\n\n let pattern = '';\n for (const ch of Array.from(needle)) {\n if (/\\s/.test(ch)) {\n pattern += flexWhitespace ? '\\\\s+' : '\\\\s*';\n } else {\n pattern += `${charClass(ch)}${after}`;\n }\n }\n\n return new RegExp(pattern, flags);\n};\n\n/**\n * Remove simple HTML/XML-like tags from a string.\n *\n * This is intentionally lightweight and does not attempt to parse HTML; it simply drops\n * substrings that look like `<...>`.\n *\n * @param content Input string\n * @returns String with tags removed\n */\nexport const removeAllTags = (content: string) => content.replace(/<[^>]*>/g, '');\n","/**\n * Adds line breaks after punctuation marks such as periods, exclamation points, and question marks.\n * Example: 'Text.' becomes 'Text.\\n'.\n *\n * Note: For the full preformatting pipeline in one pass (significantly faster and more memory-friendly\n * on very large inputs), use `preformatArabicText` from `src/preformat.ts`.\n * @param {string} text - The input text containing punctuation.\n * @returns {string} - The modified text with line breaks added after punctuation.\n */\nexport const insertLineBreaksAfterPunctuation = (text: string) => {\n // Define the punctuation marks that should trigger a new line\n const punctuation = /([.?!؟])/g;\n\n // Replace occurrences of punctuation marks followed by a space with the punctuation mark, a newline, and the space\n const formattedText = text.replace(punctuation, '$1\\n').replace(/\\n\\s+/g, '\\n').trim();\n\n return formattedText;\n};\n\n/**\n * Adds spaces before and after punctuation, except for certain cases like quoted text or ayah references.\n * Example: 'Text,word' becomes 'Text, word'.\n * @param {string} text - The input text containing punctuation.\n * @returns {string} - The modified text with spaces added before and after punctuation.\n */\nexport const addSpaceBeforeAndAfterPunctuation = (text: string) => {\n return text\n .replace(/( ?)([.!?,،؟;؛])((?![ '”“)\"\\]\\n])|(?=\\s{2,}))/g, '$1$2 ')\n .replace(/\\s([.!?,،؟;؛])\\s*([ '”“)\"\\]\\n])/g, '$1$2')\n .replace(/([^\\s\\w\\d'”“)\"\\]]+)\\s+([.!?,،؟;؛])|([.!?,،؟;؛])\\s+$/g, '$1$2$3')\n .replace(/(?<=\\D)( ?: ?)(?!(\\d+:)|(:\\d+))|(?<=\\d) ?: ?(?=\\D)|(?<=\\D) ?: ?(?=\\d)/g, ': ');\n};\n\n/**\n * Turns regular double quotes surrounding a body of text into smart quotes.\n * Also fixes incorrect starting quotes by ensuring the string starts with an opening quote if needed.\n * Example: 'The \"quick brown\" fox' becomes 'The “quick brown” fox'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with smart quotes applied.\n */\nexport const applySmartQuotes = (text: string) => {\n return text\n .replace(/[“”]/g, '\"')\n .replace(/\"([^\"]*)\"/g, '“$1”')\n .replace(/^”/g, '“');\n};\n\n/**\n * Replaces literal new line characters (\\n) and carriage returns (\\r) with actual line breaks.\n * Example: 'A\\\\nB' becomes 'A\\nB'.\n * @param {string} text - The input text containing literal new lines.\n * @returns {string} - The modified text with actual line breaks.\n */\nexport const cleanLiteralNewLines = (text: string) => {\n return text.replace(/\\\\n|\\r/g, '\\n');\n};\n\n/**\n * Removes trailing spaces from each line in a multiline string.\n * Example: \" This is a line \\nAnother line \" becomes \"This is a line\\nAnother line\".\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with trailing spaces removed.\n */\nexport const cleanMultilines = (text: string) => {\n return text.replace(/^ +| +$/gm, '');\n};\n\n/**\n * Detects if a word is by itself in a line.\n * @param text The text to check.\n * @returns true if there exists a word in any of the lines in the text that is by itself.\n */\nexport const hasWordInSingleLine = (text: string): boolean => {\n return /^\\s*\\S+\\s*$/gm.test(text);\n};\n\n/**\n * Checks if the input string consists of only punctuation characters.\n * @param {string} text - The input text to check.\n * @returns {boolean} - Returns true if the string contains only punctuation, false otherwise.\n */\nexport const isOnlyPunctuation = (text: string): boolean => {\n const regex = /^[\\u0020-\\u002f\\u003a-\\u0040\\u005b-\\u0060\\u007b-\\u007e0-9٠-٩]+$/;\n return regex.test(text);\n};\n\n/**\n * Cleans unnecessary spaces before punctuation marks such as periods, commas, and question marks.\n * Example: 'This is a sentence , with extra space .' becomes 'This is a sentence, with extra space.'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with cleaned spaces before punctuation.\n */\nexport const cleanSpacesBeforePeriod = (text: string) => {\n return text.replace(/\\s+([.؟!,،؛:?])/g, '$1');\n};\n\n/**\n * Condenses multiple asterisks (*) into a single one.\n * Example: '***' becomes '*'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed asterisks.\n */\nexport const condenseAsterisks = (text: string) => {\n return text.replace(/(\\*\\s*)+/g, '*');\n};\n\n/**\n * Replaces occurrences of colons surrounded by periods (e.g., '.:.' or ':') with a single colon.\n * Example: 'This.:. is a test' becomes 'This: is a test'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed colons.\n */\nexport const condenseColons = (text: string) => {\n return text.replace(/[.-]?:[.-]?/g, ':');\n};\n\n/**\n * Condenses two or more dashes (--) into a single dash (-).\n * Example: 'This is some ---- text' becomes 'This is some - text'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed dashes.\n */\nexport const condenseDashes = (text: string) => {\n return text.replace(/-{2,}/g, '-');\n};\n\n/**\n * Replaces sequences of two or more periods (e.g., '...') with an ellipsis character (…).\n * Example: 'This is a test...' becomes 'This is a test…'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with ellipses condensed.\n */\nexport const condenseEllipsis = (text: string) => {\n return text.replace(/\\.{2,}/g, '…');\n};\n\n/**\n * Reduces multiple consecutive line breaks (3 or more) to exactly 2 line breaks.\n * Example: 'This is line 1\\n\\n\\n\\nThis is line 2' becomes 'This is line 1\\n\\nThis is line 2'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed line breaks.\n */\nexport const reduceMultilineBreaksToDouble = (text: string) => {\n return text.replace(/(\\n\\s*){3,}/g, '\\n\\n');\n};\n\n/**\n * Reduces multiple consecutive line breaks (2 or more) to exactly 1 line break.\n * Example: 'This is line 1\\n\\nThis is line 2' becomes 'This is line 1\\nThis is line 2'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed line breaks.\n */\nexport const reduceMultilineBreaksToSingle = (text: string) => {\n return text.replace(/(\\n\\s*){2,}/g, '\\n');\n};\n\n/**\n * Condenses multiple periods separated by spaces (e.g., '. . .') into a single period.\n * Example: 'This . . . is a test' becomes 'This. is a test'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed periods.\n */\nexport const condensePeriods = (text: string) => {\n return text.replace(/\\. +\\./g, '.');\n};\n\n/**\n * Condenses multiple underscores (__) or Arabic Tatweel characters (ـــــ) into a single underscore or Tatweel.\n * Example: 'This is ـــ some text __' becomes 'This is ـ some text _'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed underscores.\n */\nexport const condenseUnderscores = (text: string) => {\n return text.replace(/ـ{2,}/g, 'ـ').replace(/_+/g, '_');\n};\n\n/**\n * Replaces double parentheses or brackets with single ones.\n * Example: '((text))' becomes '(text)'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed brackets.\n */\nexport const doubleToSingleBrackets = (text: string) => {\n return text.replace(/(\\(|\\)){2,}|(\\[|\\]){2,}/g, '$1$2');\n};\n\n/**\n * Ensures at most 1 space exists before any word before brackets.\n * Adds a space if there isn't one, or reduces multiple spaces to one.\n * @param {string} text - The input text to modify\n * @returns {string} - The modified text with proper spacing before brackets\n */\nexport const ensureSpaceBeforeBrackets = (text: string) => {\n return text.replace(/(\\S) *(\\([^)]*\\))/g, '$1 $2');\n};\n\n/**\n * Ensures at most 1 space exists before any word before Arabic quotation marks.\n * Adds a space if there isn't one, or reduces multiple spaces to one.\n * @param {string} text - The input text to modify\n * @returns {string} - The modified text with proper spacing before Arabic quotes\n */\nexport const ensureSpaceBeforeQuotes = (text: string) => {\n return text.replace(/(\\S) *(«[^»]*»)/g, '$1 $2');\n};\n\n/**\n * Fixes common bracket and quotation mark typos in text\n * Corrects malformed patterns like \"(«\", \"»)\", and misplaced digits in brackets\n * @param text - Input text that may contain bracket typos\n * @returns Text with corrected bracket and quotation mark combinations\n */\nexport const fixBracketTypos = (text: string) => {\n return (\n text\n .replace(/\\(«|\\( \\(/g, '«')\n .replace(/»\\)|\\) \\)/g, '»')\n // Fix \")digit)\" pattern to \"(digit)\"\n .replace(/\\)([0-9\\u0660-\\u0669]+)\\)/g, '($1)')\n // Fix \")digit(\" pattern to \"(digit)\"\n .replace(/\\)([0-9\\u0660-\\u0669]+)\\(/g, '($1)')\n );\n};\n\n/**\n * Fixes mismatched curly braces by converting incorrect bracket/brace combinations\n * to proper curly braces { }\n * @param text - Input text that may contain mismatched curly braces\n * @returns Text with corrected curly brace pairs\n */\nexport const fixCurlyBraces = (text: string) => {\n // Process each mismatch type separately to avoid interference\n let result = text;\n\n // Fix ( content } to { content }\n result = result.replace(/\\(([^(){}]+)\\}/g, '{$1}');\n\n // Fix { content ) to { content }\n return result.replace(/\\{([^(){}]+)\\)/g, '{$1}');\n};\n\n/**\n * Fixes mismatched quotation marks in Arabic text by converting various\n * incorrect bracket/quote combinations to proper Arabic quotation marks (« »)\n * @param text - Input text that may contain mismatched quotation marks\n * @returns Text with corrected Arabic quotation marks\n */\nexport const fixMismatchedQuotationMarks = (text: string) => {\n return (\n text\n // Matches mismatched quotation marks: « followed by content and closed with )\n .replace(/«([^»)]+)\\)/g, '«$1»')\n // Fix reverse mismatched ( content » to « content »\n .replace(/\\(([^()]+)»/g, '«$1»')\n // Matches any unclosed « quotation marks at end of content\n .replace(/«([^»]+)(?=\\s*$|$)/g, '«$1»')\n );\n};\n\n/**\n * Formats a multiline string by joining sentences and maintaining footnotes on their own lines.\n * Footnotes are identified by Arabic and English numerals.\n * Example: 'Sentence one.\\n(1) A footnote.\\nSentence two.' remains the same, while regular sentences are joined.\n * @param {string} input - The input text containing sentences and footnotes.\n * @returns {string} - The formatted text.\n */\nexport const formatStringBySentence = (input: string) => {\n const footnoteRegex = /^\\((?:\\d+|۱|۲|۳|۴|۵|۶|۷|۸|۹)\\)\\s/;\n const sentences: string[] = [];\n const lines = input.split('\\n');\n let currentSentence = '';\n\n lines.forEach((line) => {\n const trimmedLine = line.trim();\n const isFootnote = footnoteRegex.test(trimmedLine);\n const isNumber = /^\\(\\d+\\/\\d+\\)/.test(trimmedLine);\n\n if (isFootnote && !isNumber) {\n if (currentSentence) {\n sentences.push(currentSentence.trim());\n currentSentence = '';\n }\n sentences.push(trimmedLine);\n } else {\n currentSentence += `${trimmedLine} `;\n const lastChar = currentSentence.trim().slice(-1);\n if (/[.!؟]/.test(lastChar)) {\n sentences.push(currentSentence.trim());\n currentSentence = '';\n }\n }\n });\n\n // Add any remaining text to the output\n if (currentSentence) {\n sentences.push(currentSentence.trim());\n }\n\n return sentences.join('\\n');\n};\n\n/**\n * Detects if text is entirely in uppercase letters\n * @param text - The text to check\n * @returns true if all alphabetic characters are uppercase, false otherwise\n */\nexport const isAllUppercase = (text: string) => {\n // Remove non-letter characters (including numbers, punctuation, spaces)\n // \\p{L} matches any Unicode letter character\n const lettersOnly = text.replace(/[^\\p{L}]/gu, '');\n\n // If there are no letter characters, return false\n if (lettersOnly.length === 0) {\n return false;\n }\n\n return lettersOnly === lettersOnly.toUpperCase();\n};\n\n/**\n * Removes unnecessary spaces around slashes in references.\n * Example: '127 / 11' becomes '127/11'.\n * @param {string} text - The input text containing references.\n * @returns {string} - The modified text with spaces removed around slashes.\n */\nexport const normalizeSlashInReferences = (text: string) => {\n return text.replace(/(\\d+)\\s?\\/\\s?(\\d+)/g, '$1/$2');\n};\n\n/**\n * Reduces multiple spaces or tabs to a single space.\n * Example: 'This is a text' becomes 'This is a text'.\n * @param {string} text - The input text containing extra spaces.\n * @returns {string} - The modified text with reduced spaces.\n */\nexport const normalizeSpaces = (text: string) => {\n return text.replace(/[ \\t]+/g, ' ');\n};\n\n/**\n * Removes redundant punctuation marks that follow Arabic question marks or exclamation marks.\n * This function cleans up text by removing periods (.) or Arabic commas (،) that immediately\n * follow Arabic question marks (؟) or exclamation marks (!), as they are considered redundant\n * in proper Arabic punctuation.\n *\n * @param text - The Arabic text to clean up\n * @returns The text with redundant punctuation removed\n *\n * @example\n * ```typescript\n * removeRedundantPunctuation('كيف حالك؟.') // Returns: 'كيف حالك؟'\n * removeRedundantPunctuation('ممتاز!،') // Returns: 'ممتاز!'\n * removeRedundantPunctuation('هذا جيد.') // Returns: 'هذا جيد.' (unchanged)\n * ```\n */\nexport const removeRedundantPunctuation = (text: string) => {\n return text.replace(/([؟!])[.،]/g, '$1');\n};\n\n/**\n * Removes spaces inside brackets, parentheses, or square brackets.\n * Example: '( a b )' becomes '(a b)'.\n * @param {string} text - The input text with spaces inside brackets.\n * @returns {string} - The modified text with spaces removed inside brackets.\n */\nexport const removeSpaceInsideBrackets = (text: string) => {\n return text.replace(/([[(])\\s*(.*?)\\s*([\\])])/g, '$1$2$3');\n};\n\n/**\n * Replaces double parentheses single a single arrow variation.\n * Example: '((text))' becomes '«text»'.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with condensed brackets.\n */\nexport const replaceDoubleBracketsWithArrows = (text: string) => {\n return text.replace(/\\(\\(\\s?/g, '«').replace(/\\s?\\)\\)/g, '»');\n};\n\n/**\n * Removes bold styling from text by normalizing the string and removing stylistic characters.\n * @param {string} text - The input text containing bold characters.\n * @returns {string} - The modified text with bold styling removed.\n */\nexport const stripBoldStyling = (text: string) => {\n // Normalize the string to NFKD form\n const normalizedString = text.normalize('NFKD');\n\n // Remove combining marks (diacritics) and stylistic characters from the string\n return normalizedString.replace(/[\\u0300-\\u036f]/g, '').trim();\n};\n\n/**\n * Removes italicized characters by replacing italic Unicode characters with their normal counterparts.\n * Example: '𝘼𝘽𝘾' becomes 'ABC'.\n * @param {string} text - The input text containing italicized characters.\n * @returns {string} - The modified text with italics removed.\n */\nexport const stripItalicsStyling = (text: string) => {\n const italicMap: Record<string, string> = {\n '\\uD835\\uDC4E': 'I',\n '\\uD835\\uDC68': 'g',\n '\\u{1D63C}': '!',\n '\\uD835\\uDC4F': 'J',\n '\\uD835\\uDC69': 'h',\n '\\u{1D63D}': '?',\n '\\uD835\\uDC50': 'K',\n '\\uD835\\uDC6A': 'i',\n '\\uD835\\uDC51': 'L',\n '\\uD835\\uDC6B': 'j',\n '\\u{1D63F}': ',',\n '\\uD835\\uDC52': 'M',\n '\\uD835\\uDC6C': 'k',\n '\\u{1D640}': '.',\n '\\uD835\\uDC53': 'N',\n '\\uD835\\uDC6D': 'l',\n '\\uD835\\uDC54': 'O',\n '\\uD835\\uDC6E': 'm',\n '\\uD835\\uDC6F': 'n',\n '\\uD835\\uDC56': 'Q',\n '\\uD835\\uDC70': 'o',\n '\\uD835\\uDC57': 'R',\n '\\uD835\\uDC71': 'p',\n '\\uD835\\uDC58': 'S',\n '\\uD835\\uDC72': 'q',\n '\\uD835\\uDC59': 'T',\n '\\uD835\\uDC73': 'r',\n '\\u{1D647}': '-',\n '\\uD835\\uDC5A': 'U',\n '\\uD835\\uDC74': 's',\n '\\uD835\\uDC5B': 'V',\n '\\uD835\\uDC75': 't',\n '\\uD835\\uDC5C': 'W',\n '\\uD835\\uDC76': 'u',\n '\\uD835\\uDC5D': 'X',\n '\\uD835\\uDC77': 'v',\n '\\uD835\\uDC5E': 'Y',\n '\\uD835\\uDC78': 'w',\n '\\uD835\\uDC5F': 'Z',\n '\\uD835\\uDC79': 'x',\n '\\uD835\\uDC46': 'A',\n '\\uD835\\uDC7A': 'y',\n '\\uD835\\uDC47': 'B',\n '\\uD835\\uDC7B': 'z',\n '\\uD835\\uDC62': 'a',\n '\\uD835\\uDC48': 'C',\n '\\uD835\\uDC63': 'b',\n '\\uD835\\uDC49': 'D',\n '\\uD835\\uDC64': 'c',\n '\\uD835\\uDC4A': 'E',\n '\\uD835\\uDC65': 'd',\n '\\uD835\\uDC4B': 'F',\n '\\uD835\\uDC66': 'e',\n '\\uD835\\uDC4C': 'G',\n '\\uD835\\uDC67': 'f',\n '\\uD835\\uDC4D': 'H',\n '\\uD835\\uDC55': 'P',\n };\n\n return text.replace(/[\\uD835\\uDC62-\\uD835\\uDC7B\\uD835\\uDC46-\\uD835\\uDC5F\\u{1D63C}-\\u{1D647}]/gu, (match) => {\n return italicMap[match] || match;\n });\n};\n\n/**\n * Removes all bold and italic styling from the input text.\n * @param {string} text - The input text to remove styling from.\n * @returns {string} - The modified text with all styling removed.\n */\nexport const stripStyling = (text: string) => {\n return stripItalicsStyling(stripBoldStyling(text));\n};\n\n/**\n * Converts a string to title case (first letter of each word capitalized)\n * @param str - The input string to convert\n * @returns String with each word's first letter capitalized\n */\nexport const toTitleCase = (str: string) => {\n return str\n .toLowerCase()\n .split(' ')\n .map((word) => {\n if (word.length === 0) {\n return word;\n }\n // Find the first Unicode letter in the chunk\n const match = word.match(/\\p{L}/u);\n if (!match || match.index === undefined) {\n return word;\n }\n const i = match.index;\n return word.slice(0, i) + word.charAt(i).toUpperCase() + word.slice(i + 1);\n })\n .join(' ');\n};\n\n/**\n * Removes unnecessary spaces inside quotes.\n * Example: '“ Text ”' becomes '“Text”'.\n * @param {string} text - The input text with spaces inside quotes.\n * @returns {string} - The modified text with spaces removed inside quotes.\n */\nexport const trimSpaceInsideQuotes = (text: string) => {\n return text.replace(/([“”\"]|«) *(.*?) *([“”\"]|»)/g, '$1$2$3');\n};\n","/**\n * Converts a string that resembles JSON but with numeric keys and single-quoted values\n * into valid JSON format. This function replaces numeric keys with quoted numeric keys\n * and ensures all values are double-quoted as required by JSON.\n *\n * @param {string} str - The input string that needs to be fixed into valid JSON.\n * @returns {string} - A valid JSON string.\n *\n * @example\n * const result = normalizeJsonSyntax(\"{10: 'abc', 20: 'def'}\");\n * console.log(result); // '{\"10\": \"abc\", \"20\": \"def\"}'\n */\nexport const normalizeJsonSyntax = (str: string) => {\n let input = str.replace(/(\\b\\d+\\b)(?=:)/g, '\"$1\"');\n input = input.replace(/:\\s*'([^']+)'/g, ': \"$1\"');\n input = input.replace(/:\\s*\"([^\"]+)\"/g, ': \"$1\"');\n\n return JSON.stringify(JSON.parse(input));\n};\n\n/**\n * Checks if a given string resembles a JSON object with numeric or quoted keys and values\n * that are single or double quoted. This is useful for detecting malformed JSON-like\n * structures that can be fixed by the `normalizeJsonSyntax` function.\n *\n * @param {string} str - The input string to check.\n * @returns {boolean} - Returns true if the string is JSON-like, false otherwise.\n *\n * @example\n * const result = isJsonStructureValid(\"{10: 'abc', 'key': 'value'}\");\n * console.log(result); // true\n */\nexport const isJsonStructureValid = (str: string) => {\n // Checks for a pattern with numeric keys or quoted keys and values in quotes\n const jsonLikePattern =\n /^{(\\s*(\\d+|'[^']*'|\"[^\"]*\")\\s*:\\s*('|\")[^'\"]*\\3\\s*,)*(?:\\s*(\\d+|'[^']*'|\"[^\"]*\")\\s*:\\s*('|\")[^'\"]*\\5\\s*)}$/;\n return jsonLikePattern.test(str.trim());\n};\n\n/**\n * Splits a string by spaces and quoted substrings.\n *\n * This function takes an input string and splits it into parts where substrings\n * enclosed in double quotes are treated as a single part. Other substrings\n * separated by spaces are split normally.\n *\n * @param {string} query - The input string to be split.\n * @returns {string[]} An array of strings, with quoted substrings kept intact.\n *\n * @example\n * const result = splitByQuotes('\"This is\" \"a part of the\" \"string and\"');\n * console.log(result); // [\"This is\", \"a part of the\", \"string and\"]\n */\nexport const splitByQuotes = (query: string): string[] => {\n const regex = /(?:[^\\s\"]+|\"(.*?)\")+/g;\n return (query.match(regex) || []).map((s: string) => (s.startsWith('\"') ? s.slice(1, -1) : s));\n};\n\n/**\n * Checks if all double quotes in a string are balanced (even count).\n * A string has balanced quotes if every opening quote has a corresponding closing quote.\n *\n * @param str - The string to check for balanced quotes\n * @returns True if quotes are balanced (even count), false otherwise\n *\n * @example\n * ```typescript\n * areQuotesBalanced('Hello \"world\"') // Returns: true\n * areQuotesBalanced('Hello \"world') // Returns: false\n * areQuotesBalanced('No quotes') // Returns: true\n * ```\n */\nconst areQuotesBalanced = (str: string) => {\n let quoteCount = 0;\n for (const char of str) {\n if (char === '\"') {\n quoteCount++;\n }\n }\n return quoteCount % 2 === 0;\n};\n\nconst brackets = { '(': ')', '[': ']', '{': '}' };\nconst openBrackets = new Set(['(', '[', '{']);\nconst closeBrackets = new Set([')', ']', '}']);\n\n/**\n * Checks if all brackets in a string are properly balanced and matched.\n * This function validates that every opening bracket has a corresponding closing bracket\n * in the correct order and of the matching type.\n *\n * Supported bracket types: parentheses (), square brackets [], curly braces {}\n *\n * @param str - The string to check for balanced brackets\n * @returns True if all brackets are properly balanced and matched, false otherwise\n *\n * @example\n * ```typescript\n * areBracketsBalanced('(hello [world])') // Returns: true\n * areBracketsBalanced('(hello [world)') // Returns: false (mismatched)\n * areBracketsBalanced('((hello))') // Returns: true\n * areBracketsBalanced('(hello') // Returns: false (unclosed)\n * ```\n */\n\nconst areBracketsBalanced = (str: string) => {\n const stack: string[] = [];\n\n for (const char of str) {\n if (openBrackets.has(char)) {\n stack.push(char);\n } else if (closeBrackets.has(char)) {\n const lastOpen = stack.pop();\n if (!lastOpen || brackets[lastOpen as keyof typeof brackets] !== char) {\n return false;\n }\n }\n }\n\n return stack.length === 0;\n};\n\n/**\n * Checks if both quotes and brackets are balanced in a string.\n * This function combines quote balance checking and bracket balance checking\n * to ensure the entire string has properly balanced punctuation.\n *\n * A string is considered balanced when:\n * - All double quotes have matching pairs (even count)\n * - All brackets (parentheses, square brackets, curly braces) are properly matched and nested\n *\n * @param str - The string to check for balanced quotes and brackets\n * @returns True if both quotes and brackets are balanced, false otherwise\n *\n * @example\n * ```typescript\n * isBalanced('He said \"Hello (world)!\"') // Returns: true\n * isBalanced('He said \"Hello (world!\"') // Returns: false (unbalanced quote)\n * isBalanced('He said \"Hello (world)\"') // Returns: false (unbalanced quote)\n * isBalanced('Hello (world) [test]') // Returns: true\n * ```\n */\nexport const isBalanced = (str: string) => {\n return areQuotesBalanced(str) && areBracketsBalanced(str);\n};\n\n/**\n * Parses page input string into array of page numbers, supporting ranges and lists\n * @param pageInput - Page specification string (e.g., \"1-5\" or \"1,3,5\")\n * @returns Array of page numbers\n * @throws Error when start page exceeds end page in range\n */\nexport const parsePageRanges = (pageInput: string): number[] => {\n if (pageInput.includes('-')) {\n const [start, end] = pageInput.split('-').map(Number);\n\n if (start > end) {\n throw new Error('Start page cannot be greater than end page');\n }\n\n return Array.from({ length: end - start + 1 }, (_, i) => start + i);\n } else {\n return pageInput.split(',').map(Number);\n }\n};\n","/**\n * Internal implementation for `preformatArabicText`.\n *\n * This file intentionally exports additional helpers for benchmarking/testing,\n * but it is NOT exported from `src/index.ts` (public API).\n */\n\n// ==============================================================================\n// CONSTANTS & LOOKUP TABLE\n// ==============================================================================\n\nconst F_SPACE = 1;\nconst F_NO_SPACE_BEFORE = 2; // Punctuation/Closing that consumes preceding spaces\nconst F_OPENING = 4; // Opening brackets/quotes\nconst F_CLOSING = 8; // Closing brackets/quotes\nconst F_DIGIT = 16;\nconst F_ARABIC = 32;\nconst F_SPECIAL = 64; // Needs specific handling (transform, condense, etc)\nconst F_PUNCT = 128; // Punctuation that might need spacing after\n\nconst CHAR_MAP = new Uint8Array(65536);\n\n/**\n * Mark a set of characters with the given bit flags in {@link CHAR_MAP}.\n *\n * @param chars A string of individual characters to mark\n * @param flags Bitmask to OR into the lookup table for each character\n */\nconst setFlags = (chars: string, flags: number) => {\n for (let i = 0; i < chars.length; i++) {\n CHAR_MAP[chars.charCodeAt(i)] |= flags;\n }\n};\n\n// Initialize Map\nsetFlags(' \\t', F_SPACE);\nsetFlags('\\n\\r', F_SPECIAL); // Newlines are special\n\n// Digits\nfor (let i = 48; i <= 57; i++) {\n CHAR_MAP[i] |= F_DIGIT; // 0-9\n}\n\n// Arabic Letters (Common ranges)\nfor (let i = 0x0600; i <= 0x06ff; i++) {\n CHAR_MAP[i] |= F_ARABIC;\n}\nfor (let i = 0x0750; i <= 0x077f; i++) {\n CHAR_MAP[i] |= F_ARABIC;\n}\n\n// Punctuation & Brackets\n// No Space Before: . ! ? , : ; and Arabic equivalents\nsetFlags('.!?,:;', F_NO_SPACE_BEFORE | F_PUNCT | F_SPECIAL);\nsetFlags('،؛؟', F_NO_SPACE_BEFORE | F_PUNCT | F_SPECIAL);\n\n// Opening: ( [ { \" ' « “\nsetFlags('([{\"\\'«“', F_OPENING | F_SPECIAL);\n\n// Closing: ) ] } \" ' » ”\nsetFlags(')]}\"\\'»”', F_CLOSING | F_NO_SPACE_BEFORE | F_SPECIAL);\n\n// Specific Specials\nsetFlags('-_*و/', F_SPECIAL);\n\n// Codes for fast comparison\nconst C_TAB = 9;\nconst C_SPACE = 32;\nconst C_NEWLINE = 10;\nconst C_CR = 13;\nconst C_DOT = 46;\nconst C_COMMA = 44;\nconst C_COLON = 58;\nconst C_SEMICOLON = 59;\nconst C_Q_MARK = 63;\nconst C_EXCLAM = 33;\nconst C_SLASH = 47;\nconst C_DASH = 45;\nconst C_UNDERSCORE = 95;\nconst C_ASTERISK = 42;\nconst C_L_PAREN = 40;\nconst C_R_PAREN = 41;\nconst C_L_GUILLEMET = 171; // «\nconst C_R_GUILLEMET = 187; // »\nconst C_DQUOTE = 34; // \"\nconst C_SQUOTE = 39; // '\nconst C_RDQUOTE = 8221; // ”\nconst C_WOW = 1608; // و\nconst C_AR_COMMA = 1548; // ،\nconst C_AR_SEMICOLON = 1563; // ؛\nconst C_AR_Q_MARK = 1567; // ؟\nconst C_TATWEEL = 1600; // ـ\nconst C_ELLIPSIS = 8230; // …\n\n/**\n * Check whether a code unit should be treated as trim whitespace for final output.\n *\n * @param code UTF-16 code unit\n * @returns True if the code unit is a trim-whitespace character\n */\nconst isTrimWhitespace = (code: number): boolean => {\n return code === C_SPACE || code === C_TAB || code === C_NEWLINE || code === C_CR;\n};\n\n/**\n * Growable UTF-16 output buffer used by the buffer-backed preformat implementation.\n *\n * Note: This builder stores UTF-16 code units; it assumes all emitted characters are within\n * the BMP (which is true for the transformations this pipeline performs).\n */\nclass Utf16Builder {\n private buffer: Uint16Array;\n public length: number;\n\n public constructor(initialCapacity: number) {\n this.buffer = new Uint16Array(Math.max(16, initialCapacity));\n this.length = 0;\n }\n\n /**\n * Return the last written UTF-16 code unit, or 0 if empty.\n */\n public last(): number {\n return this.length > 0 ? this.buffer[this.length - 1] : 0;\n }\n\n /**\n * Return the second-to-last written UTF-16 code unit, or 0 if not available.\n */\n public secondLast(): number {\n return this.length > 1 ? this.buffer[this.length - 2] : 0;\n }\n\n /**\n * Append a UTF-16 code unit to the buffer.\n */\n public push(code: number) {\n this.ensureCapacity(1);\n this.buffer[this.length] = code;\n this.length++;\n }\n\n /**\n * Remove the last UTF-16 code unit (no-op if empty).\n */\n public pop() {\n if (this.length > 0) {\n this.length--;\n }\n }\n\n private ensureCapacity(extra: number) {\n const needed = this.length + extra;\n if (needed <= this.buffer.length) {\n return;\n }\n let nextCap = this.buffer.length * 2;\n if (nextCap < needed) {\n nextCap = needed;\n }\n const next = new Uint16Array(nextCap);\n next.set(this.buffer.subarray(0, this.length));\n this.buffer = next;\n }\n\n /**\n * Convert the buffer to a string, trimming leading/trailing whitespace (space/tab/newline/CR).\n *\n * Uses chunked `String.fromCharCode` to avoid stack limits on large outputs.\n */\n public toStringTrimmed(): string {\n let start = 0;\n let end = this.length;\n\n while (start < end && isTrimWhitespace(this.buffer[start])) {\n start++;\n }\n while (end > start && isTrimWhitespace(this.buffer[end - 1])) {\n end--;\n }\n\n if (end <= start) {\n return '';\n }\n\n // Chunked conversion to avoid call stack limits.\n const CHUNK = 0x8000;\n let out = '';\n for (let i = start; i < end; i += CHUNK) {\n const slice = this.buffer.subarray(i, Math.min(end, i + CHUNK));\n out += String.fromCharCode(...slice);\n }\n return out;\n }\n}\n\n// ==============================================================================\n// IMPLEMENTATIONS\n// ==============================================================================\n\n/**\n * NOTE ON MAINTAINABILITY:\n *\n * `processStringConcat` and `processStringBuffer` must remain byte-for-byte identical in behavior.\n * Any bug fix or behavior change MUST be applied to both implementations, and should be validated\n * by running the correctness tests (including `src/preformat.memory.test.ts` for large inputs).\n */\n\n/**\n * Preformat using string concatenation (`res += ...`).\n *\n * This is typically fastest for common page-sized inputs under Bun/V8.\n *\n * @param text Input string\n * @returns Preformatted string\n */\nconst processStringConcat = (text: string): string => {\n if (!text) {\n return '';\n }\n\n let res = '';\n const len = text.length;\n let i = 0;\n\n let pendingSpaces = 0;\n let lastCode = 0;\n\n while (i < len) {\n let code = text.charCodeAt(i);\n const originalCode = code;\n const flags = CHAR_MAP[code];\n\n // ---------------------------------------------------------\n // 1. Handle Whitespace\n // ---------------------------------------------------------\n if (flags & F_SPACE) {\n pendingSpaces++;\n i++;\n continue;\n }\n\n // ---------------------------------------------------------\n // 2. Handle Special Characters (Newlines, Transforms)\n // ---------------------------------------------------------\n if (flags & F_SPECIAL) {\n // Newlines\n if (code === C_NEWLINE || code === C_CR) {\n pendingSpaces = 0;\n if (lastCode !== C_NEWLINE) {\n res += '\\n';\n lastCode = C_NEWLINE;\n }\n // Skip subsequent whitespace\n i++;\n while (i < len) {\n const next = text.charCodeAt(i);\n if (next === C_SPACE || next === C_TAB || next === C_NEWLINE || next === C_CR) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Transforms\n if (code === C_Q_MARK) {\n code = C_AR_Q_MARK;\n } else if (code === C_SEMICOLON) {\n code = C_AR_SEMICOLON;\n } else if (code === C_COMMA) {\n code = C_AR_COMMA;\n }\n\n // Condense Colons (.:. -> :)\n else if (code === C_COLON) {\n if (lastCode === C_DOT || lastCode === C_DASH) {\n res = res.slice(0, -1);\n lastCode = res.charCodeAt(res.length - 1) || 0;\n }\n const next = text.charCodeAt(i + 1);\n if (next === C_DOT || next === C_DASH) {\n i++;\n }\n }\n\n // Double Brackets\n else if (code === C_L_PAREN && text.charCodeAt(i + 1) === C_L_PAREN) {\n code = C_L_GUILLEMET; // « (\\u00AB)\n i++;\n } else if (code === C_R_PAREN && text.charCodeAt(i + 1) === C_R_PAREN) {\n code = C_R_GUILLEMET; // » (\\u00BB)\n i++;\n }\n\n // Ellipsis (...)\n else if (code === C_DOT) {\n if (lastCode === C_ELLIPSIS) {\n i++;\n continue;\n }\n if (lastCode === C_DOT) {\n res = res.slice(0, -1) + '…';\n lastCode = C_ELLIPSIS;\n i++;\n continue;\n }\n }\n\n // Trailing Wow\n else if (code === C_WOW && lastCode === C_SPACE) {\n res += 'و';\n lastCode = C_WOW;\n i++;\n while (i < len) {\n const next = text.charCodeAt(i);\n if (next === C_SPACE || next === C_TAB) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Repeats (Tatweel, Underscore, Dash, Asterisk)\n else if (\n (code === C_TATWEEL && lastCode === C_TATWEEL) ||\n (code === C_UNDERSCORE && lastCode === C_UNDERSCORE) ||\n (code === C_DASH && lastCode === C_DASH) ||\n (code === C_ASTERISK && lastCode === C_ASTERISK)\n ) {\n i++;\n continue;\n }\n\n // Redundant Punctuation\n if ((lastCode === C_AR_Q_MARK || lastCode === C_EXCLAM) && (code === C_DOT || code === C_AR_COMMA)) {\n i++;\n continue;\n }\n }\n\n // ---------------------------------------------------------\n // 3. Resolve Pending Spaces\n // ---------------------------------------------------------\n if (pendingSpaces > 0) {\n let shouldEmitSpace = true;\n const currentFlags = CHAR_MAP[code];\n\n if (currentFlags & F_NO_SPACE_BEFORE) {\n shouldEmitSpace = false;\n }\n\n if (CHAR_MAP[lastCode] & F_OPENING) {\n shouldEmitSpace = false;\n }\n\n // Slash logic\n if (code === C_SLASH) {\n // Peek next non-space\n let nextNonSpace = 0;\n for (let k = i + 1; k < len; k++) {\n const nc = text.charCodeAt(k);\n if (nc !== C_SPACE && nc !== C_TAB && nc !== C_NEWLINE && nc !== C_CR) {\n nextNonSpace = nc;\n break;\n }\n }\n\n if (CHAR_MAP[lastCode] & F_DIGIT && CHAR_MAP[nextNonSpace] & F_DIGIT) {\n shouldEmitSpace = false;\n }\n }\n\n // No space after slash in references\n if (lastCode === C_SLASH && currentFlags & F_DIGIT) {\n if (res.length >= 2 && CHAR_MAP[res.charCodeAt(res.length - 2)] & F_DIGIT) {\n shouldEmitSpace = false;\n }\n }\n\n if (shouldEmitSpace) {\n res += ' ';\n lastCode = C_SPACE;\n }\n pendingSpaces = 0;\n }\n\n // ---------------------------------------------------------\n // 4. Insert Missing Spaces\n // ---------------------------------------------------------\n\n const currentFlags = CHAR_MAP[code];\n\n // Arabic <-> Number\n if (currentFlags & F_DIGIT && CHAR_MAP[lastCode] & F_ARABIC) {\n res += ' ';\n lastCode = C_SPACE;\n }\n\n // Space before Opening\n if (\n currentFlags & F_OPENING &&\n lastCode !== C_SPACE &&\n lastCode !== C_NEWLINE &&\n !(CHAR_MAP[lastCode] & F_OPENING) &&\n lastCode !== 0\n ) {\n res += ' ';\n lastCode = C_SPACE;\n }\n\n // Space after Punctuation\n if (CHAR_MAP[lastCode] & F_PUNCT) {\n const isSpecial =\n currentFlags & (F_SPACE | F_CLOSING | F_OPENING) ||\n code === C_SPACE ||\n code === C_NEWLINE ||\n code === C_CR ||\n currentFlags & F_CLOSING ||\n code === C_DQUOTE ||\n code === C_SQUOTE ||\n code === C_R_GUILLEMET ||\n code === C_RDQUOTE ||\n code === lastCode ||\n ((lastCode === C_AR_Q_MARK || lastCode === C_EXCLAM) && (code === C_DOT || code === C_AR_COMMA));\n\n if (!isSpecial) {\n res += ' ';\n lastCode = C_SPACE;\n }\n }\n\n // ---------------------------------------------------------\n // 5. Emit\n // ---------------------------------------------------------\n if (code !== originalCode) {\n res += String.fromCharCode(code);\n } else {\n res += text[i];\n }\n lastCode = code;\n i++;\n }\n\n return res.trim();\n};\n\n/**\n * Preformat using a growable UTF-16 buffer to reduce intermediate allocations.\n *\n * This is primarily intended for experimentation on extremely large inputs where GC pressure\n * may dominate (e.g. 100MB+ strings). It must remain byte-for-byte identical to the concat path.\n *\n * @param text Input string\n * @returns Preformatted string\n */\nconst processStringBuffer = (text: string): string => {\n if (!text) {\n return '';\n }\n\n const len = text.length;\n let i = 0;\n\n // Heuristic: output is usually close to input size; allocate a bit more to reduce growth.\n const builder = new Utf16Builder(len + (len >> 3) + 64);\n\n let pendingSpaces = 0;\n let lastCode = 0;\n\n while (i < len) {\n let code = text.charCodeAt(i);\n const originalCode = code;\n const flags = CHAR_MAP[code];\n\n // ---------------------------------------------------------\n // 1. Handle Whitespace\n // ---------------------------------------------------------\n if (flags & F_SPACE) {\n pendingSpaces++;\n i++;\n continue;\n }\n\n // ---------------------------------------------------------\n // 2. Handle Special Characters (Newlines, Transforms)\n // ---------------------------------------------------------\n if (flags & F_SPECIAL) {\n // Newlines\n if (code === C_NEWLINE || code === C_CR) {\n pendingSpaces = 0;\n if (lastCode !== C_NEWLINE) {\n builder.push(C_NEWLINE);\n lastCode = C_NEWLINE;\n }\n // Skip subsequent whitespace\n i++;\n while (i < len) {\n const next = text.charCodeAt(i);\n if (next === C_SPACE || next === C_TAB || next === C_NEWLINE || next === C_CR) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Transforms\n if (code === C_Q_MARK) {\n code = C_AR_Q_MARK;\n } else if (code === C_SEMICOLON) {\n code = C_AR_SEMICOLON;\n } else if (code === C_COMMA) {\n code = C_AR_COMMA;\n }\n\n // Condense Colons (.:. -> :)\n else if (code === C_COLON) {\n if (lastCode === C_DOT || lastCode === C_DASH) {\n builder.pop();\n lastCode = builder.last();\n }\n const next = text.charCodeAt(i + 1);\n if (next === C_DOT || next === C_DASH) {\n i++;\n }\n }\n\n // Double Brackets\n else if (code === C_L_PAREN && text.charCodeAt(i + 1) === C_L_PAREN) {\n code = C_L_GUILLEMET; // « (\\u00AB)\n i++;\n } else if (code === C_R_PAREN && text.charCodeAt(i + 1) === C_R_PAREN) {\n code = C_R_GUILLEMET; // » (\\u00BB)\n i++;\n }\n\n // Ellipsis (...)\n else if (code === C_DOT) {\n if (lastCode === C_ELLIPSIS) {\n i++;\n continue;\n }\n if (lastCode === C_DOT) {\n builder.pop();\n builder.push(C_ELLIPSIS);\n lastCode = C_ELLIPSIS;\n i++;\n continue;\n }\n }\n\n // Trailing Wow\n else if (code === C_WOW && lastCode === C_SPACE) {\n builder.push(C_WOW);\n lastCode = C_WOW;\n i++;\n while (i < len) {\n const next = text.charCodeAt(i);\n if (next === C_SPACE || next === C_TAB) {\n i++;\n } else {\n break;\n }\n }\n continue;\n }\n\n // Repeats (Tatweel, Underscore, Dash, Asterisk)\n else if (\n (code === C_TATWEEL && lastCode === C_TATWEEL) ||\n (code === C_UNDERSCORE && lastCode === C_UNDERSCORE) ||\n (code === C_DASH && lastCode === C_DASH) ||\n (code === C_ASTERISK && lastCode === C_ASTERISK)\n ) {\n i++;\n continue;\n }\n\n // Redundant Punctuation\n if ((lastCode === C_AR_Q_MARK || lastCode === C_EXCLAM) && (code === C_DOT || code === C_AR_COMMA)) {\n i++;\n continue;\n }\n }\n\n // ---------------------------------------------------------\n // 3. Resolve Pending Spaces\n // ---------------------------------------------------------\n if (pendingSpaces > 0) {\n let shouldEmitSpace = true;\n const currentFlags = CHAR_MAP[code];\n\n if (currentFlags & F_NO_SPACE_BEFORE) {\n shouldEmitSpace = false;\n }\n\n if (CHAR_MAP[lastCode] & F_OPENING) {\n shouldEmitSpace = false;\n }\n\n // Slash logic\n if (code === C_SLASH) {\n // Peek next non-space\n let nextNonSpace = 0;\n for (let k = i + 1; k < len; k++) {\n const nc = text.charCodeAt(k);\n if (nc !== C_SPACE && nc !== C_TAB && nc !== C_NEWLINE && nc !== C_CR) {\n nextNonSpace = nc;\n break;\n }\n }\n\n if (CHAR_MAP[lastCode] & F_DIGIT && CHAR_MAP[nextNonSpace] & F_DIGIT) {\n shouldEmitSpace = false;\n }\n }\n\n // No space after slash in references\n if (lastCode === C_SLASH && currentFlags & F_DIGIT) {\n if (builder.length >= 2 && CHAR_MAP[builder.secondLast()] & F_DIGIT) {\n shouldEmitSpace = false;\n }\n }\n\n if (shouldEmitSpace) {\n builder.push(C_SPACE);\n lastCode = C_SPACE;\n }\n pendingSpaces = 0;\n }\n\n // ---------------------------------------------------------\n // 4. Insert Missing Spaces\n // ---------------------------------------------------------\n\n const currentFlags = CHAR_MAP[code];\n\n // Arabic <-> Number\n if (currentFlags & F_DIGIT && CHAR_MAP[lastCode] & F_ARABIC) {\n builder.push(C_SPACE);\n lastCode = C_SPACE;\n }\n\n // Space before Opening\n if (\n currentFlags & F_OPENING &&\n lastCode !== C_SPACE &&\n lastCode !== C_NEWLINE &&\n !(CHAR_MAP[lastCode] & F_OPENING) &&\n lastCode !== 0\n ) {\n builder.push(C_SPACE);\n lastCode = C_SPACE;\n }\n\n // Space after Punctuation\n if (CHAR_MAP[lastCode] & F_PUNCT) {\n const isSpecial =\n currentFlags & (F_SPACE | F_CLOSING | F_OPENING) ||\n code === C_SPACE ||\n code === C_NEWLINE ||\n code === C_CR ||\n currentFlags & F_CLOSING ||\n code === C_DQUOTE ||\n code === C_SQUOTE ||\n code === C_R_GUILLEMET ||\n code === C_RDQUOTE ||\n code === lastCode ||\n ((lastCode === C_AR_Q_MARK || lastCode === C_EXCLAM) && (code === C_DOT || code === C_AR_COMMA));\n\n if (!isSpecial) {\n builder.push(C_SPACE);\n lastCode = C_SPACE;\n }\n }\n\n // ---------------------------------------------------------\n // 5. Emit\n // ---------------------------------------------------------\n if (code !== originalCode) {\n builder.push(code);\n } else {\n builder.push(originalCode);\n }\n lastCode = code;\n i++;\n }\n\n return builder.toStringTrimmed();\n};\n\n/**\n * Internal (non-public) variant: baseline `+=` builder.\n */\nexport const preformatArabicTextConcat = (text: string): string => processStringConcat(text);\n\n/**\n * Internal (non-public) variant: UTF-16 buffer builder to reduce intermediate allocations.\n */\nexport const preformatArabicTextBuffer = (text: string): string => processStringBuffer(text);\n\n\n","/**\n * Hyperoptimized Arabic text preformatting entry point.\n *\n * The implementation lives in `src/preformat-core.ts` to keep the public surface\n * area small while allowing internal benchmarking (buffer vs concat builders).\n *\n * @module preformat\n */\n\nimport { preformatArabicTextBuffer, preformatArabicTextConcat } from './preformat-core';\n\n/**\n * Preformat a single string.\n *\n * This is the internal implementation used by the public {@link preformatArabicText} API.\n * The builder can be forced for experiments via `BITABOOM_PREFORMAT_BUILDER`.\n *\n * @param text Input string\n * @returns Preformatted string\n */\nconst preformatOne = (text: string): string => {\n // Allow forcing a builder for benchmarks/debugging without changing public API.\n const forced = process.env.BITABOOM_PREFORMAT_BUILDER;\n if (forced === 'concat') {\n return preformatArabicTextConcat(text);\n }\n if (forced === 'buffer') {\n return preformatArabicTextBuffer(text);\n }\n\n // Default: the concat builder is typically faster in Bun/V8 for common page-sized inputs.\n // For experiments on extremely large inputs, force `BITABOOM_PREFORMAT_BUILDER=buffer`.\n return preformatArabicTextConcat(text);\n};\n\n// ==============================================================================\n// PUBLIC API\n// ==============================================================================\n\ntype PreformatArabicText = {\n (text: string): string;\n (texts: string[]): string[];\n};\n\n/**\n * High-performance Arabic preformatting pipeline.\n *\n * Consolidates common formatting steps (spacing, punctuation normalization, reference formatting,\n * bracket/quote cleanup, ellipsis condensation, newline normalization) into a single-pass formatter.\n *\n * @param text Input string or an array of strings\n * @returns Preformatted string or array of strings (matching input shape)\n */\nexport const preformatArabicText: PreformatArabicText = (text: string | string[]): string | string[] => {\n if (Array.isArray(text)) {\n return text.map(preformatOne);\n }\n return preformatOne(text);\n};\n","import { escapeRegex } from './cleaning';\n\n/**\n * Removes various symbols, part references, and numerical markers from the text.\n * Example: '(1) (2/3)' becomes ''.\n * @param {string} text - The input text to apply the rule to.\n * @returns {string} - The modified text with symbols and part references removed.\n */\nexport const cleanSymbolsAndPartReferences = (text: string) => {\n return text.replace(\n / *\\(?:\\d+(?:\\/\\d+){0,2}\\)? *| *\\[\\d+(?:\\/\\d+)?\\] *| *«\\d+» *|\\d+\\/\\d+(?:\\/\\d+)?|[،§{}؍﴿﴾<>;_؟»«:!،؛[\\]…ـ¬.\\\\/*()\"]/g,\n ' ',\n );\n};\n\n/**\n * Removes trailing page numbers formatted as '-[46]-' from the text.\n * Example: 'This is some -[46]- text' becomes 'This is some text'.\n * @param {string} text - The input text with trailing page numbers.\n * @returns {string} - The modified text with page numbers removed.\n */\nexport const cleanTrailingPageNumbers = (text: string) => {\n return text.replace(/-\\[\\d+\\]-/g, '');\n};\n\n/**\n * Replaces consecutive line breaks and whitespace characters with a single space.\n * Example: 'a\\nb' becomes 'a b'.\n * @param {string} text - The input text containing line breaks or multiple spaces.\n * @returns {string} - The modified text with spaces.\n */\nexport const replaceLineBreaksWithSpaces = (text: string) => {\n return text.replace(/\\s+/g, ' ');\n};\n\n/**\n * Removes all numeric digits from the text.\n * Example: 'abc123' becomes 'abc'.\n * @param {string} text - The input text containing digits.\n * @returns {string} - The modified text with digits removed.\n */\nexport const stripAllDigits = (text: string) => {\n return text.replace(/[0-9]/g, '');\n};\n\n/**\n * Removes death year references like \"(d. 390H)\" and \"[d. 100h]\" from the text.\n * Example: 'Sufyān ibn ‘Uyaynah (d. 198h)' becomes 'Sufyān ibn ‘Uyaynah'.\n * @param {string} text - The input text containing death year references.\n * @returns {string} - The modified text with death years removed.\n */\nexport const removeDeathYear = (text: string) => {\n return text.replace(/\\[(d)\\.\\s*\\d{1,4}[hH]\\]\\s*|\\((d)\\.\\s*\\d{1,4}[hH]\\)\\s*/g, '');\n};\n\n/**\n * Removes numeric digits and dashes from the text.\n * Example: 'ABC 123-Xyz' becomes 'ABC Xyz'.\n * @param {string} text - The input text containing digits and dashes.\n * @returns {string} - The modified text with numbers and dashes removed.\n */\nexport const removeNumbersAndDashes = (text: string) => {\n return text.replace(/[\\d-]/g, '');\n};\n\n/**\n * Removes single digit references like (1), «2», [3] from the text.\n * Example: 'Ref (1), Ref «2», Ref [3]' becomes 'Ref , Ref , Ref '.\n * @param {string} text - The input text containing single digit references.\n * @returns {string} - The modified text with single digit references removed.\n */\nexport const removeSingleDigitReferences = (text: string) => {\n return text.replace(/\\(\\d{1}\\)|\\[\\d{1}\\]|«\\d»/g, '');\n};\n\n/**\n * Removes URLs from the text.\n * Example: 'Visit https://example.com' becomes 'Visit '.\n * @param {string} text - The input text containing URLs.\n * @returns {string} - The modified text with URLs removed.\n */\nexport const removeUrls = (text: string) => {\n return text.replace(\n /https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g,\n '',\n );\n};\n\n/**\n * Removes common Markdown formatting syntax from text\n * @param text - The input text containing Markdown formatting\n * @returns Text with Markdown formatting removed (bold, italics, headers, lists, backticks)\n */\nexport const removeMarkdownFormatting = (text: string) => {\n return (\n text\n // Remove bold first (**text**) - must come before italics\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n // Remove bold with underscores (__text__)\n .replace(/__([^_]+)__/g, '$1')\n // Remove italics (*text*)\n .replace(/\\*([^*]+)\\*/g, '$1')\n // Remove italics with underscores (_text_)\n .replace(/_([^_]+)_/g, '$1')\n // Remove strikethrough (~~text~~)\n .replace(/~~([^~]+)~~/g, '$1')\n // Remove blockquotes\n .replace(/^\\s*>\\s?/gm, '')\n // Remove images \n .replace(/!\\[[^\\]]*]\\([^)]*\\)/g, '')\n // Convert links [text](url) -> text\n .replace(/\\[([^\\]]+)]\\([^)]*\\)/g, '$1')\n // Remove headers (# ## ### etc.)\n .replace(/^#+\\s*/gm, '')\n // Remove unordered list markers (- * +)\n .replace(/^\\s*[-*+]\\s+/gm, '')\n // Remove ordered list markers (1. 2. etc.)\n .replace(/^\\s*\\d+\\.\\s+/gm, '')\n // Remove backticks\n .replace(/`/gm, '')\n );\n};\n\n/**\n * Truncates a string to a specified length, adding an ellipsis if truncated.\n *\n * @param val - The string to truncate\n * @param n - Maximum length of the string (default: 150)\n * @returns The truncated string with ellipsis if needed, otherwise the original string\n *\n * @example\n * ```javascript\n * truncate('The quick brown fox jumps over the lazy dog', 20);\n * // Output: 'The quick brown fox…'\n *\n * truncate('Short text', 50);\n * // Output: 'Short text'\n * ```\n */\nexport const truncate = (val: string, n = 150): string => (val.length > n ? `${val.substring(0, n - 1)}…` : val);\n\n/**\n * Truncates a string from the middle, preserving both the beginning and end portions.\n *\n * @param text - The string to truncate\n * @param maxLength - Maximum length of the resulting string (default: 50)\n * @param endLength - Number of characters to preserve at the end (default: 1/3 of maxLength, minimum 3)\n * @returns The truncated string with ellipsis in the middle if needed, otherwise the original string\n *\n * @example\n * ```javascript\n * truncateMiddle('The quick brown fox jumps right over the lazy dog', 20);\n * // Output: 'The quick bro…zy dog'\n *\n * truncateMiddle('The quick brown fox jumps right over the lazy dog', 25, 8);\n * // Output: 'The quick brown …lazy dog'\n *\n * truncateMiddle('Short text', 50);\n * // Output: 'Short text'\n * ```\n */\nexport const truncateMiddle = (text: string, maxLength: number = 50, endLength?: number) => {\n if (text.length <= maxLength) {\n return text;\n }\n\n // Default end length is roughly 1/3 of max length, minimum 3 characters\n const defaultEndLength = Math.max(3, Math.floor(maxLength / 3));\n const actualEndLength = endLength ?? defaultEndLength;\n\n // Reserve space for the ellipsis character (1 char)\n const availableLength = maxLength - 1;\n\n // Calculate start length (remaining space after end portion)\n const startLength = availableLength - actualEndLength;\n\n // Ensure we have at least some characters at the start\n if (startLength < 1) {\n // If we can't fit both start and end, just truncate normally\n return `${text.substring(0, maxLength - 1)}…`;\n }\n\n const startPortion = text.substring(0, startLength);\n const endPortion = text.substring(text.length - actualEndLength);\n\n return `${startPortion}…${endPortion}`;\n};\n\n/**\n * Unescapes backslash-escaped spaces and trims whitespace from both ends.\n * Commonly used to clean file paths that have been escaped when pasted into terminals.\n *\n * @param input - The string to unescape and clean\n * @returns The cleaned string with escaped spaces converted to regular spaces and trimmed\n *\n * @example\n * ```javascript\n * unescapeSpaces('My\\\\ Folder\\\\ Name');\n * // Output: 'My Folder Name'\n *\n * unescapeSpaces(' /path/to/My\\\\ Document.txt ');\n * // Output: '/path/to/My Document.txt'\n *\n * unescapeSpaces('regular text');\n * // Output: 'regular text'\n * ```\n */\nexport const unescapeSpaces = (input: string) => input.replace(/\\\\ /g, ' ').trim();\n\n/**\n * Arabic diacritics (Tashkeel/Harakat).\n */\nconst DIACRITICS_CLASS = '[\\u064B\\u064C\\u064D\\u064E\\u064F\\u0650\\u0651\\u0652]';\n\n/**\n * Groups of equivalent Arabic characters — any character in a group should match\n * any other character in the same group.\n */\nconst EQUIV_GROUPS: string[][] = [\n ['\\u0627', '\\u0622', '\\u0623', '\\u0625'], // ا, آ, أ, إ\n ['\\u0629', '\\u0647'], // ة <-> ه\n ['\\u0649', '\\u064A'], // ى <-> ي\n];\n\n/**\n * Return an escaped character class representing all equivalents for the given character.\n *\n * If the character belongs to one of the predefined equivalence groups (e.g. ا/آ/أ/إ),\n * the returned class will match any member of that group. Otherwise, the original\n * character is simply escaped for safe inclusion in a regular expression.\n *\n * @param ch - A single character to expand into its equivalence class\n * @returns A RegExp-safe string representing the character (and its equivalents when applicable)\n */\nconst getEquivClass = (ch: string): string => {\n for (const group of EQUIV_GROUPS) {\n if (group.includes(ch)) {\n // join the group's members into a character class\n return `[${group.map((c) => escapeRegex(c)).join('')}]`;\n }\n }\n // not in equivalence groups -> return escaped character\n return escapeRegex(ch);\n};\n\n/** Small safe normalization: NFC, remove ZWJ/ZWNJ, collapse spaces. */\nconst normalizeArabicLight = (str: string) => {\n return str\n .normalize('NFC')\n .replace(/[\\u200C\\u200D]/g, '') // remove ZWJ/ZWNJ\n .replace(/\\s+/g, ' ')\n .trim();\n};\n\n/**\n * Creates a diacritic-insensitive regex pattern for Arabic text matching.\n * Normalizes text, handles character equivalences (ا/آ/أ/إ, ة/ه, ى/ي),\n * and makes each character tolerant of Arabic diacritics (Tashkeel/Harakat)\n * @param text - Input Arabic text to make diacritic-insensitive\n * @returns Regex pattern string that matches the text with or without diacritics and character variants\n */\nexport const makeDiacriticInsensitive = (text: string) => {\n const diacriticsMatcher = `${DIACRITICS_CLASS}*`;\n const norm = normalizeArabicLight(text);\n // Use Array.from to iterate grapheme-safe over the string (works fine for Arabic letters)\n return Array.from(norm)\n .map((ch) => getEquivClass(ch) + diacriticsMatcher)\n .join('');\n};\n","import { normalizeSpaces } from './formatting';\n\n/**\n * Replaces common Arabic prefixes (like 'Al-', 'Ar-', 'Ash-', etc.) with 'al-' in the text.\n * Handles different variations of prefixes such as Ash- and Al- but not when the second word\n * does not start with 'S'.\n * Example: 'Ash-Shafiee' becomes 'al-Shafiee'.\n *\n * @param {string} text - The input text containing Arabic prefixes.\n * @returns {string} - The modified text with standardized 'al-' prefixes.\n */\nexport const normalizeArabicPrefixesToAl = (text: string) => {\n return text\n .replace(/(\\b|\\W)(Al |Al-|Ar-|As-|Adh-|Ad-|Ats-|Ath |Ath-|Az |Az-|az-|adh-|as-|ar-)/g, '$1al-')\n .replace(/(\\b|\\W)(Ash-S|ash-S)/g, '$1al-S')\n .replace(/al- (.+?)\\b/g, 'al-$1');\n};\n\n/**\n * Removes double occurrences of Arabic apostrophes such as ʿʿ or ʾʾ in the text.\n * Example: 'ʿulamāʾʾ' becomes 'ʿulamāʾ'.\n *\n * @param {string} text - The input text containing double apostrophes.\n * @returns {string} - The modified text with condensed apostrophes.\n */\nexport const normalizeDoubleApostrophes = (text: string) => {\n return text.replace(/ʿʿ/g, 'ʿ').replace(/ʾʾ/g, 'ʾ');\n};\n\n/**\n * Replaces common salutations such as \"sallahu alayhi wasallam\" with \"ﷺ\" in the text.\n * It also handles variations of the salutation phrase, including 'peace and blessings be upon him'.\n * Example: 'Then Muḥammad (sallahu alayhi wasallam)' becomes 'Then Muḥammad ﷺ'.\n *\n * @param {string} text - The input text containing salutations.\n * @returns {string} - The modified text with salutations replaced.\n */\nexport const replaceSalutationsWithSymbol = (text: string) => {\n return text\n .replace(\n /\\(peace be upon him\\)|(Messenger of (Allah|Allāh)|Messenger|Prophet|Mu[hḥ]ammad) *\\((s[^)]*m|peace[^)]*him|May[^)]*him|may[^)]*him)\\)*/gi,\n '$1 ﷺ',\n )\n .replace(/,\\s*ﷺ\\s*,/g, ' ﷺ');\n};\n\n/**\n * Normalizes the text by removing diacritics, apostrophes, and dashes.\n * Example: 'Al-Jadwal' becomes 'AlJadwal'.\n *\n * @param {string} input - The input text to normalize.\n * @returns {string} - The normalized text.\n */\nexport const normalize = (input: string) => {\n return input\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/`|ʾ|ʿ|-/g, '');\n};\n\n/**\n * Strips common Arabic prefixes like 'al-', 'bi-', 'fī', 'wa-', etc. from the beginning of words.\n * Example: 'al-Bukhari' becomes 'Bukhari'.\n *\n * @param {string} text - The input text containing Arabic prefixes.\n * @returns {string} - The modified text with prefixes stripped.\n */\nexport const removeArabicPrefixes = (text: string) => {\n return normalizeSpaces(text.replace(/(\\bal-|\\bli-|\\bbi-|\\bfī|\\bwa[-\\s]+|\\bl-|\\bliʿl|\\Bʿalá|\\Bʿan|\\bb\\.)/gi, ''));\n};\n\n/**\n * Simplifies English transliterations by removing diacritics, apostrophes, and common prefixes.\n * Example: 'Al-Jadwal' becomes 'Jadwal', and 'āḍġḥīṣṭū' becomes 'adghistu'.\n *\n * @param {string} text - The input text to simplify.\n * @returns {string} - The simplified text.\n */\nexport const normalizeTransliteratedEnglish = (text: string) => normalize(removeArabicPrefixes(text));\n\n/**\n * Extracts the initials from the input string, typically used for names or titles.\n * Example: 'Nayl al-Awtar' becomes 'NA'.\n *\n * @param {string} text - The input text to extract initials from.\n * @returns {string} - The extracted initials.\n */\nexport const extractInitials = (fullName: string) => {\n const initials = normalizeTransliteratedEnglish(fullName)\n .trim()\n .split(/[ -]/)\n .slice(0, 2)\n .map((word) => {\n return word.charAt(0).toUpperCase();\n })\n .join('');\n return initials;\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitaboom",
|
|
3
3
|
"description": "Use string utils library to format Arabic and English translations.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": "Ragaeeb Haq",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"url": "https://github.com/ragaeeb/bitaboom/issues"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"bun": ">=1.3.
|
|
22
|
-
"node": ">=
|
|
21
|
+
"bun": ">=1.3.4",
|
|
22
|
+
"node": ">=24.0.0"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist/**"
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"arabic"
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@biomejs/biome": "^2.3.
|
|
39
|
-
"@types/bun": "^1.3.
|
|
40
|
-
"@types/node": "^
|
|
38
|
+
"@biomejs/biome": "^2.3.9",
|
|
39
|
+
"@types/bun": "^1.3.4",
|
|
40
|
+
"@types/node": "^25.0.3",
|
|
41
41
|
"semantic-release": "^25.0.2",
|
|
42
|
-
"tsdown": "^0.
|
|
42
|
+
"tsdown": "^0.18.0",
|
|
43
43
|
"typescript": "^5.9.3"
|
|
44
44
|
}
|
|
45
45
|
}
|