aboutsummaryrefslogtreecommitdiff
path: root/ext/bg
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2016-03-26 14:21:51 -0700
committerAlex Yatskov <alex@foosoft.net>2016-03-26 14:21:51 -0700
commitced7bf5f27c72b98fae3b2d24f1820e0083c22bf (patch)
tree3b6e88f5ed32b1cebfd20035c44fde6d947bf9bf /ext/bg
parent12696d3e6b3d7f1b92923ed625fdb3dc5ec660f2 (diff)
File reorg
Diffstat (limited to 'ext/bg')
-rw-r--r--ext/bg/deinflector.js126
-rw-r--r--ext/bg/dictionary.js84
-rw-r--r--ext/bg/translator.js151
-rw-r--r--ext/bg/yomichan.js73
4 files changed, 434 insertions, 0 deletions
diff --git a/ext/bg/deinflector.js b/ext/bg/deinflector.js
new file mode 100644
index 00000000..03f9d40a
--- /dev/null
+++ b/ext/bg/deinflector.js
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+class Deinflection {
+ constructor(term, tags=[], rule='') {
+ this.children = [];
+ this.term = term;
+ this.tags = tags;
+ this.rule = rule;
+ }
+
+ validate(validator) {
+ for (const tags of validator(this.term)) {
+ if (this.tags.length === 0) {
+ return true;
+ }
+
+ for (const tag of this.tags) {
+ if (this.searchTags(tag, tags)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ deinflect(validator, rules) {
+ if (this.validate(validator)) {
+ const child = new Deinflection(this.term);
+ this.children.push(child);
+ }
+
+ for (const rule in rules) {
+ const variants = rules[rule];
+ for (const v of variants) {
+ let allowed = this.tags.length === 0;
+ for (const tag of this.tags) {
+ if (this.searchTags(tag, v.tagsIn)) {
+ allowed = true;
+ break;
+ }
+ }
+
+ if (!allowed || !this.term.endsWith(v.kanaIn)) {
+ continue;
+ }
+
+ const term = this.term.slice(0, -v.kanaIn.length) + v.kanaOut;
+ const child = new Deinflection(term, v.tagsOut, rule);
+ if (child.deinflect(validator, rules)) {
+ this.children.push(child);
+ }
+ }
+ }
+
+ return this.children.length > 0;
+ }
+
+ searchTags(tag, tags) {
+ for (const t of tags) {
+ const re = new RegExp(tag);
+ if (re.test(t)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ gather() {
+ if (this.children.length === 0) {
+ return [{root: this.term, rules: []}];
+ }
+
+ const paths = [];
+ for (const child of this.children) {
+ for (const path of child.gather()) {
+ if (this.rule.length > 0) {
+ path.rules.push(this.rule);
+ }
+
+ path.source = this.term;
+ paths.push(path);
+ }
+ }
+
+ return paths;
+ }
+}
+
+
+class Deinflector {
+ constructor() {
+ this.rules = {};
+ }
+
+ setRules(rules) {
+ this.rules = rules;
+ }
+
+ deinflect(term, validator) {
+ const node = new Deinflection(term);
+ if (node.deinflect(validator, this.rules)) {
+ return node.gather();
+ }
+
+ return null;
+ }
+}
diff --git a/ext/bg/dictionary.js b/ext/bg/dictionary.js
new file mode 100644
index 00000000..eff54890
--- /dev/null
+++ b/ext/bg/dictionary.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+class Dictionary {
+ constructor() {
+ this.termDicts = [];
+ this.kanjiDicts = [];
+ }
+
+ addTermDict(termDict) {
+ this.termDicts.push(termDict);
+ }
+
+ addKanjiDict(kanjiDict) {
+ this.kanjiDicts.push(kanjiDict);
+ }
+
+
+ findTerm(term) {
+ let results = [];
+ for (let dict of this.termDicts) {
+ results = results.concat(this.findTermInDict(term, dict));
+ }
+
+ return results;
+ }
+
+ findKanji(kanji) {
+ const results = [];
+ for (let dict of this.kanjiDicts) {
+ const result = this.findKanjiInDict(kanji, dict);
+ if (result !== null) {
+ results.push(result);
+ }
+ }
+
+ return results;
+ }
+
+ findTermInDict(term, dict) {
+ return (dict.indices[term] || []).map(index => {
+ const [e, r, g, t] = dict.defs[index];
+ return {
+ id: index,
+ expression: e,
+ reading: r,
+ glossary: g,
+ tags: t.split(' ')
+ };
+ });
+ }
+
+ findKanjiInDict(kanji, dict) {
+ const def = dict.defs[kanji];
+ if (def === null) {
+ return null;
+ }
+
+ const [c, k, o, g] = def;
+ return {
+ id: kanji.charCodeAt(0),
+ character: c,
+ kunyomi: k,
+ onyomi: o,
+ glossary: g
+ };
+ }
+}
diff --git a/ext/bg/translator.js b/ext/bg/translator.js
new file mode 100644
index 00000000..7a610377
--- /dev/null
+++ b/ext/bg/translator.js
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+class Translator {
+ constructor() {
+ this.dictionary = new Dictionary();
+ this.deinflector = new Deinflector();
+ this.initialized = false;
+ }
+
+ loadData(paths, callback) {
+ if (this.initialized) {
+ callback();
+ return;
+ }
+
+ const loaders = [];
+ for (const key of ['rules', 'edict', 'enamdict', 'kanjidic']) {
+ loaders.push(
+ $.getJSON(chrome.extension.getURL(paths[key]))
+ );
+ }
+
+ $.when.apply($, loaders).done((rules, edict, enamdict, kanjidic) => {
+ this.deinflector.setRules(rules[0]);
+
+ this.dictionary.addTermDict(edict[0]);
+ this.dictionary.addTermDict(enamdict[0]);
+ this.dictionary.addKanjiDict(kanjidic[0]);
+
+ this.initialized = true;
+
+ if (callback) {
+ callback();
+ }
+ });
+ }
+
+ findTerm(text) {
+ const groups = {};
+ for (let i = text.length; i > 0; --i) {
+ const term = text.slice(0, i);
+
+ const dfs = this.deinflector.deinflect(term, t => {
+ const tags = [];
+ for (const d of this.dictionary.findTerm(t)) {
+ tags.push(d.tags);
+ }
+
+ return tags;
+ });
+
+ if (dfs === null) {
+ this.processTerm(groups, term);
+ } else {
+ for (const df of dfs) {
+ this.processTerm(groups, df.source, df.rules, df.root);
+ }
+ }
+ }
+
+ let results = [];
+ for (const key in groups) {
+ results.push(groups[key]);
+ }
+
+ results = results.sort(this.resultSorter);
+
+ let length = 0;
+ for (const result of results) {
+ length = Math.max(length, result.source.length);
+ }
+
+ return {results: results, length: length};
+ }
+
+ findKanji(text) {
+ let results = [];
+
+ const processed = {};
+ for (const c of text) {
+ if (!processed.has(c)) {
+ results = results.concat(this.dictionary.findKanji(c));
+ processed[c] = true;
+ }
+ }
+
+ return results;
+ }
+
+ processTerm(groups, source, rules=[], root='') {
+ for (const entry of this.dictionary.findTerm(root || source)) {
+ if (entry.id in groups) {
+ continue;
+ }
+
+ groups[entry.id] = {
+ expression: entry.expression,
+ reading: entry.reading,
+ glossary: entry.glossary,
+ tags: entry.tags,
+ source: source,
+ rules: rules
+ };
+ }
+ }
+
+ resultSorter(v1, v2) {
+ const sl1 = v1.source.length;
+ const sl2 = v2.source.length;
+ if (sl1 > sl2) {
+ return -1;
+ } else if (sl1 < sl2) {
+ return 1;
+ }
+
+ const p1 = v1.tags.indexOf('P') >= 0;
+ const p2 = v2.tags.indexOf('P') >= 0;
+ if (p1 && !p2) {
+ return -1;
+ } else if (!p1 && p2) {
+ return 1;
+ }
+
+ const rl1 = v1.rules.length;
+ const rl2 = v2.rules.length;
+ if (rl1 < rl2) {
+ return -1;
+ } else if (rl2 > rl1) {
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/ext/bg/yomichan.js b/ext/bg/yomichan.js
new file mode 100644
index 00000000..196785e1
--- /dev/null
+++ b/ext/bg/yomichan.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+class Yomichan {
+ constructor() {
+ this.translator = new Translator();
+ this.res = {
+ rules: 'bg/data/rules.json',
+ edict: 'bg/data/edict.json',
+ enamdict: 'bg/data/enamdict.json',
+ kanjidic: 'bg/data/kanjidic.json'
+ };
+
+ this.updateState('disabled');
+
+ chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
+ chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this));
+ }
+
+ onFindTerm({term}) {
+ return this.translator.findTerm(term);
+ }
+
+ onMessage(request, sender, callback) {
+ const {action, data} = request;
+ const handler = {
+ findTerm: this.onFindTerm
+ }[action];
+
+ if (handler !== null) {
+ const result = handler.call(this, data);
+ if (callback !== null) {
+ callback(result);
+ }
+ }
+ }
+
+ onBrowserAction(tab) {
+ switch (this.state) {
+ case 'disabled':
+ this.updateState('loading');
+ this.translator.loadData(this.res, () => this.updateState('enabled'));
+ break;
+ case 'enabled':
+ this.updateState('disabled');
+ break;
+ }
+ }
+
+ updateState(state) {
+ const text = {'disabled': '', 'enabled': 'on', 'loading': '...'}[state];
+ chrome.browserAction.setBadgeText({text: text});
+ this.state = state;
+ }
+}
+
+window.yomichan = new Yomichan();