kuroshiro
Version:
kuroshiro is a Japanese language library for converting Japanese sentence to Hiragana, Katakana or Romaji with furigana and okurigana modes supported.
337 lines (280 loc) • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _util = require("./util");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
/**
* Kuroshiro Class
*/
class Kuroshiro {
/**
* Constructor
* @constructs Kuroshiro
*/
constructor() {
this._analyzer = null;
}
/**
* Initialize Kuroshiro
* @memberOf Kuroshiro
* @instance
* @returns {Promise} Promise object represents the result of initialization
*/
init(analyzer) {
var _this = this;
return _asyncToGenerator(function* () {
if (!analyzer || typeof analyzer !== "object" || typeof analyzer.init !== "function" || typeof analyzer.parse !== "function") {
throw new Error("Invalid initialization parameter.");
} else if (_this._analyzer == null) {
yield analyzer.init();
_this._analyzer = analyzer;
} else {
throw new Error("Kuroshiro has already been initialized.");
}
})();
}
/**
* Convert given string to target syllabary with options available
* @memberOf Kuroshiro
* @instance
* @param {string} str Given String
* @param {Object} [options] Settings Object
* @param {string} [options.to="hiragana"] Target syllabary ["hiragana"|"katakana"|"romaji"]
* @param {string} [options.mode="normal"] Convert mode ["normal"|"spaced"|"okurigana"|"furigana"]
* @param {string} [options.romajiSystem="hepburn"] Romanization System ["nippon"|"passport"|"hepburn"]
* @param {string} [options.delimiter_start="("] Delimiter(Start)
* @param {string} [options.delimiter_end=")"] Delimiter(End)
* @returns {Promise} Promise object represents the result of conversion
*/
convert(str, options) {
var _this2 = this;
return _asyncToGenerator(function* () {
options = options || {};
options.to = options.to || "hiragana";
options.mode = options.mode || "normal";
options.romajiSystem = options.romajiSystem || _util.ROMANIZATION_SYSTEM.HEPBURN;
options.delimiter_start = options.delimiter_start || "(";
options.delimiter_end = options.delimiter_end || ")";
str = str || "";
if (["hiragana", "katakana", "romaji"].indexOf(options.to) === -1) {
throw new Error("Invalid Target Syllabary.");
}
if (["normal", "spaced", "okurigana", "furigana"].indexOf(options.mode) === -1) {
throw new Error("Invalid Conversion Mode.");
}
const ROMAJI_SYSTEMS = Object.keys(_util.ROMANIZATION_SYSTEM).map(e => _util.ROMANIZATION_SYSTEM[e]);
if (ROMAJI_SYSTEMS.indexOf(options.romajiSystem) === -1) {
throw new Error("Invalid Romanization System.");
}
const rawTokens = yield _this2._analyzer.parse(str);
const tokens = (0, _util.patchTokens)(rawTokens);
if (options.mode === "normal" || options.mode === "spaced") {
switch (options.to) {
case "katakana":
if (options.mode === "normal") {
return tokens.map(token => token.reading).join("");
}
return tokens.map(token => token.reading).join(" ");
case "romaji":
const romajiConv = token => {
let preToken;
if ((0, _util.hasJapanese)(token.surface_form)) {
preToken = token.pronunciation || token.reading;
} else {
preToken = token.surface_form;
}
return (0, _util.toRawRomaji)(preToken, options.romajiSystem);
};
if (options.mode === "normal") {
return tokens.map(romajiConv).join("");
}
return tokens.map(romajiConv).join(" ");
case "hiragana":
for (let hi = 0; hi < tokens.length; hi++) {
if ((0, _util.hasKanji)(tokens[hi].surface_form)) {
if (!(0, _util.hasKatakana)(tokens[hi].surface_form)) {
tokens[hi].reading = (0, _util.toRawHiragana)(tokens[hi].reading);
} else {
// handle katakana-kanji-mixed tokens
tokens[hi].reading = (0, _util.toRawHiragana)(tokens[hi].reading);
let tmp = "";
let hpattern = "";
for (let hc = 0; hc < tokens[hi].surface_form.length; hc++) {
if ((0, _util.isKanji)(tokens[hi].surface_form[hc])) {
hpattern += "(.*)";
} else {
hpattern += (0, _util.isKatakana)(tokens[hi].surface_form[hc]) ? (0, _util.toRawHiragana)(tokens[hi].surface_form[hc]) : tokens[hi].surface_form[hc];
}
}
const hreg = new RegExp(hpattern);
const hmatches = hreg.exec(tokens[hi].reading);
if (hmatches) {
let pickKJ = 0;
for (let hc1 = 0; hc1 < tokens[hi].surface_form.length; hc1++) {
if ((0, _util.isKanji)(tokens[hi].surface_form[hc1])) {
tmp += hmatches[pickKJ + 1];
pickKJ++;
} else {
tmp += tokens[hi].surface_form[hc1];
}
}
tokens[hi].reading = tmp;
}
}
} else {
tokens[hi].reading = tokens[hi].surface_form;
}
}
if (options.mode === "normal") {
return tokens.map(token => token.reading).join("");
}
return tokens.map(token => token.reading).join(" ");
default:
throw new Error("Unknown option.to param");
}
} else if (options.mode === "okurigana" || options.mode === "furigana") {
const notations = []; // [basic, basic_type[1=kanji,2=kana,3=others], notation, pronunciation]
for (let i = 0; i < tokens.length; i++) {
const strType = (0, _util.getStrType)(tokens[i].surface_form);
switch (strType) {
case 0:
notations.push([tokens[i].surface_form, 1, (0, _util.toRawHiragana)(tokens[i].reading), tokens[i].pronunciation || tokens[i].reading]);
break;
case 1:
let pattern = "";
let isLastTokenKanji = false;
const subs = []; // recognize kanjis and group them
for (let c = 0; c < tokens[i].surface_form.length; c++) {
if ((0, _util.isKanji)(tokens[i].surface_form[c])) {
if (!isLastTokenKanji) {
// ignore successive kanji tokens (#10)
isLastTokenKanji = true;
pattern += "(.+)";
subs.push(tokens[i].surface_form[c]);
} else {
subs[subs.length - 1] += tokens[i].surface_form[c];
}
} else {
isLastTokenKanji = false;
subs.push(tokens[i].surface_form[c]);
pattern += (0, _util.isKatakana)(tokens[i].surface_form[c]) ? (0, _util.toRawHiragana)(tokens[i].surface_form[c]) : tokens[i].surface_form[c];
}
}
const reg = new RegExp(`^${pattern}$`);
const matches = reg.exec((0, _util.toRawHiragana)(tokens[i].reading));
if (matches) {
let pickKanji = 1;
for (let c1 = 0; c1 < subs.length; c1++) {
if ((0, _util.isKanji)(subs[c1][0])) {
notations.push([subs[c1], 1, matches[pickKanji], (0, _util.toRawKatakana)(matches[pickKanji])]);
pickKanji += 1;
} else {
notations.push([subs[c1], 2, (0, _util.toRawHiragana)(subs[c1]), (0, _util.toRawKatakana)(subs[c1])]);
}
}
} else {
notations.push([tokens[i].surface_form, 1, (0, _util.toRawHiragana)(tokens[i].reading), tokens[i].pronunciation || tokens[i].reading]);
}
break;
case 2:
for (let c2 = 0; c2 < tokens[i].surface_form.length; c2++) {
notations.push([tokens[i].surface_form[c2], 2, (0, _util.toRawHiragana)(tokens[i].reading[c2]), tokens[i].pronunciation && tokens[i].pronunciation[c2] || tokens[i].reading[c2]]);
}
break;
case 3:
for (let c3 = 0; c3 < tokens[i].surface_form.length; c3++) {
notations.push([tokens[i].surface_form[c3], 3, tokens[i].surface_form[c3], tokens[i].surface_form[c3]]);
}
break;
default:
throw new Error("Unknown strType");
}
}
let result = "";
switch (options.to) {
case "katakana":
if (options.mode === "okurigana") {
for (let n0 = 0; n0 < notations.length; n0++) {
if (notations[n0][1] !== 1) {
result += notations[n0][0];
} else {
result += notations[n0][0] + options.delimiter_start + (0, _util.toRawKatakana)(notations[n0][2]) + options.delimiter_end;
}
}
} else {
// furigana
for (let n1 = 0; n1 < notations.length; n1++) {
if (notations[n1][1] !== 1) {
result += notations[n1][0];
} else {
result += `<ruby>${notations[n1][0]}<rp>${options.delimiter_start}</rp><rt>${(0, _util.toRawKatakana)(notations[n1][2])}</rt><rp>${options.delimiter_end}</rp></ruby>`;
}
}
}
return result;
case "romaji":
if (options.mode === "okurigana") {
for (let n2 = 0; n2 < notations.length; n2++) {
if (notations[n2][1] !== 1) {
result += notations[n2][0];
} else {
result += notations[n2][0] + options.delimiter_start + (0, _util.toRawRomaji)(notations[n2][3], options.romajiSystem) + options.delimiter_end;
}
}
} else {
// furigana
result += "<ruby>";
for (let n3 = 0; n3 < notations.length; n3++) {
result += `${notations[n3][0]}<rp>${options.delimiter_start}</rp><rt>${(0, _util.toRawRomaji)(notations[n3][3], options.romajiSystem)}</rt><rp>${options.delimiter_end}</rp>`;
}
result += "</ruby>";
}
return result;
case "hiragana":
if (options.mode === "okurigana") {
for (let n4 = 0; n4 < notations.length; n4++) {
if (notations[n4][1] !== 1) {
result += notations[n4][0];
} else {
result += notations[n4][0] + options.delimiter_start + notations[n4][2] + options.delimiter_end;
}
}
} else {
// furigana
for (let n5 = 0; n5 < notations.length; n5++) {
if (notations[n5][1] !== 1) {
result += notations[n5][0];
} else {
result += `<ruby>${notations[n5][0]}<rp>${options.delimiter_start}</rp><rt>${notations[n5][2]}</rt><rp>${options.delimiter_end}</rp></ruby>`;
}
}
}
return result;
default:
throw new Error("Invalid Target Syllabary.");
}
}
})();
}
}
const Util = {
isHiragana: _util.isHiragana,
isKatakana: _util.isKatakana,
isKana: _util.isKana,
isKanji: _util.isKanji,
isJapanese: _util.isJapanese,
hasHiragana: _util.hasHiragana,
hasKatakana: _util.hasKatakana,
hasKana: _util.hasKana,
hasKanji: _util.hasKanji,
hasJapanese: _util.hasJapanese,
kanaToHiragna: _util.kanaToHiragna,
kanaToKatakana: _util.kanaToKatakana,
kanaToRomaji: _util.kanaToRomaji
};
Kuroshiro.Util = Util;
var _default = Kuroshiro;
exports.default = _default;