変奏現実

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

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

javascript

[javascript]spread-sheetカスタムエレメント

spread-sheetカスタムエレメントってありそうで無いっぽいので作ってみた。

セルに文字入力できるダケのしろもの。

  • IMEがONになると動きがおかしい
  • 範囲指定ができない
  • 計算できない
    • 簡単な計算は可、セルアドレスでセル値参照も可
    • 関数が何も無い
      • sum,count,average,min,max
    • 値の変動に計算式を連動
      • 範囲指定時の連動が未実装
  • 入力したデータの保存ができない
    • indexedDBにセル値を保持
      • indexedDBのスコープがドメイン単位なので別ページと被ってしまいがち
        • databasse属性にurlから置換する%domain%,%path%,%filename%を追加
          • 例:databasename=”%domain%%path%%filename%_スプレッドシート1”
            • 結果はDevToolsのアプリケーションのindexedDBを参照
  • セル幅や高さが変えられない
  • 文字のスタイルを変えられない
  • javascriptからシートのデータを参照できない
  • ステータスバーが無い
  • 数式入力バーが無い
  • セルを移動し画面外に出ても画面がスクロールしない
    • 行列タイトルがスクロールロックしてない
      • スクロールバーが無反応
  • マクロが無い
  • データのインポート、エクスポートが無い
  • グラフ、絵、矢印とか貼れない
  • セルに名前が付けられない
  • シートが1枚ダケ
  • 列・行の追加や削除ができない
  • カットアンドペーストができない

等々いっぱい未実装。

1つのクラスでは長すぎるので分割してみた。

  • SpreadSheetCustomElement   全体(this)
    • this.customElement     カスタムエレメント登録Callback等
    • this.event         イベント処理
    • this.command       コマンドっぽい処理
    • this.layout         レイアウト処理

各処理からはthis.spreadsheet経由で別処理を呼び出す必要がある。

function OpenEntry () {
  let rect = this.spreadsheet.layout.cellEditing.getBoundingClientRect(); //編集するセルの位置を求め
  this.spreadsheet.layout.divTextarea.top = rect.top; //テキストエリアを内包するdivをソコに移動させ
  this.spreadsheet.layout.divTextarea.left = rect.left;
  this.spreadsheet.layout.textarea.value = event.key; //テキストエリアに入力した文字を入れ
  this.spreadsheet.layout.divTextarea.style.display = 'block'; //テキストエリアを内包するdivを表示
}

this.xxxx.yyyy.zzzzと長々と詠唱するのは読みにくいので

function OpenEntry () {
  const spreadsheet = this.spreadsheet;
  const layout = spreadsheet.layout;
  let rect = layout.cellEditing.getBoundingClientRect(); //編集するセルの位置を求め
  layout.divTextarea.top = rect.top; //テキストエリアを内包するdivをソコに移動させ
  layout.divTextarea.left = rect.left;
  layout.textarea.value = event.key; //テキストエリアに入力した文字を入れ
  layout.divTextarea.style.display = 'block'; //テキストエリアを内包するdivを表示
}

constを使い短縮化した。

後で割り当てを変更する時には直しやすいかもしれない。(気がする。

this.spreadsheet.layout.divTextarea.top 
// divTextareaをlayoutからshadowへ移動
=>this.spreadsheet.layout.spreadsheet.shadow.divTextarea.top 
// divTextareaをshadowからlayoutへ戻す
=>this.spreadsheet.layout.spreadsheet.shadow.spreadsheet.layout.divTextarea.top 
// は?

となるよりは

layout.divTextarea.top 
// divTextareaをlayoutからshadowへ移動
=>shadow.divTextarea.top 
// divTextareaをshadowからlayoutへ戻す
=>layout.divTextarea.top 
// 良し!

の方がマシ。

さて!いざ、やってみるとVScodeのリファクタリングが不十分でサクラエディタで(ガシガシ

になるのはいつものこと。

カスタムエレメントの宣言をクラスの出す事は可能だけど、クラスのstatic getterがconstructorに作られるので差し替え不可、無名のクラス文でラップするとコードがとても長くなるので取止め。

ps.

行列タイトルロックできたけど、タイトルロックの状態の表と元のサイズの表の差をスクロール量としてスクロールバーのスライダー位置を調整しているので、まだスライダーを触ってもタイトルロックの状態は変動しない。

ps

spread-sheetカスタムエレメント3 セルの範囲をMS-Office365のExcelまで拡大してみたところ、Ctrl+↓↑で移動すると1,048,576行もあるせいで移動完了まで「しばらくお待ちください」状態。カーソルをあちこちと移動するとテーブルが右端から壊れていく。

ps.

多少良くなったので、spread-sheetカスタムエレメント3を更新、データもindexedDBに保持したので、ブラウザが重くなるかもしれない。また時々DB構成を変えるかもしれないので、devToolsのアプリケーションでストレージからIndexDBに▶が付いていたらDB削除してから見た方が良いかも。まだカーソルジャンプ中に帰ってこない時がある。

ps.

indexedDB周りをdatabase.jsに分離。データベースも’R0000001’とか’C00002’など固定文字列も止めてinteger化したが今までどおりVersionアップ処理はしていない。

ps.

A1セルから右端まで移動し一番下の行へジャンプするとセルが隠れた。同様に一番下の行のA列から右端の列までジャンプし一番上の行へジャンプすると右側の隙間がひろがっていた。この修正中に列を追加する処理を呼び出す側がawaitしていなかったせいで画面が壊れることがあった。

カスタムエレメントの属性にdatabasenameを追加し、スプレッドシート毎にindexedDBのデータベースを分離可能に。

ページ移動(PageUp/Down)で同期を取っていたら、キーリピートで並行に処理が走ってしまい画面構成がクズれるので、全般的にキー操作はsetTimeoutで後回しにすることで並行に処理が走らない様にした。

ps.2024/3/18

indexDBの構成を変更!パーサーコンビーネータを組み込んで数値やセルアドレスで四則演算程度の数式を計算できた。但し、indexedDBが非同期なので、パーサーコンビーネータでArray.reduceを使った箇所の同期ズレ(Promiseレーシングや無限ループ)を対処できずfor文に戻した。

セル値の変動に連動して再計算させてみたが、inndedDBのトランザクションは階層化できない様で、トランザクションを流しながらセルデータをinndedDBで読み書きした後に、カーソルを移動させると、トランザクションが終了な済みエラーになる。また、データベースに複数キーのインデックスがあると、単キーのインデックスでもカーソルの範囲(上下限)は配列で指定しなければいけない。bound([1],[1])でデータが取れるけど、bound(1,1)ではスカっと終了する。

そう云えば、数式とかデータに空白が混入するとパーサのパターンにマッチしない。パーサの処理の先頭に空白の読飛ばし処理を追加した。でも数式は先頭文字が=の場合だけに限定したいので、読飛ばしをON・OFFするようにした。seqとchiceにパラメータに混入させたのでソコはチョットごまかし。

#初期設定
this.fSpaceSkip = false;
・・・
#数式の構文のルート木
const exprCell = this.map(this.seq(this.token('='), () => { this.fSpaceSkip = true }, expr), async (parsed) => {
・・・
#値の構文のルート木
const cell = this.choice(exprCell, () => { this.fSpaceSkip = false }, valueCell);
・・・
#各パーサの処理
async (target, position) => {
  if (this.fSpaceSkip) { [target, position] = this.skipSpace(target, position); }
・・・
}
#seqまたはchoiceのforループ
for (var i = 0; i < parsers.length; i++) {
  var parsed = await parsers[i](target, position);
  if (parsed === undefined) {
    continue;
  }
  ・・・
}

TextAreaを表示中に他のセルをクリックするとTextAreaを閉じそのセルをフォーカスっぽい表示にするけど、その際にクリックイベントで渡されたevent.targetの中身が書換わってスクリプトが暴走状態になってたので即保持しておく。※Promiseを使い出してからはよく起きる現象

ps.2024/3/19

sum関数がちょっと動き出した。なのでindexedDBの構成がまた変わった。min.max,averageもセルの範囲指定でも計算可。※結果が正しいかもしれない。

ps.2024/3/20

indexedDBのセルデータをJSON風に変更で文字列型が修正漏れ。sum関数等でほぼ同じコードが続くのでコールバック式に変更。Deleteキーでセルデータを空白に。=A1などアドレスや範囲が結果になる場合にそれっぽく表示。1+2は計算するけど1+2+3は面倒そうなので後回しにしたら、関数のパラメータも3個あるとダメだったし、アドレスに小文字を使うと変で、=A1:C7と範囲値の場合も中途半端で、セルを空欄にするとundefined(ある意味正しいけど)になってたので、見直し。確認のためにindexedDBを見ると、画面からデータを削除するとレコードの値が空になってたダケだったのでレコードごと消える様に修正。indexedDBのデータが同じドメインなら共有だがdatabasename属性に置換文字列を追加し名前で切り分け可能にした。

ps.2024/3/21

セルデータをJSON風化のmin.max関数の修正漏れ。(v)?v:”でnullやundefinedが表示されない様にしたら0も表示しなくなっていた。date型で日付と時刻の入力でDate型のformatを実装、boolean型の入力、now(), today()を実装。論理式を実装しないとif関数が作れない。round()とvlookup()はコードしてみたダケ。

ps.2024/3/24

a1形式のaddress1をaddressに。関数をグループ管理に変更中。後、細かいバグ。セル上にマウスを移動するとデバッグ用ツールチップを表示。

ps.2024/3/25

データ型をstringをtextに変更。F2で入力すると直前のセルで入力した内容がチラっと見えていた。

パーサーコンビネータに論理式等を追加したのでexpr(計算式の根っこ)の呼び出しが激増しasync、 awaitも多く連動計算の待ち時間が長くなってきてsetTimeoutで連動計算を呼び出していることもああり、僅かなミスでスタックオーバーフローやメモリ不足に陥りやすくなっている。

ps.2024/3/26

計算式のパース(parse)と計算(calc)を分離、indexedDBの式はパース結果(トークン風)。これでA1とR1C1の切り替えができそう。少し寄り道してmapメソッドをBNFテキストからパーサーコンビネータの文法部分を作るようにしてしまいたい。今は this.map(this.seq( のようにthis と括弧が多いから。

ps.2024/3/27

演算子*と/が+ーな計算をしていた。関数をexprメンバーに文字で格納していた名残で式のトークンを配列で格納していたがname,parametersに分けた。式を再入力するとexprがname,parametersに変わる様に暫くは式の文字列化と計算でexprも併用させる。アドレス型のaddressを配列に変更したのでそのうちアドレス範囲型を無くす予定。



[javascript]スクロールバーのサイズ

気になると夜しか眠れないスクロールバーのサイズ

Windowsでは画面の解像度などで異なるし、古いXP等では「画面のデザイン」でも変更可能だった。

なので状況に応じて調べるしかない。

HTMLElementにスクロールバーを付けて

div.sample {
    overflow-x: scroll;
    overflow-y: scroll;
}

スクロールバーのサイズ = スクロールバーを含む領域 ー スクロールバーを含まない領域

から求められる。

具体的には

let div = document.querySelector('div.sample');
let rectDiv = div.getBoundingClientRect();
let scrollbarSize = {
    width: rectDiv.width - div.clientWidth,
    height: rectDiv.height - div.clientHeight
};

注意点としては

いつでも正しい数値が得られる訳では無いので

scrollbarSizeのwidthかheightのいづれかが0の場合は再トライが必要。

                //  タイミングによってはwidthが0になるため再処理
                if (scrollbarSize.width === 0 || scrollbarSize.height == 0) {
                    return this.resizeDivTable(0);
                }

ブラウザのDevTools上で【F5】を押すと無限ループしやすいので再トライ数を制限した方がいい。



[javascript]childrenを全削除したい

古くからある問題に列挙されたリストを全削除したい場合・・・

for (let index = 0; index < shadow.children.length; index++) {
    shadow.children[index].remove(); あるいは shadow.removeChildren(shadow.children[index]);
}
console.log('remain object:' + shadow.children.length;
>> remain object:1.

と残ってしまいがち。

リストを先頭から順に削除しているのでイテレータの思いとのすれ違いが起きてしまう。

while (0 < shadow.children.length) {
    shadow.children[0].remove(); あるいは shadow.removeChildren(shadow.children[0]);
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:0.

この様にイテレータを使わないのが正解だけど、[0]のマジックナンバーは~とか云われそう。

while (0 < shadow.children.length) {
    shadow.firstChild.remove();
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:0.

の方がいいかな?

for (let ch in shadow.children) {
    ch.remove();
>> Uncaught TypeError TypeError: ch.remove is not a function
};
console.log('remain object:' + shadow.children.length + ".");

とすると、最後の方でchに’length’が割り当てられて、TypeErrorが起きてしまうのはお約束。

for (let ch of shadow.children) {
    ch.remove();
};
console.log('remain object:' + shadow.children.length + ".");
>> remain object:1.

で、エラーは起きないけど、やはりイテレータとのすれ違いで消し残りが出てしまう。

今なら

Array.from(shadow.children).forEach(ch => ch.remove());
console.log('remain object:' + shadow.children.length + ".");

が良さそうかな?fromの名前がちょっとアレだけど。(笑

shadow.innerHTML= '';

やっぱりコレか!

でもいっぱいエレメントが入っていると遅いらしい。

var clone = shadow.cloneNode( false );
>> Uncaught DOMException DOMException: Failed to execute 'cloneNode' on 'Node': ShadowRoot nodes are not clonable.
shadow.parentNode.replaceChild( clone , shadow );

親エレメントの方ですげ替えした方が速そうだけど・・・

shadowでは無理



[javascript]QuerySelectorAllの妙な動き

<html>
・・・
<body>
  <br>
  <br/>
  <br>
  <br/>
</body>

</html>

これを

document.querySelectorAll('br')

すると

Array.from(document.querySelectorAll('br')).map((br)=>br.outerHTML)
0:"<br>"
1:"<br>"
2:"<br>"
3:"<br>"
length: 4

となる。

しかし自作タグ(カスタムなタグ)の場合は、

<html>
・・・
<body>
    <sample-element></sample-element>
    <sample-element />
    <sample-element></sample-element>
    <sample-element />
</body>

</html>
Array.from(document.querySelectorAll('sample-element')).map((sample)=>sample.outerHTML)
0:"<sample-element></sample-element>"
1:"<sample-element>\n    <sample-element></sample-element>\n    <sample-element>\n\n\n</sample-element></sample-element>"
2:"<sample-element></sample-element>"
3:"<sample-element>\n\n\n</sample-element>"
length: 4

ブラウザのDevToolsも同様なので、HTMLファイルの読み込み時点で【そのように解釈】されているので仕方が無いんだけどね。(笑

このため、ちゃんと「Autonomous custom element(自律カスタム要素)」として書いても結果は変わらない。(ハズ



[javascript]returnした後に

return  (fValue)?[nCount[name]= 1:nCount[name];
nCount[name]++;

以前はreturn文は戻り値をスタックするだけで、後ろに処理があれば実行してくれた。(時期もあった

でも、今はそんなことは無いので・・・

return (fValue)?[nCount[name]= 1, nCount[name]++][0]: nCount[name]++;

と変な書き方をすることがマレにあるけど

return (fValue)?nCount[name]= 1: ++nCount[name];

の方がマシかな



[javascript]関数の途中のパラメータを省略したい

例えば・・・

let result=function('text',2);

2番目のパラメータを省略するには

let result=function('text');

と書けるけど、1番目のパラメータを省略すると

let result=function(2);

2番目のパラメータのつもりが1番目のパラメータとして渡ってしまう。

そこで、

let result=function(,2);

BASICでは省略したい時は書かないことで通じるので

arg0: SyntaxError: Unexpected token ',' {stack: 'SyntaxError: Unexpected token ','', message: 'Unexpected token ',''}

とエラってしまう。

javascriptでは関数の引数は指定しなければ undefined になるので、

let result=function(undefined,2);

とundefinedを引き渡せば、実質的に1番目のパラメータを省略された様に関数側で処理してくれる。



[javascript]contenteditable

contenteditable=”yes”属性付きのdivタグはブラウザ上で直接編集できる。

しかし、子エレメントの構造が複雑化すると改行位置などが把握しにくいので自動的に改行させないほうがいいと云う記事を見つけたので、試しに作ってみたカスタムエレメント版も。

確かにメンドクサイw

コードしなくてもcontenteditable=”yes”設定で、太文字(Ctrl+B)、アンダースコア(Ctrl+B)、イタリック(Ctrl+I)でテキストを自動的に修正してくれるし、勿論HTMLの中身もどんどん変わっていく。

とりあえず、

  1. divタグからpreタグに変更。※半角空白を表示できるから
  2. inputやkeydownイベントのログ表示
  3. キャロット移動の行背景色変更や「m行m列」※範囲指定時も含む
  4. cssで行番号表示
  5. javascriptソースコードの着色※javadoc部は別処理
  6. ctrl+/でそれっぽく行コメントのトグル(ON/OFF)処理
  7. HTMLソースコードの着色も追加。※画面下の言語で切替
  8. 着色した結果をHTMLファイルでダウンロード。
  9. 言語選択状態に応じたサンプルソースを貼れるようにした。
  10. ソースを分割。
  11. バグ修正
    • 2024/2/17
      • 【彩色】押すと空行が消えてる。
        • CRクラスのspanのmarge属性が未設定だった。
      • ソースが空または1トークン分のテキストしかない場合にエラっていた。
        • 気にしてなかった。
      • 右端に縦にスクロールバーが2つ。
        • 何度調整しても不意に再現する。(謎
      • 01234と数字を入力すると先頭の0が数値扱いにならない
        • 空白のパターン(\x20+)のつもりが(x20+)になっていたせい。
          • ミスってるのに空白判定ができていたのは謎

相変わらず強制的にキャッシュされるのでソースを更新してもちゃんと反映されない。

対処方法1:ブラウザのDevToolのネットワークでキャッシュを無効化。

それでもiframe内のファイルはキャッシュされたままなので

対処方法2:そのiframe.htmlのソースの空っぽな画面だったりコンソールにエラーが表示されていたら・・・【F5】でソースを表示。

大体これで最新になる。

未解決や重複のエラーが出ていたら上の方法でキャッシュをクリアできる。(ハズ

それでもダメな時はブラウザの履歴をごっそり消すしかない様です。

それにしても、予想外の動きをするので大変だわ。(笑

ps.2024/3/21

カスタムエレメント版のHTMLのサンプルが文字化けしていたので修正。



「JavaScript」連番の配列を作ってみる

どうでもいいけど、C言語っぽく

let result = new Array(10);
for (let index = 0; index < 10; index++) {
 result[index]= index + 1;
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では色々注意が必要。

から始まって・・・

let result = []
for (let index = 0; index < 10; index++) {
 result.push(index + 1);
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

この時点でまだindexをいっぱい書いてあるから

index + 1じゃなくて let index=1すれば良い!

とか余計な修正をしだすと

index < 10の部分を見落としてそう。

つまり、indexの出現は少ない方が良い。

しかし、

var result = [...Array(10)].map((_, i) => i+1) 
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では作業用配列をいくつも作ってしまう。

Array(10)とか[…xxx]とかね。

とは云え

var result = Array.from({length: 10},(_, index) => index + 1);
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

に至っては {length:10 } でObjectの振りというか {…}は紛れもなくObjectだけど、

length: 10とよくあるプロパティでArray.fromを騙すのはいいとして

indexが唐突に出てくる感じがするけど・・・

Array.from(arrayLike, mapFn)

Array.fromはこんなパラメータになっているので

Array.prototype.map((value, index) => { ... });

を思い浮かべば、理解してもらえそう。

それに無駄な作業用配列も作らないから

1から1万までの連番配列を作るには丁度良い。

気がする。

そこで

Array.from(arrayLike, mapFn, thisArg)

まで使ってみると

var result = Array.from({ length: 10 }, function (_, index) { return index + this.start; }, { start: 1 });
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

明示的に連番の初期値を設定できて気持ちはすっきりするけど、

アロー関数にとってthisはwindowしかありえないので

無名functionにしないといけないし

スクロールバーが出るくらい長いので蛇足感がある。

だが、連番+αのテストデータを作るのには向いている気がするので次点とした。

結果は・・・

var result = Array.from({length: 10},(_, index) => index + 1);

が良さげ。

/**
 *  連番作成
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @return {Array of object}        開始番号~終了番号の配列
 */
const range = (start, end) => {
    let rc = Array.from({ length: (end - start + 1) }, (_, index) => index + start);
    if (callbackMap) {
        rc = rc.map(callbackMap);
    }
    return rc;
};

range().map(callback) をラップしてrangeMapを作ってみたが、使い心地は・・・

range(1, rows).map((nRow) => {
    createTrElement(objTable, nRow, ...);
});

rangeMap(1, rows,(nRow) => {
    createTrElement(objTable, nRow, ...);
});

見分けが付かないくらい同じだった。(笑

ps.

クエスチョン:spreadSheetのコードで上のrangeを使ってみると妙に重い(初期表示2秒前後)。何でだろう?

アンサー:今のEXCELって最大1048576行16384列なのでこの最大値に合わてみたら、初期表示で物凄い量の配列を作っていた!普通のfor文に戻すとサクっと初期表示。配列のままでも動かない訳では無いけど、時々Chromeやタスクバーがマウスに反応しなかった原因のような気がする。

100万×1.6万≒160億 だからなぁ~

でもfor文のままなのも、配列じゃなくても連番指定でsomeとmapっぽく動作すればいいから

/**
 *  連番配列っぽいイテレータを作成する
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @returns {iterator}              開始番号から終了番号を格納した配列っぽいイテレータ
 */
const range = (start, end) => {
    let step = 1

    const rangeIterator = {
        some: function (func) {//someっぽく動作させる
            for (let cnt = start; cnt <= end; cnt += step) {
                if (func(cnt)) {
                    return true;
                }
            }
            return false;
        },
        map: function (func) {//mapっぽく動作させる
            const f = false;//配列を返さない
            let aRc;
            if (f) {
                aRc = new Array(end - start + 1);
            }
            let index = 0;
            for (let cnt = start; cnt <= end; cnt += step) {
                const rc = func(cnt);
                if (aRc) aRc[index++] = rc;
            }
            return aRc;
        },
    };
    return rangeIterator;
}


「javascript」postMessageとPromise

デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】の中で、
iframeへ送信したメッセージの応答をawaitで待機するようにしていますが、postMessageは本当にメッセージをポストするダケの機能なのでレス待ちなんで事ができません。

        displayStatus(`サンプルを${encoding}に変換中`);
        let unicode16CSV = await getSampleTextInnerText();

タネを明かすと、これを実現するために外部変数messageResponseStackにPromiseのresolveを保持し、どこかで応答メッセージを受信したらresolveを呼び出してawaitの待機状態を解除してもらっています。

つまり職場(プロセス)に居れば誰でも見れるホワイトボード(外部変数)に取引先(requestID)と連絡先(resolve)を書いてあるので、暇になった人が気が付けばかかってきた電話の相手先(requestID)の要件(event.data.message)を連絡してもらえる(ハズ

な仕組みです。

ついでにrequestIDはワンタイムなIDなのでホワイトボードから消してます。

/**
 * iframeのサンプルを取得する
 * @returns 
 */
const getSampleTextInnerText = () => {
    return new Promise((resolve, reject) => {
        loaderOpen();
        var us = getUniqueStr();
        messageResponseStack[us] = { 'resolve': resolve, 'reject': reject };
        myPostMessage('iframe', "getSampleText", "", us);
    });
};
    messageProcSetup({ ☚ここでmessageListner の応答メッセージタイプ毎のコールバック先をリストアップ
      ・・・
        'getEncodingListResponse': messageResponseProc,
      ・・・
    });
/**
 * メッセージのレスポンス処理 ☚ messageListnerからコールバックされる。
 */
const messageResponseStack = {};
const messageResponseProc = (data) => {
    loaderClose();
    if (data.requestId !== undefined) {
        messageResponseStack[data.requestId].resolve(JSON.parse(data.message));
        delete messageResponseStack[data.requestId];
    } else {
        alert(`not requestId${CRLF}data.type: ${data.type}`);
    }
};
/**
 * メッセージを受信する
 * @param {MessageEvent} event 
 */
const messageListner = (event) => {
    if (event.origin !== myOriginListener || event.type !== 'message') return;
    let data = event.data;
    let messageInfo = messageInfoList[data.type];
    if (messageInfo === undefined) {
        alert(`unkonwn message type = '${data.type}'${CRLF}message: ${data.message}`);
        return;
    }
    if (messageInfo !== undefined && messageInfo !== null) {
        if (Array.isArray(messageInfo)) {
            if (messageInfo.length > 1) {
                messageInfo[0](data, ...(messageInfo.slice(1)));
            } else {
                messageInfo[0](data);
            }
        } else {
            messageInfo(data);
        }
    }
};
/**
 * メッセージを送信する
 * @param {string} textType 
 * @param {string} textMessage 
 * @param {string} textRequestId
 */
const myPostMessage = (to, textType, textMessage, textRequestId) => {
    switch (to) {
        case 'parent':  //  親ドキュメントに送信する
            to = window.parent;
            break;
        case 'iframe': //   iframeへメッセージを送信する
            to = document.querySelector("iframe").contentWindow;
            break;
    }
    to.postMessage({
        type: textType,
        message: textMessage,
        requestId: textRequestId,
    }, myOriginSender);
};

もっといい方法があったらいいなぁ。



デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】

あれから6年?(大笑

結局何が原因でうまくいかなかったのかと云えば

FileReader.readAsText(${Blob型データ},”SJIS”);に

未定義あるいはUTF16に変換できない文字を与えるとの大雑把には

  • シフトJISの下位コードのMSBが0の場合は、「0xfffd」「下位コード」の2バイトに変換。
  • シフトJISの下位コードのMSBが1の場合は、「0xfffd」の1バイトに変換。

している様に見えるが・・・

Blobに1文字づつ入れFileReaderで読むと

  • シフトJISの下位コードのMSBに関わらず、「0xfffd」の1バイトに変換。

してしまうので

Blobに複数の文字を入れると、

挙動が不安定になるらしい。

なので、

1文字づつシフトJISをUTF16に変換してシフトJIS変換表を作成するしかない様だ。

実際に動かしてみると、

1秒もかからず変換表を作ってしまうので

これでいいんじゃないかな?

まだ、黑(fc4f)付近で変換に失敗している???

あ、文字領域が拡張されていたw(6年の重みかな?

f040~fc7e、f080~fcfcを変換表に追加。

ついでに外部変数を止めPromiseでガガガと作らせようとしたら・・・

Promiseを100個以上作るとPromise.Allでうまく処理できない ※返事が返ってこない。

ので、awaitで順次処理に変えてみる。※asyncも付けまくる。

さらにダウンロードの直前に変換表を作り直してみると

3秒くらいかかってしまった。

変換表をjsファイルでダウンロードできるようにした方がいいかな?

サンプルをSJIS変換を使って作成してみた。

やはり処理時間が長めだな。

あれ?未定義な文字しかない行が結構ある!

カット!

ちゃんと処理時間を表示してみると長めだなぁ。

サンプルをiframe化しUTF16未対応文字は□に変えてみた。

さらに、EUCとかも対応できそうなのでやってみた。

サンプル

まだ、EUC-jpやiso-2022-jpでコードページを変更するコード(エスケープシーケンス等)が必要な文字は変換時に化けてしまう。

どんどん遅くなっていく。(キニシナイ

外部変数アクセスするのを減らそうとawaitしてみるとasync宣言付けたアロー関数だらけ。

Promise と resolve と await を良い塩梅で混ぜないといけないのも大変。

一番困ったのはVisualStudio Codeでデバッグしてみると

頻繁にPromise.allが暴走するが

それは非同期処理中はエラっても「デバッグコンソール」は裏に隠れたままなので、

SyntaxErrorがあっても気が付かないせい。

暫くしても処理が終わらないなぁ?と思い、

「問題」「出力」「デバッグコンソール」「ターミナル」タグをひっくり返してみると。

xxx変数は未定義です。

とか

xxxにmapプロパティは存在しません。

が見つかる。

でもデバッグを再開してもコンソールは初期化されないので、 2024/3/21現在はクリアされてる。

エラったらコンソールをクリアしてからデバッグしなおさないと泥沼。

postMessageでiframeとやり取りしているので、サンプルに入力した内容等がブラウザの他のタブにもpostされている可能性があります。

またサロゲートペア(𩸽:U+D867とU+DE3D、𠮷:U+D842とU+DFB7とか)にも対応していません。

※サロゲートペアは入力したいサロゲートペア文字の16進コードが判れば、IMEパッドの文字カデゴリの上位サロゲートD867の枠をクリックすると�っぽく表示されますが下位サロゲートからDE3Dの枠をクリックすると𩸽に変わります。

と云うのも、サロゲートペアはUTF16独自のものでSJIS文字に対応する文字の定義が無いっぽいです。SJIS変換表はSJISの文字領域に対して作成していますが行頭の””で囲まれた文字に上記のサロゲートペアが無いのです。または「部」から11画の魚辺の終わりの方にある𩸽を探し出すかですね。

JIS第4水準の第93区に𩸽が、AJ1ベース文字-JIS外に𠮷があるっぽいですが、ISO-2022-JPのザックリとしたエスケープシーケンスでは得られないっぽいです。

  • ASCII: 1b 28 42
  • JIS X 0208: 1b 24 42

一応、ISO-2022-JP-2004も追加してみたけどブラウザ未対応のencoding名らしく組み込んだエスケープ文字がそのまま出力され全角どころか半角すらちゃんと出なくなってしまう。

encodingx.jsのconst encodingInfoList をイジるとブラウザが対応している色々なencodingを試せます。

JISの様に文字の前にエスケープシーケンスが必要なものは配列の5~6番目に配列で記入できます。

  • 5番目は文字を全角等に切替るシーケンス
  • 6番目は半角に戻すシーケンス。

ここに新たなencodingを追加すればHTMLのselectの項目も自動的に増えます。

※まともに動作するかどうかは不明だけど

/**
 * エンコーディング情報
 *  {
 *      "encoding": {
 *          8:  [
 *                  [上位開始バイト,上位終了バイト,下位開始バイト,下位終了バイト,[接頭語...],[接尾語...]]
 *              ... 
 *          ],
 *          16: [
 *                  [上位開始バイト,上位終了バイト,下位開始バイト,下位終了バイト,[接頭語...],[接尾語...]]
 *              ...
 *          ],
 *      },
 *  ...
 *  }
 */
const encodingInfoList = {
    "windows-1252": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x00ff],
        ],
    },
    "shift-jis": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x00ff],
        ],
        //  16ビット
        16: [
            //  第1面
            [0x0081, 0x009f, 0x0040, 0x007e],
            [0x0081, 0x009f, 0x0080, 0x00fc],
            [0x00e0, 0x00ef, 0x0040, 0x007e],
            [0x00e0, 0x00ef, 0x0080, 0x00fc],
            //  第2面
            [0x00f0, 0x00fc, 0x0040, 0x007e],
            [0x00f0, 0x00fc, 0x0080, 0x00fc],
        ],
    },
    "iso-2022-jp": {
        8: [
            //  8ビット
            [0x0000, 0x0000, 0x0000, 0x00ff, [0x1B, 0x28, 0x42], []],
        ],
        16: [
            // JIS X 0208コード表   1面
            //
            // 記号、英数字、かな(01区~08区)
            [0x0021, 0x0028, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            // 第1水準漢字(16区~47区)
            [0x0030, 0x004f, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            // 第2水準漢字(48区~84区)
            [0x0050, 0x0073, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            [0x0074, 0x0074, 0x0020, 0x002f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
        ],
    },
    "iso-2022-jp-2004": {
        8: [
            //  ASCII
            [0x0000, 0x0000, 0x0000, 0x00ff, [0x1B, 0x28, 0x42], []],
        ],
        16: [
            // JIS X 0213漢字集合1面:コード表
            // 1面(JIS X 0208および第3水準漢字)(01区~94区)
            [0x0021, 0x007e, 0x0020, 0x007f, [0x1B, 0x24, 0x28, 0x51], [0x1B, 0x28, 0x42]],

            // JIS X 0213漢字集合2面:コード表
            // 2面(第4水準漢字)(01区~08区)
            [0x0021, 0x007e, 0x0020, 0x007f, [0x1B, 0x24, 0x28, 0x50], [0x1B, 0x28, 0x42]],
        ],
    },
    "euc-jp": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x007f],
            [0x0000, 0x0000, 0x00a0, 0x00df, [0x8e], []],
        ],
        // JIS X 0208コード表   1面
        //
        // 記号、英数字、かな(01区~08区)
        16: [
            [1 + 0x0a0, 8 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            // 第1水準漢字(16区~47区)
            [16 + 0x0a0, 47 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            // 第2水準漢字(48区~84区)
            [48 + 0x0a0, 83 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            [84 + 0x0a0, 84 + 0x0a0, 0 + 0x0a0, 15 + 0x0a0],
        ],
    },
};

ここまでくるとブラウザのJavaScriptのreadAsTextのencodingのチェッカーっぽくなってきた。(笑

今更UTF-8以外のテキストデータをブラウザで使う意味は遠の昔に消えているので、

ExcelがUTF-8のCSVをちゃんと読める様になったら、もう存在意義が無いに等しい。(大笑

なので、あて名書き5をアップ。おっと2014年?2024年に修正っと(笑

なんてこった10年も経ってたのか!

ps.2024/2/13

readAsTextで1文字づつ処理するのはやはり遅い。文字間にNULエリアを挿入してみたら文字同士の影響が無い様なので、エスケープシーケンスも考慮して16バイト毎に文字を配置することで、データの下位バイトの範囲単位で(サンプルの場合は1行単位)で処理するように修正したら、1秒以内に処理を終える様にしたのでchromeで見ているなら【24時間以内の履歴を削除】するとサンプルの変換処理が妙に速くなる。(ハズ

全データの範囲を一度に処理すればもっと速くなるハズ。




top