text-sanctifier 1.3.1 → 1.3.3
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 +349 -277
- package/bin/text-sanctifier.js +753 -519
- package/dist/text-sanctifier.min.js +15 -14
- package/dist/types/categories.generated.d.ts +27 -0
- package/dist/types/hiddenText.d.ts +50 -0
- package/dist/types/inspectText.d.ts +7 -7
- package/dist/types/sanctifyText.d.ts +3 -29
- package/package.json +80 -74
- package/src/categories.generated.js +223 -0
- package/src/hiddenText.js +120 -0
- package/src/inspectText.js +343 -390
- package/src/sanctifyText.js +506 -511
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/hiddenText.js
|
|
2
|
+
//
|
|
3
|
+
// Finds runs of the characters used to hide instructions in text a model reads:
|
|
4
|
+
// tag characters, variation selectors and invisible operators. Tag runs are decoded
|
|
5
|
+
// to the ASCII they spell. Used by `text-sanctifier check --ai-config`, and exported
|
|
6
|
+
// as `text-sanctifier/hidden`.
|
|
7
|
+
//
|
|
8
|
+
// Plain JavaScript with no Node APIs, so it also runs in a browser. src/index.js does
|
|
9
|
+
// not import this file, so it stays out of the browser bundle and its size budget.
|
|
10
|
+
|
|
11
|
+
/** @typedef {'tag' | 'variation_selector' | 'invisible_operator'} HiddenRunKind */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} HiddenRun
|
|
15
|
+
* @property {HiddenRunKind} kind
|
|
16
|
+
* @property {number} offset - UTF-16 index of the first character
|
|
17
|
+
* @property {number} end - UTF-16 index just past the last character
|
|
18
|
+
* @property {number} count - number of code points in the run
|
|
19
|
+
* @property {string} [decoded] - tag runs only: the ASCII the tags spell (see decodeTagText)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} HiddenTextScan
|
|
24
|
+
* @property {HiddenRun[]} runs - runs that may hide text, in order
|
|
25
|
+
* @property {Array<[number, number]>} benign - [offset, end) spans of legitimate
|
|
26
|
+
* sequences (subdivision flags, a single emoji or text presentation selector)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const TAG_FIRST = 0xE0000;
|
|
30
|
+
const TAG_LAST = 0xE007F;
|
|
31
|
+
const CANCEL_TAG = 0xE007F;
|
|
32
|
+
const BLACK_FLAG = 0x1F3F4;
|
|
33
|
+
|
|
34
|
+
// RGI subdivision flags: U+1F3F4, the tag letters, U+E007F
|
|
35
|
+
const SUBDIVISION_FLAGS = new Set(['gbeng', 'gbsct', 'gbwls']);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {number} cp
|
|
39
|
+
* @returns {HiddenRunKind | null}
|
|
40
|
+
*/
|
|
41
|
+
function kindOf(cp) {
|
|
42
|
+
if (cp >= TAG_FIRST && cp <= TAG_LAST) return 'tag';
|
|
43
|
+
if ((cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF) ||
|
|
44
|
+
(cp >= 0x180B && cp <= 0x180D) || cp === 0x180F) return 'variation_selector';
|
|
45
|
+
if (cp >= 0x2061 && cp <= 0x2064) return 'invisible_operator';
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {number} cp */
|
|
50
|
+
const hex = (cp) => 'U+' + cp.toString(16).toUpperCase().padStart(4, '0');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Decodes tag characters to the ASCII they shadow: U+E0020–U+E007E become
|
|
54
|
+
* U+0020–U+007E. Anything else (U+E0001 LANGUAGE TAG, U+E007F CANCEL TAG, unassigned
|
|
55
|
+
* tags, other characters) is written as `<U+XXXX>`, so the result is printable.
|
|
56
|
+
* @param {string} text
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function decodeTagText(text) {
|
|
60
|
+
let out = '';
|
|
61
|
+
for (const ch of text) {
|
|
62
|
+
const cp = /** @type {number} */ (ch.codePointAt(0));
|
|
63
|
+
out += cp >= 0xE0020 && cp <= 0xE007E ? String.fromCharCode(cp - TAG_FIRST) : `<${hex(cp)}>`;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Finds runs of tag characters, variation selectors and invisible operators.
|
|
70
|
+
* Consecutive characters of the same kind form one run. Two kinds of sequence are
|
|
71
|
+
* legitimate and listed under `benign` instead of `runs`:
|
|
72
|
+
* - the subdivision flags of England, Scotland and Wales: U+1F3F4 followed by the
|
|
73
|
+
* tags for "gbeng", "gbsct" or "gbwls" and U+E007F
|
|
74
|
+
* - a single U+FE0E or U+FE0F (text or emoji presentation) after another character
|
|
75
|
+
* @param {string} text
|
|
76
|
+
* @returns {HiddenTextScan}
|
|
77
|
+
*/
|
|
78
|
+
export function findHiddenRuns(text) {
|
|
79
|
+
/** @type {HiddenRun[]} */
|
|
80
|
+
const runs = [];
|
|
81
|
+
/** @type {Array<[number, number]>} */
|
|
82
|
+
const benign = [];
|
|
83
|
+
|
|
84
|
+
let i = 0;
|
|
85
|
+
while (i < text.length) {
|
|
86
|
+
const cp = /** @type {number} */ (text.codePointAt(i));
|
|
87
|
+
const kind = kindOf(cp);
|
|
88
|
+
if (kind === null) { i += cp > 0xFFFF ? 2 : 1; continue; }
|
|
89
|
+
|
|
90
|
+
const offset = i;
|
|
91
|
+
let count = 0;
|
|
92
|
+
let last = cp;
|
|
93
|
+
while (i < text.length) {
|
|
94
|
+
const c = /** @type {number} */ (text.codePointAt(i));
|
|
95
|
+
if (kindOf(c) !== kind) break;
|
|
96
|
+
last = c;
|
|
97
|
+
count++;
|
|
98
|
+
i += c > 0xFFFF ? 2 : 1;
|
|
99
|
+
}
|
|
100
|
+
const end = i;
|
|
101
|
+
|
|
102
|
+
if (kind === 'tag') {
|
|
103
|
+
const body = text.slice(offset, end);
|
|
104
|
+
const afterFlag = offset >= 2 && text.codePointAt(offset - 2) === BLACK_FLAG;
|
|
105
|
+
if (afterFlag && last === CANCEL_TAG && SUBDIVISION_FLAGS.has(decodeTagText(body).slice(0, -'<U+E007F>'.length))) {
|
|
106
|
+
benign.push([offset, end]);
|
|
107
|
+
} else {
|
|
108
|
+
runs.push({ kind, offset, end, count, decoded: decodeTagText(body) });
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (kind === 'variation_selector' && count === 1 && (cp === 0xFE0E || cp === 0xFE0F) &&
|
|
113
|
+
offset > 0 && !/\s/.test(text[offset - 1])) {
|
|
114
|
+
benign.push([offset, end]);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
runs.push({ kind, offset, end, count });
|
|
118
|
+
}
|
|
119
|
+
return { runs, benign };
|
|
120
|
+
}
|