henkan 2.4.12 → 3.0.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.
Files changed (86) hide show
  1. package/dist/index.cjs.js +431 -20
  2. package/dist/index.cjs.js.map +2 -2
  3. package/dist/index.mjs +428 -20
  4. package/dist/index.mjs.map +2 -2
  5. package/dist/types/types.d.ts +106 -15
  6. package/dist/types/types.d.ts.map +1 -1
  7. package/dist/types/utils.d.ts +28 -6
  8. package/dist/types/utils.d.ts.map +1 -1
  9. package/docs/api/README.md +8 -2
  10. package/docs/api/functions/capitalizeString.md +1 -1
  11. package/docs/api/functions/convertJMdict.md +1 -1
  12. package/docs/api/functions/convertJMnedict.md +33 -0
  13. package/docs/api/functions/convertJawiktionaryAsync.md +1 -1
  14. package/docs/api/functions/convertJawiktionarySync.md +1 -1
  15. package/docs/api/functions/convertKanjiDic.md +1 -1
  16. package/docs/api/functions/convertKradFile.md +1 -1
  17. package/docs/api/functions/convertRadkFile.md +1 -1
  18. package/docs/api/functions/convertTanakaCorpus.md +1 -1
  19. package/docs/api/functions/convertTanakaCorpusWithFurigana.md +1 -1
  20. package/docs/api/functions/createEntryMaps.md +9 -3
  21. package/docs/api/functions/generateAnkiNote.md +2 -2
  22. package/docs/api/functions/generateAnkiNotesFile.md +1 -1
  23. package/docs/api/functions/generateFurigana.md +1 -1
  24. package/docs/api/functions/getKanji.md +1 -1
  25. package/docs/api/functions/getKanjiExtended.md +1 -1
  26. package/docs/api/functions/getName.md +57 -0
  27. package/docs/api/functions/getValidForms.md +1 -1
  28. package/docs/api/functions/getWord.md +3 -3
  29. package/docs/api/functions/getWordDefinitions.md +1 -1
  30. package/docs/api/functions/getWordDefinitionsWithFurigana.md +1 -1
  31. package/docs/api/functions/hiraganaToKatakana.md +1 -1
  32. package/docs/api/functions/isObjectArray.md +1 -1
  33. package/docs/api/functions/isStringArray.md +1 -1
  34. package/docs/api/functions/isValidArrayWithFirstElement.md +1 -1
  35. package/docs/api/functions/katakanaToHiragana.md +1 -1
  36. package/docs/api/functions/shuffleArray.md +1 -1
  37. package/docs/api/interfaces/DefaultNoteInfo.md +10 -10
  38. package/docs/api/interfaces/Definition.md +4 -4
  39. package/docs/api/interfaces/DictKanji.md +5 -5
  40. package/docs/api/interfaces/DictKanjiForm.md +2 -2
  41. package/docs/api/interfaces/DictKanjiMisc.md +5 -5
  42. package/docs/api/interfaces/DictKanjiReading.md +3 -3
  43. package/docs/api/interfaces/DictKanjiReadingMeaning.md +3 -3
  44. package/docs/api/interfaces/DictKanjiReadingMeaningGroup.md +3 -3
  45. package/docs/api/interfaces/DictKanjiWithRadicals.md +3 -3
  46. package/docs/api/interfaces/DictName.md +75 -0
  47. package/docs/api/interfaces/DictRadical.md +4 -4
  48. package/docs/api/interfaces/DictReading.md +2 -2
  49. package/docs/api/interfaces/EntryMaps.md +33 -9
  50. package/docs/api/interfaces/ExamplePart.md +7 -7
  51. package/docs/api/interfaces/GlossSpecificNumber.md +3 -3
  52. package/docs/api/interfaces/Grammar.md +15 -15
  53. package/docs/api/interfaces/GrammarMeaning.md +3 -3
  54. package/docs/api/interfaces/JaWiktionaryEntry.md +5 -5
  55. package/docs/api/interfaces/Kana.md +11 -11
  56. package/docs/api/interfaces/Kanji.md +23 -23
  57. package/docs/api/interfaces/KanjiComponent.md +3 -3
  58. package/docs/api/interfaces/KanjiForm.md +5 -5
  59. package/docs/api/interfaces/Name.md +163 -0
  60. package/docs/api/interfaces/NeDictMeaning.md +33 -0
  61. package/docs/api/interfaces/NoteAndTag.md +3 -3
  62. package/docs/api/interfaces/NoteHeaderKeys.md +7 -7
  63. package/docs/api/interfaces/Phrase.md +5 -5
  64. package/docs/api/interfaces/Radical.md +16 -16
  65. package/docs/api/interfaces/Reading.md +6 -6
  66. package/docs/api/interfaces/ResultEntry.md +8 -7
  67. package/docs/api/interfaces/TanakaExample.md +7 -7
  68. package/docs/api/interfaces/Translation.md +5 -5
  69. package/docs/api/interfaces/UsefulRegExps.md +8 -8
  70. package/docs/api/interfaces/Word.md +15 -15
  71. package/docs/api/interfaces/WordDefinitionPair.md +4 -4
  72. package/docs/api/type-aliases/Dict.md +1 -1
  73. package/docs/api/type-aliases/{DictName.md → DictNames.md} +3 -3
  74. package/docs/api/type-aliases/EntryExamplesMap.md +13 -0
  75. package/docs/api/type-aliases/EntryType.md +2 -2
  76. package/docs/api/type-aliases/KanjiEntryMap.md +1 -1
  77. package/docs/api/type-aliases/KanjiSVGMap.md +1 -1
  78. package/docs/api/type-aliases/KanjiWordsMap.md +1 -1
  79. package/docs/api/type-aliases/NameIDEntryMap.md +13 -0
  80. package/docs/api/type-aliases/Result.md +2 -2
  81. package/docs/api/type-aliases/WordDefinitionsMap.md +1 -1
  82. package/docs/api/type-aliases/WordIDEntryMap.md +1 -1
  83. package/package.json +8 -7
  84. package/src/types.ts +115 -15
  85. package/src/utils.ts +735 -80
  86. package/docs/api/type-aliases/WordExamplesMap.md +0 -13
package/src/utils.ts CHANGED
@@ -20,10 +20,12 @@ import {
20
20
  DictKanjiReadingMeaningGroup,
21
21
  DictKanjiWithRadicals,
22
22
  DictMeaning,
23
+ DictName,
23
24
  DictRadical,
24
25
  DictReading,
25
26
  DictTranslation,
26
27
  DictWord,
28
+ EntryExamplesMap,
27
29
  EntryMaps,
28
30
  ExamplePart,
29
31
  Grammar,
@@ -35,6 +37,8 @@ import {
35
37
  KanjiForm,
36
38
  KanjiSVGMap,
37
39
  KanjiWordsMap,
40
+ Name,
41
+ NameIDEntryMap,
38
42
  NoteAndTag,
39
43
  Phrase,
40
44
  POS,
@@ -49,7 +53,6 @@ import {
49
53
  Word,
50
54
  WordDefinitionPair,
51
55
  WordDefinitionsMap,
52
- WordExamplesMap,
53
56
  WordIDEntryMap,
54
57
  } from "./types";
55
58
 
@@ -461,6 +464,135 @@ export function convertJMdict(
461
464
  return dict;
462
465
  }
463
466
 
467
+ /**
468
+ * Converts a JMnedict `JMnedict.xml` file into an array of {@link DictWord} objects.
469
+ * @param xmlString The raw `JMnedict.xml` file contents
470
+ * @param examples An array of converted `Tanaka Corpus` examples
471
+ * @returns An array of converted {@link DictWord} objects
472
+ */
473
+ export function convertJMnedict(
474
+ xmlString: string,
475
+ examples?: readonly TanakaExample[],
476
+ ): DictName[] {
477
+ const dictParsed: libxml.Document = libxml.parseXml(xmlString, {
478
+ dtdvalid: true,
479
+ nonet: false,
480
+ noent: true,
481
+ recover: false,
482
+ });
483
+ const dict: DictName[] = [];
484
+
485
+ xml.parseString(dictParsed, (_err: Error | null, result: any) => {
486
+ const tanakaParts: Set<string> | undefined =
487
+ examples !== undefined && examples.length > 0
488
+ ? new Set<string>(
489
+ examples.flatMap((example: TanakaExample) =>
490
+ example.parts.flatMap((part: ExamplePart) => [
491
+ part.baseForm,
492
+ ...(part.reading !== undefined ? [part.reading] : []),
493
+ ...(part.inflectedForm !== undefined
494
+ ? [part.inflectedForm]
495
+ : []),
496
+ ...(part.referenceID !== undefined ? [part.referenceID] : []),
497
+ ]),
498
+ ),
499
+ )
500
+ : undefined;
501
+
502
+ for (const entry of result.JMnedict.entry) {
503
+ const entryObj: DictName = {
504
+ id: entry.ent_seq[0],
505
+ nameReadings: [],
506
+ meanings: [],
507
+ };
508
+
509
+ const kanjiForms: any = entry.k_ele;
510
+ const readings: any = entry.r_ele;
511
+ const translations: any = entry.trans;
512
+
513
+ if (isObjectArray(kanjiForms)) {
514
+ entryObj.kanjiForms = [];
515
+
516
+ for (const kanjiForm of kanjiForms)
517
+ entryObj.kanjiForms.push({ form: kanjiForm.keb[0] });
518
+ }
519
+
520
+ for (const reading of readings) {
521
+ const readingObj: DictReading = {
522
+ reading: reading.reb[0],
523
+ };
524
+
525
+ if (isStringArray(reading.re_restr))
526
+ readingObj.kanjiFormRestrictions = reading.re_restr;
527
+
528
+ if (isStringArray(reading.re_pri)) {
529
+ readingObj.commonness = reading.re_pri;
530
+
531
+ entryObj.isCommon = true;
532
+ }
533
+
534
+ entryObj.nameReadings.push(readingObj);
535
+ }
536
+
537
+ for (const trans of translations)
538
+ entryObj.meanings.push({
539
+ translations: trans.trans_det,
540
+ ...(isStringArray(trans.name_type)
541
+ ? { nameTypes: trans.name_type }
542
+ : {}),
543
+ });
544
+
545
+ if (examples !== undefined) {
546
+ let existsExample: boolean = false;
547
+
548
+ const rkf: ReadingsKanjiFormsPair = getValidForms(
549
+ entryObj.nameReadings,
550
+ entryObj.kanjiForms,
551
+ entryObj.isCommon,
552
+ );
553
+
554
+ const validReadings: Set<string> = new Set<string>(
555
+ rkf.readings.map((r: DictReading) => r.reading),
556
+ );
557
+ const validKanjiForms: Set<string> | undefined =
558
+ rkf.kanjiForms !== undefined
559
+ ? new Set<string>(
560
+ rkf.kanjiForms.map((kf: DictKanjiForm) => kf.form),
561
+ )
562
+ : undefined;
563
+
564
+ if (
565
+ validKanjiForms !== undefined &&
566
+ validKanjiForms.size > 0 &&
567
+ tanakaParts !== undefined
568
+ )
569
+ for (const kf of validKanjiForms)
570
+ if (tanakaParts.has(kf)) {
571
+ existsExample = true;
572
+ break;
573
+ }
574
+
575
+ if (
576
+ entryObj.kanjiForms === undefined &&
577
+ validReadings.size > 0 &&
578
+ tanakaParts !== undefined
579
+ )
580
+ for (const r of validReadings)
581
+ if (tanakaParts.has(r)) {
582
+ existsExample = true;
583
+ break;
584
+ }
585
+
586
+ if (existsExample) entryObj.hasPhrases = true;
587
+ }
588
+
589
+ dict.push(entryObj);
590
+ }
591
+ });
592
+
593
+ return dict;
594
+ }
595
+
464
596
  /**
465
597
  * Converts a KANJIDIC `kanjidic2.xml` file into an array of {@link DictKanji} objects.
466
598
  * @param xmlString The raw `kanjidic2.xml` file contents
@@ -805,13 +937,16 @@ export function convertKradFile(
805
937
  *
806
938
  * - {@link jmDict} => {@link WordIDEntryMap}, {@link KanjiWordsMap}
807
939
  *
940
+ * - {@link jmNedict} => {@link NameIDEntryMap}
941
+ *
808
942
  * - {@link kanjiDic} => {@link KanjiEntryMap}, {@link KanjiSVGMap} (only if {@link svgList} is present)
809
943
  *
810
- * - {@link tanakaExamples} (requires {@link jmDict}) => {@link WordExamplesMap}
944
+ * - {@link tanakaExamples} => {@link EntryExamplesMap} (requires {@link jmDict} or/and {@link jmNedict})
811
945
  *
812
946
  * - {@link wordDefinitionPairs} => {@link WordDefinitionsMap}
813
947
  *
814
948
  * @param jmDict An array of converted `JMdict` entries
949
+ * @param jmNedict
815
950
  * @param kanjiDic An array of converted `KANJIDIC` entries
816
951
  * @param tanakaExamples An array of converted `Tanaka Corpus` examples
817
952
  * @param wordDefinitionPairs An array of `ja.wiktionary.org` word-definitions pairs
@@ -820,6 +955,7 @@ export function convertKradFile(
820
955
  */
821
956
  export function createEntryMaps(
822
957
  jmDict?: readonly DictWord[],
958
+ jmNedict?: readonly DictName[],
823
959
  kanjiDic?: readonly DictKanji[],
824
960
  tanakaExamples?: readonly TanakaExample[],
825
961
  wordDefinitionPairs?: readonly WordDefinitionPair[],
@@ -827,8 +963,13 @@ export function createEntryMaps(
827
963
  ): EntryMaps {
828
964
  const kanjiEntryMap: KanjiEntryMap = new Map<string, DictKanji>();
829
965
  const wordIDEntryMap: WordIDEntryMap = new Map<StringNumber, DictWord>();
966
+ const nameIDEntryMap: NameIDEntryMap = new Map<StringNumber, DictName>();
830
967
  const kanjiWordsMap: KanjiWordsMap = new Map<string, DictWord[]>();
831
- const wordExamplesMap: WordExamplesMap = new Map<
968
+ const wordExamplesMap: EntryExamplesMap = new Map<
969
+ StringNumber,
970
+ TanakaExample[]
971
+ >();
972
+ const nameExamplesMap: EntryExamplesMap = new Map<
832
973
  StringNumber,
833
974
  TanakaExample[]
834
975
  >();
@@ -838,16 +979,6 @@ export function createEntryMaps(
838
979
  >();
839
980
  const kanjiSVGMap: KanjiSVGMap = new Map<string, string>();
840
981
 
841
- const wordPartsMap: Map<StringNumber, Set<string>> = new Map<
842
- StringNumber,
843
- Set<string>
844
- >();
845
- const partExamplesMap: Map<string, TanakaExample[]> = new Map<
846
- string,
847
- TanakaExample[]
848
- >();
849
- const entryParts: Set<string> = new Set<string>();
850
-
851
982
  if (kanjiDic !== undefined)
852
983
  for (const kanji of kanjiDic) kanjiEntryMap.set(kanji.kanji, kanji);
853
984
 
@@ -872,6 +1003,16 @@ export function createEntryMaps(
872
1003
  }
873
1004
 
874
1005
  if (jmDict !== undefined) {
1006
+ const wordPartsMap: Map<StringNumber, Set<string>> = new Map<
1007
+ StringNumber,
1008
+ Set<string>
1009
+ >();
1010
+ const partExamplesMap: Map<string, TanakaExample[]> = new Map<
1011
+ string,
1012
+ TanakaExample[]
1013
+ >();
1014
+ const entryParts: Set<string> = new Set<string>();
1015
+
875
1016
  for (const word of jmDict) {
876
1017
  wordIDEntryMap.set(word.id, word);
877
1018
 
@@ -978,11 +1119,109 @@ export function createEntryMaps(
978
1119
  }
979
1120
  }
980
1121
 
1122
+ if (jmNedict !== undefined) {
1123
+ const namePartsMap: Map<StringNumber, Set<string>> = new Map<
1124
+ StringNumber,
1125
+ Set<string>
1126
+ >();
1127
+ const partExamplesMap: Map<string, TanakaExample[]> = new Map<
1128
+ string,
1129
+ TanakaExample[]
1130
+ >();
1131
+ const entryParts: Set<string> = new Set<string>();
1132
+
1133
+ for (const name of jmNedict) {
1134
+ nameIDEntryMap.set(name.id, name);
1135
+
1136
+ if (tanakaExamples !== undefined) {
1137
+ const rkf: ReadingsKanjiFormsPair = getValidForms(
1138
+ name.nameReadings,
1139
+ name.kanjiForms,
1140
+ name.isCommon,
1141
+ );
1142
+
1143
+ const localPartParts: Set<string> = new Set<string>();
1144
+
1145
+ for (const reading of rkf.readings) {
1146
+ entryParts.add(reading.reading);
1147
+ localPartParts.add(reading.reading);
1148
+ }
1149
+
1150
+ if (rkf.kanjiForms !== undefined && rkf.kanjiForms.length > 0)
1151
+ for (const kanjiForm of rkf.kanjiForms) {
1152
+ entryParts.add(kanjiForm.form);
1153
+ localPartParts.add(kanjiForm.form);
1154
+ }
1155
+
1156
+ entryParts.add(name.id);
1157
+ localPartParts.add(name.id);
1158
+
1159
+ namePartsMap.set(name.id, localPartParts);
1160
+ }
1161
+ }
1162
+
1163
+ if (tanakaExamples !== undefined) {
1164
+ for (const ex of tanakaExamples)
1165
+ for (const part of ex.parts) {
1166
+ if (entryParts.has(part.baseForm)) {
1167
+ const exList: TanakaExample[] | undefined = partExamplesMap.get(
1168
+ part.baseForm,
1169
+ );
1170
+
1171
+ if (exList === undefined) partExamplesMap.set(part.baseForm, [ex]);
1172
+ else exList.push(ex);
1173
+ }
1174
+ if (part.reading !== undefined && entryParts.has(part.reading)) {
1175
+ const exList: TanakaExample[] | undefined = partExamplesMap.get(
1176
+ part.reading,
1177
+ );
1178
+
1179
+ if (exList === undefined) partExamplesMap.set(part.reading, [ex]);
1180
+ else exList.push(ex);
1181
+ }
1182
+ if (
1183
+ part.inflectedForm !== undefined &&
1184
+ entryParts.has(part.inflectedForm)
1185
+ ) {
1186
+ const exList: TanakaExample[] | undefined = partExamplesMap.get(
1187
+ part.inflectedForm,
1188
+ );
1189
+
1190
+ if (exList === undefined)
1191
+ partExamplesMap.set(part.inflectedForm, [ex]);
1192
+ else exList.push(ex);
1193
+ }
1194
+ }
1195
+
1196
+ for (const name of jmNedict) {
1197
+ const seenEx: Set<string> = new Set<string>();
1198
+ const validExamples: TanakaExample[] = [];
1199
+
1200
+ for (const p of namePartsMap.get(name.id)!) {
1201
+ const examplesForPart: TanakaExample[] | undefined = partExamplesMap
1202
+ .get(p)
1203
+ ?.filter((ex: TanakaExample) => !seenEx.has(ex.id));
1204
+ if (examplesForPart === undefined) continue;
1205
+
1206
+ for (const ex of examplesForPart) {
1207
+ seenEx.add(ex.id);
1208
+ validExamples.push(ex);
1209
+ }
1210
+ }
1211
+
1212
+ if (validExamples.length > 0)
1213
+ nameExamplesMap.set(name.id, validExamples);
1214
+ }
1215
+ }
1216
+ }
1217
+
981
1218
  return {
982
1219
  ...(wordIDEntryMap.size > 0 ? { wordIDEntryMap: wordIDEntryMap } : {}),
1220
+ ...(nameIDEntryMap.size > 0 ? { nameIDEntryMap: nameIDEntryMap } : {}),
983
1221
  ...(kanjiWordsMap.size > 0 ? { kanjiWordsMap: kanjiWordsMap } : {}),
984
1222
  ...(kanjiEntryMap.size > 0 ? { kanjiEntryMap: kanjiEntryMap } : {}),
985
1223
  ...(wordExamplesMap.size > 0 ? { wordExamplesMap: wordExamplesMap } : {}),
1224
+ ...(nameExamplesMap.size > 0 ? { nameExamplesMap: nameExamplesMap } : {}),
986
1225
  ...(wordDefinitionsMap.size > 0
987
1226
  ? { wordDefinitionsMap: wordDefinitionsMap }
988
1227
  : {}),
@@ -2039,7 +2278,7 @@ export function getKanjiExtended(
2039
2278
  * @param searchedWord The ID of the `JMdict` entry (requires {@link dict}) or a {@link DictWord} object
2040
2279
  * @param dict An array converted `JMdict` entries or a {@link WordIDEntryMap} *(not needed if {@link searchedWord} is a {@link DictWord} object)*
2041
2280
  * @param kanjiDic An array of converted `KANJIDIC` entries or a {@link KanjiEntryMap}
2042
- * @param examples An array of converted `Tanaka Corpus` examples or a {@link WordExamplesMap}
2281
+ * @param examples An array of converted `Tanaka Corpus` examples or a {@link EntryExamplesMap}
2043
2282
  * @param definitions An array of `ja.wiktionary.org` word-definitions pairs or a {@link WordDefinitionsMap}
2044
2283
  * @param noteTypeName The Anki note type name
2045
2284
  * @param deckPath The full Anki deck path
@@ -2049,7 +2288,7 @@ export function getWord(
2049
2288
  searchedWord: StringNumber | DictWord,
2050
2289
  dict?: readonly DictWord[] | WordIDEntryMap,
2051
2290
  kanjiDic?: readonly DictKanji[] | KanjiEntryMap,
2052
- examples?: readonly TanakaExample[] | WordExamplesMap,
2291
+ examples?: readonly TanakaExample[] | EntryExamplesMap,
2053
2292
  definitions?: readonly WordDefinitionPair[] | WordDefinitionsMap,
2054
2293
  noteTypeName?: string,
2055
2294
  deckPath?: string,
@@ -2287,7 +2526,7 @@ export function getWord(
2287
2526
  ? new Set<string>(rkf.kanjiForms.map((kf: DictKanjiForm) => kf.form))
2288
2527
  : undefined;
2289
2528
 
2290
- let readingMatchingKanjiFormExamples: {
2529
+ const readingMatchingKanjiFormExamples: {
2291
2530
  ex: TanakaExample;
2292
2531
  partIndex: number;
2293
2532
  form: string;
@@ -2308,8 +2547,6 @@ export function getWord(
2308
2547
  includesTranslation?: true | undefined;
2309
2548
  }[] = [];
2310
2549
 
2311
- let hasReadingMatchingKanjiFormWithTranslation: boolean = false;
2312
-
2313
2550
  for (const example of exampleList)
2314
2551
  for (let i: number = 0; i < example.parts.length; i++) {
2315
2552
  if (seenPhrases.has(example.phrase)) break;
@@ -2333,7 +2570,7 @@ export function getWord(
2333
2570
  readingAsReadingMatch ||
2334
2571
  readingAsInflectedFormMatch ||
2335
2572
  referenceIDMatch
2336
- ) {
2573
+ )
2337
2574
  readingMatchingKanjiFormExamples.push({
2338
2575
  ex: example,
2339
2576
  partIndex: i,
@@ -2341,13 +2578,7 @@ export function getWord(
2341
2578
  ...(referenceIDMatch ? { referenceIDMatch: true } : {}),
2342
2579
  ...(includesTranslation ? { includesTranslation: true } : {}),
2343
2580
  });
2344
-
2345
- if (
2346
- !hasReadingMatchingKanjiFormWithTranslation &&
2347
- includesTranslation
2348
- )
2349
- hasReadingMatchingKanjiFormWithTranslation = true;
2350
- } else
2581
+ else
2351
2582
  kanjiFormExamples.push({
2352
2583
  ex: example,
2353
2584
  partIndex: i,
@@ -2379,28 +2610,6 @@ export function getWord(
2379
2610
  }
2380
2611
  }
2381
2612
 
2382
- if (readingMatchingKanjiFormExamples.length > 0) {
2383
- if (hasReadingMatchingKanjiFormWithTranslation)
2384
- readingMatchingKanjiFormExamples =
2385
- readingMatchingKanjiFormExamples.filter(
2386
- (ex: {
2387
- ex: TanakaExample;
2388
- partIndex: number;
2389
- form: string;
2390
- referenceIDMatch?: true | undefined;
2391
- includesTranslation?: true | undefined;
2392
- }) =>
2393
- ex.includesTranslation === true ||
2394
- ex.referenceIDMatch === true ||
2395
- ex.ex.parts.some(
2396
- (part: ExamplePart) =>
2397
- ex.form === part.baseForm && part.glossNumber !== undefined,
2398
- ),
2399
- );
2400
-
2401
- kanjiFormExamples.length = 0;
2402
- }
2403
-
2404
2613
  if (
2405
2614
  kanjiFormExamples.length > 0 &&
2406
2615
  kanjiFormExamples.some(
@@ -2409,7 +2618,12 @@ export function getWord(
2409
2618
  partIndex: number;
2410
2619
  form: string;
2411
2620
  includesTranslation?: true | undefined;
2412
- }) => ex.includesTranslation === true,
2621
+ }) =>
2622
+ ex.includesTranslation === true ||
2623
+ ex.ex.parts.some(
2624
+ (part: ExamplePart) =>
2625
+ ex.form === part.baseForm && part.glossNumber !== undefined,
2626
+ ),
2413
2627
  )
2414
2628
  )
2415
2629
  kanjiFormExamples = kanjiFormExamples.filter(
@@ -2425,6 +2639,11 @@ export function getWord(
2425
2639
  ex.form === part.baseForm && part.glossNumber !== undefined,
2426
2640
  ),
2427
2641
  );
2642
+ else if (
2643
+ kanjiFormExamples.length > 0 &&
2644
+ readingMatchingKanjiFormExamples.length > 0
2645
+ )
2646
+ kanjiFormExamples.length = 0;
2428
2647
 
2429
2648
  if (
2430
2649
  readingExamples.length > 0 &&
@@ -2581,41 +2800,330 @@ export function getWord(
2581
2800
  } else return undefined;
2582
2801
  }
2583
2802
 
2584
- export function isWord(entry: Result): entry is Word {
2585
- return (
2586
- isObjectArray(Object.getOwnPropertyDescriptor(entry, "readings")?.value) &&
2587
- isObjectArray(Object.getOwnPropertyDescriptor(entry, "translations")?.value)
2588
- );
2589
- }
2803
+ /**
2804
+ * Transforms a converted `JMnedict` entry into a more readable format, by providing either its JMnedict entry ID or the {@link DictName} object directly.
2805
+ * @param searchedName The ID of the `JMnedict` entry (requires {@link dict}) or a {@link DictName} object
2806
+ * @param dict An array converted `JMnedict` entries or a {@link NameIDEntryMap} *(not needed if {@link searchedName} is a {@link DictName} object)*
2807
+ * @param kanjiDic An array of converted `KANJIDIC` entries or a {@link KanjiEntryMap}
2808
+ * @param examples An array of converted `Tanaka Corpus` examples or a {@link EntryExamplesMap}
2809
+ * @param noteTypeName The Anki note type name
2810
+ * @param deckPath The full Anki deck path
2811
+ * @returns The transformed {@link DictName} object or `undefined` if entry is not found
2812
+ */
2813
+ export function getName(
2814
+ searchedName: StringNumber | DictName,
2815
+ dict?: readonly DictName[] | NameIDEntryMap,
2816
+ kanjiDic?: readonly DictKanji[] | KanjiEntryMap,
2817
+ examples?: readonly TanakaExample[] | EntryExamplesMap,
2818
+ noteTypeName?: string,
2819
+ deckPath?: string,
2820
+ ): Name | undefined {
2821
+ let dictName: DictName | undefined = undefined;
2590
2822
 
2591
- export function isRadical(entry: Result): entry is Radical {
2592
- return (
2593
- typeof Object.getOwnPropertyDescriptor(entry, "radical")?.value === "string"
2594
- );
2595
- }
2823
+ if (typeof searchedName === "string" && dict !== undefined) {
2824
+ if (Array.isArray(dict))
2825
+ dictName = (dict as readonly DictName[]).find(
2826
+ (entry: DictName) => entry.id === searchedName,
2827
+ );
2596
2828
 
2597
- export function isKanji(entry: Result): entry is Kanji {
2598
- return (
2599
- !Object.hasOwn(entry, "translations") &&
2600
- !Object.hasOwn(entry, "readings") &&
2601
- !Object.hasOwn(entry, "radical") &&
2602
- typeof Object.getOwnPropertyDescriptor(entry, "kanji")?.value === "string"
2603
- );
2604
- }
2829
+ if (dict instanceof Map) dictName = dict.get(searchedName);
2830
+ }
2605
2831
 
2606
- export function isKana(entry: Result): entry is Kana {
2607
- return (
2608
- typeof Object.getOwnPropertyDescriptor(entry, "kana")?.value === "string"
2609
- );
2610
- }
2832
+ if (typeof searchedName === "object") dictName = searchedName;
2611
2833
 
2612
- export function isGrammar(entry: Result): entry is Grammar {
2613
- return (
2614
- typeof Object.getOwnPropertyDescriptor(entry, "point")?.value === "string"
2615
- );
2616
- }
2834
+ if (dictName !== undefined) {
2835
+ const name: Name = {
2836
+ id: dictName.id,
2837
+ nameReadings: [],
2838
+ translations: [],
2839
+ noteID: `name_${dictName.id}`,
2840
+ noteTypeName: noteTypeName,
2841
+ deckPath: deckPath,
2842
+ tags: [],
2843
+ };
2617
2844
 
2618
- const createNotes: (notes: string[], phrase?: true) => string = (
2845
+ if (dictName.isCommon === true) {
2846
+ name.common = true;
2847
+ name.tags!.push("name::common");
2848
+ }
2849
+
2850
+ if (dictName.kanjiForms !== undefined)
2851
+ name.kanjiForms = dictName.kanjiForms.map(
2852
+ (dictKanjiForm: DictKanjiForm) => ({
2853
+ kanjiForm: dictKanjiForm.form,
2854
+ }),
2855
+ );
2856
+
2857
+ name.nameReadings = dictName.nameReadings.map(
2858
+ (dictReading: DictReading) => ({
2859
+ reading: dictReading.reading,
2860
+ ...(dictReading.kanjiFormRestrictions !== undefined
2861
+ ? {
2862
+ notes: dictReading.kanjiFormRestrictions.map(
2863
+ (restriction: string) => `Reading restricted to ${restriction}`,
2864
+ ),
2865
+ }
2866
+ : {}),
2867
+ ...(dictReading.commonness !== undefined &&
2868
+ dictReading.commonness.length > 0
2869
+ ? { common: true }
2870
+ : {}),
2871
+ }),
2872
+ );
2873
+
2874
+ name.translations = [];
2875
+
2876
+ const meanings: string[] | undefined =
2877
+ dictName.hasPhrases === true && examples !== undefined ? [] : undefined;
2878
+ const seenPhrases: Set<string> = new Set<string>();
2879
+ let hasNameTypes: boolean = false;
2880
+
2881
+ for (const dictMeaning of dictName.meanings) {
2882
+ if (!hasNameTypes && dictMeaning.nameTypes !== undefined)
2883
+ hasNameTypes = true;
2884
+
2885
+ name.translations.push({
2886
+ translation: dictMeaning.translations
2887
+ .map((translation: string) => {
2888
+ if (meanings !== undefined) {
2889
+ const cleanTranslation: string = translation
2890
+ .replaceAll(/\([^)]*\)|\[[^\]]*\]|\{[^}]*\}/g, "")
2891
+ .trim();
2892
+
2893
+ if (!seenPhrases.has(cleanTranslation)) {
2894
+ seenPhrases.add(cleanTranslation);
2895
+ meanings.push(cleanTranslation);
2896
+ }
2897
+ }
2898
+
2899
+ return translation;
2900
+ })
2901
+ .join("; "),
2902
+ ...(dictMeaning.nameTypes !== undefined
2903
+ ? {
2904
+ notes: dictMeaning.nameTypes.map((type: string) => {
2905
+ const noteAndTag: NoteAndTag = lookupWordNote(
2906
+ type,
2907
+ [],
2908
+ name.tags!,
2909
+ );
2910
+
2911
+ return capitalizeString(noteAndTag.note);
2912
+ }),
2913
+ }
2914
+ : {}),
2915
+ });
2916
+ }
2917
+
2918
+ if (!hasNameTypes) name.tags!.push("name::no_name_types");
2919
+
2920
+ seenPhrases.clear();
2921
+
2922
+ if (kanjiDic !== undefined && name.kanjiForms !== undefined) {
2923
+ const kanji: Kanji[] = [];
2924
+ const seenChars: Set<string> = new Set<string>();
2925
+
2926
+ for (const kanjiForm of name.kanjiForms)
2927
+ for (const char of kanjiForm.kanjiForm
2928
+ .split("")
2929
+ .filter((c: string) => regexps.kanji.test(c))) {
2930
+ if (seenChars.has(char)) continue;
2931
+ seenChars.add(char);
2932
+
2933
+ const kanjiEntry: DictKanji | undefined =
2934
+ kanjiDic instanceof Map ? kanjiDic.get(char) : undefined;
2935
+
2936
+ const kanjiObj: Kanji | undefined = getKanji(
2937
+ kanjiEntry ?? char,
2938
+ !(kanjiDic instanceof Map) ? kanjiDic : undefined,
2939
+ );
2940
+
2941
+ if (kanjiObj !== undefined)
2942
+ kanji.push({
2943
+ kanji: kanjiObj.kanji,
2944
+ ...(kanjiObj.meanings !== undefined &&
2945
+ kanjiObj.meanings.length > 0
2946
+ ? { meanings: kanjiObj.meanings }
2947
+ : {}),
2948
+ });
2949
+ }
2950
+
2951
+ if (kanji.length > 0) name.kanji = kanji;
2952
+ }
2953
+
2954
+ if (meanings !== undefined) {
2955
+ const exampleList: readonly TanakaExample[] =
2956
+ examples instanceof Map ? (examples.get(dictName.id) ?? []) : examples!;
2957
+
2958
+ const rkf: ReadingsKanjiFormsPair = getValidForms(
2959
+ dictName.nameReadings,
2960
+ dictName.kanjiForms,
2961
+ dictName.isCommon,
2962
+ );
2963
+
2964
+ const readings: Set<string> = new Set<string>(
2965
+ rkf.readings.map((r: DictReading) => r.reading),
2966
+ );
2967
+ const kanjiForms: Set<string> | undefined =
2968
+ rkf.kanjiForms !== undefined
2969
+ ? new Set<string>(rkf.kanjiForms.map((kf: DictKanjiForm) => kf.form))
2970
+ : undefined;
2971
+
2972
+ let readingMatchingKanjiFormExamples: {
2973
+ ex: TanakaExample;
2974
+ includesTranslation?: true | undefined;
2975
+ }[] = [];
2976
+
2977
+ let readingExamples: {
2978
+ ex: TanakaExample;
2979
+ includesTranslation?: true | undefined;
2980
+ }[] = [];
2981
+
2982
+ let hasReadingMatchingKanjiFormWithTranslation: boolean = false;
2983
+ let hasReadingWithTranslation: boolean = false;
2984
+
2985
+ for (const example of exampleList)
2986
+ for (let i: number = 0; i < example.parts.length; i++) {
2987
+ if (seenPhrases.has(example.phrase)) break;
2988
+
2989
+ const includesTranslation: boolean = meanings.some((m: string) =>
2990
+ example.translation.includes(m),
2991
+ );
2992
+
2993
+ const part: ExamplePart = example.parts[i]!;
2994
+
2995
+ const readingAsReadingMatch: boolean =
2996
+ part.reading !== undefined && readings.has(part.reading);
2997
+
2998
+ if (
2999
+ kanjiForms !== undefined &&
3000
+ kanjiForms.has(part.baseForm) &&
3001
+ readingAsReadingMatch
3002
+ ) {
3003
+ readingMatchingKanjiFormExamples.push({
3004
+ ex: example,
3005
+ ...(includesTranslation ? { includesTranslation: true } : {}),
3006
+ });
3007
+
3008
+ if (
3009
+ !hasReadingMatchingKanjiFormWithTranslation &&
3010
+ includesTranslation
3011
+ )
3012
+ hasReadingMatchingKanjiFormWithTranslation = true;
3013
+
3014
+ seenPhrases.add(example.phrase);
3015
+
3016
+ break;
3017
+ }
3018
+
3019
+ const readingAsBaseFormMatch: boolean = readings.has(part.baseForm);
3020
+
3021
+ if (readingAsBaseFormMatch && kanjiForms === undefined) {
3022
+ readingExamples.push({
3023
+ ex: example,
3024
+ ...(includesTranslation ? { includesTranslation: true } : {}),
3025
+ });
3026
+
3027
+ if (!hasReadingWithTranslation && includesTranslation)
3028
+ hasReadingWithTranslation = true;
3029
+
3030
+ seenPhrases.add(example.phrase);
3031
+
3032
+ break;
3033
+ }
3034
+ }
3035
+
3036
+ if (readingMatchingKanjiFormExamples.length > 0)
3037
+ if (hasReadingMatchingKanjiFormWithTranslation)
3038
+ readingMatchingKanjiFormExamples =
3039
+ readingMatchingKanjiFormExamples.filter(
3040
+ (ex: {
3041
+ ex: TanakaExample;
3042
+ includesTranslation?: true | undefined;
3043
+ }) => ex.includesTranslation === true,
3044
+ );
3045
+
3046
+ if (readingExamples.length > 0 && hasReadingWithTranslation)
3047
+ readingExamples = readingExamples.filter(
3048
+ (ex: { ex: TanakaExample; includesTranslation?: true | undefined }) =>
3049
+ ex.includesTranslation === true,
3050
+ );
3051
+
3052
+ const wordExamples: {
3053
+ ex: TanakaExample;
3054
+ includesTranslation?: true | undefined;
3055
+ }[] = [
3056
+ ...(name.kanjiForms !== undefined
3057
+ ? readingMatchingKanjiFormExamples
3058
+ : readingExamples),
3059
+ ];
3060
+
3061
+ if (wordExamples.length > 0) {
3062
+ name.phrases = wordExamples
3063
+ .slice(0, 5)
3064
+ .map(
3065
+ (ex: {
3066
+ ex: TanakaExample;
3067
+ includesTranslation?: true | undefined;
3068
+ }) => ({
3069
+ phrase: ex.ex.furigana ?? ex.ex.phrase,
3070
+ translation: ex.ex.translation,
3071
+ originalPhrase: ex.ex.phrase,
3072
+ }),
3073
+ );
3074
+
3075
+ name.tags!.push("name::has_phrases");
3076
+ }
3077
+ }
3078
+
3079
+ return name;
3080
+ } else return undefined;
3081
+ }
3082
+
3083
+ export function isWord(entry: Result): entry is Word {
3084
+ return (
3085
+ isObjectArray(Object.getOwnPropertyDescriptor(entry, "readings")?.value) &&
3086
+ isObjectArray(Object.getOwnPropertyDescriptor(entry, "translations")?.value)
3087
+ );
3088
+ }
3089
+
3090
+ export function isName(entry: Result): entry is Name {
3091
+ return (
3092
+ isObjectArray(
3093
+ Object.getOwnPropertyDescriptor(entry, "nameReadings")?.value,
3094
+ ) &&
3095
+ isObjectArray(Object.getOwnPropertyDescriptor(entry, "translations")?.value)
3096
+ );
3097
+ }
3098
+
3099
+ export function isRadical(entry: Result): entry is Radical {
3100
+ return (
3101
+ typeof Object.getOwnPropertyDescriptor(entry, "radical")?.value === "string"
3102
+ );
3103
+ }
3104
+
3105
+ export function isKanji(entry: Result): entry is Kanji {
3106
+ return (
3107
+ !Object.hasOwn(entry, "translations") &&
3108
+ !Object.hasOwn(entry, "readings") &&
3109
+ !Object.hasOwn(entry, "radical") &&
3110
+ typeof Object.getOwnPropertyDescriptor(entry, "kanji")?.value === "string"
3111
+ );
3112
+ }
3113
+
3114
+ export function isKana(entry: Result): entry is Kana {
3115
+ return (
3116
+ typeof Object.getOwnPropertyDescriptor(entry, "kana")?.value === "string"
3117
+ );
3118
+ }
3119
+
3120
+ export function isGrammar(entry: Result): entry is Grammar {
3121
+ return (
3122
+ typeof Object.getOwnPropertyDescriptor(entry, "point")?.value === "string"
3123
+ );
3124
+ }
3125
+
3126
+ const createNotes: (notes: string[], phrase?: true) => string = (
2619
3127
  notes: string[],
2620
3128
  phrase?: true,
2621
3129
  ) =>
@@ -2635,7 +3143,7 @@ const createEntry: (
2635
3143
 
2636
3144
  /**
2637
3145
  * Generates an array where each field holds an entry’s info wrapped in HTML tags.
2638
- * @param entry Any type of mapped entry ({@link Word}, {@link Kanji}, {@link Radical}, {@link Kana}, {@link Grammar})
3146
+ * @param entry Any type of mapped entry ({@link Word}, {@link Name}, {@link Kanji}, {@link Radical}, {@link Kana}, {@link Grammar})
2639
3147
  * @param customData An additional string that will be added on the first field of any note type, as a `data-custom` attribute inside an invisible `div`
2640
3148
  * @param additionalTags Additional tags that will be added alongside the existing entry tags
2641
3149
  * @returns An array of fields, each corresponding to an Anki note type field
@@ -2884,6 +3392,153 @@ export function generateAnkiNote(
2884
3392
  );
2885
3393
  }
2886
3394
 
3395
+ if (isName(entry)) {
3396
+ const firstReading: string = createEntry(
3397
+ `<span class="name name-reading">${entry.nameReadings[0]!.reading}${entry.nameReadings[0]!.audio !== undefined ? `<br>[sound:${entry.nameReadings[0]!.audio}]` : ""}</span>`,
3398
+ entry.nameReadings[0]!.notes,
3399
+ );
3400
+ const otherReadings: string =
3401
+ entry.nameReadings.length > 1
3402
+ ? `<details><summary>Show other readings</summary>${entry.nameReadings
3403
+ .slice(1)
3404
+ .map((readingEntry: Reading) =>
3405
+ createEntry(
3406
+ `<span class="name name-reading">${readingEntry.reading}${readingEntry.audio !== undefined ? `<br>[sound:${readingEntry.audio}]` : ""}</span>`,
3407
+ readingEntry.notes,
3408
+ ),
3409
+ )
3410
+ .join("")}</details>`
3411
+ : "";
3412
+ const readingsField: string = `${firstReading}${otherReadings}`;
3413
+
3414
+ let readingsFieldWithoutAudio: string =
3415
+ '<div id="no-r-audio" style="display: none"></div>';
3416
+ let hasAudio: boolean = false;
3417
+
3418
+ if (entry.nameReadings.some((r: Reading) => r.audio !== undefined)) {
3419
+ const firstReadingWithoutAudio: string = createEntry(
3420
+ `<span class="name name-reading">${entry.nameReadings[0]!.reading}</span>`,
3421
+ entry.nameReadings[0]!.notes,
3422
+ );
3423
+ const otherReadingsWithoutAudio: string =
3424
+ entry.nameReadings.length > 1
3425
+ ? `<details><summary>Show other readings</summary>${entry.nameReadings
3426
+ .slice(1)
3427
+ .map((readingEntry: Reading) =>
3428
+ createEntry(
3429
+ `<span class="name name-reading">${readingEntry.reading}</span>`,
3430
+ readingEntry.notes,
3431
+ ),
3432
+ )
3433
+ .join("")}</details>`
3434
+ : "";
3435
+ readingsFieldWithoutAudio = `${firstReadingWithoutAudio}${otherReadingsWithoutAudio}`;
3436
+ hasAudio = true;
3437
+ }
3438
+
3439
+ const firstKanjiForm: string | undefined =
3440
+ entry.kanjiForms !== undefined
3441
+ ? createEntry(
3442
+ `<span class="name name-kanjiform"><ruby><rb>${entry.kanjiForms[0]!.kanjiForm}</rb><rt>${entry.nameReadings[0]!.reading}</rt></ruby></span>`,
3443
+ entry.kanjiForms[0]!.notes,
3444
+ )
3445
+ : undefined;
3446
+ const otherKanjiForms: string =
3447
+ entry.kanjiForms !== undefined && entry.kanjiForms.length > 1
3448
+ ? `<details><summary>Show other kanji forms</summary>${entry.kanjiForms
3449
+ .slice(1)
3450
+ .map((kanjiFormEntry: KanjiForm) => {
3451
+ const restrictedReading: Reading | undefined =
3452
+ entry.nameReadings.find(
3453
+ (r: Reading) =>
3454
+ r.notes !== undefined &&
3455
+ r.notes.includes(
3456
+ `Reading restricted to ${kanjiFormEntry.kanjiForm}`,
3457
+ ),
3458
+ );
3459
+
3460
+ return createEntry(
3461
+ `<span class="name name-kanjiform">${restrictedReading !== undefined ? "<ruby><rb>" : ""}${kanjiFormEntry.kanjiForm}${restrictedReading !== undefined ? `</rb><rt>${restrictedReading.reading}</rt></ruby>` : ""}</span>`,
3462
+ kanjiFormEntry.notes,
3463
+ );
3464
+ })
3465
+ .join("")}</details>`
3466
+ : "";
3467
+
3468
+ const kanjiFormsField: string =
3469
+ firstKanjiForm !== undefined
3470
+ ? `${firstKanjiForm}${otherKanjiForms}`
3471
+ : '<span class="name name-kanjiform" id="no-kanjiforms">(no kanji forms)</span>';
3472
+
3473
+ const firstThreeTranslations: string = entry.translations
3474
+ .slice(0, 3)
3475
+ .map((translationEntry: Translation) =>
3476
+ createEntry(
3477
+ `<span class="name name-translation">${translationEntry.translation}</span>`,
3478
+ translationEntry.notes,
3479
+ ),
3480
+ )
3481
+ .join("");
3482
+
3483
+ const otherTranslations: string =
3484
+ entry.translations.length > 3
3485
+ ? `<details><summary>Show other translations</summary>${entry.translations
3486
+ .map((translationEntry: Translation, index: number) => {
3487
+ if (index < 3) return "null";
3488
+
3489
+ return createEntry(
3490
+ `<span class="name name-translation">${translationEntry.translation}</span>`,
3491
+ translationEntry.notes,
3492
+ );
3493
+ })
3494
+ .filter((translation: string) => translation !== "null")
3495
+ .join("")}</details>`
3496
+ : "";
3497
+
3498
+ const translationsField: string = `${firstThreeTranslations}${otherTranslations}`;
3499
+
3500
+ const phrasesField: string | undefined =
3501
+ entry.phrases !== undefined
3502
+ ? entry.phrases
3503
+ .map((phraseEntry: Phrase) =>
3504
+ createEntry(
3505
+ `<span class="name name-phrase"><span class="name name-phrase-original">${phraseEntry.originalPhrase}</span><span class="name name-phrase-furigana">${phraseEntry.phrase}</span></span>`,
3506
+ [phraseEntry.translation],
3507
+ true,
3508
+ ),
3509
+ )
3510
+ .join("")
3511
+ : '<span class="name name-phrase" id="no-phrases">(no phrases)</span>';
3512
+
3513
+ const searchField: string = `${entry.nameReadings.map((r: Reading) => r.reading).join(" ")}${entry.kanjiForms !== undefined ? ` ${entry.kanjiForms.map((kf: KanjiForm) => kf.kanjiForm).join(" ")}` : ""} ${entry.id}`;
3514
+
3515
+ fields.push(
3516
+ ...(entry.kanjiForms !== undefined
3517
+ ? [
3518
+ `${customData !== undefined ? `<div id="custom-data" style="display: none" data-custom="${customData}"></div>` : ""}${kanjiFormsField}<div id="kf-pos" style="display: none" data-pos="1"></div>`,
3519
+ `${hasAudio ? readingsFieldWithoutAudio : readingsField}<div id="r-pos" style="display: none" data-pos="2"></div>`,
3520
+ ]
3521
+ : [
3522
+ `${customData !== undefined ? `<div id="custom-data" style="display: none" data-custom="${customData}"></div>` : ""}${kanjiFormsField}<div id="kf-pos" style="display: none" data-pos="2"></div>`,
3523
+ `${hasAudio ? readingsFieldWithoutAudio : readingsField}<div id="r-pos" style="display: none" data-pos="1"></div>`,
3524
+ ]),
3525
+ `${hasAudio ? readingsField : readingsFieldWithoutAudio}<div id="r-pos" style="display: none" data-pos="${entry.kanjiForms !== undefined ? "2" : "1"}"></div>`,
3526
+ translationsField,
3527
+ phrasesField,
3528
+ entry.kanji !== undefined
3529
+ ? entry.kanji
3530
+ .map((kanjiEntry: Kanji) =>
3531
+ createEntry(
3532
+ `<span class="name name-kanji">${kanjiEntry.kanji}${kanjiEntry.meanings === undefined ? " (no meanings)" : ""}</span>`,
3533
+ kanjiEntry.meanings,
3534
+ ),
3535
+ )
3536
+ .join("")
3537
+ : '<span class="name name-kanji" id="no-kanji">(no kanji)</span>',
3538
+ searchField,
3539
+ );
3540
+ }
3541
+
2887
3542
  if (isRadical(entry))
2888
3543
  fields.push(
2889
3544
  `${customData !== undefined ? `<div id="custom-data" style="display: none" data-custom="${customData}"></div>` : ""}${createEntry(