LoginSignup
1
1

More than 3 years have passed since last update.

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

Posted at

目的

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

1
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
1
1