[C#] How to Define and Apply Paragraph Styles in Word Documents using OpenXML

目次

Overview

This implementation demonstrates how to programmatically apply “styles,” such as headings or emphasis, to a Word document. When handling styles in OpenXML, simply specifying a Style ID is insufficient; the style definition itself must exist within the document’s internal structure (styles.xml). This article introduces a safe method to “add the definition if it does not exist, then apply it to the paragraph.”

Specifications (Input/Output)

  • Input: – Target Word file path.
    • Style ID (e.g., “MyCustomStyle”).
    • Style Display Name (e.g., “My Custom Style”).
    • Target paragraph object.
  • Output: A Word document with the style applied.
  • Library: DocumentFormat.OpenXml (NuGet package).

Implemented Methods

Method NameDescription
AddStyleIfNotDefinedChecks if the specified Style ID exists in the document; if not, creates and registers it.
ApplyStyleToParagraphManipulates paragraph properties to assign a specific Style ID.

Basic Usage

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

// 1. Ensure the style definition exists (creates a blue, bold style if missing)
editor.AddStyleIfNotDefined("BlueHeader", "Blue Header Style");

// 2. Create and add a paragraph
var para = editor.AppendParagraph("Apply a custom style to this line.");

// 3. Apply the style
editor.ApplyStyleToParagraph(para, "BlueHeader");

Full Code Example

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

        // Create a dummy file for testing
        CreateDummyFile(filePath);

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

            // 1. Add a custom style definition (ID: CustomTitle, Name: Custom Title)
            // It will only be created if it does not already exist
            editor.AddStyleIfNotDefined("CustomTitle", "Custom Title");

            // 2. Add a standard paragraph
            editor.AppendParagraph("This is normal text.");

            // 3. Add a paragraph to be styled
            var styledPara = editor.AppendParagraph("This is text with a style applied.");

            // 4. Apply the style
            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>
/// Class for performing Word style operations
/// </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>
    /// Checks if a style with the specified ID exists in the document; if not, creates and adds it.
    /// </summary>
    public void AddStyleIfNotDefined(string styleId, string styleName)
    {
        // Retrieve the StyleDefinitionsPart (create it if missing)
        var stylePart = _document.MainDocumentPart.StyleDefinitionsPart 
                        ?? _document.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();

        // Retrieve the Styles root element (create it if missing)
        if (stylePart.Styles == null)
        {
            stylePart.Styles = new Styles();
            stylePart.Styles.Save();
        }

        // Check if the ID already exists
        if (stylePart.Styles.Elements<Style>().Any(s => s.StyleId == styleId))
        {
            return; // Do nothing as it already exists
        }

        // --- Create a new style ---
        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" });

        // Formatting (e.g., Blue text, Bold, Size 24pt)
        var rPr = new StyleRunProperties();
        rPr.Append(new Color() { Val = "0000FF" }); // Blue
        rPr.Append(new Bold());                     // Bold
        rPr.Append(new FontSize() { Val = "48" });  // 24pt (Specified in half-points, so 48)
        
        style.Append(rPr);

        // Append to Styles and save
        stylePart.Styles.Append(style);
        stylePart.Styles.Save();
    }

    /// <summary>
    /// Applies a style ID to the specified paragraph
    /// </summary>
    public void ApplyStyleToParagraph(Paragraph p, string styleId)
    {
        // Create properties if they do not exist
        if (p.ParagraphProperties == null)
        {
            p.ParagraphProperties = new ParagraphProperties();
        }

        // Set the Style ID
        p.ParagraphProperties.ParagraphStyleId = new ParagraphStyleId() { Val = styleId };
    }

    // Helper: Add paragraph
    public Paragraph AppendParagraph(string text)
    {
        var p = new Paragraph(new Run(new Text(text)));
        _body.AppendChild(p);
        return p;
    }
}

Customization Points

  • Modifying Style Definitions: You can customize the look by changing the settings in StyleRunProperties within the AddStyleIfNotDefined method.
    • Color: Text color in hex RGB.
    • FontSize: Text size in half-points (e.g., “48” for 24pt).
    • Underline: Text underlining.
  • Setting Paragraph-Level Styles: In addition to text decoration (RunProperties), you can include paragraph-level settings (ParagraphProperties) in a style, such as Justification (alignment) or SpacingBetweenLines.

Important Notes

  • Difference Between StyleId and StyleName: StyleId is the identifier used programmatically (recommended to have no spaces), while StyleName is the name visible to the user in Word’s UI. Use StyleId in ApplyStyleToParagraph.
  • Missing StyleDefinitionsPart: When creating a blank document from scratch, the StyleDefinitionsPart (styles.xml) might not exist. The logic using AddNewPart as shown in the example is required.
  • Built-in Styles: Built-in styles like “Heading1” may not have definitions included in every document. You may still need to add the definition, although built-in definitions can be complex. Using a Word template is often more efficient for complex styles.

Advanced Usage

Searching and Applying an Existing Style

If the ID is unknown, you can search for and apply a style using its display name.

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

    // Search for a style with a matching name
    var style = styles.Elements<Style>()
        .FirstOrDefault(s => s.StyleName != null && s.StyleName.Val == styleName);

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

Summary

Applying styles in OpenXML is a two-step process: “registering the style definition (AddStyle)” and “linking it to the paragraph (ApplyStyle).” While the XML structure for style definitions can be complex, encapsulating the “check existence then add” logic into a class keeps your primary processing code clean and maintainable.

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

この記事を書いた人

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

目次