4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

D言語Advent Calendar 2023

Day 8

D言語でExcel(.xlsx)ファイル読み込み

Posted at

はじめに

この記事では、Excel(.xlsx)ファイルをD言語で読み込む処理について、書きます。
dubパッケージを使用しますが、そのまま使うだけでは正しく動作しない場合があります。
その回避策についても、情報提供します。

XLSXファイルを読み込むためのdubパッケージ

以下のdubパッケージが提供されています。
mir-excel
xlsx
xlsxreader

この記事では、xlsxreaderを使用します。

開発環境

ソースコードのコンパイルは、以下のバージョンのDMDを使います。

インプットとなるExcelファイルは、このページにあるものを使います。
(何でもよかったのですが、ネット上で手に入るExcelファイルということで)

問題点:うまくいかない理由

XLSXファイルは内部にXML形式でデータを持っていますが、ExcelLibreOffice Calcで作成したXLSXファイルでは、XMLの構造に違いがあります。
具体的には、Excelで作成した場合、<t>タグが入れ子で存在する場合があります。
LibreOffice Calcでは入れ子の<t>タグは生成されません。
この差により、Excelの処理では文字列を正しく取得できないという問題が発生します。
どのdubパッケージを使っても同じ問題が発生するため、もしかしたらExcel日本語版特有の問題なのかもしれません。

excel.png

回避策とソースコード

問題を回避するために、以下のようなソースコードを実装しました。
具体的には、xlsxreaderパッケージのソースを参考にreadSheetImpl2を実装しました。

readexcel.d
/+ 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ホールディングス, スタンダード

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?