summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/js/background/backend.js28
-rw-r--r--ext/js/data/anki-note-data-creator.js598
-rw-r--r--ext/js/data/anki-note-data.js299
-rw-r--r--ext/js/display/display-generator.js141
-rw-r--r--ext/js/language/dictionary-data-util.js136
-rw-r--r--ext/js/language/translator.js1572
-rw-r--r--ext/js/templates/template-renderer-frame-main.js5
-rw-r--r--ext/template-renderer.html2
8 files changed, 1519 insertions, 1262 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index 5cbb65cb..82457a7e 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -414,9 +414,9 @@ class Backend {
const options = this._getProfileOptions(optionsContext);
const {general: {resultOutputMode: mode, maxResults}} = options;
const findTermsOptions = this._getTranslatorFindTermsOptions(details, options);
- const [definitions, length] = await this._translator.findTerms(mode, text, findTermsOptions);
- definitions.splice(maxResults);
- return {length, definitions};
+ const {dictionaryEntries, originalTextLength} = await this._translator.findTerms(mode, text, findTermsOptions);
+ dictionaryEntries.splice(maxResults);
+ return {length: originalTextLength, definitions: dictionaryEntries};
}
async _onApiTextParse({text, optionsContext}) {
@@ -1050,7 +1050,7 @@ class Backend {
let i = 0;
const ii = text.length;
while (i < ii) {
- const [definitions, sourceLength] = await this._translator.findTerms(
+ const {dictionaryEntries, originalTextLength} = await this._translator.findTerms(
'simple',
text.substring(i, i + scanningLength),
findTermsOptions
@@ -1058,20 +1058,20 @@ class Backend {
const codePoint = text.codePointAt(i);
const character = String.fromCodePoint(codePoint);
if (
- definitions.length > 0 &&
- sourceLength > 0 &&
- (sourceLength !== character.length || this._japaneseUtil.isCodePointJapanese(codePoint))
+ dictionaryEntries.length > 0 &&
+ originalTextLength > 0 &&
+ (originalTextLength !== character.length || this._japaneseUtil.isCodePointJapanese(codePoint))
) {
previousUngroupedSegment = null;
- const {expression, reading} = definitions[0];
- const source = text.substring(i, i + sourceLength);
- const term = [];
- for (const {text: text2, furigana} of jp.distributeFuriganaInflected(expression, reading, source)) {
+ const {headwords: [{term, reading}]} = dictionaryEntries[0];
+ const source = text.substring(i, i + originalTextLength);
+ const textSegments = [];
+ for (const {text: text2, furigana} of jp.distributeFuriganaInflected(term, reading, source)) {
const reading2 = jp.convertReading(text2, furigana, readingMode);
- term.push({text: text2, reading: reading2});
+ textSegments.push({text: text2, reading: reading2});
}
- results.push(term);
- i += sourceLength;
+ results.push(textSegments);
+ i += originalTextLength;
} else {
if (previousUngroupedSegment === null) {
previousUngroupedSegment = {text: character, reading: ''};
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;
- }
-}
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index 3977815b..be5d5e66 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -60,23 +60,20 @@ class DisplayGenerator {
const definitionsContainer = node.querySelector('.definition-list');
const termTagsContainer = node.querySelector('.expression-list-tag-list');
- const {expressions, type, reasons, frequencies} = details;
- const definitions = (type === 'term' ? [details] : details.definitions);
- const merged = (type === 'termMerged' || type === 'termMergedByGlossary');
+ const {headwords: expressions, type, inflections: reasons, definitions, frequencies, pronunciations} = details;
const pitches = DictionaryDataUtil.getPitchAccentInfos(details);
const pitchCount = pitches.reduce((i, v) => i + v.pitches.length, 0);
- const groupedFrequencies = DictionaryDataUtil.groupTermFrequencies(frequencies);
+ const groupedFrequencies = DictionaryDataUtil.groupTermFrequencies(details);
const termTags = DictionaryDataUtil.groupTermTags(details);
const uniqueExpressions = new Set();
const uniqueReadings = new Set();
- for (const {expression, reading} of expressions) {
+ for (const {term: expression, reading} of expressions) {
uniqueExpressions.add(expression);
uniqueReadings.add(reading);
}
node.dataset.format = type;
- node.dataset.expressionMulti = `${merged}`;
node.dataset.expressionCount = `${expressions.length}`;
node.dataset.definitionCount = `${definitions.length}`;
node.dataset.pitchAccentDictionaryCount = `${pitches.length}`;
@@ -86,7 +83,13 @@ class DisplayGenerator {
node.dataset.frequencyCount = `${frequencies.length}`;
node.dataset.groupedFrequencyCount = `${groupedFrequencies.length}`;
- this._appendMultiple(expressionsContainer, this._createTermExpression.bind(this), expressions);
+ for (let i = 0, ii = expressions.length; i < ii; ++i) {
+ const node2 = this._createTermExpression(expressions[i], i, pronunciations);
+ node2.dataset.index = `${i}`;
+ expressionsContainer.appendChild(node2);
+ }
+ expressionsContainer.dataset.count = `${expressions.length}`;
+
this._appendMultiple(reasonsContainer, this._createTermReason.bind(this), reasons);
this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false);
this._appendMultiple(pitchesContainer, this._createPitches.bind(this), pitches);
@@ -114,7 +117,7 @@ class DisplayGenerator {
dictionaryTag.name = dictionary;
}
- const node2 = this._createTermDefinitionItem(definition, dictionaryTag);
+ const node2 = this._createTermDefinitionItem(definition, dictionaryTag, expressions, uniqueExpressions, uniqueReadings);
node2.dataset.index = `${i}`;
definitionsContainer.appendChild(node2);
}
@@ -144,7 +147,7 @@ class DisplayGenerator {
this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, true);
this._appendMultiple(tagContainer, this._createTag.bind(this), [...details.tags, dictionaryTag]);
- this._appendMultiple(glossaryContainer, this._createKanjiGlossaryItem.bind(this), details.glossary);
+ this._appendMultiple(glossaryContainer, this._createKanjiGlossaryItem.bind(this), details.definitions);
this._appendMultiple(chineseReadingsContainer, this._createKanjiReading.bind(this), details.onyomi);
this._appendMultiple(japaneseReadingsContainer, this._createKanjiReading.bind(this), details.kunyomi);
@@ -229,8 +232,8 @@ class DisplayGenerator {
// Private
- _createTermExpression(details) {
- const {expression, reading, termTags, pitches} = details;
+ _createTermExpression(headword, headwordIndex, pronunciations) {
+ const {term: expression, reading, tags: termTags} = headword;
const searchQueries = [];
if (expression) { searchQueries.push(expression); }
@@ -244,7 +247,7 @@ class DisplayGenerator {
node.dataset.readingIsSame = `${reading === expression}`;
node.dataset.frequency = DictionaryDataUtil.getTermFrequency(termTags);
- const pitchAccentCategories = this._getPitchAccentCategories(pitches);
+ const pitchAccentCategories = this._getPitchAccentCategories(reading, pronunciations, headwordIndex);
if (pitchAccentCategories !== null) {
node.dataset.pitchAccentCategories = pitchAccentCategories;
}
@@ -266,19 +269,21 @@ class DisplayGenerator {
return fragment;
}
- _createTermDefinitionItem(details, dictionaryTag) {
+ _createTermDefinitionItem(details, dictionaryTag, headwords, uniqueTerms, uniqueReadings) {
+ const {dictionary, tags, headwordIndices, entries} = details;
+ const disambiguations = DictionaryDataUtil.getDisambiguations(headwords, headwordIndices, uniqueTerms, uniqueReadings);
+
const node = this._templates.instantiate('definition-item');
const tagListContainer = node.querySelector('.definition-tag-list');
const onlyListContainer = node.querySelector('.definition-disambiguation-list');
const glossaryContainer = node.querySelector('.glossary-list');
- const {dictionary, definitionTags} = details;
node.dataset.dictionary = dictionary;
- this._appendMultiple(tagListContainer, this._createTag.bind(this), [...definitionTags, dictionaryTag]);
- this._appendMultiple(onlyListContainer, this._createTermDisambiguation.bind(this), details.only);
- this._appendMultiple(glossaryContainer, this._createTermGlossaryItem.bind(this), details.glossary, dictionary);
+ this._appendMultiple(tagListContainer, this._createTag.bind(this), [...tags, dictionaryTag]);
+ this._appendMultiple(onlyListContainer, this._createTermDisambiguation.bind(this), disambiguations);
+ this._appendMultiple(glossaryContainer, this._createTermGlossaryItem.bind(this), entries, dictionary);
return node;
}
@@ -406,11 +411,12 @@ class DisplayGenerator {
}
_createKanjiInfoTableItem(details) {
+ const {content, name, value} = details;
const node = this._templates.instantiate('kanji-info-table-item');
const nameNode = node.querySelector('.kanji-info-table-item-header');
const valueNode = node.querySelector('.kanji-info-table-item-value');
- this._setTextContent(nameNode, details.notes || details.name);
- this._setTextContent(valueNode, details.value);
+ this._setTextContent(nameNode, content.length > 0 ? content : name);
+ this._setTextContent(valueNode, value);
return node;
}
@@ -419,37 +425,46 @@ class DisplayGenerator {
}
_createTag(details) {
- const {notes, name, category, redundant} = details;
+ const {content, name, category, redundant} = details;
const node = this._templates.instantiate('tag');
const inner = node.querySelector('.tag-label-content');
- node.title = notes;
+ const contentString = content.join('\n');
+
+ node.title = contentString;
this._setTextContent(inner, name);
- node.dataset.details = notes || name;
+ node.dataset.details = contentString.length > 0 ? contentString : name;
node.dataset.category = category;
if (redundant) { node.dataset.redundant = 'true'; }
return node;
}
- _createTermTag(details, totalExpressionCount) {
- const {tag, expressions} = details;
+ _createTermTag(details, totalHeadwordCount) {
+ const {tag, headwordIndices} = details;
const node = this._createTag(tag);
- node.dataset.disambiguation = `${JSON.stringify(expressions)}`;
- node.dataset.totalExpressionCount = `${totalExpressionCount}`;
- node.dataset.matchedExpressionCount = `${expressions.length}`;
- node.dataset.unmatchedExpressionCount = `${Math.max(0, totalExpressionCount - expressions.length)}`;
+ node.dataset.headwords = headwordIndices.join(' ');
+ node.dataset.totalExpressionCount = `${totalHeadwordCount}`;
+ node.dataset.matchedExpressionCount = `${headwordIndices.length}`;
+ node.dataset.unmatchedExpressionCount = `${Math.max(0, totalHeadwordCount - headwordIndices.length)}`;
return node;
}
- _createSearchTag(text) {
- return this._createTag({
- notes: '',
- name: text,
- category: 'search',
+ _createTagData(name, category) {
+ return {
+ name,
+ category,
+ order: 0,
+ score: 0,
+ content: [],
+ dictionaries: [],
redundant: false
- });
+ };
+ }
+
+ _createSearchTag(text) {
+ return this._createTag(this._createTagData(text, 'search'));
}
_createPitches(details) {
@@ -462,7 +477,7 @@ class DisplayGenerator {
node.dataset.pitchesMulti = 'true';
node.dataset.pitchesCount = `${pitches.length}`;
- const tag = this._createTag({notes: '', name: dictionary, category: 'pitch-accent-dictionary'});
+ const tag = this._createTag(this._createTagData(dictionary, 'pitch-accent-dictionary'));
node.querySelector('.pitch-accent-group-tag-list').appendChild(tag);
let hasTags = false;
@@ -482,7 +497,7 @@ class DisplayGenerator {
_createPitch(details) {
const jp = this._japaneseUtil;
- const {reading, position, tags, exclusiveExpressions, exclusiveReadings} = details;
+ const {reading, position, tags, exclusiveTerms, exclusiveReadings} = details;
const morae = jp.getKanaMorae(reading);
const node = this._templates.instantiate('pitch-accent');
@@ -497,7 +512,7 @@ class DisplayGenerator {
this._appendMultiple(n, this._createTag.bind(this), tags);
n = node.querySelector('.pitch-accent-disambiguation-list');
- this._createPitchAccentDisambiguations(n, exclusiveExpressions, exclusiveReadings);
+ this._createPitchAccentDisambiguations(n, exclusiveTerms, exclusiveReadings);
n = node.querySelector('.pitch-accent-characters');
for (let i = 0, ii = morae.length; i < ii; ++i) {
@@ -523,9 +538,9 @@ class DisplayGenerator {
return node;
}
- _createPitchAccentDisambiguations(container, exclusiveExpressions, exclusiveReadings) {
+ _createPitchAccentDisambiguations(container, exclusiveTerms, exclusiveReadings) {
const templateName = 'pitch-accent-disambiguation';
- for (const exclusiveExpression of exclusiveExpressions) {
+ for (const exclusiveExpression of exclusiveTerms) {
const node = this._templates.instantiate(templateName);
node.dataset.type = 'expression';
this._setTextContent(node, exclusiveExpression, 'ja');
@@ -539,8 +554,8 @@ class DisplayGenerator {
container.appendChild(node);
}
- container.dataset.count = `${exclusiveExpressions.length + exclusiveReadings.length}`;
- container.dataset.expressionCount = `${exclusiveExpressions.length}`;
+ container.dataset.count = `${exclusiveTerms.length + exclusiveReadings.length}`;
+ container.dataset.expressionCount = `${exclusiveTerms.length}`;
container.dataset.readingCount = `${exclusiveReadings.length}`;
}
@@ -586,7 +601,7 @@ class DisplayGenerator {
}
_createFrequencyGroup(details, kanji) {
- const {dictionary, frequencyData} = details;
+ const {dictionary, frequencies} = details;
const node = this._templates.instantiate('frequency-group-item');
const body = node.querySelector('.tag-body-content');
@@ -594,36 +609,37 @@ class DisplayGenerator {
this._setTextContent(node.querySelector('.tag-label-content'), dictionary);
node.dataset.details = dictionary;
- for (let i = 0, ii = frequencyData.length; i < ii; ++i) {
- const item = frequencyData[i];
+ const ii = frequencies.length;
+ for (let i = 0; i < ii; ++i) {
+ const item = frequencies[i];
const itemNode = (kanji ? this._createKanjiFrequency(item, dictionary) : this._createTermFrequency(item, dictionary));
itemNode.dataset.index = `${i}`;
body.appendChild(itemNode);
}
- body.dataset.count = `${frequencyData.length}`;
- node.dataset.count = `${frequencyData.length}`;
+ body.dataset.count = `${ii}`;
+ node.dataset.count = `${ii}`;
node.dataset.details = dictionary;
return node;
}
_createTermFrequency(details, dictionary) {
- const {expression, reading, frequencies} = details;
+ const {term, reading, values} = details;
const node = this._templates.instantiate('term-frequency-item');
this._setTextContent(node.querySelector('.tag-label-content'), dictionary);
- const frequency = frequencies.join(', ');
+ const frequency = values.join(', ');
- this._setTextContent(node.querySelector('.frequency-disambiguation-expression'), expression, 'ja');
+ this._setTextContent(node.querySelector('.frequency-disambiguation-expression'), term, 'ja');
this._setTextContent(node.querySelector('.frequency-disambiguation-reading'), (reading !== null ? reading : ''), 'ja');
this._setTextContent(node.querySelector('.frequency-value'), frequency, 'ja');
- node.dataset.expression = expression;
+ node.dataset.expression = term;
node.dataset.reading = reading;
node.dataset.hasReading = `${reading !== null}`;
- node.dataset.readingIsSame = `${reading === expression}`;
+ node.dataset.readingIsSame = `${reading === term}`;
node.dataset.dictionary = dictionary;
node.dataset.frequency = `${frequency}`;
node.dataset.details = dictionary;
@@ -632,10 +648,10 @@ class DisplayGenerator {
}
_createKanjiFrequency(details, dictionary) {
- const {character, frequencies} = details;
+ const {character, values} = details;
const node = this._templates.instantiate('kanji-frequency-item');
- const frequency = frequencies.join(', ');
+ const frequency = values.join(', ');
this._setTextContent(node.querySelector('.tag-label-content'), dictionary);
this._setTextContent(node.querySelector('.frequency-value'), frequency, 'ja');
@@ -707,15 +723,7 @@ class DisplayGenerator {
}
_createDictionaryTag(dictionary) {
- return {
- name: dictionary,
- category: 'dictionary',
- notes: '',
- order: 100,
- score: 0,
- dictionary,
- redundant: false
- };
+ return this._createTagData(dictionary, 'dictionary');
}
_setTextContent(node, value, language) {
@@ -751,11 +759,12 @@ class DisplayGenerator {
}
}
- _getPitchAccentCategories(pitches) {
- if (pitches.length === 0) { return null; }
+ _getPitchAccentCategories(reading, pronunciations, headwordIndex) {
+ if (pronunciations.length === 0) { return null; }
const categories = new Set();
- for (const {reading, pitches: pitches2} of pitches) {
- for (const {position} of pitches2) {
+ for (const pronunciation of pronunciations) {
+ if (pronunciation.headwordIndex !== headwordIndex) { continue; }
+ for (const {position} of pronunciation.pitches) {
const category = this._japaneseUtil.getPitchCategory(reading, position, false);
if (category !== null) {
categories.add(category);
diff --git a/ext/js/language/dictionary-data-util.js b/ext/js/language/dictionary-data-util.js
index dff9d212..f44b81c5 100644
--- a/ext/js/language/dictionary-data-util.js
+++ b/ext/js/language/dictionary-data-util.js
@@ -16,40 +16,41 @@
*/
class DictionaryDataUtil {
- static groupTermTags(definition) {
- const {expressions} = definition;
- const expressionsLength = expressions.length;
- const uniqueCheck = (expressionsLength > 1);
- const resultsMap = new Map();
+ static groupTermTags(dictionaryEntry) {
+ const {headwords} = dictionaryEntry;
+ const headwordCount = headwords.length;
+ const uniqueCheck = (headwordCount > 1);
+ const resultsIndexMap = new Map();
const results = [];
- for (let i = 0; i < expressionsLength; ++i) {
- const {termTags, expression, reading} = expressions[i];
- for (const tag of termTags) {
+ for (let i = 0; i < headwordCount; ++i) {
+ const {tags} = headwords[i];
+ for (const tag of tags) {
if (uniqueCheck) {
const {name, category, notes, dictionary} = tag;
const key = this._createMapKey([name, category, notes, dictionary]);
- const index = resultsMap.get(key);
+ const index = resultsIndexMap.get(key);
if (typeof index !== 'undefined') {
const existingItem = results[index];
- existingItem.expressions.push({index: i, expression, reading});
+ existingItem.headwordIndices.push(i);
continue;
}
- resultsMap.set(key, results.length);
+ resultsIndexMap.set(key, results.length);
}
- const item = {
- tag,
- expressions: [{index: i, expression, reading}]
- };
+ const item = {tag, headwordIndices: [i]};
results.push(item);
}
}
return results;
}
- static groupTermFrequencies(frequencies) {
+ static groupTermFrequencies(dictionaryEntry) {
+ const {headwords, frequencies} = dictionaryEntry;
+
const map1 = new Map();
- for (const {dictionary, expression, reading, hasReading, frequency} of frequencies) {
+ for (const {headwordIndex, dictionary, hasReading, frequency} of frequencies) {
+ const {term, reading} = headwords[headwordIndex];
+
let map2 = map1.get(dictionary);
if (typeof map2 === 'undefined') {
map2 = new Map();
@@ -57,14 +58,14 @@ class DictionaryDataUtil {
}
const readingKey = hasReading ? reading : null;
- const key = this._createMapKey([expression, readingKey]);
+ const key = this._createMapKey([term, readingKey]);
let frequencyData = map2.get(key);
if (typeof frequencyData === 'undefined') {
- frequencyData = {expression, reading: readingKey, frequencies: new Set()};
+ frequencyData = {term, reading: readingKey, values: new Set()};
map2.set(key, frequencyData);
}
- frequencyData.frequencies.add(frequency);
+ frequencyData.values.add(frequency);
}
return this._createFrequencyGroupsFromMap(map1);
}
@@ -80,64 +81,66 @@ class DictionaryDataUtil {
let frequencyData = map2.get(character);
if (typeof frequencyData === 'undefined') {
- frequencyData = {character, frequencies: new Set()};
+ frequencyData = {character, values: new Set()};
map2.set(character, frequencyData);
}
- frequencyData.frequencies.add(frequency);
+ frequencyData.values.add(frequency);
}
return this._createFrequencyGroupsFromMap(map1);
}
- static getPitchAccentInfos(definition) {
- if (definition.type === 'kanji') { return []; }
+ static getPitchAccentInfos(dictionaryEntry) {
+ const {headwords, pronunciations} = dictionaryEntry;
- const results = new Map();
const allExpressions = new Set();
const allReadings = new Set();
-
- for (const {expression, reading, pitches: expressionPitches} of definition.expressions) {
- allExpressions.add(expression);
+ for (const {term, reading} of headwords) {
+ allExpressions.add(term);
allReadings.add(reading);
+ }
- for (const {pitches, dictionary} of expressionPitches) {
- let dictionaryResults = results.get(dictionary);
- if (typeof dictionaryResults === 'undefined') {
- dictionaryResults = [];
- results.set(dictionary, dictionaryResults);
- }
-
- for (const {position, tags} of pitches) {
- let pitchAccentInfo = this._findExistingPitchAccentInfo(reading, position, tags, dictionaryResults);
- if (pitchAccentInfo === null) {
- pitchAccentInfo = {expressions: new Set(), reading, position, tags};
- dictionaryResults.push(pitchAccentInfo);
- }
- pitchAccentInfo.expressions.add(expression);
+ const pitchAccentInfoMap = new Map();
+ for (const {headwordIndex, dictionary, pitches} of pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ let dictionaryPitchAccentInfoList = pitchAccentInfoMap.get(dictionary);
+ if (typeof dictionaryPitchAccentInfoList === 'undefined') {
+ dictionaryPitchAccentInfoList = [];
+ pitchAccentInfoMap.set(dictionary, dictionaryPitchAccentInfoList);
+ }
+ for (const {position, tags} of pitches) {
+ let pitchAccentInfo = this._findExistingPitchAccentInfo(reading, position, tags, dictionaryPitchAccentInfoList);
+ if (pitchAccentInfo === null) {
+ pitchAccentInfo = {
+ terms: new Set(),
+ reading,
+ position,
+ tags,
+ exclusiveTerms: [],
+ exclusiveReadings: []
+ };
+ dictionaryPitchAccentInfoList.push(pitchAccentInfo);
}
+ pitchAccentInfo.terms.add(term);
}
}
const multipleReadings = (allReadings.size > 1);
- for (const dictionaryResults of results.values()) {
- for (const result of dictionaryResults) {
- const exclusiveExpressions = [];
- const exclusiveReadings = [];
- const resultExpressions = result.expressions;
- if (!this._areSetsEqual(resultExpressions, allExpressions)) {
- exclusiveExpressions.push(...this._getSetIntersection(resultExpressions, allExpressions));
+ for (const dictionaryPitchAccentInfoList of pitchAccentInfoMap.values()) {
+ for (const pitchAccentInfo of dictionaryPitchAccentInfoList) {
+ const {terms, reading, exclusiveTerms, exclusiveReadings} = pitchAccentInfo;
+ if (!this._areSetsEqual(terms, allExpressions)) {
+ exclusiveTerms.push(...this._getSetIntersection(terms, allExpressions));
}
if (multipleReadings) {
- exclusiveReadings.push(result.reading);
+ exclusiveReadings.push(reading);
}
- result.expressions = [...resultExpressions];
- result.exclusiveExpressions = exclusiveExpressions;
- result.exclusiveReadings = exclusiveReadings;
+ pitchAccentInfo.terms = [...terms];
}
}
const results2 = [];
- for (const [dictionary, pitches] of results.entries()) {
+ for (const [dictionary, pitches] of pitchAccentInfoMap.entries()) {
results2.push({dictionary, pitches});
}
return results2;
@@ -157,17 +160,34 @@ class DictionaryDataUtil {
}
}
+ static getDisambiguations(headwords, headwordIndices, allTermsSet, allReadingsSet) {
+ if (allTermsSet.size <= 1 && allReadingsSet.size <= 1) { return []; }
+
+ const terms = new Set();
+ const readings = new Set();
+ for (const headwordIndex of headwordIndices) {
+ const {term, reading} = headwords[headwordIndex];
+ terms.add(term);
+ readings.add(reading);
+ }
+
+ const disambiguations = [];
+ if (!this._areSetsEqual(terms, allTermsSet)) { disambiguations.push(...this._getSetIntersection(terms, allTermsSet)); }
+ if (!this._areSetsEqual(readings, allReadingsSet)) { disambiguations.push(...this._getSetIntersection(readings, allReadingsSet)); }
+ return disambiguations;
+ }
+
// Private
static _createFrequencyGroupsFromMap(map) {
const results = [];
for (const [dictionary, map2] of map.entries()) {
- const frequencyDataArray = [];
+ const frequencies = [];
for (const frequencyData of map2.values()) {
- frequencyData.frequencies = [...frequencyData.frequencies];
- frequencyDataArray.push(frequencyData);
+ frequencyData.values = [...frequencyData.values];
+ frequencies.push(frequencyData);
}
- results.push({dictionary, frequencyData: frequencyDataArray});
+ results.push({dictionary, frequencies});
}
return results;
}
diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js
index 151b1172..934c8e4a 100644
--- a/ext/js/language/translator.js
+++ b/ext/js/language/translator.js
@@ -22,7 +22,7 @@
*/
/**
- * Class which finds term and kanji definitions for text.
+ * Class which finds term and kanji dictionary entries for text.
*/
class Translator {
/**
@@ -59,6 +59,7 @@ class Translator {
* One of: 'group', 'merge', 'split', 'simple'
* @param text The text to find terms for.
* @param options An object using the following structure:
+ * ```
* {
* wildcard: (enum: null, 'prefix', 'suffix'),
* mainDictionary: (string),
@@ -85,22 +86,35 @@ class Translator {
* }
* ])
* }
- * @returns An array of [definitions, textLength]. The structure of each definition depends on the
- * mode parameter, see the _create?TermDefinition?() functions for structure details.
+ * ```
+ * @returns An object of the structure `{dictionaryEntries, originalTextLength}`.
*/
async findTerms(mode, text, options) {
+ const {enabledDictionaryMap} = options;
+ let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, enabledDictionaryMap, options);
+
switch (mode) {
case 'group':
- return await this._findTermsGrouped(text, options);
+ dictionaryEntries = this._groupDictionaryEntriesByHeadword(dictionaryEntries);
+ break;
case 'merge':
- return await this._findTermsMerged(text, options);
- case 'split':
- return await this._findTermsSplit(text, options);
- case 'simple':
- return await this._findTermsSimple(text, options);
- default:
- return [[], 0];
+ dictionaryEntries = await this._getRelatedDictionaryEntries(dictionaryEntries, options.mainDictionary, enabledDictionaryMap);
+ break;
}
+
+ if (dictionaryEntries.length > 1) {
+ this._sortTermDictionaryEntries(dictionaryEntries);
+ }
+
+ if (mode === 'simple') {
+ this._clearTermTags(dictionaryEntries);
+ } else {
+ await this._addTermMeta(dictionaryEntries, enabledDictionaryMap);
+ await this._expandTermTags(dictionaryEntries);
+ this._sortTermDictionaryEntryData(dictionaryEntries);
+ }
+
+ return {dictionaryEntries, originalTextLength};
}
/**
@@ -127,93 +141,28 @@ class Translator {
kanjiUnique.add(c);
}
- const databaseDefinitions = await this._database.findKanjiBulk([...kanjiUnique], enabledDictionaryMap);
- if (databaseDefinitions.length === 0) { return []; }
-
- this._sortDatabaseDefinitionsByIndex(databaseDefinitions);
-
- const definitions = [];
- for (const {character, onyomi, kunyomi, tags, glossary, stats, dictionary} of databaseDefinitions) {
- const expandedStats = await this._expandStats(stats, dictionary);
- const expandedTags = await this._expandTags(tags, dictionary);
- this._sortTags(expandedTags);
-
- const definition = this._createKanjiDefinition(character, dictionary, onyomi, kunyomi, glossary, expandedTags, expandedStats);
- definitions.push(definition);
- }
-
- await this._buildKanjiMeta(definitions, enabledDictionaryMap);
-
- return definitions;
- }
-
- // Find terms core functions
-
- async _findTermsSimple(text, options) {
- const {enabledDictionaryMap} = options;
- const [definitions, length] = await this._findTermsInternal(text, enabledDictionaryMap, options);
- this._sortDefinitions(definitions);
- return [definitions, length];
- }
-
- async _findTermsSplit(text, options) {
- const {enabledDictionaryMap} = options;
- const [definitions, length] = await this._findTermsInternal(text, enabledDictionaryMap, options);
- await this._buildTermMeta(definitions, enabledDictionaryMap);
- this._sortDefinitions(definitions);
- return [definitions, length];
- }
-
- async _findTermsGrouped(text, options) {
- const {enabledDictionaryMap} = options;
- const [definitions, length] = await this._findTermsInternal(text, enabledDictionaryMap, options);
-
- const groupedDefinitions = this._groupTerms(definitions, enabledDictionaryMap);
- await this._buildTermMeta(groupedDefinitions, enabledDictionaryMap);
- this._sortDefinitions(groupedDefinitions);
+ const databaseEntries = await this._database.findKanjiBulk([...kanjiUnique], enabledDictionaryMap);
+ if (databaseEntries.length === 0) { return []; }
- for (const definition of groupedDefinitions) {
- this._flagRedundantDefinitionTags(definition.definitions);
- }
+ this._sortDatabaseEntriesByIndex(databaseEntries);
- return [groupedDefinitions, length];
- }
+ const dictionaryEntries = [];
+ for (const {character, onyomi, kunyomi, tags, glossary, stats, dictionary} of databaseEntries) {
+ const expandedStats = await this._expandKanjiStats(stats, dictionary);
- async _findTermsMerged(text, options) {
- const {mainDictionary, enabledDictionaryMap} = options;
- const [definitions, length] = await this._findTermsInternal(text, enabledDictionaryMap, options);
- const {sequencedDefinitions, unsequencedDefinitions} = await this._getSequencedDefinitions(definitions, mainDictionary, enabledDictionaryMap);
- const definitionsMerged = [];
+ const tagGroups = [];
+ if (tags.length > 0) { tagGroups.push(this._createTagGroup(dictionary, tags)); }
- for (const {relatedDefinitions, secondaryDefinitions} of sequencedDefinitions) {
- const mergedDefinition = this._getMergedDefinition(relatedDefinitions, secondaryDefinitions);
- definitionsMerged.push(mergedDefinition);
+ const dictionaryEntry = this._createKanjiDictionaryEntry(character, dictionary, onyomi, kunyomi, tagGroups, expandedStats, glossary);
+ dictionaryEntries.push(dictionaryEntry);
}
- for (const groupedDefinition of this._groupTerms(unsequencedDefinitions, enabledDictionaryMap)) {
- const {reasons, score, expression, reading, source, rawSource, definitions: definitions2} = groupedDefinition;
- const termDetailsList = this._createTermDetailsList(definitions2);
- const compatibilityDefinition = this._createMergedTermDefinition(
- source,
- rawSource,
- this._convertTermDefinitionsToMergedGlossaryTermDefinitions(definitions2),
- [expression],
- [reading],
- termDetailsList,
- reasons,
- score
- );
- definitionsMerged.push(compatibilityDefinition);
- }
-
- await this._buildTermMeta(definitionsMerged, enabledDictionaryMap);
- this._sortDefinitions(definitionsMerged);
+ await this._addKanjiMeta(dictionaryEntries, enabledDictionaryMap);
+ await this._expandKanjiTags(dictionaryEntries);
- for (const definition of definitionsMerged) {
- this._flagRedundantDefinitionTags(definition.definitions);
- }
+ this._sortKanjiDictionaryEntryData(dictionaryEntries);
- return [definitionsMerged, length];
+ return dictionaryEntries;
}
// Find terms internal implementation
@@ -225,33 +174,33 @@ class Translator {
return [[], 0];
}
- const deinflections = (
+ const deinflections = await (
wildcard ?
- await this._findTermWildcard(text, enabledDictionaryMap, wildcard) :
- await this._findTermDeinflections(text, enabledDictionaryMap, options)
+ this._findTermsWildcard(text, enabledDictionaryMap, wildcard) :
+ this._findTermDeinflections(text, enabledDictionaryMap, options)
);
- let maxLength = 0;
- const definitions = [];
- const definitionIds = new Set();
- for (const {databaseDefinitions, source, rawSource, term, reasons} of deinflections) {
- if (databaseDefinitions.length === 0) { continue; }
- maxLength = Math.max(maxLength, rawSource.length);
- for (const databaseDefinition of databaseDefinitions) {
- const {id} = databaseDefinition;
- if (definitionIds.has(id)) { continue; }
- const definition = await this._createTermDefinitionFromDatabaseDefinition(databaseDefinition, source, rawSource, term, reasons, true, enabledDictionaryMap);
- definitions.push(definition);
- definitionIds.add(id);
+ let originalTextLength = 0;
+ const dictionaryEntries = [];
+ const ids = new Set();
+ for (const {databaseEntries, originalText, transformedText, deinflectedText, reasons} of deinflections) {
+ if (databaseEntries.length === 0) { continue; }
+ originalTextLength = Math.max(originalTextLength, originalText.length);
+ for (const databaseEntry of databaseEntries) {
+ const {id} = databaseEntry;
+ if (ids.has(id)) { continue; }
+ const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, reasons, true, enabledDictionaryMap);
+ dictionaryEntries.push(dictionaryEntry);
+ ids.add(id);
}
}
- return [definitions, maxLength];
+ return {dictionaryEntries, originalTextLength};
}
- async _findTermWildcard(text, enabledDictionaryMap, wildcard) {
- const databaseDefinitions = await this._database.findTermsBulk([text], enabledDictionaryMap, wildcard);
- return databaseDefinitions.length > 0 ? [this._createDeinflection(text, text, text, 0, [], databaseDefinitions)] : [];
+ async _findTermsWildcard(text, enabledDictionaryMap, wildcard) {
+ const databaseEntries = await this._database.findTermsBulk([text], enabledDictionaryMap, wildcard);
+ return databaseEntries.length > 0 ? [this._createDeinflection(text, text, text, 0, [], databaseEntries)] : [];
}
async _findTermDeinflections(text, enabledDictionaryMap, options) {
@@ -265,7 +214,7 @@ class Translator {
const uniqueDeinflectionArrays = [];
const uniqueDeinflectionsMap = new Map();
for (const deinflection of deinflections) {
- const term = deinflection.term;
+ const term = deinflection.deinflectedText;
let deinflectionArray = uniqueDeinflectionsMap.get(term);
if (typeof deinflectionArray === 'undefined') {
deinflectionArray = [];
@@ -276,14 +225,14 @@ class Translator {
deinflectionArray.push(deinflection);
}
- const databaseDefinitions = await this._database.findTermsBulk(uniqueDeinflectionTerms, enabledDictionaryMap, null);
+ const databaseEntries = await this._database.findTermsBulk(uniqueDeinflectionTerms, enabledDictionaryMap, null);
- for (const databaseDefinition of databaseDefinitions) {
- const definitionRules = Deinflector.rulesToRuleFlags(databaseDefinition.rules);
- for (const deinflection of uniqueDeinflectionArrays[databaseDefinition.index]) {
+ for (const databaseEntry of databaseEntries) {
+ const definitionRules = Deinflector.rulesToRuleFlags(databaseEntry.rules);
+ for (const deinflection of uniqueDeinflectionArrays[databaseEntry.index]) {
const deinflectionRules = deinflection.rules;
if (deinflectionRules === 0 || (definitionRules & deinflectionRules) !== 0) {
- deinflection.databaseDefinitions.push(databaseDefinition);
+ deinflection.databaseEntries.push(databaseEntry);
}
}
}
@@ -291,6 +240,8 @@ class Translator {
return deinflections;
}
+ // Deinflections and text transformations
+
_getAllDeinflections(text, options) {
const textOptionVariantArray = [
this._getTextReplacementsVariants(options),
@@ -336,120 +287,159 @@ class Translator {
used.add(source);
const rawSource = sourceMap.source.substring(0, sourceMap.getSourceLength(i));
for (const {term, rules, reasons} of this._deinflector.deinflect(source)) {
- deinflections.push(this._createDeinflection(source, rawSource, term, rules, reasons, []));
+ deinflections.push(this._createDeinflection(rawSource, source, term, rules, reasons, []));
}
}
}
return deinflections;
}
- _createDeinflection(source, rawSource, term, rules, reasons, databaseDefinitions) {
- return {source, rawSource, term, rules, reasons, databaseDefinitions};
+ _applyTextReplacements(text, sourceMap, replacements) {
+ for (const {pattern, replacement} of replacements) {
+ text = RegexUtil.applyTextReplacement(text, sourceMap, pattern, replacement);
+ }
+ return text;
+ }
+
+ _getSearchableText(text, allowAlphanumericCharacters) {
+ if (allowAlphanumericCharacters) { return text; }
+ const jp = this._japaneseUtil;
+ let length = 0;
+ for (const c of text) {
+ if (!jp.isCodePointJapanese(c.codePointAt(0))) { break; }
+ length += c.length;
+ }
+ return length >= text.length ? text : text.substring(0, length);
}
- /**
- * @param definitions An array of 'term' definitions.
- * @param mainDictionary The name of the main dictionary.
- * @param enabledDictionaryMap The map of enabled dictionaries and their settings.
- */
- async _getSequencedDefinitions(definitions, mainDictionary, enabledDictionaryMap) {
- const secondarySearchDictionaryMap = this._getSecondarySearchDictionaryMap(enabledDictionaryMap);
+ _getTextOptionEntryVariants(value) {
+ switch (value) {
+ case 'true': return [true];
+ case 'variant': return [false, true];
+ default: return [false];
+ }
+ }
+
+ _getCollapseEmphaticOptions(options) {
+ const collapseEmphaticOptions = [[false, false]];
+ switch (options.collapseEmphaticSequences) {
+ case 'true':
+ collapseEmphaticOptions.push([true, false]);
+ break;
+ case 'full':
+ collapseEmphaticOptions.push([true, false], [true, true]);
+ break;
+ }
+ return collapseEmphaticOptions;
+ }
+
+ _getTextReplacementsVariants(options) {
+ return options.textReplacements;
+ }
+
+ _createDeinflection(originalText, transformedText, deinflectedText, rules, reasons, databaseEntries) {
+ return {originalText, transformedText, deinflectedText, rules, reasons, databaseEntries};
+ }
+
+ // Term dictionary entry grouping
+
+ async _getRelatedDictionaryEntries(dictionaryEntries, mainDictionary, enabledDictionaryMap) {
const sequenceList = [];
- const sequencedDefinitionMap = new Map();
- const sequencedDefinitions = [];
- const unsequencedDefinitions = new Map();
- for (const definition of definitions) {
- const {sequence, dictionary, id} = definition;
+ const groupedDictionaryEntries = [];
+ const groupedDictionaryEntriesMap = new Map();
+ const ungroupedDictionaryEntriesMap = new Map();
+ for (const dictionaryEntry of dictionaryEntries) {
+ const {id, sequence, definitions: [{dictionary}]} = dictionaryEntry;
if (mainDictionary === dictionary && sequence >= 0) {
- let sequencedDefinition = sequencedDefinitionMap.get(sequence);
- if (typeof sequencedDefinition === 'undefined') {
- sequencedDefinition = {
- relatedDefinitions: [],
- definitionIds: new Set(),
- secondaryDefinitions: []
- };
- sequencedDefinitionMap.set(sequence, sequencedDefinition);
- sequencedDefinitions.push(sequencedDefinition);
- sequenceList.push(sequence);
+ let group = groupedDictionaryEntriesMap.get(sequence);
+ if (typeof group === 'undefined') {
+ group = {ids: new Set(), dictionaryEntries: []};
+ sequenceList.push({query: sequence, dictionary});
+ groupedDictionaryEntries.push(group);
+ groupedDictionaryEntriesMap.set(sequence, group);
}
- sequencedDefinition.relatedDefinitions.push(definition);
- sequencedDefinition.definitionIds.add(id);
+ group.dictionaryEntries.push(dictionaryEntry);
+ group.ids.add(id);
} else {
- unsequencedDefinitions.set(id, definition);
+ ungroupedDictionaryEntriesMap.set(id, dictionaryEntry);
}
}
if (sequenceList.length > 0) {
- await this._addRelatedDefinitions(sequencedDefinitions, unsequencedDefinitions, sequenceList, mainDictionary, enabledDictionaryMap);
- await this._addSecondaryDefinitions(sequencedDefinitions, unsequencedDefinitions, enabledDictionaryMap, secondarySearchDictionaryMap);
+ const secondarySearchDictionaryMap = this._getSecondarySearchDictionaryMap(enabledDictionaryMap);
+ await this._addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, mainDictionary, enabledDictionaryMap);
+ for (const group of groupedDictionaryEntries) {
+ this._sortTermDictionaryEntriesById(group.dictionaryEntries);
+ }
+ if (ungroupedDictionaryEntriesMap.size !== 0 || secondarySearchDictionaryMap.size !== 0) {
+ await this._addSecondaryRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap);
+ }
}
- for (const {relatedDefinitions} of sequencedDefinitions) {
- this._sortDefinitionsById(relatedDefinitions);
+ const newDictionaryEntries = [];
+ for (const group of groupedDictionaryEntries) {
+ newDictionaryEntries.push(this._createGroupedDictionaryEntry(group.dictionaryEntries, true));
}
-
- return {sequencedDefinitions, unsequencedDefinitions: [...unsequencedDefinitions.values()]};
- }
-
- async _addRelatedDefinitions(sequencedDefinitions, unsequencedDefinitions, sequenceList, mainDictionary, enabledDictionaryMap) {
- const items = sequenceList.map((query) => ({query, dictionary: mainDictionary}));
- const databaseDefinitions = await this._database.findTermsBySequenceBulk(items);
- for (const databaseDefinition of databaseDefinitions) {
- const {relatedDefinitions, definitionIds} = sequencedDefinitions[databaseDefinition.index];
- const {id} = databaseDefinition;
- if (definitionIds.has(id)) { continue; }
-
- const {source, rawSource, sourceTerm} = relatedDefinitions[0];
- const definition = await this._createTermDefinitionFromDatabaseDefinition(databaseDefinition, source, rawSource, sourceTerm, [], false, enabledDictionaryMap);
- relatedDefinitions.push(definition);
- definitionIds.add(id);
- unsequencedDefinitions.delete(id);
+ newDictionaryEntries.push(...this._groupDictionaryEntriesByHeadword(ungroupedDictionaryEntriesMap.values()));
+ return newDictionaryEntries;
+ }
+
+ async _addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, mainDictionary, enabledDictionaryMap) {
+ const databaseEntries = await this._database.findTermsBySequenceBulk(sequenceList);
+ for (const databaseEntry of databaseEntries) {
+ const {dictionaryEntries, ids} = groupedDictionaryEntries[databaseEntry.index];
+ const {id} = databaseEntry;
+ if (ids.has(id)) { continue; }
+
+ const sourceText = databaseEntry.expression;
+ const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], false, enabledDictionaryMap);
+ dictionaryEntries.push(dictionaryEntry);
+ ids.add(id);
+ ungroupedDictionaryEntriesMap.delete(id);
}
}
- async _addSecondaryDefinitions(sequencedDefinitions, unsequencedDefinitions, enabledDictionaryMap, secondarySearchDictionaryMap) {
- if (unsequencedDefinitions.length === 0 && secondarySearchDictionaryMap.size === 0) { return; }
-
+ async _addSecondaryRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap) {
// Prepare grouping info
const termList = [];
const targetList = [];
const targetMap = new Map();
- for (const sequencedDefinition of sequencedDefinitions) {
- const {relatedDefinitions} = sequencedDefinition;
- for (const definition of relatedDefinitions) {
- const {expressions: [{expression, reading}]} = definition;
- const key = this._createMapKey([expression, reading]);
+ for (const group of groupedDictionaryEntries) {
+ const {dictionaryEntries} = group;
+ for (const dictionaryEntry of dictionaryEntries) {
+ const {term, reading} = dictionaryEntry.headwords[0];
+ const key = this._createMapKey([term, reading]);
let target = targetMap.get(key);
if (typeof target === 'undefined') {
target = {
- sequencedDefinitions: [],
+ groups: [],
searchSecondary: false
};
targetMap.set(key, target);
}
- target.sequencedDefinitions.push(sequencedDefinition);
- if (!definition.isPrimary && !target.searchSecondary) {
+ target.groups.push(group);
+ if (!dictionaryEntry.isPrimary && !target.searchSecondary) {
target.searchSecondary = true;
- termList.push({expression, reading});
+ termList.push({expression: term, reading});
targetList.push(target);
}
}
}
- // Group unsequenced definitions with sequenced definitions that have a matching [expression, reading].
- for (const [id, definition] of unsequencedDefinitions.entries()) {
- const {expressions: [{expression, reading}]} = definition;
- const key = this._createMapKey([expression, reading]);
+ // Group unsequenced dictionary entries with sequenced entries that have a matching [expression, reading].
+ for (const [id, dictionaryEntry] of ungroupedDictionaryEntriesMap.entries()) {
+ const {term, reading} = dictionaryEntry.headwords[0];
+ const key = this._createMapKey([term, reading]);
const target = targetMap.get(key);
if (typeof target === 'undefined') { continue; }
- for (const {definitionIds, secondaryDefinitions} of target.sequencedDefinitions) {
- if (definitionIds.has(id)) { continue; }
+ for (const {ids, dictionaryEntries} of target.groups) {
+ if (ids.has(id)) { continue; }
- secondaryDefinitions.push(definition);
- definitionIds.add(id);
- unsequencedDefinitions.delete(id);
+ dictionaryEntries.push(dictionaryEntry);
+ ids.add(id);
+ ungroupedDictionaryEntriesMap.delete(id);
break;
}
}
@@ -457,102 +447,200 @@ class Translator {
// Search database for additional secondary terms
if (termList.length === 0 || secondarySearchDictionaryMap.size === 0) { return; }
- const databaseDefinitions = await this._database.findTermsExactBulk(termList, secondarySearchDictionaryMap);
- this._sortDatabaseDefinitionsByIndex(databaseDefinitions);
+ const databaseEntries = await this._database.findTermsExactBulk(termList, secondarySearchDictionaryMap);
+ this._sortDatabaseEntriesByIndex(databaseEntries);
- for (const databaseDefinition of databaseDefinitions) {
- const {index, id} = databaseDefinition;
- const source = termList[index].expression;
+ for (const databaseEntry of databaseEntries) {
+ const {index, id} = databaseEntry;
+ const sourceText = termList[index].expression;
const target = targetList[index];
- for (const {definitionIds, secondaryDefinitions} of target.sequencedDefinitions) {
- if (definitionIds.has(id)) { continue; }
+ for (const {ids, dictionaryEntries} of target.groups) {
+ if (ids.has(id)) { continue; }
- const definition = await this._createTermDefinitionFromDatabaseDefinition(databaseDefinition, source, source, source, [], false, enabledDictionaryMap);
- secondaryDefinitions.push(definition);
- definitionIds.add(id);
- unsequencedDefinitions.delete(id);
+ const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], false, enabledDictionaryMap);
+ dictionaryEntries.push(dictionaryEntry);
+ ids.add(id);
+ ungroupedDictionaryEntriesMap.delete(id);
}
}
}
- _getMergedDefinition(relatedDefinitions, secondaryDefinitions) {
- const {reasons, source, rawSource} = relatedDefinitions[0];
- const allDefinitions = secondaryDefinitions.length > 0 ? [...relatedDefinitions, ...secondaryDefinitions] : relatedDefinitions;
- const score = this._getMaxPrimaryDefinitionScore(allDefinitions);
+ _groupDictionaryEntriesByHeadword(dictionaryEntries) {
+ const groups = new Map();
+ for (const dictionaryEntry of dictionaryEntries) {
+ const {inflections, headwords: [{term, reading}]} = dictionaryEntry;
+ const key = this._createMapKey([term, reading, ...inflections]);
+ let dictionaryEntries2 = groups.get(key);
+ if (typeof dictionaryEntries2 === 'undefined') {
+ dictionaryEntries2 = [];
+ groups.set(key, dictionaryEntries2);
+ }
+ dictionaryEntries2.push(dictionaryEntry);
+ }
- // Merge by glossary
- const allExpressions = new Set();
- const allReadings = new Set();
- const glossaryDefinitionGroupMap = new Map();
- for (const definition of allDefinitions) {
- const {dictionary, glossary, expressions: [{expression, reading}]} = definition;
+ const results = [];
+ for (const dictionaryEntries2 of groups.values()) {
+ const dictionaryEntry = this._createGroupedDictionaryEntry(dictionaryEntries2, false);
+ results.push(dictionaryEntry);
+ }
+ return results;
+ }
- const key = this._createMapKey([dictionary, ...glossary]);
- let group = glossaryDefinitionGroupMap.get(key);
- if (typeof group === 'undefined') {
- group = {
- expressions: new Set(),
- readings: new Set(),
- definitions: []
- };
- glossaryDefinitionGroupMap.set(key, group);
+ // Tags
+
+ _getTermTagTargets(dictionaryEntries) {
+ const tagTargets = [];
+ for (const {headwords, definitions, pronunciations} of dictionaryEntries) {
+ this._addTagExpansionTargets(tagTargets, headwords);
+ this._addTagExpansionTargets(tagTargets, definitions);
+ for (const {pitches} of pronunciations) {
+ this._addTagExpansionTargets(tagTargets, pitches);
}
+ }
+ return tagTargets;
+ }
+
+ _clearTermTags(dictionaryEntries) {
+ this._getTermTagTargets(dictionaryEntries);
+ }
+
+ async _expandTermTags(dictionaryEntries) {
+ const tagTargets = this._getTermTagTargets(dictionaryEntries);
+ await this._expandTagGroups(tagTargets);
+ this._groupTags(tagTargets);
+ }
- allExpressions.add(expression);
- allReadings.add(reading);
- group.expressions.add(expression);
- group.readings.add(reading);
- group.definitions.push(definition);
+ async _expandKanjiTags(dictionaryEntries) {
+ const tagTargets = [];
+ this._addTagExpansionTargets(tagTargets, dictionaryEntries);
+ await this._expandTagGroups(tagTargets);
+ this._groupTags(tagTargets);
+ }
+
+ async _expandTagGroups(tagTargets) {
+ const allItems = [];
+ const targetMap = new Map();
+ for (const {tagGroups, tags} of tagTargets) {
+ for (const {dictionary, tagNames} of tagGroups) {
+ let dictionaryItems = targetMap.get(dictionary);
+ if (typeof dictionaryItems === 'undefined') {
+ dictionaryItems = new Map();
+ targetMap.set(dictionary, dictionaryItems);
+ }
+ for (const tagName of tagNames) {
+ let item = dictionaryItems.get(tagName);
+ if (typeof item === 'undefined') {
+ const query = this._getNameBase(tagName);
+ item = {query, dictionary, tagName, cache: null, databaseTag: null, targets: []};
+ dictionaryItems.set(tagName, item);
+ allItems.push(item);
+ }
+ item.targets.push(tags);
+ }
+ }
}
- const glossaryDefinitions = [];
- for (const {expressions, readings, definitions} of glossaryDefinitionGroupMap.values()) {
- const glossaryDefinition = this._createMergedGlossaryTermDefinition(
- source,
- rawSource,
- definitions,
- expressions,
- readings,
- allExpressions,
- allReadings
- );
- glossaryDefinitions.push(glossaryDefinition);
+ const nonCachedItems = [];
+ const tagCache = this._tagCache;
+ for (const [dictionary, dictionaryItems] of targetMap.entries()) {
+ let cache = tagCache.get(dictionary);
+ if (typeof cache === 'undefined') {
+ cache = new Map();
+ tagCache.set(dictionary, cache);
+ }
+ for (const item of dictionaryItems.values()) {
+ const databaseTag = cache.get(item.query);
+ if (typeof databaseTag !== 'undefined') {
+ item.databaseTag = databaseTag;
+ } else {
+ item.cache = cache;
+ nonCachedItems.push(item);
+ }
+ }
}
- this._sortDefinitions(glossaryDefinitions, false);
- const termDetailsList = this._createTermDetailsList(allDefinitions);
+ const nonCachedItemCount = nonCachedItems.length;
+ if (nonCachedItemCount > 0) {
+ const databaseTags = await this._database.findTagMetaBulk(nonCachedItems);
+ for (let i = 0; i < nonCachedItemCount; ++i) {
+ const item = nonCachedItems[i];
+ let databaseTag = databaseTags[i];
+ if (typeof databaseTag === 'undefined') { databaseTag = null; }
+ item.databaseTag = databaseTag;
+ item.cache.set(item.query, databaseTag);
+ }
+ }
- return this._createMergedTermDefinition(
- source,
- rawSource,
- glossaryDefinitions,
- [...allExpressions],
- [...allReadings],
- termDetailsList,
- reasons,
- score
- );
+ for (const {dictionary, tagName, databaseTag, targets} of allItems) {
+ for (const tags of targets) {
+ tags.push(this._createTag(databaseTag, tagName, dictionary));
+ }
+ }
}
- _getUniqueDefinitionTags(definitions) {
- const definitionTagsMap = new Map();
- for (const {definitionTags} of definitions) {
- for (const tag of definitionTags) {
- const {name} = tag;
- if (definitionTagsMap.has(name)) { continue; }
- definitionTagsMap.set(name, this._cloneTag(tag));
+ _groupTags(tagTargets) {
+ const stringComparer = this._stringComparer;
+ const compare = (v1, v2) => {
+ const i = v1.order - v2.order;
+ return i !== 0 ? i : stringComparer.compare(v1.name, v2.name);
+ };
+
+ for (const {tags} of tagTargets) {
+ if (tags.length <= 1) { continue; }
+ this._mergeSimilarTags(tags);
+ tags.sort(compare);
+ }
+ }
+
+ _addTagExpansionTargets(tagTargets, objects) {
+ for (const value of objects) {
+ const tagGroups = value.tags;
+ if (tagGroups.length === 0) { continue; }
+ const tags = [];
+ value.tags = tags;
+ tagTargets.push({tagGroups, tags});
+ }
+ }
+
+ _mergeSimilarTags(tags) {
+ let tagCount = tags.length;
+ for (let i = 0; i < tagCount; ++i) {
+ const tag1 = tags[i];
+ const {category, name} = tag1;
+ for (let j = i + 1; j < tagCount; ++j) {
+ const tag2 = tags[j];
+ if (tag2.name !== name || tag2.category !== category) { continue; }
+ // Merge tag
+ tag1.order = Math.min(tag1.order, tag2.order);
+ tag1.score = Math.max(tag1.score, tag2.score);
+ tag1.dictionaries.push(...tag2.dictionaries);
+ this._addUniqueStrings(tag1.content, tag2.content);
+ tags.splice(j, 1);
+ --tagCount;
+ --j;
}
}
- return [...definitionTagsMap.values()];
+ }
+
+ _getTagNamesWithCategory(tags, category) {
+ const results = [];
+ for (const tag of tags) {
+ if (tag.category !== category) { continue; }
+ results.push(tag.name);
+ }
+ results.sort();
+ return results;
}
_flagRedundantDefinitionTags(definitions) {
+ if (definitions.length === 0) { return; }
+
let lastDictionary = null;
let lastPartOfSpeech = '';
const removeCategoriesSet = new Set();
- for (const {dictionary, definitionTags} of definitions) {
- const partOfSpeech = this._createMapKey(this._getTagNamesWithCategory(definitionTags, 'partOfSpeech'));
+ for (const {dictionary, tags} of definitions) {
+ const partOfSpeech = this._createMapKey(this._getTagNamesWithCategory(tags, 'partOfSpeech'));
if (lastDictionary !== dictionary) {
lastDictionary = dictionary;
@@ -566,87 +654,46 @@ class Translator {
}
if (removeCategoriesSet.size > 0) {
- this._flagTagsWithCategoryAsRedundant(definitionTags, removeCategoriesSet);
+ for (const tag of tags) {
+ if (removeCategoriesSet.has(tag.category)) {
+ tag.redundant = true;
+ }
+ }
removeCategoriesSet.clear();
}
}
}
- /**
- * Groups definitions with the same [source, expression, reading, reasons].
- * @param definitions An array of 'term' definitions.
- * @returns An array of 'termGrouped' definitions.
- */
- _groupTerms(definitions) {
- const groups = new Map();
- for (const definition of definitions) {
- const {source, reasons, expressions: [{expression, reading}]} = definition;
- const key = this._createMapKey([source, expression, reading, ...reasons]);
- let groupDefinitions = groups.get(key);
- if (typeof groupDefinitions === 'undefined') {
- groupDefinitions = [];
- groups.set(key, groupDefinitions);
- }
-
- groupDefinitions.push(definition);
- }
-
- const results = [];
- for (const groupDefinitions of groups.values()) {
- this._sortDefinitions(groupDefinitions, false);
- const definition = this._createGroupedTermDefinition(groupDefinitions);
- results.push(definition);
- }
-
- return results;
- }
-
- _convertTermDefinitionsToMergedGlossaryTermDefinitions(definitions) {
- const convertedDefinitions = [];
- for (const definition of definitions) {
- const {source, rawSource, expression, reading} = definition;
- const expressions = new Set([expression]);
- const readings = new Set([reading]);
- const convertedDefinition = this._createMergedGlossaryTermDefinition(source, rawSource, [definition], expressions, readings, expressions, readings);
- convertedDefinitions.push(convertedDefinition);
- }
- return convertedDefinitions;
- }
-
- // Metadata building
+ // Metadata
- async _buildTermMeta(definitions, enabledDictionaryMap) {
- const allDefinitions = this._getAllDefinitions(definitions);
- const expressionMap = new Map();
- const expressionValues = [];
- const expressionKeys = [];
+ async _addTermMeta(dictionaryEntries, enabledDictionaryMap) {
+ const headwordMap = new Map();
+ const headwordMapKeys = [];
+ const headwordReadingMaps = [];
- for (const {expressions, frequencies: frequencies1, pitches: pitches1} of allDefinitions) {
- for (let i = 0, ii = expressions.length; i < ii; ++i) {
- const {expression, reading, frequencies: frequencies2, pitches: pitches2} = expressions[i];
- let readingMap = expressionMap.get(expression);
+ for (const {headwords, pronunciations, frequencies} of dictionaryEntries) {
+ for (let i = 0, ii = headwords.length; i < ii; ++i) {
+ const {term, reading} = headwords[i];
+ let readingMap = headwordMap.get(term);
if (typeof readingMap === 'undefined') {
readingMap = new Map();
- expressionMap.set(expression, readingMap);
- expressionValues.push(readingMap);
- expressionKeys.push(expression);
+ headwordMap.set(term, readingMap);
+ headwordMapKeys.push(term);
+ headwordReadingMaps.push(readingMap);
}
let targets = readingMap.get(reading);
if (typeof targets === 'undefined') {
targets = [];
readingMap.set(reading, targets);
}
- targets.push(
- {frequencies: frequencies1, pitches: pitches1, index: i},
- {frequencies: frequencies2, pitches: pitches2, index: i}
- );
+ targets.push({headwordIndex: i, pronunciations, frequencies});
}
}
- const metas = await this._database.findTermMetaBulk(expressionKeys, enabledDictionaryMap);
- for (const {expression, mode, data, dictionary, index} of metas) {
- const dictionaryOrder = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
- const map2 = expressionValues[index];
+ const metas = await this._database.findTermMetaBulk(headwordMapKeys, enabledDictionaryMap);
+ for (const {mode, data, dictionary, index} of metas) {
+ const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
+ const map2 = headwordReadingMaps[index];
for (const [reading, targets] of map2.entries()) {
switch (mode) {
case 'freq':
@@ -657,171 +704,124 @@ class Translator {
if (data.reading !== reading) { continue; }
frequency = data.frequency;
}
- for (const {frequencies, index: expressionIndex} of targets) {
- frequencies.push({index: frequencies.length, expressionIndex, dictionary, dictionaryOrder, expression, reading, hasReading, frequency});
+ for (const {frequencies, headwordIndex} of targets) {
+ frequencies.push(this._createTermFrequency(
+ frequencies.length,
+ headwordIndex,
+ dictionary,
+ dictionaryIndex,
+ dictionaryPriority,
+ hasReading,
+ frequency
+ ));
}
}
break;
case 'pitch':
{
if (data.reading !== reading) { continue; }
- const pitches2 = [];
- for (let {position, tags} of data.pitches) {
- tags = Array.isArray(tags) ? await this._expandTags(tags, dictionary) : [];
- pitches2.push({position, tags});
+ const pitches = [];
+ for (const {position, tags} of data.pitches) {
+ const tags2 = [];
+ if (Array.isArray(tags) && tags.length > 0) {
+ tags2.push(this._createTagGroup(dictionary, tags));
+ }
+ pitches.push({position, tags: tags2});
}
- for (const {pitches, index: expressionIndex} of targets) {
- pitches.push({index: pitches.length, expressionIndex, dictionary, dictionaryOrder, expression, reading, pitches: pitches2});
+ for (const {pronunciations, headwordIndex} of targets) {
+ pronunciations.push(this._createTermPronunciation(
+ pronunciations.length,
+ headwordIndex,
+ dictionary,
+ dictionaryIndex,
+ dictionaryPriority,
+ pitches
+ ));
}
}
break;
}
}
}
-
- for (const definition of allDefinitions) {
- this._sortTermDefinitionMeta(definition);
- }
}
- async _buildKanjiMeta(definitions, enabledDictionaryMap) {
+ async _addKanjiMeta(dictionaryEntries, enabledDictionaryMap) {
const kanjiList = [];
- for (const {character} of definitions) {
+ for (const {character} of dictionaryEntries) {
kanjiList.push(character);
}
const metas = await this._database.findKanjiMetaBulk(kanjiList, enabledDictionaryMap);
for (const {character, mode, data, dictionary, index} of metas) {
- const dictionaryOrder = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
+ const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
switch (mode) {
case 'freq':
{
- const {frequencies} = definitions[index];
- frequencies.push({index: frequencies.length, dictionary, dictionaryOrder, character, frequency: data});
+ const {frequencies} = dictionaryEntries[index];
+ frequencies.push(this._createKanjiFrequency(
+ frequencies.length,
+ dictionary,
+ dictionaryIndex,
+ dictionaryPriority,
+ character,
+ data
+ ));
}
break;
}
}
-
- for (const definition of definitions) {
- this._sortKanjiDefinitionMeta(definition);
- }
}
- async _expandTags(names, dictionary) {
- const tagMetaList = await this._getTagMetaList(names, dictionary);
- const results = [];
- for (let i = 0, ii = tagMetaList.length; i < ii; ++i) {
- const meta = tagMetaList[i];
- const name = names[i];
- const {category, notes, order, score} = (meta !== null ? meta : {});
- const tag = this._createTag(name, category, notes, order, score, dictionary, false);
- results.push(tag);
+ async _expandKanjiStats(stats, dictionary) {
+ const statsEntries = Object.entries(stats);
+ const items = [];
+ for (const [name] of statsEntries) {
+ const query = this._getNameBase(name);
+ items.push({query, dictionary});
}
- return results;
- }
- async _expandStats(items, dictionary) {
- const names = Object.keys(items);
- const tagMetaList = await this._getTagMetaList(names, dictionary);
+ const databaseInfos = await this._database.findTagMetaBulk(items);
const statsGroups = new Map();
- for (let i = 0; i < names.length; ++i) {
- const name = names[i];
- const meta = tagMetaList[i];
- if (meta === null) { continue; }
+ for (let i = 0, ii = statsEntries.length; i < ii; ++i) {
+ const databaseInfo = databaseInfos[i];
+ if (databaseInfo === null) { continue; }
- const {category, notes, order, score} = meta;
+ const [name, value] = statsEntries[i];
+ const {category} = databaseInfo;
let group = statsGroups.get(category);
if (typeof group === 'undefined') {
group = [];
statsGroups.set(category, group);
}
- const value = items[name];
- const stat = this._createKanjiStat(name, category, notes, order, score, dictionary, value);
- group.push(stat);
+ group.push(this._createKanjiStat(name, value, databaseInfo, dictionary));
}
- const stats = {};
+ const groupedStats = {};
for (const [category, group] of statsGroups.entries()) {
this._sortKanjiStats(group);
- stats[category] = group;
+ groupedStats[category] = group;
}
- return stats;
+ return groupedStats;
}
- async _getTagMetaList(names, dictionary) {
- const tagMetaList = [];
- let cache = this._tagCache.get(dictionary);
- if (typeof cache === 'undefined') {
- cache = new Map();
- this._tagCache.set(dictionary, cache);
- }
-
- for (const name of names) {
- const base = this._getNameBase(name);
-
- let tagMeta = cache.get(base);
- if (typeof tagMeta === 'undefined') {
- tagMeta = await this._database.findTagForTitle(base, dictionary);
- cache.set(base, tagMeta);
- }
-
- tagMetaList.push(tagMeta);
- }
-
- return tagMetaList;
+ _sortKanjiStats(stats) {
+ if (stats.length <= 1) { return; }
+ const stringComparer = this._stringComparer;
+ stats.sort((v1, v2) => {
+ const i = v1.order - v2.order;
+ return (i !== 0) ? i : stringComparer.compare(v1.content, v2.content);
+ });
}
- // Simple helpers
+ // Helpers
_getNameBase(name) {
const pos = name.indexOf(':');
return (pos >= 0 ? name.substring(0, pos) : name);
}
- _getSearchableText(text, allowAlphanumericCharacters) {
- if (allowAlphanumericCharacters) {
- return text;
- }
-
- const jp = this._japaneseUtil;
- let newText = '';
- for (const c of text) {
- if (!jp.isCodePointJapanese(c.codePointAt(0))) {
- break;
- }
- newText += c;
- }
- return newText;
- }
-
- _getTextOptionEntryVariants(value) {
- switch (value) {
- case 'true': return [true];
- case 'variant': return [false, true];
- default: return [false];
- }
- }
-
- _getCollapseEmphaticOptions(options) {
- const collapseEmphaticOptions = [[false, false]];
- switch (options.collapseEmphaticSequences) {
- case 'true':
- collapseEmphaticOptions.push([true, false]);
- break;
- case 'full':
- collapseEmphaticOptions.push([true, false], [true, true]);
- break;
- }
- return collapseEmphaticOptions;
- }
-
- _getTextReplacementsVariants(options) {
- return options.textReplacements;
- }
-
_getSecondarySearchDictionaryMap(enabledDictionaryMap) {
const secondarySearchDictionaryMap = new Map();
for (const [dictionary, details] of enabledDictionaryMap.entries()) {
@@ -837,58 +837,6 @@ class Translator {
return {index, priority};
}
- _getTagNamesWithCategory(tags, category) {
- const results = [];
- for (const tag of tags) {
- if (tag.category !== category) { continue; }
- results.push(tag.name);
- }
- results.sort();
- return results;
- }
-
- _flagTagsWithCategoryAsRedundant(tags, removeCategoriesSet) {
- for (const tag of tags) {
- if (removeCategoriesSet.has(tag.category)) {
- tag.redundant = true;
- }
- }
- }
-
- _getUniqueDictionaryNames(definitions) {
- const uniqueDictionaryNames = new Set();
- for (const {dictionaryNames} of definitions) {
- for (const dictionaryName of dictionaryNames) {
- uniqueDictionaryNames.add(dictionaryName);
- }
- }
- return [...uniqueDictionaryNames];
- }
-
- _getUniqueTermTags(definitions) {
- const newTermTags = [];
- if (definitions.length <= 1) {
- for (const {termTags} of definitions) {
- for (const tag of termTags) {
- newTermTags.push(this._cloneTag(tag));
- }
- }
- } else {
- const tagsSet = new Set();
- let checkTagsMap = false;
- for (const {termTags} of definitions) {
- for (const tag of termTags) {
- const key = this._getTagMapKey(tag);
- if (checkTagsMap && tagsSet.has(key)) { continue; }
- tagsSet.add(key);
- newTermTags.push(this._cloneTag(tag));
- }
- checkTagsMap = true;
- }
- }
- return newTermTags;
- }
-
*_getArrayVariants(arrayVariants) {
const ii = arrayVariants.length;
@@ -909,110 +857,18 @@ class Translator {
}
}
- _areSetsEqual(set1, set2) {
- if (set1.size !== set2.size) {
- return false;
- }
-
- for (const value of set1) {
- if (!set2.has(value)) {
- return false;
- }
- }
-
- return true;
- }
-
- _getSetIntersection(set1, set2) {
- const result = [];
- for (const value of set1) {
- if (set2.has(value)) {
- result.push(value);
- }
- }
- return result;
- }
-
- _getAllDefinitions(definitions) {
- definitions = [...definitions];
- for (let i = 0; i < definitions.length; ++i) {
- const childDefinitions = definitions[i].definitions;
- if (Array.isArray(childDefinitions)) {
- definitions.push(...childDefinitions);
- }
- }
- return definitions;
- }
-
- // Reduction functions
-
- _getSourceTermMatchCountSum(definitions) {
- let result = 0;
- for (const {sourceTermExactMatchCount} of definitions) {
- result += sourceTermExactMatchCount;
- }
- return result;
- }
-
- _getMaxDefinitionScore(definitions) {
- let result = Number.MIN_SAFE_INTEGER;
- for (const {score} of definitions) {
- if (score > result) { result = score; }
- }
- return result;
- }
-
- _getMaxPrimaryDefinitionScore(definitions) {
- let result = Number.MIN_SAFE_INTEGER;
- for (const {isPrimary, score} of definitions) {
- if (isPrimary && score > result) { result = score; }
- }
- return result;
- }
-
- _getBestDictionaryOrder(definitions) {
- let index = Number.MAX_SAFE_INTEGER;
- let priority = Number.MIN_SAFE_INTEGER;
- for (const {dictionaryOrder: {index: index2, priority: priority2}} of definitions) {
- if (index2 < index) { index = index2; }
- if (priority2 > priority) { priority = priority2; }
- }
- return {index, priority};
- }
-
- // Common data creation and cloning functions
-
- _cloneTag(tag) {
- const {name, category, notes, order, score, dictionary, redundant} = tag;
- return this._createTag(name, category, notes, order, score, dictionary, redundant);
- }
-
- _getTagMapKey(tag) {
- const {name, category, notes} = tag;
- return this._createMapKey([name, category, notes]);
- }
-
_createMapKey(array) {
return JSON.stringify(array);
}
- _createTag(name, category, notes, order, score, dictionary, redundant) {
- return {
- name,
- category: (typeof category === 'string' && category.length > 0 ? category : 'default'),
- notes: (typeof notes === 'string' ? notes : ''),
- order: (typeof order === 'number' ? order : 0),
- score: (typeof score === 'number' ? score : 0),
- dictionary: (typeof dictionary === 'string' ? dictionary : null),
- redundant
- };
- }
+ // Kanji data
- _createKanjiStat(name, category, notes, order, score, dictionary, value) {
+ _createKanjiStat(name, value, databaseInfo, dictionary) {
+ const {category, notes, order, score} = databaseInfo;
return {
name,
category: (typeof category === 'string' && category.length > 0 ? category : 'default'),
- notes: (typeof notes === 'string' ? notes : ''),
+ content: (typeof notes === 'string' ? notes : ''),
order: (typeof order === 'number' ? order : 0),
score: (typeof score === 'number' ? score : 0),
dictionary: (typeof dictionary === 'string' ? dictionary : null),
@@ -1020,322 +876,404 @@ class Translator {
};
}
- _createKanjiDefinition(character, dictionary, onyomi, kunyomi, glossary, tags, stats) {
+ _createKanjiFrequency(index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency) {
+ return {index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency};
+ }
+
+ _createKanjiDictionaryEntry(character, dictionary, onyomi, kunyomi, tags, stats, definitions) {
return {
type: 'kanji',
character,
dictionary,
onyomi,
kunyomi,
- glossary,
tags,
stats,
+ definitions,
frequencies: []
};
}
- async _createTermDefinitionFromDatabaseDefinition(databaseDefinition, source, rawSource, sourceTerm, reasons, isPrimary, enabledDictionaryMap) {
- const {expression, reading: rawReading, definitionTags, termTags, glossary, score, dictionary, id, sequence} = databaseDefinition;
- const reading = (rawReading.length > 0 ? rawReading : expression);
- const dictionaryOrder = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
- const termTagsExpanded = await this._expandTags(termTags, dictionary);
- const definitionTagsExpanded = await this._expandTags(definitionTags, dictionary);
+ // Term data
- this._sortTags(definitionTagsExpanded);
- this._sortTags(termTagsExpanded);
+ _createTag(databaseTag, name, dictionary) {
+ const {category, notes, order, score} = (databaseTag !== null ? databaseTag : {});
+ return {
+ name,
+ category: (typeof category === 'string' && category.length > 0 ? category : 'default'),
+ order: (typeof order === 'number' ? order : 0),
+ score: (typeof score === 'number' ? score : 0),
+ content: (typeof notes === 'string' && notes.length > 0 ? [notes] : []),
+ dictionaries: [dictionary],
+ redundant: false
+ };
+ }
- const termDetailsList = [this._createTermDetails(sourceTerm, expression, reading, termTagsExpanded)];
- const sourceTermExactMatchCount = (sourceTerm === expression ? 1 : 0);
+ _createTagGroup(dictionary, tagNames) {
+ return {dictionary, tagNames};
+ }
+
+ _createSource(originalText, transformedText, deinflectedText, isPrimary) {
+ return {originalText, transformedText, deinflectedText, isPrimary};
+ }
+
+ _createTermHeadword(index, term, reading, sources, tags) {
+ return {index, term, reading, sources, tags};
+ }
+
+ _createTermDefinition(index, headwordIndices, dictionary, tags, entries) {
+ return {index, headwordIndices, dictionary, tags, entries};
+ }
+ _createTermPronunciation(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches) {
+ return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches};
+ }
+
+ _createTermFrequency(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency) {
+ return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency};
+ }
+
+ _createTermDictionaryEntry(id, isPrimary, sequence, inflections, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, maxDeinflectedTextLength, headwords, definitions) {
return {
type: 'term',
id,
- source,
- rawSource,
- sourceTerm,
- reasons,
- score,
isPrimary,
sequence,
- dictionary,
- dictionaryOrder,
- dictionaryNames: [dictionary],
- expression,
- reading,
- expressions: termDetailsList,
- glossary,
- definitionTags: definitionTagsExpanded,
- termTags: termTagsExpanded,
- // definitions
- frequencies: [],
- pitches: [],
- // only
- sourceTermExactMatchCount
- };
- }
-
- /**
- * Creates a grouped definition from an array of 'term' definitions.
- * @param definitions An array of 'term' definitions.
- * @returns A single 'termGrouped' definition.
- */
- _createGroupedTermDefinition(definitions) {
- const {reasons, source, rawSource, sourceTerm, expressions: [{expression, reading}]} = definitions[0];
- const score = this._getMaxDefinitionScore(definitions);
- const dictionaryOrder = this._getBestDictionaryOrder(definitions);
- const dictionaryNames = this._getUniqueDictionaryNames(definitions);
- const termTags = this._getUniqueTermTags(definitions);
- const termDetailsList = [this._createTermDetails(sourceTerm, expression, reading, termTags)];
- const sourceTermExactMatchCount = (sourceTerm === expression ? 1 : 0);
- return {
- type: 'termGrouped',
- // id
- source,
- rawSource,
- sourceTerm,
- reasons: [...reasons],
+ inflections,
score,
- // isPrimary
- // sequence
- dictionary: dictionaryNames[0],
- dictionaryOrder,
- dictionaryNames,
- expression,
- reading,
- expressions: termDetailsList,
- // glossary
- // definitionTags
- termTags,
- definitions, // type: 'term'
- frequencies: [],
- pitches: [],
- // only
- sourceTermExactMatchCount
+ dictionaryIndex,
+ dictionaryPriority,
+ sourceTermExactMatchCount,
+ maxDeinflectedTextLength,
+ headwords,
+ definitions,
+ pronunciations: [],
+ frequencies: []
};
}
- _createMergedTermDefinition(source, rawSource, definitions, expressions, readings, termDetailsList, reasons, score) {
- const dictionaryOrder = this._getBestDictionaryOrder(definitions);
- const sourceTermExactMatchCount = this._getSourceTermMatchCountSum(definitions);
- const dictionaryNames = this._getUniqueDictionaryNames(definitions);
- return {
- type: 'termMerged',
- // id
- source,
- rawSource,
- // sourceTerm
+ _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, reasons, isPrimary, enabledDictionaryMap) {
+ const {expression, reading: rawReading, definitionTags, termTags, glossary, score, dictionary, id, sequence} = databaseEntry;
+ const reading = (rawReading.length > 0 ? rawReading : expression);
+ const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap);
+ const sourceTermExactMatchCount = (isPrimary && deinflectedText === expression ? 1 : 0);
+ const source = this._createSource(originalText, transformedText, deinflectedText, isPrimary);
+ const maxDeinflectedTextLength = deinflectedText.length;
+
+ const headwordTagGroups = [];
+ const definitionTagGroups = [];
+ if (termTags.length > 0) { headwordTagGroups.push(this._createTagGroup(dictionary, termTags)); }
+ if (definitionTags.length > 0) { definitionTagGroups.push(this._createTagGroup(dictionary, definitionTags)); }
+
+ return this._createTermDictionaryEntry(
+ id,
+ isPrimary,
+ sequence,
reasons,
score,
- // isPrimary
- // sequence
- dictionary: dictionaryNames[0],
- dictionaryOrder,
- dictionaryNames,
- expression: expressions,
- reading: readings,
- expressions: termDetailsList,
- // glossary
- // definitionTags
- // termTags
- definitions, // type: 'termMergedByGlossary'
- frequencies: [],
- pitches: [],
- // only
- sourceTermExactMatchCount
- };
+ dictionaryIndex,
+ dictionaryPriority,
+ sourceTermExactMatchCount,
+ maxDeinflectedTextLength,
+ [this._createTermHeadword(0, expression, reading, [source], headwordTagGroups)],
+ [this._createTermDefinition(0, [0], dictionary, definitionTagGroups, glossary)]
+ );
}
- _createMergedGlossaryTermDefinition(source, rawSource, definitions, expressions, readings, allExpressions, allReadings) {
- const only = [];
- if (!this._areSetsEqual(expressions, allExpressions)) {
- only.push(...this._getSetIntersection(expressions, allExpressions));
+ _createGroupedDictionaryEntry(dictionaryEntries, checkDuplicateDefinitions) {
+ // Headwords are generated before sorting, so that the order of dictionaryEntries can be maintained
+ const definitionEntries = [];
+ const headwords = new Map();
+ for (const dictionaryEntry of dictionaryEntries) {
+ const headwordIndexMap = this._addTermHeadwords(headwords, dictionaryEntry.headwords);
+ definitionEntries.push({index: definitionEntries.length, dictionaryEntry, headwordIndexMap});
}
- if (!this._areSetsEqual(readings, allReadings)) {
- only.push(...this._getSetIntersection(readings, allReadings));
+
+ // Sort
+ if (definitionEntries.length > 1) {
+ this._sortTermDefinitionEntries(definitionEntries);
+ } else {
+ checkDuplicateDefinitions = false;
}
- const sourceTermExactMatchCount = this._getSourceTermMatchCountSum(definitions);
- const dictionaryNames = this._getUniqueDictionaryNames(definitions);
+ // Merge dictionary entry data
+ let score = Number.MIN_SAFE_INTEGER;
+ let dictionaryIndex = Number.MAX_SAFE_INTEGER;
+ let dictionaryPriority = Number.MIN_SAFE_INTEGER;
+ let maxDeinflectedTextLength = 0;
+ let sourceTermExactMatchCount = 0;
+ let isPrimary = false;
+ const definitions = [];
+ const definitionsMap = checkDuplicateDefinitions ? new Map() : null;
+ let inflections = null;
+
+ for (const {dictionaryEntry, headwordIndexMap} of definitionEntries) {
+ score = Math.max(score, dictionaryEntry.score);
+ dictionaryIndex = Math.min(dictionaryIndex, dictionaryEntry.dictionaryIndex);
+ dictionaryPriority = Math.max(dictionaryPriority, dictionaryEntry.dictionaryPriority);
+ if (dictionaryEntry.isPrimary) {
+ isPrimary = true;
+ maxDeinflectedTextLength = Math.max(maxDeinflectedTextLength, dictionaryEntry.maxDeinflectedTextLength);
+ sourceTermExactMatchCount += dictionaryEntry.sourceTermExactMatchCount;
+ const dictionaryEntryInflections = dictionaryEntry.inflections;
+ if (inflections === null || dictionaryEntryInflections.length < inflections.length) {
+ inflections = dictionaryEntryInflections;
+ }
+ }
+ if (checkDuplicateDefinitions) {
+ this._addTermDefinitions2(definitions, definitionsMap, dictionaryEntry.definitions, headwordIndexMap);
+ } else {
+ this._addTermDefinitions(definitions, dictionaryEntry.definitions, headwordIndexMap);
+ }
+ }
- const termDetailsList = this._createTermDetailsList(definitions);
+ return this._createTermDictionaryEntry(
+ -1,
+ isPrimary,
+ -1,
+ inflections !== null ? inflections : [],
+ score,
+ dictionaryIndex,
+ dictionaryPriority,
+ sourceTermExactMatchCount,
+ maxDeinflectedTextLength,
+ [...headwords.values()],
+ definitions
+ );
+ }
- const definitionTags = this._getUniqueDefinitionTags(definitions);
- this._sortTags(definitionTags);
+ // Data collection addition functions
- const {glossary} = definitions[0];
- const score = this._getMaxDefinitionScore(definitions);
- const dictionaryOrder = this._getBestDictionaryOrder(definitions);
- return {
- type: 'termMergedByGlossary',
- // id
- source,
- rawSource,
- // sourceTerm
- reasons: [],
- score,
- // isPrimary
- // sequence
- dictionary: dictionaryNames[0],
- dictionaryOrder,
- dictionaryNames,
- expression: [...expressions],
- reading: [...readings],
- expressions: termDetailsList,
- glossary: [...glossary],
- definitionTags,
- // termTags
- definitions, // type: 'term'; contains duplicate data
- frequencies: [],
- pitches: [],
- only,
- sourceTermExactMatchCount
- };
+ _addUniqueStrings(list, newItems) {
+ for (const item of newItems) {
+ if (!list.includes(item)) {
+ list.push(item);
+ }
+ }
}
- /**
- * Creates a list of term details from an array of 'term' definitions.
- * @param definitions An array of 'term' definitions.
- * @returns An array of term details.
- */
- _createTermDetailsList(definitions) {
- const termInfoMap = new Map();
- for (const {expression, reading, sourceTerm, termTags} of definitions) {
- let readingMap = termInfoMap.get(expression);
- if (typeof readingMap === 'undefined') {
- readingMap = new Map();
- termInfoMap.set(expression, readingMap);
+ _addUniqueSources(sources, newSources) {
+ if (newSources.length === 0) { return; }
+ if (sources.length === 0) {
+ sources.push(...newSources);
+ return;
+ }
+ for (const newSource of newSources) {
+ const {originalText, transformedText, deinflectedText, isPrimary} = newSource;
+ let has = false;
+ for (const source of sources) {
+ if (
+ source.deinflectedText === deinflectedText &&
+ source.transformedText === transformedText &&
+ source.originalText === originalText
+ ) {
+ if (isPrimary) { source.isPrimary = true; }
+ has = true;
+ break;
+ }
}
+ if (!has) {
+ sources.push(newSource);
+ }
+ }
+ }
- let termInfo = readingMap.get(reading);
- if (typeof termInfo === 'undefined') {
- termInfo = {
- sourceTerm,
- termTagsMap: new Map()
- };
- readingMap.set(reading, termInfo);
+ _addUniqueTagGroups(tagGroups, newTagGroups) {
+ if (newTagGroups.length === 0) { return; }
+ for (const newTagGroup of newTagGroups) {
+ const {dictionary} = newTagGroup;
+ const ii = tagGroups.length;
+ if (ii > 0) {
+ let i = 0;
+ for (; i < ii; ++i) {
+ const tagGroup = tagGroups[i];
+ if (tagGroup.dictionary === dictionary) {
+ this._addUniqueStrings(tagGroup.tagNames, newTagGroup.tagNames);
+ break;
+ }
+ }
+ if (i < ii) { continue; }
}
+ tagGroups.push(newTagGroup);
+ }
+ }
- const {termTagsMap} = termInfo;
- for (const tag of termTags) {
- const {name} = tag;
- if (termTagsMap.has(name)) { continue; }
- termTagsMap.set(name, this._cloneTag(tag));
+ _addTermHeadwords(headwordsMap, headwords) {
+ const headwordIndexMap = [];
+ for (const {term, reading, sources, tags} of headwords) {
+ const key = this._createMapKey([term, reading]);
+ let headword = headwordsMap.get(key);
+ if (typeof headword === 'undefined') {
+ headword = this._createTermHeadword(headwordsMap.size, term, reading, [], []);
+ headwordsMap.set(key, headword);
}
+ this._addUniqueSources(headword.sources, sources);
+ this._addUniqueTagGroups(headword.tags, tags);
+ headwordIndexMap.push(headword.index);
+ }
+ return headwordIndexMap;
+ }
+
+ _addUniqueTermHeadwordIndex(headwordIndices, headwordIndex) {
+ let end = headwordIndices.length;
+ if (end === 0) {
+ headwordIndices.push(headwordIndex);
+ return;
}
- const termDetailsList = [];
- for (const [expression, readingMap] of termInfoMap.entries()) {
- for (const [reading, {termTagsMap, sourceTerm}] of readingMap.entries()) {
- const termTags = [...termTagsMap.values()];
- this._sortTags(termTags);
- termDetailsList.push(this._createTermDetails(sourceTerm, expression, reading, termTags));
+ let start = 0;
+ while (start < end) {
+ const mid = Math.floor((start + end) / 2);
+ const value = headwordIndices[mid];
+ if (headwordIndex === value) { return; }
+ if (headwordIndex > value) {
+ start = mid + 1;
+ } else {
+ end = mid;
}
}
- return termDetailsList;
+
+ if (headwordIndex === headwordIndices[start]) { return; }
+ headwordIndices.splice(start, 0, headwordIndex);
}
- _createTermDetails(sourceTerm, expression, reading, termTags) {
- return {
- sourceTerm,
- expression,
- reading,
- termTags,
- frequencies: [],
- pitches: []
- };
+ _addTermDefinitions(definitions, newDefinitions, headwordIndexMap) {
+ for (const {headwordIndices, dictionary, tags, entries} of newDefinitions) {
+ const headwordIndicesNew = [];
+ for (const headwordIndex of headwordIndices) {
+ headwordIndicesNew.push(headwordIndexMap[headwordIndex]);
+ }
+ definitions.push(this._createTermDefinition(definitions.length, headwordIndicesNew, dictionary, tags, entries));
+ }
}
- // Sorting functions
+ _addTermDefinitions2(definitions, definitionsMap, newDefinitions, headwordIndexMap) {
+ for (const {headwordIndices, dictionary, tags, entries} of newDefinitions) {
+ const key = this._createMapKey([dictionary, ...entries]);
+ let definition = definitionsMap.get(key);
+ if (typeof definition === 'undefined') {
+ definition = this._createTermDefinition(definitions.length, [], dictionary, [], [...entries]);
+ definitions.push(definition);
+ definitionsMap.set(key, definition);
+ }
- _sortTags(tags) {
- if (tags.length <= 1) { return; }
- const stringComparer = this._stringComparer;
- tags.sort((v1, v2) => {
- const i = v1.order - v2.order;
- if (i !== 0) { return i; }
+ const newHeadwordIndices = definition.headwordIndices;
+ for (const headwordIndex of headwordIndices) {
+ this._addUniqueTermHeadwordIndex(newHeadwordIndices, headwordIndexMap[headwordIndex]);
+ }
+ this._addUniqueTagGroups(definition.tags, tags);
+ }
+ }
- return stringComparer.compare(v1.name, v2.name);
- });
+ // Sorting functions
+
+ _sortDatabaseEntriesByIndex(databaseEntries) {
+ if (databaseEntries.length <= 1) { return; }
+ databaseEntries.sort((a, b) => a.index - b.index);
}
- _sortDefinitions(definitions, topLevel=true) {
- if (definitions.length <= 1) { return; }
+ _sortTermDictionaryEntries(dictionaryEntries) {
const stringComparer = this._stringComparer;
const compareFunction = (v1, v2) => {
- let i;
- if (topLevel) {
- // Sort by length of source term
- i = v2.source.length - v1.source.length;
- if (i !== 0) { return i; }
+ // Sort by length of source term
+ let i = v2.maxDeinflectedTextLength - v1.maxDeinflectedTextLength;
+ if (i !== 0) { return i; }
- // Sort by the number of inflection reasons
- i = v1.reasons.length - v2.reasons.length;
- if (i !== 0) { return i; }
+ // Sort by the number of inflection reasons
+ i = v1.inflections.length - v2.inflections.length;
+ if (i !== 0) { return i; }
- // Sort by how many terms exactly match the source (e.g. for exact kana prioritization)
- i = v2.sourceTermExactMatchCount - v1.sourceTermExactMatchCount;
- if (i !== 0) { return i; }
- }
+ // Sort by how many terms exactly match the source (e.g. for exact kana prioritization)
+ i = v2.sourceTermExactMatchCount - v1.sourceTermExactMatchCount;
+ if (i !== 0) { return i; }
// Sort by dictionary priority
- i = v2.dictionaryOrder.priority - v1.dictionaryOrder.priority;
+ i = v2.dictionaryPriority - v1.dictionaryPriority;
if (i !== 0) { return i; }
// Sort by term score
i = v2.score - v1.score;
if (i !== 0) { return i; }
- // Sort by expression string comparison (skip if either expression is not a string, e.g. array)
- const expression1 = v1.expression;
- const expression2 = v2.expression;
- if (typeof expression1 === 'string' && typeof expression2 === 'string') {
- i = expression2.length - expression1.length;
+ // Sort by expression text
+ const headwords1 = v1.headwords;
+ const headwords2 = v2.headwords;
+ for (let j = 0, jj = Math.min(headwords1.length, headwords2.length); j < jj; ++j) {
+ const term1 = headwords1[j].term;
+ const term2 = headwords2[j].term;
+
+ i = term2.length - term1.length;
if (i !== 0) { return i; }
- i = stringComparer.compare(expression1, expression2);
+ i = stringComparer.compare(term1, term2);
if (i !== 0) { return i; }
}
// Sort by dictionary order
- i = v1.dictionaryOrder.index - v2.dictionaryOrder.index;
+ i = v1.dictionaryIndex - v2.dictionaryIndex;
return i;
};
- definitions.sort(compareFunction);
+ dictionaryEntries.sort(compareFunction);
}
- _sortDatabaseDefinitionsByIndex(definitions) {
- if (definitions.length <= 1) { return; }
- definitions.sort((a, b) => a.index - b.index);
- }
+ _sortTermDefinitionEntries(definitionEntries) {
+ const compareFunction = (e1, e2) => {
+ const v1 = e1.dictionaryEntry;
+ const v2 = e2.dictionaryEntry;
- _sortDefinitionsById(definitions) {
- if (definitions.length <= 1) { return; }
- definitions.sort((a, b) => a.id - b.id);
- }
+ // Sort by dictionary priority
+ let i = v2.dictionaryPriority - v1.dictionaryPriority;
+ if (i !== 0) { return i; }
- _sortKanjiStats(stats) {
- if (stats.length <= 1) { return; }
- const stringComparer = this._stringComparer;
- stats.sort((v1, v2) => {
- const i = v1.order - v2.order;
+ // Sort by term score
+ i = v2.score - v1.score;
if (i !== 0) { return i; }
- return stringComparer.compare(v1.notes, v2.notes);
- });
+ // Sort by definition headword index
+ const definitions1 = v1.definitions;
+ const definitions2 = v2.definitions;
+ const headwordIndexMap1 = e1.headwordIndexMap;
+ const headwordIndexMap2 = e2.headwordIndexMap;
+ for (let j = 0, jj = Math.min(definitions1.length, definitions2.length); j < jj; ++j) {
+ const headwordIndices1 = definitions1[j].headwordIndices;
+ const headwordIndices2 = definitions2[j].headwordIndices;
+ const kk = headwordIndices1.length;
+ i = headwordIndices2.length - kk;
+ if (i !== 0) { return i; }
+ for (let k = 0; k < kk; ++k) {
+ i = headwordIndexMap1[headwordIndices1[k]] - headwordIndexMap2[headwordIndices2[k]];
+ if (i !== 0) { return i; }
+ }
+ }
+
+ // Sort by dictionary order
+ i = v1.dictionaryIndex - v2.dictionaryIndex;
+ if (i !== 0) { return i; }
+
+ // Sort by original order
+ i = e1.index - e2.index;
+ return i;
+ };
+ definitionEntries.sort(compareFunction);
}
- _sortTermDefinitionMeta(definition) {
- const compareFunction = (v1, v2) => {
+ _sortTermDictionaryEntriesById(dictionaryEntries) {
+ if (dictionaryEntries.length <= 1) { return; }
+ dictionaryEntries.sort((a, b) => a.id - b.id);
+ }
+
+ _sortTermDictionaryEntryData(dictionaryEntries) {
+ const compare = (v1, v2) => {
// Sort by dictionary priority
- let i = v2.dictionaryOrder.priority - v1.dictionaryOrder.priority;
+ let i = v2.dictionaryPriority - v1.dictionaryPriority;
if (i !== 0) { return i; }
// Sory by expression order
- i = v1.expressionIndex - v2.expressionIndex;
+ i = v1.headwordIndex - v2.headwordIndex;
if (i !== 0) { return i; }
// Sort by dictionary order
- i = v1.dictionaryOrder.index - v2.dictionaryOrder.index;
+ i = v1.dictionaryIndex - v2.dictionaryIndex;
if (i !== 0) { return i; }
// Default order
@@ -1343,23 +1281,21 @@ class Translator {
return i;
};
- const {expressions, frequencies: frequencies1, pitches: pitches1} = definition;
- frequencies1.sort(compareFunction);
- pitches1.sort(compareFunction);
- for (const {frequencies: frequencies2, pitches: pitches2} of expressions) {
- frequencies2.sort(compareFunction);
- pitches2.sort(compareFunction);
+ for (const {definitions, frequencies, pronunciations} of dictionaryEntries) {
+ this._flagRedundantDefinitionTags(definitions);
+ frequencies.sort(compare);
+ pronunciations.sort(compare);
}
}
- _sortKanjiDefinitionMeta(definition) {
- const compareFunction = (v1, v2) => {
+ _sortKanjiDictionaryEntryData(dictionaryEntries) {
+ const compare = (v1, v2) => {
// Sort by dictionary priority
- let i = v2.dictionaryOrder.priority - v1.dictionaryOrder.priority;
+ let i = v2.dictionaryPriority - v1.dictionaryPriority;
if (i !== 0) { return i; }
// Sort by dictionary order
- i = v1.dictionaryOrder.index - v2.dictionaryOrder.index;
+ i = v1.dictionaryIndex - v2.dictionaryIndex;
if (i !== 0) { return i; }
// Default order
@@ -1367,16 +1303,8 @@ class Translator {
return i;
};
- const {frequencies} = definition;
- frequencies.sort(compareFunction);
- }
-
- // Regex functions
-
- _applyTextReplacements(text, sourceMap, replacements) {
- for (const {pattern, replacement} of replacements) {
- text = RegexUtil.applyTextReplacement(text, sourceMap, pattern, replacement);
+ for (const {frequencies} of dictionaryEntries) {
+ frequencies.sort(compare);
}
- return text;
}
}
diff --git a/ext/js/templates/template-renderer-frame-main.js b/ext/js/templates/template-renderer-frame-main.js
index bfa18c82..c915d6b0 100644
--- a/ext/js/templates/template-renderer-frame-main.js
+++ b/ext/js/templates/template-renderer-frame-main.js
@@ -16,7 +16,7 @@
*/
/* globals
- * AnkiNoteData
+ * AnkiNoteDataCreator
* JapaneseUtil
* TemplateRenderer
* TemplateRendererFrameApi
@@ -25,8 +25,9 @@
(() => {
const japaneseUtil = new JapaneseUtil(null);
const templateRenderer = new TemplateRenderer(japaneseUtil);
+ const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
templateRenderer.registerDataType('ankiNote', {
- modifier: ({data, marker}) => new AnkiNoteData(japaneseUtil, marker, data).createPublic()
+ modifier: ({data, marker}) => ankiNoteDataCreator.create(marker, data)
});
const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
templateRendererFrameApi.prepare();
diff --git a/ext/template-renderer.html b/ext/template-renderer.html
index eb3695e1..74167551 100644
--- a/ext/template-renderer.html
+++ b/ext/template-renderer.html
@@ -17,7 +17,7 @@
<!-- Scripts -->
<script src="/lib/handlebars.min.js"></script>
-<script src="/js/data/anki-note-data.js"></script>
+<script src="/js/data/anki-note-data-creator.js"></script>
<script src="/js/language/dictionary-data-util.js"></script>
<script src="/js/language/japanese-util.js"></script>
<script src="/js/templates/template-renderer.js"></script>