Moodle1.9(CentOS)にフィードバックモジュールを追加して,アンケート結果をCSV出力したら,漢字コードがおかしくなりました.
Moodle1.8.4でも同じ症状がでたので,analysis_to_excel.phpの66行あたりの
$workbook -> setVersion(8);
を10に変更して,正常になりましたが,今回のバージョンfeedback_19では,その他の変更も必要みたいです.
正常に出力されている実績や修正の方法がわかれば,お知らせください.
前々から気になっていましたので,これを機にfeedbackモジュールをインストールしてみました.fs_moodle2.9b(Moodle1.9.1+),WindowsXPです.
化けているのはExcelのシート内の文字でしょうか? 当方では特に文字化けしていません.ただ,シート名(画面下部のタブ)の部分は化けています.この問題は随分手を入れたはずなのですが,PEARライブラリではなく,個別のモジュール等で対策しなければならない箇所があったかも知れません...
シート名に関する文字化けが問題であるならば,こちらの情報が役立つかも知れません.もしかしたらここで私が行なった修正のお陰でExcelの内部フォーマットの異常が訂正されていて,シート名だけが化けているのかも知れません.当方でエクスポートしたExcelのファイルを添付します.
確かsetVersion()のお話が以前に出たのもシート名絡みでExcelのファイルが異常になる問題だったと思いますので,Shirai028が関わっている公算が高いです.
既に報告されているのかも知れませんが,シート名が化ける問題については,明白なミスがありますねぇ.でも,それで解決するわけではいまのところありません.あくまで調査の途中です.仲野先生仰るとおり,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"のように強制的に書き換えてもシート名が文字化けしますねぇ.なんでだろうか.
// $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とは少し事情が違うかも知れません.
ちなみに,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ライブラリを用いていますね.
とりあえず当方の環境(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ではない)仲野先生の環境でうまく行くか,ですね.
http://moodle.org/mod/forum/discuss.php?d=86252
こちらにも似たような議論がありました.
半年前のことはすっかり忘れていますね...五月女さんにも随分と色々と調べて頂いておきながら,自分では完全に何を書いたか覚えていませんでした.やはりsetVersion(10)はあまり良い解決方法では無さそうですよ(半年の間に状況が変わっていないのであれば).
動作報告待ちに加えて業務多忙のため,まとめを先送りしていたのですが,そろそろまとめます.
その際に先の非Latin言語圏殺しのコードを修正しようと思ったときに気になったのが,
- substr()でシート名を31byteに制限しているが,これは31byteなのか31文字なのか? (答えは,Excel95までは31byte,Excel97以降はUTF-16LEなので31文字)
- PARAM_ALPHANUMをPARAM_MULTI_LANGにするだけで良いのか? (シート名に使用不可な文字はアンダーバーに置き換えるなどの作業も必要)
です.2に関しては,「ログ」や「課題」の場合はシート名が自動生成されるのでシート名に使用不可な文字を与えられる恐れが無いのに対して,フィードバックモジュールの場合はフィードバックの名前としてユーザが自由に入力可能な文字列をシート名に使うので,考慮する必要があります.たとえば'['や']'など.
この2つに対応したコードが完成したら報告します.でも,できれば先の修正でオリジナル(あるいは三重大版Moodle)に最新のフィードバックモジュールを組み合わせで正常にExcelファイルをダウンロードできた,という報告も頂けると安心です.
迅速な確認,ありがとうございます.
まだ,テストが出来ていません.
漢字コードがおかしいのは,シートに書いてある文字です.
念のため,添付します.
修正を参考に,やってみます.
まず仲野先生が添付された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);
}