summaryrefslogtreecommitdiff
path: root/ext/js/data
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-03-25 19:55:31 -0400
committerGitHub <noreply@github.com>2021-03-25 19:55:31 -0400
commit4be5c8fd9f7860e701d0b7d3c8c0ee934bc60a4f (patch)
treedcd78316afdf00bbb67d3d1aa6555a9c8ea3efec /ext/js/data
parente7035dcff41d94f20c0bc8865d413412afc7c229 (diff)
Refactor Translator and dictionary entry format (#1553)
* Update test data * Move translator.js * Create new version of Translator * Update Backend * Update DictionaryDataUtil * Update DisplayGenerator * Create AnkiNoteDataCreator * Replace AnkiNoteData with AnkiNoteDataCreator * Update tests * Remove AnkiNoteData * Update test data * Remove translator-old.js * Add TypeScript interface definitions for the new translator data format
Diffstat (limited to 'ext/js/data')
-rw-r--r--ext/js/data/anki-note-data-creator.js598
-rw-r--r--ext/js/data/anki-note-data.js299
2 files changed, 598 insertions, 299 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..c7047633
--- /dev/null
+++ b/ext/js/data/anki-note-data-creator.js
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2021 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/>.
+ */
+
+/* global
+ * DictionaryDataUtil
+ */
+
+/**
+ * This class is used to convert the internal dictionary entry format to the
+ * format used by Anki, for backwards compatibility.
+ */
+class AnkiNoteDataCreator {
+ /**
+ * Creates a new instance.
+ * @param japaneseUtil An instance of `JapaneseUtil`.
+ */
+ constructor(japaneseUtil) {
+ this._japaneseUtil = japaneseUtil;
+ }
+
+ /**
+ * Creates a compatibility representation of the specified data.
+ * @param marker The marker that is being used for template rendering.
+ * @returns An object used for rendering Anki templates.
+ */
+ create(marker, {
+ definition: dictionaryEntry,
+ resultOutputMode,
+ mode,
+ glossaryLayoutMode,
+ compactTags,
+ context,
+ injectedMedia=null
+ }) {
+ const self = this;
+ const definition = this.createCachedValue(this._getDefinition.bind(this, dictionaryEntry, injectedMedia, context, resultOutputMode));
+ const uniqueExpressions = this.createCachedValue(this._getUniqueExpressions.bind(this, dictionaryEntry));
+ const uniqueReadings = this.createCachedValue(this._getUniqueReadings.bind(this, dictionaryEntry));
+ const context2 = this.createCachedValue(this._getPublicContext.bind(this, context));
+ const pitches = this.createCachedValue(this._getPitches.bind(this, dictionaryEntry));
+ const pitchCount = this.createCachedValue(this._getPitchCount.bind(this, pitches));
+ return {
+ marker,
+ get definition() { return self.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 self.getCachedValue(uniqueExpressions); },
+ get uniqueReadings() { return self.getCachedValue(uniqueReadings); },
+ get pitches() { return self.getCachedValue(pitches); },
+ get pitchCount() { return self.getCachedValue(pitchCount); },
+ get context() { return self.getCachedValue(context2); }
+ };
+ }
+
+ /**
+ * Creates a deferred-evaluation value.
+ * @param getter The function to invoke to get the return value.
+ * @returns An object which can be passed into `getCachedValue`.
+ */
+ createCachedValue(getter) {
+ return {getter, hasValue: false, value: void 0};
+ }
+
+ /**
+ * Gets the value of a cached object.
+ * @param item An object that was returned from `createCachedValue`.
+ * @returns The result of evaluating the getter, which is cached after the first invocation.
+ */
+ getCachedValue(item) {
+ if (item.hasValue) { return item.value; }
+ const value = item.getter();
+ item.value = value;
+ item.hasValue = true;
+ return value;
+ }
+
+ // Private
+
+ _asObject(value) {
+ return (typeof value === 'object' && value !== null ? value : {});
+ }
+
+ _getPrimarySource(dictionaryEntry) {
+ for (const headword of dictionaryEntry.headwords) {
+ for (const source of headword.sources) {
+ if (source.isPrimary) { return source; }
+ }
+ }
+ return null;
+ }
+
+ _getUniqueExpressions(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {term} of dictionaryEntry.headwords) {
+ results.add(term);
+ }
+ return [...results];
+ } else {
+ return [];
+ }
+ }
+
+ _getUniqueReadings(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {reading} of dictionaryEntry.headwords) {
+ results.add(reading);
+ }
+ return [...results];
+ } else {
+ return [];
+ }
+ }
+
+ _getPublicContext(context) {
+ let {documentTitle} = this._asObject(context);
+ if (typeof documentTitle !== 'string') { documentTitle = ''; }
+ return {
+ document: {
+ title: documentTitle
+ }
+ };
+ }
+
+ _getPitches(dictionaryEntry) {
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pitches} of DictionaryDataUtil.getPitchAccentInfos(dictionaryEntry)) {
+ const pitches2 = [];
+ for (const {terms, reading, position, tags, exclusiveTerms, exclusiveReadings} of pitches) {
+ pitches2.push({
+ expressions: terms,
+ reading,
+ position,
+ tags,
+ exclusiveExpressions: exclusiveTerms,
+ exclusiveReadings
+ });
+ }
+ results.push({dictionary, pitches: pitches2});
+ }
+ }
+ return results;
+ }
+
+ _getPitchCount(cachedPitches) {
+ const pitches = this.getCachedValue(cachedPitches);
+ return pitches.reduce((i, v) => i + v.pitches.length, 0);
+ }
+
+ _getDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode) {
+ switch (dictionaryEntry.type) {
+ case 'term':
+ return this._getTermDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode);
+ case 'kanji':
+ return this._getKanjiDefinition(dictionaryEntry, injectedMedia, context);
+ default:
+ return {};
+ }
+ }
+
+ _getKanjiDefinition(dictionaryEntry, injectedMedia, context) {
+ const self = this;
+
+ const {character, dictionary, onyomi, kunyomi, definitions} = dictionaryEntry;
+
+ const {
+ screenshotFileName=null,
+ clipboardImageFileName=null,
+ clipboardText=null,
+ audioFileName=null
+ } = this._asObject(injectedMedia);
+
+ let {url} = this._asObject(context);
+ if (typeof url !== 'string') { url = ''; }
+
+ const stats = this.createCachedValue(this._getKanjiStats.bind(this, dictionaryEntry));
+ const tags = this.createCachedValue(this._convertTags.bind(this, dictionaryEntry.tags));
+ const frequencies = this.createCachedValue(this._getKanjiFrequencies.bind(this, dictionaryEntry));
+ const cloze = this.createCachedValue(this._getCloze.bind(this, dictionaryEntry, context));
+
+ return {
+ type: 'kanji',
+ character,
+ dictionary,
+ onyomi,
+ kunyomi,
+ glossary: definitions,
+ get tags() { return self.getCachedValue(tags); },
+ get stats() { return self.getCachedValue(stats); },
+ get frequencies() { return self.getCachedValue(frequencies); },
+ screenshotFileName,
+ clipboardImageFileName,
+ clipboardText,
+ audioFileName,
+ url,
+ get cloze() { return self.getCachedValue(cloze); }
+ };
+ }
+
+ _getKanjiStats(dictionaryEntry) {
+ const results = {};
+ for (const [key, value] of Object.entries(dictionaryEntry.stats)) {
+ results[key] = value.map(this._convertKanjiStat.bind(this));
+ }
+ return results;
+ }
+
+ _convertKanjiStat({name, category, content, order, score, dictionary, value}) {
+ return {
+ name,
+ category,
+ notes: content,
+ order,
+ score,
+ dictionary,
+ value
+ };
+ }
+
+ _getKanjiFrequencies(dictionaryEntry) {
+ const results = [];
+ for (const {index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency} of dictionaryEntry.frequencies) {
+ results.push({
+ index,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ character,
+ frequency
+ });
+ }
+ return results;
+ }
+
+ _getTermDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode) {
+ const self = this;
+
+ let type = 'term';
+ switch (resultOutputMode) {
+ case 'group': type = 'termGrouped'; break;
+ case 'merge': type = 'termMerged'; break;
+ }
+
+ const {id, inflections, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount} = dictionaryEntry;
+
+ const {
+ screenshotFileName=null,
+ clipboardImageFileName=null,
+ clipboardText=null,
+ audioFileName=null
+ } = this._asObject(injectedMedia);
+
+ let {url} = this._asObject(context);
+ if (typeof url !== 'string') { url = ''; }
+
+ const primarySource = this._getPrimarySource(dictionaryEntry);
+
+ const dictionaryNames = this.createCachedValue(this._getTermDictionaryNames.bind(this, dictionaryEntry));
+ const commonInfo = this.createCachedValue(this._getTermDictionaryEntryCommonInfo.bind(this, dictionaryEntry, type));
+ const termTags = this.createCachedValue(this._getTermTags.bind(this, dictionaryEntry, type));
+ const expressions = this.createCachedValue(this._getTermExpressions.bind(this, dictionaryEntry));
+ const frequencies = this.createCachedValue(this._getTermFrequencies.bind(this, dictionaryEntry));
+ const pitches = this.createCachedValue(this._getTermPitches.bind(this, dictionaryEntry));
+ const glossary = this.createCachedValue(this._getTermGlossaryArray.bind(this, dictionaryEntry, type));
+ const cloze = this.createCachedValue(this._getCloze.bind(this, dictionaryEntry, context));
+ const furiganaSegments = this.createCachedValue(this._getTermFuriganaSegments.bind(this, dictionaryEntry, type));
+
+ return {
+ type,
+ id: (type === 'term' ? 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),
+ reasons: inflections,
+ score,
+ isPrimary: (type === 'term' ? dictionaryEntry.isPrimary : void 0),
+ sequence: (type === 'term' ? dictionaryEntry.sequence : void 0),
+ get dictionary() { return self.getCachedValue(dictionaryNames)[0]; },
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ get dictionaryNames() { return self.getCachedValue(dictionaryNames); },
+ get expression() {
+ const {uniqueTerms} = self.getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueTerms[0] : uniqueTerms);
+ },
+ get reading() {
+ const {uniqueReadings} = self.getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueReadings[0] : uniqueReadings);
+ },
+ get expressions() { return self.getCachedValue(expressions); },
+ get glossary() { return self.getCachedValue(glossary); },
+ get definitionTags() { return type === 'term' ? self.getCachedValue(commonInfo).definitionTags : void 0; },
+ get termTags() { return self.getCachedValue(termTags); },
+ get definitions() { return self.getCachedValue(commonInfo).definitions; },
+ get frequencies() { return self.getCachedValue(frequencies); },
+ get pitches() { return self.getCachedValue(pitches); },
+ sourceTermExactMatchCount,
+ screenshotFileName,
+ clipboardImageFileName,
+ clipboardText,
+ audioFileName,
+ url,
+ get cloze() { return self.getCachedValue(cloze); },
+ get furiganaSegments() { return self.getCachedValue(furiganaSegments); }
+ };
+ }
+
+ _getTermDictionaryNames(dictionaryEntry) {
+ const dictionaryNames = new Set();
+ for (const {dictionary} of dictionaryEntry.definitions) {
+ dictionaryNames.add(dictionary);
+ }
+ return [...dictionaryNames];
+ }
+
+ _getTermDictionaryEntryCommonInfo(dictionaryEntry, type) {
+ const merged = (type === 'termMerged');
+ const hasDefinitions = (type !== 'term');
+
+ const allTermsSet = new Set();
+ const allReadingsSet = new Set();
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ allTermsSet.add(term);
+ allReadingsSet.add(reading);
+ }
+ const uniqueTerms = [...allTermsSet];
+ const uniqueReadings = [...allReadingsSet];
+
+ const definitions = [];
+ const definitionTags = [];
+ for (const {tags, headwordIndices, entries, dictionary} of dictionaryEntry.definitions) {
+ const definitionTags2 = [];
+ for (const tag of tags) {
+ definitionTags.push(this._convertTag(tag));
+ definitionTags2.push(this._convertTag(tag));
+ }
+ if (!hasDefinitions) { continue; }
+ const only = merged ? DictionaryDataUtil.getDisambiguations(dictionaryEntry.headwords, headwordIndices, allTermsSet, allReadingsSet) : void 0;
+ definitions.push({
+ dictionary,
+ glossary: entries,
+ definitionTags: definitionTags2,
+ only
+ });
+ }
+
+ return {
+ uniqueTerms,
+ uniqueReadings,
+ definitionTags,
+ definitions: hasDefinitions ? definitions : void 0
+ };
+ }
+
+ _getTermFrequencies(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency} 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
+ });
+ }
+ return results;
+ }
+
+ _getTermPitches(dictionaryEntry) {
+ const self = this;
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return self.getCachedValue(cachedPitches); }
+ });
+ }
+ return results;
+ }
+
+ _getTermPitchesInner(pitches) {
+ const self = this;
+ const results = [];
+ for (const {position, tags} of pitches) {
+ const cachedTags = this.createCachedValue(this._convertTags.bind(this, tags));
+ results.push({
+ position,
+ get tags() { return self.getCachedValue(cachedTags); }
+ });
+ }
+ return results;
+ }
+
+ _getTermExpressions(dictionaryEntry) {
+ const self = this;
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (let i = 0, ii = headwords.length; i < ii; ++i) {
+ const {term, reading, tags, sources: [{deinflectedText}]} = headwords[i];
+ const termTags = this.createCachedValue(this._convertTags.bind(this, tags));
+ const frequencies = this.createCachedValue(this._getTermExpressionFrequencies.bind(this, dictionaryEntry, i));
+ const pitches = this.createCachedValue(this._getTermExpressionPitches.bind(this, dictionaryEntry, i));
+ const termFrequency = this.createCachedValue(this._getTermExpressionTermFrequency.bind(this, termTags));
+ const furiganaSegments = this.createCachedValue(this._getTermHeadwordFuriganaSegments.bind(this, term, reading));
+ const item = {
+ sourceTerm: deinflectedText,
+ expression: term,
+ reading,
+ get termTags() { return self.getCachedValue(termTags); },
+ get frequencies() { return self.getCachedValue(frequencies); },
+ get pitches() { return self.getCachedValue(pitches); },
+ get furiganaSegments() { return self.getCachedValue(furiganaSegments); },
+ get termFrequency() { return self.getCachedValue(termFrequency); }
+ };
+ results.push(item);
+ }
+ return results;
+ }
+
+ _getTermExpressionFrequencies(dictionaryEntry, i) {
+ const results = [];
+ const {headwords, frequencies} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency} 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
+ });
+ }
+ return results;
+ }
+
+ _getTermExpressionPitches(dictionaryEntry, i) {
+ const self = this;
+ const results = [];
+ const {headwords, pronunciations} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of pronunciations) {
+ if (headwordIndex !== i) { continue; }
+ const {term, reading} = headwords[headwordIndex];
+ const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return self.getCachedValue(cachedPitches); }
+ });
+ }
+ return results;
+ }
+
+ _getTermExpressionTermFrequency(cachedTermTags) {
+ const termTags = this.getCachedValue(cachedTermTags);
+ return DictionaryDataUtil.getTermFrequency(termTags);
+ }
+
+ _getTermGlossaryArray(dictionaryEntry, type) {
+ if (type === 'term') {
+ const results = [];
+ for (const {entries} of dictionaryEntry.definitions) {
+ results.push(...entries);
+ }
+ return results;
+ }
+ return void 0;
+ }
+
+ _getTermTags(dictionaryEntry, type) {
+ if (type !== 'termMerged') {
+ const results = [];
+ for (const {tag} of DictionaryDataUtil.groupTermTags(dictionaryEntry)) {
+ results.push(this._convertTag(tag));
+ }
+ return results;
+ }
+ return void 0;
+ }
+
+ _convertTags(tags) {
+ const results = [];
+ for (const tag of tags) {
+ results.push(this._convertTag(tag));
+ }
+ return results;
+ }
+
+ _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
+ };
+ }
+
+ _getCloze(dictionaryEntry, context) {
+ let originalText = '';
+ switch (dictionaryEntry.type) {
+ case 'term':
+ {
+ const primarySource = this._getPrimarySource(dictionaryEntry);
+ if (primarySource !== null) { originalText = primarySource.originalText; }
+ }
+ break;
+ case 'kanji':
+ originalText = dictionaryEntry.character;
+ break;
+ }
+
+ const {sentence} = this._asObject(context);
+ let {text, offset} = this._asObject(sentence);
+ if (typeof text !== 'string') { text = ''; }
+ if (typeof offset !== 'number') { offset = 0; }
+
+ return {
+ sentence: text,
+ prefix: text.substring(0, offset),
+ body: text.substring(offset, offset + originalText.length),
+ suffix: text.substring(offset + originalText.length)
+ };
+ }
+
+ _getTermFuriganaSegments(dictionaryEntry, type) {
+ if (type === 'term') {
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ return this._getTermHeadwordFuriganaSegments(term, reading);
+ }
+ }
+ return void 0;
+ }
+
+ _getTermHeadwordFuriganaSegments(term, reading) {
+ return this._japaneseUtil.distributeFurigana(term, reading);
+ }
+}
diff --git a/ext/js/data/anki-note-data.js b/ext/js/data/anki-note-data.js
deleted file mode 100644
index f7f4c641..00000000
--- a/ext/js/data/anki-note-data.js
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2021 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/>.
- */
-
-/* global
- * DictionaryDataUtil
- */
-
-/**
- * This class represents the data that is exposed to the Anki template renderer.
- * The public properties and data should be backwards compatible.
- */
-class AnkiNoteData {
- constructor(japaneseUtil, marker, {
- definition,
- resultOutputMode,
- mode,
- glossaryLayoutMode,
- compactTags,
- context,
- injectedMedia=null
- }) {
- this._japaneseUtil = japaneseUtil;
- this._definition = definition;
- this._resultOutputMode = resultOutputMode;
- this._mode = mode;
- this._glossaryLayoutMode = glossaryLayoutMode;
- this._compactTags = compactTags;
- this._context = context;
- this._marker = marker;
- this._injectedMedia = injectedMedia;
- this._pitches = null;
- this._pitchCount = null;
- this._uniqueExpressions = null;
- this._uniqueReadings = null;
- this._publicContext = null;
- this._cloze = null;
- this._furiganaSegmentsCache = null;
-
- this._prepareDefinition(definition, injectedMedia, context);
- }
-
- get marker() {
- return this._marker;
- }
-
- set marker(value) {
- this._marker = value;
- }
-
- get definition() {
- return this._definition;
- }
-
- get uniqueExpressions() {
- if (this._uniqueExpressions === null) {
- this._uniqueExpressions = this._getUniqueExpressions();
- }
- return this._uniqueExpressions;
- }
-
- get uniqueReadings() {
- if (this._uniqueReadings === null) {
- this._uniqueReadings = this._getUniqueReadings();
- }
- return this._uniqueReadings;
- }
-
- get pitches() {
- if (this._pitches === null) {
- this._pitches = DictionaryDataUtil.getPitchAccentInfos(this._definition);
- }
- return this._pitches;
- }
-
- get pitchCount() {
- if (this._pitchCount === null) {
- this._pitchCount = this.pitches.reduce((i, v) => i + v.pitches.length, 0);
- }
- return this._pitchCount;
- }
-
- get group() {
- return this._resultOutputMode === 'group';
- }
-
- get merge() {
- return this._resultOutputMode === 'merge';
- }
-
- get modeTermKanji() {
- return this._mode === 'term-kanji';
- }
-
- get modeTermKana() {
- return this._mode === 'term-kana';
- }
-
- get modeKanji() {
- return this._mode === 'kanji';
- }
-
- get compactGlossaries() {
- return this._glossaryLayoutMode === 'compact';
- }
-
- get glossaryLayoutMode() {
- return this._glossaryLayoutMode;
- }
-
- get compactTags() {
- return this._compactTags;
- }
-
- get context() {
- if (this._publicContext === null) {
- this._publicContext = this._getPublicContext();
- }
- return this._publicContext;
- }
-
- createPublic() {
- const self = this;
- return {
- get marker() { return self.marker; },
- set marker(value) { self.marker = value; },
- get definition() { return self.definition; },
- get glossaryLayoutMode() { return self.glossaryLayoutMode; },
- get compactTags() { return self.compactTags; },
- get group() { return self.group; },
- get merge() { return self.merge; },
- get modeTermKanji() { return self.modeTermKanji; },
- get modeTermKana() { return self.modeTermKana; },
- get modeKanji() { return self.modeKanji; },
- get compactGlossaries() { return self.compactGlossaries; },
- get uniqueExpressions() { return self.uniqueExpressions; },
- get uniqueReadings() { return self.uniqueReadings; },
- get pitches() { return self.pitches; },
- get pitchCount() { return self.pitchCount; },
- get context() { return self.context; }
- };
- }
-
- // Private
-
- _asObject(value) {
- return (typeof value === 'object' && value !== null ? value : {});
- }
-
- _getUniqueExpressions() {
- const results = new Set();
- const definition = this._definition;
- if (definition.type !== 'kanji') {
- for (const {expression} of definition.expressions) {
- results.add(expression);
- }
- }
- return [...results];
- }
-
- _getUniqueReadings() {
- const results = new Set();
- const definition = this._definition;
- if (definition.type !== 'kanji') {
- for (const {reading} of definition.expressions) {
- results.add(reading);
- }
- }
- return [...results];
- }
-
- _getPublicContext() {
- let {documentTitle} = this._asObject(this._context);
- if (typeof documentTitle !== 'string') { documentTitle = ''; }
-
- return {
- document: {
- title: documentTitle
- }
- };
- }
-
- _getCloze() {
- const {sentence} = this._asObject(this._context);
- let {text, offset} = this._asObject(sentence);
- if (typeof text !== 'string') { text = ''; }
- if (typeof offset !== 'number') { offset = 0; }
-
- const definition = this._definition;
- const source = definition.type === 'kanji' ? definition.character : definition.rawSource;
-
- return {
- sentence: text,
- prefix: text.substring(0, offset),
- body: text.substring(offset, offset + source.length),
- suffix: text.substring(offset + source.length)
- };
- }
-
- _getClozeCached() {
- if (this._cloze === null) {
- this._cloze = this._getCloze();
- }
- return this._cloze;
- }
-
- _prepareDefinition(definition, injectedMedia, context) {
- const {
- screenshotFileName=null,
- clipboardImageFileName=null,
- clipboardText=null,
- audioFileName=null
- } = this._asObject(injectedMedia);
-
- let {url} = this._asObject(context);
- if (typeof url !== 'string') { url = ''; }
-
- definition.screenshotFileName = screenshotFileName;
- definition.clipboardImageFileName = clipboardImageFileName;
- definition.clipboardText = clipboardText;
- definition.audioFileName = audioFileName;
- definition.url = url;
- Object.defineProperty(definition, 'cloze', {
- configurable: true,
- enumerable: true,
- get: this._getClozeCached.bind(this)
- });
-
- for (const definition2 of this._getAllDefinitions(definition)) {
- if (definition2.type === 'term') {
- this._defineFuriganaSegments(definition2);
- }
- if (definition2.type === 'kanji') { continue; }
- for (const expression of definition2.expressions) {
- this._defineFuriganaSegments(expression);
- this._defineTermFrequency(expression);
- }
- }
- }
-
- _defineFuriganaSegments(object) {
- Object.defineProperty(object, 'furiganaSegments', {
- configurable: true,
- enumerable: true,
- get: this._getFuriganaSegments.bind(this, object)
- });
- }
-
- _defineTermFrequency(object) {
- Object.defineProperty(object, 'termFrequency', {
- configurable: true,
- enumerable: true,
- get: this._getTermFrequency.bind(this, object)
- });
- }
-
- _getFuriganaSegments(object) {
- if (this._furiganaSegmentsCache !== null) {
- const cachedResult = this._furiganaSegmentsCache.get(object);
- if (typeof cachedResult !== 'undefined') { return cachedResult; }
- } else {
- this._furiganaSegmentsCache = new Map();
- }
-
- const {expression, reading} = object;
- const result = this._japaneseUtil.distributeFurigana(expression, reading);
- this._furiganaSegmentsCache.set(object, result);
- return result;
- }
-
- _getTermFrequency(object) {
- const {termTags} = object;
- return DictionaryDataUtil.getTermFrequency(termTags);
- }
-
- _getAllDefinitions(definition) {
- const definitions = [definition];
- for (let i = 0; i < definitions.length; ++i) {
- const childDefinitions = definitions[i].definitions;
- if (Array.isArray(childDefinitions)) {
- definitions.push(...childDefinitions);
- }
- }
- return definitions;
- }
-}