import { UnicodeRange } from "./string.ts"; import "./number.ts"; declare global { interface String { /** * @summary check if string is hiragana only * * @argument strict don't allow ascii whitespace and punctuation (default: false) * * return `true` if at least one hiragana character is in string, and no other * unicode ranges are found. ascii whitespace and punctuation is still allowed, * but not counted as hiragana. this behavior can be turned off by setting * `strict` to true */ hiraganaOnly(strict?: boolean): boolean /** * @summary check if string is katakana only * * @argument strict don't allow ascii whitespace and punctuation (default: false) * * return `true` if at least one katakana character is in string, and no other * unicode ranges are found. ascii whitespace and punctuation is still allowed, * but not counted as katakana. this behavior can be turned off by setting * `strict` to true */ katakanaOnly(strict?: boolean): boolean /** * @summary check if string is kanji only * * @argument strict don't allow ascii whitespace and punctuation (default: false) * * return `true` if at least one kanji character is in string, and no other * unicode ranges are found. ascii whitespace and punctuation is still allowed, * but not counted as kanji. this behavior can be turned off by setting * `strict` to true */ kanjiOnly(strict?: boolean): boolean /** * @summary check if string is kana only * * @argument strict don't allow ascii whitespace and punctuation (default: false) * * return `true` if at least one kana character is in string, and no other * unicode ranges are found. ascii whitespace and punctuation is still allowed, * but not counted as kana. this behavior can be turned off by setting `strict` * to true */ kanaOnly(strict?: boolean): boolean /** * @summary check if string is japanese only * * @argument strict don't allow ascii whitespace and punctuation (default: false) * * return `true` if at least one japanese character is in string, and no other * unicode ranges are found. ascii whitespace and punctuation is still allowed, * but not counted as japanese. this behavior can be turned off by setting * `strict` to true */ japaneseOnly(strict?: boolean): boolean /** @summary check if the string contains kanji characters */ hasKanji(): boolean; /** @summary convert any half-width katakana to full-width */ widenKatakana(): string; /** @summary convert any full-width katakana to hiragana */ katakanaToHiragana(): string; /** @summary convert any kana (full and half-width) to full-width hiragana */ normalizeKana(): string; } } enum StringOnlyReturnValue { TallyAdd, TallyIgnore, TallyStop, } /** @summary check tally for allowed scripts (internal use only) */ function stringOnly(input: string, check: (key: string, val: number) => StringOnlyReturnValue): boolean { var tally = input.rangeTally(); var ok = false; for (var [key, val] of Object.entries(tally)) { switch(check(key, val)) { case StringOnlyReturnValue.TallyAdd: { ok = true; break; } case StringOnlyReturnValue.TallyIgnore: { break; } case StringOnlyReturnValue.TallyStop: { return false; } } } return ok; } String.prototype.hiraganaOnly = function(strict = false) { return stringOnly(this as string, (key, val) => { if (key == UnicodeRange.JapaneseFWHiragana) return StringOnlyReturnValue.TallyAdd; // count hiragana characters else if (!strict && key.startsWith("any-")) return StringOnlyReturnValue.TallyIgnore; // allow any- (ascii whitespace and punctuation) else if (val > 0) return StringOnlyReturnValue.TallyStop; // don't allow any other ranges return StringOnlyReturnValue.TallyIgnore; }); } String.prototype.katakanaOnly = function(strict = false) { return stringOnly(this as string, (key, val) => { if ([UnicodeRange.JapaneseHWKatakana, UnicodeRange.JapaneseFWKatakana].includes(key as UnicodeRange)) return StringOnlyReturnValue.TallyAdd; // count katakana characters else if (!strict && key.startsWith("any-")) return StringOnlyReturnValue.TallyIgnore; // allow any- (ascii whitespace and punctuation) else if (val > 0) return StringOnlyReturnValue.TallyStop; // don't allow any other ranges return StringOnlyReturnValue.TallyIgnore; }); } String.prototype.kanjiOnly = function(strict = false) { let temp = this.replaceAll("々", "力"); // kanjiOnly should return true for kanji repeat mark as well return stringOnly(temp, (key, val) => { if (key == UnicodeRange.JapaneseKanji) return StringOnlyReturnValue.TallyAdd; // count kanji characters else if (!strict && key.startsWith("any-")) return StringOnlyReturnValue.TallyIgnore; // allow any- (ascii whitespace and punctuation) else if (val > 0) return StringOnlyReturnValue.TallyStop; // don't allow any other ranges return StringOnlyReturnValue.TallyIgnore; }); } String.prototype.kanaOnly = function(strict = false) { return stringOnly(this as string, (key, val) => { if ([UnicodeRange.JapaneseHWKatakana, UnicodeRange.JapaneseFWKatakana, UnicodeRange.JapaneseFWHiragana].includes(key as UnicodeRange)) return StringOnlyReturnValue.TallyAdd; // count kana characters else if (!strict && key.startsWith("any-")) return StringOnlyReturnValue.TallyIgnore; // allow any- (ascii whitespace and punctuation) else if (val > 0) return StringOnlyReturnValue.TallyStop; // don't allow any other ranges return StringOnlyReturnValue.TallyIgnore; }); } String.prototype.japaneseOnly = function(strict = false) { return stringOnly(this as string, (key, val) => { if (key.startsWith("jp-")) return StringOnlyReturnValue.TallyAdd; // count japanese characters else if (!strict && key.startsWith("any-")) return StringOnlyReturnValue.TallyIgnore; // allow any- (ascii whitespace and punctuation) else if (val > 0) return StringOnlyReturnValue.TallyStop; // don't allow any other ranges return StringOnlyReturnValue.TallyIgnore; }); } String.prototype.widenKatakana = function() { const map: { [key: string]: string } = { "ァ": "ァ", "ア": "ア", "ィ": "ィ", "イ": "イ", "ゥ": "ゥ", "ウ": "ウ", "ェ": "ェ", "エ": "エ", "ォ": "ォ", "オ": "オ", "ガ": "ガ", "カ": "カ", "ギ": "ギ", "キ": "キ", "グ": "グ", "ク": "ク", "ゲ": "ゲ", "ケ": "ケ", "ゴ": "ゴ", "コ": "コ", "ザ": "ザ", "サ": "サ", "ジ": "ジ", "シ": "シ", "ズ": "ズ", "ス": "ス", "ゼ": "ゼ", "セ": "セ", "ゾ": "ゾ", "ソ": "ソ", "ダ": "ダ", "タ": "タ", "ヂ": "ヂ", "チ": "チ", "ヅ": "ヅ", "ッ": "ッ", "ツ": "ツ", "デ": "デ", "テ": "テ", "ド": "ド", "ト": "ト", "ナ": "ナ", "ニ": "ニ", "ヌ": "ヌ", "ネ": "ネ", "ノ": "ノ", "バ": "バ", "パ": "パ", "ハ": "ハ", "ビ": "ビ", "ピ": "ピ", "ヒ": "ヒ", "ブ": "ブ", "プ": "プ", "フ": "フ", "ベ": "ベ", "ペ": "ペ", "ヘ": "ヘ", "ボ": "ボ", "ポ": "ポ", "ホ": "ホ", "マ": "マ", "ミ": "ミ", "ム": "ム", "メ": "メ", "モ": "モ", "ャ": "ャ", "ヤ": "ヤ", "ュ": "ュ", "ユ": "ユ", "ョ": "ョ", "ヨ": "ヨ", "ラ": "ラ", "リ": "リ", "ル": "ル", "レ": "レ", "ロ": "ロ", "ワ": "ワ", "ヲ": "ヲ", "ン": "ン", "ヴ": "ヴ", "ヷ": "ヷ", "イ゙": "イ゙", "エ゙": "エ゙", "ヺ": "ヺ", "ー": "ー", " ": " ", }; var out = ""; outer: for (let i = 0; i < this.length; i++) { for (var key in map) { if (!this.substring(i).startsWith(key)) continue; out += map[key]; i += key.length - 1; continue outer; } out += this[i]; } return out; } String.prototype.katakanaToHiragana = function() { return this.map(char => { var code = char.codePointAt(0)!; if (0x30a1 <= code && code <= 0x30f6) return (code + (0x3041 - 0x30a1)).toChar(); return char; }) } String.prototype.normalizeKana = function() { return this.widenKatakana().katakanaToHiragana(); } String.prototype.hasKanji = function() { for (var c of this) if ([UnicodeRange.JapaneseKanji].includes(c.range())) return true; return false; }