hanzi-chai 0.3.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/dist/affine.d.ts +8 -0
- package/dist/affine.js +153 -0
- package/dist/assembly.d.ts +63 -0
- package/dist/assembly.js +286 -0
- package/dist/bezier.d.ts +74 -0
- package/dist/bezier.js +140 -0
- package/dist/classifier.d.ts +109 -0
- package/dist/classifier.js +108 -0
- package/dist/component.d.ts +115 -0
- package/dist/component.js +278 -0
- package/dist/compound.d.ts +64 -0
- package/dist/compound.js +652 -0
- package/dist/config.d.ts +236 -0
- package/dist/config.js +48 -0
- package/dist/data/cjk.txt +20902 -0
- package/dist/data/data/cjk.txt +20902 -0
- package/dist/data/data/dictionary.txt +69429 -0
- package/dist/data/data/frequency.txt +263137 -0
- package/dist/data/data/key_distribution.txt +46 -0
- package/dist/data/data/pair_equivalence.txt +2116 -0
- package/dist/data/data/repertoire.json.deflate +0 -0
- package/dist/data/data/tygf.txt +8105 -0
- package/dist/data/dictionary.txt +69429 -0
- package/dist/data/frequency.txt +263137 -0
- package/dist/data/key_distribution.txt +46 -0
- package/dist/data/pair_equivalence.txt +2116 -0
- package/dist/data/repertoire.json.deflate +0 -0
- package/dist/data/tygf.txt +8105 -0
- package/dist/data.d.ts +195 -0
- package/dist/data.js +18 -0
- package/dist/degenerator.d.ts +21 -0
- package/dist/degenerator.js +134 -0
- package/dist/element.d.ts +56 -0
- package/dist/element.js +225 -0
- package/dist/equivalence.d.ts +21 -0
- package/dist/equivalence.js +74 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +126 -0
- package/dist/index.mjs +3553 -0
- package/dist/mathjs.d.ts +10 -0
- package/dist/mathjs.js +9 -0
- package/dist/repertoire.d.ts +75 -0
- package/dist/repertoire.js +406 -0
- package/dist/selector.d.ts +110 -0
- package/dist/selector.js +310 -0
- package/dist/topology.d.ts +41 -0
- package/dist/topology.js +209 -0
- package/dist/transformer.d.ts +18 -0
- package/dist/transformer.js +111 -0
- package/dist/utils.d.ts +107 -0
- package/dist/utils.js +546 -0
- package/package.json +34 -0
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Feature } from "./classifier.js";
|
|
2
|
+
import type { DerivedComponent, Compound, Repertoire, Draw, Operator, Point, SVGStroke, BasicComponent, ReferenceStroke, PrimitiveRepertoire, Character, SplicedComponent, Glyph, Identity } from "./data.js";
|
|
3
|
+
import { type Value, type Mapping, GeneralizedKey, CharacterSetSpecifier } from "./config.js";
|
|
4
|
+
import type { IndexedElement } from "./assembly.js";
|
|
5
|
+
interface Loss {
|
|
6
|
+
ideal: number;
|
|
7
|
+
lt_penalty: number;
|
|
8
|
+
gt_penalty: number;
|
|
9
|
+
}
|
|
10
|
+
export type Dictionary = [string, string][];
|
|
11
|
+
export type Frequency = Record<string, number>;
|
|
12
|
+
export type AdaptedFrequency = Map<string, number>;
|
|
13
|
+
export type Distribution = Record<string, Loss>;
|
|
14
|
+
export type Equivalence = Record<string, number>;
|
|
15
|
+
export type CustomElementMap = Record<string, string[]>;
|
|
16
|
+
export declare const printableAscii: string[];
|
|
17
|
+
export declare const formatDate: (date: Date) => string;
|
|
18
|
+
export interface UnicodeBlock {
|
|
19
|
+
name: string;
|
|
20
|
+
label: string;
|
|
21
|
+
begin: number;
|
|
22
|
+
end: number;
|
|
23
|
+
count: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const unicodeBlocks: UnicodeBlock[];
|
|
26
|
+
export declare const unicodeBlock: (code: number) => string;
|
|
27
|
+
export declare const unicodeBlockRank: (code: number) => number;
|
|
28
|
+
export declare const isValidCJKBasicChar: (char: string) => boolean;
|
|
29
|
+
export declare const isValidCJKChar: (char: string) => boolean;
|
|
30
|
+
export declare const isValidCJKSupplement: (char: string) => boolean;
|
|
31
|
+
export declare const isPUA: (char: string) => boolean;
|
|
32
|
+
export declare const chars: (s: string) => number;
|
|
33
|
+
export declare const getDummyBasicComponent: () => BasicComponent;
|
|
34
|
+
export declare const getDummyDerivedComponent: () => DerivedComponent;
|
|
35
|
+
export declare const getDummySplicedComponent: () => SplicedComponent;
|
|
36
|
+
export declare const getDummyReferenceStroke: () => ReferenceStroke;
|
|
37
|
+
export declare const getDummySVGStroke: (feature: Feature, start?: Point, oldCurveList?: Draw[]) => SVGStroke;
|
|
38
|
+
export declare const getDummyCompound: (operator: Operator) => Compound;
|
|
39
|
+
export declare const isComponent: (glyph: Glyph) => glyph is BasicComponent | DerivedComponent | SplicedComponent | Identity;
|
|
40
|
+
export declare const getSupplemental: (repertoire: Repertoire, list: string[]) => string[];
|
|
41
|
+
export declare const listToObject: <T extends {
|
|
42
|
+
unicode: number;
|
|
43
|
+
}>(list: T[]) => {
|
|
44
|
+
[k: string]: T;
|
|
45
|
+
};
|
|
46
|
+
export declare function getRecordFromTSV(text: string): Record<string, number>;
|
|
47
|
+
export declare function getDistributionFromTSV(text: string): Distribution;
|
|
48
|
+
export declare function getDictFromTSV(text: string): [string, string][];
|
|
49
|
+
export declare const exportYAML: (config: object, filename: string, flowLevel?: number, sanitize?: boolean) => void;
|
|
50
|
+
export declare const exportJSON: (data: object, filename: string) => void;
|
|
51
|
+
export declare const exportTSV: (data: string[][], filename: string) => void;
|
|
52
|
+
export declare const stringify: (element: IndexedElement | undefined) => string;
|
|
53
|
+
export declare const parseKey: (key: string) => IndexedElement | undefined;
|
|
54
|
+
export declare const joinKeys: (keys: GeneralizedKey[]) => string | GeneralizedKey[];
|
|
55
|
+
export declare const makeCharacterFilter: (input: CharacterFilter, repertoire: Repertoire | PrimitiveRepertoire, sequenceMap: Map<string, string>) => (name: string) => boolean | undefined;
|
|
56
|
+
export interface CharacterFilter {
|
|
57
|
+
name?: string;
|
|
58
|
+
sequence?: string;
|
|
59
|
+
unicode?: number;
|
|
60
|
+
tag?: string;
|
|
61
|
+
part?: string;
|
|
62
|
+
operator?: Operator;
|
|
63
|
+
}
|
|
64
|
+
export declare const makeFilter: (input: string, form: Repertoire, sequence: Map<string, string>) => (char: string) => boolean;
|
|
65
|
+
export interface AnalyzerForm {
|
|
66
|
+
type: "single" | "multi" | "all";
|
|
67
|
+
position: number[];
|
|
68
|
+
top: number;
|
|
69
|
+
}
|
|
70
|
+
export declare const makeDefaultAnalyzer: (maxLength: number) => AnalyzerForm;
|
|
71
|
+
interface LevelMetric {
|
|
72
|
+
length: number;
|
|
73
|
+
frequency: number;
|
|
74
|
+
}
|
|
75
|
+
interface TierMetric {
|
|
76
|
+
top?: number;
|
|
77
|
+
duplication?: number;
|
|
78
|
+
levels?: LevelMetric[];
|
|
79
|
+
fingering?: (number | undefined)[];
|
|
80
|
+
}
|
|
81
|
+
export interface PartialMetric {
|
|
82
|
+
tiers?: TierMetric[];
|
|
83
|
+
duplication?: number;
|
|
84
|
+
key_distribution?: number;
|
|
85
|
+
pair_equivalence?: number;
|
|
86
|
+
extended_pair_equivalence?: number;
|
|
87
|
+
fingering?: (number | undefined)[];
|
|
88
|
+
levels?: LevelMetric[];
|
|
89
|
+
}
|
|
90
|
+
export interface Metric {
|
|
91
|
+
characters_full?: PartialMetric;
|
|
92
|
+
characters_short?: PartialMetric;
|
|
93
|
+
words_full?: PartialMetric;
|
|
94
|
+
words_short?: PartialMetric;
|
|
95
|
+
}
|
|
96
|
+
export declare const adapt: (原始词频映射: Frequency, 词典: Set<string>) => Map<string, number>;
|
|
97
|
+
export type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
|
|
98
|
+
type _TupleOf<T, N extends number, R extends unknown[]> = R["length"] extends N ? R : _TupleOf<T, N, [T, ...R]>;
|
|
99
|
+
export interface MappedInfo {
|
|
100
|
+
name: string;
|
|
101
|
+
code: Exclude<Value, null | {
|
|
102
|
+
element: string;
|
|
103
|
+
}>;
|
|
104
|
+
}
|
|
105
|
+
export declare const getReversedMapping: (mapping: Mapping, alphabet: string) => Map<string, MappedInfo[]>;
|
|
106
|
+
export declare const characterSetFilters: Record<CharacterSetSpecifier, (k: string, v: Character, s?: Set<string>) => boolean>;
|
|
107
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { schema } from "./classifier.js";
|
|
2
|
+
import { range } from "lodash-es";
|
|
3
|
+
import { dump } from "js-yaml";
|
|
4
|
+
export const printableAscii = range(33, 127).map((x) => String.fromCodePoint(x));
|
|
5
|
+
export const formatDate = (date) => {
|
|
6
|
+
return `${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
|
7
|
+
};
|
|
8
|
+
export const unicodeBlocks = [
|
|
9
|
+
// 统一汉字基本集与扩展
|
|
10
|
+
{
|
|
11
|
+
name: "cjk",
|
|
12
|
+
label: "中日韩统一表意文字",
|
|
13
|
+
begin: 0x4e00,
|
|
14
|
+
end: 0x9fff,
|
|
15
|
+
count: 20992,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "cjk-a",
|
|
19
|
+
label: "中日韩统一表意文字扩展A",
|
|
20
|
+
begin: 0x3400,
|
|
21
|
+
end: 0x4dbf,
|
|
22
|
+
count: 6592,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "cjk-b",
|
|
26
|
+
label: "中日韩统一表意文字扩展B",
|
|
27
|
+
begin: 0x20000,
|
|
28
|
+
end: 0x2a6df,
|
|
29
|
+
count: 42720,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "cjk-c",
|
|
33
|
+
label: "中日韩统一表意文字扩展C",
|
|
34
|
+
begin: 0x2a700,
|
|
35
|
+
end: 0x2b73f,
|
|
36
|
+
count: 4160,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "cjk-d",
|
|
40
|
+
label: "中日韩统一表意文字扩展D",
|
|
41
|
+
begin: 0x2b740,
|
|
42
|
+
end: 0x2b81f,
|
|
43
|
+
count: 222,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "cjk-e",
|
|
47
|
+
label: "中日韩统一表意文字扩展E",
|
|
48
|
+
begin: 0x2b820,
|
|
49
|
+
end: 0x2ceaf,
|
|
50
|
+
count: 5774,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "cjk-f",
|
|
54
|
+
label: "中日韩统一表意文字扩展F",
|
|
55
|
+
begin: 0x2ceb0,
|
|
56
|
+
end: 0x2ebef,
|
|
57
|
+
count: 7473,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "cjk-g",
|
|
61
|
+
label: "中日韩统一表意文字扩展G",
|
|
62
|
+
begin: 0x30000,
|
|
63
|
+
end: 0x3134f,
|
|
64
|
+
count: 4939,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "cjk-h",
|
|
68
|
+
label: "中日韩统一表意文字扩展H",
|
|
69
|
+
begin: 0x31350,
|
|
70
|
+
end: 0x323af,
|
|
71
|
+
count: 4192,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "cjk-i",
|
|
75
|
+
label: "中日韩统一表意文字扩展I",
|
|
76
|
+
begin: 0x2ebf0,
|
|
77
|
+
end: 0x2ee5f,
|
|
78
|
+
count: 622,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "cjk-j",
|
|
82
|
+
label: "中日韩统一表意文字扩展J",
|
|
83
|
+
begin: 0x323b0,
|
|
84
|
+
end: 0x3347f,
|
|
85
|
+
count: 4298,
|
|
86
|
+
},
|
|
87
|
+
// 部件
|
|
88
|
+
{
|
|
89
|
+
name: "radicals-sup",
|
|
90
|
+
label: "中日韩部首补充",
|
|
91
|
+
begin: 0x2e80,
|
|
92
|
+
end: 0x2eff,
|
|
93
|
+
count: 115,
|
|
94
|
+
},
|
|
95
|
+
{ name: "kangxi", label: "康熙部首", begin: 0x2f00, end: 0x2fdf, count: 214 },
|
|
96
|
+
{
|
|
97
|
+
name: "strokes",
|
|
98
|
+
label: "中日韩笔画",
|
|
99
|
+
begin: 0x31c0,
|
|
100
|
+
end: 0x31ef,
|
|
101
|
+
count: 38,
|
|
102
|
+
},
|
|
103
|
+
// 兼容汉字
|
|
104
|
+
{
|
|
105
|
+
name: "compat",
|
|
106
|
+
label: "中日韩兼容表意文字",
|
|
107
|
+
begin: 0xf900,
|
|
108
|
+
end: 0xfaff,
|
|
109
|
+
count: 472,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "compat-sup",
|
|
113
|
+
label: "中日韩兼容表意文字补充",
|
|
114
|
+
begin: 0x2f800,
|
|
115
|
+
end: 0x2fa1f,
|
|
116
|
+
count: 542,
|
|
117
|
+
},
|
|
118
|
+
// 古文字系统
|
|
119
|
+
{
|
|
120
|
+
name: "tangut",
|
|
121
|
+
label: "西夏文",
|
|
122
|
+
begin: 0x17000,
|
|
123
|
+
end: 0x187ff,
|
|
124
|
+
count: 6144,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "tangut-comp",
|
|
128
|
+
label: "西夏文字构件",
|
|
129
|
+
begin: 0x18800,
|
|
130
|
+
end: 0x18aff,
|
|
131
|
+
count: 768,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "tangut-sup",
|
|
135
|
+
label: "西夏文补充",
|
|
136
|
+
begin: 0x18d00,
|
|
137
|
+
end: 0x18d7f,
|
|
138
|
+
count: 31,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "tangut-comp-sup",
|
|
142
|
+
label: "西夏文字构件补充",
|
|
143
|
+
begin: 0x18d80,
|
|
144
|
+
end: 0x18dff,
|
|
145
|
+
count: 115,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "khitan-small",
|
|
149
|
+
label: "契丹小字",
|
|
150
|
+
begin: 0x18b00,
|
|
151
|
+
end: 0x18cff,
|
|
152
|
+
count: 470,
|
|
153
|
+
},
|
|
154
|
+
// 标点符号与排版字符
|
|
155
|
+
{
|
|
156
|
+
name: "punct",
|
|
157
|
+
label: "中日韩符号和标点",
|
|
158
|
+
begin: 0x3000,
|
|
159
|
+
end: 0x303f,
|
|
160
|
+
count: 64,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "enclosed",
|
|
164
|
+
label: "中日韩带圈字母和月份",
|
|
165
|
+
begin: 0x3200,
|
|
166
|
+
end: 0x32ff,
|
|
167
|
+
count: 255,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "enclosed-sup",
|
|
171
|
+
label: "中日韩带圈文字补充",
|
|
172
|
+
begin: 0x1f200,
|
|
173
|
+
end: 0x1f2ff,
|
|
174
|
+
count: 64,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "ideo-symbols",
|
|
178
|
+
label: "表意文字符号与标点",
|
|
179
|
+
begin: 0x16fe0,
|
|
180
|
+
end: 0x16fff,
|
|
181
|
+
count: 7,
|
|
182
|
+
},
|
|
183
|
+
// 私用区
|
|
184
|
+
{ name: "pua", label: "私用区 (BMP)", begin: 0xe000, end: 0xf8ff, count: 0 },
|
|
185
|
+
{
|
|
186
|
+
name: "pua-plane15",
|
|
187
|
+
label: "私用区 (Plane 15)",
|
|
188
|
+
begin: 0xf0000,
|
|
189
|
+
end: 0xffffd,
|
|
190
|
+
count: 0,
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
export const unicodeBlock = (code) => {
|
|
194
|
+
for (const block of unicodeBlocks) {
|
|
195
|
+
if (code >= block.begin && code <= block.end) {
|
|
196
|
+
return block.name;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return "unknown";
|
|
200
|
+
};
|
|
201
|
+
export const unicodeBlockRank = (code) => {
|
|
202
|
+
for (const [rank, block] of unicodeBlocks.entries()) {
|
|
203
|
+
if (code >= block.begin && code <= block.end) {
|
|
204
|
+
return rank;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return -1;
|
|
208
|
+
};
|
|
209
|
+
export const isValidCJKBasicChar = (char) => {
|
|
210
|
+
const code = char.codePointAt(0);
|
|
211
|
+
const block = unicodeBlock(code);
|
|
212
|
+
return block === "cjk";
|
|
213
|
+
};
|
|
214
|
+
export const isValidCJKChar = (char) => {
|
|
215
|
+
const code = char.codePointAt(0);
|
|
216
|
+
const block = unicodeBlock(code);
|
|
217
|
+
return block.startsWith("cjk");
|
|
218
|
+
};
|
|
219
|
+
export const isValidCJKSupplement = (char) => {
|
|
220
|
+
const code = char.codePointAt(0);
|
|
221
|
+
const block = unicodeBlock(code);
|
|
222
|
+
return (block === "radicals-sup" ||
|
|
223
|
+
block === "kangxi" ||
|
|
224
|
+
block === "strokes" ||
|
|
225
|
+
block === "compat" ||
|
|
226
|
+
block === "compat-sup" ||
|
|
227
|
+
block === "punct");
|
|
228
|
+
};
|
|
229
|
+
export const isPUA = (char) => {
|
|
230
|
+
const code = char.codePointAt(0);
|
|
231
|
+
const block = unicodeBlock(code);
|
|
232
|
+
return block === "pua" || block === "pua-plane15";
|
|
233
|
+
};
|
|
234
|
+
export const chars = (s) => {
|
|
235
|
+
return Array.from(s).length;
|
|
236
|
+
};
|
|
237
|
+
export const getDummyBasicComponent = () => ({
|
|
238
|
+
type: "basic_component",
|
|
239
|
+
strokes: [getDummySVGStroke("横")],
|
|
240
|
+
});
|
|
241
|
+
export const getDummyDerivedComponent = () => ({
|
|
242
|
+
type: "derived_component",
|
|
243
|
+
source: "一",
|
|
244
|
+
strokes: [getDummyReferenceStroke()],
|
|
245
|
+
});
|
|
246
|
+
export const getDummySplicedComponent = () => ({
|
|
247
|
+
type: "spliced_component",
|
|
248
|
+
operator: "⿰",
|
|
249
|
+
operandList: ["一", "丨"],
|
|
250
|
+
});
|
|
251
|
+
export const getDummyReferenceStroke = () => ({
|
|
252
|
+
feature: "reference",
|
|
253
|
+
index: 0,
|
|
254
|
+
});
|
|
255
|
+
export const getDummySVGStroke = (feature, start = [0, 0], oldCurveList = []) => {
|
|
256
|
+
const typelist = schema[feature];
|
|
257
|
+
return {
|
|
258
|
+
feature,
|
|
259
|
+
start,
|
|
260
|
+
curveList: typelist.map((command, index) => {
|
|
261
|
+
if (oldCurveList[index]?.command === command) {
|
|
262
|
+
return oldCurveList[index];
|
|
263
|
+
}
|
|
264
|
+
switch (command) {
|
|
265
|
+
case "a":
|
|
266
|
+
return { command, parameterList: [20] };
|
|
267
|
+
case "h":
|
|
268
|
+
case "v":
|
|
269
|
+
return { command, parameterList: [20] };
|
|
270
|
+
default:
|
|
271
|
+
return { command, parameterList: [10, 10, 20, 20, 30, 30] };
|
|
272
|
+
}
|
|
273
|
+
}),
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
export const getDummyCompound = (operator) => ({
|
|
277
|
+
type: "compound",
|
|
278
|
+
operator,
|
|
279
|
+
operandList: ["一", "一"],
|
|
280
|
+
});
|
|
281
|
+
export const isComponent = (glyph) => glyph.type === "basic_component" ||
|
|
282
|
+
glyph.type === "derived_component" ||
|
|
283
|
+
glyph.type === "spliced_component" ||
|
|
284
|
+
glyph.type === "identity";
|
|
285
|
+
export const getSupplemental = (repertoire, list) => {
|
|
286
|
+
const set = new Set(list);
|
|
287
|
+
const reverseForm = Object.fromEntries(Object.entries(repertoire).map(([x]) => [x, []]));
|
|
288
|
+
for (const [char, { glyph }] of Object.entries(repertoire)) {
|
|
289
|
+
if (glyph?.type === "compound") {
|
|
290
|
+
glyph.operandList.forEach((x) => reverseForm[x]?.push(char));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const componentsNotChar = Object.entries(repertoire)
|
|
294
|
+
.filter(([, v]) => v.glyph?.type === "basic_component")
|
|
295
|
+
.map(([x]) => x)
|
|
296
|
+
.filter((x) => !set.has(x));
|
|
297
|
+
const suppList = [];
|
|
298
|
+
componentsNotChar.forEach((char) => {
|
|
299
|
+
let trial = char;
|
|
300
|
+
while (trial && !set.has(trial)) {
|
|
301
|
+
trial = reverseForm[trial]?.[0];
|
|
302
|
+
}
|
|
303
|
+
if (trial)
|
|
304
|
+
suppList.push(trial);
|
|
305
|
+
});
|
|
306
|
+
return Array.from(new Set(suppList));
|
|
307
|
+
};
|
|
308
|
+
export const listToObject = (list) => Object.fromEntries(list.map((x) => [String.fromCodePoint(x.unicode), x]));
|
|
309
|
+
export function getRecordFromTSV(text) {
|
|
310
|
+
const tsv = text
|
|
311
|
+
.trim()
|
|
312
|
+
.split("\n")
|
|
313
|
+
.map((x) => x.trim().split("\t"));
|
|
314
|
+
const data = {};
|
|
315
|
+
tsv.forEach(([char, freq]) => {
|
|
316
|
+
if (char === undefined || freq === undefined)
|
|
317
|
+
return;
|
|
318
|
+
const maybeNumber = Number(freq);
|
|
319
|
+
if (Number.isNaN(maybeNumber))
|
|
320
|
+
return;
|
|
321
|
+
data[char] = maybeNumber;
|
|
322
|
+
});
|
|
323
|
+
return data;
|
|
324
|
+
}
|
|
325
|
+
export function getDistributionFromTSV(text) {
|
|
326
|
+
const tsv = text
|
|
327
|
+
.trim()
|
|
328
|
+
.split("\n")
|
|
329
|
+
.map((x) => x.trim().split("\t"));
|
|
330
|
+
const data = {};
|
|
331
|
+
tsv.forEach(([char, ideal_s, lt_penalty_s, gt_penalty_s]) => {
|
|
332
|
+
if (char === undefined ||
|
|
333
|
+
ideal_s === undefined ||
|
|
334
|
+
lt_penalty_s === undefined ||
|
|
335
|
+
gt_penalty_s === undefined)
|
|
336
|
+
return;
|
|
337
|
+
const [ideal, lt_penalty, gt_penalty] = [
|
|
338
|
+
ideal_s,
|
|
339
|
+
lt_penalty_s,
|
|
340
|
+
gt_penalty_s,
|
|
341
|
+
].map(Number);
|
|
342
|
+
if (Number.isNaN(ideal) ||
|
|
343
|
+
Number.isNaN(lt_penalty) ||
|
|
344
|
+
Number.isNaN(gt_penalty))
|
|
345
|
+
return;
|
|
346
|
+
data[char] = { ideal, lt_penalty, gt_penalty };
|
|
347
|
+
});
|
|
348
|
+
return data;
|
|
349
|
+
}
|
|
350
|
+
export function getDictFromTSV(text) {
|
|
351
|
+
const result = [];
|
|
352
|
+
for (const line of text.trim().split("\n")) {
|
|
353
|
+
const [key, value] = line.trim().split("\t");
|
|
354
|
+
if (key === undefined || value === undefined)
|
|
355
|
+
continue;
|
|
356
|
+
result.push([key, value]);
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
const processExport = (content, filename) => {
|
|
361
|
+
const blob = new Blob([content], { type: "text/plain" });
|
|
362
|
+
const a = document.createElement("a");
|
|
363
|
+
a.download = filename;
|
|
364
|
+
const url = window.URL.createObjectURL(blob);
|
|
365
|
+
a.href = url;
|
|
366
|
+
a.click();
|
|
367
|
+
window.URL.revokeObjectURL(url); // 避免内存泄漏
|
|
368
|
+
};
|
|
369
|
+
export const exportYAML = (config, filename, flowLevel = 4, sanitize = true) => {
|
|
370
|
+
const unsafeContent = dump(config, { flowLevel });
|
|
371
|
+
if (!sanitize) {
|
|
372
|
+
processExport(unsafeContent, `${filename}.yaml`);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
const fileContent = unsafeContent.replace(/[\uE000-\uFFFF]/g, (c) => {
|
|
376
|
+
return `"\\u${c.codePointAt(0)?.toString(16)}"`;
|
|
377
|
+
});
|
|
378
|
+
processExport(fileContent, `${filename}.yaml`);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
export const exportJSON = (data, filename) => {
|
|
382
|
+
const unsafeContent = JSON.stringify(data);
|
|
383
|
+
const fileContent = unsafeContent.replace(/[\uE000-\uFFFF]/g, (c) => {
|
|
384
|
+
return `\\u${c.codePointAt(0)?.toString(16)}`;
|
|
385
|
+
});
|
|
386
|
+
processExport(fileContent, filename);
|
|
387
|
+
};
|
|
388
|
+
export const exportTSV = (data, filename) => {
|
|
389
|
+
const fileContent = data.map((x) => x.join("\t")).join("\n");
|
|
390
|
+
processExport(fileContent, filename);
|
|
391
|
+
};
|
|
392
|
+
export const stringify = (element) => {
|
|
393
|
+
if (element === undefined) {
|
|
394
|
+
return "ε";
|
|
395
|
+
}
|
|
396
|
+
else if (typeof element === "string") {
|
|
397
|
+
return element;
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
return `${element.element}.${element.index}`;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
export const parseKey = (key) => {
|
|
404
|
+
if (key === "ε") {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
else if (key.includes(".")) {
|
|
408
|
+
const [element, index_s] = key.split(".");
|
|
409
|
+
const index = Number(index_s);
|
|
410
|
+
if (Number.isNaN(index)) {
|
|
411
|
+
throw new Error(`无法解析的键:${key}`);
|
|
412
|
+
}
|
|
413
|
+
return { element: element, index };
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
return key;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
export const joinKeys = (keys) => {
|
|
420
|
+
return keys.every((x) => typeof x === "string") ? keys.join("") : keys;
|
|
421
|
+
};
|
|
422
|
+
const match = (character, input) => {
|
|
423
|
+
const { tag, operator, part } = input;
|
|
424
|
+
if ("glyphs" in character) {
|
|
425
|
+
const isTagMatched = tag === undefined || character.glyphs.some((x) => x.tags?.includes(tag));
|
|
426
|
+
const isOperatorMatched = operator === undefined ||
|
|
427
|
+
character.glyphs.some((x) => "operator" in x && x.operator.includes(operator));
|
|
428
|
+
const isPartMatched = part === undefined ||
|
|
429
|
+
character.glyphs.some((x) => "operandList" in x && x.operandList.includes(part));
|
|
430
|
+
return isTagMatched && isOperatorMatched && isPartMatched;
|
|
431
|
+
}
|
|
432
|
+
const isTagMatched = tag === undefined || character.glyph?.tags?.includes(tag);
|
|
433
|
+
const isOperatorMatched = operator === undefined ||
|
|
434
|
+
(character.glyph?.type === "compound" &&
|
|
435
|
+
character.glyph.operator.includes(operator));
|
|
436
|
+
const isPartMatched = part === undefined ||
|
|
437
|
+
(character.glyph?.type === "compound" &&
|
|
438
|
+
character.glyph.operandList.includes(part));
|
|
439
|
+
return isTagMatched && isOperatorMatched && isPartMatched;
|
|
440
|
+
};
|
|
441
|
+
export const makeCharacterFilter = (input, repertoire, sequenceMap) => {
|
|
442
|
+
let sequenceRegex;
|
|
443
|
+
try {
|
|
444
|
+
if (input.sequence) {
|
|
445
|
+
sequenceRegex = new RegExp(input.sequence);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch { }
|
|
449
|
+
return (name) => {
|
|
450
|
+
const character = repertoire[name];
|
|
451
|
+
if (character === undefined)
|
|
452
|
+
return false;
|
|
453
|
+
const sequence = sequenceMap.get(name) ?? "";
|
|
454
|
+
const isNameMatched = ((character.name ?? "") + name).includes(input.name ?? "");
|
|
455
|
+
const isSequenceMatched = sequenceRegex?.test(sequence) ?? true;
|
|
456
|
+
const isUnicodeMatched = input.unicode === undefined || input.unicode === name.codePointAt(0);
|
|
457
|
+
const isMatched = match(character, input);
|
|
458
|
+
return isNameMatched && isSequenceMatched && isUnicodeMatched && isMatched;
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
export const makeFilter = (input, form, sequence) => (char) => {
|
|
462
|
+
if ((sequence.get(char)?.length ?? 0) <= 1)
|
|
463
|
+
return false;
|
|
464
|
+
const name = form[char]?.name ?? "";
|
|
465
|
+
const seq = sequence.get(char) ?? "";
|
|
466
|
+
return (name.includes(input) || char.includes(input) || seq.startsWith(input));
|
|
467
|
+
};
|
|
468
|
+
export const makeDefaultAnalyzer = (maxLength) => {
|
|
469
|
+
const form = {
|
|
470
|
+
type: "all",
|
|
471
|
+
position: range(0, maxLength),
|
|
472
|
+
top: 0,
|
|
473
|
+
};
|
|
474
|
+
return form;
|
|
475
|
+
};
|
|
476
|
+
function 最大逆向匹配分词(词典, 文本) {
|
|
477
|
+
const 词列表 = [];
|
|
478
|
+
const 字符列表 = [...文本];
|
|
479
|
+
let 结束位置 = 字符列表.length;
|
|
480
|
+
while (结束位置 > 0) {
|
|
481
|
+
const 末字 = 字符列表[结束位置 - 1];
|
|
482
|
+
if (!词典.has(末字)) {
|
|
483
|
+
词列表.unshift(末字);
|
|
484
|
+
结束位置 -= 1;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
for (let 开始位置 = 0; 开始位置 < 结束位置; 开始位置++) {
|
|
488
|
+
const 词 = 字符列表.slice(开始位置, 结束位置).join("");
|
|
489
|
+
if (词典.has(词)) {
|
|
490
|
+
词列表.unshift(词);
|
|
491
|
+
结束位置 = 开始位置;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return 词列表;
|
|
497
|
+
}
|
|
498
|
+
// 使用使用逆向最大匹配算法来分词
|
|
499
|
+
export const adapt = (原始词频映射, 词典) => {
|
|
500
|
+
const 新词频映射 = new Map();
|
|
501
|
+
for (const [词, 词频] of Object.entries(原始词频映射)) {
|
|
502
|
+
if (词典.has(词)) {
|
|
503
|
+
新词频映射.set(词, (新词频映射.get(词) ?? 0) + 词频);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
const 子词列表 = 最大逆向匹配分词(词典, 词);
|
|
507
|
+
for (const 子词 of 子词列表) {
|
|
508
|
+
新词频映射.set(子词, (新词频映射.get(子词) ?? 0) + 词频);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return 新词频映射;
|
|
513
|
+
};
|
|
514
|
+
export const getReversedMapping = (mapping, alphabet) => {
|
|
515
|
+
const reversedMapping = new Map(Array.from(alphabet).map((key) => [key, []]));
|
|
516
|
+
const recursivelyRenderKey = (key) => {
|
|
517
|
+
if (typeof key === "string")
|
|
518
|
+
return key;
|
|
519
|
+
const mapped = mapping[key.element];
|
|
520
|
+
if (typeof mapped === "object" && "element" in mapped) {
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
523
|
+
return recursivelyRenderKey(mapped[key.index]);
|
|
524
|
+
};
|
|
525
|
+
for (const [name, code] of Object.entries(mapping)) {
|
|
526
|
+
if (typeof code === "string" || Array.isArray(code)) {
|
|
527
|
+
const first = recursivelyRenderKey(code[0]);
|
|
528
|
+
reversedMapping.get(first)?.push({ name, code });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return reversedMapping;
|
|
532
|
+
};
|
|
533
|
+
export const characterSetFilters = {
|
|
534
|
+
minimal: (_, v) => v.gb2312 > 0 && v.tygf > 0,
|
|
535
|
+
gb2312: (_, v) => v.gb2312 > 0,
|
|
536
|
+
general: (_, v) => v.tygf > 0,
|
|
537
|
+
basic: (k, v) => v.tygf > 0 || isValidCJKBasicChar(k),
|
|
538
|
+
extended: (k, v) => v.tygf > 0 || isValidCJKChar(k),
|
|
539
|
+
supplement: (k, v) => v.tygf > 0 || isValidCJKChar(k) || isValidCJKSupplement(k),
|
|
540
|
+
maximal: (k, v) => !isPUA(k),
|
|
541
|
+
custom: (k, v, s) => {
|
|
542
|
+
if (s !== undefined)
|
|
543
|
+
return s.has(k);
|
|
544
|
+
return v.gb2312 > 0 && v.tygf > 0;
|
|
545
|
+
},
|
|
546
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hanzi-chai",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "提取自主仓库的汉字拆分/编码核心算法包",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json && cp -r src/data dist/data",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"testui": "vitest --ui --coverage",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
23
|
+
"@vitest/ui": "^4.0.16",
|
|
24
|
+
"d3-array": "^3.0.2",
|
|
25
|
+
"js-yaml": "^4.1.0",
|
|
26
|
+
"lodash-es": "^4.17.21"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.0.3",
|
|
30
|
+
"tsup": "^6.0.0",
|
|
31
|
+
"typescript": "^5.0.0",
|
|
32
|
+
"vitest": "^4.0.16"
|
|
33
|
+
}
|
|
34
|
+
}
|