Posted at

Excelの埋め込みオブジェクトのファイル保存を自動化しようとして、まだできていない話


目的

Excelに埋め込まれたpdfとかのファイルを楽に保存したい。


結論

クリップボード経由でやれなくはなさそうだが、

かなり面倒くさそうなので、自作するのはお勧めできない。

結局できていないですが、

判ったところまでを残したいので投稿します。


やってみたこと(1/2) - VBA or C#からExcel操作する

埋め込みオブジェクトは _OLEObjectのようである。

プロパティやメソッドを探すも、使えそうなものが見つからなかった。

(埋め込み元のアプリで開いたり(Verb()メソッド)、

埋め込みオブジェクトとしてクリップボードにコピーさせる(Copy()メソッド)くらいしかできなかった。

Copy()でテンポラリファイルができるというblogもあったが、できず。)

_OLEObject に関するメモ


  • _OLEObjectインタフェースは、Microsoft Excel 12.0 Object Library に属している(環境により異なるかも)。


  • _OLEObjectは、sheetオブジェクトのもつ OLEObjects() を呼び出して、foreachとかで要素として取り出せる。


  • _OLEObjectの一部のメソッドやプロパティ(.Objectとか)は、C#で作ってみたが実行時に例外吐いて落ちる。。。



やってみたこと(2/2) - クリップボードのデータを保存する

今回の本題。

Excel Sheet上の埋め込みオブジェクトをコピーすると、クリップボードにちゃんとデータが入る。

クリップボードからEmbedded Objectをファイルに保存するソースコード

IDataObject data = Clipboard.GetDataObject();

if (data != null) {
// 関連付けられているすべての形式を列挙する
// foreach(string fmt in data.GetFormats(){Console.WriteLine(fmt);}

if (data.GetDataPresent("Embedded Object")) {
dynamic obj = data.GetData("Embedded Object");
if ( obj is MemoryStream ) {
var ms = (MemoryStream)obj;
using ( var fs = new FileStream("testout.dat", FileMode.Create) ) {
ms.WriteTo(fs);
}
}
}
}

// Mainメソッドに [STAThread] つけ忘れなきよう・・・


上の処理で、クリップボードのデータをファイルに保存できる。

問題はこれが元データ(EXCELに埋め込まれる前のファイル)のままではなく、謎な形式・・というかOLEになっている。

バイナリエディタで開いてみると先頭がD0 CF 11 E0 A1 B1 1A E1となっていて、調べてみた結果、

[MS-CFB]: Compound File Binary File Format ・・・ (仕様書(Rev 9.0))

という形式らしい。

ファイルシステムを埋め込んだような構造で、割とめんどくさい。。

GitHubさがせばありそう。


参考になりそうなサイト


おまけ

バイナリエディタ(BZ Editor)向けの構造体定義の一部を張っておく




BZ.DEF に下記を付け足し。

struct CFB_HEADER {

BYTE signature [8];
BYTE clsid [16];
WORD minorVersion;
WORD majorVersion;
WORD byteOrder;
WORD sectorSize;
WORD miniStream;
BYTE reserved1[6];
DWORD numOfDirSectors;
DWORD numOfFatSectors;
DWORD dirStartSectLoca;
DWORD tranSignature;
DWORD miniStreamSizeCo;
DWORD miniFatStartSectLoca;
DWORD numOfMiniFatSectors;
DWORD difatStartSectLoca;
DWORD numOfDifatSectors;
DWORD difat[109];
} cfb_header;

struct CFB_DIR_ENTRY {
BYTE nameUtf16 [64];
WORD nameLen;
BYTE ObjectType;
BYTE ColorFlag;
DWORD LeftSiblingID;
DWORD RightSiblingID;
DWORD ChildID;
BYTE CLSID [16];
DWORD StateBits;
BYTE CreationTime [8];
BYTE ModifiedTime [8];
DWORD StartSectLoca;
DWORD StreamSize[2];
} cfb_dir_entry;


乱暴なやりかた:




Sectorの概念を無視してデータが連番のセクターに保存されていると仮定すれば、

Name(※UTF-16)が"CONTENTS"のdirectory entry上の

(StartingSectorLocation + 1) * 0x0200 = (下記例では 0x2600) から

StreamSizeだけデータを取り出せば取り出せそうだが、

ファイルフォーマットを完全に無視しているので危険。。

image.png

image.png