@rudra-js/attested 0.0.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +362 -4
- package/dist/hidden.d.ts +3 -0
- package/dist/hidden.d.ts.map +1 -0
- package/dist/hidden.js +22 -0
- package/dist/hidden.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/numerals.d.ts +12 -0
- package/dist/numerals.d.ts.map +1 -0
- package/dist/numerals.js +220 -0
- package/dist/numerals.js.map +1 -0
- package/dist/phrases.d.ts +31 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +199 -0
- package/dist/phrases.js.map +1 -0
- package/dist/verify.d.ts +50 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +153 -0
- package/dist/verify.js.map +1 -0
- package/package.json +39 -3
- package/src/hidden.ts +26 -0
- package/src/index.ts +15 -0
- package/src/numerals.ts +247 -0
- package/src/phrases.ts +227 -0
- package/src/verify.ts +223 -0
package/dist/numerals.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Everything here is string work. No float ever holds a value, so a twenty-digit
|
|
2
|
+
// order number compares exactly.
|
|
3
|
+
import { stripInvisible, stripMarks } from './hidden.js';
|
|
4
|
+
/** A dot or a comma. Either mark is a decimal point in one locale and grouping in another. */
|
|
5
|
+
const AMBIGUOUS = '.,';
|
|
6
|
+
/** The Arabic decimal separator, which is never grouping. */
|
|
7
|
+
const DECIMAL_ONLY = '\u066b';
|
|
8
|
+
/** Marks that are only ever grouping: Arabic thousands, the space family, the apostrophe. */
|
|
9
|
+
const GROUPING_ONLY = '\u066c\u0020\u00a0\u2007\u2008\u2009\u202f\u0027\u2019';
|
|
10
|
+
const SEPARATORS = AMBIGUOUS + DECIMAL_ONLY + GROUPING_ONLY;
|
|
11
|
+
const RUN = `\\p{Nd}+(?:[${SEPARATORS}]\\p{Nd}+)*`;
|
|
12
|
+
/** Marks that multiply the digits in front of them: 万 億 兆, and the k/M/B of English copy. */
|
|
13
|
+
const MAGNITUDE = '(?:[十百千万萬億亿兆]|[kKMB](?!\\p{L}))';
|
|
14
|
+
/** Numeric characters that are not decimal digits: ½ ² ② Ⅲ. */
|
|
15
|
+
const OTHER_NUMERAL = '[\\p{No}\\p{Nl}]+';
|
|
16
|
+
const SCAN = new RegExp(`${RUN}${MAGNITUDE}|${RUN}|${OTHER_NUMERAL}`, 'gu');
|
|
17
|
+
const LOOSE = new RegExp(`[${GROUPING_ONLY}]`, 'u');
|
|
18
|
+
const IS_DIGIT = /^\p{Nd}$/u;
|
|
19
|
+
const STARTS_DIGIT = /^\p{Nd}/u;
|
|
20
|
+
const ENDS_DIGIT = /\p{Nd}$/u;
|
|
21
|
+
const zeros = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Unicode writes decimal digits in complete sets of ten, and adjacent sets abut, so
|
|
24
|
+
* the start of a maximal run of digit code points is a zero and the offset into that
|
|
25
|
+
* run, modulo ten, is the value. Walking back without that modulo reads every digit
|
|
26
|
+
* of the second and later sets in the maths block as a nine.
|
|
27
|
+
*/
|
|
28
|
+
function digitValue(char) {
|
|
29
|
+
const code = char.codePointAt(0) ?? 0;
|
|
30
|
+
let zero = zeros.get(code);
|
|
31
|
+
if (zero === undefined) {
|
|
32
|
+
zero = code;
|
|
33
|
+
while (zero > 0 && IS_DIGIT.test(String.fromCodePoint(zero - 1)))
|
|
34
|
+
zero -= 1;
|
|
35
|
+
zeros.set(code, zero);
|
|
36
|
+
}
|
|
37
|
+
return (code - zero) % 10;
|
|
38
|
+
}
|
|
39
|
+
function toAscii(token) {
|
|
40
|
+
let out = '';
|
|
41
|
+
for (const char of token) {
|
|
42
|
+
out += SEPARATORS.includes(char) ? char : String(digitValue(char));
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** Strips the zeros that carry no value, so 39.00 and 39 are one string. */
|
|
47
|
+
function canonical(whole, fraction) {
|
|
48
|
+
let start = 0;
|
|
49
|
+
while (start < whole.length - 1 && whole[start] === '0')
|
|
50
|
+
start += 1;
|
|
51
|
+
let end = fraction.length;
|
|
52
|
+
while (end > 0 && fraction[end - 1] === '0')
|
|
53
|
+
end -= 1;
|
|
54
|
+
const head = whole.slice(start);
|
|
55
|
+
const tail = fraction.slice(0, end);
|
|
56
|
+
return tail.length === 0 ? head : `${head}.${tail}`;
|
|
57
|
+
}
|
|
58
|
+
/** Whether these runs read as one grouped whole number: 12,345,678 or 1,29,999. */
|
|
59
|
+
function isGrouping(groups) {
|
|
60
|
+
if (groups.length === 1)
|
|
61
|
+
return true;
|
|
62
|
+
if ((groups[groups.length - 1] ?? '').length !== 3)
|
|
63
|
+
return false;
|
|
64
|
+
let three = true;
|
|
65
|
+
let two = true;
|
|
66
|
+
for (let index = 1; index < groups.length - 1; index += 1) {
|
|
67
|
+
const size = (groups[index] ?? '').length;
|
|
68
|
+
if (size !== 3)
|
|
69
|
+
three = false;
|
|
70
|
+
if (size !== 2)
|
|
71
|
+
two = false;
|
|
72
|
+
}
|
|
73
|
+
const lead = (groups[0] ?? '').length;
|
|
74
|
+
if (three)
|
|
75
|
+
return lead >= 1 && lead <= 3;
|
|
76
|
+
// 1,29,999 — the Indian grouping, two digits at a time above the last three.
|
|
77
|
+
if (two)
|
|
78
|
+
return lead >= 1 && lead <= 2;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/** Whether every mark is the same one, and every one of them can be grouping. */
|
|
82
|
+
function allGrouping(separators) {
|
|
83
|
+
for (const separator of separators) {
|
|
84
|
+
if (separator !== separators[0])
|
|
85
|
+
return false;
|
|
86
|
+
if (DECIMAL_ONLY.includes(separator))
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/** `1,299` is 1299 in London and in Berlin. `1234,567` is a decimal in both. */
|
|
92
|
+
function formsOf(token) {
|
|
93
|
+
const ascii = toAscii(token);
|
|
94
|
+
const groups = [];
|
|
95
|
+
const separators = [];
|
|
96
|
+
let current = '';
|
|
97
|
+
for (const char of ascii) {
|
|
98
|
+
if (SEPARATORS.includes(char)) {
|
|
99
|
+
groups.push(current);
|
|
100
|
+
separators.push(char);
|
|
101
|
+
current = '';
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
current += char;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
groups.push(current);
|
|
108
|
+
if (separators.length === 0)
|
|
109
|
+
return [canonical(groups[0] ?? '', '')];
|
|
110
|
+
const forms = [];
|
|
111
|
+
if (allGrouping(separators) && isGrouping(groups))
|
|
112
|
+
forms.push(canonical(groups.join(''), ''));
|
|
113
|
+
const whole = groups.slice(0, -1);
|
|
114
|
+
const leading = separators.slice(0, -1);
|
|
115
|
+
const point = separators[separators.length - 1] ?? '';
|
|
116
|
+
const fraction = groups[groups.length - 1] ?? '';
|
|
117
|
+
// One dot or comma with exactly three digits behind it is grouping wherever it is
|
|
118
|
+
// written, so `4.800` is four thousand eight hundred and never the rating 4.8.
|
|
119
|
+
const groupingWins = forms.length > 0 && leading.length === 0 && fraction.length === 3;
|
|
120
|
+
if (!groupingWins &&
|
|
121
|
+
!GROUPING_ONLY.includes(point) &&
|
|
122
|
+
allGrouping(leading) &&
|
|
123
|
+
leading[0] !== point &&
|
|
124
|
+
isGrouping(whole)) {
|
|
125
|
+
forms.push(canonical(whole.join(''), fraction));
|
|
126
|
+
}
|
|
127
|
+
return forms;
|
|
128
|
+
}
|
|
129
|
+
function collect(found, token) {
|
|
130
|
+
if (!STARTS_DIGIT.test(token)) {
|
|
131
|
+
found.push({ token, forms: [], kind: 'other-numeral' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!ENDS_DIGIT.test(token)) {
|
|
135
|
+
found.push({ token, forms: [], kind: 'magnitude' });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const forms = formsOf(token);
|
|
139
|
+
if (forms.length > 0) {
|
|
140
|
+
found.push({ token, forms, kind: 'digits' });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// `8 10 12` is three numbers, not one badly grouped one.
|
|
144
|
+
if (LOOSE.test(token)) {
|
|
145
|
+
for (const part of token.split(LOOSE)) {
|
|
146
|
+
if (part.length > 0)
|
|
147
|
+
collect(found, part);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// No locale reads `24.12.2026` as a number, so only a fact written the same way
|
|
152
|
+
// stands behind it. The raw run carries a mark, and a reading never does, so it
|
|
153
|
+
// can never be mistaken for one.
|
|
154
|
+
found.push({ token, forms: [toAscii(token)], kind: 'digits' });
|
|
155
|
+
}
|
|
156
|
+
export function numeralsIn(text) {
|
|
157
|
+
const found = [];
|
|
158
|
+
for (const match of stripMarks(stripInvisible(text)).matchAll(SCAN))
|
|
159
|
+
collect(found, match[0]);
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
/** A whole fact in exponent notation and nothing else, so expanding it swallows no prose. */
|
|
163
|
+
const BARE_EXPONENT = /^[+-]?\d+(?:\.\d+)?[eE][+-]?\d+$/;
|
|
164
|
+
/**
|
|
165
|
+
* The widest digit run a fact may lay out to. Every finite JS number fits — 5e-324 is
|
|
166
|
+
* the longest, at 326 characters — and nothing a shop sells is wider than that.
|
|
167
|
+
*/
|
|
168
|
+
const DIGIT_LIMIT = 1000;
|
|
169
|
+
/**
|
|
170
|
+
* `String(1e21)` is `1e+21`, which reads as the two numerals 1 and 21. Lay the same digits
|
|
171
|
+
* out in place. Empty past the limit: `1e2000000000` is twelve characters and no product
|
|
172
|
+
* fact, and laying it out builds a run long enough to take the process down with it.
|
|
173
|
+
*/
|
|
174
|
+
function positional(written) {
|
|
175
|
+
const marker = written.search(/[eE]/);
|
|
176
|
+
if (marker < 0)
|
|
177
|
+
return written;
|
|
178
|
+
const power = Number(written.slice(marker + 1));
|
|
179
|
+
// The sign never reaches a numeral, so dropping it here mints no digit the host lacks.
|
|
180
|
+
const body = written.slice(0, marker).replace(/^[+-]/, '');
|
|
181
|
+
const point = body.indexOf('.');
|
|
182
|
+
const digits = point < 0 ? body : body.slice(0, point) + body.slice(point + 1);
|
|
183
|
+
const place = (point < 0 ? body.length : point) + power;
|
|
184
|
+
if (place <= 0) {
|
|
185
|
+
if (digits.length - place > DIGIT_LIMIT)
|
|
186
|
+
return '';
|
|
187
|
+
return `0.${'0'.repeat(-place)}${digits}`;
|
|
188
|
+
}
|
|
189
|
+
if (place >= digits.length) {
|
|
190
|
+
if (place > DIGIT_LIMIT)
|
|
191
|
+
return '';
|
|
192
|
+
return `${digits}${'0'.repeat(place - digits.length)}`;
|
|
193
|
+
}
|
|
194
|
+
return `${digits.slice(0, place)}.${digits.slice(place)}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A string fact is read as the host typed it, except when the whole of it is a number
|
|
198
|
+
* in exponent notation: `String`, `JSON.stringify`, a CSV export and an API that writes
|
|
199
|
+
* 64-bit values as strings all produce that, and none of them is the host choosing digits.
|
|
200
|
+
*/
|
|
201
|
+
function digitsOf(value) {
|
|
202
|
+
if (typeof value === 'number')
|
|
203
|
+
return positional(String(value));
|
|
204
|
+
if (typeof value !== 'string')
|
|
205
|
+
return String(value);
|
|
206
|
+
const trimmed = value.trim();
|
|
207
|
+
return BARE_EXPONENT.test(trimmed) ? positional(trimmed) : value;
|
|
208
|
+
}
|
|
209
|
+
/** Every reading of every numeral the host stands behind. */
|
|
210
|
+
export function supportedValues(values) {
|
|
211
|
+
const supported = new Set();
|
|
212
|
+
for (const value of values) {
|
|
213
|
+
for (const numeral of numeralsIn(digitsOf(value))) {
|
|
214
|
+
for (const form of numeral.forms)
|
|
215
|
+
supported.add(form);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return supported;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=numerals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numerals.js","sourceRoot":"","sources":["../src/numerals.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Claims with no value behind them. Stored in the form `normalisePhrasing` produces. */
|
|
2
|
+
export declare const BANNED_PHRASES: readonly string[];
|
|
3
|
+
/** One shape for text and phrases, so a hyphen or a stray mark cannot hide a claim. */
|
|
4
|
+
export declare function normalisePhrasing(text: string): string;
|
|
5
|
+
export interface Span {
|
|
6
|
+
start: number;
|
|
7
|
+
/** The last character of the match, not one past it. */
|
|
8
|
+
end: number;
|
|
9
|
+
}
|
|
10
|
+
export interface Indexed {
|
|
11
|
+
/** The text as it was handed in, which is what spans index and the word gap reads. */
|
|
12
|
+
text: string;
|
|
13
|
+
/** The same characters with the gaps dropped, which is what matching runs against. */
|
|
14
|
+
haystack: string;
|
|
15
|
+
/** Where each character of `haystack` really sat in `text`. */
|
|
16
|
+
spots: number[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build this once and read every phrase off it. The text must already be normalised.
|
|
20
|
+
*
|
|
21
|
+
* Matching runs with every space dropped, because 送料 無料 is 送料無料 to anyone and a
|
|
22
|
+
* space is free. `spots` carries each character back to where it really sat, so the
|
|
23
|
+
* word-gap guard still reads the neighbours the shopper sees.
|
|
24
|
+
*/
|
|
25
|
+
export declare function indexPhrasing(text: string): Indexed;
|
|
26
|
+
/** Spans index the text the index was built from. The phrase must already be normalised. */
|
|
27
|
+
export declare function spansIn(indexed: Indexed, phrase: string): Span[];
|
|
28
|
+
/** Both arguments must already be normalised. Spans index the text passed in. */
|
|
29
|
+
export declare function phraseSpans(text: string, phrase: string): Span[];
|
|
30
|
+
export declare function phraseIn(text: string, phrase: string): boolean;
|
|
31
|
+
//# sourceMappingURL=phrases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"AAEA,yFAAyF;AACzF,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAkF3C,CAAC;AA0CF,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAetD;AAQD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYnD;AAED,4FAA4F;AAG5F,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CA2BhE;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CAEhE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE9D"}
|
package/dist/phrases.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { stripInvisible, stripMarks } from './hidden.js';
|
|
2
|
+
/** Claims with no value behind them. Stored in the form `normalisePhrasing` produces. */
|
|
3
|
+
export const BANNED_PHRASES = [
|
|
4
|
+
'cheap',
|
|
5
|
+
'cheaper',
|
|
6
|
+
'cheapest',
|
|
7
|
+
'affordable',
|
|
8
|
+
'bargain',
|
|
9
|
+
'great value',
|
|
10
|
+
'best value',
|
|
11
|
+
'low price',
|
|
12
|
+
'lower price',
|
|
13
|
+
'lowest price',
|
|
14
|
+
'best price',
|
|
15
|
+
'price drop',
|
|
16
|
+
'price dropped',
|
|
17
|
+
'just dropped',
|
|
18
|
+
'price cut',
|
|
19
|
+
'reduced price',
|
|
20
|
+
'sale',
|
|
21
|
+
'marked down',
|
|
22
|
+
'half price',
|
|
23
|
+
'half off',
|
|
24
|
+
'discount',
|
|
25
|
+
'discounted',
|
|
26
|
+
'clearance',
|
|
27
|
+
'free delivery',
|
|
28
|
+
'free shipping',
|
|
29
|
+
'free postage',
|
|
30
|
+
'free returns',
|
|
31
|
+
'ships free',
|
|
32
|
+
'fast delivery',
|
|
33
|
+
'next day',
|
|
34
|
+
'same day',
|
|
35
|
+
'overnight delivery',
|
|
36
|
+
'arrives tomorrow',
|
|
37
|
+
'delivered tomorrow',
|
|
38
|
+
'by tomorrow',
|
|
39
|
+
'in stock',
|
|
40
|
+
'out of stock',
|
|
41
|
+
'low stock',
|
|
42
|
+
'limited stock',
|
|
43
|
+
'running low',
|
|
44
|
+
'running out',
|
|
45
|
+
'back in stock',
|
|
46
|
+
'restocked',
|
|
47
|
+
'sold out',
|
|
48
|
+
'selling out',
|
|
49
|
+
'almost sold out',
|
|
50
|
+
'nearly sold out',
|
|
51
|
+
'selling fast',
|
|
52
|
+
'going fast',
|
|
53
|
+
'going quick',
|
|
54
|
+
'flying off',
|
|
55
|
+
'while stocks last',
|
|
56
|
+
'almost gone',
|
|
57
|
+
'nearly gone',
|
|
58
|
+
'few left',
|
|
59
|
+
'last few',
|
|
60
|
+
'last chance',
|
|
61
|
+
'limited time',
|
|
62
|
+
'today only',
|
|
63
|
+
'ends soon',
|
|
64
|
+
'hurry',
|
|
65
|
+
'act fast',
|
|
66
|
+
'do not miss',
|
|
67
|
+
"don't miss",
|
|
68
|
+
'best seller',
|
|
69
|
+
'best selling',
|
|
70
|
+
'bestseller',
|
|
71
|
+
'bestselling',
|
|
72
|
+
'most popular',
|
|
73
|
+
'popular pick',
|
|
74
|
+
'top pick',
|
|
75
|
+
'top rated',
|
|
76
|
+
'highly rated',
|
|
77
|
+
'highest rated',
|
|
78
|
+
'rated highest',
|
|
79
|
+
'best rated',
|
|
80
|
+
'five star',
|
|
81
|
+
'star rating',
|
|
82
|
+
'customer favourite',
|
|
83
|
+
'customer favorite',
|
|
84
|
+
'money back',
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Letters that render as a Latin letter but are not one. Folded so a Cyrillic о in
|
|
88
|
+
* "in stоck" cannot walk a claim past its own entry on the list. Both the text and
|
|
89
|
+
* the phrase are folded, so a genuine Cyrillic phrase still matches itself.
|
|
90
|
+
*/
|
|
91
|
+
const CONFUSABLES = {
|
|
92
|
+
а: 'a',
|
|
93
|
+
в: 'b',
|
|
94
|
+
е: 'e',
|
|
95
|
+
к: 'k',
|
|
96
|
+
м: 'm',
|
|
97
|
+
н: 'h',
|
|
98
|
+
о: 'o',
|
|
99
|
+
р: 'p',
|
|
100
|
+
с: 'c',
|
|
101
|
+
т: 't',
|
|
102
|
+
х: 'x',
|
|
103
|
+
у: 'y',
|
|
104
|
+
ѕ: 's',
|
|
105
|
+
і: 'i',
|
|
106
|
+
ј: 'j',
|
|
107
|
+
ԁ: 'd',
|
|
108
|
+
һ: 'h',
|
|
109
|
+
ӏ: 'l',
|
|
110
|
+
α: 'a',
|
|
111
|
+
ε: 'e',
|
|
112
|
+
ι: 'i',
|
|
113
|
+
κ: 'k',
|
|
114
|
+
ν: 'v',
|
|
115
|
+
ο: 'o',
|
|
116
|
+
ρ: 'p',
|
|
117
|
+
τ: 't',
|
|
118
|
+
υ: 'u',
|
|
119
|
+
χ: 'x',
|
|
120
|
+
ϲ: 'c',
|
|
121
|
+
};
|
|
122
|
+
/** A run of whitespace holding one of these is a break the shopper sees. */
|
|
123
|
+
const LINE_BREAK = /[\n\v\f\r\u0085\u2028\u2029]/;
|
|
124
|
+
/** One shape for text and phrases, so a hyphen or a stray mark cannot hide a claim. */
|
|
125
|
+
export function normalisePhrasing(text) {
|
|
126
|
+
// Marks come off after composition, so an accented e keeps its accent while an
|
|
127
|
+
// overline, which composes with nothing, does not hide `free` from its own entry.
|
|
128
|
+
const lowered = stripMarks(stripInvisible(text).normalize('NFKC').toLowerCase().normalize('NFC'));
|
|
129
|
+
let folded = '';
|
|
130
|
+
for (const char of lowered)
|
|
131
|
+
folded += CONFUSABLES[char] ?? char;
|
|
132
|
+
// A line break survives as itself. Matching reads straight through it, and only
|
|
133
|
+
// the allowance in verify.ts cares, because it will not forgive across a break.
|
|
134
|
+
return folded
|
|
135
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
136
|
+
.replace(/[-_\u2010-\u2015]+/g, ' ')
|
|
137
|
+
.replace(/\s+/g, (run) => (LINE_BREAK.test(run) ? '\n' : ' '))
|
|
138
|
+
.trim();
|
|
139
|
+
}
|
|
140
|
+
const ASCII_WORD = /[a-z0-9]/;
|
|
141
|
+
function isGlued(char) {
|
|
142
|
+
return char !== undefined && ASCII_WORD.test(char);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Build this once and read every phrase off it. The text must already be normalised.
|
|
146
|
+
*
|
|
147
|
+
* Matching runs with every space dropped, because 送料 無料 is 送料無料 to anyone and a
|
|
148
|
+
* space is free. `spots` carries each character back to where it really sat, so the
|
|
149
|
+
* word-gap guard still reads the neighbours the shopper sees.
|
|
150
|
+
*/
|
|
151
|
+
export function indexPhrasing(text) {
|
|
152
|
+
const tight = [];
|
|
153
|
+
const spots = [];
|
|
154
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
155
|
+
const char = text[index] ?? '';
|
|
156
|
+
// A line break reads like a space here, so a claim split over two lines is caught.
|
|
157
|
+
if (char === ' ' || char === '\n')
|
|
158
|
+
continue;
|
|
159
|
+
tight.push(char);
|
|
160
|
+
spots.push(index);
|
|
161
|
+
}
|
|
162
|
+
return { text, haystack: tight.join(''), spots };
|
|
163
|
+
}
|
|
164
|
+
/** Spans index the text the index was built from. The phrase must already be normalised. */
|
|
165
|
+
// The word-gap rule only guards an ASCII edge, so `cheap` misses `cheapskate`
|
|
166
|
+
// while a Japanese phrase — which has no word gaps to find — still matches.
|
|
167
|
+
export function spansIn(indexed, phrase) {
|
|
168
|
+
const { text, haystack, spots } = indexed;
|
|
169
|
+
const spans = [];
|
|
170
|
+
const needle = phrase.replaceAll(' ', '').replaceAll('\n', '');
|
|
171
|
+
if (needle.length === 0)
|
|
172
|
+
return spans;
|
|
173
|
+
const guardStart = ASCII_WORD.test(needle[0] ?? '');
|
|
174
|
+
const guardEnd = ASCII_WORD.test(needle[needle.length - 1] ?? '');
|
|
175
|
+
let from = 0;
|
|
176
|
+
for (;;) {
|
|
177
|
+
const at = haystack.indexOf(needle, from);
|
|
178
|
+
if (at === -1)
|
|
179
|
+
return spans;
|
|
180
|
+
const start = spots[at] ?? 0;
|
|
181
|
+
const end = spots[at + needle.length - 1] ?? 0;
|
|
182
|
+
const next = text[end + 1];
|
|
183
|
+
// A trailing s is a plural, not a different word: `discount` catches `discounts`.
|
|
184
|
+
const plural = next === 's' && !isGlued(text[end + 2]);
|
|
185
|
+
const before = guardStart && isGlued(text[start - 1]);
|
|
186
|
+
const after = guardEnd && isGlued(next) && !plural;
|
|
187
|
+
if (!before && !after)
|
|
188
|
+
spans.push({ start, end });
|
|
189
|
+
from = at + 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Both arguments must already be normalised. Spans index the text passed in. */
|
|
193
|
+
export function phraseSpans(text, phrase) {
|
|
194
|
+
return spansIn(indexPhrasing(text), phrase);
|
|
195
|
+
}
|
|
196
|
+
export function phraseIn(text, phrase) {
|
|
197
|
+
return phraseSpans(text, phrase).length > 0;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=phrases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phrases.js","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type Layer = 'quantity' | 'wording';
|
|
2
|
+
export interface Finding {
|
|
3
|
+
layer: Layer;
|
|
4
|
+
/** The numeral as the text wrote it, or the phrase as the denylist holds it. */
|
|
5
|
+
token: string;
|
|
6
|
+
/** Audit evidence. Names the layer and the token. */
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface LayerReport {
|
|
10
|
+
supported: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* What this layer's verdict is worth. `proof` means every numeral in the text is
|
|
13
|
+
* a value the host supplied — not that the sentence around it is true.
|
|
14
|
+
*/
|
|
15
|
+
strength: 'proof' | 'best-effort';
|
|
16
|
+
/** Numerals found, or phrases screened against. Zero means nothing was checked. */
|
|
17
|
+
checked: number;
|
|
18
|
+
/** One per distinct token that failed. */
|
|
19
|
+
findings: Finding[];
|
|
20
|
+
}
|
|
21
|
+
export interface VerifyResult {
|
|
22
|
+
/** True when both layers are. */
|
|
23
|
+
supported: boolean;
|
|
24
|
+
quantity: LayerReport;
|
|
25
|
+
wording: LayerReport;
|
|
26
|
+
}
|
|
27
|
+
export interface Facts {
|
|
28
|
+
/** In any shape they are written: `39`, `'$1,299.00'`, `'4,8'`. Every numeral in one counts. */
|
|
29
|
+
values: readonly (string | number | bigint)[];
|
|
30
|
+
/** Claims to ban on top of the built-in English list, for the host's own language. */
|
|
31
|
+
bannedPhrases?: readonly string[];
|
|
32
|
+
/** Wording the shop stands behind. A banned claim inside one of these is not reported. */
|
|
33
|
+
allowedPhrases?: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
export interface FieldResult {
|
|
36
|
+
field: string;
|
|
37
|
+
result: VerifyResult;
|
|
38
|
+
}
|
|
39
|
+
export interface BatchResult {
|
|
40
|
+
/** True when every field is, and when the fields read as one carry no banned claim. */
|
|
41
|
+
supported: boolean;
|
|
42
|
+
fields: FieldResult[];
|
|
43
|
+
/** The wording layer over every field joined, so a phrase split across two fields still reads. */
|
|
44
|
+
acrossFields: LayerReport;
|
|
45
|
+
}
|
|
46
|
+
/** Whether one piece of model-written text stands on the facts the host supplied. */
|
|
47
|
+
export declare function verify(text: string, facts: Facts): VerifyResult;
|
|
48
|
+
/** The same check over every field of one product, without repeating the facts. */
|
|
49
|
+
export declare function verifyFields(fields: Record<string, string>, facts: Facts): BatchResult;
|
|
50
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,OAAO,GAAG,aAAa,CAAC;IAClC,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,gGAAgG;IAChG,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC9C,sFAAsF;IACtF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,kGAAkG;IAClG,YAAY,EAAE,WAAW,CAAC;CAC3B;AA4ID,qFAAqF;AACrF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,CAE/D;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,WAAW,CAqBtF"}
|