フィードバックからのCSV出力の漢字コード

フィードバックからのCSV出力の漢字コード

- Takumi NAKANO の投稿
返信数: 9

Moodle1.9(CentOS)にフィードバックモジュールを追加して,アンケート結果をCSV出力したら,漢字コードがおかしくなりました.
Moodle1.8.4でも同じ症状がでたので,analysis_to_excel.phpの66行あたりの
 $workbook -> setVersion(8);
を10に変更して,正常になりましたが,今回のバージョンfeedback_19では,その他の変更も必要みたいです.
正常に出力されている実績や修正の方法がわかれば,お知らせください.

Takumi NAKANO への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

 前々から気になっていましたので,これを機にfeedbackモジュールをインストールしてみました.fs_moodle2.9b(Moodle1.9.1+),WindowsXPです.

 化けているのはExcelのシート内の文字でしょうか? 当方では特に文字化けしていません.ただ,シート名(画面下部のタブ)の部分は化けています.この問題は随分手を入れたはずなのですが,PEARライブラリではなく,個別のモジュール等で対策しなければならない箇所があったかも知れません...

 シート名に関する文字化けが問題であるならば,こちらの情報が役立つかも知れません.もしかしたらここで私が行なった修正のお陰でExcelの内部フォーマットの異常が訂正されていて,シート名だけが化けているのかも知れません.当方でエクスポートしたExcelのファイルを添付します.


確かsetVersion()のお話が以前に出たのもシート名絡みでExcelのファイルが異常になる問題だったと思いますので,Shirai028が関わっている公算が高いです.

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

 既に報告されているのかも知れませんが,シート名が化ける問題については,明白なミスがありますねぇ.でも,それで解決するわけではいまのところありません.あくまで調査の途中です.仲野先生仰るとおり,mod/feedback/analysis_to_excel.phpの冒頭,

    $filename = "feedback.xls";
   
    //get the groupid for this module
    //get the groupid
    $mygroupid = $SESSION->feedback->lstgroupid;

    // Creating a workbook
    $workbook = new EasyWorkbook("-");
    $workbook->setTempDir($CFG->dataroot.'/temp');
    $workbook->send($filename);
    $workbook->setVersion(8);
    // Creating the worksheets
    $sheetname = clean_param($feedback->name, PARAM_ALPHANUM);
    error_reporting(0);
    $worksheet1 =& $workbook->addWorksheet(substr($sheetname, 0, 31));
    $worksheet1->set_workbook($workbook);
    $worksheet2 =& $workbook->addWorksheet('detailed');
    $worksheet2->set_workbook($workbook);

この2行は,非Latin圏殺しのコードですね,どちらも.

 フィードバックの名前に日本語文字列を用いていると,PARAM_ALPHANUMとされることで$sheetnameはNULLになってしまいます.
 ここをPARAM_RAW(PARAM_MAULTILANGもアリでしたか?)にすれば,とりあえずNULLになるのは防げます.

 でも,そのあとのsubstr()でマルチバイトのUTF-8は分断されてしまう危険性があります.


とはいうものの,2枚作成されるワークシートの2枚目は"detailed"ですので化けないはずですし,1枚目のワークシートの名前を$sheetname = "aaaa"のように強制的に書き換えてもシート名が文字化けしますねぇ.なんでだろうか.

 

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

//    $workbook->setVersion(8);
    $workbook->setVersion(10);

とすると,ANK文字のシート名は正常に出力されるのに対して本文は化けまくりですね.また,シート名も"FeedbackTest"(シート1,つまりフィードバックの名前)は"Feedba"で切れて,"detailed"(シート2)は"deta"に切れています.

 これはちょっとおかしいですよ.っと,思ってよーくソースを見直してみたら,頭のところに,

    require_once('easy_excel.php');

というのがありますね.もしや,と思ってみたところ,function addWorksheet()はPEARのライブラリではなく,feedbackモジュールに同梱されているeasy_excel.phpのものを使用しているようですね.ここでワークシート名を短くしたりといった作業が行われている可能性が高い.これがPEARのライブラリのラッパーなのか,それともここで全ての作業が完了しているのか分かりませんが.ちょっとeasy_excel.phpを調査する必要がありそうですね.

#以上はfs_moodle(Excelのシート名文字化け対策済み)での話ですので,オリジナルのMoodleとは少し事情が違うかも知れません.

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

ちなみに,easy_excel.php(feedbackモジュール同梱)のコードの中に,何箇所か現れる,

        if(!isset($CFG->latinexcelexport) || !$CFG->latinexcelexport) {
            $worksheet->setInputEncoding('UTF-16LE');
            // $worksheet->setInputEncoding('utf-8');
        }

このような表現.$CFG->latinexcelexport,重要な意味があるのかも知れません.Excelのデータを書き出すSpreadsheet_Excel_Writer_Worksheet()などでも使われています.(実は非Latin言語圏に優しいコード?)

 特に最後のfunction feedback_convert_to_win()関数などは,重要な気がしますね.


easy_excel.phpの中にはsetVersion()は含まれて居ませんが,

require_once 'Spreadsheet/Excel/Writer.php';

を読んでいますので,やはりPEARライブラリを用いていますね.

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

 とりあえず当方の環境(fs_moodle)ではANK文字でもシート名が文字化けしてしまう問題はこちらで示したaddWorksheet()同様に,

        if (function_exists('mb_convert_encoding')) $name = mb_convert_encoding($name, 'UTF-16LE', 'UTF-8');  // (追加)
        if ($name == '') {
            $name = $sheetname.($index+1);
        }

とすることで化けなくなりました.UTF-8ではなく,シート名はUTF-16LEでセットしないといけない,これはLatin圏でも”日本語Excel”用のデータを出力する場合は同じようです.

 仲野先生の環境ですと,さらに,lib/pear/Spreadsheet/Excel/Writer/Workbook.phpに対して,(Shirai028)の(b)と(c)の修正も必要かも知れません.ちょっと試してみて貰えます? その際にはsetVersion(10)をsetVersion(8)に戻すのをお忘れなく.


日本語文字を含むfeedback名でも正しくワークシート名が代入されるのを確認しました.ちょっとsubstr()をmb_strcut()に置き換えないとおかしくなる可能性も残っていますが,試験的にはOKです.またこの問題には後日まとめを書きますが,まずは(fs_moodleではない)仲野先生の環境でうまく行くか,ですね.

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

http://moodle.org/mod/forum/discuss.php?d=86252

 こちらにも似たような議論がありました.

 半年前のことはすっかり忘れていますね...五月女さんにも随分と色々と調べて頂いておきながら,自分では完全に何を書いたか覚えていませんでした.やはりsetVersion(10)はあまり良い解決方法では無さそうですよ(半年の間に状況が変わっていないのであれば).

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コード

- Tatsuya Shirai の投稿

 動作報告待ちに加えて業務多忙のため,まとめを先送りしていたのですが,そろそろまとめます.

 その際に先の非Latin言語圏殺しのコードを修正しようと思ったときに気になったのが,

  1. substr()でシート名を31byteに制限しているが,これは31byteなのか31文字なのか? (答えは,Excel95までは31byte,Excel97以降はUTF-16LEなので31文字)
  2. PARAM_ALPHANUMをPARAM_MULTI_LANGにするだけで良いのか? (シート名に使用不可な文字はアンダーバーに置き換えるなどの作業も必要)

です.2に関しては,「ログ」や「課題」の場合はシート名が自動生成されるのでシート名に使用不可な文字を与えられる恐れが無いのに対して,フィードバックモジュールの場合はフィードバックの名前としてユーザが自由に入力可能な文字列をシート名に使うので,考慮する必要があります.たとえば'['や']'など.

 この2つに対応したコードが完成したら報告します.でも,できれば先の修正でオリジナル(あるいは三重大版Moodle)に最新のフィードバックモジュールを組み合わせで正常にExcelファイルをダウンロードできた,という報告も頂けると安心です.


参考URL: http://ittechinf.wiki.zoho.com/Excel-VBA-Tips.html

Tatsuya Shirai への返信

Re: フィードバックからのCSV出力の漢字コードだ,

- Takumi NAKANO の投稿

迅速な確認,ありがとうございます.
まだ,テストが出来ていません.
漢字コードがおかしいのは,シートに書いてある文字です.
念のため,添付します.
修正を参考に,やってみます.

Takumi NAKANO への返信

Re: フィードバックからのCSV出力の漢字コードだ,

- Tatsuya Shirai の投稿

 まず仲野先生が添付されたfeedback.xlsの中身をバイナリエディタで見てみました.
 シート名に関するデータフォーマットはSheet1/detailedとASCIIコードで入力されており,シート名の前に付くバイナリデータはそれぞれ1byteの数字(Sheet1:06,detailed: 08)でした.古いフォーマットのようです.試しに拡張子をxlw(Excel4)に変えて読み込ませてみたのですがダメでした.本文中のデータはバイナリエディタ上で見てみるとUTF-8とUCS-2(UTF-16)の日本語文字データが混在していましたので,これらのデータがシフトJISでないとこの古いフォーマットでは対応できないのでしょう.

 さて,仕事が一段楽したのでまとめます.
 ワークシート名は31文字まで(ANKでも31文字,日本語でも31文字)入力可能であることを確認済みです.さらにExcelのワークシート名として使用不可な文字が含まれる場合は'_'に置き換えられることも確認済みですので安心です.


(1) mod/feedback/analysis_to_excel.php

    // Creating a workbook
    $workbook = new EasyWorkbook("-");
    $workbook->setTempDir($CFG->dataroot.'/temp');
    $workbook->send($filename);
    $workbook->setVersion(8);
    // Creating the worksheets
// (Shirai073): フィードバックモジュールのエクスポート(Excel形式)においてExcelファイルが文字化け(あるいはシート名が文字化け)する問題 (2008/07/11)
// (Shirai073): 以下修正
//
  $sheetname = clean_param($feedback->name, PARAM_ALPHANUM);
    $sheetname = clean_param($feedback->name, PARAM_MULTILANG);
// (Shirai073): ここから追加
    $sheetname = mb_ereg_replace('[#\[\]\/:*?]', '_', $sheetname);  // シート名に使用不可な文字を'_'に置換
// (Shirai073): ここまで追加
    error_reporting(0);
// (Shirai073): ここから修正
//
  $worksheet1 =& $workbook->addWorksheet(substr($sheetname, 0, 31));
    $worksheet1 =& $workbook->addWorksheet(mb_substr($sheetname, 0, 31));
// (Shirai073): ここまで修正
    $worksheet1->set_workbook($workbook);
    $worksheet2 =& $workbook->addWorksheet('detailed');
    $worksheet2->set_workbook($workbook);
    error_reporting($CFG->debug);

(2) mod/feedback/easy_excel.php

 class EasyWorkbook extends Spreadsheet_Excel_Writer {
    function &addWorksheet($name = ''){
        global $CFG;
        
        $index      = count($this->_worksheets);
        $sheetname = $this->_sheetname;

// (Shirai073): フィードバックモジュールのエクスポート(Excel形式)においてExcelファイルが文字化け(あるいはシート名が文字化け)する問題 (2008/07/11)
// (Shirai073): ここから追加
        if (function_exists('mb_convert_encoding')) $name = mb_convert_encoding($name, 'UTF-16LE', 'UTF-8');
// (Shirai073): ここまで追加
        if ($name == '') {
            $name = $sheetname.($index+1);
        }

        // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
        if ($this->_BIFF_version != 0x0600)
        {
            if (strlen($name) > 31) {
                return $this->raiseError("Sheetname $name must be <= 31 chars");
            }
        }


以上のfeedbackモジュールの修正(Moodle1.8用, Moodle1.9用共通)に加え,以下のPEARライブラリの修正も必要です.(a)から(c)のうち,feedbackモジュールに関わるのは(b)と(c)のみですが,(a)の修正も行なわないと他のExcel形式でのエクスポート機能に悪影響が出る可能性があります.

(3) lib/pear/Spreadsheet/Excel/Writer/Workbook.php

(a) function &addWorksheet(), 321行近辺

    function &addWorksheet($name = '')
    {
        $index     = count($this->_worksheets);
        $sheetname = $this->_sheetname;

        if (function_exists('mb_convert_encoding')) $name = mb_convert_encoding($name, 'UTF-16LE', 'UTF-8');  // (追加)
        if ($name == '') {
            $name = $sheetname.($index+1);
        }

(b) function _storeBoundsheet() : 945行近辺

     function _storeBoundsheet($sheetname,$offset)
    {
      $record    = 0x0085;                    // Record identifier
        if ($this->_BIFF_version == 0x0600) {
            $length    = 0x08 + strlen($sheetname); // Number of bytes to follow
        } else {
            $length = 0x07 + strlen($sheetname); // Number of bytes to follow
        }

        $grbit     = 0x0000;                    // Visibility and sheet type
//      $cch       = strlen($sheetname);        // Length of sheet name
        $cch       = function_exists('mb_strlen') ? mb_strlen($sheetname, 'UTF-16LE') : strlen($sheetname); // Length of sheet name  //
        $cch      += 0x0100;  // (追加)

        $header    = pack("vv",  $record, $length);
        if ($this->_BIFF_version == 0x0600) {
            $data      = pack("Vvv", $offset, $grbit, $cch);
        } else {
            $data      = pack("VvC", $offset, $grbit, $cch);
        }
        $this->_append($header.$data.$sheetname);
    }

(c) function _storeExternsheet(): 1104行近辺

    function _storeExternsheet($sheetname)
    {
        $record      = 0x0017;                     // Record identifier
        $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow

//      $cch         = strlen($sheetname);         // Length of sheet name
        $cch         = function_exists('mb_strlen') ? mb_strlen($sheetname) : strlen($sheetname);      // Length of sheet name
        $rgch        = 0x03;                       // Filename encoding

        $header      = pack("vv",  $record, $length);
        $data        = pack("CC", $cch, $rgch);
        $this->_append($header . $data . $sheetname);
    }