DOSという名前

今頃はDOSと言えば「DoS攻撃」の事になりそうですが、昔は「MS-DOS」を意味するものでした。 C言語の初歩的なプログラムを作っていた時代、フロッピーディスクに結果を保存出来た時には、画面のやりとりだけのプログラムから、ワンランク上に進んだ実感がありました。 その時に、OSに「Disk Operating System」と言う名前が付いている意味に気付きました。 記憶媒体に記録する事は、今では当たり前の事ですが、コンピューターにとってそれが可能になった事は、月着陸の様に大きな一歩だったはずです。「DOS」の名は、その先進性を意味していたのです。

 

 

JavaScriptのファイル保存と読込み

JavaScriptはブラウザ上で動作する特殊性から、ファイル保存に関するメソッドは、PC上のプログラムの様に確立されていない気がします。 先ず、セキュリティ上の問題から、ブラウザとPCの間に境界が作られていて、ダウンロードと言う形でファイルをPCに保存する必要があります。 また、メソッドの動作にもブラウザ間の不統一があったりします。 IEは後景化しましたが、ブラウザ間の微妙な差異がありそうです。

 

「Ranking Blocker」では、ネット上の記事を調べてなんとかファイル保存を実現しましたが、その主要なコードを以下に整理しておきます。

 

 

ファイル保存(ダウンロード)

以下は、各所に書かれているブラウザ内のデータをファイル保存するサンプルコードを整理したものです。(以下は 現時点の Chrome / Firefox を前提にしています)

 

ページ上に「button1」(保存ボタン)が有り、これをクリックした場合にファイル保存が行われるコードです。 なお、このコードはブラウザのダウンロード機能を利用するので、ファイルはOSに設定されたダウンロードフォルダーに保存されます。 

 

button1.onclick=function(){
    let write_json=JSON.stringify(block_id);
    let blob=new Blob([write_json], {type: 'application/json'});
    let a=document.createElement("a");
    a.href=URL.createObjectURL(blob);
    document.body.appendChild(a); // Firefoxで必要
    a.download='ranking_bl.json';
    a.click();
    document.body.removeChild(a); // Firefoxで必要
    URL.revokeObjectURL(a.href); }

 

ファイル保存するのはスクリプト上の「block_id」です。(この場合は配列ですが、他のオブジェクトでも同じです)

 

これをJSON形式の「write_json」にエンコードして、更に「File API」が扱えるバイナリ化をして「blob」を得ています。

 

この後、「blob」に「URL.createObjectURL(blob)」ででっち上げたURLを与え、あたかも「blob」がそのURLに有るオブジェクトで、「a.href」はそのリンクという構図を作ります。(「a要素」も仮のものです)

 

次の「a.download」の構文は、a要素にダウンロード属性を指定してダウンロード可能とするもので、その際のファイル名と拡張子を同時に指定しています。

 

これらの準備を整えた上で、「a.click();」で実際にダウンロードを指示します。

 

最後の2行は後処理で、仮生成したリンク(a.href)を「revokeObjectURL(a.href)」でメモリから解放します。

 

以上の様に、まわりくどく複雑な手続きで「ranking_bl.json」が保存されます。

 

〔追記〕2022.09.14

上記の保存コードで「Firefoxで必要」としている「a要素」の「appendChild()」と「remove.Child()」のコードは、2022年の時点で不要になっています。 これは、ファイル保存・読込みでユーザーエージェント別のコードが不要になり、大変楽になりました。 上記の消し線のコードは、現在は不要です。

 

 

ファイルの読込み

こちらも、ネットの各所からサンプルを得て整理したものです。

 

先の「button1」は「input type="submit"」ですが、「button2」(読込みボタン)は「input type="file"」でなければなりません。 この事から、ボタンによる起動には「addEventListener("change" , function(){})」の構文が必要になります。

 

button2.addEventListener("change" , function(){
    if(!(button2.value)) return; // ファイルが選択されない場合
    let file_list=button2.files;
    if(!file_list) return; // ファイルリストが選択されない場合
    let file=file_list[0];
    if(!file) return; // ファイルが無い場合

    let file_reader=new FileReader();
    file_reader.readAsText(file);
    file_reader.onload=function(){
            block_id=JSON.parse(file_reader.result);
            let write_json=JSON.stringify(block_id);
            localStorage.setItem('id_back', write_json);
            location.reload(); }; }

 

起動される function() の最初の5行は他所の受け売りですが、読込み操作でOSのファイル選択ウインドウが表示され、そこからファイルを指定する操作で考えられる例外を処理しています。 この部分はファイルを1個だけ選択する前提のコードです。

 

後半の7行は、ファイル読込みの核心部です。 これも殆ど定番の書式を利用していますが、「JSON形式」のファイルは「text」として読めるので「.readAsText(file);」を指定しています。

 

最後の5行は読込みが成功した場合に実行されます。

読込んだJSONファイルは、着込み時にJSON形式にエンコードしたものです。 これを「JSON.parse(file_reader.result);」で元の配列オブジェクトに戻し、「block_id」に代入しています。

 

この後、再び「JSON.stringify(block_id);」でJSON形式にエンコードし、ローカルストレージに「localStorage.setItem」で保存し直しています。 デコード・エンコードを飛ばして直接バックアップファイルをローカルストレージにコピー可能かも知れませんが、確証が無いので着実なコードにしています。

 

block_id」に代入した時点で、メモリー上で排除リストは更新されているのですが、最後の「location.reload();」(リロード)をしないと、ページ上に反映させる事が難しい状態でした。 コードを工夫すると、リロード無しでマスク更新が出来るかも知れません。

 

読込んだファイルはチェックする必要がある

上記コードは、見易くするために読込んだファイルのチェック部を省いています。 全く無関係なファイルや別スクリプトの「.json」ファイルを読込んだ場合、それが排除リストの壊れた配列となり、スクリプトが動作しなくなります。 そこから抜け出すには、ローカルストレージのリセットが必要になるので、公開するコードとしてはチェック部は不可欠です。

 

実装したコードでは、最後の5行には以下の太字のチェック部が追加されています。

 

file_reader.onload=function(){
if(file_reader.result.slice(0, 7)=='["tmp1"'){ // ranking_bl.jsonの確認
    block_id=JSON.parse(file_reader.result);
    let write_json=JSON.stringify(block_id);
    localStorage.setItem('id_back', write_json);
    location.reload(); } };

 

file_reader.result.slice(0, 7)」は、読み込まれた直後のデータ(text)の最初の7文字を切り出すものです。 これが「["tmp1"」ならば「Ranking Blocker」が保存したバックアップファイル「ranking_bl.json」だと信じて良いというわけです。

 

排除リストの2個のダミーID

実は、排除リストの「配列」の先頭には「tmp1」「tmp2」という2個のダミーIDがセットされています。 このダミーは、排除リストに登録がない初期状態を避ける目的です。

 

スクリプトは、起動直後にローカルストレージの排除リストを読込み、ランキング表示をマスクします。 マスクには、排除IDの配列を結合した「排除フィルター」が必要で、結合操作は以下のコードの太字の部分です。

 

var read_json=localStorage.getItem('id_back');
    block_id=JSON.parse(read_json);
    if(block_id==null){ block_id=['tmp1','tmp2']; }
    var block_filter=block_id.join('|');
    var block_regex=RegExp(block_filter);

 

この「join('|')」メソッドまでのコードは、最低2個の中身を持つ配列が無いとエラーが出て、肝心のマスク機能が働かず、配列の登録も出来ません。 ダミーIDは、初期状態のスクリプトを救う「呼び水」の重要な役目があります。

 

排除リストを初期化した場合、3行目の「配列」が無い場合のコードが働くので、どんな場合も排除リストの先頭には、このダミーIDが入ります。「Ranking Blocker」の「排除リスト」のバックアップファイルか否かの確認に、これを利用しています。