変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

javascript

[javascript]正規表現

以前作ったJSのUTF-16をSJISに変換するサンプルが時代の波にモマれ動かなくなってたので作り直し。

それにつけても非推奨が増える一方な感じがするJS。

同様に正規表現のデバッガも動かなくなっていた。(【javascript】 RegExp 正規表現デバッガ

でも正規表現で名前付きキャプショングループを使える様になってきたみたいだから良しとしよう。

※2016年の記事(9年も前か!)だし、単に使いこなせなかったかもしれないけどね。

今回は名前付きキャプショングループのおかげで内容をとても単純になった。

正規表現の/…/の中のパターンとオプションを手入力して【チェック】ボタンを押すと

new RegExp({正規表現パターン}, {オプション})

が動き、RegExpオブジェクトのプロパティを画面下の結果欄に表示する。

だけのハズだったが、

実際 サンプルをRegExe.execメソッドに与えてみないと

文法上正しいだけの正規表現になってしまうので、

【RegExp.exec(サンプル)をループ実行】ボタン

whie(true) {
  if({正規表現パターン}.exec({サンプル}) {
    #結果に出力を追記
  } else {
    break;
  }
  {無限ループチェック}
}

を追加した。

空の正規表現パターンを無限ループ内のexecメソッドに与えると空振りし続け

CPUファンが最大回転数になる上にブラウザが「メモリが~メッセージ」を出すので

空回りしない様にしてる。(つもり

名前付きキャプチャグループ名をCSSの色名にすれば、replaceメソッドに正規表現とリプレッサーを組み込めば、簡単にテキストの色付けができる様になってた。(笑

※色指定方法:キャプチャーグループ名_文字色_背景色と(_)で区切る。

前回の苦労は何だったのかな?(只の徒労だな(昔は大変だったよね?(笑って忘れよう

【名前付きキャプチャグループ名で色付け】ボタン

{サンプル} .replace ( /{正規表現パターン}/{オプション} , {名前付きキャプチャグループ名で色付けする処理} )       

も追加した。

正規表現パターンを拡張しテキストの文字を全て色付けできたら「完成」なんだろうなぁ~

正規表現パターンが完璧なのにテキスト中に色指定無しがあれば、多分「シンタックスエラー」だから

replaceで「<span style=”color: xxx”>・・・</span>」部分を消去すれば、エラってる箇所を抽出できそうダケど、色指定をdarkgrayに差し替えるか色指定が無い部分に下線属性を付加した方が見つけやすそう。

正規表現の最後に何でもヒットするパターンに下線の名前を付ければ、

(?<blue>true)|(?<red>false)|(?<green>\d+)|(?<underline>.+)
で
aaaatruebbbbfalsecccc1234dddd
を色分けしてみると?

予想外の部分に下線が付くと思ったら、全部下線が付いてしまった(合掌

ま、気軽に進めよう(笑

以下、サンプルデータ。

(?<blue>true)|(?<red>false)|(?<green>\d+)
gi
aaaatruebbbbfalsecccc1234dddd

最後に画面が寂しいのでtitleとplaceholder属性を追記した。

今はtitle内容を改行して表示させるには\nと書いてもブラウザが”\n”と思ってしまうので、

テキストエディタ上で直にLFコードを入れる(Enterキー押下)しかなけど、気が付いたら\nって書けば改行してくれる様な気がする。

最近のテキストエディタは「Enterキー」入力をCR、LF、CRLFのいづれかに変更する機能を持つものが多いので何とかなるけどね。

ps.2025/4/16

画面はサンプルなデータを初期表示して試しやすく(具体的にはデバッグ)した。コードも少し変えてみた

結果にオブジェクトの内容を表示する処理を共通化してみた

JSONなオブジェクトでは十分だけど、

for(const propertyName in oobject)

クラスでは静的なプロパティしか見せたくないらしく出てこない。


/**
 * JSON.stringifyのreplacer
 * @param {*} key 
 * @param {*} value 
 */
const stringifyReplacer = (key, value) => {
  if (key !== '' && !value) {
    value = 'undefined';
  }
  return value;
};
/**
 * オブジェクトのプロパティを列挙
 * @param {object} object オブジェクト
 */
const objectPropertyStringify = (object) => {
  let ar = {};
  // for(..in object)
  for (const id in object) { ar[id] = object[id]; }
  // Object.keys(object)
  const keys = Object.keys(object);
  keys.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(object)
  const props = Object.getOwnPropertyNames(object);
  props.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(obj.__proto__)
  const prototypeProps = Object.getOwnPropertyNames(object.__proto__);
  prototypeProps.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // 纏め
  const txts = [];
  for (const id in ar) {
    // typeof value !== 'function' で メソッドを排除
    if (typeof object[id] !== 'function') {
      txts.push(`${id}:${textColor(ar[id])}`);
    }
  }
  //
  return `<ul><li>${txts.join('</li><li>')}</li></ul>`.replace('<li></li>', '');
};

アレレ?配色が効かない!

/**
 * JSON.stringifyのreplacer
 * @param {*} key 
 * @param {*} value 
 */
const stringifyReplacer = (key, value) => {
  if (key !== '' && !value) {
    value = 'undefined';
  }
  return value;
};
/**
 * オブジェクトのプロパティを列挙
 * @param {object} object オブジェクト
 */
const objectPropertyStringify = (object) => {
  let ar = {};
  // for(..in object)
  for (const id in object) { ar[id] = object[id]; }
  // Object.keys(object)
  const keys = Object.keys(object);
  keys.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(object)
  const props = Object.getOwnPropertyNames(object);
  props.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // Object.getOwnPropertyNames(obj.__proto__)
  const prototypeProps = Object.getOwnPropertyNames(object.__proto__);
  prototypeProps.forEach((id) => { ar[id] = JSON.stringify(object[id], stringifyReplacer, 0); });
  // 纏め
  const txts = [];
  for (const id in ar) {
    // typeof value !== 'function' で メソッドを排除
    if (typeof object[id] !== 'function') {
      txts.push(`${id}:${textColor(ar[id])}`);
    }
  }
  //
  return `<ul><li>${txts.join('</li><li>')}</li></ul>`.replace('<li></li>', '');
};

にしている。

後、文法解析用の字句解析にReExpの(…)|(…)|…|(…)な書き方用【↓組み合わせ】機能も追加。

ちゃんと字句解析できてるかと思いきや「初期色」が読み飛ばされていた。

キャプチャー名が名前以外の場合は適当に色を割り当ててます。(今は2色を交互に使ってる。

※2025/4/17 : 配色が赤・緑・赤・赤・緑・赤・赤・緑と変なので修正、空白にも配色してたので修正。読飛ばされた箇所は背景色を黒にしてみた。早速「:」忘れてたのに気が付く。

長いログを見ても判りにくいのでexecを実行させる際に「macthしたテキストのみ」結果に出るチェックボックスを追加してみた。

※2025/4/17 : 名無しキャプチャーグループのみの正規表現でも表示する様に修正

なるほど、BINOP単項演算子に見えたか

完成までの道程はまだ遠い

※適当なソースを貼って遊ぶとオモシロイかもしれないけど、ページを「保存」してローカルで遊びましょう。

/* ・・・ */の複数行コメントの字句解析が思わしくない。

  • Windowsから貼ると改行がCRLFになると正規表現のmオプションが暴走しやすい
    • 仕方がないからCRLFはLFに置換
  • /* コメント1 */ コード /* コメント2 */」と複数のコメントがあると
    • /* コメント1 */ コード /* コメント2 */」と一括りにしてしまう
  • /* コメント1 / コード / コメント2 /コード / コメント3 /」と複数のコメントがあると
    • / コメント1 / コード / コメント2 /コード / コメント3 */」と一括りにしてしまう
  • パターンを先読みアサーション((?=))、([\s\S]*)、後読みアサーション((?<=))
    • (?=ここから/*) ([\s\S]*) (?=*/ここまで)」も
    • ここから/* * */ここまで * ここから/* * */ここまで」と一括りにしてしまう
  • 初期表示のサンプルは/* … */は1つだけなので2度コピペすると↑を際限できます
  • とりあえず良さげなパターンが見つかった。
    • COMMENT: (/{2}.$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)
      • \/\*:つまり / *
      • [ \ s | \S ]* を使うと 最後に見つけた * / までブチ抜けくので
      • \/?([^/]|[^*]\/|\r|\n)* で頑張る
        • 先頭の\/?:/*直後に/があった場合のお守り
        • ([^/]|[^*]\/|\r|\n)*: */ 以外のパターンのつもり
          • ( ) * で中のパターンを0回以上繰り返し
            • [^/] | [^*] \/ | \r | \n
            • 非 / or 非 * and / or CR or LF  であるから
          • 非 / 非 * and / 改行のパターンを0回以上繰り返すパターンの意味
      • \*\/:つまり * /
    • CR: ([\r|\n]+)
    • 記事にペーストなんか違う?勝手に斜体になってる???
    • まさかWordPressの記事入力のエスケープシーケンスまで絡んでくるとは思わなんだ

結果に改行がそのまま出力してたので<br/>に変えた。

ついでに空白も&nbsp;や&emsp;に変えたらなぜか色設定が消えた!(あ、タグの中に空白がいっぱい

font-familyの設定もイジることにした見やすくなった。(結果オーライ

ps,2025/4/18

’ ・・・ ’の字句解析が思わしくない。

  • \’ ・・・・ ‘ も判定してしまう。
    • LITERAL:    (‘[^’]*’) では不完全らしいから
      • WQLITERAL:  (“[^”]*”) や QLITERAL:   (‘[^’]*’) もダメだろう。
  • でも普通はいきなり \’ なんて出てこない

コードの中の正規表現の後がズタズタだった・・・(涙

spreadSheetを作ってた時もBNF記述でハマってた。パーサーコンビネーターで作ってたから厳密な字句解析をすっ飛ばして token === ‘/.*/{flags}’ でいいや的な誤魔化しができた。

正規表現でコードハイライトのラスボスは正規表現

嫌すぎ

ps.2025/4/18:多少改善できたので更新。

TODO:/\\/な正規表現が苦手で、以下ボロボロになる。

text = text
  .replaceAll(/\\/g, '\\\\') 👈今この辺で滞っている
  .replaceAll(/\"/g, '\\"')
  ;
copyClipboardText(`new RegExp("${text}", "${flags}");`);

やっと片付いた(かもしれない

COMMENT:    (\/{2}.*$|\\/\\*\\/?([^/]|[^*]\\/|\\r|\\n)*\\*\\/)
CR:         ([\\r|\\n]+)
SPACE:      ([\\s]+)
BQLITERAL:  (\`(\\\\\\\\|\\\\\`|[^\`]|\\r|\\n)*\`)
LITERAL:    (\'(\\\\\\\\|\\\\\'|[^'])*\')
WQLITERAL:  ("(\\\\\\\\|\\\\"|[^"])*")
REGEXP:     (\\/(\\\\\\\\|\\\\/|[^\\/])*\\/[a-z]*)
REGION_B:   (s*#regions+[a-zA-Z0-9]+.*$)
REGION_E:   (s*#endregion$)
L_BRACE:    ({)
R_BRACE:    (})
L_PAREN:    (\\()
R_PAREN:    (\\))
L_BRACKET:  (\\[)
R_BRACKET:  (\\])
COMMMA:     (,)
COLON:      (:)
DOT:        (\\.)
BINOP:      (\\|\\||&&|===|!==|!=|==|\\+|<|>|\\?|<=|>=|-|\\*|\\/|%|=|!)
DELIMITER:  (;)
NULL:       (null)
VALUE_TYPE: (void|integer|bool|string)
SCOPE:      (public|private|friend|static)
THIS:       (this)
NEW:        (new)
BOOL:       (true|false)
SYMBOLE:    ([_a-zA-Z0-9]+)
NUMBER:     ([1-9][0-9]*[.]?[0-9]*)
※オプションは「gim」指定時のみ有効。

から【↓組合せ】ボタンで論理和で結合したパターンを貼り付ける。

このパターンとオプションからVScodeに貼ると、自動的にフォーマッタが働きグチャグチャになるので

"・・・"

とペーストする部分をクリックした後にそ~っとペーストしなければいけない。

※【コピー】ボタンでエスケープ処理済みのテキスト(↓参照)をクリップボードにコピーさせてからファイルにペースト。

const re = new RegExp(
  "/(?<COMMENT>\\/{2}.*$|\\/\\*\\/?([^/]|[^*]\\/|\\r|\\n)*\\*\\/)|(?<CR>[\\r|\\n]+)|(?<SPACE>[\\s]+)|(?<BQLITERAL>`(\\\\\\\\|\\\\`|[^`]|\\r|\\n)*`)|(?<LITERAL>'(\\\\\\\\|\\\\'|[^'])*')|(?<WQLITERAL>\"(\\\\\\\\|\\\\\"|[^\"])*\")|(?<REGEXP>\\/(\\\\\\\\|\\\\\\/|[^\\/])*\\/[a-z]*)|(?<REGION_B>s*#regions+[a-zA-Z0-9]+.*$)|(?<REGION_E>s*#endregion$)|(?<L_BRACE>{)|(?<R_BRACE>})|(?<L_PAREN>\\()|(?<R_PAREN>\\))|(?<L_BRACKET>\\[)|(?<R_BRACKET>\\])|(?<COMMMA>,)|(?<COLON>:)|(?<DOT>\\.)|(?<BINOP>\\|\\||&&|===|!==|!=|==|\\+|<|>|\\?|<=|>=|-|\\*|\\/|%|=|!)|(?<DELIMITER>;)|(?<NULL>null)|(?<VALUE_TYPE>void|integer|bool|string)|(?<SCOPE>public|private|friend|static)|(?<THIS>this)|(?<NEW>new)|(?<BOOL>true|false)|(?<SYMBOLE>[_a-zA-Z0-9]+)|(?<NUMBER>[1-9][0-9]*[.]?[0-9]*)",
  "gim");

な感じなら、使用しているjavascriptファイルは大体OK(だと思う

リテラル系の「 \ \ \ \ \ \ \ \ 」:「\」8キャラが酷い。

  • javascriptファイル上の「 \ \ \ \ \ \ \ \ 」:「\」8キャラ
    • をjavascriptエンジンが読み込んで
    • 「\\」を「\」に変換するので
    • \ \ \ \ 」:「\」4キャラのデータになる
      • これを渡されたRegExpクラスも
      • 「\\」を「\」に変換するので
      • \ \ 」:「\」2キャラのデータになる。

なのでRegExpに「 \ 」を2キャラ渡すために

javascriptのソースコードには「 \ \ \ \ \ \ \ \ 」と8キャラ書くことになる

なんてこったい。(知ってたけど

ついでにココ(段落)に貼るとWordPressのエスケープシーケンスが即反応するので、専用のプラグインに貼る方がいい。

ps.2025/4/19:少し調整

・テキストエリアでタブキーを入力できる様にした

・正規表現のキャプチャーグループ名に文字色と背景色を指定できるようした

COMMENT_RED_000000 な感じに書くと

色付け処理で、

グループ名を(_)で区切って「グループ名_文字色_背景色」な感じで色付けできる。

色番号の「#」はキャプチャーグループ名に使えないので「#」を除いた16進表記のみ書く。

これで、ちょっと「(」が読みにくいので色を変えるとかが簡単になった。

ps.2025/4/23

XMLのツリービュー表示で使う正規表現のデバッグをしていたら

正規表現
(?<attr_name>[-:a-z]+)(?<attr_sep>=)(?<attr_value>"[^"]*")|(?<attr_name>[-:a-z]+)|(?<SP>[\t\s\r\n]+)
サンプル
<style:style style:family="paragraph" style:name="a213">

一度のexec実行で複数のグループがヒットする正規表現の場合には・・・

[attr_name, attr_sep, attr_value] : '{attr_nameの抽出結果のみ}'

と最初の抽出結果しか表示してないコトに気付いたので

[キャプチャ1名]:'{キャプチャ1抽出結果}'、・・・[キャプチャn名]:'{キャプチャn抽出結果}'

の様な感じに変更した。

隣の「色付け」ボタンを押すとキャプチャ対象外の文字は赤で背景色が黒で表示するので、

あ、「<」と「>」を無視してるじゃん!

とか判りやすいと思う。



[javascript]コードは短い方が良い(かもしれない

const divMatchPattern = document.getElementById('matchPattern');
const txtMatchPattern = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
divMatchPattern.value = txtMatchPattern.toString();

というコードがあって、

id=matchPatternなオブジェクトが無い場合があるので

const divMatchPattern = document.getElementById('matchPattern'); if(divMatchPattern){
const txtMatchPattern = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
divMatchPattern.value = txtMatchPattern.toString(); }

とすると読みにくいから「保存(Ctrl+S)」すると

const divMatchPattern = document.getElementById('matchPattern');
if(divMatchPattern){
    const txtMatchPattern = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
    divMatchPattern.value = txtMatchPattern.toString();
}

となって長くなるから1行でも短くしようと捻って

for (const divMatchPattern of document.querySelectorAll('#matchPattern')) {
  const txtMatchPattern = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
  divMatchPattern.value = txtMatchPattern.toString();
}

とか

document.querySelectorAll('#matchPattern').forEach((divMatchPattern) => {
  const txtMatchPattern = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
  divMatchPattern.value = txtMatchPattern.toString();
});

とか

try {
  document.querySelector('#matchPattern').value = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";
} catch() {}; ※とミスっても原因が~になるパターン

とか最後には

document.querySelector('#matchPattern') {左辺がnullじゃなければ右辺も続けて評価する演算子} .value = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";

があったらいいなぁとか思う。

Null合体演算子(??)が近いけど、基本は || なので

const 結論=(Aプラン) ?? (Bプラン) ?? (冗談ではない)

的に

const ストーリィ展開=(なんだかわかないけど) ?? (なんかわかった) ?? もうどうなってもいいや

の様に使うものなので、

document.querySelector('#matchPattern') ?? .value = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";

は期待通りには動かない。

「.」を「.$.」にすると左辺がnullだったらもうどうなってもいいやドット演算子があったらいいな

document.querySelector('#matchPattern').$.value = "/(\\/\\*[\\s\\S]*?\\*\\/|\\/{2,}・・・/g\n";

一応、try ・・・ catchで包まれていたらthrowするけど

try {
   document.querySelector('#matchPattern').$.value = "/(\\/・・・"; ※中で throw(undefined)する
   ※Throw.resume()で、ここに戻る
} catch (ex, stack) {
  if(ex) console.log(ex) ※ ex が undefinedなので
  else stack.resume();   ※ throwした直後に戻る。
                         ※ 状況は stack.stackを読む
                         ※  行番号とかカラムとかnullな処理箇所が判ると助かる
}

だといいな。

あとドット演算子(.)とアロー演算子(->)の違いの記事を読んだ感想。

昔のC言語では、メモリがとっても少なかったので作りがとても簡素で

簡素な構文解析に落とし込まないとメモリに入らない

というゲーム制作みたいな理由。

変数は基本的にアドレス+オフセット+サイズとして考えるポインタ変数でプリミティブな型変数はオフセットが0でサイズがプリセットで決まっているポインタ変数。

このため、構造体のドット演算子もポインタ変数のアロー演算子も「この変数の構造(オフセット)を見てオフセットを計算してね」という意味では同じだから一緒で良かったハズ。

しかし、基本な皆中身がポインタ変数なので、内部構造であるハズのポインタ変数を明示的に使われるとコンパイラは

  • 普通の変数は梱包済み
  • ポインタ変数は開梱済み

を切り分けて処理するのも面倒なので

※2通りの変数の種別があると毎度毎度似た様な処理が4通り必要になるのでウザい

仕方なく演算子(という表現上で人が指示する様に)を分けた様に思える。

昔のソースって概ね一本の長さが80列×25行程度に収まってたのもあるけどね。



[javascript]spreadSheet5 ネームスペース

自前コードなので、関数やクラスをネームスペースにバラ巻いていた。

ただ、このままでは、再利用する度に、困りそうなので、カスタムタグのソースのクラスなどをMYAPPに纏めてみた。

まず、ネームスペースをまとめる関数を追加。※ネットで見つけたコードを少し改良したもの

/**
 *  MYAPP   グローバルオブジェクトのアプリ名
 */
const MYAPP = {};   //  初期設定

/**
 *  指定ネームスペースを作成する
 * @param {string} pNameSpace       作成するネームスペース・パス
 * @param {undefined | JSON} pJson  追記する内容 undefined または JSON
 * @param {boolean} pFreeze         変更不可指定
 */
MYAPP.namespace = (pNameSpace, pJson = undefined, pFreeze = false) => {
    /**
     * 再帰的にオブジェクトを凍結する
     * @param {object} obj  凍結するオブジェクト
     */
    const freezeR = (obj) => {
        if (typeof obj === 'object') {
            // オブジェクトの配下を再帰的に凍結
            for (const key of Object.keys(obj)) {
                freezeR(obj[key]);
            }
            // オブジェクト自体を凍結
            Object.freeze(obj);
        }
    };
    // ネームスペースのテキストを .で区切った配列を作成する。※先頭がMYAPPなら削除する
    const aNameSpace = pNameSpace.split('.').filter((element, index) => index !== 0 || element !== 'MYAPP');
    //  現在のパスをMYAPPへ移動する
    let curPath = MYAPP;
    //  パス最後の要素の有無で処理が分岐する為、配列から最後の要素を分離する。※constでもpopはオブジェクトを差替(代入)えないのでOK(らしい
    const lastElement = aNameSpace.pop();
    //  配列の順にネームスペースの有無をチェックする
    for (const part of aNameSpace) {
        // パスが存在しなければ作成し、パス位置を現在位置へ移動する
        curPath = curPath[part] = curPath[part] ?? {};
    }
    // パス最後の要素が未設定なら空リストを代入
    if (curPath[lastElement]) {
        // pJsonをマージする
        Object.assign(curPath[lastElement], pJson ?? {});
    } else {
        // pJsonに差替える
        curPath[lastElement] = pJson ?? {};
    }
    //  編集不可指定ありの場合
    if (pFreeze) {
        //  作成したネームスペースを再帰的に凍結します
        if (!pJson) {
            freezeR(curPath[lastElement]);
        } else {
            //  JSON配下のオブジェクトを再帰的に凍結します
            for (const key of Object.keys(pJson)) {
                freezeR(curPath[lastElement][key]);
            }
        }
    }
};

aNameSpaceからそのままfor (const part of aNameSpace)を回すと、curPathが最後まで移動し、pJsonをネームスペースに追加しようとすると、

 curPath = pJson ?? {} ;                            // MYAPPの方は書き換わらない

となってしまい、MYAPPの方は書き換わらないので、最終の一歩手前でcurPathを止めて、

curPath[lastElement] = pJson ?? {};                 // curPath[lastElement]はMYAPPの一部なのでOK

としたかったので、aNameSpaceから最後の要素を切り取った。

また、Object.assignの第一パラメータの中身がundefinedの場合は、エラってしまうので、

try {
  let obj = undefined;
  Object.assign(obj, pJson ?? {}); 
} catch(ex) {
  debug.print('Fail. '+ex);
}

⇒ Fail. Uncaught TypeError TypeError: Cannot convert undefined or null to object

undefined判定で空リストに変えてマージ。

    if (!curPath[lastElement]) {
        curPath[lastElement] = {};                      // 空リストにする
    }
    Object.assign(curPath[lastElement], pJson ?? {});   // マージする

さすがに

Object.assign(curPath[lastElement]??{}, pJson);   // マージする

は無理。

また、参照先が同じJSONにある場合は未確定な状態になりやすいので、MYAPP.namespaceの呼び出しを参照元と参照先で分けるのも面倒なので、第一パラメータで非参照先のネームパスを作成した方が楽。

これで完了と思ったら( ^ω^)・・・

MYAPP.namespace('aaa.bbb.ccc',
   (p1, p2, p3) => {
     ...
   }
);

aaa.bbb.ccc(1,2,3);

Uncaught TypeError TypeError: aaa.bbb.ccc is not a function

Object.assignは関数をプロパティっぽく書き込み、参照時に未定義な関数のエラーになるので、

    // パス最後の要素が未設定なら空リストを代入
    if (curPath[lastElement]) {
        // pJsonをマージする
        Object.assign(curPath[lastElement], pJson ?? {});
    } else {
        // pJsonに差替える
        curPath[lastElement] = pJson ?? {};
    }

で落ち着いた。

※まずpJsonがJSONかどうか判定した後の処理にした方が意図が読み取りやすいけど、深く意味を読み取ると無駄だと判るコードになる。

元ネタでは、

/**
 * スプレッドシートクラス
 * @class MYAPP.spreadSheet.SpreadSheetCustomElement
 */
MYAPP.namespace('spreadSheet.SpreadSheetCustomElement'),
MYAPP.spreadSheet.SpreadSheetCustomElement = class extends HTMLElement {
    ...
};

と、ネームスペースを作成し、作成したネームスペースにクラス等を設定する感じになっていた。

これを実際に動かしてみると、クラスの#変数がエラるw(パラメータがクラス宣言部なせいだろう)し、ネームスペースの文字も2度書いてるw(被ってるのは嫌い)なので、ネームスペースはファイルのパスまでとし、クラス名等はJSON形式でキー指定した方に変更して、クラスの定義は、こんな感じになった。

/**
 * スプレッドシートクラス
 * @class MYAPP.spreadSheet.SpreadSheetCustomElement
 */
MYAPP.namespace('spreadSheet', {
  'SpreadSheetCustomElement': class extends HTMLElement {
    ...
  }
});

ゴチャ付いてないから見やすくなった。(と思う

クラスを使用する場合はグローバルにクラスを宣言してないので、フルパス名で指定する。

_spreadSheet._command = new MYAPP.spreadSheet.SpreadSheetCommand(this);

関数も同様で、無名関数「function(…) { } 」でもいい。

MYAPP.namespace('lib', {
  'createHtmlElement': (elementName, options) => {
    ...
    },
});

使う時はフルパス名で指定する。

_layout.divTableN = MYAPP.lib.createHtmlElement('div', ... );

オブジェクトのプロパティはObject.freeze(object)で変更不可にできる。

メソッド名プロパティの追加プロパティの削除プロパティの値の変更
Object.preventExtensions()
Object.seal()
Object.freeze()

Object.freeze(object)以外は値の変更ができるので今回は対象外。

MYAPP.namespace('lib', {
  'char': {
    BR:'<br/>',
    CR: '\n',
    TAB: '\t',
    },
},true);             // 変更不可を指示

関数やクラスの外では意図的にstrictモードにしないとエラー(TypeError)が起きない。

ちなみに

"use strict";    // strictモード全開!

//  const 変数
MYAPP.namespace('lib', {
    'char': {
        BR: '<br/>',
        CR: '\n',
        TAB: '\t',
    },
}, true);
MYAPP.lib.char.BR = 'aaaaa'; ☚ここでType Error

Uncaught TypeError TypeError: Cannot assign to read only property 'BR' of object '#<Object>'

修正した結果

デバッグして止めて、スクリプト・スコープの内容を見ると、修正が漏れ過ぎ( ´∀` )

やっとのことでMYAPPにまとまった。

ネームスペースはすっきりした。

しかし、こんなコードを見た試しが無い。(大笑

パス名が付くのでコードが長くなる上、VSCodeの自動でコメントを生成するプラグインが怪しい動きをする可能性があるが、このクラス、変数はどこ?と探すのは楽かもしれない。

ps.2024/10/8

アップロード処理でvalueの無いセルデータでエラっていたので修正



[javascript]spreadSheet5

長らく放置していたセル幅高編集機能をspreadSheet5として実装した。

spreadSheet4はさっぱり進んでいないマクロ機能の実装用に残した。

操作方法:セルの境界線上にマウスポインタを乗せるとカーソルがリサイズっぽく変わりドラッグするとドットラインも表示するので好みの位置でリリースし確定する。

データ保持:セル幅やセル高を変えるとindex.html内のspread-sheetタグのdatabasename属性で指定したデータベースのcellテーブルに列ヘッダー(r0cNNN)または行ヘッダー(rNNNc0)のセルに変更内容(style:{width:’NNNpx’あるいはheight:’NNNpx’})が保持される。

確認方法:変更内容はCtrl+Sでデータをダウンロードすることを確認でき、更に変更を加えspreadSheet上にドロップすることで結果を確認できる。

永続性:再表示(F5)時も変更内容を参照し画面に反映される。

TODO1:セルのHTMLエレメントを作成する部分が初期のセル幅やセル高を前提に作ってるので、セルを初期より狭くするとセルがうまく表示されない。カーソルキーでセル移動すると補正されるケースがある。実装を見直し中。

TODO2:セル内テキストがセル幅を越える場合は…付きの表示になるが、セル高を複数行分まで広げてもセル内テキストは改行しない。canvas.measureTextを使って適切な行数を計測できそうだけど、複数行表示で自動的に省略文字(…)を書きたい場合に困り、思案中。

TODO3:初期のセル幅やセル高に戻すUIは無いので下限値が16pxになっているが、r0c{セル位置}、r{行位置}c0のデータを削除することで初期に戻すことは可能だから、コンテキストメニュー実装時に考慮。

TODO4:セル境界線上でダブルクリックすると、良い感じのセル幅に変更するUIも実装したいが、まずタグにテキストを埋め込んで実測すると遅いのは明白で、canvasで描画して調べるしかなさそうだ。

const context = document.getElementById("canvas").getContext("2d");
/* 以下必要な分繰り返す*/
context.font = "48px serif";  /* な感じでセルのテキスト属性を指定 */
const text = '適当なテキスト'; /* な感じでセルのテキストを指定 */
const textMetrics = context.measureText(text);
let width = textMetrics.width;
let height = textMetrics.height;

これを画面で見える範囲で繰り返すなら処理時間も短かくて済そう。

しかし、測定値を保持するとページスクロールとダブルクリックを最終セルまで繰り返さないとドコかで不都合な場合もありそうなので、’best-value’:「適切な測定値」とかで保持するといいかな?とか思案中。

TODO5:ドラッグ中の値を表示してない。

TODO6:ドットラインの初期位置が現在のセル幅の位置ではなくカーソルの位置になっている。FIX9/30

予想通り色々と問題が発覚しすぎ。ここまで尾を引くなら、セル描画はcanvasに任せた方が良いかも。HitTestの実装もセル単位で判れば良いし。

ps.2024/9/30 ラバーバンドの位置を調整。横スクロールのY軸調整がうまくいかないので外す。セルサイズの小数部を切り捨て(52.3px⇒52px)。画面構成の計算は単位がpxのみ対処。他単位を指定した場合は×。コードを見返してみると長いし複雑、もっと手短にしたい。

ps.2024/10/8

アップロード処理でvalueの無いセルデータでエラっていたので修正



[javascript]varとlet

再宣言の差異は

再宣言可なvarと再宣言不可のlet。

どっちがいいのか微妙だけど、外部変数の宣言の場合には・・・

概ね宣言が被る方が困るので、letにしておこう。

尚、スコープの差異は

ブロックスコープなletの方が関数スコープのvarより使いやすい。

結論、varは要らない。



[JavaScript]配列のkeyとvalueの両方を使う

const createHtmlElement = (elementName, options) => {
・・・
・・・
  const rc = Object.keys(options).map(
    (key) => (chkOriginal(key) || chkStyle(key) || chkEvent(key) || setAttribute(key)));
  );
}

と書くと、呼び出された各処理で

function chkOriginal(key) {
  const value = options[key];
  ...
}
function chkStyle(key) {
  const value = options[key];
  ...
}
function chkEvent(key) {
  const value = options[key];
  ...
}
function setAttribute(key) {
  const value = options[key];
  ...
}

と書いてたけど・・・

const createHtmlElement = (elementName, options) => {
・・・
const chkOriginal = (key,value) => {
  ...
}
const chkStyle = (key,value) => {
  ...
}
const chkEvent = (key,value) => {
  ...
}
const setAttribute = (key,value) => {
  ...
}

・・・
  const rc = Object.keys(options).map(
    const value = options[key];
    (key) => (chkOriginal(key,value) || chkStyle(key,value) || chkEvent(key,value) || setAttribute(key,value)));
  );
}

としてみた。自分で書いたコードから、ローカル関数で元関数のパラメータを見ても特に支障は無いけど、functionを使わない書き方で統一できた。

MDNを見ると、mapでも第2パラメータ(thisArgs)が使えるみたいなので、

const rc = Object.keys(options).map(function (key) {
    const value = this[key];
    return chkOriginal(key, value) || chkStyle(key, value) || chkEvent(key, value) || setAttribute(key, value);
}, options);

としてみた。

本来はコールバックに特定のオブジェクトをthisとして引き渡したい場合に使うものだし、コールバックの中でoptionsを書きたくないと意地を張ったものの、optionsを2回書いてしまう事に変わりは無いので、無駄なthisArgsの使い方になってしまった。外部の変数を見てる怪しい部分も修正したら結果がコレ。

/**
 * HTMLエレメントを作成する
 * @param {string} elementName                              エレメント名
 * @param {{attributeName:,... eventName:,...}}} options    設定内容
 * @returns {HTMLElement}                                   作成したHTMLElement
 */
const createHtmlElement = (elementName, options) => {
    const fileName = 'js/lib/createHtmlElement.js';
    const funcName = 'createHtmlElement'; //debugLog(`${fileName}:${funcName}(elementName: ${elementName}, options)`);
    /**
     * 独自の処理が必要な場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkOriginal = (param) => {
        let rc = false;
        try {
            switch (param.name) {
                case 'className': // classNameはメソッドで、\x20で文字列を区切って複数の属性を指定できる
                case 'classList': // classListはメソッド
                    let v = param.value;
                    if (!Array.isArray(v)) { v = v.split('\x20'); }
                    param.element.classList.add(...v);  // 配列を展開してクラスリストに追加する
                    rc = true;
                    break;
                case 'innerHTML': // innerHTMLはメソッド扱い    ※未使用
                    param.element.innerHTML = param.value;
                    rc = true;
                    break;
                case 'innerText': // innerTextはメソッド扱い
                    param.element.innerText = param.value;
                    rc = true;
                    break;
                case 'parent':      // parentはメソッド
                    param.value.appendChild(param.element);
                    rc = true;
                    break;
                case 'appendChild': // appendChildはメソッド    ※未使用
                    param.element.appendChild(param.value);
                    rc = true;
                    break;
                case 'insertBefore':    // insertBeforeはメソッド   ※スクロール処理で座標の小さい方向へセルを作る際に使用
                    param.value.parentNode.insertBefore(param.element, param.value);
                    rc = true;
                    break;
            }   // end of switch
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'属性の固有処理に失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    };
    /**
     * スタイル属性の場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkStyle = (param) => {
        let rc;
        try {
            //  style-xxxxのキーの場合
            let styleChk = param.name.match(/^style_(.*)/);
            if (styleChk) {
                let styleName = styleChk[1];
                switch (styleName) {
                    case 'width':
                        param.element.style.width = param.value;
                        break;
                    case 'height':
                        param.element.style.height = param.value;
                        break;
                    default:
                        styleChk = null;
                        break;
                }   // end of switch
            }
            rc = styleChk !== null;
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'スタイル属性に${param.value}を設定失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    }
    /**
     * eventの場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkEvent = (param) => {
        let rc;
        try {
            const eventList = ['ended'];    // イベントリスト
            rc = eventList.includes(param.name);   // イベントリストに含まれているかどうか
            if (rc) {   // イベントリスナを登録する
                param.element.addEventListener(param.name, param.value);
            }
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'イベントリスナの登録に失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    }
    /**
     * HTMLエレメントに属性を追加する
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const setAttribute = (param) => {
        try {
            param.element.setAttribute(param.name, param.value);
            return true;
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'属性に${param.value}を設定失敗 '${ex}'`);
            return false;
        }
    }
    let elm = document.createElement(elementName);
    //  optionsの設定名を取得しぶん回す!
    const rc = Object.keys(options).map(function (key) {
        const value = this[key];
        const param = { 'name': key, 'value': value, 'element': elm };
        return chkOriginal(param) || chkStyle(param) || chkEvent(param) || setAttribute(param);
    }, options)
        .filter(v => !v);   // trueならば削除する
    if (0 < rc.length) {
        debugLog(`${funcName}(elementName: ${elementName}) 未処理属性数:${rc.length}件`);
    }
    return elm;
}

数か月経つと

※未使用

と書かないと、「どこかで使っている(気がする」し、

※スクロール処理で座標の小さい方向へセルを作る際に使用

とか書いておかないと、「要らない(気がする」する、

なぜか、逆バイアスがかかってしまうのは・・・謎。

あ、使い方も忘れた。

こんな感じだった。

let a = createHtmlElement('a', {
    href: url,
    download: fileName,
    text: "re-download",
});

色々設定したいHTMLエレメントを作る時に

const div = createElement('div');
shadow.addChiled(div);
div.className = 'scroll';
div.id = `div${sn}`;
div.style.height = _spreadSheet.width;
div.style.width = _spreadSheet.height;
div.tabIndex =  `${sn}`;

を1行で書ける。更にそんなモノがいっぱいある場合には、

//  シャドールートを作成
_layout.shadowRoot = (!_layout.shadowRoot) ? _spreadSheet.customElement.attachShadow({ mode: "open" }) : _layout.shadowRoot;
const shadow = _layout.shadowRoot;
//  既存のHTMLElementを削除
Array.from(_layout.shadowRoot.children).forEach((ch) => ch.remove());
//  スタイルシート部
const style = _layout.createStyle(sn, _spreadSheet.rows, _spreadSheet.cols, true, true);
shadow.appendChild(style);
//  スプレッドシート部
_layout.divTableN = createHtmlElement('div', { parent: shadow, className: 'scroll', id: `div${sn}`, style_height: _spreadSheet.width, style_width: _spreadSheet.height, tabIndex: `${sn}`, });
_layout.tableN = await _layout.createTableElement(_layout.divTableN, 'spread-sheet', `table${sn}`);
_layout.divScrollX = createHtmlElement('div', { parent: _layout.divTableN, className: 'scroll-x', id: `divScrollX${sn}`, style_height: '17px', style_width: _spreadSheet.height, tabIndex: `${sn}`, });
_layout.divScrollY = createHtmlElement('div', { parent: _layout.divTableN, className: 'scroll-y', id: `divScrollY${sn}`, style_height: _spreadSheet.width, style_width: '17px', tabIndex: `${sn}`, });

と書け、すっきりする。

もっとも、インターネットの記事でそんなケースはまず有り得ないし、必要に応じてcreateHtmlElementの機能を増設していかないと「すっきり」しないので、仕事で作るソースにも使ったことはないね。

最初はcreateDivElementとかcreateAElementとかタグ毎に作っていたけど多すぎるので、createTableElementを除いて一本化した。

createTableElementみたいに複雑で同期を取る必要もあるモノは別物にするしかないが、コード全般の流れを単純化するとスッキリした。勿論、createTableElementの中のアチコチでcreateHtmlElementを呼び出している。

今見ると、イベント登録まで入れたのはやり過ぎ感があるなぁ。



[javascript]spreadSheet4 コードのオブジェクト化

テキストを構文解析したパースした結果のオブジェクトはindexedDBに保存してあるけど、オブジェクトをテキストに戻したり、計算させるコードはJavaScriptのままだったので、中途半端だった。

あまり複雑なコトは出来なくていいので、オブジェクトをstringifyしたりcalulateしたりexecuteできるオブジェクトを考えてparserオブジェクトストアに保存すればいいかな?

そう考えると、まず俺々EBNFパーサで試してみるのが良さそうだ。

  • パースするEBNFなテキストを用意する
  • parserオブジェクトストアから俺々EBNFパーサを読みだす
  • パース部分とパース結果を手直す部分に分離
  • パース部分からdefinitionっぽいパーサ・コンビネーションを作る
  • パース結果を手直す部分から実行コードを作る
  • parseメソッドに先のテキストを読ませる
  • 何かの構文解析パーサができる

な感じだろうか

とりあえずはmakeParserメソッドを「パースした結果のオブジェクト」っぽいオブジェクトに展開。

するところから始め、stringify、calculateも「パースした結果のオブジェクト」っぽいオブジェクトに展開してみよう。

多分、

{ function_definiton: { name: x..., procedure:[.....] } }
{ return: {expr:... } }
{ if:{expr:... }, then:{...}, else:{...} }
{ while:{expr }, procedure:[.....]},
  {continue:null},
  {break:null},
{ function:{name:x..., parameter:{... } } }
{ expr:[x..., '+', x..., ...] }
{ value:{ type;integer, value:1 } }
{ set:{ name:x... }, value:{ expr or value:.... } }
{ try:[...], catch:[...], finally:[...] }

とかになるんだろう。+-と/*演算子の優先度は{}のネストでカバーすればいいし。

{ export: x..., from y... }
{ import: x..., from y... }

も必要か、javascriptのコードの呼び出しは、事前に「{function_definiton」の内部に登録しておこう。

execObjectList.map((execObject) => {
  switch() {
    case 'function_definition':
        {
        }
    ....
  }
});

と、実行用オブジェクトを種類別に仕分けてコード化すれば何とかなるだろう。



「javascript」spreadSheet4 EBNFの「,」

やっとマクロ構文を組み込み始めたが・・・

デリミタではまった。

(*$ imports: expression="EXPR" $*)
(*$ SEQUENCE SEPARATOR IS REQUIRED $*)
(* マクロ *)
macro               = { ( class declaration | function declaration | variable declaration ) }, /\s*/ ;
class declaration   = "class", class name, "{", { member declaration | method declaration } "}" ;
class name          = identifier ;
comment             = "/*", /.*/, "*/" | "//", /.*/, "\n" ;
member declaration  = [ "const" | "static" ], member name, [ "=", expression], { ",", member name, [ "=", expression] } ";" ;
member name         = identifier ;
method declaration  = [ ? ebnf reference jdoc.document ? ], [ method name ], parameter list, procedure list ;
method name         = identifier ;
function declaration= [ ? ebnf reference jdoc.document ? ], [ function name ], parameter list, procedure list ;
function name       = identifier ;
parameter list      = "(", [ parameter name, [ "=", expression ], { ",", parameter name, [ "=", expression ] } ], ")" ;
parameter name      = identifier ;
procedure list      = "{", procedure, "}";
procedure           =    {
                         (
                         | variable declaration 
                         | assignment expression
                         | branch procedure
                         | iterative procedure
                         | exception procedure
                         )
                      } ;
variable declaration= [ "const" | "let" | "static" ], variable name, [ "=", expression], { ",", variable name, [ "=", expression] } ";" ;
variable name       = identifier ;
assignment expression= variable name, [ ".", member name ] "=" expression;
branch procedure    = if procedure | switch procedure ;
if procedure        = "if", "(", expression, ")", procedure list, { "else", if procedure } ;
switch procedure    = "switch", "{", { case procedure } "}" ;
case procedure      = ( "case", expression | "default" ), ":", { procedure }, [ "break" ] ;
iterative procedure = for procedure | while procedure | do while procedure ;
for procedure       = "for", "(", assignment expression, ",",  expression  ",",  assignment expression ")" ;
while procedure     = "while", "(", expression ")", procedure list ;
do while procedure  = "do", "(", expression ")", procedure list, "while", "(", expression ")" ;
exception procedure = "try", procedure list, "catch", "(", parameter name ")", procedure list, [ "finally", procedure list ]
                    |  "throw", expression ;
identifier          = { comment }, /[A-Za-z][A-Z_a-z]/, { comment } ;

どこが間違っているのかな?

(*$ import expr from "EXPR" $*)
(*$ SEQUENCE SEPARATOR IS REQUIRED $*)
(* マクロ *)
macro               = { class declaration | function declaration | variable declaration }, /\s*/ ;
class declaration   = "class", class name, "{", member declaration, { method declaration }, "}" ;
class name          = identifier ;
comment             = "/*", /.*/, "*/" | "//", /.*/, "\n" ;
member declaration  = [ "const" | "static" ], member name, [ "=", expression], { ",", member name, [ "=", expression] }, ";" ;
member name         = identifier ;
method declaration  = [ ? ebnf reference jdoc.document ? ], [ method name ], parameter list, procedure list ;
method name         = identifier ;
function declaration= [ ? ebnf reference jdoc.document ? ], [ function name ], parameter list, procedure list ;
function name       = identifier ;
parameter list      = "(", [ parameter name, [ "=", expression ], { ",", parameter name, [ "=", expression ] } ], ")" ;
parameter name      = identifier ;
procedure list      = "{", procedure, "}";
procedure           =    { variable declaration 
                         | assignment expression
                         | branch procedure
                         | iterative procedure
                         | exception procedure
                         } ;
variable declaration= [ "const" | "let" | "static" ], variable name, [ "=", expression], { ",", variable name, [ "=", expression] }, ";" ;
variable name       = identifier ;
assignment expression= variable name, [ ".", member name ],"=", expression;
branch procedure    = if procedure | switch procedure ;
if procedure        = "if", "(", expression, ")", procedure list, { "else", if procedure } ;
switch procedure    = "switch", "{", { case procedure }, "}" ;
case procedure      = ( "case", expression | "default" ), ":", { procedure }, [ "break" ] ;
iterative procedure = for procedure | while procedure | do while procedure ;
for procedure       = "for", "(", assignment expression, ",", expression, ",", assignment expression, ")" ;
while procedure     = "while", "(", expression, ")", procedure list ;
do while procedure  = "do", "(", expression, ")", procedure list, "while", "(", expression, ")" ;
expression          = expr ;
exception procedure = "try", procedure list, "catch", "(", parameter name, ")", procedure list, [ "finally", procedure list ]
                    |  "throw", expression ;
identifier          = { comment }, /[A-Za-z][A-Z_a-z]/, { comment } ;

違いが判るだろうか?

最初は [ … […] … ] なんて囲み文字のネストは考えて無かったw!とか慌てたけど、そんなことを気にする様な気難しい文法解析方法は今回実装していない。

正解は・・・

  • importが旧仕様
    • ☓ imports: expression=“EXPR”
    • 〇 import expr from “EXPR”
  • sequenceのデリミタ(カンマ)がところどころ抜けている
    • 「}」の後
      • { … } , “;”
        • 「}」の後にトークンがあるなら、カンマで区切る必要がある
    • 「”}”」や「”)”」の前
      • { … } の } の前にカンマは不要、 ( … ) なら ) の前にカンマは不要
      • “{” … “}” の “}” の前にカンマは必須、 “(” … “)” なら “)” の前にカンマは必須

いづれも独自の仕様と云えば独自なのかもしれないけどね。

ps.import の処理が間違っていたので修正。EbnfParserクラスのparseメソッドを実行すると、export、importの情報が履歴の様に残っていたので、parseメソッド内でクリアするように変えるのでシャローコピーを各パーサに引き渡す様にした。

ps.2024/4/25

マクロファイルのアップロード、パーサ、indexedDB処理までできた。後はパーサのMap処理と実行部。どうすればいいのかは・・・やってみないと判らない。(笑




top