はじめに
この記事では、Excel(.xlsx)
ファイルをD言語で読み込む処理について、書きます。
dub
パッケージを使用しますが、そのまま使うだけでは正しく動作しない場合があります。
その回避策についても、情報提供します。
XLSXファイルを読み込むためのdubパッケージ
以下のdub
パッケージが提供されています。
mir-excel
xlsx
xlsxreader
この記事では、xlsxreader
を使用します。
開発環境
ソースコードのコンパイルは、以下のバージョンのDMD
を使います。
- DMD v2.105.3
インプットとなるExcel
ファイルは、このページにあるものを使います。
(何でもよかったのですが、ネット上で手に入るExcel
ファイルということで)
問題点:うまくいかない理由
XLSX
ファイルは内部にXML
形式でデータを持っていますが、Excel
とLibreOffice Calc
で作成したXLSX
ファイルでは、XML
の構造に違いがあります。
具体的には、Excel
で作成した場合、<t>
タグが入れ子で存在する場合があります。
LibreOffice Calc
では入れ子の<t>
タグは生成されません。
この差により、Excel
の処理では文字列を正しく取得できないという問題が発生します。
どのdub
パッケージを使っても同じ問題が発生するため、もしかしたらExcel
日本語版特有の問題なのかもしれません。
回避策とソースコード
問題を回避するために、以下のようなソースコードを実装しました。
具体的には、xlsxreader
パッケージのソースを参考にreadSheetImpl2
を実装しました。
/+ dub.sdl:
name "readexcel"
dependency "xlsxreader" version="~>3.0.1"
+/
// dub build --build=release --single readexcel.d
import std.file : exists;
import std.path : extension;
import std.stdio;
import std.string : toLower;
import xlsxreader;
void main(string[] args)
{
if ( args.length < 2 ){
writeln("Error : Requires parameters");
return;
}
string file = args[1];
if ( !file.exists ){
writefln("Error : Not exists %s", file);
return;
}
if ( file.extension.toLower() == ".xlsx" ){
file.readFile("HP用");
} else {
writeln("Error : Requires .xlsx file");
}
}
void readFile(string fileName, string sheetName = "Sheet1")
{
writefln("File = %s", fileName);
writefln("Sheet = %s", sheetName);
Sheet sheet = readSheet2(fileName, sheetName);
writefln("row = %d, col = %d", sheet.maxPos.row + 1, sheet.maxPos.col + 1);
for ( int row = 3; row <= sheet.maxPos.row; row++ ){
if ( sheet.getString(row, 1) == "" ){
break;
}
writefln("%s, %s, %s",
sheet.getString(row, 1),
sheet.getString(row, 2),
sheet.getString(row, 6));
}
}
string getString(Sheet sheet, string sPos)
{
Pos p = toPos(sPos);
return ( sheet.getCell(p.row, p.col).xmlValue );
}
string getString(Sheet sheet, size_t row, size_t col)
{
return ( sheet.getCell(row, col).xmlValue );
}
Cell getCell(Sheet sheet, size_t row, size_t col)
{
if ( row > sheet.maxPos.row || col > sheet.maxPos.col ){
return ( Cell.init );
}
return ( sheet.table[row][col] );
}
Sheet readSheet2(in string filename, in string sheetName) @safe {
import std.algorithm : filter;
SheetNameId[] sheets = sheetNames(filename);
auto sRng = sheets.filter!(s => s.name == sheetName);
return ( readSheetImpl2(filename, sRng.front.rid) );
}
Sheet readSheetImpl2(in string filename, in string rid) @trusted {
import std.file : read;
import std.zip;
auto file = new ZipArchive(read(filename));
auto ams = file.directory;
immutable ss = "xl/sharedStrings.xml";
string[] sharedStrings = (ss in ams)
? readSharedEntries(file, ams[ss])
: [];
string[] sharedStrings2;
for ( int n = 0; n < sharedStrings.length; n++ ){
if ( sharedStrings[n] != "" ){
sharedStrings2 ~= sharedStrings[n];
}
}
Relationships[string] rels = parseRelationships(file,
ams["xl/_rels/workbook.xml.rels"]);
Relationships* sheetRel = rid in rels;
string shrFn = eatXlPrefix(sheetRel.file);
string fn = "xl/" ~ shrFn;
ArchiveMember* sheet = fn in ams;
Sheet ret;
ret.cells = insertValueIntoCell(readCells(file, *sheet), sharedStrings2);
Pos maxPos;
foreach(ref c; ret.cells) {
c.position = toPos(c.r);
maxPos = elementMax(maxPos, c.position);
}
ret.maxPos = maxPos;
ret.table = new Cell[][](ret.maxPos.row + 1, ret.maxPos.col + 1);
foreach(const c; ret.cells) {
ret.table[c.position.row][c.position.col] = c;
}
return ( ret );
}
回避策のキモは、問題のあるreadSharedEntries
呼び出しの後に追加した、以下の処理です。
string[] sharedStrings2;
for ( int n = 0; n < sharedStrings.length; n++ ){
if ( sharedStrings[n] != "" ){
sharedStrings2 ~= sharedStrings[n];
}
}
// ・・・省略・・・
ret.cells = insertValueIntoCell(readCells(file, *sheet), sharedStrings2);
コンパイルと実行結果
いつも通り、コンパイルと実行結果を記します。
D:\Dev> dub build --build=release --single readexcel.d
Fetching dxml 0.4.4 (getting selected version)
Fetching xlsxreader 3.0.1 (getting selected version)
Starting Performing "release" build using D:\App\Dev\dmd2\windows\bin64\dmd.exe for x86_64.
Building dxml 0.4.4: building configuration [library]
Building xlsxreader 3.0.1: building configuration [library]
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(110,53): Deprecation: `@safe` function `getColumn` calling `array`
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(66,8): which wouldn't be `@safe` because of:
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(66,8): scope variable `range` assigned to non-scope parameter `items` calling `put`
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(159,56): Deprecation: `@safe` function `getRow` calling `array`
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(66,8): which wouldn't be `@safe` because of:
C:\Users\user1\AppData\Local\dub\packages\xlsxreader\3.0.1\xlsxreader\source\xlsxreader.d(66,8): scope variable `range` assigned to non-scope parameter `items` calling `put`
Building readexcel ~master: building configuration [application]
Linking readexcel
Finished To force a rebuild of up-to-date targets, run again with --force
D:\Dev> readexcel kessan09_1102.xlsx
File = kessan09_1102.xlsx
Sheet = HP用
row = 3133, col = 7
2753, あみやき亭, プライム
2408, KG情報, スタンダード
3593, ホギメディカル, プライム
・・・省略・・・
8160, 木曽路, プライム
9973, 小僧寿し, スタンダード
9980, MRKホールディングス, スタンダード