【C#】OpenXMLで段落にスタイルを定義して適用する

目次

概要

Word文書において、見出しや強調表示などの「スタイル」をプログラムから適用する実装です。

OpenXMLでスタイルを扱う場合、単にスタイルIDを指定するだけでは不十分で、そのスタイル定義自体が文書内(styles.xml)に存在する必要があります。ここでは「スタイルがなければ定義を追加し、その上で段落に適用する」という安全な手法を紹介します。

仕様(入出力)

  • 入力:
    • 対象のWordファイルパス
    • 適用したいスタイルID(例: “MyCustomStyle”)
    • スタイルの表示名(例: “My Custom Style”)
    • 適用対象の段落
  • 出力: スタイルが適用されたWord文書
  • ライブラリ: DocumentFormat.OpenXml (NuGetパッケージ)

実装メソッド

メソッド名説明
AddStyleIfNotDefined文書内に指定されたスタイルIDが存在するか確認し、なければ新規作成して登録します。
ApplyStyleToParagraph指定された段落のプロパティを操作し、スタイルIDを割り当てます。

基本の使い方

using var editor = new MyWordStyleEditor("StyleDoc.docx");

// 1. スタイル定義が存在することを保証(なければ青色・太字のスタイルを作成)
editor.AddStyleIfNotDefined("BlueHeader", "Blue Header Style");

// 2. 段落を作成して追加
var para = editor.AppendParagraph("この行にカスタムスタイルを適用します。");

// 3. スタイルを適用
editor.ApplyStyleToParagraph(para, "BlueHeader");

コード全文

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

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

        // テスト用にファイル作成
        CreateDummyFile(filePath);

        try
        {
            Console.WriteLine($"Opening {filePath}...");
            using var editor = new MyWordStyleEditor(filePath);

            // 1. カスタムスタイル定義の追加(ID: CustomTitle, 名前: Custom Title)
            // 定義がない場合のみ作成されます
            editor.AddStyleIfNotDefined("CustomTitle", "Custom Title");

            // 2. 通常の段落追加
            editor.AppendParagraph("これは通常のテキストです。");

            // 3. スタイル適用対象の段落追加
            var styledPara = editor.AppendParagraph("ここはスタイルが適用されたテキストです。");

            // 4. スタイルの適用
            editor.ApplyStyleToParagraph(styledPara, "CustomTitle");

            Console.WriteLine("Style applied and saved.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }

    static void CreateDummyFile(string path)
    {
        using var doc = WordprocessingDocument.Create(path, WordprocessingDocumentType.Document);
        var main = doc.AddMainDocumentPart();
        main.Document = new Document(new Body());
    }
}

/// <summary>
/// Wordのスタイル操作を行うクラス
/// </summary>
public sealed class MyWordStyleEditor : IDisposable
{
    private WordprocessingDocument _document;
    private Body _body;

    public MyWordStyleEditor(string filePath)
    {
        _document = WordprocessingDocument.Open(filePath, true);
        _body = _document.MainDocumentPart.Document.Body;
    }

    public void Dispose()
    {
        _document?.Dispose();
    }

    /// <summary>
    /// 指定したIDのスタイルが文書にない場合、新規作成して追加します
    /// </summary>
    public void AddStyleIfNotDefined(string styleId, string styleName)
    {
        // StyleDefinitionsPartを取得(なければ作成)
        var stylePart = _document.MainDocumentPart.StyleDefinitionsPart 
                        ?? _document.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();

        // Stylesルート要素を取得(なければ作成)
        if (stylePart.Styles == null)
        {
            stylePart.Styles = new Styles();
            stylePart.Styles.Save();
        }

        // 既にIDが存在するか確認
        if (stylePart.Styles.Elements<Style>().Any(s => s.StyleId == styleId))
        {
            return; // 既に存在するので何もしない
        }

        // --- 新しいスタイルの作成 ---
        var style = new Style()
        {
            Type = StyleValues.Paragraph,
            StyleId = styleId,
            CustomStyle = true
        };
        style.Append(new StyleName() { Val = styleName });
        style.Append(new BasedOn() { Val = "Normal" });
        style.Append(new NextParagraphStyle() { Val = "Normal" });

        // 書式設定(例:青文字、太字、サイズ24pt)
        var rPr = new StyleRunProperties();
        rPr.Append(new Color() { Val = "0000FF" }); // 青
        rPr.Append(new Bold());                     // 太字
        rPr.Append(new FontSize() { Val = "48" });  // 24pt (半ポイント指定なので48)
        
        style.Append(rPr);

        // Stylesに追加して保存
        stylePart.Styles.Append(style);
        stylePart.Styles.Save();
    }

    /// <summary>
    /// 指定した段落にスタイルIDを適用します
    /// </summary>
    public void ApplyStyleToParagraph(Paragraph p, string styleId)
    {
        // プロパティがなければ作成
        if (p.ParagraphProperties == null)
        {
            p.ParagraphProperties = new ParagraphProperties();
        }

        // スタイルIDを設定
        p.ParagraphProperties.ParagraphStyleId = new ParagraphStyleId() { Val = styleId };
    }

    // 補助用:段落追加
    public Paragraph AppendParagraph(string text)
    {
        var p = new Paragraph(new Run(new Text(text)));
        _body.AppendChild(p);
        return p;
    }
}

カスタムポイント

  • スタイル定義の内容変更:AddStyleIfNotDefined メソッド内の StyleRunProperties に対する設定を変更することで、好みのデザインを作成できます。
    • Color: 文字色(16進数RGB)
    • FontSize: 文字サイズ(単位は半ポイント。24ptなら “48”)
    • Underline: 下線
  • 段落スタイルの設定:文字単位の装飾(RunProperties)だけでなく、段落単位の設定(ParagraphProperties)もスタイルに含めることができます。例えば Justification(配置)や SpacingBetweenLines(行間)などを style.Append(…) で追加します。

注意点

  1. StyleIdとStyleNameの違い:StyleId はプログラム内部で使用する識別子(スペースなし推奨)、StyleName はWordの画面上でユーザーに見える名前です。ApplyStyleToParagraph では StyleId を使用します。
  2. StyleDefinitionsPartの欠落:空の文書を新規作成した直後は、StyleDefinitionsPart(styles.xml)自体が存在しないことがあります。コード例のように AddNewPart で作成するロジックが必須です。
  3. 組み込みスタイル:”Heading1″(見出し1)などの組み込みスタイルも、文書によっては定義が含まれていない場合があります。その場合も同様に定義を追加する必要がありますが、組み込みスタイルの定義は複雑であるため、あらかじめWord側でテンプレートを作成しておく手法も有効です。

応用

既存のスタイルを検索して適用する

IDが不明な場合に、スタイル名(表示名)からIDを検索して適用する方法です。

public void ApplyStyleByName(Paragraph p, string styleName)
{
    var styles = _document.MainDocumentPart.StyleDefinitionsPart?.Styles;
    if (styles == null) return;

    // 名前が一致するスタイルを探す
    var style = styles.Elements<Style>()
        .FirstOrDefault(s => s.StyleName != null && s.StyleName.Val == styleName);

    if (style != null)
    {
        ApplyStyleToParagraph(p, style.StyleId);
    }
}

まとめ

OpenXMLでスタイルを適用するには、「スタイル定義の登録(AddStyle)」と「段落への紐付け(ApplyStyle)」の2段階のプロセスが必要です。特にスタイル定義部分はXML構造が複雑になりがちですが、コード例のように「存在確認をしてから追加する」ロジックをクラス化しておくことで、メインの処理コードを簡潔に保つことができます。

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

この記事を書いた人

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

目次