text-sanctifier 1.0.18 → 1.0.19
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/ADDITIONAL_TERMS.md +122 -0
- package/LICENSE +694 -0
- package/README.md +163 -167
- package/package.json +65 -63
- package/robots.txt +79 -0
- package/src/index.js +12 -12
- package/src/inspectText.js +174 -174
- package/src/sanctifyText.js +507 -507
- package/LICENSE.md +0 -285
package/src/inspectText.js
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
// ./src/inspectText.js
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
CONTROL_CHARS_REGEX,
|
|
5
|
-
INVISIBLE_TRASH_REGEX,
|
|
6
|
-
EMOJI_REGEX,
|
|
7
|
-
ASCII_KEYBOARD_SAFE_REGEX,
|
|
8
|
-
normalizeTypographicJank,
|
|
9
|
-
} from './sanctifyText.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {Object} UnicodeTrashReport
|
|
14
|
-
* @property {boolean} hasControlChars
|
|
15
|
-
* @property {boolean} hasInvisibleChars
|
|
16
|
-
* @property {boolean} hasMixedNewlines
|
|
17
|
-
* @property {'LF'|'CRLF'|'CR'|'Mixed'|null} newlineStyle
|
|
18
|
-
* @property {boolean} hasEmojis
|
|
19
|
-
* @property {boolean} hasNonKeyboardChars
|
|
20
|
-
* @property {string[]} summary
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Safe `.test()` for global/sticky regexes.
|
|
26
|
-
* Global regexes mutate `lastIndex`, which makes `.test()` unreliable across calls.
|
|
27
|
-
* @param {RegExp} re
|
|
28
|
-
* @param {string} s
|
|
29
|
-
*/
|
|
30
|
-
function stableTest(re, s) {
|
|
31
|
-
re.lastIndex = 0;
|
|
32
|
-
return re.test(s);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Returns true if text contains any non-keyboard characters (excluding emojis),
|
|
37
|
-
* after typographic normalization.
|
|
38
|
-
*
|
|
39
|
-
* "Keyboard" here means:
|
|
40
|
-
* - Printable ASCII (0x20–0x7E)
|
|
41
|
-
* - CR/LF
|
|
42
|
-
* - Emojis (per EMOJI_REGEX)
|
|
43
|
-
*
|
|
44
|
-
* @param {string} text
|
|
45
|
-
* @returns {boolean}
|
|
46
|
-
*/
|
|
47
|
-
function hasNonKeyboardCharsExcludingEmoji(text) {
|
|
48
|
-
const normalized = normalizeTypographicJank(text);
|
|
49
|
-
|
|
50
|
-
// Fast path: no non-ascii runs => no non-keyboard chars
|
|
51
|
-
if (!stableTest(ASCII_KEYBOARD_SAFE_REGEX, normalized)) return false;
|
|
52
|
-
|
|
53
|
-
// Walk code points so we can distinguish emoji from other non-ascii safely
|
|
54
|
-
for (const ch of normalized) {
|
|
55
|
-
// allowed: ASCII printable + newlines
|
|
56
|
-
if (ch === '\n' || ch === '\r' || (ch >= ' ' && ch <= '~')) continue;
|
|
57
|
-
|
|
58
|
-
// allowed: emoji
|
|
59
|
-
if (stableTest(EMOJI_REGEX, ch)) continue;
|
|
60
|
-
|
|
61
|
-
// anything else non-ascii is "non-keyboard"
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Detects textual "trash" or anomalies in a given string.
|
|
70
|
-
* @param {string} text @returns {UnicodeTrashReport}
|
|
71
|
-
*/
|
|
72
|
-
export function inspectText(text) {
|
|
73
|
-
if (typeof text !== 'string') {
|
|
74
|
-
throw new TypeError('inspectText expects a string input.');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const summary = [];
|
|
78
|
-
const report = {
|
|
79
|
-
hasControlChars: false,
|
|
80
|
-
hasInvisibleChars: false,
|
|
81
|
-
hasMixedNewlines: false,
|
|
82
|
-
newlineStyle: null,
|
|
83
|
-
hasEmojis: false,
|
|
84
|
-
hasNonKeyboardChars: false,
|
|
85
|
-
summary,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const flag = (condition, key, message) => {
|
|
89
|
-
if (condition) {
|
|
90
|
-
report[key] = true;
|
|
91
|
-
summary.push(message);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// === Pattern Checks ===
|
|
96
|
-
flag(stableTest(CONTROL_CHARS_REGEX, text), 'hasControlChars', 'Control characters detected.');
|
|
97
|
-
flag(stableTest(INVISIBLE_TRASH_REGEX, text), 'hasInvisibleChars', 'Invisible Unicode characters detected.');
|
|
98
|
-
flag(stableTest(EMOJI_REGEX, text), 'hasEmojis', 'Emojis detected.');
|
|
99
|
-
|
|
100
|
-
// === Newline Analysis ===
|
|
101
|
-
const { mixed, types } = getNewlineStats(text);
|
|
102
|
-
report.hasMixedNewlines = mixed;
|
|
103
|
-
report.newlineStyle = mixed ? 'Mixed' : types[0] || null;
|
|
104
|
-
|
|
105
|
-
if (report.newlineStyle) {
|
|
106
|
-
summary.push(
|
|
107
|
-
mixed ? 'Mixed newline styles detected.' : `Consistent newline style: ${report.newlineStyle}`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// === Non-keyboard characters (excluding emojis) ===
|
|
112
|
-
flag(
|
|
113
|
-
hasNonKeyboardCharsExcludingEmoji(text),
|
|
114
|
-
'hasNonKeyboardChars',
|
|
115
|
-
'Non-keyboard characters detected.'
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return report;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Counts the number of different newline types in a string.
|
|
123
|
-
* @param {string} text
|
|
124
|
-
* @returns {{
|
|
125
|
-
* crlf: number,
|
|
126
|
-
* cr: number,
|
|
127
|
-
* lf: number,
|
|
128
|
-
* types: string[],
|
|
129
|
-
* mixed: boolean
|
|
130
|
-
* }}
|
|
131
|
-
*/
|
|
132
|
-
export function getNewlineStats(text) {
|
|
133
|
-
if (typeof text !== 'string') {
|
|
134
|
-
throw new TypeError('getNewlineStats expects a string input.');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const crlfMatches = text.match(/\r\n/g) || [];
|
|
138
|
-
const textWithoutCRLF = text.replace(/\r\n/g, '');
|
|
139
|
-
|
|
140
|
-
const crMatches = textWithoutCRLF.match(/\r/g) || [];
|
|
141
|
-
const lfMatches = textWithoutCRLF.match(/\n/g) || [];
|
|
142
|
-
|
|
143
|
-
const count = {
|
|
144
|
-
crlf: crlfMatches.length,
|
|
145
|
-
cr: crMatches.length,
|
|
146
|
-
lf: lfMatches.length,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const types = [];
|
|
150
|
-
if (count.crlf > 0) types.push('CRLF');
|
|
151
|
-
if (count.cr > 0) types.push('CR');
|
|
152
|
-
if (count.lf > 0) types.push('LF');
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
...count,
|
|
156
|
-
types,
|
|
157
|
-
mixed: types.length > 1,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Creates defaultOptions for summonSanctifier based on inspectText result
|
|
163
|
-
* @param {!UnicodeTrashReport} report
|
|
164
|
-
* @return {!SanctifyOptions}
|
|
165
|
-
*/
|
|
166
|
-
export function getRecommendedSanctifierOptions(report) {
|
|
167
|
-
return {
|
|
168
|
-
purgeInvisibleChars: report.hasInvisibleChars,
|
|
169
|
-
purgeEmojis: report.hasEmojis,
|
|
170
|
-
nukeControls: report.hasControlChars,
|
|
171
|
-
keyboardOnlyFilter: report.hasNonKeyboardChars,
|
|
172
|
-
normalizeNewlines:
|
|
173
|
-
report.hasMixedNewlines || report.newlineStyle === 'CRLF' || report.newlineStyle === 'CR',
|
|
174
|
-
};
|
|
1
|
+
// ./src/inspectText.js
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CONTROL_CHARS_REGEX,
|
|
5
|
+
INVISIBLE_TRASH_REGEX,
|
|
6
|
+
EMOJI_REGEX,
|
|
7
|
+
ASCII_KEYBOARD_SAFE_REGEX,
|
|
8
|
+
normalizeTypographicJank,
|
|
9
|
+
} from './sanctifyText.js';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} UnicodeTrashReport
|
|
14
|
+
* @property {boolean} hasControlChars
|
|
15
|
+
* @property {boolean} hasInvisibleChars
|
|
16
|
+
* @property {boolean} hasMixedNewlines
|
|
17
|
+
* @property {'LF'|'CRLF'|'CR'|'Mixed'|null} newlineStyle
|
|
18
|
+
* @property {boolean} hasEmojis
|
|
19
|
+
* @property {boolean} hasNonKeyboardChars
|
|
20
|
+
* @property {string[]} summary
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Safe `.test()` for global/sticky regexes.
|
|
26
|
+
* Global regexes mutate `lastIndex`, which makes `.test()` unreliable across calls.
|
|
27
|
+
* @param {RegExp} re
|
|
28
|
+
* @param {string} s
|
|
29
|
+
*/
|
|
30
|
+
function stableTest(re, s) {
|
|
31
|
+
re.lastIndex = 0;
|
|
32
|
+
return re.test(s);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if text contains any non-keyboard characters (excluding emojis),
|
|
37
|
+
* after typographic normalization.
|
|
38
|
+
*
|
|
39
|
+
* "Keyboard" here means:
|
|
40
|
+
* - Printable ASCII (0x20–0x7E)
|
|
41
|
+
* - CR/LF
|
|
42
|
+
* - Emojis (per EMOJI_REGEX)
|
|
43
|
+
*
|
|
44
|
+
* @param {string} text
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
function hasNonKeyboardCharsExcludingEmoji(text) {
|
|
48
|
+
const normalized = normalizeTypographicJank(text);
|
|
49
|
+
|
|
50
|
+
// Fast path: no non-ascii runs => no non-keyboard chars
|
|
51
|
+
if (!stableTest(ASCII_KEYBOARD_SAFE_REGEX, normalized)) return false;
|
|
52
|
+
|
|
53
|
+
// Walk code points so we can distinguish emoji from other non-ascii safely
|
|
54
|
+
for (const ch of normalized) {
|
|
55
|
+
// allowed: ASCII printable + newlines
|
|
56
|
+
if (ch === '\n' || ch === '\r' || (ch >= ' ' && ch <= '~')) continue;
|
|
57
|
+
|
|
58
|
+
// allowed: emoji
|
|
59
|
+
if (stableTest(EMOJI_REGEX, ch)) continue;
|
|
60
|
+
|
|
61
|
+
// anything else non-ascii is "non-keyboard"
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detects textual "trash" or anomalies in a given string.
|
|
70
|
+
* @param {string} text @returns {UnicodeTrashReport}
|
|
71
|
+
*/
|
|
72
|
+
export function inspectText(text) {
|
|
73
|
+
if (typeof text !== 'string') {
|
|
74
|
+
throw new TypeError('inspectText expects a string input.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const summary = [];
|
|
78
|
+
const report = {
|
|
79
|
+
hasControlChars: false,
|
|
80
|
+
hasInvisibleChars: false,
|
|
81
|
+
hasMixedNewlines: false,
|
|
82
|
+
newlineStyle: null,
|
|
83
|
+
hasEmojis: false,
|
|
84
|
+
hasNonKeyboardChars: false,
|
|
85
|
+
summary,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const flag = (condition, key, message) => {
|
|
89
|
+
if (condition) {
|
|
90
|
+
report[key] = true;
|
|
91
|
+
summary.push(message);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// === Pattern Checks ===
|
|
96
|
+
flag(stableTest(CONTROL_CHARS_REGEX, text), 'hasControlChars', 'Control characters detected.');
|
|
97
|
+
flag(stableTest(INVISIBLE_TRASH_REGEX, text), 'hasInvisibleChars', 'Invisible Unicode characters detected.');
|
|
98
|
+
flag(stableTest(EMOJI_REGEX, text), 'hasEmojis', 'Emojis detected.');
|
|
99
|
+
|
|
100
|
+
// === Newline Analysis ===
|
|
101
|
+
const { mixed, types } = getNewlineStats(text);
|
|
102
|
+
report.hasMixedNewlines = mixed;
|
|
103
|
+
report.newlineStyle = mixed ? 'Mixed' : types[0] || null;
|
|
104
|
+
|
|
105
|
+
if (report.newlineStyle) {
|
|
106
|
+
summary.push(
|
|
107
|
+
mixed ? 'Mixed newline styles detected.' : `Consistent newline style: ${report.newlineStyle}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// === Non-keyboard characters (excluding emojis) ===
|
|
112
|
+
flag(
|
|
113
|
+
hasNonKeyboardCharsExcludingEmoji(text),
|
|
114
|
+
'hasNonKeyboardChars',
|
|
115
|
+
'Non-keyboard characters detected.'
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return report;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Counts the number of different newline types in a string.
|
|
123
|
+
* @param {string} text
|
|
124
|
+
* @returns {{
|
|
125
|
+
* crlf: number,
|
|
126
|
+
* cr: number,
|
|
127
|
+
* lf: number,
|
|
128
|
+
* types: string[],
|
|
129
|
+
* mixed: boolean
|
|
130
|
+
* }}
|
|
131
|
+
*/
|
|
132
|
+
export function getNewlineStats(text) {
|
|
133
|
+
if (typeof text !== 'string') {
|
|
134
|
+
throw new TypeError('getNewlineStats expects a string input.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const crlfMatches = text.match(/\r\n/g) || [];
|
|
138
|
+
const textWithoutCRLF = text.replace(/\r\n/g, '');
|
|
139
|
+
|
|
140
|
+
const crMatches = textWithoutCRLF.match(/\r/g) || [];
|
|
141
|
+
const lfMatches = textWithoutCRLF.match(/\n/g) || [];
|
|
142
|
+
|
|
143
|
+
const count = {
|
|
144
|
+
crlf: crlfMatches.length,
|
|
145
|
+
cr: crMatches.length,
|
|
146
|
+
lf: lfMatches.length,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const types = [];
|
|
150
|
+
if (count.crlf > 0) types.push('CRLF');
|
|
151
|
+
if (count.cr > 0) types.push('CR');
|
|
152
|
+
if (count.lf > 0) types.push('LF');
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
...count,
|
|
156
|
+
types,
|
|
157
|
+
mixed: types.length > 1,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Creates defaultOptions for summonSanctifier based on inspectText result
|
|
163
|
+
* @param {!UnicodeTrashReport} report
|
|
164
|
+
* @return {!SanctifyOptions}
|
|
165
|
+
*/
|
|
166
|
+
export function getRecommendedSanctifierOptions(report) {
|
|
167
|
+
return {
|
|
168
|
+
purgeInvisibleChars: report.hasInvisibleChars,
|
|
169
|
+
purgeEmojis: report.hasEmojis,
|
|
170
|
+
nukeControls: report.hasControlChars,
|
|
171
|
+
keyboardOnlyFilter: report.hasNonKeyboardChars,
|
|
172
|
+
normalizeNewlines:
|
|
173
|
+
report.hasMixedNewlines || report.newlineStyle === 'CRLF' || report.newlineStyle === 'CR',
|
|
174
|
+
};
|
|
175
175
|
}
|