【C#】OpenXMLで既存のWord文書の特定位置に段落を挿入する

目次

概要

作成済みのWordファイル(.docx)を開き、指定した段落の「前」に新しい段落を挿入する実装です。

OpenXML SDKでは Body 要素が持つ InsertBefore メソッドを使用することで、文書構造の任意の位置に新しい要素を割り込ませることができます。

仕様(入出力)

  • 入力: 編集対象のファイルパス、挿入位置の基準となる段落オブジェクト、挿入するテキスト
  • 出力: 編集・保存された .docx ファイル
  • ライブラリ: DocumentFormat.OpenXml (NuGetパッケージ)

実装メソッド

メソッド名説明
Open既存のWordファイルを「編集モード」で開き、ドキュメント操作用インスタンスを生成します。
GetParagraphs文書(Body)直下にあるすべての段落を取得します。
InsertParagraph指定した段落の直前に、新しいテキスト段落を挿入します。

基本の使い方

// 編集モードで開く
using var doc = MyWordEditor.Open("Report.docx");

// 2番目の段落(インデックス1)を取得
var targetParams = doc.GetParagraphs().ElementAtOrDefault(1);

if (targetParams != null)
{
    // 取得した段落の「前」に挿入
    doc.InsertParagraph(targetParams, "【追記】ここに新しい行が入ります");
}

コード全文

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

class Program
{
    static void Main()
    {
        string filePath = "ExistingDocument.docx";

        // テスト用にファイルが存在しない場合は作成しておく
        EnsureFileExists(filePath);

        try
        {
            Console.WriteLine($"Opening {filePath} ...");

            // 1. 既存ファイルを開く
            using var editor = MyWordEditor.Open(filePath);

            // 2. 挿入位置の特定
            // ここでは「2つ目の段落(Index 1)」を取得します
            var paragraphs = editor.GetParagraphs().ToList();
            var targetParagraph = paragraphs.ElementAtOrDefault(1);

            if (targetParagraph != null)
            {
                // 3. 特定の段落の前に挿入
                editor.InsertParagraph(targetParagraph, ">>> 割り込み挿入されたテキスト <<<");
                Console.WriteLine("Inserted text before paragraph 2.");
            }
            else
            {
                // ターゲットが見つからない場合は末尾に追加
                editor.AppendParagraph(">>> 末尾に追加されたテキスト <<<");
                Console.WriteLine("Target not found. Appended text instead.");
            }

            // Dispose時に自動的に保存されます
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }

    // テストデータ作成用
    static void EnsureFileExists(string path)
    {
        if (!File.Exists(path))
        {
            using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
            var mainPart = doc.AddMainDocumentPart();
            mainPart.Document = new Document(new Body());
            var body = mainPart.Document.Body;
            
            // 初期データをいくつか入れておく
            body.AppendChild(new Paragraph(new Run(new Text("1. First Line"))));
            body.AppendChild(new Paragraph(new Run(new Text("2. Second Line"))));
            body.AppendChild(new Paragraph(new Run(new Text("3. Third Line"))));
        }
    }
}

// OpenXML SDKを使用したWord編集クラス
public sealed class MyWordEditor : IDisposable
{
    private WordprocessingDocument _document;
    private Body _body;

    // コンストラクタを隠蔽
    private MyWordEditor() { }

    // 既存ファイルを開くファクトリメソッド
    public static MyWordEditor Open(string filePath)
    {
        var editor = new MyWordEditor();

        // 第2引数 isEditable: true で編集モードとして開く
        editor._document = WordprocessingDocument.Open(filePath, true);
        
        // Body要素への参照を保持
        editor._body = editor._document.MainDocumentPart.Document.Body;
        
        return editor;
    }

    // Body直下の段落一覧を取得
    public IEnumerable<Paragraph> GetParagraphs()
    {
        // Elements<Paragraph>() は直下の子要素のみを取得する
        // 表の中の段落なども含めたい場合は Descendants<Paragraph>() を使用する
        return _body.Elements<Paragraph>();
    }

    // 指定した段落の直前に挿入
    public void InsertParagraph(Paragraph target, string text)
    {
        // 新しい段落構造を作成
        var newPara = new Paragraph(
            new Run(
                new Text(text)
            )
        );

        // OpenXMLの InsertBefore メソッドを使用
        _body.InsertBefore(newPara, target);
    }

    // 文書の末尾に追加(補助用)
    public void AppendParagraph(string text)
    {
        var newPara = new Paragraph(
            new Run(
                new Text(text)
            )
        );
        _body.AppendChild(newPara);
    }

    public void Dispose()
    {
        // 変更を確定して閉じる
        _document?.Dispose();
    }
}

カスタムポイント

  • 直後に挿入したい場合:InsertBefore の代わりに InsertAfter メソッドを使用することで、指定した段落の下に行を追加できます。
  • 検索して挿入:GetParagraphs() をLINQでフィルタリングし、「”特定のキーワード”を含む段落」を探して、その前後に挿入するといった処理が可能です。例: var target = editor.GetParagraphs().FirstOrDefault(p => p.InnerText.Contains(“ここに追加”));
  • スタイル適用:挿入する Paragraph オブジェクトに対して ParagraphProperties を設定すれば、挿入テキストだけ「太字」「赤文字」などに装飾できます。

注意点

  1. 編集権限(isEditable):WordprocessingDocument.Open の第2引数を true にしないと、読み取り専用モードとなり、保存時に例外が発生します。
  2. 要素の階層:GetParagraphs で Elements<Paragraph>() を使用している場合、表(Table)の中にある段落は取得されません。表の中も含めて検索したい場合は Descendants<Paragraph>() を使用してください。ただし、表の中に単純に挿入するとレイアウトが崩れる可能性があるため注意が必要です。
  3. インデックス範囲:ElementAtOrDefault(1) などで取得する際、文書の行数が少ないと null が返ってきます。必ず null チェックを行ってから挿入処理を実行してください。

応用

特定の文字を含む段落を削除する

挿入だけでなく、不要な段落を削除する機能です。

public void RemoveParagraphsByText(string keyword)
{
    // 削除対象をリスト化(列挙中の削除を防ぐため)
    var targets = _body.Elements<Paragraph>()
                       .Where(p => p.InnerText.Contains(keyword))
                       .ToList();

    foreach (var p in targets)
    {
        p.Remove();
    }
}

まとめ

既存のWord文書を編集する際は、WordprocessingDocument.Open(path, true) で開き、Body プロパティ経由で段落オブジェクトにアクセスします。InsertBefore メソッドは、リスト構造への挿入と同じ感覚で扱えるため、基準となる段落さえ特定できれば自由に文書構造を変更できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次