diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-03-25 19:55:31 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-25 19:55:31 -0400 | 
| commit | 4be5c8fd9f7860e701d0b7d3c8c0ee934bc60a4f (patch) | |
| tree | dcd78316afdf00bbb67d3d1aa6555a9c8ea3efec /ext/js/data | |
| parent | e7035dcff41d94f20c0bc8865d413412afc7c229 (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.js | 598 | ||||
| -rw-r--r-- | ext/js/data/anki-note-data.js | 299 | 
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; -    } -} |