famibee/SKYNovel

View on GitHub
src/sn/RubySpliter.ts

Summary

Maintainability
A
0 mins
Test Coverage
/* ***** BEGIN LICENSE BLOCK *****
    Copyright (c) 2018-2024 Famibee (famibee.blog38.fc2.com)

    This software is released under the MIT License.
    http://opensource.org/licenses/mit-license.php
** ***** END LICENSE BLOCK ***** */

import {HArg} from './Grammar';
import {IPutCh} from './CmnInterface';

export interface IAutoPage { (idx: number, str: string): void; }

export class RubySpliter {
    static    #sesame        = 'ヽ';
    static    setting(hArg: HArg) {if (hArg.sesame) RubySpliter.#sesame = hArg.sesame}
    static    getSesame() {return RubySpliter.#sesame}

    static    destroy() {RubySpliter.#sesame = 'ヽ'}

    #putCh    : IPutCh    = ()=> {};
    init(putCh: IPutCh) {this.#putCh = putCh}

/*
        ★Unicodeで「漢字」の正規表現 – ものかの http://tama-san.com/kanji-regex/
        2E80..2FDF CJK部首補助+康熙部首
        3005 々(漢字の踊り字)
        3007 〇(漢数字のゼロ)
        303B 〻(漢字の踊り字)
        3400..4DBF CJK統合漢字拡張A
        4E00..9FFF CJK統合漢字
        F900..FAFF CJK互換漢字
        20000..2FFFF CJK統合漢字拡張B〜F+CJK互換漢字追加+念のためU+2FFFFまで

        [\x{2E80}-\x{2FDF}々〇〻\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}\x{20000}-\x{2FFFF}]
        [\u2E80-\u2FDF々〇〻\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\u20000-\u2FFFF]
        [⺀-⿟々〇〻㐀-䶿一-鿿豈-﫿\u20000-\u2FFFF]        // 含まれない文字がある
        [⺀-⿟々〇〻㐀-鿿豈-﫿\u20000-\u2FFFF]            // ヽ--30FD が変に引っかかる。多分\u2000-\u2FFF解釈
        \\u{20000}-\\u{2FFFF}    // 五桁だとエラー

        【2022/10/03】ruby正規表現のUnicode プロパティ(とPOSIX文字クラス) - Qiita https://qiita.com/Takayuki_Nakano/items/8d38beaddb84b488d683
            > このHiraganaプロパティ、長音記号は含まれていません。
            > \p{Han}…簡体字や繁体字、韓国語の漢字…ベトナム語の漢字にもマッチ
        
        ・Unicode文字一覧表 - instant tools https://tools.m-bsys.com/ex/unicode_table.php
*/
    static    #REG_RUBY    : RegExp;
    static    setEscape(ce: string) {
        // 830 match 11293 step(1.7ms) PCRE2 https://regex101.com/r/BBrQtC/1
        /*
(?<txt4>\\\S)
|    |(?<str>[^《\n]+)《(?<ruby>[^》\n]+)》
|    (?: (?<kan>[⺀-⿟々〇〻㐀-鿿豈-﫿]+ [ぁ-ヿ]* | [^ |《》\n])
        《(?<kan_ruby>[^》\n]+)》)
|    (?<txt>
    [\xD800-\xDBFF][\xDC00-\xDFFF]
|    [^|《》]+?
|    .)
        */
        RubySpliter.#REG_RUBY = new RegExp(
            `${ce ?`(?<ce>\\${ce}\\S)|` :''}`+
            `|(?<str>[^《\\n]+)《(?<ruby>[^》\\n]+)》`+
            `|(?:(?<kan>[⺀-⿟々〇〻㐀-鿿豈-﫿]+[ぁ-ヿ]*|[^ |《》\\n])`+
            `《(?<kan_ruby>[^》\\n]+)》)`+
            `|(?<txt>`+
            `[\uD800-\uDBFF][\uDC00-\uDFFF]`+    // 上位 + 下位サロゲート
            `|[^|《》]+?`+        // 不要だが細切れにしないほうが後々効率で有利
            `|.)`,
            'gs'
        );
    }

    putTxt(text: string) {
        for (const {groups} of text.matchAll(RubySpliter.#REG_RUBY)) {
            const {ruby, kan_ruby, kan, ce, txt='', str} = groups!;
            if (ruby) {this.putTxtRb(decodeURIComponent(str), ruby); continue}

            if (kan_ruby) {this.putTxtRb(kan, kan_ruby); continue}
            if (ce) {this.#putCh(ce.slice(1), ''); continue}

            for (const v of Array.from(<string>txt)) this.#putCh(v, '');
                // txt.split('')や [...txt] はサロゲートペアで問題
        }
    }

    putTxtRb(text: string, ruby: string) {    // テスト用にpublic
        // 自動区切りを行わない(内部的 json文法)
        if (/^\w+|{"/.test(ruby)) {this.#putCh(text, ruby); return}

        const a: string[] = Array.from(text);
        const len = a.length;
        if (/^\*.?$/.test(ruby)) {    // 傍点文法
            const rb_ses = 'center|'+ (ruby === '*' ?RubySpliter.#sesame :ruby.charAt(1));
            for (let i=0; i<len; ++i) this.#putCh(a[i], rb_ses);
            return;
        }

        // 自動区切りを行わない(単漢字など)
        if (len === 1 || ruby.indexOf(' ') === -1) {
            this.#putCh(text, decodeURIComponent(ruby));
            return;
        }

        // 空白がある場合はユーザが区切りを指定しているものと見なす
        const aR = ruby.split(' ');
        const lenR = aR.length;
        const len_max = (lenR > len) ?lenR :len;
        for (let i=0; i<len_max; ++i) this.#putCh(
            (i < len) ? a[i] : '',
            (i < lenR) ? decodeURIComponent(aR[i]) : ''
        );
    }

}