punctilio 3.0.10 → 3.1.1
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 +12 -8
- package/dist/constants.d.ts +24 -10
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +28 -13
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +2 -3
- package/dist/dashes.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +2 -1
- package/dist/nbsp.js.map +1 -1
- package/dist/quotes.d.ts +1 -1
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +74 -8
- package/dist/quotes.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -6
- package/dist/utils.js.map +1 -1
- package/package.json +5 -1
- package/src/constants.ts +28 -13
- package/src/dashes.ts +2 -3
- package/src/index.ts +17 -8
- package/src/nbsp.ts +2 -1
- package/src/quotes.ts +79 -8
- package/src/tests/index.test.ts +47 -4
- package/src/tests/quotes.test.ts +37 -0
- package/src/tests/utils.test.ts +2 -2
- package/src/utils.ts +9 -5
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Pretty good at making your text pretty. The most feature-complete and reliable English typography package. `punctilio` transforms plain ASCII into typographically correct Unicode, even across HTML element boundaries.
|
|
8
8
|
|
|
9
|
-
**Smart quotes** · **Em/en dashes** · **Ellipses** · **Math symbols** · **Legal symbols** · **Arrows** · **Primes** · **Fractions** · **Superscripts** · **Ligatures** · **Non-breaking spaces** · **HTML-aware** · **Bri’ish localisation support**
|
|
9
|
+
**Smart quotes** · **Em/en dashes** · **Ellipses** · **Math symbols** · **Legal symbols** · **Arrows** · **Primes** · **Fractions** · **Superscripts** · **Ligatures** · **Non-breaking spaces** · **HTML-aware** · **Bri’ish, German, and French localisation support**
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
import { transform } from 'punctilio'
|
|
@@ -39,10 +39,10 @@ My [`benchmark.mjs`](https://github.com/alexander-turner/punctilio/blob/main/ben
|
|
|
39
39
|
|
|
40
40
|
| Package | Passed (of 157) |
|
|
41
41
|
|--------:|:----------------|
|
|
42
|
-
| `punctilio` |
|
|
43
|
-
| `tipograph` |
|
|
42
|
+
| `punctilio` | 155 (99%) |
|
|
43
|
+
| `tipograph` | 91 (58%) |
|
|
44
44
|
| `typograf` | 74 (47%) |
|
|
45
|
-
| `smartquotes` | 72 (
|
|
45
|
+
| `smartquotes` | 72 (46%) |
|
|
46
46
|
| `smartypants` | 68 (43%) |
|
|
47
47
|
| `retext-smartypants` | 65 (41%) |
|
|
48
48
|
|
|
@@ -64,7 +64,7 @@ My [`benchmark.mjs`](https://github.com/alexander-turner/punctilio/blob/main/ben
|
|
|
64
64
|
| Superscripts | <span class="no-formatting">2nd → 2ⁿᵈ</span> | ✓ | ✗ | ✗ | ✗ | ✗ |
|
|
65
65
|
| English localization | <span class="no-formatting">American / British</span> | ✓ | ✗ | ✗ | ✗ | ✗ |
|
|
66
66
|
| Ligatures | <span class="no-formatting">?? → ⁇</span> | ✓ | ✗ | ✓ | ✗ | ✗ |
|
|
67
|
-
| Non-English quotes | <span class
|
|
67
|
+
| Non-English quotes | <span class=”no-formatting”>„Hallo”</span> | ✓ | ✗ | ✓ | ✗ | ◐ |
|
|
68
68
|
| Non-breaking spaces | <span class="no-formatting">Chapter 1</span> | ✓ | ✗ | ✗ | ✗ | ✓ |
|
|
69
69
|
|
|
70
70
|
### Known limitations of `punctilio`
|
|
@@ -72,11 +72,10 @@ My [`benchmark.mjs`](https://github.com/alexander-turner/punctilio/blob/main/ben
|
|
|
72
72
|
| Pattern | Behavior | Notes |
|
|
73
73
|
|:--------|:---------|:------|
|
|
74
74
|
| `'99 but 5' clearance` | `5'` not converted to `5′` | Leading apostrophe is indistinguishable from an opening quote without semantic understanding |
|
|
75
|
-
| `«Bonjour»` | Not spaced to `« Bonjour »` | French localization not supported |
|
|
76
75
|
|
|
77
76
|
## Test suite
|
|
78
77
|
|
|
79
|
-
Setting aside the benchmark, `punctilio`’s test suite includes 1,
|
|
78
|
+
Setting aside the benchmark, `punctilio`’s test suite includes 1,450+ tests at 100% branch coverage, including edge cases derived from competitor libraries ([`smartquotes`](https://github.com/kellym/smartquotes.js), [`retext-smartypants`](https://github.com/retextjs/retext-smartypants), [`typograf`](https://github.com/typograf/typograf)) and the [Standard Ebooks typography manual](https://standardebooks.org/manual/). I also verify that all transformations are stable when applied multiple times.
|
|
80
79
|
|
|
81
80
|
## Works with HTML DOMs via separation boundaries
|
|
82
81
|
|
|
@@ -126,7 +125,7 @@ For manual DOM walking or custom transforms, use `transformElement` from `puncti
|
|
|
126
125
|
|
|
127
126
|
```typescript
|
|
128
127
|
transform(text, {
|
|
129
|
-
punctuationStyle: 'american' | 'british' | 'none', // default: 'american'
|
|
128
|
+
punctuationStyle: 'american' | 'british' | 'german' | 'french' | 'none', // default: 'american'
|
|
130
129
|
dashStyle: 'american' | 'british' | 'none', // default: 'american'
|
|
131
130
|
|
|
132
131
|
symbols: true, // ellipsis, math, legal, arrows
|
|
@@ -147,6 +146,11 @@ transform(text, {
|
|
|
147
146
|
- The `british` style follows [Oxford style](https://www.ox.ac.uk/sites/files/oxford/Style%20Guide%20quick%20reference%20A-Z.pdf):
|
|
148
147
|
- Periods and commas go outside quotation marks (“Hello”, she said.)
|
|
149
148
|
- Spaced en-dashes between words (word – word)
|
|
149
|
+
- The `german` style uses low-9 quotes: „double” (U+201E/U+201C) and ‚single' (U+201A/U+2018).
|
|
150
|
+
- Punctuation outside quotes
|
|
151
|
+
- The `french` style uses guillemets with non-breaking space padding: «\u00A0Bonjour\u00A0».
|
|
152
|
+
- Single quotes remain as curly quotes
|
|
153
|
+
- Punctuation outside quotes
|
|
150
154
|
- Setting either style to `none` skips the entire transform category: `punctuationStyle: 'none'` preserves straight quotes, apostrophes, and prime marks; `dashStyle: 'none'` preserves all hyphens, number ranges, date ranges, and minus signs.
|
|
151
155
|
- `punctilio` is idempotent by design: `transform(transform(text))` always equals `transform(text)`. This is verified automatically by default (`checkIdempotency: true`). Set `checkIdempotency: false` to disable the check.
|
|
152
156
|
- Use `classifyApostrophes(text)` to distinguish apostrophes from closing single quotes. It returns text with apostrophes as U+02BC (MODIFIER LETTER APOSTROPHE) and closing quotes as U+2019 (RIGHT SINGLE QUOTATION MARK). Per the [Unicode Standard](https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-6/#G30602), `transform()` and `niceQuotes()` use U+2019 for both in their output.
|
package/dist/constants.d.ts
CHANGED
|
@@ -51,6 +51,10 @@ export declare const UNICODE_SYMBOLS: {
|
|
|
51
51
|
readonly RIGHT_SINGLE_QUOTE: "’";
|
|
52
52
|
readonly MODIFIER_LETTER_APOSTROPHE: "ʼ";
|
|
53
53
|
readonly NBSP: " ";
|
|
54
|
+
readonly DOUBLE_LOW_9_QUOTE: "„";
|
|
55
|
+
readonly SINGLE_LOW_9_QUOTE: "‚";
|
|
56
|
+
readonly LEFT_GUILLEMET: "«";
|
|
57
|
+
readonly RIGHT_GUILLEMET: "»";
|
|
54
58
|
readonly SUPERSCRIPT_ST: "ˢᵗ";
|
|
55
59
|
readonly SUPERSCRIPT_ND: "ⁿᵈ";
|
|
56
60
|
readonly SUPERSCRIPT_RD: "ʳᵈ";
|
|
@@ -94,17 +98,21 @@ export declare const TERMINAL_PUNCTUATION: readonly ["!", "?", ".", ",", ";", ":
|
|
|
94
98
|
*/
|
|
95
99
|
export declare const LATIN_LETTERS = "A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\u017F\\u0180-\\u024F";
|
|
96
100
|
/**
|
|
97
|
-
* Default separator
|
|
98
|
-
* Uses Unicode Private Use Area
|
|
101
|
+
* Default separator for text spanning HTML elements.
|
|
102
|
+
* Uses a two-character sentinel from the Unicode Private Use Area
|
|
103
|
+
* (U+E000 U+E001) to avoid collisions with CJK fonts, Apple emoji
|
|
104
|
+
* internals, PDF-to-text output, and icon fonts that repurpose U+E000.
|
|
99
105
|
*/
|
|
100
|
-
export declare const DEFAULT_SEPARATOR = "\uE000";
|
|
106
|
+
export declare const DEFAULT_SEPARATOR = "\uE000\uE001";
|
|
101
107
|
export declare const ESCAPED_DEFAULT_SEPARATOR: string;
|
|
102
108
|
/**
|
|
103
|
-
* Returns the regex-escaped separator string for the given options
|
|
104
|
-
*
|
|
109
|
+
* Returns the regex-escaped separator string for the given options,
|
|
110
|
+
* wrapped in a non-capturing group so that quantifiers like `?` and `*`
|
|
111
|
+
* apply to the entire separator sequence (critical for multi-character
|
|
112
|
+
* separators like the default U+E000 U+E001).
|
|
105
113
|
*
|
|
106
114
|
* @param options - Object with an optional `separator` field
|
|
107
|
-
* @returns Regex-escaped separator string
|
|
115
|
+
* @returns Regex-escaped separator string wrapped in `(?:...)`
|
|
108
116
|
*/
|
|
109
117
|
export declare function getEscapedSeparator(options: {
|
|
110
118
|
separator?: string;
|
|
@@ -157,16 +165,22 @@ export declare const SPACE_CHARS: string;
|
|
|
157
165
|
* Creates a lookbehind pattern that matches after whitespace, separator, or start of string.
|
|
158
166
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
159
167
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
168
|
+
* Uses alternation instead of a character class so that multi-character
|
|
169
|
+
* separators are matched as a sequence, not as individual characters.
|
|
170
|
+
*
|
|
171
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
172
|
+
* @returns Pattern string: `(?<=\\s|${sep}|^)`
|
|
162
173
|
*/
|
|
163
174
|
export declare function spaceBoundaryStart(escapedSeparator: string): string;
|
|
164
175
|
/**
|
|
165
176
|
* Creates a lookahead pattern that matches before whitespace, separator, or end of string.
|
|
166
177
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
167
178
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
179
|
+
* Uses alternation instead of a character class so that multi-character
|
|
180
|
+
* separators are matched as a sequence, not as individual characters.
|
|
181
|
+
*
|
|
182
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
183
|
+
* @returns Pattern string: `(?=\\s|${sep}|$)`
|
|
170
184
|
*/
|
|
171
185
|
export declare function spaceBoundaryEnd(escapedSeparator: string): string;
|
|
172
186
|
/**
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;GAEG;AACH,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuElB,CAAA;AAEV;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,8HAYvB,CAAA;AAEV;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,sFAAsF,CAAA;AAEhH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,iBAAiB,CAAA;AAC/C,eAAO,MAAM,yBAAyB,QAAwC,CAAA;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wEAAyE,CAAA;AAEzG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,QAA6B,CAAA;AAErD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEjE;AAUD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAcnE"}
|
package/dist/constants.js
CHANGED
|
@@ -52,6 +52,10 @@ export const UNICODE_SYMBOLS = {
|
|
|
52
52
|
RIGHT_SINGLE_QUOTE: "\u2019",
|
|
53
53
|
MODIFIER_LETTER_APOSTROPHE: "\u02BC",
|
|
54
54
|
NBSP: "\u00A0",
|
|
55
|
+
DOUBLE_LOW_9_QUOTE: "\u201E", // „
|
|
56
|
+
SINGLE_LOW_9_QUOTE: "\u201A", // ‚
|
|
57
|
+
LEFT_GUILLEMET: "\u00AB", // «
|
|
58
|
+
RIGHT_GUILLEMET: "\u00BB", // »
|
|
55
59
|
// Superscript ordinal suffixes
|
|
56
60
|
SUPERSCRIPT_ST: "\u02E2\u1D57", // ˢᵗ
|
|
57
61
|
SUPERSCRIPT_ND: "\u207F\u1D48", // ⁿᵈ
|
|
@@ -113,22 +117,27 @@ export const TERMINAL_PUNCTUATION = [
|
|
|
113
117
|
*/
|
|
114
118
|
export const LATIN_LETTERS = "A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\u017F\\u0180-\\u024F";
|
|
115
119
|
/**
|
|
116
|
-
* Default separator
|
|
117
|
-
* Uses Unicode Private Use Area
|
|
120
|
+
* Default separator for text spanning HTML elements.
|
|
121
|
+
* Uses a two-character sentinel from the Unicode Private Use Area
|
|
122
|
+
* (U+E000 U+E001) to avoid collisions with CJK fonts, Apple emoji
|
|
123
|
+
* internals, PDF-to-text output, and icon fonts that repurpose U+E000.
|
|
118
124
|
*/
|
|
119
|
-
export const DEFAULT_SEPARATOR = "\uE000";
|
|
125
|
+
export const DEFAULT_SEPARATOR = "\uE000\uE001";
|
|
120
126
|
export const ESCAPED_DEFAULT_SEPARATOR = escapeStringRegexp(DEFAULT_SEPARATOR);
|
|
121
127
|
/**
|
|
122
|
-
* Returns the regex-escaped separator string for the given options
|
|
123
|
-
*
|
|
128
|
+
* Returns the regex-escaped separator string for the given options,
|
|
129
|
+
* wrapped in a non-capturing group so that quantifiers like `?` and `*`
|
|
130
|
+
* apply to the entire separator sequence (critical for multi-character
|
|
131
|
+
* separators like the default U+E000 U+E001).
|
|
124
132
|
*
|
|
125
133
|
* @param options - Object with an optional `separator` field
|
|
126
|
-
* @returns Regex-escaped separator string
|
|
134
|
+
* @returns Regex-escaped separator string wrapped in `(?:...)`
|
|
127
135
|
*/
|
|
128
136
|
export function getEscapedSeparator(options) {
|
|
129
|
-
|
|
137
|
+
const escaped = options.separator
|
|
130
138
|
? escapeStringRegexp(options.separator)
|
|
131
139
|
: ESCAPED_DEFAULT_SEPARATOR;
|
|
140
|
+
return `(?:${escaped})`;
|
|
132
141
|
}
|
|
133
142
|
/**
|
|
134
143
|
* Characters that have special meaning in regular expressions.
|
|
@@ -182,21 +191,27 @@ export const SPACE_CHARS = ` ${UNICODE_SYMBOLS.NBSP}`;
|
|
|
182
191
|
* Creates a lookbehind pattern that matches after whitespace, separator, or start of string.
|
|
183
192
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
184
193
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
194
|
+
* Uses alternation instead of a character class so that multi-character
|
|
195
|
+
* separators are matched as a sequence, not as individual characters.
|
|
196
|
+
*
|
|
197
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
198
|
+
* @returns Pattern string: `(?<=\\s|${sep}|^)`
|
|
187
199
|
*/
|
|
188
200
|
export function spaceBoundaryStart(escapedSeparator) {
|
|
189
|
-
return `(
|
|
201
|
+
return `(?<=\\s|${escapedSeparator}|^)`;
|
|
190
202
|
}
|
|
191
203
|
/**
|
|
192
204
|
* Creates a lookahead pattern that matches before whitespace, separator, or end of string.
|
|
193
205
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
194
206
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
207
|
+
* Uses alternation instead of a character class so that multi-character
|
|
208
|
+
* separators are matched as a sequence, not as individual characters.
|
|
209
|
+
*
|
|
210
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
211
|
+
* @returns Pattern string: `(?=\\s|${sep}|$)`
|
|
197
212
|
*/
|
|
198
213
|
export function spaceBoundaryEnd(escapedSeparator) {
|
|
199
|
-
return `(
|
|
214
|
+
return `(?=\\s|${escapedSeparator}|$)`;
|
|
200
215
|
}
|
|
201
216
|
/**
|
|
202
217
|
* Cache for compiled RegExp objects keyed by `pattern + '\0' + flags`.
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
package/dist/dashes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashes.d.ts","sourceRoot":"","sources":["../src/dashes.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"dashes.d.ts","sourceRoot":"","sources":["../src/dashes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;AAEvD,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mFAAmF;IACnF,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAID;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,+BAA0C,CAAA;AAEpF,eAAO,MAAM,MAAM,QAKR,CAAA;AAEX,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAuDjF;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAe/E;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAkC5E;AA0ED,gCAAgC;AAChC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAU7E"}
|
package/dist/dashes.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dash transformation: hyphens → em-dashes, en-dashes, minus signs.
|
|
3
3
|
*/
|
|
4
|
-
import escapeStringRegexp from "escape-string-regexp";
|
|
5
4
|
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, wordBoundaryStart, wordBoundaryEnd, getEscapedSeparator, cachedRegExp } from "./constants.js";
|
|
6
5
|
const { EN_DASH, EM_DASH, MINUS, LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE, LEFT_SINGLE_QUOTE, RIGHT_SINGLE_QUOTE } = UNICODE_SYMBOLS;
|
|
7
6
|
/**
|
|
@@ -102,7 +101,7 @@ export function minusReplace(text, options = {}) {
|
|
|
102
101
|
function convertParentheticalDashes(text, sep, style) {
|
|
103
102
|
const localizedDash = style === "british" ? EN_DASH : EM_DASH;
|
|
104
103
|
const maybeSpace = style === "british" ? " " : "";
|
|
105
|
-
const escapedSep =
|
|
104
|
+
const escapedSep = getEscapedSeparator({ separator: sep });
|
|
106
105
|
// Convert spaced dashes: "word - word" or "word — word"
|
|
107
106
|
// When a separator follows the dash, preserve trailing spaces (they belong to the next text segment).
|
|
108
107
|
// A single plain hyphen directly before a word character (through optional separators) is a
|
|
@@ -137,7 +136,7 @@ function convertParentheticalDashes(text, sep, style) {
|
|
|
137
136
|
* allows a space after the dash: "Don't inter— Hey! Who threw that?"
|
|
138
137
|
*/
|
|
139
138
|
function normalizeEmDashSpacing(text, sep) {
|
|
140
|
-
const escapedSep =
|
|
139
|
+
const escapedSep = getEscapedSeparator({ separator: sep });
|
|
141
140
|
// Remove all spaces around em-dashes
|
|
142
141
|
text = text.replace(cachedRegExp(`(?<before>${escapedSep}?)[ ]*${EM_DASH}[ ]*(?<after>${escapedSep}?)`, "g"), `$<before>${EM_DASH}$<after>`);
|
|
143
142
|
// Preserve space after em-dash at start of line (e.g., attribution)
|
package/dist/dashes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashes.js","sourceRoot":"","sources":["../src/dashes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"dashes.js","sourceRoot":"","sources":["../src/dashes.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export interface TransformOptions {
|
|
|
47
47
|
* - `"british"`: Oxford style. Converts straight quotes to smart quotes,
|
|
48
48
|
* converts prime marks, and places periods/commas outside quotes.
|
|
49
49
|
* Example: "Hello". and "Hello",
|
|
50
|
+
* - `"german"`: German style. Uses low-9 quote characters: „..." and ‚...'
|
|
51
|
+
* (U+201E/U+201C double, U+201A/U+2018 single). Punctuation outside quotes.
|
|
52
|
+
* - `"french"`: French style. Uses guillemets with NBSP padding: «\u00A0...\u00A0»
|
|
53
|
+
* Single quotes remain as curly quotes. Punctuation outside quotes.
|
|
50
54
|
* - `"none"`: Skip all quote and punctuation transforms entirely.
|
|
51
55
|
* Straight quotes, apostrophes, and prime marks are left unmodified.
|
|
52
56
|
*
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,MAAM,EACN,6BAA6B,EAC7B,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,YAAY,EACZ,MAAM,EACN,OAAO,EACP,SAAS,EACT,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,KAAK,EACL,UAAU,EACV,uBAAuB,EACvB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAA;AAElB,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IAExB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,MAAM,EACN,6BAA6B,EAC7B,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,YAAY,EACZ,MAAM,EACN,OAAO,EACP,SAAS,EACT,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,KAAK,EACL,UAAU,EACV,uBAAuB,EACvB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAA;AAElB,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IAExB;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IAErB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAE3B;AASD,OAAO,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAClG,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACnE,eAAO,MAAM,0BAA0B,KAA6C,CAAA;AAiDpF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAwE9E"}
|
package/dist/index.js
CHANGED
|
@@ -68,12 +68,17 @@ const defaultOpts = {
|
|
|
68
68
|
};
|
|
69
69
|
export function transform(text, options = {}) {
|
|
70
70
|
const separator = options.separator ?? DEFAULT_SEPARATOR;
|
|
71
|
-
// Validate separator: must be
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Validate separator: must be non-empty and contain only BMP characters.
|
|
72
|
+
// for...of iterates by code point; surrogate pairs (non-BMP) yield a
|
|
73
|
+
// two-UTF16-unit string, so ch.length > 1 detects them.
|
|
74
|
+
if (separator.length === 0) {
|
|
75
|
+
throw new Error("Invalid separator: must not be empty.");
|
|
76
|
+
}
|
|
77
|
+
for (const ch of separator) {
|
|
78
|
+
if (ch.length > 1) {
|
|
79
|
+
throw new Error(`Invalid separator: must contain only BMP characters (no characters outside the Basic Multilingual Plane). ` +
|
|
80
|
+
`Received "${separator}" which contains a non-BMP character.`);
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
const original = text;
|
|
79
84
|
const { symbols, fractions, degrees, superscript, ligatures, nbsp, collapseSpaces, checkIdempotency, ...separatorOpts } = { ...defaultOpts, ...options };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAA4C,MAAM,aAAa,CAAA;AAEvG,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,MAAM,EACN,6BAA6B,GAG9B,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,YAAY,EACZ,MAAM,EACN,OAAO,EACP,SAAS,EACT,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,GAEhB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,KAAK,EACL,UAAU,EACV,uBAAuB,GAExB,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/nbsp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nbsp.d.ts","sourceRoot":"","sources":["../src/nbsp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAajD,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;AAO1D;;;GAGG;AACH,eAAO,MAAM,KAAK,UA2BjB,CAAA;AAED,eAAO,MAAM,UAAU,UActB,CAAA;AAED,eAAO,MAAM,uBAAuB,UASnC,CAAA;AASD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOnF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAQxF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"nbsp.d.ts","sourceRoot":"","sources":["../src/nbsp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAajD,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;AAO1D;;;GAGG;AACH,eAAO,MAAM,KAAK,UA2BjB,CAAA;AAED,eAAO,MAAM,UAAU,UActB,CAAA;AAED,eAAO,MAAM,uBAAuB,UASnC,CAAA;AASD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOnF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAQxF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAQlF;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAO/F;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOvF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOnF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOzF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAOnF;AAsBD,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAK7E"}
|
package/dist/nbsp.js
CHANGED
|
@@ -106,7 +106,8 @@ export function nbspBetweenNumberAndUnit(text, options = {}) {
|
|
|
106
106
|
*/
|
|
107
107
|
export function nbspBeforeLastWord(text, options = {}) {
|
|
108
108
|
const sep = getEscapedSeparator(options);
|
|
109
|
-
|
|
109
|
+
// Use alternation instead of character classes for multi-char separator support
|
|
110
|
+
const pattern = cachedRegExp(`(?<=\\w|${sep})${SPACE}(?<lastWord>(?:(?!\\s)(?!${sep}).){1,10})(?<ending>${sep}?(?:\\n\\n|$))`, "g");
|
|
110
111
|
return text.replace(pattern, `${NBSP}$<lastWord>$<ending>`);
|
|
111
112
|
}
|
|
112
113
|
/**
|
package/dist/nbsp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nbsp.js","sourceRoot":"","sources":["../src/nbsp.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"nbsp.js","sourceRoot":"","sources":["../src/nbsp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAGnH,MAAM,EACJ,IAAI,EACJ,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,UAAU,EACV,SAAS,GACV,GAAG,eAAe,CAAA;AAInB,2DAA2D;AAC3D,MAAM,KAAK,GAAG,QAAQ,IAAI,GAAG,CAAA;AAE7B,MAAM,iBAAiB,GAAG,SAAS,CAAA;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,SAAS;IACT,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG;IACzD,OAAO;IACP,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IACvC,SAAS;IACT,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IACjC,OAAO;IACP,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG;IAClC,oBAAoB;IACpB,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI;IACvC,UAAU;IACV,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACtE,iBAAiB;IACjB,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAC1D,cAAc;IACd,GAAG;IACH,aAAa;IACb,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAC1B,kBAAkB;IAClB,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;IACtC,mBAAmB;IACnB,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC1C,UAAU;IACV,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IACnB,OAAO;IACP,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI;CAC3B,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU;IACV,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;IACtC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IAC5C,SAAS;IACT,KAAK,EAAE,MAAM,EAAE,KAAK;IACpB,kBAAkB;IAClB,IAAI,EAAE,IAAI;IACV,uBAAuB;IACvB,KAAK,EAAE,MAAM;IACb,UAAU;IACV,KAAK,EAAE,MAAM;IACb,QAAQ;IACR,KAAK,EAAE,MAAM;CACd,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,kBAAkB;IAClB,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;IACjC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;IAC9B,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI;IAC/B,SAAS;IACT,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;IACxB,2CAA2C;IAC3C,KAAK;CACN,CAAA;AAED,8BAA8B;AAC9B,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACpC,MAAM,iBAAiB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACpE,MAAM,oBAAoB,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACpF,MAAM,oBAAoB,GAAG,YAAY,iBAAiB,GAAG,kBAAkB,eAAe,iBAAiB,GAAG,kBAAkB,IAAI,CAAA;AACxI,MAAM,iBAAiB,GAAG,IAAI,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,CAAA;AAEnE;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,UAAuB,EAAE;IACzE,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,SAAS,KAAK,IAAI,oBAAoB,oBAAoB,aAAa,oBAAoB,GAAG,KAAK,KAAK,EAAE,EAC1G,KAAK,CACN,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,wBAAwB,IAAI,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAE,UAAuB,EAAE;IAC9E,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,OAAO,GAAG,YAAY,CAC1B,2BAA2B,GAAG,KAAK,KAAK,cAAc,GAAG,aAAa,YAAY,IAAI,GAAG,EAAE,EAC3F,IAAI,CACL,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,qBAAqB,IAAI,mBAAmB,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,UAAuB,EAAE;IACxE,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,gFAAgF;IAChF,MAAM,OAAO,GAAG,YAAY,CAC1B,WAAW,GAAG,IAAI,KAAK,4BAA4B,GAAG,uBAAuB,GAAG,gBAAgB,EAChG,GAAG,CACJ,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,sBAAsB,CAAC,CAAA;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,+BAA+B,CAAC,IAAY,EAAE,UAAuB,EAAE;IACrF,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,aAAa,oBAAoB,cAAc,GAAG,KAAK,KAAK,MAAM,GAAG,OAAO,EAC5E,GAAG,CACJ,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,qBAAqB,IAAI,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY,EAAE,UAAuB,EAAE;IAC7E,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,4BAA4B,GAAG,KAAK,KAAK,MAAM,GAAG,OAAO,EACzD,GAAG,CACJ,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,qBAAqB,IAAI,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,UAAuB,EAAE;IACzE,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,gBAAgB,iBAAiB,cAAc,GAAG,KAAK,KAAK,MAAM,GAAG,IAAI,iBAAiB,GAAG,EAC7F,IAAI,CACL,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,wBAAwB,IAAI,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY,EAAE,UAAuB,EAAE;IAC/E,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,aAAa,iBAAiB,cAAc,GAAG,KAAK,KAAK,MAAM,GAAG,QAAQ,iBAAiB,IAAI,EAC/F,IAAI,CACL,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,qBAAqB,IAAI,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,UAAuB,EAAE;IACzE,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,YAAY,CAC1B,cAAc,iBAAiB,iBAAiB,GAAG,KAAK,KAAK,MAAM,GAAG,IAAI,iBAAiB,GAAG,EAC9F,IAAI,CACL,CAAA;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,IAAI,EAAE,CAAC,CAAA;AAC5D,CAAC;AAID;;;;;;GAMG;AACH,MAAM,eAAe,GAAa;IAChC,mBAAmB;IACnB,+BAA+B;IAC/B,uBAAuB;IACvB,yBAAyB;IACzB,mBAAmB;IACnB,wBAAwB;IACxB,mBAAmB;IACnB,kBAAkB;CACnB,CAAA;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,UAAuB,EAAE;IACnE,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC;QACjC,IAAI,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
package/dist/quotes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Smart quote transformation: straight quotes → curly quotes.
|
|
3
3
|
*/
|
|
4
|
-
export type PunctuationStyle = "american" | "british" | "none";
|
|
4
|
+
export type PunctuationStyle = "american" | "british" | "german" | "french" | "none";
|
|
5
5
|
export interface QuoteOptions {
|
|
6
6
|
/** Boundary marker for HTML element boundaries. Default: "\uE000" */
|
|
7
7
|
separator?: string;
|
package/dist/quotes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEpF,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACpC;AAqOD,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAK3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAEpF"}
|
package/dist/quotes.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Smart quote transformation: straight quotes → curly quotes.
|
|
3
3
|
*/
|
|
4
4
|
import escapeStringRegexp from "escape-string-regexp";
|
|
5
|
-
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, TERMINAL_PUNCTUATION, cachedRegExp } from "./constants.js";
|
|
5
|
+
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, TERMINAL_PUNCTUATION, getEscapedSeparator, cachedRegExp } from "./constants.js";
|
|
6
6
|
const { EM_DASH, LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE, LEFT_SINGLE_QUOTE, RIGHT_SINGLE_QUOTE, MODIFIER_LETTER_APOSTROPHE, } = UNICODE_SYMBOLS;
|
|
7
7
|
/** Joined string of all terminal punctuation characters for use in regex character classes. */
|
|
8
8
|
const TERMINAL_PUNCTUATION_CLASS = TERMINAL_PUNCTUATION.join("");
|
|
9
9
|
/** Convert straight single quotes to curly quotes and apostrophes */
|
|
10
10
|
function convertSingleQuotes(text, sep) {
|
|
11
|
-
const escapedSep =
|
|
11
|
+
const escapedSep = getEscapedSeparator({ separator: sep });
|
|
12
12
|
// Handle empty single quotes '' and whitespace-only quotes ' ' first
|
|
13
13
|
// Only match straight quotes, not already-converted curly quotes
|
|
14
14
|
const singleQuoteChars = `'${LEFT_SINGLE_QUOTE}${RIGHT_SINGLE_QUOTE}${MODIFIER_LETTER_APOSTROPHE}`;
|
|
@@ -60,8 +60,9 @@ function convertUnmatchedPluralPossessives(text, sep) {
|
|
|
60
60
|
return match;
|
|
61
61
|
}
|
|
62
62
|
let i = offset - 1;
|
|
63
|
-
while (i >=
|
|
64
|
-
i
|
|
63
|
+
while (i >= sep.length - 1 && text.startsWith(sep, i - sep.length + 1)) {
|
|
64
|
+
i -= sep.length;
|
|
65
|
+
}
|
|
65
66
|
if (i >= 0 && (text[i] === "s" || text[i] === "S")) {
|
|
66
67
|
return MODIFIER_LETTER_APOSTROPHE;
|
|
67
68
|
}
|
|
@@ -70,13 +71,14 @@ function convertUnmatchedPluralPossessives(text, sep) {
|
|
|
70
71
|
}
|
|
71
72
|
/** Convert straight double quotes to curly quotes */
|
|
72
73
|
function convertDoubleQuotes(text, sep) {
|
|
73
|
-
const
|
|
74
|
+
const rawEscSep = escapeStringRegexp(sep);
|
|
75
|
+
const escapedSep = getEscapedSeparator({ separator: sep });
|
|
74
76
|
// Handle empty quotes "" first - match only when not part of adjacent quotes
|
|
75
77
|
// Require word boundary or start/end of string on at least one side
|
|
76
78
|
text = text.replace(/(?<=^|[\s([{])""(?=$|[\s)\]}.!?,;:])/g, `${LEFT_DOUBLE_QUOTE}${RIGHT_DOUBLE_QUOTE}`);
|
|
77
79
|
// Handle whitespace-only quotes " " - require non-quote chars on both sides
|
|
78
80
|
text = text.replace(/(?<=^|[\s([{])"(?<whitespace>\s+)"(?=$|[\s)\]}.!?,;:])/g, `${LEFT_DOUBLE_QUOTE}$<whitespace>${RIGHT_DOUBLE_QUOTE}`);
|
|
79
|
-
const beginningDouble = cachedRegExp(`(?<=^|[\\s\\(\\/\\[\\{\\-${EM_DASH}
|
|
81
|
+
const beginningDouble = cachedRegExp(`(?<=^|[\\s\\(\\/\\[\\{\\-${EM_DASH}]|${escapedSep})(?<beforeChr>${escapedSep}?)["](?<afterChr>(?<sepWithPunct>${escapedSep}[ .,])|(?=${escapedSep}?\\.{3}|${escapedSep}?[^\\s\\)${EM_DASH},!?;:.\\}${rawEscSep}]))`, "gm");
|
|
80
82
|
text = text.replace(beginningDouble, `$<beforeChr>${LEFT_DOUBLE_QUOTE}$<afterChr>`);
|
|
81
83
|
text = text.replace(cachedRegExp(`(?<=\\{)(?<sepSpace>${escapedSep}? )?["]`, "g"), `$<sepSpace>${LEFT_DOUBLE_QUOTE}`);
|
|
82
84
|
const endingDouble = `(?<beforeQuote>[^\\s\\(])["]((?<sepAfter>${escapedSep})?)(?=${escapedSep}|[\\s/\\).,;${EM_DASH}:\\-\\}!?s]|$)`;
|
|
@@ -87,7 +89,7 @@ function convertDoubleQuotes(text, sep) {
|
|
|
87
89
|
}
|
|
88
90
|
/** Apply American or British punctuation style */
|
|
89
91
|
function applyPunctuationStyle(text, sep, style) {
|
|
90
|
-
const escapedSep =
|
|
92
|
+
const escapedSep = getEscapedSeparator({ separator: sep });
|
|
91
93
|
if (style === "american") {
|
|
92
94
|
// Period outside → inside: "Hello". → "Hello."
|
|
93
95
|
const periodOutsideRegex = cachedRegExp(`(?<![${TERMINAL_PUNCTUATION_CLASS}])(?<sepBefore>${escapedSep}?)(?<quote>[${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}])(?<sepAfter>${escapedSep}?)(?!\\.\\.\\.)\\.`, "g");
|
|
@@ -96,7 +98,7 @@ function applyPunctuationStyle(text, sep, style) {
|
|
|
96
98
|
const commaOutsideRegex = cachedRegExp(`(?<![${TERMINAL_PUNCTUATION_CLASS}])(?<sepBefore>${escapedSep}?)(?<quote>[${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}])(?<sepAfter>${escapedSep}?),`, "g");
|
|
97
99
|
text = text.replace(commaOutsideRegex, "$<sepBefore>,$<quote>$<sepAfter>");
|
|
98
100
|
}
|
|
99
|
-
else if (style === "british") {
|
|
101
|
+
else if (style === "british" || style === "german" || style === "french") {
|
|
100
102
|
// Period inside → outside: "Hello." → "Hello".
|
|
101
103
|
// No terminal punctuation guard — "Stop!." inside is always wrong; move the period out.
|
|
102
104
|
const periodInsideRegex = cachedRegExp(`(?<sepBefore>${escapedSep}?)\\.(?<sepMiddle>${escapedSep}?)(?<quote>[${RIGHT_SINGLE_QUOTE}${RIGHT_DOUBLE_QUOTE}])`, "g");
|
|
@@ -107,15 +109,79 @@ function applyPunctuationStyle(text, sep, style) {
|
|
|
107
109
|
}
|
|
108
110
|
return text;
|
|
109
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Normalize German quotes back to American for idempotent re-processing.
|
|
114
|
+
* Tracks „..." (U+201E/U+201C) and ‚...' (U+201A/U+2018) pairs so that
|
|
115
|
+
* the ambiguous closer characters are mapped to the correct American equivalents.
|
|
116
|
+
*/
|
|
117
|
+
function normalizeGermanQuotes(text) {
|
|
118
|
+
const DLQ = UNICODE_SYMBOLS.DOUBLE_LOW_9_QUOTE;
|
|
119
|
+
const SLQ = UNICODE_SYMBOLS.SINGLE_LOW_9_QUOTE;
|
|
120
|
+
let result = "";
|
|
121
|
+
let doubleDepth = 0;
|
|
122
|
+
let singleDepth = 0;
|
|
123
|
+
for (const ch of text) {
|
|
124
|
+
if (ch === DLQ) {
|
|
125
|
+
result += LEFT_DOUBLE_QUOTE;
|
|
126
|
+
doubleDepth++;
|
|
127
|
+
}
|
|
128
|
+
else if (ch === LEFT_DOUBLE_QUOTE && doubleDepth > 0) {
|
|
129
|
+
result += RIGHT_DOUBLE_QUOTE;
|
|
130
|
+
doubleDepth--;
|
|
131
|
+
}
|
|
132
|
+
else if (ch === SLQ) {
|
|
133
|
+
result += LEFT_SINGLE_QUOTE;
|
|
134
|
+
singleDepth++;
|
|
135
|
+
}
|
|
136
|
+
else if (ch === LEFT_SINGLE_QUOTE && singleDepth > 0) {
|
|
137
|
+
result += RIGHT_SINGLE_QUOTE;
|
|
138
|
+
singleDepth--;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
result += ch;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/** Remap American curly quotes to German low-9 style. Safe because apostrophes are still MLA at this stage. */
|
|
147
|
+
function applyGermanQuotes(text) {
|
|
148
|
+
return text
|
|
149
|
+
.replaceAll(LEFT_DOUBLE_QUOTE, UNICODE_SYMBOLS.DOUBLE_LOW_9_QUOTE)
|
|
150
|
+
.replaceAll(RIGHT_DOUBLE_QUOTE, LEFT_DOUBLE_QUOTE)
|
|
151
|
+
.replaceAll(LEFT_SINGLE_QUOTE, UNICODE_SYMBOLS.SINGLE_LOW_9_QUOTE)
|
|
152
|
+
.replaceAll(RIGHT_SINGLE_QUOTE, LEFT_SINGLE_QUOTE);
|
|
153
|
+
}
|
|
154
|
+
/** Normalize French guillemets back to American for idempotent re-processing. */
|
|
155
|
+
function normalizeFrenchQuotes(text) {
|
|
156
|
+
return text
|
|
157
|
+
.replace(cachedRegExp(`${UNICODE_SYMBOLS.LEFT_GUILLEMET}${UNICODE_SYMBOLS.NBSP}?`, "g"), LEFT_DOUBLE_QUOTE)
|
|
158
|
+
.replace(cachedRegExp(`${UNICODE_SYMBOLS.NBSP}?${UNICODE_SYMBOLS.RIGHT_GUILLEMET}`, "g"), RIGHT_DOUBLE_QUOTE);
|
|
159
|
+
}
|
|
160
|
+
/** Remap American curly double quotes to French guillemets with NBSP padding. */
|
|
161
|
+
function applyFrenchQuotes(text) {
|
|
162
|
+
return text
|
|
163
|
+
.replaceAll(LEFT_DOUBLE_QUOTE, `${UNICODE_SYMBOLS.LEFT_GUILLEMET}${UNICODE_SYMBOLS.NBSP}`)
|
|
164
|
+
.replaceAll(RIGHT_DOUBLE_QUOTE, `${UNICODE_SYMBOLS.NBSP}${UNICODE_SYMBOLS.RIGHT_GUILLEMET}`);
|
|
165
|
+
}
|
|
166
|
+
/** Locale-specific normalize (pre-pipeline) and apply (post-pipeline) functions. */
|
|
167
|
+
const localeQuoteTransforms = {
|
|
168
|
+
german: { normalize: normalizeGermanQuotes, apply: applyGermanQuotes },
|
|
169
|
+
french: { normalize: normalizeFrenchQuotes, apply: applyFrenchQuotes },
|
|
170
|
+
};
|
|
110
171
|
/** Shared quote-processing pipeline. Returns text with MLA for apostrophes. */
|
|
111
172
|
function processQuotes(text, options) {
|
|
112
173
|
const sep = options.separator ?? DEFAULT_SEPARATOR;
|
|
113
174
|
const punctuationStyle = options.punctuationStyle ?? "american";
|
|
114
175
|
if (punctuationStyle === "none")
|
|
115
176
|
return text;
|
|
177
|
+
const locale = localeQuoteTransforms[punctuationStyle];
|
|
178
|
+
if (locale)
|
|
179
|
+
text = locale.normalize(text);
|
|
116
180
|
text = convertSingleQuotes(text, sep);
|
|
117
181
|
text = convertDoubleQuotes(text, sep);
|
|
118
182
|
text = applyPunctuationStyle(text, sep, punctuationStyle);
|
|
183
|
+
if (locale)
|
|
184
|
+
text = locale.apply(text);
|
|
119
185
|
return text;
|
|
120
186
|
}
|
|
121
187
|
/** Convert straight quotes to smart quotes. */
|
package/dist/quotes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,kBAAkB,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,kBAAkB,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3I,MAAM,EACJ,OAAO,EACP,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,GAC3B,GAAG,eAAe,CAAA;AAEnB,+FAA+F;AAC/F,MAAM,0BAA0B,GAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAWhE,qEAAqE;AACrE,SAAS,mBAAmB,CAAC,IAAY,EAAE,GAAW;IACpD,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;IAE1D,qEAAqE;IACrE,iEAAiE;IACjE,MAAM,gBAAgB,GAAG,IAAI,iBAAiB,GAAG,kBAAkB,GAAG,0BAA0B,EAAE,CAAA;IAClG,MAAM,iBAAiB,GAAG,IAAI,gBAAgB,MAAM,CAAA;IACpD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,iBAAiB,SAAS,iBAAiB,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,iBAAiB,GAAG,kBAAkB,EAAE,CAAC,CAAA;IAC1I,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,iBAAiB,eAAe,iBAAiB,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,iBAAiB,KAAK,kBAAkB,EAAE,CAAC,CAAA;IAElJ,MAAM,yBAAyB,GAAG,gBAAgB,OAAO,SAAS,CAAA;IAClE,4EAA4E;IAC5E,MAAM,iBAAiB,GAAG,MAAM,UAAU,QAAQ,UAAU,UAAU,yBAAyB,OAAO,CAAA;IAEtG,2EAA2E;IAC3E,6EAA6E;IAC7E,+EAA+E;IAC/E,mFAAmF;IACnF,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,UAAU,QAAQ,kBAAkB,OAAO,kBAAkB,QAAQ,UAAU,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,0BAA0B,IAAI,0BAA0B,EAAE,CAAC,CAAA;IAExM,mEAAmE;IACnE,MAAM,eAAe,GAAG,MAAM,UAAU,KAAK,UAAU,QAAQ,yBAAyB,OAAO,CAAA;IAC/F,MAAM,gBAAgB,GAAG,YAAY,iBAAiB,QAAQ,kBAAkB,IAAI,eAAe,EAAE,CAAA;IACrG,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,EAAE,0BAA0B,CAAC,CAAA;IAErF,4DAA4D;IAC5D,MAAM,kBAAkB,GAAG,MAAM,UAAU,QAAQ,yBAAyB,OAAO,CAAA;IACnF,MAAM,aAAa,GAAG,YAAY,iBAAiB,SAAS,kBAAkB,EAAE,CAAA;IAChF,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAA;IAE1E,MAAM,WAAW,GAAG,QAAQ,aAAa,OAAO,kBAAkB,GAAG,0BAA0B,OAAO,UAAU,KAAK,aAAa,IAAI,CAAA;IACtI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,0BAA0B,CAAC,CAAA;IAEhF,MAAM,mBAAmB,GAAG,OAAO,0BAA0B,IAAI,CAAA;IACjE,MAAM,sBAAsB,GAAG,MAAM,WAAW,KAAK,kBAAkB,GAAG,0BAA0B,IAAI,iBAAiB,EAAE,CAAA;IAC3H,iGAAiG;IACjG,MAAM,eAAe,GAAG,YAAY,CAClC,oBAAoB,mBAAmB,SAAS,iBAAiB,gBAAgB,sBAAsB,IAAI,EAC3G,IAAI,CACL,CAAA;IACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAA;IAEhE,MAAM,eAAe,GAAG,6BAA6B,iBAAiB,GAAG,kBAAkB,GAAG,OAAO,WAAW,UAAU,WAAW,UAAU,OAAO,CAAA;IACtJ,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,eAAe,EAAE,IAAI,CAAC,EAAE,mBAAmB,iBAAiB,EAAE,CAAC,CAAA;IAEhG,IAAI,GAAG,iCAAiC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAEnD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,iCAAiC,CAAC,IAAY,EAAE,GAAW;IAClE,IAAI,kBAAkB,GAAG,CAAC,CAAA;IAC1B,OAAO,IAAI,CAAC,OAAO,CACjB,YAAY,CAAC,IAAI,iBAAiB,GAAG,kBAAkB,GAAG,EAAE,GAAG,CAAC,EAChE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAChB,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;YAChC,kBAAkB,EAAE,CAAA;YACpB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,kBAAkB,GAAG,CAAC,EAAE,CAAC;YAC3B,kBAAkB,EAAE,CAAA;YACpB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,CAAA;QAClB,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;YACvE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAA;QACjB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YACnD,OAAO,0BAA0B,CAAA;QACnC,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CACF,CAAA;AACH,CAAC;AAED,qDAAqD;AACrD,SAAS,mBAAmB,CAAC,IAAY,EAAE,GAAW;IACpD,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;IACzC,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;IAE1D,6EAA6E;IAC7E,oEAAoE;IACpE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,uCAAuC,EAAE,GAAG,iBAAiB,GAAG,kBAAkB,EAAE,CAAC,CAAA;IACzG,4EAA4E;IAC5E,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,yDAAyD,EAAE,GAAG,iBAAiB,gBAAgB,kBAAkB,EAAE,CAAC,CAAA;IAExI,MAAM,eAAe,GAAG,YAAY,CAClC,4BAA4B,OAAO,KAAK,UAAU,iBAAiB,UAAU,oCAAoC,UAAU,aAAa,UAAU,WAAW,UAAU,YAAY,OAAO,YAAY,SAAS,KAAK,EACpN,IAAI,CACL,CAAA;IACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,eAAe,iBAAiB,aAAa,CAAC,CAAA;IAEnF,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,uBAAuB,UAAU,SAAS,EAAE,GAAG,CAAC,EAAE,cAAc,iBAAiB,EAAE,CAAC,CAAA;IAErH,MAAM,YAAY,GAAG,4CAA4C,UAAU,SAAS,UAAU,eAAe,OAAO,gBAAgB,CAAA;IACpI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,GAAG,CAAC,EAAE,iBAAiB,kBAAkB,aAAa,CAAC,CAAA;IAEtG,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,gBAAgB,UAAU,KAAK,EAAE,GAAG,CAAC,EAAE,GAAG,kBAAkB,WAAW,CAAC,CAAA;IACzG,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,kBAAkB,GAAG,EAAE,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAA;IAEzF,OAAO,IAAI,CAAA;AACb,CAAC;AAED,kDAAkD;AAClD,SAAS,qBAAqB,CAAC,IAAY,EAAE,GAAW,EAAE,KAAuB;IAC/E,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;IAE1D,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QACzB,+CAA+C;QAC/C,MAAM,kBAAkB,GAAG,YAAY,CACrC,QAAQ,0BAA0B,kBAAkB,UAAU,eAAe,kBAAkB,GAAG,kBAAkB,iBAAiB,UAAU,oBAAoB,EACnK,GAAG,CACJ,CAAA;QACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,kCAAkC,CAAC,CAAA;QAE3E,8CAA8C;QAC9C,MAAM,iBAAiB,GAAG,YAAY,CACpC,QAAQ,0BAA0B,kBAAkB,UAAU,eAAe,kBAAkB,GAAG,kBAAkB,iBAAiB,UAAU,KAAK,EACpJ,GAAG,CACJ,CAAA;QACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,kCAAkC,CAAC,CAAA;IAC5E,CAAC;SAAM,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC3E,+CAA+C;QAC/C,wFAAwF;QACxF,MAAM,iBAAiB,GAAG,YAAY,CACpC,gBAAgB,UAAU,qBAAqB,UAAU,eAAe,kBAAkB,GAAG,kBAAkB,IAAI,EACnH,GAAG,CACJ,CAAA;QACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,mCAAmC,CAAC,CAAA;QAE3E,8CAA8C;QAC9C,MAAM,gBAAgB,GAAG,YAAY,CACnC,mBAAmB,UAAU,KAAK,kBAAkB,GAAG,kBAAkB,IAAI,EAC7E,GAAG,CACJ,CAAA;QACD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAA;IAC1D,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,IAAY;IACzC,MAAM,GAAG,GAAG,eAAe,CAAC,kBAAkB,CAAA;IAC9C,MAAM,GAAG,GAAG,eAAe,CAAC,kBAAkB,CAAA;IAE9C,IAAI,MAAM,GAAG,EAAE,CAAA;IACf,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,WAAW,GAAG,CAAC,CAAA;IAEnB,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,iBAAiB,CAAA;YAC3B,WAAW,EAAE,CAAA;QACf,CAAC;aAAM,IAAI,EAAE,KAAK,iBAAiB,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,kBAAkB,CAAA;YAC5B,WAAW,EAAE,CAAA;QACf,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,MAAM,IAAI,iBAAiB,CAAA;YAC3B,WAAW,EAAE,CAAA;QACf,CAAC;aAAM,IAAI,EAAE,KAAK,iBAAiB,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,kBAAkB,CAAA;YAC5B,WAAW,EAAE,CAAA;QACf,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,EAAE,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,+GAA+G;AAC/G,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI;SACR,UAAU,CAAC,iBAAiB,EAAE,eAAe,CAAC,kBAAkB,CAAC;SACjE,UAAU,CAAC,kBAAkB,EAAE,iBAAiB,CAAC;SACjD,UAAU,CAAC,iBAAiB,EAAE,eAAe,CAAC,kBAAkB,CAAC;SACjE,UAAU,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAA;AACtD,CAAC;AAED,iFAAiF;AACjF,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI;SACR,OAAO,CAAC,YAAY,CAAC,GAAG,eAAe,CAAC,cAAc,GAAG,eAAe,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,iBAAiB,CAAC;SAC1G,OAAO,CAAC,YAAY,CAAC,GAAG,eAAe,CAAC,IAAI,IAAI,eAAe,CAAC,eAAe,EAAE,EAAE,GAAG,CAAC,EAAE,kBAAkB,CAAC,CAAA;AACjH,CAAC;AAED,iFAAiF;AACjF,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI;SACR,UAAU,CAAC,iBAAiB,EAAE,GAAG,eAAe,CAAC,cAAc,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC;SACzF,UAAU,CAAC,kBAAkB,EAAE,GAAG,eAAe,CAAC,IAAI,GAAG,eAAe,CAAC,eAAe,EAAE,CAAC,CAAA;AAChG,CAAC;AAED,oFAAoF;AACpF,MAAM,qBAAqB,GAA0G;IACnI,MAAM,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,KAAK,EAAE,iBAAiB,EAAE;IACtE,MAAM,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,KAAK,EAAE,iBAAiB,EAAE;CACvE,CAAA;AAED,+EAA+E;AAC/E,SAAS,aAAa,CAAC,IAAY,EAAE,OAAqB;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,IAAI,iBAAiB,CAAA;IAClD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,UAAU,CAAA;IAC/D,IAAI,gBAAgB,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAE5C,MAAM,MAAM,GAAG,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IACtD,IAAI,MAAM;QAAE,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAEzC,IAAI,GAAG,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACrC,IAAI,GAAG,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACrC,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAA;IAEzD,IAAI,MAAM;QAAE,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAErC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,UAAwB,EAAE;IACjE,kEAAkE;IAClE,oEAAoE;IACpE,8DAA8D;IAC9D,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,0BAA0B,EAAE,kBAAkB,CAAC,CAAA;AAChG,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,UAAwB,EAAE;IAC1E,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACrC,CAAC"}
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAanF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAA0B,GAAG,MAAM,CAQ3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAA0B,EACrC,aAAa,GAAE,MAAoB,GAClC,IAAI,CAUN"}
|
package/dist/utils.js
CHANGED
|
@@ -41,17 +41,20 @@ export function assertSeparatorAbsent(textValues, separator) {
|
|
|
41
41
|
for (const value of textValues) {
|
|
42
42
|
if (!value.includes(separator))
|
|
43
43
|
continue;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const codePoints = [...separator]
|
|
45
|
+
.map(ch => `U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`)
|
|
46
|
+
.join(" ");
|
|
47
|
+
throw new Error(`Text contains the separator sequence ${codePoints} which is used internally by punctilio ` +
|
|
48
|
+
`to track element boundaries. Pass a different separator via the "separator" option.\n` +
|
|
47
49
|
`Text: ${formatErrorString(value, "input")}`);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
export function countSeparators(text, separator = DEFAULT_SEPARATOR) {
|
|
51
53
|
let count = 0;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
let index = 0;
|
|
55
|
+
while ((index = text.indexOf(separator, index)) !== -1) {
|
|
56
|
+
count++;
|
|
57
|
+
index += separator.length;
|
|
55
58
|
}
|
|
56
59
|
return count;
|
|
57
60
|
}
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAElD,qEAAqE;AACrE,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAEnC;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,KAAa;IAC9D,IAAI,OAAO,CAAC,MAAM,IAAI,sBAAsB,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,IAAI,OAAO,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;YAC5D,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAC7B,gBAAgB,KAAK,kBAAkB,OAAO,CAAC,MAAM,cAAc,OAAO,MAAM,CACjF,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAA;IAC1D,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,OAAO,CAAC,MAAM,gBAAgB,CAAA;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAoB,EAAE,SAAiB;IAC3E,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,SAAQ;QAExC,MAAM,SAAS,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAElD,qEAAqE;AACrE,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAEnC;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,KAAa;IAC9D,IAAI,OAAO,CAAC,MAAM,IAAI,sBAAsB,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,IAAI,OAAO,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;YAC5D,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAC7B,gBAAgB,KAAK,kBAAkB,OAAO,CAAC,MAAM,cAAc,OAAO,MAAM,CACjF,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAA;IAC1D,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,OAAO,CAAC,MAAM,gBAAgB,CAAA;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAoB,EAAE,SAAiB;IAC3E,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,SAAQ;QAExC,MAAM,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC;aAC9B,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;aAChF,IAAI,CAAC,GAAG,CAAC,CAAA;QACZ,MAAM,IAAI,KAAK,CACb,wCAAwC,UAAU,yCAAyC;YAC3F,uFAAuF;YACvF,SAAS,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAC7C,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,YAAoB,iBAAiB;IACjF,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACvD,KAAK,EAAE,CAAA;QACP,KAAK,IAAI,SAAS,CAAC,MAAM,CAAA;IAC3B,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAgB,EAChB,WAAmB,EACnB,YAAoB,iBAAiB,EACrC,gBAAwB,WAAW;IAEnC,MAAM,aAAa,GAAG,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;IAC1D,MAAM,gBAAgB,GAAG,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAChE,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,GAAG,aAAa,sCAAsC,aAAa,SAAS,gBAAgB,KAAK;YACjG,4GAA4G;YAC5G,gDAAgD,CACjD,CAAA;IACH,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "punctilio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Smart typography transformations: curly quotes, em-dashes, en-dashes, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"eslint-plugin-regexp": "^3.0.0",
|
|
69
69
|
"hastscript": "9.0.1",
|
|
70
70
|
"jest": "^29.7.0",
|
|
71
|
+
"lint-staged": "16.3.2",
|
|
71
72
|
"rehype-parse": "9.0.1",
|
|
72
73
|
"rehype-stringify": "10.0.1",
|
|
73
74
|
"remark-gfm": "^4.0.1",
|
|
@@ -104,6 +105,9 @@
|
|
|
104
105
|
"escape-string-regexp": "5.0.0",
|
|
105
106
|
"unist-util-visit-parents": "^6.0.1"
|
|
106
107
|
},
|
|
108
|
+
"lint-staged": {
|
|
109
|
+
"*.{ts,js,mjs}": "eslint --fix"
|
|
110
|
+
},
|
|
107
111
|
"scripts": {
|
|
108
112
|
"build": "tsc",
|
|
109
113
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
package/src/constants.ts
CHANGED
|
@@ -54,6 +54,10 @@ export const UNICODE_SYMBOLS = {
|
|
|
54
54
|
RIGHT_SINGLE_QUOTE: "\u2019",
|
|
55
55
|
MODIFIER_LETTER_APOSTROPHE: "\u02BC",
|
|
56
56
|
NBSP: "\u00A0",
|
|
57
|
+
DOUBLE_LOW_9_QUOTE: "\u201E", // „
|
|
58
|
+
SINGLE_LOW_9_QUOTE: "\u201A", // ‚
|
|
59
|
+
LEFT_GUILLEMET: "\u00AB", // «
|
|
60
|
+
RIGHT_GUILLEMET: "\u00BB", // »
|
|
57
61
|
// Superscript ordinal suffixes
|
|
58
62
|
SUPERSCRIPT_ST: "\u02E2\u1D57", // ˢᵗ
|
|
59
63
|
SUPERSCRIPT_ND: "\u207F\u1D48", // ⁿᵈ
|
|
@@ -118,23 +122,28 @@ export const TERMINAL_PUNCTUATION = [
|
|
|
118
122
|
export const LATIN_LETTERS = "A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\u017F\\u0180-\\u024F"
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
|
-
* Default separator
|
|
122
|
-
* Uses Unicode Private Use Area
|
|
125
|
+
* Default separator for text spanning HTML elements.
|
|
126
|
+
* Uses a two-character sentinel from the Unicode Private Use Area
|
|
127
|
+
* (U+E000 U+E001) to avoid collisions with CJK fonts, Apple emoji
|
|
128
|
+
* internals, PDF-to-text output, and icon fonts that repurpose U+E000.
|
|
123
129
|
*/
|
|
124
|
-
export const DEFAULT_SEPARATOR = "\uE000"
|
|
130
|
+
export const DEFAULT_SEPARATOR = "\uE000\uE001"
|
|
125
131
|
export const ESCAPED_DEFAULT_SEPARATOR = escapeStringRegexp(DEFAULT_SEPARATOR)
|
|
126
132
|
|
|
127
133
|
/**
|
|
128
|
-
* Returns the regex-escaped separator string for the given options
|
|
129
|
-
*
|
|
134
|
+
* Returns the regex-escaped separator string for the given options,
|
|
135
|
+
* wrapped in a non-capturing group so that quantifiers like `?` and `*`
|
|
136
|
+
* apply to the entire separator sequence (critical for multi-character
|
|
137
|
+
* separators like the default U+E000 U+E001).
|
|
130
138
|
*
|
|
131
139
|
* @param options - Object with an optional `separator` field
|
|
132
|
-
* @returns Regex-escaped separator string
|
|
140
|
+
* @returns Regex-escaped separator string wrapped in `(?:...)`
|
|
133
141
|
*/
|
|
134
142
|
export function getEscapedSeparator(options: { separator?: string }): string {
|
|
135
|
-
|
|
143
|
+
const escaped = options.separator
|
|
136
144
|
? escapeStringRegexp(options.separator)
|
|
137
145
|
: ESCAPED_DEFAULT_SEPARATOR
|
|
146
|
+
return `(?:${escaped})`
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
/**
|
|
@@ -193,22 +202,28 @@ export const SPACE_CHARS = ` ${UNICODE_SYMBOLS.NBSP}`
|
|
|
193
202
|
* Creates a lookbehind pattern that matches after whitespace, separator, or start of string.
|
|
194
203
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
195
204
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
205
|
+
* Uses alternation instead of a character class so that multi-character
|
|
206
|
+
* separators are matched as a sequence, not as individual characters.
|
|
207
|
+
*
|
|
208
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
209
|
+
* @returns Pattern string: `(?<=\\s|${sep}|^)`
|
|
198
210
|
*/
|
|
199
211
|
export function spaceBoundaryStart(escapedSeparator: string): string {
|
|
200
|
-
return `(
|
|
212
|
+
return `(?<=\\s|${escapedSeparator}|^)`
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
/**
|
|
204
216
|
* Creates a lookahead pattern that matches before whitespace, separator, or end of string.
|
|
205
217
|
* Used for arrow patterns and other constructs that should appear at word boundaries.
|
|
206
218
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
219
|
+
* Uses alternation instead of a character class so that multi-character
|
|
220
|
+
* separators are matched as a sequence, not as individual characters.
|
|
221
|
+
*
|
|
222
|
+
* @param escapedSeparator - Regex-escaped separator string (may be grouped)
|
|
223
|
+
* @returns Pattern string: `(?=\\s|${sep}|$)`
|
|
209
224
|
*/
|
|
210
225
|
export function spaceBoundaryEnd(escapedSeparator: string): string {
|
|
211
|
-
return `(
|
|
226
|
+
return `(?=\\s|${escapedSeparator}|$)`
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
/**
|
package/src/dashes.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Dash transformation: hyphens → em-dashes, en-dashes, minus signs.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import escapeStringRegexp from "escape-string-regexp"
|
|
6
5
|
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, wordBoundaryStart, wordBoundaryEnd, getEscapedSeparator, cachedRegExp } from "./constants.js"
|
|
7
6
|
|
|
8
7
|
export type DashStyle = "american" | "british" | "none"
|
|
@@ -150,7 +149,7 @@ export function minusReplace(text: string, options: DashOptions = {}): string {
|
|
|
150
149
|
function convertParentheticalDashes(text: string, sep: string, style: DashStyle): string {
|
|
151
150
|
const localizedDash = style === "british" ? EN_DASH : EM_DASH
|
|
152
151
|
const maybeSpace = style === "british" ? " " : ""
|
|
153
|
-
const escapedSep =
|
|
152
|
+
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
154
153
|
|
|
155
154
|
// Convert spaced dashes: "word - word" or "word — word"
|
|
156
155
|
// When a separator follows the dash, preserve trailing spaces (they belong to the next text segment).
|
|
@@ -198,7 +197,7 @@ function convertParentheticalDashes(text: string, sep: string, style: DashStyle)
|
|
|
198
197
|
* allows a space after the dash: "Don't inter— Hey! Who threw that?"
|
|
199
198
|
*/
|
|
200
199
|
function normalizeEmDashSpacing(text: string, sep: string): string {
|
|
201
|
-
const escapedSep =
|
|
200
|
+
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
202
201
|
|
|
203
202
|
// Remove all spaces around em-dashes
|
|
204
203
|
text = text.replace(
|
package/src/index.ts
CHANGED
|
@@ -89,6 +89,10 @@ export interface TransformOptions {
|
|
|
89
89
|
* - `"british"`: Oxford style. Converts straight quotes to smart quotes,
|
|
90
90
|
* converts prime marks, and places periods/commas outside quotes.
|
|
91
91
|
* Example: "Hello". and "Hello",
|
|
92
|
+
* - `"german"`: German style. Uses low-9 quote characters: „..." and ‚...'
|
|
93
|
+
* (U+201E/U+201C double, U+201A/U+2018 single). Punctuation outside quotes.
|
|
94
|
+
* - `"french"`: French style. Uses guillemets with NBSP padding: «\u00A0...\u00A0»
|
|
95
|
+
* Single quotes remain as curly quotes. Punctuation outside quotes.
|
|
92
96
|
* - `"none"`: Skip all quote and punctuation transforms entirely.
|
|
93
97
|
* Straight quotes, apostrophes, and prime marks are left unmodified.
|
|
94
98
|
*
|
|
@@ -218,14 +222,19 @@ const defaultOpts: Required<Omit<TransformOptions, "separator">> = {
|
|
|
218
222
|
export function transform(text: string, options: TransformOptions = {}): string {
|
|
219
223
|
const separator = options.separator ?? DEFAULT_SEPARATOR
|
|
220
224
|
|
|
221
|
-
// Validate separator: must be
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
225
|
+
// Validate separator: must be non-empty and contain only BMP characters.
|
|
226
|
+
// for...of iterates by code point; surrogate pairs (non-BMP) yield a
|
|
227
|
+
// two-UTF16-unit string, so ch.length > 1 detects them.
|
|
228
|
+
if (separator.length === 0) {
|
|
229
|
+
throw new Error("Invalid separator: must not be empty.")
|
|
230
|
+
}
|
|
231
|
+
for (const ch of separator) {
|
|
232
|
+
if (ch.length > 1) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Invalid separator: must contain only BMP characters (no characters outside the Basic Multilingual Plane). ` +
|
|
235
|
+
`Received "${separator}" which contains a non-BMP character.`
|
|
236
|
+
)
|
|
237
|
+
}
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
const original = text
|
package/src/nbsp.ts
CHANGED
|
@@ -135,8 +135,9 @@ export function nbspBetweenNumberAndUnit(text: string, options: NbspOptions = {}
|
|
|
135
135
|
*/
|
|
136
136
|
export function nbspBeforeLastWord(text: string, options: NbspOptions = {}): string {
|
|
137
137
|
const sep = getEscapedSeparator(options)
|
|
138
|
+
// Use alternation instead of character classes for multi-char separator support
|
|
138
139
|
const pattern = cachedRegExp(
|
|
139
|
-
`(
|
|
140
|
+
`(?<=\\w|${sep})${SPACE}(?<lastWord>(?:(?!\\s)(?!${sep}).){1,10})(?<ending>${sep}?(?:\\n\\n|$))`,
|
|
140
141
|
"g"
|
|
141
142
|
)
|
|
142
143
|
return text.replace(pattern, `${NBSP}$<lastWord>$<ending>`)
|
package/src/quotes.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import escapeStringRegexp from "escape-string-regexp"
|
|
6
|
-
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, TERMINAL_PUNCTUATION, cachedRegExp } from "./constants.js"
|
|
6
|
+
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, TERMINAL_PUNCTUATION, getEscapedSeparator, cachedRegExp } from "./constants.js"
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
EM_DASH,
|
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
/** Joined string of all terminal punctuation characters for use in regex character classes. */
|
|
18
18
|
const TERMINAL_PUNCTUATION_CLASS = TERMINAL_PUNCTUATION.join("")
|
|
19
19
|
|
|
20
|
-
export type PunctuationStyle = "american" | "british" | "none"
|
|
20
|
+
export type PunctuationStyle = "american" | "british" | "german" | "french" | "none"
|
|
21
21
|
|
|
22
22
|
export interface QuoteOptions {
|
|
23
23
|
/** Boundary marker for HTML element boundaries. Default: "\uE000" */
|
|
@@ -28,7 +28,7 @@ export interface QuoteOptions {
|
|
|
28
28
|
|
|
29
29
|
/** Convert straight single quotes to curly quotes and apostrophes */
|
|
30
30
|
function convertSingleQuotes(text: string, sep: string): string {
|
|
31
|
-
const escapedSep =
|
|
31
|
+
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
32
32
|
|
|
33
33
|
// Handle empty single quotes '' and whitespace-only quotes ' ' first
|
|
34
34
|
// Only match straight quotes, not already-converted curly quotes
|
|
@@ -96,7 +96,9 @@ function convertUnmatchedPluralPossessives(text: string, sep: string): string {
|
|
|
96
96
|
return match
|
|
97
97
|
}
|
|
98
98
|
let i = offset - 1
|
|
99
|
-
while (i >=
|
|
99
|
+
while (i >= sep.length - 1 && text.startsWith(sep, i - sep.length + 1)) {
|
|
100
|
+
i -= sep.length
|
|
101
|
+
}
|
|
100
102
|
if (i >= 0 && (text[i] === "s" || text[i] === "S")) {
|
|
101
103
|
return MODIFIER_LETTER_APOSTROPHE
|
|
102
104
|
}
|
|
@@ -107,7 +109,8 @@ function convertUnmatchedPluralPossessives(text: string, sep: string): string {
|
|
|
107
109
|
|
|
108
110
|
/** Convert straight double quotes to curly quotes */
|
|
109
111
|
function convertDoubleQuotes(text: string, sep: string): string {
|
|
110
|
-
const
|
|
112
|
+
const rawEscSep = escapeStringRegexp(sep)
|
|
113
|
+
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
111
114
|
|
|
112
115
|
// Handle empty quotes "" first - match only when not part of adjacent quotes
|
|
113
116
|
// Require word boundary or start/end of string on at least one side
|
|
@@ -116,7 +119,7 @@ function convertDoubleQuotes(text: string, sep: string): string {
|
|
|
116
119
|
text = text.replace(/(?<=^|[\s([{])"(?<whitespace>\s+)"(?=$|[\s)\]}.!?,;:])/g, `${LEFT_DOUBLE_QUOTE}$<whitespace>${RIGHT_DOUBLE_QUOTE}`)
|
|
117
120
|
|
|
118
121
|
const beginningDouble = cachedRegExp(
|
|
119
|
-
`(?<=^|[\\s\\(\\/\\[\\{\\-${EM_DASH}
|
|
122
|
+
`(?<=^|[\\s\\(\\/\\[\\{\\-${EM_DASH}]|${escapedSep})(?<beforeChr>${escapedSep}?)["](?<afterChr>(?<sepWithPunct>${escapedSep}[ .,])|(?=${escapedSep}?\\.{3}|${escapedSep}?[^\\s\\)${EM_DASH},!?;:.\\}${rawEscSep}]))`,
|
|
120
123
|
"gm"
|
|
121
124
|
)
|
|
122
125
|
text = text.replace(beginningDouble, `$<beforeChr>${LEFT_DOUBLE_QUOTE}$<afterChr>`)
|
|
@@ -134,7 +137,7 @@ function convertDoubleQuotes(text: string, sep: string): string {
|
|
|
134
137
|
|
|
135
138
|
/** Apply American or British punctuation style */
|
|
136
139
|
function applyPunctuationStyle(text: string, sep: string, style: PunctuationStyle): string {
|
|
137
|
-
const escapedSep =
|
|
140
|
+
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
138
141
|
|
|
139
142
|
if (style === "american") {
|
|
140
143
|
// Period outside → inside: "Hello". → "Hello."
|
|
@@ -150,7 +153,7 @@ function applyPunctuationStyle(text: string, sep: string, style: PunctuationStyl
|
|
|
150
153
|
"g"
|
|
151
154
|
)
|
|
152
155
|
text = text.replace(commaOutsideRegex, "$<sepBefore>,$<quote>$<sepAfter>")
|
|
153
|
-
} else if (style === "british") {
|
|
156
|
+
} else if (style === "british" || style === "german" || style === "french") {
|
|
154
157
|
// Period inside → outside: "Hello." → "Hello".
|
|
155
158
|
// No terminal punctuation guard — "Stop!." inside is always wrong; move the period out.
|
|
156
159
|
const periodInsideRegex = cachedRegExp(
|
|
@@ -169,16 +172,84 @@ function applyPunctuationStyle(text: string, sep: string, style: PunctuationStyl
|
|
|
169
172
|
return text
|
|
170
173
|
}
|
|
171
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Normalize German quotes back to American for idempotent re-processing.
|
|
177
|
+
* Tracks „..." (U+201E/U+201C) and ‚...' (U+201A/U+2018) pairs so that
|
|
178
|
+
* the ambiguous closer characters are mapped to the correct American equivalents.
|
|
179
|
+
*/
|
|
180
|
+
function normalizeGermanQuotes(text: string): string {
|
|
181
|
+
const DLQ = UNICODE_SYMBOLS.DOUBLE_LOW_9_QUOTE
|
|
182
|
+
const SLQ = UNICODE_SYMBOLS.SINGLE_LOW_9_QUOTE
|
|
183
|
+
|
|
184
|
+
let result = ""
|
|
185
|
+
let doubleDepth = 0
|
|
186
|
+
let singleDepth = 0
|
|
187
|
+
|
|
188
|
+
for (const ch of text) {
|
|
189
|
+
if (ch === DLQ) {
|
|
190
|
+
result += LEFT_DOUBLE_QUOTE
|
|
191
|
+
doubleDepth++
|
|
192
|
+
} else if (ch === LEFT_DOUBLE_QUOTE && doubleDepth > 0) {
|
|
193
|
+
result += RIGHT_DOUBLE_QUOTE
|
|
194
|
+
doubleDepth--
|
|
195
|
+
} else if (ch === SLQ) {
|
|
196
|
+
result += LEFT_SINGLE_QUOTE
|
|
197
|
+
singleDepth++
|
|
198
|
+
} else if (ch === LEFT_SINGLE_QUOTE && singleDepth > 0) {
|
|
199
|
+
result += RIGHT_SINGLE_QUOTE
|
|
200
|
+
singleDepth--
|
|
201
|
+
} else {
|
|
202
|
+
result += ch
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Remap American curly quotes to German low-9 style. Safe because apostrophes are still MLA at this stage. */
|
|
210
|
+
function applyGermanQuotes(text: string): string {
|
|
211
|
+
return text
|
|
212
|
+
.replaceAll(LEFT_DOUBLE_QUOTE, UNICODE_SYMBOLS.DOUBLE_LOW_9_QUOTE)
|
|
213
|
+
.replaceAll(RIGHT_DOUBLE_QUOTE, LEFT_DOUBLE_QUOTE)
|
|
214
|
+
.replaceAll(LEFT_SINGLE_QUOTE, UNICODE_SYMBOLS.SINGLE_LOW_9_QUOTE)
|
|
215
|
+
.replaceAll(RIGHT_SINGLE_QUOTE, LEFT_SINGLE_QUOTE)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Normalize French guillemets back to American for idempotent re-processing. */
|
|
219
|
+
function normalizeFrenchQuotes(text: string): string {
|
|
220
|
+
return text
|
|
221
|
+
.replace(cachedRegExp(`${UNICODE_SYMBOLS.LEFT_GUILLEMET}${UNICODE_SYMBOLS.NBSP}?`, "g"), LEFT_DOUBLE_QUOTE)
|
|
222
|
+
.replace(cachedRegExp(`${UNICODE_SYMBOLS.NBSP}?${UNICODE_SYMBOLS.RIGHT_GUILLEMET}`, "g"), RIGHT_DOUBLE_QUOTE)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Remap American curly double quotes to French guillemets with NBSP padding. */
|
|
226
|
+
function applyFrenchQuotes(text: string): string {
|
|
227
|
+
return text
|
|
228
|
+
.replaceAll(LEFT_DOUBLE_QUOTE, `${UNICODE_SYMBOLS.LEFT_GUILLEMET}${UNICODE_SYMBOLS.NBSP}`)
|
|
229
|
+
.replaceAll(RIGHT_DOUBLE_QUOTE, `${UNICODE_SYMBOLS.NBSP}${UNICODE_SYMBOLS.RIGHT_GUILLEMET}`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Locale-specific normalize (pre-pipeline) and apply (post-pipeline) functions. */
|
|
233
|
+
const localeQuoteTransforms: Partial<Record<PunctuationStyle, { normalize: (t: string) => string; apply: (t: string) => string }>> = {
|
|
234
|
+
german: { normalize: normalizeGermanQuotes, apply: applyGermanQuotes },
|
|
235
|
+
french: { normalize: normalizeFrenchQuotes, apply: applyFrenchQuotes },
|
|
236
|
+
}
|
|
237
|
+
|
|
172
238
|
/** Shared quote-processing pipeline. Returns text with MLA for apostrophes. */
|
|
173
239
|
function processQuotes(text: string, options: QuoteOptions): string {
|
|
174
240
|
const sep = options.separator ?? DEFAULT_SEPARATOR
|
|
175
241
|
const punctuationStyle = options.punctuationStyle ?? "american"
|
|
176
242
|
if (punctuationStyle === "none") return text
|
|
177
243
|
|
|
244
|
+
const locale = localeQuoteTransforms[punctuationStyle]
|
|
245
|
+
if (locale) text = locale.normalize(text)
|
|
246
|
+
|
|
178
247
|
text = convertSingleQuotes(text, sep)
|
|
179
248
|
text = convertDoubleQuotes(text, sep)
|
|
180
249
|
text = applyPunctuationStyle(text, sep, punctuationStyle)
|
|
181
250
|
|
|
251
|
+
if (locale) text = locale.apply(text)
|
|
252
|
+
|
|
182
253
|
return text
|
|
183
254
|
}
|
|
184
255
|
|
package/src/tests/index.test.ts
CHANGED
|
@@ -93,6 +93,31 @@ describe("transform", () => {
|
|
|
93
93
|
})
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
describe("german and french locale styles", () => {
|
|
97
|
+
it.each([
|
|
98
|
+
['"Guten Tag"', `${UNICODE_SYMBOLS.DOUBLE_LOW_9_QUOTE}Guten Tag${LEFT_DOUBLE_QUOTE}`, { punctuationStyle: "german" as const, nbsp: false }],
|
|
99
|
+
['"Bonjour"', `${UNICODE_SYMBOLS.LEFT_GUILLEMET}${NBSP}Bonjour${NBSP}${UNICODE_SYMBOLS.RIGHT_GUILLEMET}`, { punctuationStyle: "french" as const, nbsp: false }],
|
|
100
|
+
["it's", `it${RIGHT_SINGLE_QUOTE}s`, { punctuationStyle: "german" as const, nbsp: false }],
|
|
101
|
+
["l'homme", `l${RIGHT_SINGLE_QUOTE}homme`, { punctuationStyle: "french" as const, nbsp: false }],
|
|
102
|
+
])('transforms "%s" with locale style', (input, expected, options) => {
|
|
103
|
+
expect(transform(input, options)).toBe(expected)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it("german style is idempotent", () => {
|
|
107
|
+
const input = '"Guten Tag"'
|
|
108
|
+
const first = transform(input, { punctuationStyle: "german", nbsp: false })
|
|
109
|
+
const second = transform(first, { punctuationStyle: "german", nbsp: false })
|
|
110
|
+
expect(second).toBe(first)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("french style is idempotent", () => {
|
|
114
|
+
const input = '"Bonjour"'
|
|
115
|
+
const first = transform(input, { punctuationStyle: "french", nbsp: false })
|
|
116
|
+
const second = transform(first, { punctuationStyle: "french", nbsp: false })
|
|
117
|
+
expect(second).toBe(first)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
96
121
|
describe("collapseSpaces option", () => {
|
|
97
122
|
it.each([
|
|
98
123
|
["hello world", "hello world", "multiple spaces"],
|
|
@@ -533,18 +558,17 @@ describe("transform", () => {
|
|
|
533
558
|
|
|
534
559
|
describe("separator validation", () => {
|
|
535
560
|
const invalidSeparators = [
|
|
536
|
-
["🎉", "emoji (
|
|
537
|
-
["ab", "multi-character"],
|
|
561
|
+
["🎉", "emoji (non-BMP)"],
|
|
538
562
|
["", "empty string"],
|
|
539
563
|
] as const
|
|
540
564
|
|
|
541
565
|
it.each(invalidSeparators)("rejects %s separator (%s)", (sep) => {
|
|
542
566
|
expect(() => transform('"Hello"', { separator: sep, nbsp: false })).toThrow(
|
|
543
|
-
/Invalid separator
|
|
567
|
+
/Invalid separator/
|
|
544
568
|
)
|
|
545
569
|
})
|
|
546
570
|
|
|
547
|
-
const validSeparators = ["\uE000", "|", "\u2603"] // Default, pipe, snowman
|
|
571
|
+
const validSeparators = ["\uE000\uE001", "\uE000", "|", "\u2603", "ab"] // Default, single PUA, pipe, snowman, multi-char
|
|
548
572
|
|
|
549
573
|
it.each(validSeparators)("accepts '%s' as separator", (sep) => {
|
|
550
574
|
expect(() => transform('"Hello"', { separator: sep, nbsp: false })).not.toThrow()
|
|
@@ -559,6 +583,25 @@ describe("transform", () => {
|
|
|
559
583
|
})
|
|
560
584
|
})
|
|
561
585
|
|
|
586
|
+
describe("multi-character separator behavior", () => {
|
|
587
|
+
const sep = "\uE000\uE001"
|
|
588
|
+
|
|
589
|
+
it.each([
|
|
590
|
+
["quotes with separator at boundary", `${sep}"Hello"`, `${sep}${LEFT_DOUBLE_QUOTE}Hello${RIGHT_DOUBLE_QUOTE}`],
|
|
591
|
+
["em-dash with adjacent separators", `word${sep} - ${sep}word`, `word${sep}${EM_DASH}${sep}word`],
|
|
592
|
+
["ellipsis with separator between dots", `a.${sep}.${sep}.`, `a\u2026${sep}${sep}`],
|
|
593
|
+
["nbsp before last word with trailing separator", `Hello world${sep}`, `Hello${NBSP}world${sep}`],
|
|
594
|
+
])("%s", (_desc, input, expected) => {
|
|
595
|
+
expect(transform(input, { separator: sep, nbsp: true })).toBe(expected)
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
it("preserves separator count through transform", () => {
|
|
599
|
+
const input = `${sep}"Hello"${sep} -- ${sep}world${sep}`
|
|
600
|
+
const result = transform(input, { separator: sep })
|
|
601
|
+
expect(countSeparators(result, sep)).toBe(countSeparators(input, sep))
|
|
602
|
+
})
|
|
603
|
+
})
|
|
604
|
+
|
|
562
605
|
describe("Unicode preservation", () => {
|
|
563
606
|
it.each([
|
|
564
607
|
["emoji", '"Hello 👋 World"', `${LEFT_DOUBLE_QUOTE}Hello 👋 World${RIGHT_DOUBLE_QUOTE}`],
|
package/src/tests/quotes.test.ts
CHANGED
|
@@ -9,6 +9,11 @@ const {
|
|
|
9
9
|
MODIFIER_LETTER_APOSTROPHE,
|
|
10
10
|
EM_DASH,
|
|
11
11
|
ELLIPSIS,
|
|
12
|
+
DOUBLE_LOW_9_QUOTE,
|
|
13
|
+
SINGLE_LOW_9_QUOTE,
|
|
14
|
+
LEFT_GUILLEMET,
|
|
15
|
+
RIGHT_GUILLEMET,
|
|
16
|
+
NBSP,
|
|
12
17
|
} = UNICODE_SYMBOLS
|
|
13
18
|
|
|
14
19
|
describe("niceQuotes", () => {
|
|
@@ -631,6 +636,38 @@ describe("niceQuotes", () => {
|
|
|
631
636
|
})
|
|
632
637
|
})
|
|
633
638
|
|
|
639
|
+
describe("locale quotes", () => {
|
|
640
|
+
describe.each([
|
|
641
|
+
// German double quotes
|
|
642
|
+
['"Guten Tag"', `${DOUBLE_LOW_9_QUOTE}Guten Tag${LEFT_DOUBLE_QUOTE}`, { punctuationStyle: "german" as const }],
|
|
643
|
+
['"Hello," she said.', `${DOUBLE_LOW_9_QUOTE}Hello${LEFT_DOUBLE_QUOTE}, she said.`, { punctuationStyle: "german" as const }],
|
|
644
|
+
// German single quotes
|
|
645
|
+
["'Hallo'", `${SINGLE_LOW_9_QUOTE}Hallo${LEFT_SINGLE_QUOTE}`, { punctuationStyle: "german" as const }],
|
|
646
|
+
// German apostrophes stay as RSQ (after MLA→RSQ conversion in niceQuotes)
|
|
647
|
+
["it's", `it${RIGHT_SINGLE_QUOTE}s`, { punctuationStyle: "german" as const }],
|
|
648
|
+
// French double quotes
|
|
649
|
+
['"Bonjour"', `${LEFT_GUILLEMET}${NBSP}Bonjour${NBSP}${RIGHT_GUILLEMET}`, { punctuationStyle: "french" as const }],
|
|
650
|
+
// French apostrophes stay as RSQ
|
|
651
|
+
["l'homme", `l${RIGHT_SINGLE_QUOTE}homme`, { punctuationStyle: "french" as const }],
|
|
652
|
+
// Nested quotes in German
|
|
653
|
+
[`"She said 'hello'"`, `${DOUBLE_LOW_9_QUOTE}She said ${SINGLE_LOW_9_QUOTE}hello${LEFT_SINGLE_QUOTE}${LEFT_DOUBLE_QUOTE}`, { punctuationStyle: "german" as const }],
|
|
654
|
+
// French punctuation placement
|
|
655
|
+
['"Bonjour," dit-il.', `${LEFT_GUILLEMET}${NBSP}Bonjour${NBSP}${RIGHT_GUILLEMET}, dit-il.`, { punctuationStyle: "french" as const }],
|
|
656
|
+
])("locale quotes: %s → %s", (input, expected, options) => {
|
|
657
|
+
it("transforms correctly", () => {
|
|
658
|
+
expect(niceQuotes(input, options)).toBe(expected)
|
|
659
|
+
})
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it.each([
|
|
663
|
+
[`${DOUBLE_LOW_9_QUOTE}Guten Tag${LEFT_DOUBLE_QUOTE}`, { punctuationStyle: "german" as const }],
|
|
664
|
+
[`${SINGLE_LOW_9_QUOTE}Hallo${LEFT_SINGLE_QUOTE}`, { punctuationStyle: "german" as const }],
|
|
665
|
+
[`${LEFT_GUILLEMET}${NBSP}Bonjour${NBSP}${RIGHT_GUILLEMET}`, { punctuationStyle: "french" as const }],
|
|
666
|
+
])("locale output is idempotent: %s", (input, options) => {
|
|
667
|
+
expect(niceQuotes(input, options)).toBe(input)
|
|
668
|
+
})
|
|
669
|
+
})
|
|
670
|
+
|
|
634
671
|
describe("pathological inputs", () => {
|
|
635
672
|
it("handles 1500-char input without closing quote", () => {
|
|
636
673
|
const input = `'${"a".repeat(1500)}`
|
package/src/tests/utils.test.ts
CHANGED
|
@@ -10,9 +10,9 @@ describe("assertSeparatorAbsent", () => {
|
|
|
10
10
|
})
|
|
11
11
|
|
|
12
12
|
it.each([
|
|
13
|
-
["default separator", [`hello${DEFAULT_SEPARATOR}world`], DEFAULT_SEPARATOR, /U\+E000/],
|
|
13
|
+
["default separator", [`hello${DEFAULT_SEPARATOR}world`], DEFAULT_SEPARATOR, /U\+E000 U\+E001/],
|
|
14
14
|
["custom separator", ["hello|world"], "|", /U\+007C/],
|
|
15
|
-
["separator in middle element", ["clean", `has${DEFAULT_SEPARATOR}sep`, "also clean"], DEFAULT_SEPARATOR, /separator
|
|
15
|
+
["separator in middle element", ["clean", `has${DEFAULT_SEPARATOR}sep`, "also clean"], DEFAULT_SEPARATOR, /separator sequence/],
|
|
16
16
|
])("throws: %s", (_desc, values, separator, expectedPattern) => {
|
|
17
17
|
expect(() => assertSeparatorAbsent(values, separator)).toThrow(expectedPattern)
|
|
18
18
|
})
|
package/src/utils.ts
CHANGED
|
@@ -48,10 +48,12 @@ export function assertSeparatorAbsent(textValues: string[], separator: string):
|
|
|
48
48
|
for (const value of textValues) {
|
|
49
49
|
if (!value.includes(separator)) continue
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const codePoints = [...separator]
|
|
52
|
+
.map(ch => `U+${ch.codePointAt(0)!.toString(16).toUpperCase().padStart(4, "0")}`)
|
|
53
|
+
.join(" ")
|
|
52
54
|
throw new Error(
|
|
53
|
-
`Text contains the separator
|
|
54
|
-
`to track element boundaries. Pass a different
|
|
55
|
+
`Text contains the separator sequence ${codePoints} which is used internally by punctilio ` +
|
|
56
|
+
`to track element boundaries. Pass a different separator via the "separator" option.\n` +
|
|
55
57
|
`Text: ${formatErrorString(value, "input")}`
|
|
56
58
|
)
|
|
57
59
|
}
|
|
@@ -59,8 +61,10 @@ export function assertSeparatorAbsent(textValues: string[], separator: string):
|
|
|
59
61
|
|
|
60
62
|
export function countSeparators(text: string, separator: string = DEFAULT_SEPARATOR): number {
|
|
61
63
|
let count = 0
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
let index = 0
|
|
65
|
+
while ((index = text.indexOf(separator, index)) !== -1) {
|
|
66
|
+
count++
|
|
67
|
+
index += separator.length
|
|
64
68
|
}
|
|
65
69
|
return count
|
|
66
70
|
}
|