[C#] How to Insert Paragraphs at Specific Positions in Existing Word Documents Using OpenXML

目次

Overview

This implementation demonstrates how to open an existing Word file (.docx) and insert a new paragraph “before” a specified reference paragraph. In the OpenXML SDK, you can interject new elements at any position within the document structure by using the InsertBefore method of the Body element.

Specifications (Input/Output)

  • Input: Target file path, the reference paragraph object for the insertion point, and the text to be inserted.
  • Output: An edited and saved .docx file.
  • Library: DocumentFormat.OpenXml (NuGet package).

Implemented Methods

Method NameDescription
OpenOpens an existing Word file in “edit mode” and creates a document manipulation instance.
GetParagraphsRetrieves all paragraphs located directly under the document (Body).
InsertParagraphInserts a new text paragraph immediately before the specified paragraph.

Basic Usage

// Open in edit mode
using var doc = MyWordEditor.Open("Report.docx");

// Get the second paragraph (index 1)
var targetParams = doc.GetParagraphs().ElementAtOrDefault(1);

if (targetParams != null)
{
    // Insert "before" the retrieved paragraph
    doc.InsertParagraph(targetParams, "[Addition] A new line will be inserted here.");
}

Full Code Example

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";

        // Create the file for testing if it does not exist
        EnsureFileExists(filePath);

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

            // 1. Open the existing file
            using var editor = MyWordEditor.Open(filePath);

            // 2. Identify the insertion point
            // Here, we retrieve the "second paragraph (Index 1)"
            var paragraphs = editor.GetParagraphs().ToList();
            var targetParagraph = paragraphs.ElementAtOrDefault(1);

            if (targetParagraph != null)
            {
                // 3. Insert before the specified paragraph
                editor.InsertParagraph(targetParagraph, ">>> Interjected Text <<<");
                Console.WriteLine("Inserted text before paragraph 2.");
            }
            else
            {
                // Append to the end if the target is not found
                editor.AppendParagraph(">>> Text added to the end <<<");
                Console.WriteLine("Target not found. Appended text instead.");
            }

            // Automatically saved upon Disposal
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }

    // For creating test data
    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;
            
            // Populate with some initial data
            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"))));
        }
    }
}

// Word editing class using OpenXML SDK
public sealed class MyWordEditor : IDisposable
{
    private WordprocessingDocument _document;
    private Body _body;

    private MyWordEditor() { }

    // Factory method to open an existing file
    public static MyWordEditor Open(string filePath)
    {
        var editor = new MyWordEditor();

        // Open in edit mode by setting the second argument isEditable to true
        editor._document = WordprocessingDocument.Open(filePath, true);
        
        // Maintain a reference to the Body element
        editor._body = editor._document.MainDocumentPart.Document.Body;
        
        return editor;
    }

    // Retrieve a list of paragraphs directly under the Body
    public IEnumerable<Paragraph> GetParagraphs()
    {
        // Elements<Paragraph>() retrieves only immediate child elements
        // Use Descendants<Paragraph>() if you want to include paragraphs inside tables, etc.
        return _body.Elements<Paragraph>();
    }

    // Insert immediately before the specified paragraph
    public void InsertParagraph(Paragraph target, string text)
    {
        // Create new paragraph structure
        var newPara = new Paragraph(
            new Run(
                new Text(text)
            )
        );

        // Use the OpenXML InsertBefore method
        _body.InsertBefore(newPara, target);
    }

    // Append to the end of the document (Helper)
    public void AppendParagraph(string text)
    {
        var newPara = new Paragraph(
            new Run(
                new Text(text)
            )
        );
        _body.AppendChild(newPara);
    }

    public void Dispose()
    {
        // Commit changes and close
        _document?.Dispose();
    }
}

Customization Points

  • Inserting Immediately After: By using the InsertAfter method instead of InsertBefore, you can add a line below the specified paragraph.
  • Search and Insert: You can filter GetParagraphs() using LINQ to find a paragraph containing a “specific keyword” and insert text before or after it.
    • Example: var target = editor.GetParagraphs().FirstOrDefault(p => p.InnerText.Contains("Insert here"));
  • Applying Styles: By setting ParagraphProperties on the inserted Paragraph object, you can format the inserted text as “bold” or “red text,” for example.

Important Notes

  • Edit Permissions (isEditable): You must set the second argument of WordprocessingDocument.Open to true; otherwise, it will open in read-only mode, and an exception will occur during saving.
  • Element Hierarchy: When using Elements<Paragraph>() in GetParagraphs, paragraphs within tables will not be retrieved. Use Descendants<Paragraph>() to search inside tables. However, be cautious as inserting directly into a table may disrupt the layout.
  • Index Range: When retrieving via ElementAtOrDefault(1), null will be returned if the document has too few lines. Always perform a null check before executing the insertion process.

Advanced Usage

Removing Paragraphs Containing Specific Text

This function allows you to remove unwanted paragraphs in addition to inserting them.

public void RemoveParagraphsByText(string keyword)
{
    // Create a list of targets to prevent errors during enumeration
    var targets = _body.Elements<Paragraph>()
                       .Where(p => p.InnerText.Contains(keyword))
                       .ToList();

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

Conclusion

When editing an existing Word document, open it with WordprocessingDocument.Open(path, true) and access paragraph objects through the Body property. The InsertBefore method functions similarly to inserting into a list structure, allowing you to freely modify the document structure once the reference paragraph is identified.

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

この記事を書いた人

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

目次