🔄

ワード文書とテキストファイルの相互変換(C#+OpenXML)

2024/01/19に公開

はじめに

発端

覚え書きとしてテキストファイルにメモを残すことが多々あります。しかし、とあるオンラインストレージでは、テキストファイルをアップロードし外部ネットワークからアクセスしたときに、そのテキストファイルの表示ができない仕様となっています。ファイルは覚え書きメモですので、様々な場所から閲覧できると利便性が高いのですが、これが叶いません。

ところが、そのオンラインストレージでは、ワード文書については、外部アクセスでも閲覧することができ、編集すら可能です。

ですので、オンラインストレージにアップロードする前に、テキストファイルからワード文書へ変換してアップロードすれば、外部からでも覚え書きメモの確認ができると考えました。

変換手順の簡略化

だだ、テキストファイルからワード文書への変換は、以下のような手順が必要となり、煩わしい作業となります。

1.覚え書きメモのテキストファイルをテキストエディタで開く
2.テキストエディタで、テキストを全選択してコピーする
3.ワードを起動し、新規文書を作成する
4.新規文書に、テキストを貼り付けする
5.ワードで、名前を付けて保存する(この際テキストファイルと同名にする)

そこで、テキストファイルからワード文書への変換が簡単にできないかと模索し、Microsoftが公開するOpenXMLというライブラリに行き着きました。このライブラリは、プログラムからワード文書を生成したり操作したりすることができます。インストールはNuGetに対応していたので、C#で試してみました。

今回作成したアプリケーションを利用すると、ワード文書への変換が、次のように1ステップで完了します。(今回のサンプルプログラムの実行ファイル名を「ConvTxtDocxTiny」としたとき。)

1.覚え書きメモのテキストファイルを右クリックして「送る」→「ConvTxtDocxTiny」
→ テキストファイルと同名の(拡張子は .docx の)ワード文書ファイルが生成される

ちなみに、調査途中で見つけた情報では、Pythonの実行環境があればpython-docxというモジュールを利用することで、同じような機能のプログラムが作成できるようです。

開発するアプリケーション

必要な機能

上記説明からすれば、テキストファイルからワード文書への変換機能のみで済みますが、ワード文書を別の場所で編集した場合も考慮し、ワード文書からテキストファイルへの変換機能も追加します。そうすると、今回開発するアプリケーションは、以下のような機能が必要となります。

  • テキストファイル → ワード文書
    • テキストファイルからワード文書への変換機能
    • 入力テキストファイルの文字コード判定機能
  • ワード文書 → テキストファイル
    • ワード文書からテキストファイルへの変換機能
    • 出力テキストファイルの文字コード指定機能

※ 今回、テキストファイルとワード文書の相互変換が目的のため、ワード文書の書式等は全く考慮しません。

OpenXMLライブラリ

正式には「Open XML SDK for Office」と呼ばれます。Microsoft社が公開しており、当然、Microsoft社のOffice製品との相性も良いです。Microsoft社のサイトにて、サンプルコードなども多数公開されており、アプリケーション開発に非常に役立ちます。

参考サイト1:
Open XML SDK for Office へようこそ - Microsoft Learn

MiscrosoftのOffice製品は、Microsoft Office 2007からXMLベースのファイル形式となりました。ファイル拡張子で末尾に「x」が付くファイルです(ワードなら「.docx」、エクセルなら「.xlsx」)。そしてOpenXMLライブラリは、XMLベースのファイル形式を扱うことができるライブラリです。

XMLは、要素と内容が階層的に構築される構造で、OpenXMLライブラリもこの階層を意識したプログラミングとなります。

文字コードの判別

文字コードの判別には、テキストファイルの内容を解析する必要があります。このため、今回のアプリケーションでは、一旦バイト列として読み込んで、文字コードを判別してからテキストへ変換します。

文字コードを判別するコードは、以下の参考サイトのコードを少し変更(関数名などを変更)して使用しました。今回は、dobonNetDetectEncoding.cs というファイルに静的メソッドとして記述しています。

参考サイト2:
文字コードを判別する - .NET Tips (VB.NET,C#...)

OpenXmlライブラリの使い方

基本ドキュメント構造

ワード文書(docx)の基本ドキュメント構造は、WordprocessingMLというXMLを使用しています。たとえば、次のような構造になっています。

<w:document xmlns:w="https://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p>
      <w:r>
        <w:t>テスト文章。</w:t>
      </w:r>
    </w:p>
  </w:body>
</w:document>

要素名('<'と'>'で囲まれた部分)の頭に付くw:は名前空間の指定です。その後ろのdocumentbodyが実際の要素名です。

多くの要素は内容を持ちます。'<要素名>' から '</要素名>' で囲まれた部分が、その要素の内容となります。たとえば、上記XMLの先頭行 <w:document ...> から末尾の </w:document> で囲まれた部分が、document 要素の内容です。document 要素の内容は、さらに別の要素と内容を含んでいることが分かります。このように、XMLは、要素と内容が階層的に構成されます。

さて、上記XMLをもう一度見てみると、ルート要素(一番外側の要素)は document 要素となっており、その中に body 要素があります。そして、body 要素の中に、段落や表などの実際のパーツが配置されます。一般的なワード文書では、document 要素と body 要素は必ず存在します。

p 要素は段落(Paragraph)です。実際の文章は、段落内に入ります。ワード文書の段落は、改行で区切られた1つの固まりのことで、テキストファイルでは「1行」に相当します。

p 要素の内側に r 要素があります。r 要素はセクション(Run)を表します。セクションは、同じ書式(フォント、サイズ、色など)を持つテキスト領域となります。今回は、単純なテキストファイル ←→ ワード文書変換となりますので、書式等は考えず1段落内に1セクションとします。

r 要素の内側は t 要素で、実際の文字(Text)となります。

OpenXMLライブラリを使ったプログラムでは、この構造を生成することでワード文書を作ったり、この構造からテキストを抜き出して保存することでテキストファイルへ変換したりします。

※ 上記XMLは改行や字下げが入っていますが、これらは全く意味のない(あってもなくてもよい)記号です。見やすさだけのために入れられています。

※ ワード文書(.docx)はZIP形式でアーカイブされたファイルです。.docxファイルをZIPツールで展開すると様々なフォルダやファイルが作成されます。このうち以下のファイルが上記構造のファイルです。
展開先フォルダ \ word \ document.xml

コード1:ワード文書の作成

以下は、単純なテキストだけのワード文書を作成するコードの例です。

//...

//■■■ (1) ■■■
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

//...

private void txtToDocx()
{
    //出力ファイル名
    string fn_out = @"C:\code1.docx";

    //■■■ (2) ■■■
    //5行の元テキストを作成(テキストファイルから読み込んだと仮定)
    string[] txt_arr = new string[5];
    for (int i = 0; i < 5; i++)
    {
        txt_arr[i] = $"テスト文章 {i + 1}行目";
    }

    //■■■ (3) ■■■
    //ワード文書を作成
    using (WordprocessingDocument word_doc = 
        WordprocessingDocument.Create(fn_out, WordprocessingDocumentType.Document))
    {
        //■■■ (4) ■■■
        //ワード文書にメインドキュメントパーツを追加
        MainDocumentPart main_part = word_doc.AddMainDocumentPart();

        //■■■ (5) ■■■
        //document要素を生成
        main_part.Document = new Document();

        //■■■ (6) ■■■
        //body要素を作成し、document要素に追加
        Body elem_body = new Body();
        main_part.Document.AppendChild(elem_body);

        //■■■ (7) ■■■
        //元テキストを行毎にループ
        foreach (string txt in txt_arr)
        {
            //■■■ (8) ■■■
            //テキスト(t要素)生成
            Text elem_t = new Text(txt);

            //■■■ (9) ■■■
            //セクション(r要素)を生成し、内部にテキスト(t要素)を追加
            Run elem_r = new Run();
            elem_r.Append(elem_t);

            //■■■ (10) ■■■
            //段落(p要素)を生成し、内部にセクション(r要素)を追加
            Paragraph elem_p = new Paragraph();
            elem_p.Append(elem_r);

            //■■■ (11) ■■■
            //bodyに段落(p要素)を追加
            elem_body.Append(elem_p);
        }
    }
}

コード冒頭のコメント(1)では、usingにより使用する名前空間をデフォルト指定しています。

コメント(2)は、元となるテキストを作成しています。テキストファイルからワード文書へ変換する際は、テキストファイルから読み込みますが、ここでは、5行のテキストファイルを読み込んだと仮定して、要素数5個の文字列配列を作っています。

コメント(3)により、ワード文書を作成しています。Createメソッドの第1引数は作成するファイル名です。第2引数はワード文書の種類で、通常の文書の場合はWordprocessingDocumentType.Documentを指定します。他に、テンプレートファイルやマクロファイルの指定が可能です。

コメント(4)では、ワード文書にメインドキュメントパーツを追加し、それをmain_part変数に代入しています。メインドキュメントパーツは、文書全体(document要素全体)や書式情報などすべてを保存するコンテナー(入れ物)です。

コメント(5)にてdocument要素を生成したあと、コメント(6)にてbody要素を生成しdocument要素に追加しています。body要素はelem_bodyという変数に代入しており、以降で使用しています。

コメント(7)は、foreach文です。テキストファイルから読み込んだ(と仮定した)文字列配列を、1要素ずつ(1行ずつ)変数txtに取り出しループします。foreach文内のブロックでは、「段落生成 → body要素に追加」を繰り返しています。具体的には、以下を繰り返します。

  • コメント(8) … 1行分の文字列(変数txtの値)を内容として持つ t 要素を変数elem_tとして生成
  • コメント(9) … 変数elem_rとして r 要素(セクション)を生成し、内部(内容)に t 要素(変数elem_t)を追加
  • コメント(10) … 変数elem_pとして p 要素(段落)を生成し、内部(内容)に r 要素(変数elem_r)を追加
  • コメント(11) … body 要素に、p 要素(変数elem_p)を追加

以上より、最終的には次のようなドキュメント構造ができあがります。

<w:document xmlns:w="https://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p> <w:r> <w:t>テスト文章 1行目</w:t> </w:r> </w:p>
    <w:p> <w:r> <w:t>テスト文章 2行目</w:t> </w:r> </w:p>
    <w:p> <w:r> <w:t>テスト文章 3行目</w:t> </w:r> </w:p>
    <w:p> <w:r> <w:t>テスト文章 4行目</w:t> </w:r> </w:p>
    <w:p> <w:r> <w:t>テスト文章 5行目</w:t> </w:r> </w:p>
  </w:body>
</w:document>

実際にコードを実行し、生成されたワード文書「code1.docx」を開いた画面例が以下です。

画面1.生成されたワード文書
画面1.生成されたワード文書

コード2:ワード文書からテキスト抽出

以下は、ワード文書からテキストを抽出して、テキストファイルへ保存するコードです。

//...

//■■■ (1) ■■■
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

//...

private void docxToTxt()
{
    //ワード文書ファイル
    string fn = @"C:\code1.docx";

    //出力先ファイル名
    string fn_out = System.IO.Path.ChangeExtension(fn, ".txt");

    //■■■ (2) ■■■
    //出力用リスト
    List<string> txt_out_list = new List<string>();

    //■■■ (3) ■■■
    //ワード文書を開く
    using (WordprocessingDocument word_doc = WordprocessingDocument.Open(fn, false))
    {
        //■■■ (4) ■■■
        //body要素を取得
        Body elem_body = word_doc.MainDocumentPart.Document.Body;

        //■■■ (5) ■■■
        //body要素の子要素でループ
        foreach (OpenXmlElement elem_child in elem_body.ChildElements)
        {
            //■■■ (6) ■■■
            //段落?
            if (elem_child is Paragraph elem_p)
            {
                //■■■ (7) ■■■
                //段落内部のテキストを出力用リストに追加
                txt_out_list.Add(elem_p.InnerText);
            }
        }
    }

    //■■■ (8) ■■■
    //出力用リストをファイルへ書き出し
    System.IO.File.WriteAllLines(fn_out, txt_out_list);
}

コード1と同様、コメント(1)にて名前空間をデフォルト指定しています。

コメント(2)は、string型のListを用意しています。これは、テキストファイルへの出力文字列を1要素1行として格納するものです。実際には、ワード文書から1段落ずつテキストを取り出し、それを1要素として格納していき、最後に一括してテキストファイルに出力します(コメント(8)にて)。

コメント(3)は、ワード文書ファイルを開いています。Openメソッドの第1引数でワード文書のファイル名を指定し、第2引数で編集可能な状態で開くかを指定します。今回、ワード文書の内容を読み取るだけなので、編集不要であるため、第2引数はfalseを指定しています。

コメント(4)は、開いたワード文書のbody要素を取得し、変数elem_bodyとして使えるようにしています。

コメント(5)は、body要素の子要素でループしています。上の基本ドキュメント構造で示したように、body要素の子要素は p 要素(段落)です。(他に表(table 要素)などもbody要素の子要素となります。)

コメント(6)のif文では、子要素elem_childが段落かどうか(elem_childParagraphクラスかどうか)を調べています。段落だった場合、コメント(7)を実行します。

コメント(7)は、ParagraphクラスのInnerTextプロパティを使用しています。InnerTextプロパティは段落内のすべてのテキストを連結した文字列が取得できます。これを、出力用リストの要素として追加しています。

コメント(5)のループにより、ワード文書内のすべての段落が処理され、最終的には、段落から取り出したテキストを要素とする出力用リストができあがります。出力用リストの1要素はワード文書の1段落であり、ワード文書の1段落はテキストファイルでは1行となります。このため、コメント(8)では、リストの1要素を1行としてSystem.IO.File.WriteAllLinesメソッドによりテキストファイルに書き出しています。

コード1で生成したワード文書「code1.docx」を、このコード2でテキストファイルに変換した結果、以下のようなテキストファイルとなりました(メモ帳で開いた画面)。

画面2.ワード文書から変換したテキストファイル(メモ帳)
画面2.ワード文書から変換したテキストファイル(メモ帳)

サンプルプログラム

作成したプロジェクト

サンプルプログラムとして、Visual Studio 2019 を使用して、以下のプロジェクトを作成しました。

項目 内容
開発ツール Visual Studio 2019 Community
言語 C#
テンプレート Windowsフォームアプリケーション
フレームワーク .NET Framework 4.7.2
プロジェクト名 ConvTxtDocxTiny
実際のコード
(掲載しているコード)
Form1.cs、Form1.Designer.cs、dobonNetDetectEncoding.cs
追加パッケージ NuGetにて
DocumentFormat.OpenXml

概要

アプリケーションの実行例を以下に示します。

画面3.アプリケーションの実行例
画面3.アプリケーションの実行例

ワード文書ファイル(.docx)かテキストファイル(.txt)をドラッグ&ドロップし、「実行」ボタンをクリックすると、変換されたファイルが生成されます。生成されるファイル名は、拡張子のみが置き換わった(.docx ←→ .txt)ものとなります。

すでにファイルが存在した場合、無言で上書きされますので、注意してください。

コマンドライン引数にファイル名が指定された場合、指定ファイルを変換処理します。ですので、このアプリケーションを「送る」メニューに入れておけば、ファイルの右クリックから簡単に呼び出すことができます(冒頭で「1ステップで変換できる」と紹介したのは、この方法です)。

※ コマンドライン引数での処理では、テキストファイル出力時の出力文字コードが指定できません(UTF-8固定)。プログラムを改造し、コマンドライン引数で出力文字コード指定できるようにしてみましょう。

※ 「送る」メニューのフォルダは、エクスプローラーのパス名欄(フォルダ名が表示されている部分)に「shell:sendto」を入力すると開きます(あるいは「ファイル名を指定して実行」にて)。このフォルダに、実行ファイル ConvTxtDocxTiny.exe のショートカットを作成することで、「送る」メニューに追加できます。

C# サンプルプログラムの試し方

実際のコードの動作確認方法は、以下を参照してください。

C# サンプルプログラムの試し方

このうち、今回示すサンプルプログラムでは、以下の手順を実行します。

2.掲載コードをファイル保存 【Form1.cs、Form1.Designer.cs、dobonNetDetectEncoding.cs】
3.Visual Studio で、プロジェクト(ソリューション)作成 【プロジェクト名:ConvTxtDocxTiny】
5.すべてのタブを閉じる
6.プロジェクトへファイルを追加 【手順2のすべてのファイル】
7.NuGetによるパッケージの追加 【DocumentFormat.OpenXml】
8.試しに実行

実際のコード

コード3:Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.IO;

namespace ConvTxtDocxTiny
{
    public partial class Form1 : Form
    {
        private string InputFile = null;
        private Encoding OutputEnc = Encoding.UTF8;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //コマンドライン引数
            String[] args = Environment.GetCommandLineArgs();

            //コマンドラインオプションあり?
            if (args.Length >= 2)
            {
                //ウインドウ最小化
                this.WindowState = FormWindowState.Minimized;
                //入力ファイル名
                this.InputFile = args[1];
            }
            //コマンドラインオプションなし
            else
            {
                //最小サイズ
                this.MinimumSize = this.Size;

                //キャプション
                this.Text = "ConvTxtDocxTiny";

                //ドラッグ&ドロップを許可
                this.AllowDrop = true;
                //イベントハンドラ登録
                this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
                this.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);

                //出力テキストファイルの文字コードの選択
                this.rbEncoding_Utf8.Checked = true;
            }
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            //コマンドラインオプションでファイル指定あり?
            if (!string.IsNullOrEmpty(this.InputFile))
            {
                //フォーム非表示
                this.Visible = false;
                //実行
                this.exec(this.InputFile);
                //終了
                this.Close();
            }
        }

        //DragEnterイベントハンドラ
        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
            else e.Effect = DragDropEffects.None;
        }

        //DragDropイベントハンドラ
        private void Form1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] files = (string[])(e.Data.GetData(DataFormats.FileDrop));
                if (File.Exists(files[0]))
                {
                    this.tbFilename.Text = files[0];
                }
            }
        }

        //ボタンのClickイベントハンドラ
        private void btnExec_Click(object sender, EventArgs e)
        {
            //エンコーディング取得(RadioButtonから)
            this.OutputEnc = this.rbEncoding_Utf8.Checked ?
                Encoding.UTF8 : Encoding.GetEncoding("shift_jis");

            //実行
            this.exec(this.tbFilename.Text);
        }

        private void exec(string fn)
        {
            try
            {
                if (!string.IsNullOrEmpty(fn) && !File.Exists(fn))
                {
                    MessageBox.Show("ファイルが存在しません", 
                        "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else if (fn.EndsWith(".txt", StringComparison.CurrentCultureIgnoreCase))
                {
                    this.txtToDocx(fn);

                    MessageBox.Show("ワード文書を生成しました",
                        "情報", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else if (fn.EndsWith(".docx", StringComparison.CurrentCultureIgnoreCase))
                {
                    this.docxToTxt(fn);

                    MessageBox.Show("テキストファイルを生成しました",
                        "情報", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else
                {
                    MessageBox.Show("未対応のファイルです",
                        "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message,
                    "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        //テキストファイル→ワード文書
        private void txtToDocx(string fn)
        {
            //出力先ファイル名
            string fn_out = Path.ChangeExtension(fn, ".docx");

            //テキストファイルをバイト配列に読み込み
            byte[] bytes = File.ReadAllBytes(fn);

            //エンコーディング取得(バイト配列から)
            Encoding enc = dobonNetDetectEncoding.DetectEncoding(bytes);
            //不明ならUTF-8
            if (enc == null) enc = Encoding.UTF8;

            //バイト配列から文字列へ変換
            string txt_all = enc.GetString(bytes);
            //CR+LFをLFへ置換
            txt_all = txt_all.Replace("\r\n", "\n");
            //CRかLFで分割して、行毎の配列へ
            string[] txt_arr = txt_all.Split(new char[] { '\r', '\n' });

            //ワード文書を作成
            using (WordprocessingDocument word_doc =
                WordprocessingDocument.Create(fn_out, WordprocessingDocumentType.Document))
            {
                //ワード文書にメインドキュメントパーツを追加
                MainDocumentPart main_part = word_doc.AddMainDocumentPart();

                //document要素を生成
                main_part.Document = new Document();

                //body要素を作成し、document要素に追加
                Body elem_body = new Body();
                main_part.Document.AppendChild(elem_body);

                //元テキストを行毎にループ
                foreach (string txt in txt_arr)
                {
                    //テキスト(t要素)生成
                    Text elem_t = new Text(txt);

                    //セクション(r要素)を生成し、内部にテキスト(t要素)を追加
                    Run elem_r = new Run();
                    elem_r.Append(elem_t);

                    //段落(p要素)を生成し、内部にセクション(r要素)を追加
                    Paragraph elem_p = new Paragraph();
                    elem_p.Append(elem_r);

                    //bodyに段落(p要素)を追加
                    elem_body.Append(elem_p);
                }
            }
        }

        //ワード文書→テキストファイル
        private void docxToTxt(string fn)
        {
            //出力先ファイル名
            string fn_out = Path.ChangeExtension(fn, ".txt");

            //出力用リスト
            List<string> txt_out_list = new List<string>();

            //ワード文書を開く
            using (WordprocessingDocument word_doc = WordprocessingDocument.Open(fn, false))
            {
                //body要素を取得
                Body elem_body = word_doc.MainDocumentPart.Document.Body;

                //body要素の子要素でループ
                foreach (OpenXmlElement elem_child in elem_body.ChildElements)
                {
                    //段落?
                    if (elem_child is Paragraph elem_p)
                    {
                        //段落内部のテキストを出力用リストに追加
                        txt_out_list.Add(elem_p.InnerText);
                    }
                    //表?
                    else if (elem_child is Table elem_table)
                    {
                        //行でループ
                        foreach (TableRow elem_row in elem_table.Elements<TableRow>())
                        {
                            //セルテキスト用リスト
                            List<string> cell_txt_list = new List<string>();

                            //セルでループ
                            foreach (TableCell elem_cell in elem_row)
                            {
                                //セル内テキストをセルテキスト用リストに追加
                                cell_txt_list.Add(elem_cell.InnerText);
                            }

                            //セルテキストをタブで連結して、出力用リストへ追加
                            string cell_txt_str = string.Join("\t", cell_txt_list);
                            txt_out_list.Add(cell_txt_str);
                        }
                    }
                }
            }

            //出力用リストをファイルへ書き出し
            File.WriteAllLines(fn_out, txt_out_list, this.OutputEnc);
        }
    }

}

コード4:Form1.Designer.cs

namespace ConvTxtDocxTiny
{
    partial class Form1
    {
        /// <summary>
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナーで生成されたコード

        /// <summary>
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.panel1 = new System.Windows.Forms.Panel();
            this.rbEncoding_Sjis = new System.Windows.Forms.RadioButton();
            this.rbEncoding_Utf8 = new System.Windows.Forms.RadioButton();
            this.label2 = new System.Windows.Forms.Label();
            this.btnExec = new System.Windows.Forms.Button();
            this.tbFilename = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.panel1.SuspendLayout();
            this.SuspendLayout();
            // 
            // panel1
            // 
            this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.panel1.Controls.Add(this.rbEncoding_Sjis);
            this.panel1.Controls.Add(this.rbEncoding_Utf8);
            this.panel1.Location = new System.Drawing.Point(80, 37);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(222, 14);
            this.panel1.TabIndex = 12;
            // 
            // rbEncoding_Sjis
            // 
            this.rbEncoding_Sjis.AutoSize = true;
            this.rbEncoding_Sjis.Location = new System.Drawing.Point(63, 0);
            this.rbEncoding_Sjis.Name = "rbEncoding_Sjis";
            this.rbEncoding_Sjis.Size = new System.Drawing.Size(47, 16);
            this.rbEncoding_Sjis.TabIndex = 1;
            this.rbEncoding_Sjis.TabStop = true;
            this.rbEncoding_Sjis.Text = "SJIS";
            this.rbEncoding_Sjis.UseVisualStyleBackColor = true;
            // 
            // rbEncoding_Utf8
            // 
            this.rbEncoding_Utf8.AutoSize = true;
            this.rbEncoding_Utf8.Location = new System.Drawing.Point(0, 0);
            this.rbEncoding_Utf8.Name = "rbEncoding_Utf8";
            this.rbEncoding_Utf8.Size = new System.Drawing.Size(57, 16);
            this.rbEncoding_Utf8.TabIndex = 0;
            this.rbEncoding_Utf8.TabStop = true;
            this.rbEncoding_Utf8.Text = "UTF-8";
            this.rbEncoding_Utf8.UseVisualStyleBackColor = true;
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(12, 39);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(62, 12);
            this.label2.TabIndex = 11;
            this.label2.Text = "出力コード:";
            // 
            // btnExec
            // 
            this.btnExec.Location = new System.Drawing.Point(227, 56);
            this.btnExec.Name = "btnExec";
            this.btnExec.Size = new System.Drawing.Size(75, 23);
            this.btnExec.TabIndex = 10;
            this.btnExec.Text = "実行";
            this.btnExec.UseVisualStyleBackColor = true;
            this.btnExec.Click += new System.EventHandler(this.btnExec_Click);
            // 
            // tbFilename
            // 
            this.tbFilename.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.tbFilename.Location = new System.Drawing.Point(63, 12);
            this.tbFilename.Name = "tbFilename";
            this.tbFilename.ReadOnly = true;
            this.tbFilename.Size = new System.Drawing.Size(239, 19);
            this.tbFilename.TabIndex = 9;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(12, 15);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(45, 12);
            this.label1.TabIndex = 8;
            this.label1.Text = "ファイル:";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(314, 91);
            this.Controls.Add(this.panel1);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.btnExec);
            this.Controls.Add(this.tbFilename);
            this.Controls.Add(this.label1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.Shown += new System.EventHandler(this.Form1_Shown);
            this.panel1.ResumeLayout(false);
            this.panel1.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.RadioButton rbEncoding_Sjis;
        private System.Windows.Forms.RadioButton rbEncoding_Utf8;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Button btnExec;
        private System.Windows.Forms.TextBox tbFilename;
        private System.Windows.Forms.Label label1;
    }
}

コード5:dobonNetDetectEncoding.cs

//============================================================
//参考
//  文字コードを判別する - .NET Tips (VB.NET,C#...)
//  https://dobon.net/vb/dotnet/string/detectcode.html
//============================================================

namespace ConvTxtDocxTiny
{
    public class dobonNetDetectEncoding
    {
        public static System.Text.Encoding DetectEncoding(byte[] bytes, bool is_check_bom = true)
        {
            System.Text.Encoding enc = null;

            if (is_check_bom)
            {
                enc = detectEncodingFromBOM(bytes);
            }

            if (enc == null)
            {
                enc = detectEncodingNoBom(bytes);
            }

            return enc;
        }

        /// <summary>
        /// BOMを調べて、文字コードを判別する。
        /// </summary>
        /// <param name="bytes">文字コードを調べるデータ。</param>
        /// <returns>BOMが見つかった時は、対応するEncodingオブジェクト。
        /// 見つからなかった時は、null。</returns>
        private static System.Text.Encoding detectEncodingFromBOM(byte[] bytes)
        {
            if (bytes.Length < 2)
            {
                return null;
            }
            if ((bytes[0] == 0xfe) && (bytes[1] == 0xff))
            {
                //UTF-16 BE
                return new System.Text.UnicodeEncoding(true, true);
            }
            if ((bytes[0] == 0xff) && (bytes[1] == 0xfe))
            {
                if ((4 <= bytes.Length) &&
                    (bytes[2] == 0x00) && (bytes[3] == 0x00))
                {
                    //UTF-32 LE
                    return new System.Text.UTF32Encoding(false, true);
                }
                //UTF-16 LE
                return new System.Text.UnicodeEncoding(false, true);
            }
            if (bytes.Length < 3)
            {
                return null;
            }
            if ((bytes[0] == 0xef) && (bytes[1] == 0xbb) && (bytes[2] == 0xbf))
            {
                //UTF-8
                return new System.Text.UTF8Encoding(true, true);
            }
            if (bytes.Length < 4)
            {
                return null;
            }
            if ((bytes[0] == 0x00) && (bytes[1] == 0x00) &&
                (bytes[2] == 0xfe) && (bytes[3] == 0xff))
            {
                //UTF-32 BE
                return new System.Text.UTF32Encoding(true, true);
            }

            return null;
        }

        /// <summary>
        /// 文字コードを判別する
        /// </summary>
        /// <remarks>
        /// Jcode.pmのgetcodeメソッドを移植したものです。
        /// Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
        /// Jcode.pmのCopyright: Copyright 1999-2005 Dan Kogai
        /// </remarks>
        /// <param name="bytes">文字コードを調べるデータ</param>
        /// <returns>適当と思われるEncodingオブジェクト。
        /// 判断できなかった時はnull。</returns>
        private static System.Text.Encoding detectEncodingNoBom(byte[] bytes)
        {
            System.Text.Encoding enc = detectEncodingNoBom0(bytes);
            if (enc == null)
            {
                bool ascii = true;
                for (int i = 0; i < bytes.Length; i++)
                {
                    byte b1 = bytes[i];
                    if (!(b1 >= 0x20 && b1 <= 0x7E))
                    {
                        ascii = false;
                        break;
                    }
                }

                if (ascii)
                {
                    enc = System.Text.Encoding.ASCII;
                }
            }

            return enc;
        }
        private static System.Text.Encoding detectEncodingNoBom0(byte[] bytes)
        {
            const byte bEscape = 0x1B;
            const byte bAt = 0x40;
            const byte bDollar = 0x24;
            const byte bAnd = 0x26;
            const byte bOpen = 0x28;    //'('
            const byte bB = 0x42;
            const byte bD = 0x44;
            const byte bJ = 0x4A;
            const byte bI = 0x49;

            int len = bytes.Length;
            byte b1, b2, b3, b4;

            //Encode::is_utf8 は無視

            bool isBinary = false;
            for (int i = 0; i < len; i++)
            {
                b1 = bytes[i];
                if (b1 <= 0x06 || b1 == 0x7F || b1 == 0xFF)
                {
                    //'binary'
                    isBinary = true;
                    if (b1 == 0x00 && i < len - 1 && bytes[i + 1] <= 0x7F)
                    {
                        //smells like raw unicode
                        return System.Text.Encoding.Unicode;
                    }
                }
            }
            if (isBinary)
            {
                return null;
            }

            //not Japanese
            bool notJapanese = true;
            for (int i = 0; i < len; i++)
            {
                b1 = bytes[i];
                if (b1 == bEscape || 0x80 <= b1)
                {
                    notJapanese = false;
                    break;
                }
            }
            if (notJapanese)
            {
                return System.Text.Encoding.ASCII;
            }

            for (int i = 0; i < len - 2; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                b3 = bytes[i + 2];

                if (b1 == bEscape)
                {
                    if (b2 == bDollar && b3 == bAt)
                    {
                        //JIS_0208 1978
                        //JIS
                        return System.Text.Encoding.GetEncoding(50220);
                    }
                    else if (b2 == bDollar && b3 == bB)
                    {
                        //JIS_0208 1983
                        //JIS
                        return System.Text.Encoding.GetEncoding(50220);
                    }
                    else if (b2 == bOpen && (b3 == bB || b3 == bJ))
                    {
                        //JIS_ASC
                        //JIS
                        return System.Text.Encoding.GetEncoding(50220);
                    }
                    else if (b2 == bOpen && b3 == bI)
                    {
                        //JIS_KANA
                        //JIS
                        return System.Text.Encoding.GetEncoding(50220);
                    }
                    if (i < len - 3)
                    {
                        b4 = bytes[i + 3];
                        if (b2 == bDollar && b3 == bOpen && b4 == bD)
                        {
                            //JIS_0212
                            //JIS
                            return System.Text.Encoding.GetEncoding(50220);
                        }
                        if (i < len - 5 &&
                            b2 == bAnd && b3 == bAt && b4 == bEscape &&
                            bytes[i + 4] == bDollar && bytes[i + 5] == bB)
                        {
                            //JIS_0208 1990
                            //JIS
                            return System.Text.Encoding.GetEncoding(50220);
                        }
                    }
                }
            }

            //should be euc|sjis|utf8
            //use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp>
            int sjis = 0;
            int euc = 0;
            int utf8 = 0;
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if (((0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC)) &&
                    ((0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC)))
                {
                    //SJIS_C
                    sjis += 2;
                    i++;
                }
            }
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if (((0xA1 <= b1 && b1 <= 0xFE) && (0xA1 <= b2 && b2 <= 0xFE)) ||
                    (b1 == 0x8E && (0xA1 <= b2 && b2 <= 0xDF)))
                {
                    //EUC_C
                    //EUC_KANA
                    euc += 2;
                    i++;
                }
                else if (i < len - 2)
                {
                    b3 = bytes[i + 2];
                    if (b1 == 0x8F && (0xA1 <= b2 && b2 <= 0xFE) &&
                        (0xA1 <= b3 && b3 <= 0xFE))
                    {
                        //EUC_0212
                        euc += 3;
                        i += 2;
                    }
                }
            }
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if ((0xC0 <= b1 && b1 <= 0xDF) && (0x80 <= b2 && b2 <= 0xBF))
                {
                    //UTF8
                    utf8 += 2;
                    i++;
                }
                else if (i < len - 2)
                {
                    b3 = bytes[i + 2];
                    if ((0xE0 <= b1 && b1 <= 0xEF) && (0x80 <= b2 && b2 <= 0xBF) &&
                        (0x80 <= b3 && b3 <= 0xBF))
                    {
                        //UTF8
                        utf8 += 3;
                        i += 2;
                    }
                }
            }
            //M. Takahashi's suggestion
            //utf8 += utf8 / 2;

            System.Diagnostics.Debug.WriteLine(
                string.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8));
            if (euc > sjis && euc > utf8)
            {
                //EUC
                return System.Text.Encoding.GetEncoding(51932);
            }
            else if (sjis > euc && sjis > utf8)
            {
                //SJIS
                return System.Text.Encoding.GetEncoding(932);
            }
            else if (utf8 > euc && utf8 > sjis)
            {
                //UTF8
                return System.Text.Encoding.UTF8;
            }

            return null;
        }
    }
}

Discussion