【C#】エクセルからのコピペを string で受け取る

 色々と記事にしないといけないことが山積しているのですけど、ツイッターとやら始めるとブログが疎かになりますねぇ。


 数秒で呟けて後腐れない(私がそう思ってるだけかもしれないが)のに対して、ブログのほうは文章量もさることながら、検索で辿り着いた方々のことも考えてあげたりすると、短い記事でも 30分 くらいかかるので、それなりにエネルギーが要るんですが、(早い日で)8時すぎに家に帰っても、(遅い日で)11時すぎに家に帰っても、どっちであっても風呂に入って飯を食ってビール飲めば30分でバタンキュ〜って生活が続いてるもんで、なかなか・・・


 と言い訳がましいことはこの辺においといて。。


 私、C# を始めて1年ちょっと(2年は経ってない)なのですが、フォロワーさんに喜んでもらえた ので、そんときのコードをお披露目させて頂こうかなと思います。


 エクセルでセルを選択し「クリップボードにコピー」して、自作アプリに貼り付けるとき、エクセル特有の腐った処理があって、貼れないときがあるじゃないですか。

(腐る例)

  • セルの中に "(ダブルクォーテーション)が混じるとき(特に先頭とか)
  • セルの中で改行してるとき
  • セルの中にタブコードが含まれるとき
  • 上記みたいなセルを複数選択してコピーしたとき

http://dl.ftrans.etr.jp/?d0564528c7314dbe95e109cca20c77492ef47ded.jpg
 右の画像がそういうパターンですが、通常の TSV(タブ区切りテキスト)とも微妙に違うんですね。


 セル内に特殊コード(改行/タブ)があるかないかで大きく変わり、ない場合はダブルクォーテーションは特に意識せず普通の文字として取り扱って最初に出くわした改行/タブで項目の最後、という風なのですが、ある場合は、前後にダブルクォーテーションが入り、かつ途中のダブルクォーテーションは×2になり、その上で改行は \n に置換され、タブは普通に \t のまま、って具合です。


 この時点でも十分に腐っていると思いますが、エクセルでコピーしてエディタに貼り付けして、そのエディタに貼り付けた内容をコピーしてエクセルに貼り付け、という風に戻したら、元に戻らないんですよ。
 つまり、エクセルでコピーするときの挙動と貼り付けするときの挙動は違うという・・・


http://dl.ftrans.etr.jp/?cf1d8d413e164089833c214a901aef178c46e646.png


 いやもう何を言ってるのだか・・・って状態かと思いますが(笑)


 とりあえず、自作アプリ→エクセルにおいては、先頭と末尾にダブルクォーテーションを追加して、項目内のダブルクォーテーションはダブルクォーテーション×2に機械的にしてやれば済むので大したことないですが、反対方向のエクセル→自作アプリのほうはマイクロソフトの独自仕様が強烈すぎて結構たいへんです。


 ということで、そこら辺をクリアする関数を作ってみました。

private static string[][] GetClipboardFromExcel()
{
    if (System.Windows.Forms.Clipboard.ContainsData("XML Spreadsheet"))
    {
        // 元がエクセル
        var xmlDoc = new System.Xml.XmlDocument();
        xmlDoc.Load((System.IO.MemoryStream)System.Windows.Forms.Clipboard.GetData("XML Spreadsheet"));
        var strArray = xmlDoc.DocumentElement["Worksheet"]["Table"].ChildNodes.OfType<System.Xml.XmlElement>()
            .Where(a => a.Name.Equals("Row"))
            .Select(
                b => b.ChildNodes.OfType<System.Xml.XmlElement>()
                    .Where(c => c.Name.Equals("Cell"))
                    .Select(d => d["Data"].ChildNodes[0].Value).ToArray()
                )
            .ToArray();
        return strArray;
    }
    else
    {
        // 普通のテキスト形式で受け取る
        var result = new System.Collections.Generic.List<string[]>();
        var result = new System.Collections.Generic.List<string[]>();
        using (var sr = new System.IO.StringReader(System.Windows.Forms.Clipboard.GetText(System.Windows.Forms.TextDataFormat.UnicodeText)))
        using (var parse = new Microsoft.VisualBasic.FileIO.TextFieldParser(sr))
        {
            parse.SetDelimiters("\t");
            while(!parse.EndOfData)
                result.Add(parse.ReadFields());

            parse.Close();
            sr.Close();
        }
        return result.ToArray();
    }
}

 エクセル形式じゃなかったら通常のテキストで受け取ってますが、Microsoft.VisualBasic.FileIO.TextFieldParser で誤魔化してるあたり、笑って許してくださいませ〜