aboutsummaryrefslogtreecommitdiff
path: root/ext/js/data/anki-note-data-creator.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/data/anki-note-data-creator.js')
-rw-r--r--ext/js/data/anki-note-data-creator.js936
1 files changed, 936 insertions, 0 deletions
diff --git a/ext/js/data/anki-note-data-creator.js b/ext/js/data/anki-note-data-creator.js
new file mode 100644
index 00000000..fbeb8cee
--- /dev/null
+++ b/ext/js/data/anki-note-data-creator.js
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) 2023-2024 Yomitan Authors
+ * Copyright (C) 2021-2022 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import {getDisambiguations, getGroupedPronunciations, getPronunciationsOfType, getTermFrequency, groupTermTags} from '../dictionary/dictionary-data-util.js';
+import {distributeFurigana, distributeFuriganaInflected} from '../language/ja/japanese.js';
+
+/**
+ * Creates a compatibility representation of the specified data.
+ * @param {string} marker The marker that is being used for template rendering.
+ * @param {import('anki-templates-internal').CreateDetails} details Information which is used to generate the data.
+ * @returns {import('anki-templates').NoteData} An object used for rendering Anki templates.
+ */
+export function createAnkiNoteData(marker, {
+ dictionaryEntry,
+ resultOutputMode,
+ mode,
+ glossaryLayoutMode,
+ compactTags,
+ context,
+ media
+}) {
+ const definition = createCachedValue(getDefinition.bind(null, dictionaryEntry, context, resultOutputMode));
+ const uniqueExpressions = createCachedValue(getUniqueExpressions.bind(null, dictionaryEntry));
+ const uniqueReadings = createCachedValue(getUniqueReadings.bind(null, dictionaryEntry));
+ const context2 = createCachedValue(getPublicContext.bind(null, context));
+ const pitches = createCachedValue(getPitches.bind(null, dictionaryEntry));
+ const pitchCount = createCachedValue(getPitchCount.bind(null, pitches));
+ const phoneticTranscriptions = createCachedValue(getPhoneticTranscriptions.bind(null, dictionaryEntry));
+
+ if (typeof media !== 'object' || media === null || Array.isArray(media)) {
+ media = {
+ audio: void 0,
+ screenshot: void 0,
+ clipboardImage: void 0,
+ clipboardText: void 0,
+ selectionText: void 0,
+ textFurigana: [],
+ dictionaryMedia: {}
+ };
+ }
+ /** @type {import('anki-templates').NoteData} */
+ const result = {
+ marker,
+ get definition() { return getCachedValue(definition); },
+ glossaryLayoutMode,
+ compactTags,
+ group: (resultOutputMode === 'group'),
+ merge: (resultOutputMode === 'merge'),
+ modeTermKanji: (mode === 'term-kanji'),
+ modeTermKana: (mode === 'term-kana'),
+ modeKanji: (mode === 'kanji'),
+ compactGlossaries: (glossaryLayoutMode === 'compact'),
+ get uniqueExpressions() { return getCachedValue(uniqueExpressions); },
+ get uniqueReadings() { return getCachedValue(uniqueReadings); },
+ get pitches() { return getCachedValue(pitches); },
+ get pitchCount() { return getCachedValue(pitchCount); },
+ get phoneticTranscriptions() { return getCachedValue(phoneticTranscriptions); },
+ get context() { return getCachedValue(context2); },
+ media,
+ dictionaryEntry
+ };
+ Object.defineProperty(result, 'dictionaryEntry', {
+ configurable: false,
+ enumerable: false,
+ writable: false,
+ value: dictionaryEntry
+ });
+ return result;
+}
+
+/**
+ * Creates a deferred-evaluation value.
+ * @template [T=unknown]
+ * @param {() => T} getter The function to invoke to get the return value.
+ * @returns {import('anki-templates-internal').CachedValue<T>} An object which can be passed into `getCachedValue`.
+ */
+export function createCachedValue(getter) {
+ return {getter, hasValue: false, value: void 0};
+}
+
+/**
+ * Gets the value of a cached object.
+ * @template [T=unknown]
+ * @param {import('anki-templates-internal').CachedValue<T>} item An object that was returned from `createCachedValue`.
+ * @returns {T} The result of evaluating the getter, which is cached after the first invocation.
+ */
+export function getCachedValue(item) {
+ if (item.hasValue) { return /** @type {T} */ (item.value); }
+ const value = item.getter();
+ item.value = value;
+ item.hasValue = true;
+ return value;
+}
+
+// Private
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {?import('dictionary').TermSource}
+ */
+function getPrimarySource(dictionaryEntry) {
+ for (const headword of dictionaryEntry.headwords) {
+ for (const source of headword.sources) {
+ if (source.isPrimary) { return source; }
+ }
+ }
+ return null;
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getUniqueExpressions(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {term} of dictionaryEntry.headwords) {
+ results.add(term);
+ }
+ return [...results];
+ } else {
+ return [];
+ }
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getUniqueReadings(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {reading} of dictionaryEntry.headwords) {
+ results.add(reading);
+ }
+ return [...results];
+ } else {
+ return [];
+ }
+}
+
+/**
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').Context}
+ */
+function getPublicContext(context) {
+ let {documentTitle, query, fullQuery} = context;
+ if (typeof documentTitle !== 'string') { documentTitle = ''; }
+ return {
+ query,
+ fullQuery,
+ document: {
+ title: documentTitle
+ }
+ };
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry|import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {number[]}
+ */
+function getFrequencyNumbers(dictionaryEntry) {
+ let previousDictionary;
+ const frequencies = [];
+ for (const {dictionary, frequency, displayValue} of dictionaryEntry.frequencies) {
+ if (dictionary === previousDictionary) {
+ continue;
+ }
+ previousDictionary = dictionary;
+
+ if (displayValue !== null) {
+ const frequencyMatch = displayValue.match(/\d+/);
+ if (frequencyMatch !== null) {
+ frequencies.push(Number.parseInt(frequencyMatch[0], 10));
+ continue;
+ }
+ }
+ frequencies.push(frequency);
+ }
+ return frequencies;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry|import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {number}
+ */
+function getFrequencyHarmonic(dictionaryEntry) {
+ const frequencies = getFrequencyNumbers(dictionaryEntry);
+
+ if (frequencies.length === 0) {
+ return -1;
+ }
+
+ let total = 0;
+ for (const frequency of frequencies) {
+ total += 1 / frequency;
+ }
+ return Math.floor(frequencies.length / total);
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry|import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {number}
+ */
+function getFrequencyAverage(dictionaryEntry) {
+ const frequencies = getFrequencyNumbers(dictionaryEntry);
+
+ if (frequencies.length === 0) {
+ return -1;
+ }
+
+ let total = 0;
+ for (const frequency of frequencies) {
+ total += frequency;
+ }
+ return Math.floor(total / frequencies.length);
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').PitchGroup[]}
+ */
+function getPitches(dictionaryEntry) {
+ /** @type {import('anki-templates').PitchGroup[]} */
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pronunciations} of getGroupedPronunciations(dictionaryEntry)) {
+ /** @type {import('anki-templates').Pitch[]} */
+ const pitches = [];
+ for (const groupedPronunciation of pronunciations) {
+ const {pronunciation} = groupedPronunciation;
+ if (pronunciation.type !== 'pitch-accent') { continue; }
+ const {position, nasalPositions, devoicePositions, tags} = pronunciation;
+ const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation;
+ pitches.push({
+ expressions: terms,
+ reading,
+ position,
+ nasalPositions,
+ devoicePositions,
+ tags: convertPitchTags(tags),
+ exclusiveExpressions: exclusiveTerms,
+ exclusiveReadings
+ });
+ }
+ results.push({dictionary, pitches});
+ }
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TranscriptionGroup[]}
+ */
+function getPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pronunciations} of getGroupedPronunciations(dictionaryEntry)) {
+ const phoneticTranscriptions = [];
+ for (const groupedPronunciation of pronunciations) {
+ const {pronunciation} = groupedPronunciation;
+ if (pronunciation.type !== 'phonetic-transcription') { continue; }
+ const {ipa, tags} = pronunciation;
+ const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation;
+ phoneticTranscriptions.push({
+ expressions: terms,
+ reading,
+ ipa,
+ tags,
+ exclusiveExpressions: exclusiveTerms,
+ exclusiveReadings
+ });
+ }
+ results.push({dictionary, phoneticTranscriptions});
+ }
+ }
+ return results;
+}
+
+/**
+ * @param {import('anki-templates-internal').CachedValue<import('anki-templates').PitchGroup[]>} cachedPitches
+ * @returns {number}
+ */
+function getPitchCount(cachedPitches) {
+ const pitches = getCachedValue(cachedPitches);
+ return pitches.reduce((i, v) => i + v.pitches.length, 0);
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @param {import('settings').ResultOutputMode} resultOutputMode
+ * @returns {import('anki-templates').DictionaryEntry}
+ */
+function getDefinition(dictionaryEntry, context, resultOutputMode) {
+ switch (dictionaryEntry.type) {
+ case 'term':
+ return getTermDefinition(dictionaryEntry, context, resultOutputMode);
+ case 'kanji':
+ return getKanjiDefinition(dictionaryEntry, context);
+ default:
+ return /** @type {import('anki-templates').UnknownDictionaryEntry} */ ({});
+ }
+}
+
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').KanjiDictionaryEntry}
+ */
+function getKanjiDefinition(dictionaryEntry, context) {
+ const {character, dictionary, onyomi, kunyomi, definitions} = dictionaryEntry;
+
+ let {url} = context;
+ if (typeof url !== 'string') { url = ''; }
+
+ const stats = createCachedValue(getKanjiStats.bind(null, dictionaryEntry));
+ const tags = createCachedValue(convertTags.bind(null, dictionaryEntry.tags));
+ const frequencies = createCachedValue(getKanjiFrequencies.bind(null, dictionaryEntry));
+ const frequencyHarmonic = createCachedValue(getFrequencyHarmonic.bind(null, dictionaryEntry));
+ const frequencyAverage = createCachedValue(getFrequencyAverage.bind(null, dictionaryEntry));
+ const cloze = createCachedValue(getCloze.bind(null, dictionaryEntry, context));
+
+ return {
+ type: 'kanji',
+ character,
+ dictionary,
+ onyomi,
+ kunyomi,
+ glossary: definitions,
+ get tags() { return getCachedValue(tags); },
+ get stats() { return getCachedValue(stats); },
+ get frequencies() { return getCachedValue(frequencies); },
+ get frequencyHarmonic() { return getCachedValue(frequencyHarmonic); },
+ get frequencyAverage() { return getCachedValue(frequencyAverage); },
+ url,
+ get cloze() { return getCachedValue(cloze); }
+ };
+}
+
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').KanjiStatGroups}
+ */
+function getKanjiStats(dictionaryEntry) {
+ /** @type {import('anki-templates').KanjiStatGroups} */
+ const results = {};
+ for (const [key, value] of Object.entries(dictionaryEntry.stats)) {
+ results[key] = value.map(convertKanjiStat);
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').KanjiStat} kanjiStat
+ * @returns {import('anki-templates').KanjiStat}
+ */
+function convertKanjiStat({name, category, content, order, score, dictionary, value}) {
+ return {
+ name,
+ category,
+ notes: content,
+ order,
+ score,
+ dictionary,
+ value
+ };
+}
+
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').KanjiFrequency[]}
+ */
+function getKanjiFrequencies(dictionaryEntry) {
+ /** @type {import('anki-templates').KanjiFrequency[]} */
+ const results = [];
+ for (const {index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency, displayValue} of dictionaryEntry.frequencies) {
+ results.push({
+ index,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ character,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @param {import('settings').ResultOutputMode} resultOutputMode
+ * @returns {import('anki-templates').TermDictionaryEntry}
+ */
+function getTermDefinition(dictionaryEntry, context, resultOutputMode) {
+ /** @type {import('anki-templates').TermDictionaryEntryType} */
+ let type = 'term';
+ switch (resultOutputMode) {
+ case 'group': type = 'termGrouped'; break;
+ case 'merge': type = 'termMerged'; break;
+ }
+
+ const {inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry;
+
+ let {url} = context;
+ if (typeof url !== 'string') { url = ''; }
+
+ const primarySource = getPrimarySource(dictionaryEntry);
+
+ const dictionaryNames = createCachedValue(getTermDictionaryNames.bind(null, dictionaryEntry));
+ const commonInfo = createCachedValue(getTermDictionaryEntryCommonInfo.bind(null, dictionaryEntry, type));
+ const termTags = createCachedValue(getTermTags.bind(null, dictionaryEntry, type));
+ const expressions = createCachedValue(getTermExpressions.bind(null, dictionaryEntry));
+ const frequencies = createCachedValue(getTermFrequencies.bind(null, dictionaryEntry));
+ const frequencyHarmonic = createCachedValue(getFrequencyHarmonic.bind(null, dictionaryEntry));
+ const frequencyAverage = createCachedValue(getFrequencyAverage.bind(null, dictionaryEntry));
+ const pitches = createCachedValue(getTermPitches.bind(null, dictionaryEntry));
+ const phoneticTranscriptions = createCachedValue(getTermPhoneticTranscriptions.bind(null, dictionaryEntry));
+ const glossary = createCachedValue(getTermGlossaryArray.bind(null, dictionaryEntry, type));
+ const cloze = createCachedValue(getCloze.bind(null, dictionaryEntry, context));
+ const furiganaSegments = createCachedValue(getTermFuriganaSegments.bind(null, dictionaryEntry, type));
+ const sequence = createCachedValue(getTermDictionaryEntrySequence.bind(null, dictionaryEntry));
+
+ return {
+ type,
+ id: (type === 'term' && definitions.length > 0 ? definitions[0].id : void 0),
+ source: (primarySource !== null ? primarySource.transformedText : null),
+ rawSource: (primarySource !== null ? primarySource.originalText : null),
+ sourceTerm: (type !== 'termMerged' ? (primarySource !== null ? primarySource.deinflectedText : null) : void 0),
+ inflectionRuleChainCandidates,
+ score,
+ isPrimary: (type === 'term' ? dictionaryEntry.isPrimary : void 0),
+ get sequence() { return getCachedValue(sequence); },
+ get dictionary() { return getCachedValue(dictionaryNames)[0]; },
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ get dictionaryNames() { return getCachedValue(dictionaryNames); },
+ get expression() {
+ const {uniqueTerms} = getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueTerms[0] : uniqueTerms);
+ },
+ get reading() {
+ const {uniqueReadings} = getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueReadings[0] : uniqueReadings);
+ },
+ get expressions() { return getCachedValue(expressions); },
+ get glossary() { return getCachedValue(glossary); },
+ get definitionTags() { return type === 'term' ? getCachedValue(commonInfo).definitionTags : void 0; },
+ get termTags() { return getCachedValue(termTags); },
+ get definitions() { return getCachedValue(commonInfo).definitions; },
+ get frequencies() { return getCachedValue(frequencies); },
+ get frequencyHarmonic() { return getCachedValue(frequencyHarmonic); },
+ get frequencyAverage() { return getCachedValue(frequencyAverage); },
+ get pitches() { return getCachedValue(pitches); },
+ get phoneticTranscriptions() { return getCachedValue(phoneticTranscriptions); },
+ sourceTermExactMatchCount,
+ url,
+ get cloze() { return getCachedValue(cloze); },
+ get furiganaSegments() { return getCachedValue(furiganaSegments); }
+ };
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getTermDictionaryNames(dictionaryEntry) {
+ const dictionaryNames = new Set();
+ for (const {dictionary} of dictionaryEntry.definitions) {
+ dictionaryNames.add(dictionary);
+ }
+ return [...dictionaryNames];
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').TermDictionaryEntryCommonInfo}
+ */
+function getTermDictionaryEntryCommonInfo(dictionaryEntry, type) {
+ const merged = (type === 'termMerged');
+ const hasDefinitions = (type !== 'term');
+
+ /** @type {Set<string>} */
+ const allTermsSet = new Set();
+ /** @type {Set<string>} */
+ const allReadingsSet = new Set();
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ allTermsSet.add(term);
+ allReadingsSet.add(reading);
+ }
+ const uniqueTerms = [...allTermsSet];
+ const uniqueReadings = [...allReadingsSet];
+
+ /** @type {import('anki-templates').TermDefinition[]} */
+ const definitions = [];
+ /** @type {import('anki-templates').Tag[]} */
+ const definitionTags = [];
+ for (const {tags, headwordIndices, entries, dictionary, sequences} of dictionaryEntry.definitions) {
+ const definitionTags2 = [];
+ for (const tag of tags) {
+ definitionTags.push(convertTag(tag));
+ definitionTags2.push(convertTag(tag));
+ }
+ if (!hasDefinitions) { continue; }
+ const only = merged ? getDisambiguations(dictionaryEntry.headwords, headwordIndices, allTermsSet, allReadingsSet) : void 0;
+ definitions.push({
+ sequence: sequences[0],
+ dictionary,
+ glossary: entries,
+ definitionTags: definitionTags2,
+ only
+ });
+ }
+
+ return {
+ uniqueTerms,
+ uniqueReadings,
+ definitionTags,
+ definitions: hasDefinitions ? definitions : void 0
+ };
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermFrequency[]}
+ */
+function getTermFrequencies(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of dictionaryEntry.frequencies) {
+ const {term, reading} = headwords[headwordIndex];
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ hasReading,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermPitchAccent[]}
+ */
+function getTermPitches(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const pitches = getPronunciationsOfType(pronunciations, 'pitch-accent');
+ const cachedPitches = createCachedValue(getTermPitchesInner.bind(null, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return getCachedValue(cachedPitches); }
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').PitchAccent[]} pitches
+ * @returns {import('anki-templates').PitchAccent[]}
+ */
+function getTermPitchesInner(pitches) {
+ const results = [];
+ for (const {position, tags} of pitches) {
+ const cachedTags = createCachedValue(convertTags.bind(null, tags));
+ results.push({
+ position,
+ get tags() { return getCachedValue(cachedTags); }
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermPhoneticTranscription[]}
+ */
+function getTermPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const phoneticTranscriptions = getPronunciationsOfType(pronunciations, 'phonetic-transcription');
+ const termPhoneticTranscriptions = getTermPhoneticTranscriptionsInner(phoneticTranscriptions);
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get phoneticTranscriptions() { return termPhoneticTranscriptions; }
+ });
+ }
+
+ return results;
+}
+
+/**
+ * @param {import('dictionary').PhoneticTranscription[]} phoneticTranscriptions
+ * @returns {import('anki-templates').PhoneticTranscription[]}
+ */
+function getTermPhoneticTranscriptionsInner(phoneticTranscriptions) {
+ const results = [];
+ for (const {ipa, tags} of phoneticTranscriptions) {
+ const cachedTags = createCachedValue(convertTags.bind(null, tags));
+ results.push({
+ ipa,
+ get tags() { return getCachedValue(cachedTags); }
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermHeadword[]}
+ */
+function getTermExpressions(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (let i = 0, ii = headwords.length; i < ii; ++i) {
+ const {term, reading, tags, sources: [{deinflectedText}], wordClasses} = headwords[i];
+ const termTags = createCachedValue(convertTags.bind(null, tags));
+ const frequencies = createCachedValue(getTermExpressionFrequencies.bind(null, dictionaryEntry, i));
+ const pitches = createCachedValue(getTermExpressionPitches.bind(null, dictionaryEntry, i));
+ const termFrequency = createCachedValue(getTermExpressionTermFrequency.bind(null, termTags));
+ const furiganaSegments = createCachedValue(getTermHeadwordFuriganaSegments.bind(null, term, reading));
+ const item = {
+ sourceTerm: deinflectedText,
+ expression: term,
+ reading,
+ get termTags() { return getCachedValue(termTags); },
+ get frequencies() { return getCachedValue(frequencies); },
+ get pitches() { return getCachedValue(pitches); },
+ get furiganaSegments() { return getCachedValue(furiganaSegments); },
+ get termFrequency() { return getCachedValue(termFrequency); },
+ wordClasses
+ };
+ results.push(item);
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {number} i
+ * @returns {import('anki-templates').TermFrequency[]}
+ */
+function getTermExpressionFrequencies(dictionaryEntry, i) {
+ const results = [];
+ const {headwords, frequencies} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of frequencies) {
+ if (headwordIndex !== i) { continue; }
+ const {term, reading} = headwords[headwordIndex];
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ hasReading,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {number} i
+ * @returns {import('anki-templates').TermPitchAccent[]}
+ */
+function getTermExpressionPitches(dictionaryEntry, i) {
+ const results = [];
+ const {headwords, pronunciations: termPronunciations} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of termPronunciations) {
+ if (headwordIndex !== i) { continue; }
+ const {term, reading} = headwords[headwordIndex];
+ const pitches = getPronunciationsOfType(pronunciations, 'pitch-accent');
+ const cachedPitches = createCachedValue(getTermPitchesInner.bind(null, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return getCachedValue(cachedPitches); }
+ });
+ }
+ return results;
+}
+
+/**
+ * @param {import('anki-templates-internal').CachedValue<import('anki-templates').Tag[]>} cachedTermTags
+ * @returns {import('anki-templates').TermFrequencyType}
+ */
+function getTermExpressionTermFrequency(cachedTermTags) {
+ const termTags = getCachedValue(cachedTermTags);
+ return getTermFrequency(termTags);
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('dictionary-data').TermGlossary[]|undefined}
+ */
+function getTermGlossaryArray(dictionaryEntry, type) {
+ if (type === 'term') {
+ const results = [];
+ for (const {entries} of dictionaryEntry.definitions) {
+ results.push(...entries);
+ }
+ return results;
+ }
+ return void 0;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').Tag[]|undefined}
+ */
+function getTermTags(dictionaryEntry, type) {
+ if (type !== 'termMerged') {
+ const results = [];
+ for (const {tag} of groupTermTags(dictionaryEntry)) {
+ results.push(convertTag(tag));
+ }
+ return results;
+ }
+ return void 0;
+}
+
+/**
+ * @param {import('dictionary').Tag[]} tags
+ * @returns {import('anki-templates').Tag[]}
+ */
+function convertTags(tags) {
+ const results = [];
+ for (const tag of tags) {
+ results.push(convertTag(tag));
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').Tag} tag
+ * @returns {import('anki-templates').Tag}
+ */
+function convertTag({name, category, content, order, score, dictionaries, redundant}) {
+ return {
+ name,
+ category,
+ notes: (content.length > 0 ? content[0] : ''),
+ order,
+ score,
+ dictionary: (dictionaries.length > 0 ? dictionaries[0] : ''),
+ redundant
+ };
+}
+
+/**
+ * @param {import('dictionary').Tag[]} tags
+ * @returns {import('anki-templates').PitchTag[]}
+ */
+function convertPitchTags(tags) {
+ const results = [];
+ for (const tag of tags) {
+ results.push(convertPitchTag(tag));
+ }
+ return results;
+}
+
+/**
+ * @param {import('dictionary').Tag} tag
+ * @returns {import('anki-templates').PitchTag}
+ */
+function convertPitchTag({name, category, content, order, score, dictionaries, redundant}) {
+ return {
+ name,
+ category,
+ order,
+ score,
+ content: [...content],
+ dictionaries: [...dictionaries],
+ redundant
+ };
+}
+
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').Cloze}
+ */
+function getCloze(dictionaryEntry, context) {
+ let originalText = '';
+ let term = '';
+ let reading = '';
+ switch (dictionaryEntry.type) {
+ case 'term':
+ {
+ term = dictionaryEntry.headwords[0].term;
+ reading = dictionaryEntry.headwords[0].reading;
+ const primarySource = getPrimarySource(dictionaryEntry);
+ if (primarySource !== null) { originalText = primarySource.originalText; }
+ }
+ break;
+ case 'kanji':
+ originalText = dictionaryEntry.character;
+ break;
+ }
+
+ const {sentence} = context;
+ let text;
+ let offset;
+ if (typeof sentence === 'object' && sentence !== null) {
+ ({text, offset} = sentence);
+ }
+ if (typeof text !== 'string') { text = ''; }
+ if (typeof offset !== 'number') { offset = 0; }
+
+ const textSegments = [];
+ for (const {text: text2, reading: reading2} of distributeFuriganaInflected(term, reading, text.substring(offset, offset + originalText.length))) {
+ textSegments.push(reading2.length > 0 ? reading2 : text2);
+ }
+
+ return {
+ sentence: text,
+ prefix: text.substring(0, offset),
+ body: text.substring(offset, offset + originalText.length),
+ bodyKana: textSegments.join(''),
+ suffix: text.substring(offset + originalText.length)
+ };
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').FuriganaSegment[]|undefined}
+ */
+function getTermFuriganaSegments(dictionaryEntry, type) {
+ if (type === 'term') {
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ return getTermHeadwordFuriganaSegments(term, reading);
+ }
+ }
+ return void 0;
+}
+
+/**
+ * @param {string} term
+ * @param {string} reading
+ * @returns {import('anki-templates').FuriganaSegment[]}
+ */
+function getTermHeadwordFuriganaSegments(term, reading) {
+ /** @type {import('anki-templates').FuriganaSegment[]} */
+ const result = [];
+ for (const {text, reading: reading2} of distributeFurigana(term, reading)) {
+ result.push({text, furigana: reading2});
+ }
+ return result;
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {number}
+ */
+function getTermDictionaryEntrySequence(dictionaryEntry) {
+ let hasSequence = false;
+ let mainSequence = -1;
+ if (!dictionaryEntry.isPrimary) { return mainSequence; }
+ for (const {sequences} of dictionaryEntry.definitions) {
+ const sequence = sequences[0];
+ if (!hasSequence) {
+ mainSequence = sequence;
+ hasSequence = true;
+ if (mainSequence === -1) { break; }
+ } else if (mainSequence !== sequence) {
+ mainSequence = -1;
+ break;
+ }
+ }
+ return mainSequence;
+}