デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる 改訂2018/10/2

サーバーサイドでSJISにエンコードすること自体どうにでもなる。
しかし、デスクトップにHTMLとJavaScriptだけ貼ってローカルにあるテキストをCSVファイルとしてダウンロードさせようとすると、
JavaScriptのテキストはUnicode(16Bit)なので、そのまま出力するとEXCELで読むと文字化けする。
JavaScriptでは、SJISをUnicode(16Bit)に変換する機能はあるが、Unicode(16Bit)をSJISにする機能は無い。
JScriptならWindowsのドコかにStrSonvがあれば拾って来ればいいのかもしれない。
今風なら既に【外部のJSファイル(延々と長いコード表が載っている)】があるのだから、これを借りるのが普通。
しかし、今のブラウザは大抵のコードページならUnicode(16Bit)に変換できる能力を持っているのでコレを使って逆変換テーブルを作ってもいい。
SJISの文字の入ったバイナリーなイメージを作り、Readerに読ませ、Unicode(16Bit)に変換させた結果を、Unicode(16Bit)のコードをキーワードにSJISのコードを得られるArrayに詰め込めばいいハズだ。難しいのはUnicode(16Bit)しか使えないJavaScriptの上でどうやってSJISのイメージを作るのかというあたりと、SJISのコード表で有効な範囲だけイメージを作ること。
0x80など未定義になっている部分のイメージが混ざると・・・続くバイトを全角の2バイトと思って変換され文字化けてしまうし、また変換できなかった文字が空白に変換された場合、最後に変換できなかった文字がコード表の「空白」を上書きしてしまい派手な文字化けの原因になる。
それでも、MakeConvert16bitTable や file_reader.readAsText(b,”SJIS”); をゴニョればeuc-jpもできるはずだ。
16ビット領域の文字コード表を作るとループしまくるのでとても遅くなりそうな気がしたけど、今のパソコンならどうということはないらしい。
これでデータベース用のコード変換表もできるハズ。

//Unicode16 - SJIS文字コード表
var utf16Ar=null;
//画面表示直後(onload)に文字コード表を作るMakeSJISConvertの呼び出しが消えないオマジナイ
if( window.addEventListener ) {
  window.addEventListener( 'load', MakeSJISConvert, false );
} else if( window.attachEvent ) {
  window.attachEvent( 'onload', MakeSJISConvert );
} else {
  window.onload = MakeSJISConvert;
}
// Unicode を SJISに変換する
function UTF16toSJIS(utf16) {
  try{
    //コード表作成時に未定義文字をしていした時のオマジナイ
    utf16Ar[" "] = " ";
    //コード表作成時に未定義文字をしていした時のオマジナイ
    utf16Ar[undefined] = " ";
    var a = utf16.split('');
    var buf = new Uint8Array(a.length * 2);
    var j=0;
    for(var i=0; i< a.length; i++) {
      var ch = utf16Ar[a[i]];
      if(ch === undefined) {
        buf[j++] = (0x20);
      } else {
        var chH = ((ch>>8) & 0x0ff);
        var chL = ( ch     & 0x0ff);
        buf[j++] = chL;
        if( chH != 0) {
          buf[j++] = chH;
        }
      }
    }
    var rc = new Uint8Array(j);
    for(var i=0; i<j; i++) {
      rc[i] = buf[i];
    }
    return rc;
  } catch(e) {
    alert(e);
  }
}
//Unicode16 - SJIS文字コード表(utf16Ar)の作成
function MakeSJISConvert() {
  MakeConvert8bitTable();
  MakeConvert16bitTable();
}
//Unicode16 - SJIS文字コード表(utf16Ar)の16bit文字コード分の作成
function MakeConvert16bitTable() {
  //SJISで文字コードに指定している領域のみ作成すること
  MakeConvert16bitSubTable(0x0081,0x009f,0x0040,0x007e);
  MakeConvert16bitSubTable(0x00e0,0x00ef,0x0040,0x007e);
  MakeConvert16bitSubTable(0x0081,0x009f,0x0080,0x00fc);
  MakeConvert16bitSubTable(0x00e0,0x00ef,0x0080,0x00fc);
}
//変換処理の呼び出し回数
var callCount=0;
var transferCharCodeCount=0;
var undefined8BitCount=0;
var undefined16BitCount=0;
var throw16BitCount=0;
//Unicode16 - SJIS文字コード表(utf16Ar)の8bit文字コード分の作成
function MakeConvert8bitTable() {
  try {
    // 8bit area
    var buffer1 = new Uint8Array(256);
    for(var i=0; i<256; i++) {
      if(0x00<=i && i <= 0x1f) {
        switch(i) {
        case  9 : case 10 : case 13 ://制御コードはCSVで使用するTAB,CR,LFだけ登録
          buffer1[i] = i;
          break;
        default://制御コードはCSVで使用するTAB,CR,LF以外は空白として登録
          buffer1[i]=" ";
          break;
        }
      } else if(0x80<=i && i <=0xa0) {//CSVで使わないコードは空白にする
        buffer1[i]=" ";
      } else if(0xe0<=i && i <=0xff) {//CSVで使わないコードは空白にする
        buffer1[i]=" ";
      } else {
        buffer1[i] = i;
      }
    }
    var b = new Blob( [buffer1], {type: "application/octet-stream"});
    var file_reader = new FileReader();
    file_reader.onload = function(e) {
      var sjisAr = e.target.result;
      utf16Ar = new Array();
      for(var i=0;i<256;i++) {
        var utf16 = sjisAr.substr(i,1);
        if(utf16!==undefined){
          if( utf16Ar[utf16] != buffer1[i] ) transferCharCodeCount++;
          utf16Ar[utf16] = buffer1[i];
        } else {
          undefined8BitCount++;
        }
      }
    }
    file_reader.readAsText(b,"SJIS");
  } catch(e) {
    alert(e);
  }
}
//Unicode16 - SJIS文字コード表(utf16Ar)の8bit文字コード分の作成
function MakeConvert16bitSubTable(hiBegin,hiEnd,lowBegin,lowEnd) {
  try {
    // 16bit area
    callCount++;
    var sizeHi  = ( hiEnd  - hiBegin  + 1 );
    if(isNaN(sizeHi)) { alert("Nan sizeHi"); }
    var sizeLow = ( lowEnd - lowBegin + 1 );
    if(isNaN(sizeLow)) { alert("Nan sizeLow"); }
    var sizeAr  = sizeHi * sizeLow;
    if(isNaN(sizeAr)) { alert("Nan sizeHi,sizeHi("+sizeHi+") * sizeLow("+sizeLow+")"); }
    var buffer1 = new Uint16Array(sizeAr);
    var n1=0;
    var tx = "";
    for(var hi=hiBegin; hi<=hiEnd; hi++) {
      for(var low=lowBegin; low<=lowEnd; low++) {
        buffer1[n1++] = (hi & 0x0ff) + ((low <<8) & 0x0ff00);
      }
    }
    var b = new Blob( [buffer1], {type: "application/octet-stream"});
    var file_reader = new FileReader();
    file_reader.onload = function(e) {
      var sjisAr = e.target.result;
      var i=0;
      for(var hi=hiBegin; hi<=hiEnd; hi++) {
        for(var low=lowBegin; low<=lowEnd; low++) {
          var sjis = (hi & 0x0ff) + ((low <<8) & 0x0ff00);
          var utf16 = sjisAr.substr(i++,1);
          if(utf16!==undefined){
          try {//Unicode16で扱えない文字かどうかチェック
            if(utf16.charCodeAt(0).toString(16)!="3000") {
              var test = "【" + utf16 + "】" + utf16.charCodeAt(0).toString(16);
            }
            if( utf16Ar[utf16] != sjis ) transferCharCodeCount++;//登録した文字数を数えてる
            utf16Ar[utf16] = sjis;
          } catch(e) {//Unicode16で扱えない文字は無視
            alert(e+"sjis="+sjis);
            throw16BitCount++;
          }
          } else {
            undefined16BitCount++;
          }
        }
      }
    }
    file_reader.readAsText(b,"SJIS");
  } catch(e) {
    alert("MakeConvert16bitTable():"+e);
  }
}

CSVをダウンロードするコードは、

//JavaScriptはUnicode(16bit)なので、
//htmlに<input type='button' onclick='exportData("漢字\tあいうえお\tカキクケコ\r\n")'>と書いて呼び出す。
//\tがデリミタとして判定されない場合は , に変える。
function exportData(unicode16CSV) {
  var sjis = UTF16toSJIS(unicode16CSV);
  var blob = new Blob([sjis], {type: "application/octet-stream"});
  var url = URL.createObjectURL(blob);
//ここはHTMLにダウンロード用のaタグを貼っておいて再利用した方がいい。
  var a = document.querySelector("#results");
  a.href = url;
  a.download = "シフトJISコードで作ったCSVファイル.csv";
  a.text="re-download";
  a.click();
}

日本語のファイル名でダウンロードするとファイル名が文字化けするブラウザもあるだろう
画面でCSVデータを編集してCSVにするサンプル
↑2018/10/2修正済(あるいは改悪済)
〇 FireFox(62.0.2 (64ビット)),Chrome(バージョン: 69.0.3497.100(Official Build) (64 ビット)),IE Ege(バージョン不明)で治ってるように見えた。
× IEではダウンロード不可だった。
ps.2018/1/4
今では文字化けしている。サロゲートペアを考慮していないせいだと思うけど・・・
※サロゲートペア:16ビット固定の文字表現であったUnicodeので未使用でだった0xD800~0xDBFFを上位サロゲート、0xDC00~0xDFFFを下位サロゲートとし、上位サロゲート+下位サロゲートの4バイトで文字を表現する文字を拡張したものがUTF-16らしい。
※2018/10/2 FireFoxで8ビットコードが化けまくっていたので以下修正。
おかしくなっていた原因は、
(1)16ビット文字のマップを作るつもりが、英字が半角文字に変換されていた。⇒マップに登録済みの文字は無視する。
⇒まだ、上位ビットがFF(1111)の文字だけ、うまく変換できない。
「a」(0xff41)は、なぜか成功するけど
「U」(0x0ff35)は「ガ」に、
「1」(0x0ff11)が「・」に
なってしまう」。
(2)nullコードは’\u0000’で判定しないとダメだった。
(3)ダウンロード用に作成したSJIS(バイナリー)データにnullが混じっていた⇒null部分はカット。

var utf16Ar;
function UTF16toSJIS(utf16) {
  try{
    var i = 0;
    var a = utf16.split('');
    //alert(a.length);
    var rc = new Uint8Array(a.length*2);
    var countSP=0;
    var ch16=null;
    var j=0;
    for(var i=0; i< a.length; i++) {
      var ch = a[i];
      if(utf16Ar[ch.charCodeAt(0)] === undefined)
        rc[j++] = (0x20);
      else
        ch16 = (utf16Ar[ch.charCodeAt(0)]);
      if((ch16 >> 8)==0) {
        rc[j++] = ch16;
        countSP++;
      } else {
        rc[j++] = ch16 & 0x0ff;
        rc[j++] = ch16 >> 8;
      }
    }
    var rc8 = new Uint8Array(j);
    for(var i=0; i< j; i++) {
      rc8[i] = rc[i];
    }
    return rc8;
  } catch(e) {
    alert(e);
  }
}
function MakeConvert8bitTable() {
  try {
    // 8bit area
    var buffer1 = new Uint8Array(256);
    for(var i=0; i<256; i++) {
      if(0x00<=i && i <= 0x1f) {
        switch(i) {
        case  9 : case 10 : case 13 :
          buffer1[i] = i;
          break;
        default:
        buffer1[i]=0;
          break;
        }
      } else if(0x80<=i && i <=0xa0) {
        buffer1[i]=0;
      } else if(0xe0<=i && i <=0xff) {
        buffer1[i]=0;
      } else {
        buffer1[i] = i;
      }
    }
    // download sjis code list file
    if(downloadSjisCodeList) {
      var b = new Blob( [buffer1], {type: "application/octet-stream"});
      var url = URL.createObjectURL(b);
      var a = document.querySelector("#results");
      a.href = url;
      a.download = "sjis8.txt";
      a.text="re-download";
      a.click();
    }
    var b = new Blob( [buffer1], {type: "application/octet-stream"});
    var file_reader = new FileReader();
    file_reader.onload = function(e) {
      var sjisAr = e.target.result;
      utf16Ar = new Array();
      for(var i=0;i<256;i++) {
        var utf16 = sjisAr.substr(i,1);
        if( utf16 != '\u0000' && utf16Ar[utf16.charCodeAt(0)] === undefined ){
          utf16Ar[utf16.charCodeAt(0)] = buffer1[i];
        }
      }
    }
    file_reader.readAsText(b,"SJIS");
  } catch(e) {
    alert(e);
  }
}
var callCount=0;
function MakeConvert16bitSubTable(hiBegin,hiEnd,lowBegin,lowEnd) {
  try {
    // 16bit area
    callCount++;
    var sizeHi  = ( hiEnd  - hiBegin  + 1 );
    if(isNaN(sizeHi)) { alert("Nan sizeHi"); }
    var sizeLow = ( lowEnd - lowBegin + 1 );
    if(isNaN(sizeLow)) { alert("Nan sizeLow"); }
    var sizeAr  = sizeHi * sizeLow;
    if(isNaN(sizeAr)) { alert("Nan sizeHi,sizeHi("+sizeHi+") * sizeLow("+sizeLow+")"); }
    var buffer1 = new Uint16Array(sizeAr);
    var n1=0;
    var tx = "";
    for(var hi=hiBegin; hi<=hiEnd; hi++) {
      for(var low=lowBegin; low<=lowEnd; low++) {
        buffer1[n1++] = (hi & 0x0ff) + ((low <<8) & 0x0ff00);
      }
    }
    // download sjis code list file
    if(downloadSjisCodeList) {
      var b = new Blob( [buffer1], {type: "application/octet-stream"});
      var url = URL.createObjectURL(b);
      var a = document.querySelector("#results");
      a.href = url;
      a.download = "sjis16(" + callCount + ").txt";
      a.text="re-download";
      a.click();
    }
    var b = new Blob( [buffer1], {type: "application/octet-stream"});
    var file_reader = new FileReader();
    file_reader.onload = function(e) {
      var sjisAr = e.target.result;
      var i=0;
      var count=0;
      for(var hi=hiBegin; hi<=hiEnd; hi++) {
        for(var low=lowBegin; low<=lowEnd; low++) {
          var sjis = (hi & 0x0ff) + ((low <<8) & 0x0ff00);
          var utf16 = sjisAr.substr(i++,1);
          try {
            if( utf16 != '\u0000' && utf16Ar[utf16.charCodeAt(0) ] === undefined ){
              utf16Ar[utf16.charCodeAt(0) ] = sjis;
            }
          } catch(e) {
            // invalidated char
          }
        }
      }
    }
    file_reader.readAsText(b,"SJIS");
  } catch(e) {
    alert(e);
  }
}

※2023/3/7 クラシック版エディタの記事だったので差替え




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA