こんにちは、ふちがみです。
前回の記事でHTMLのテーブルをCSVにしてダウンロードするJavascriptを作ってみましたが、
クロスブラウザ対応と、文字コード変換を後まわしにしていました。

今回は、その2つの対応を行いましたので、ご紹介したいと思います。
まずは完成版のコードとサンプルを先に示し、今回追加した処理を後で説明します。

完成版サンプル

完成版コード

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
table, th, td {
  border: 1px solid;
}
</style>
<script src="./encoding.min.js"></script>
<script>
var tableToCSV = {
  export: function(elm /*, delimiter */) {
    var table = elm;
    var rows  = this.getRows(table);
    var lines = [];
    var delimiter = delimiter || ',';

    for (var i = 0, numOfRows = rows.length; i < numOfRows; i++) {
      var cols    = this.getCols(rows[i]);
      var line = [];

      for (var j = 0, numOfCols = cols.length; j < numOfCols; j++) {
          var text = cols[j].textContent || cols[j].innerText;
          text = '"'+text.replace(/"/g, '""')+'"';

          line.push(text);
      }

      lines.push(line.join(delimiter));
    }

    return lines.join("\r\n");
  },

  getRows: function(elm){
    return Util.getNodesByName(elm, 'tr');
  },

  getCols: function(elm){
    return Util.getNodesByName(elm, ['td', 'th']);
  }
}

var Util = {
  getNodesByName: function(elm /*, string or array*/) {
    var children  = elm.childNodes;
    var nodeNames = ('string' === typeof arguments[1]) ? [arguments[1]] : arguments[1] ;
    nodeNames = nodeNames.map(function(str){ return str.toLowerCase() });

    var results  = [];

    for (var i = 0, max = children.length; i < max; i++ ) {
      if (nodeNames.indexOf(children[i].nodeName.toLowerCase()) !== -1)
      {
         results.push(children[i]);
      }
      else
      {
         results = results.concat(this.getNodesByName(children[i], nodeNames));
      }
    }

    return results;
  }
}

window.onload = function(){
  document.getElementById('download').addEventListener('click', function (e){
    var csv   = tableToCSV.export(document.getElementById('tbl'));

    var sjisArray = Encoding.convert(Encoding.stringToCode(csv), {to: 'SJIS'});

    var blob  = new Blob([new Uint8Array(sjisArray)], {type: 'text/csv'});

    if (window.navigator.msSaveBlob) {
      e.preventDefault();
      window.navigator.msSaveBlob(blob, this.getAttribute('download'));
    }
    else {
      this.href = URL.createObjectURL(blob);
    }
  });
}

</script>
</head>
<body>

<table id="tbl">
  <thead>
    <tr>
      <th><strong>店舗名</strong></th>
      <th>メールアドレス</th>
      <th>電話番号</th>
      <th>FAX番号</th>
      <th>所在地</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A店</td>
      <td>a@example.com</td>
      <td>01-1111-1111</td>
      <td>01-1111-0000</td>
      <td>東京都新宿区</td>
    </tr>
    <tr>
      <td>"B"店</td>
      <td>a@example.com</td>
      <td>02-2222-2222,02-2222-2220(採用担当)</td>
      <td>02-2222-0000</td>
      <td>東京都中野区</td>
    </tr>
  </tbody>
</table>

<a href="#" id="download" download="test.csv">CSVダウンロード</a>
</body>
</html>

クロスブラウザ対応

IEではmsSaveBlobという関数を使ってファイルの保存ができるそうです。
window.navigator.msSaveBlob の存在をチェックして、
存在する場合はこの関数でファイル保存するようにしてみました。

msSaveBlobは第2引数に保存するファイル名を取ることができるので、
aタグのダウンロード属性から値を取得してきています。

 if (window.navigator.msSaveBlob) {
   e.preventDefault();
   window.navigator.msSaveBlob(blob, this.getAttribute('download'));
 }
 else {
   this.href = URL.createObjectURL(blob);
 }

文字コードの変換

CSVファイルはExcelで取りまわすことが多いため、文字コードをSJISに変換しました。

Encoding.jsを利用する

今回はencoding.jsというライブラリを使って、
UTF8 -> SJIS 変換を試みました。

 var csv   = tableToCSV.export(document.getElementById('tbl'));
 var sjisArray = Encoding.convert(Encoding.stringToCode(csv), {to: 'SJIS'});
 var blob  = new Blob([new Uint8Array(sjisArray)], {type: 'text/csv'});

このとき、変換結果を配列で受け取ることと、文字列で受け取ることができるのですが、
文字列で受け取ると、どうしてもうまくいかなかったため、配列で受け取る方法を選択しました。