@kensio/pinyinjs 1.5.3 → 1.7.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/README.md +252 -5
- package/data/core.entries +462 -460
- package/data/core.freq +0 -0
- package/data/core.keys +2 -0
- package/data/full.entries +13732 -13734
- package/data/full.freq +0 -0
- package/data/full.keys +0 -2
- package/data/manifest.json +15 -15
- package/data/script.map +34 -55
- package/data/standard.entries +1354 -1352
- package/data/standard.freq +0 -0
- package/data/standard.keys +2 -0
- package/dist/cli/arguments.d.ts +38 -0
- package/dist/cli/arguments.d.ts.map +1 -1
- package/dist/cli/arguments.js +46 -0
- package/dist/cli/arguments.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +229 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +5 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/decode/convert.d.ts +58 -0
- package/dist/decode/convert.d.ts.map +1 -1
- package/dist/decode/convert.js +135 -37
- package/dist/decode/convert.js.map +1 -1
- package/dist/decode/decode.d.ts +3 -2
- package/dist/decode/decode.d.ts.map +1 -1
- package/dist/decode/decode.js +15 -10
- package/dist/decode/decode.js.map +1 -1
- package/dist/decode/hints.d.ts +138 -0
- package/dist/decode/hints.d.ts.map +1 -0
- package/dist/decode/hints.js +214 -0
- package/dist/decode/hints.js.map +1 -0
- package/dist/decode/lattice.d.ts +9 -1
- package/dist/decode/lattice.d.ts.map +1 -1
- package/dist/decode/lattice.js +5 -3
- package/dist/decode/lattice.js.map +1 -1
- package/dist/decode/reading-rules.d.ts +28 -0
- package/dist/decode/reading-rules.d.ts.map +1 -1
- package/dist/decode/reading-rules.js +157 -0
- package/dist/decode/reading-rules.js.map +1 -1
- package/dist/decode/runs.d.ts +11 -0
- package/dist/decode/runs.d.ts.map +1 -1
- package/dist/decode/runs.js +19 -0
- package/dist/decode/runs.js.map +1 -1
- package/dist/decode/segment.d.ts +66 -0
- package/dist/decode/segment.d.ts.map +1 -0
- package/dist/decode/segment.js +79 -0
- package/dist/decode/segment.js.map +1 -0
- package/dist/dictionary/artifact.d.ts +10 -0
- package/dist/dictionary/artifact.d.ts.map +1 -1
- package/dist/dictionary/artifact.js +17 -0
- package/dist/dictionary/artifact.js.map +1 -1
- package/dist/dictionary/assertions.d.ts.map +1 -1
- package/dist/dictionary/assertions.js +8 -0
- package/dist/dictionary/assertions.js.map +1 -1
- package/dist/dictionary/corpus-mass.d.ts +68 -0
- package/dist/dictionary/corpus-mass.d.ts.map +1 -0
- package/dist/dictionary/corpus-mass.js +123 -0
- package/dist/dictionary/corpus-mass.js.map +1 -0
- package/dist/dictionary/dictionary.d.ts +56 -0
- package/dist/dictionary/dictionary.d.ts.map +1 -1
- package/dist/dictionary/dictionary.js +116 -1
- package/dist/dictionary/dictionary.js.map +1 -1
- package/dist/dictionary/erhua.d.ts +4 -0
- package/dist/dictionary/erhua.d.ts.map +1 -1
- package/dist/dictionary/erhua.js +6 -0
- package/dist/dictionary/erhua.js.map +1 -1
- package/dist/dictionary/frequency-table.js +1 -1
- package/dist/dictionary/locale.js +2 -2
- package/dist/dictionary/merge.d.ts.map +1 -1
- package/dist/dictionary/merge.js +46 -9
- package/dist/dictionary/merge.js.map +1 -1
- package/dist/dictionary/tiers.d.ts +2 -1
- package/dist/dictionary/tiers.d.ts.map +1 -1
- package/dist/dictionary/tiers.js +77 -11
- package/dist/dictionary/tiers.js.map +1 -1
- package/dist/format/html.d.ts +32 -0
- package/dist/format/html.d.ts.map +1 -1
- package/dist/format/html.js +144 -0
- package/dist/format/html.js.map +1 -1
- package/dist/grade/check.d.ts +156 -0
- package/dist/grade/check.d.ts.map +1 -0
- package/dist/grade/check.js +356 -0
- package/dist/grade/check.js.map +1 -0
- package/dist/index.d.ts +14 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/search/candidates.d.ts +84 -0
- package/dist/search/candidates.d.ts.map +1 -0
- package/dist/search/candidates.js +290 -0
- package/dist/search/candidates.js.map +1 -0
- package/dist/search/match.d.ts +72 -0
- package/dist/search/match.d.ts.map +1 -0
- package/dist/search/match.js +419 -0
- package/dist/search/match.js.map +1 -0
- package/dist/search/query.d.ts +49 -0
- package/dist/search/query.d.ts.map +1 -0
- package/dist/search/query.js +115 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/reverse-index.d.ts +141 -0
- package/dist/search/reverse-index.d.ts.map +1 -0
- package/dist/search/reverse-index.js +356 -0
- package/dist/search/reverse-index.js.map +1 -0
- package/dist/sources/unihan.d.ts +22 -4
- package/dist/sources/unihan.d.ts.map +1 -1
- package/dist/sources/unihan.js +74 -13
- package/dist/sources/unihan.js.map +1 -1
- package/package.json +2 -3
- package/CHANGELOG.md +0 -485
|
@@ -37,7 +37,8 @@ export declare const STANDARD_TIER_WORDS = 50000;
|
|
|
37
37
|
* makes a dictionary usable at all: they cover roughly half of running text on
|
|
38
38
|
* their own, and they are the fallback for every position no word matches.
|
|
39
39
|
* Tiering cuts the phrase tail and the unused characters, never a character
|
|
40
|
-
* that some word is written with
|
|
40
|
+
* that some word is written with — and never one the tier is keyed for anyway,
|
|
41
|
+
* which is what {@link charactersHeld} adds on top.
|
|
41
42
|
*
|
|
42
43
|
* `full` holds everything, including the characters no source uses in a word.
|
|
43
44
|
* They cost little there and are the difference between reading a rare
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiers.d.ts","sourceRoot":"","sources":["../../src/dictionary/tiers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tiers.d.ts","sourceRoot":"","sources":["../../src/dictionary/tiers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,YAAI,MAAM,EAAE,UAAU,EAAE,MAAM,CAAU,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SAAwB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAqH1C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,IAAI,EAAE,IAAI,GACT,SAAS,eAAe,EAAE,CAkB5B"}
|
package/dist/dictionary/tiers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSingleCharacter, toCharacters } from "../script/characters.js";
|
|
2
|
+
import { traditionalForms } from "./artifact.js";
|
|
2
3
|
/**
|
|
3
4
|
* The dictionary tiers, smallest first and each contained in the next.
|
|
4
5
|
*
|
|
@@ -65,6 +66,71 @@ function charactersInUse(entries) {
|
|
|
65
66
|
}
|
|
66
67
|
return inUse;
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The characters a tier holds: the ones in use, and the ones it has to answer.
|
|
71
|
+
*
|
|
72
|
+
* "In use" alone is not enough, because a tier is keyed by more than the
|
|
73
|
+
* headwords it holds — an entry claims every 繁體 spelling attested for it, and
|
|
74
|
+
* some of those spellings are rare characters with entries of their own. A tier
|
|
75
|
+
* holding 卒 is keyed for 䘚 whether or not it holds 䘚, so without the
|
|
76
|
+
* character's own entry it answers that key from the 卒 that reached it
|
|
77
|
+
* sideways and reads `zú`, where `full` lets 䘚's own entry win the key and
|
|
78
|
+
* reads `zhú`. Neither reading is wrong — 䘚 written for 卒 really is `zú` —
|
|
79
|
+
* but which one you get must not depend on how much of the dictionary has
|
|
80
|
+
* loaded, or the documented upgrade path contradicts itself mid-paragraph.
|
|
81
|
+
*
|
|
82
|
+
* Admitting the entry is all it takes: the artifact's `claimKeys` puts
|
|
83
|
+
* headwords ahead of 繁體 aliases in every tier, so once 䘚 is present it wins
|
|
84
|
+
* its own key exactly as it does in `full`.
|
|
85
|
+
*
|
|
86
|
+
* It repeats to a fixpoint because an admitted character claims spellings of
|
|
87
|
+
* its own, and one of those can be another character with an entry behind it.
|
|
88
|
+
* On the current sources it settles at once — 242 characters admitted, then a
|
|
89
|
+
* confirming pass that admits none — but nothing in the merge rules a chain
|
|
90
|
+
* out, and the pass is a set lookup per entry against a build that reads four
|
|
91
|
+
* source files.
|
|
92
|
+
*/
|
|
93
|
+
function charactersHeld(entries, words) {
|
|
94
|
+
const headed = new Set();
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (isSingleCharacter(entry.hans)) {
|
|
97
|
+
headed.add(entry.hans);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const inUse = charactersInUse(entries);
|
|
101
|
+
const held = new Set();
|
|
102
|
+
for (const character of headed) {
|
|
103
|
+
if (inUse.has(character)) {
|
|
104
|
+
held.add(character);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Admit the characters this entry claims the key of, reporting any news.
|
|
109
|
+
*/
|
|
110
|
+
const admitClaims = (entry) => {
|
|
111
|
+
let admitted = false;
|
|
112
|
+
for (const form of traditionalForms(entry)) {
|
|
113
|
+
if (headed.has(form) && !held.has(form)) {
|
|
114
|
+
held.add(form);
|
|
115
|
+
admitted = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return admitted;
|
|
119
|
+
};
|
|
120
|
+
for (const entry of words) {
|
|
121
|
+
admitClaims(entry);
|
|
122
|
+
}
|
|
123
|
+
let growing = true;
|
|
124
|
+
while (growing) {
|
|
125
|
+
growing = false;
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (isSingleCharacter(entry.hans) && held.has(entry.hans)) {
|
|
128
|
+
growing = admitClaims(entry) || growing;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return held;
|
|
133
|
+
}
|
|
68
134
|
/**
|
|
69
135
|
* The entries a tier contains.
|
|
70
136
|
*
|
|
@@ -72,7 +138,8 @@ function charactersInUse(entries) {
|
|
|
72
138
|
* makes a dictionary usable at all: they cover roughly half of running text on
|
|
73
139
|
* their own, and they are the fallback for every position no word matches.
|
|
74
140
|
* Tiering cuts the phrase tail and the unused characters, never a character
|
|
75
|
-
* that some word is written with
|
|
141
|
+
* that some word is written with — and never one the tier is keyed for anyway,
|
|
142
|
+
* which is what {@link charactersHeld} adds on top.
|
|
76
143
|
*
|
|
77
144
|
* `full` holds everything, including the characters no source uses in a word.
|
|
78
145
|
* They cost little there and are the difference between reading a rare
|
|
@@ -82,15 +149,14 @@ export function selectTier(entries, tier) {
|
|
|
82
149
|
if (tier === "full") {
|
|
83
150
|
return entries;
|
|
84
151
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return [...characters, ...words];
|
|
152
|
+
const words = tier === "core"
|
|
153
|
+
? []
|
|
154
|
+
: entries
|
|
155
|
+
.filter((entry) => !isSingleCharacter(entry.hans))
|
|
156
|
+
.toSorted(byFrequency)
|
|
157
|
+
.slice(0, STANDARD_TIER_WORDS);
|
|
158
|
+
const held = charactersHeld(entries, words);
|
|
159
|
+
const characters = entries.filter((entry) => isSingleCharacter(entry.hans) && held.has(entry.hans));
|
|
160
|
+
return tier === "core" ? characters : [...characters, ...words];
|
|
95
161
|
}
|
|
96
162
|
//# sourceMappingURL=tiers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiers.js","sourceRoot":"","sources":["../../src/dictionary/tiers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"tiers.js","sourceRoot":"","sources":["../../src/dictionary/tiers.ts"],"names":[],"mappings":"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"}
|
package/dist/format/html.d.ts
CHANGED
|
@@ -35,6 +35,38 @@ export interface HtmlOptions extends ConvertOptions {
|
|
|
35
35
|
* once and render more than one way.
|
|
36
36
|
*/
|
|
37
37
|
export declare function toHtml(pieces: readonly ConvertedPiece[], options?: HtmlOptions): string;
|
|
38
|
+
/**
|
|
39
|
+
* Mark up a converted text as hanzi with its reading above, syllable by
|
|
40
|
+
* syllable.
|
|
41
|
+
*
|
|
42
|
+
* The output every other mode cannot give: `convert` and {@link toHtml} write
|
|
43
|
+
* the pinyin *instead of* the hanzi, and a learner's text, a subtitle and a
|
|
44
|
+
* dictionary entry all want both at once. The markup is `<ruby>`, which every
|
|
45
|
+
* current browser lays out natively and which degrades to parentheses through
|
|
46
|
+
* `<rp>` where it is not supported.
|
|
47
|
+
*
|
|
48
|
+
* ```html
|
|
49
|
+
* <ruby lang="zh">银<rp>(</rp><rt><span …>yín</span></rt><rp>)</rp></ruby>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Each `<rt>` holds whatever {@link toHtml} would have written for those
|
|
53
|
+
* syllables, so tone classes and uncertainty marking work inside an annotation
|
|
54
|
+
* exactly as they do outside one.
|
|
55
|
+
*
|
|
56
|
+
* **A base is not always one character.** 玩儿 is two characters and the one
|
|
57
|
+
* syllable `wánr`, and a read number reverses on the way — 95% is
|
|
58
|
+
* `bǎifēnzhījiǔshíwǔ` — so both are annotated whole. Splitting them per
|
|
59
|
+
* character is what everything that emits ruby from a pinyin library gets
|
|
60
|
+
* wrong, and it is why {@link ConvertedPiece.source} exists.
|
|
61
|
+
*/
|
|
62
|
+
export declare function toAnnotatedHtml(pieces: readonly ConvertedPiece[], options?: HtmlOptions): string;
|
|
63
|
+
/**
|
|
64
|
+
* Convert hanzi to pinyin as HTML, hanzi and reading together.
|
|
65
|
+
*
|
|
66
|
+
* {@link toAnnotatedHtml} applied to a fresh conversion; see it for what the
|
|
67
|
+
* markup is and why a base is not always one character.
|
|
68
|
+
*/
|
|
69
|
+
export declare function convertToAnnotatedHtml(dictionary: Dictionary, text: string, options?: HtmlOptions): string;
|
|
38
70
|
/**
|
|
39
71
|
* Convert hanzi to pinyin as HTML, one element per syllable.
|
|
40
72
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/format/html.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAI9D;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AA+HD;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,GAAE,WAAgB,GACxB,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CAER"}
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/format/html.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAI9D;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AA+HD;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,GAAE,WAAgB,GACxB,MAAM,CAER;AA+HD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,GAAE,WAAgB,GACxB,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,CAER"}
|
package/dist/format/html.js
CHANGED
|
@@ -122,6 +122,150 @@ function writePiece(piece, options) {
|
|
|
122
122
|
export function toHtml(pieces, options = {}) {
|
|
123
123
|
return pieces.map((piece) => writePiece(piece, options)).join("");
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* What the characters being annotated declare themselves to be.
|
|
127
|
+
*
|
|
128
|
+
* The mirror of {@link languageTag}: inside an annotation the base is hanzi and
|
|
129
|
+
* only the reading is pinyin, so on an English page the two need different tags
|
|
130
|
+
* and neither can be left to inherit.
|
|
131
|
+
*
|
|
132
|
+
* **`zh`, with no script subtag, and that is deliberate.** `Hans` or `Hant`
|
|
133
|
+
* would be better for font selection, and neither can be known here. The
|
|
134
|
+
* conversion's locale does not settle it — script and locale are separate axes,
|
|
135
|
+
* and a mainland edition of a classical text is 繁體 read as `zh-CN` — and
|
|
136
|
+
* detecting it from the characters needs the script tables, which load
|
|
137
|
+
* separately from the dictionary precisely so that converting does not pay for
|
|
138
|
+
* them. A tag that is merely unspecific is worth more than one that is wrong,
|
|
139
|
+
* so a caller who knows the script should wrap the output and say so there.
|
|
140
|
+
*/
|
|
141
|
+
const BASE_TAG = "zh";
|
|
142
|
+
/**
|
|
143
|
+
* Gather pieces into the bases they are readings of.
|
|
144
|
+
*
|
|
145
|
+
* A piece naming characters opens a group and every piece after it that names
|
|
146
|
+
* none reads on into the same one, which is how 玩儿 keeps `wánr` over both
|
|
147
|
+
* characters and 95% keeps `bǎifēnzhījiǔshíwǔ` over all three. Pieces writing
|
|
148
|
+
* no reading at all — the spaces between words, and runs that were never Han —
|
|
149
|
+
* are their own groups, with nothing to annotate.
|
|
150
|
+
*/
|
|
151
|
+
function annotationsOf(pieces) {
|
|
152
|
+
const groups = [];
|
|
153
|
+
for (const [at, piece] of pieces.entries()) {
|
|
154
|
+
const open = groups.at(-1);
|
|
155
|
+
if (open?.source !== undefined && continues(piece, pieces[at + 1])) {
|
|
156
|
+
groups[groups.length - 1] = {
|
|
157
|
+
source: open.source,
|
|
158
|
+
pieces: [...open.pieces, piece],
|
|
159
|
+
};
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
groups.push({ source: piece.source, pieces: [piece] });
|
|
163
|
+
}
|
|
164
|
+
return groups;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Whether a piece belongs to the annotation the group before it opened.
|
|
168
|
+
*
|
|
169
|
+
* Three kinds do:
|
|
170
|
+
*
|
|
171
|
+
* - A **syllable naming no characters** is reading on into the ones already
|
|
172
|
+
* named, which is 儿化 and every syllable of a read number after the first.
|
|
173
|
+
* - A **separator that is not a space** is a mark *inside* one orthographic
|
|
174
|
+
* word rather than a gap between two — the hyphen of `gāngān-jìngjìng`, the
|
|
175
|
+
* only one GB/T 16159 writes — so it belongs beside the syllables it divides.
|
|
176
|
+
* - A **space with more of the same reading after it**. 1988 is read
|
|
177
|
+
* `yī jiǔ bā bā`, four syllables over one base, and the spaces between them
|
|
178
|
+
* are inside that reading rather than between two of them.
|
|
179
|
+
*
|
|
180
|
+
* That last one is why this needs the piece after it. A space is written the
|
|
181
|
+
* same way whether it separates two words or two syllables of one number, and
|
|
182
|
+
* what tells them apart is whether what follows is still reading the same
|
|
183
|
+
* characters. Told apart wrongly, the reading escapes its own annotation:
|
|
184
|
+
* 1988年 came out as `1988` over `yī` with `jiǔ bā bā` loose beside it.
|
|
185
|
+
*
|
|
186
|
+
* A space between two words is the case that does not continue, and dropping it
|
|
187
|
+
* is the point: the hanzi it annotates has no space in it, and each base is
|
|
188
|
+
* already its own group on the page.
|
|
189
|
+
*/
|
|
190
|
+
function continues(piece, next) {
|
|
191
|
+
if (piece.syllable !== undefined) {
|
|
192
|
+
return piece.source === undefined;
|
|
193
|
+
}
|
|
194
|
+
if (piece.source !== undefined) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return (piece.text.trim() !== "" ||
|
|
198
|
+
(next?.syllable !== undefined && next.source === undefined));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Mark up one base and its reading.
|
|
202
|
+
*/
|
|
203
|
+
function writeAnnotation(annotation, options) {
|
|
204
|
+
const { lang = true } = options;
|
|
205
|
+
const { source, pieces } = annotation;
|
|
206
|
+
if (pieces.every((piece) => piece.syllable === undefined)) {
|
|
207
|
+
// Nothing is read here. Either it is source text that simply has no
|
|
208
|
+
// reading — punctuation, a Latin word — and is written as the author wrote
|
|
209
|
+
// it, or it is pinyin orthography with no source behind it at all: the
|
|
210
|
+
// space between two words, and the hyphen in `gāngān-jìngjìng`. Writing
|
|
211
|
+
// those into the hanzi would annotate 干干净净 as 干干-净净.
|
|
212
|
+
return source === undefined ? "" : escape(source);
|
|
213
|
+
}
|
|
214
|
+
if (source === undefined) {
|
|
215
|
+
// A reading whose piece never named what it reads. Nothing a conversion
|
|
216
|
+
// builds looks like this — every group is opened by a piece carrying its
|
|
217
|
+
// characters — but {@link toAnnotatedHtml} takes pieces from the caller,
|
|
218
|
+
// and there is no base to hang an annotation on, so it is written as the
|
|
219
|
+
// plain markup it would have got from {@link toHtml}.
|
|
220
|
+
return pieces.map((piece) => writePiece(piece, options)).join("");
|
|
221
|
+
}
|
|
222
|
+
const reading = pieces.map((piece) => writePiece(piece, options)).join("");
|
|
223
|
+
// The tag goes on the <ruby> rather than around the base, and the syllables
|
|
224
|
+
// inside the <rt> override it with their own. `<rb>` would be the obvious
|
|
225
|
+
// place for it and is not an element any more: the WHATWG parser drops it,
|
|
226
|
+
// leaving the base as bare text, so writing one would mean emitting markup
|
|
227
|
+
// that no browser keeps.
|
|
228
|
+
const opening = lang ? `<ruby lang="${BASE_TAG}">` : "<ruby>";
|
|
229
|
+
return `${opening}${escape(source)}<rp>(</rp><rt>${reading}</rt><rp>)</rp></ruby>`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Mark up a converted text as hanzi with its reading above, syllable by
|
|
233
|
+
* syllable.
|
|
234
|
+
*
|
|
235
|
+
* The output every other mode cannot give: `convert` and {@link toHtml} write
|
|
236
|
+
* the pinyin *instead of* the hanzi, and a learner's text, a subtitle and a
|
|
237
|
+
* dictionary entry all want both at once. The markup is `<ruby>`, which every
|
|
238
|
+
* current browser lays out natively and which degrades to parentheses through
|
|
239
|
+
* `<rp>` where it is not supported.
|
|
240
|
+
*
|
|
241
|
+
* ```html
|
|
242
|
+
* <ruby lang="zh">银<rp>(</rp><rt><span …>yín</span></rt><rp>)</rp></ruby>
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* Each `<rt>` holds whatever {@link toHtml} would have written for those
|
|
246
|
+
* syllables, so tone classes and uncertainty marking work inside an annotation
|
|
247
|
+
* exactly as they do outside one.
|
|
248
|
+
*
|
|
249
|
+
* **A base is not always one character.** 玩儿 is two characters and the one
|
|
250
|
+
* syllable `wánr`, and a read number reverses on the way — 95% is
|
|
251
|
+
* `bǎifēnzhījiǔshíwǔ` — so both are annotated whole. Splitting them per
|
|
252
|
+
* character is what everything that emits ruby from a pinyin library gets
|
|
253
|
+
* wrong, and it is why {@link ConvertedPiece.source} exists.
|
|
254
|
+
*/
|
|
255
|
+
export function toAnnotatedHtml(pieces, options = {}) {
|
|
256
|
+
return annotationsOf(pieces)
|
|
257
|
+
.map((annotation) => writeAnnotation(annotation, options))
|
|
258
|
+
.join("");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Convert hanzi to pinyin as HTML, hanzi and reading together.
|
|
262
|
+
*
|
|
263
|
+
* {@link toAnnotatedHtml} applied to a fresh conversion; see it for what the
|
|
264
|
+
* markup is and why a base is not always one character.
|
|
265
|
+
*/
|
|
266
|
+
export function convertToAnnotatedHtml(dictionary, text, options = {}) {
|
|
267
|
+
return toAnnotatedHtml(convertPieces(dictionary, text, options), options);
|
|
268
|
+
}
|
|
125
269
|
/**
|
|
126
270
|
* Convert hanzi to pinyin as HTML, one element per syllable.
|
|
127
271
|
*
|
package/dist/format/html.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/format/html.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/format/html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type ConvertOptions } from "../decode/convert.js";
|
|
2
|
+
import type { Dictionary } from "../dictionary/dictionary.js";
|
|
3
|
+
import { type Syllable } from "../syllable/syllable.js";
|
|
4
|
+
/**
|
|
5
|
+
* What one syllable of a typed transcription turned out to be.
|
|
6
|
+
*
|
|
7
|
+
* `toneless` is deliberately separate from `tone`: {@link Syllable.tone} says
|
|
8
|
+
* whether a tone was written at all, so "you left the tones off" is a different
|
|
9
|
+
* thing to report than "you wrote the wrong one", and only the second is a
|
|
10
|
+
* mistake about the language. {@link CheckOptions.tones} decides which of them
|
|
11
|
+
* counts against the score.
|
|
12
|
+
*/
|
|
13
|
+
export type PinyinVerdict = "correct" | "toneless" | "tone" | "wrong" | "missing" | "extra";
|
|
14
|
+
/**
|
|
15
|
+
* Where a typed syllable falls against the word boundaries the reading has.
|
|
16
|
+
*
|
|
17
|
+
* `split` is a word written as two — `yín háng` for 银行 — and `joined` is two
|
|
18
|
+
* words run into one. Both are reported whatever {@link CheckOptions.spacing}
|
|
19
|
+
* says, so an application can show them without having to grade them.
|
|
20
|
+
*/
|
|
21
|
+
export type SpacingVerdict = "correct" | "split" | "joined";
|
|
22
|
+
/**
|
|
23
|
+
* Whether one dimension of the writing has to be right for a perfect score.
|
|
24
|
+
*
|
|
25
|
+
* `optional` still reports the verdict and counts the syllable correct anyway;
|
|
26
|
+
* `required` counts it wrong. Both dimensions this applies to — tones and word
|
|
27
|
+
* spacing — are things an exercise may or may not be teaching yet.
|
|
28
|
+
*/
|
|
29
|
+
export type CheckRequirement = "optional" | "required";
|
|
30
|
+
/**
|
|
31
|
+
* How a transcription should be checked.
|
|
32
|
+
*
|
|
33
|
+
* Every {@link ConvertOptions} field is accepted and passed to the conversion
|
|
34
|
+
* the answer is read from, which is what `readings` is worth having here for:
|
|
35
|
+
* an exercise knows which sense of 长 its own sentence uses, where the decoder
|
|
36
|
+
* can only weigh the evidence.
|
|
37
|
+
*/
|
|
38
|
+
export interface CheckOptions extends ConvertOptions {
|
|
39
|
+
/** Whether a tone has to be written. Defaults to `optional`. */
|
|
40
|
+
readonly tones?: CheckRequirement;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the word spacing has to be right. Defaults to `optional`.
|
|
43
|
+
*
|
|
44
|
+
* Off by default because the 分词连写 this grades against is not a complete
|
|
45
|
+
* 正词法 implementation — see [orthography](../../docs/orthography/) for
|
|
46
|
+
* where it stops — so a learner can write a word the standard writes
|
|
47
|
+
* differently from the way this does. On for an exercise teaching the
|
|
48
|
+
* spacing, where getting it right is the point.
|
|
49
|
+
*/
|
|
50
|
+
readonly spacing?: CheckRequirement;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* One syllable of the check: what was expected there, and what was typed.
|
|
54
|
+
*/
|
|
55
|
+
export interface CheckedSyllable {
|
|
56
|
+
readonly verdict: PinyinVerdict;
|
|
57
|
+
/**
|
|
58
|
+
* Where this syllable falls against the word boundaries, where both a reading
|
|
59
|
+
* and a typed syllable met here.
|
|
60
|
+
*
|
|
61
|
+
* A separate axis from {@link CheckedSyllable.verdict}, because it is a
|
|
62
|
+
* separate mistake: `yín háng` reads 银行 perfectly and writes it as two
|
|
63
|
+
* words. Undefined for a syllable that was only expected or only typed.
|
|
64
|
+
*/
|
|
65
|
+
readonly spacing: SpacingVerdict | undefined;
|
|
66
|
+
/** Whether this counts as right in {@link PinyinCheck.score}. */
|
|
67
|
+
readonly isCorrect: boolean;
|
|
68
|
+
/** The reading expected here, or undefined where nothing was expected. */
|
|
69
|
+
readonly expected: Syllable | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* What was typed here, parsed, or undefined where nothing was typed.
|
|
72
|
+
*
|
|
73
|
+
* Also undefined for something typed that is not a syllable at all, which
|
|
74
|
+
* {@link CheckedSyllable.text} still reports as written.
|
|
75
|
+
*/
|
|
76
|
+
readonly actual: Syllable | undefined;
|
|
77
|
+
/** What was typed here, exactly as written, or empty where nothing was. */
|
|
78
|
+
readonly text: string;
|
|
79
|
+
/**
|
|
80
|
+
* The characters the expected syllable reads.
|
|
81
|
+
*
|
|
82
|
+
* Undefined where a syllable reads on into the characters named before it,
|
|
83
|
+
* exactly as {@link ConvertedPiece.source} is, and where nothing was
|
|
84
|
+
* expected at all.
|
|
85
|
+
*/
|
|
86
|
+
readonly source: string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Where those characters start, in code points from the start of the text.
|
|
89
|
+
*
|
|
90
|
+
* What highlighting the mistake in the text itself needs, and counted the way
|
|
91
|
+
* {@link import("../decode/segment.js").Segment.at} counts.
|
|
92
|
+
*/
|
|
93
|
+
readonly at: number | undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A typed transcription marked against the text it was written for.
|
|
97
|
+
*/
|
|
98
|
+
export interface PinyinCheck {
|
|
99
|
+
/** One entry per syllable expected or typed, in order. */
|
|
100
|
+
readonly syllables: readonly CheckedSyllable[];
|
|
101
|
+
/** Whether every syllable counted as correct. */
|
|
102
|
+
readonly isCorrect: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The share of the syllables that counted as correct, from 0 to 1.
|
|
105
|
+
*
|
|
106
|
+
* Over the entries reported rather than over the expected reading, so that
|
|
107
|
+
* inventing a syllable costs as much as dropping one: an answer with an extra
|
|
108
|
+
* syllable in it has more entries than the reading has syllables.
|
|
109
|
+
*/
|
|
110
|
+
readonly score: number;
|
|
111
|
+
/** The reading that was expected, as the conversion writes it. */
|
|
112
|
+
readonly reading: readonly Syllable[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Mark a typed pinyin transcription against the text it was written for.
|
|
116
|
+
*
|
|
117
|
+
* ```ts
|
|
118
|
+
* check(dictionary, "北京", "bei3jing3").syllables.map((one) => one.verdict);
|
|
119
|
+
* // ["correct", "tone"]
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* **The point of doing this with a dictionary is being fair in the ways a
|
|
123
|
+
* string comparison cannot be**, and every one of these is a learner being
|
|
124
|
+
* marked wrong for being right:
|
|
125
|
+
*
|
|
126
|
+
* - **Either notation, mixed freely.** `běi` and `bei3` are the same syllable
|
|
127
|
+
* and both parse, so `bei3jīng` is not a spelling mistake.
|
|
128
|
+
* - **A reading the decoder itself was unsure of.** Where another reading of
|
|
129
|
+
* the same character was there for the taking, it is accepted — see
|
|
130
|
+
* {@link rejectedReadings}. 银行 is not one of those: the word settles both
|
|
131
|
+
* syllables, so `yínxíng` really is wrong.
|
|
132
|
+
* - **Sandhi either way.** 你好 is written `nǐ hǎo` and said `ní hǎo`, and 不是
|
|
133
|
+
* is `bú shì` written and `bù shì` underneath. Both pass.
|
|
134
|
+
* - **Tones written or not.** A syllable typed with no tone is `toneless`
|
|
135
|
+
* rather than `tone`, and {@link CheckOptions.tones} says whether that counts
|
|
136
|
+
* against the score. A neutral tone typed without a mark is simply correct,
|
|
137
|
+
* because that is how pinyin writes it.
|
|
138
|
+
* - **Apostrophes.** Not a word boundary and not a sound: `nǐhǎo`, `nǐ hǎo` and
|
|
139
|
+
* `nǐ'hǎo` all read the same, and check the same.
|
|
140
|
+
*
|
|
141
|
+
* Word spacing is graded on its own axis, reported as
|
|
142
|
+
* {@link CheckedSyllable.spacing} and counted only where
|
|
143
|
+
* {@link CheckOptions.spacing} asks for it — and tolerant in the same spirit,
|
|
144
|
+
* since 分词连写 and the words the dictionary knows are two conventions this
|
|
145
|
+
* package writes and a learner may have been taught either.
|
|
146
|
+
*
|
|
147
|
+
* What comes back is one {@link CheckedSyllable} per syllable expected or
|
|
148
|
+
* typed, in order, each naming the characters it reads and where they are, so
|
|
149
|
+
* that a mistake can be shown against the text rather than against the answer.
|
|
150
|
+
*
|
|
151
|
+
* This is {@link import("../accuracy/score.js").scoreCase} — the scorer that
|
|
152
|
+
* marks the decoder against the gold corpus — pointed at a person instead, and
|
|
153
|
+
* it costs three conversions of the text.
|
|
154
|
+
*/
|
|
155
|
+
export declare function check(dictionary: Dictionary, text: string, typed: string, options?: CheckOptions): PinyinCheck;
|
|
156
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/grade/check.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EAGpB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,UAAU,GACV,MAAM,GACN,OAAO,GACP,SAAS,GACT,OAAO,CAAC;AAEZ;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IAC7C,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;CACvC;AAqaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,KAAK,CACnB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,YAAiB,GACzB,WAAW,CAuBb"}
|