0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python で Word 文書のしおりを操作する:テンプレートマークと動的コンテンツ埋め込み

0
Posted at

Word のしおり(Bookmark)は、文書内の名前付き領域を定義する機能で、プログラミングにおける変数と同じ役割を果たす。定義時に名前を付け、実行時に名前で参照する。報告書テンプレートにあらかじめしおりを挿入しておき、プログラム実行時にしおりの内容を実際のデータで置き換えれば、「テンプレート + データ = 文書」の流程を完全に自動化できる。手動の検索置換と比べ、しおりは位置が正確、ネスト可能、再利用可能で、文書内の一意テキストに依存しない。

本記事では Free Spire.Doc for Python を使い、Word しおりの操作チェーンを示す:しおりの作成、テキストと表の内容置換、しおり位置への画像挿入、しおり内容の抽出、しおりの削除、そしてこれらを組み合わせたプロジェクト報告書の自動生成流程。

pip install spire.doc.free

1. しおりの作成:文書内のコンテンツ位置をマーク

しおりは AppendBookmarkStart("name") と AppendBookmarkEnd("name") のペアで定義する。両者の間にあるコンテンツがしおりの適用範囲となる。以下、プロジェクト報告書テンプレートを作成し、埋め込み対象位置に 10 個のしおりを挿入する:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

# タイトル
p = section.AddParagraph()
p.AppendText("Project Phase Report")
p.ApplyStyle(BuiltinStyle.Heading1)
p.Format.HorizontalAlignment = HorizontalAlignment.Center

# 基本情報エリア——しおりで埋め込み位置をマーク
p = section.AddParagraph()
p.AppendText("Project Name: ")
p.AppendBookmarkStart("ProjectName")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("ProjectName")

p = section.AddParagraph()
p.AppendText("Client: ")
p.AppendBookmarkStart("ClientName")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("ClientName")

p = section.AddParagraph()
p.AppendText("Report Date: ")
p.AppendBookmarkStart("ReportDate")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("ReportDate")

p = section.AddParagraph()
p.AppendText("Project Phase: ")
p.AppendBookmarkStart("ProjectPhase")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("ProjectPhase")

section.AddParagraph()

# プロジェクト概要
p = section.AddParagraph()
p.AppendText("Project Summary")
p.ApplyStyle(BuiltinStyle.Heading2)

p = section.AddParagraph()
p.AppendBookmarkStart("Summary")
p.AppendText("(Auto-filled by program)")
p.AppendBookmarkEnd("Summary")

section.AddParagraph()

# マイルストーン進捗表のプレースホルダー
p = section.AddParagraph()
p.AppendText("Milestone Status")
p.ApplyStyle(BuiltinStyle.Heading2)

p = section.AddParagraph()
p.AppendBookmarkStart("MilestoneTable")
p.AppendText("(Auto-inserted by program)")
p.AppendBookmarkEnd("MilestoneTable")

section.AddParagraph()

# リスクと課題——ネストしおり
p = section.AddParagraph()
p.AppendText("Risk and Suggestions")
p.ApplyStyle(BuiltinStyle.Heading2)

p = section.AddParagraph()
p.AppendBookmarkStart("RiskSection")
p.AppendBookmarkStart("RiskLevel")
p.AppendText("(risk level to be filled)")
p.AppendBookmarkEnd("RiskLevel")
p.AppendText(". ")
p.AppendBookmarkStart("RiskDetail")
p.AppendText("(risk detail to be filled)")
p.AppendBookmarkEnd("RiskDetail")
p.AppendBookmarkEnd("RiskSection")

section.AddParagraph()

# 捺印エリア
p = section.AddParagraph()
p.AppendText("Signature")
p.ApplyStyle(BuiltinStyle.Heading2)

p = section.AddParagraph()
p.AppendBookmarkStart("Signature")
p.AppendText("(Auto-inserted by program)")
p.AppendBookmarkEnd("Signature")

document.SaveToFile("template.docx", FileFormat.Docx)
document.Close()

リスクと課題のセクションではネストしおりを使用している。外側の RiskSection が内側の RiskLevel と RiskDetail を囲み、3 つとも同じ段落内にある。ネストしおりの利点は、内側のしおりを個別に置換できることと、外側のしおりで全体を一括操作できること。AppendBookmarkStart と AppendBookmarkEnd はペアで使用し、名称を一致させ、End は Start の後に配置する必要がある。

実行後、テンプレートには 10 個のしおりが含まれる:

Template created: template.docx
Bookmark count: 10
Bookmarks: ['ProjectName', 'ClientName', 'ReportDate', 'ProjectPhase',
            'Summary', 'MilestoneTable', 'RiskSection', 'RiskLevel',
            'RiskDetail', 'Signature']

報告書テンプレート効果


2. しおり内容の置換:テキストと表

テキスト内容の置換

BookmarksNavigator はしおり操作の中核クラスである。MoveToBookmark で指定しおりに定位し、ReplaceBookmarkContent でしおり覆盖の内容を新しいテキストに置き換える:

from spire.doc import *
from spire.doc.common import *

doc = Document()
doc.LoadFromFile("template.docx")

navigator = BookmarksNavigator(doc)

navigator.MoveToBookmark("ProjectName")
navigator.ReplaceBookmarkContent("Smart City Data Platform Phase II", False)

navigator.MoveToBookmark("ClientName")
navigator.ReplaceBookmarkContent("Orient Municipal Group", False)

navigator.MoveToBookmark("ReportDate")
navigator.ReplaceBookmarkContent("September 28, 2026", False)

navigator.MoveToBookmark("ProjectPhase")
navigator.ReplaceBookmarkContent("Phase II - Mid-term Acceptance", False)

navigator.MoveToBookmark("Summary")
navigator.ReplaceBookmarkContent(
    "This phase completed a comprehensive upgrade of the data collection module, "
    "adding 320 sensor access points and increasing daily data throughput from "
    "120 million to 350 million records. The platform has integrated with 5 subsystems "
    "(traffic, environmental, security, water, and municipal), with an average API "
    "response time of 87ms and 99.6% availability. Mid-term acceptance passed 13 of "
    "14 indicators; the remaining 1 (real-time alert latency) is expected to meet "
    "the target by October 15.", False)

# ネストしおりの内側は個別に置換可能
navigator.MoveToBookmark("RiskLevel")
navigator.ReplaceBookmarkContent("Medium", False)

navigator.MoveToBookmark("RiskDetail")
navigator.ReplaceBookmarkContent(
    "The Kafka cluster on which the real-time alert module depends experienced "
    "message accumulation during peak hours, with a peak latency of 4.2 seconds, "
    "exceeding the 2-second SLA limit. The ops team has been coordinated to expand "
    "to 6 nodes, expected to complete by October 10.", False)

doc.SaveToFile("step2_text.docx", FileFormat.Docx)
doc.Close()

ReplaceBookmarkContent の第二パラメータはブール値で、しおりの元の文字書式を保持するかを制御する。False の場合は新テキストにデフォルト書式を適用し、True の場合はしおり内元テキストのフォント、サイズ、色などを継承する。テンプレートのプレースホルダーテキスト "(to be filled)" に特定の書式が設定されている場合、True を渡すことで埋め込み後のデータも同じ見た目を保てる。

ネストしおりの内側 RiskLevel と RiskDetail は独立して定位・置換でき、外側 RiskSection と干渉しない。MoveToBookmark は名称で検索し、しおりのネスト階層を意識しない。

表への置換

しおりの内容はテキストに限らず、表も置換できる。Table オブジェクトを構築し、TextBodyPart でラップしてから置換する:

doc = Document()
doc.LoadFromFile("step2_text.docx")

table = Table(doc, True)
table.ResetCells(6, 4)
data = [
    ["Milestone", "Planned Date", "Actual Date", "Status"],
    ["Requirements", "2026-07-15", "2026-07-14", "Completed"],
    ["Architecture", "2026-07-30", "2026-07-30", "Completed"],
    ["Core Development", "2026-08-31", "2026-09-02", "Completed (2-day delay)"],
    ["Integration Test", "2026-09-20", "—", "In Progress (85%)"],
    ["Mid-term Review", "2026-09-28", "2026-09-28", "Passed"],
]

for i in range(6):
    for j in range(4):
        cell = table.Rows[i].Cells[j]
        para = cell.AddParagraph()
        txtRange = para.AppendText(data[i][j])
        if i == 0:
            txtRange.CharacterFormat.Bold = True

navigator = BookmarksNavigator(doc)
navigator.MoveToBookmark("MilestoneTable")

part = TextBodyPart(doc)
part.BodyItems.Add(table)
navigator.ReplaceBookmarkContent(part)

doc.SaveToFile("step3_table.docx", FileFormat.Docx)
doc.Close()

Table(doc, True) の第二パラメータ True はテーブルの固定レイアウトモードを有効にし、列幅は ResetCells が自動割当する。TextBodyPart は表を格納するコンテナで、ReplaceBookmarkContent は文字列と TextBodyPart の両方を引数に取る。後者は構造化コンテンツ(表、複数段落など)の置換に用いる。

埋め込み後の報告書


3. しおり位置への画像挿入

しおり位置に画像を挿入するには InsertParagraph メソッドを使用する。AppendPicture は段落上で呼び出す必要があるが、しおり位置に空の段落があるとは限らない。そのため、一時 section で画像付き段落を作成し、その段落をしおり位置に挿入する:

doc = Document()
doc.LoadFromFile("step3_table.docx")

bn = BookmarksNavigator(doc)
bn.MoveToBookmark("Signature", True, True)

# 一時 section に画像段落を作成
section0 = doc.AddSection()
paragraph = section0.AddParagraph()
paragraph.Format.HorizontalAlignment = HorizontalAlignment.Center

picture = paragraph.AppendPicture("signature.png")
picture.Width = 150.0
picture.Height = 150.0

# 画像段落をしおり位置に挿入
bn.InsertParagraph(paragraph)

# 一時 section を削除
doc.Sections.Remove(section0)

doc.SaveToFile("step4_image.docx", FileFormat.Docx)
doc.Close()

MoveToBookmark("Signature", True, True) の後ろ 2 つのパラメータは検索動作を制御する。最初の True は大文字小文字を区別するか、次の True は前方検索するかを示す。InsertParagraph は段落をしおりの現在位置の後に挿入し、画像もそれに伴って文書に配置される。一時 section は挿入完了後に削除し、余分なページを生成しない。

picture.Width と picture.Height の単位はポイント(point)。150 ポイントは約 5.3 センチメートルで、円形印鑑のサイズに適する。横長の画像を挿入する場合は比率を保って設定する、例えば Width = 400.0, Height = 100.0。

捺印挿入効果


4. しおり内容の抽出と一覧

他者が作成した文書を処理する際、まず文書内にどのしおりがあるか、各しおりがどんな内容を含むかを把握する必要がよくある。

しおりの一覧

document.Bookmarks はしおりコレクションで、インデックスと名称の両方でアクセスできる:

doc = Document()
doc.LoadFromFile("step4_image.docx")

for i in range(doc.Bookmarks.Count):
    bm = doc.Bookmarks[i]
    print(f"  {i+1}. {bm.Name}")

print(f"Total: {doc.Bookmarks.Count} bookmarks")

# 名称で直接アクセス
bookmark = doc.Bookmarks["ProjectName"]
print(f"ProjectName bookmark found, name: {bookmark.Name}")

出力:

  1. ProjectName
  2. ClientName
  3. ReportDate
  4. ProjectPhase
  5. Summary
  6. MilestoneTable
  7. RiskSection
  8. RiskLevel
  9. RiskDetail
  10. Signature
Total: 10 bookmarks

しおりテキストの抽出

BookmarksNavigator.GetBookmarkContent() はしおり覆盖の内容オブジェクトを返す。BodyItems を巡回してテキストを取り出す:

navigator = BookmarksNavigator(doc)

targets = ["ProjectName", "ClientName", "Summary", "RiskLevel", "RiskDetail"]
for name in targets:
    navigator.MoveToBookmark(name)
    content = navigator.GetBookmarkContent()
    text = ""
    for i in range(content.BodyItems.Count):
        item = content.BodyItems.get_Item(i)
        if isinstance(item, Paragraph):
            for j in range(item.ChildObjects.Count):
                child = item.ChildObjects.get_Item(j)
                if isinstance(child, TextRange):
                    text += child.Text
    display = text[:80] + "..." if len(text) > 80 else text
    print(f"  [{name}] {display}")

出力:

  [ProjectName] Smart City Data Platform Phase II
  [ClientName] Orient Municipal Group
  [Summary] This phase completed a comprehensive upgrade of the data collection module, addi...
  [RiskLevel] Medium
  [RiskDetail] The Kafka cluster on which the real-time alert module depends experienced messag...

GetBookmarkContent が返す TextBodyPart には段落、表など複数の要素が含まれる可能性がある。巡回時に isinstance でタイプを判定する必要がある。純テキストのしおり場合は Paragraph と TextRange だけを処理すればよい。しおりが表を覆盖している場合は Table タイプの処理も必要となる。


5. しおりの削除としおり内容の削除

削除操作には 2 種類ある。しおりマークだけ削除(内容は保持)と、マークと内容を同時に削除である。

しおりマークの削除、内容は保持

document.Bookmarks.Remove(bookmark) はしおりの Start と End マークだけを削除し、しおり内のテキストや表は文書中にそのまま残る:

doc = Document()
doc.LoadFromFile("step4_image.docx")

bookmark = doc.Bookmarks["RiskSection"]
doc.Bookmarks.Remove(bookmark)

doc.SaveToFile("step6a_remove_mark.docx", FileFormat.Docx)
doc.Close()
# 削除後、残り 9 個のしおり(元 10 個から RiskSection を減算)

この操作は報告書の清書段階に適する。埋め込み完了後に全しおりマークを削除し、クリーンな最終文書を出力する。同時に埋め込んだ内容は保持される。

しおりと内容の同時削除

マークと内容を同時に削除するには、BookmarkStart と BookmarkEnd の段落内位置インデックスを手動で特定し、両者の間の子オブジェクトを順次削除する:

doc = Document()
doc.LoadFromFile("step4_image.docx")

bookmark = doc.Bookmarks["Signature"]
para = bookmark.BookmarkStart.Owner
startIndex = para.ChildObjects.IndexOf(bookmark.BookmarkStart)
para2 = bookmark.BookmarkEnd.Owner
endIndex = para2.ChildObjects.IndexOf(bookmark.BookmarkEnd)

# Start と End の間の内容を削除
for i in range(startIndex + 1, endIndex):
    para.ChildObjects.RemoveAt(startIndex + 1)

# しおりマーク自体も削除
doc.Bookmarks.Remove(bookmark)

doc.SaveToFile("step6b_remove_content.docx", FileFormat.Docx)
doc.Close()
# 削除後、残り 9 個のしおり

BookmarkStart.Owner はしおりマークが所在する段落オブジェクトを返す。ChildObjects.IndexOf はマークの段落子オブジェクトリスト内での位置を取得する。ループ RemoveAt(startIndex + 1) は Start の直後の要素を毎回削除し、End の前一つまで進む。RemoveAt 後にリストがシフトするため、ループ中は常に startIndex + 1 の位置を削除する。インデックスをインクリメントしない点に注意。


6. 総合応用:プロジェクト報告書の自動生成

上記の操作を組み合わせると、完整的な報告書生成流程になる。しおり付きテンプレート作成 → テキストしおり置換 → 表しおり置換 → 印鑑画像挿入 → 保存出力。以下のスクリプトは一回の実行で全ステップを完了する:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

# --- テンプレート作成 ---
p = section.AddParagraph()
p.AppendText("Project Phase Report")
p.ApplyStyle(BuiltinStyle.Heading1)
p.Format.HorizontalAlignment = HorizontalAlignment.Center

fields = [
    ("Project Name: ", "ProjectName"),
    ("Client: ", "ClientName"),
    ("Report Date: ", "ReportDate"),
    ("Project Phase: ", "ProjectPhase"),
]
for label, bm in fields:
    p = section.AddParagraph()
    p.AppendText(label)
    p.AppendBookmarkStart(bm)
    p.AppendText("(to be filled)")
    p.AppendBookmarkEnd(bm)

section.AddParagraph()
p = section.AddParagraph()
p.AppendText("Project Summary")
p.ApplyStyle(BuiltinStyle.Heading2)
p = section.AddParagraph()
p.AppendBookmarkStart("Summary")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("Summary")

section.AddParagraph()
p = section.AddParagraph()
p.AppendText("Milestone Status")
p.ApplyStyle(BuiltinStyle.Heading2)
p = section.AddParagraph()
p.AppendBookmarkStart("MilestoneTable")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("MilestoneTable")

section.AddParagraph()
p = section.AddParagraph()
p.AppendText("Risk and Suggestions")
p.ApplyStyle(BuiltinStyle.Heading2)
p = section.AddParagraph()
p.AppendBookmarkStart("RiskLevel")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("RiskLevel")
p.AppendText(". ")
p.AppendBookmarkStart("RiskDetail")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("RiskDetail")

section.AddParagraph()
p = section.AddParagraph()
p.AppendText("Signature")
p.ApplyStyle(BuiltinStyle.Heading2)
p = section.AddParagraph()
p.AppendBookmarkStart("Signature")
p.AppendText("(to be filled)")
p.AppendBookmarkEnd("Signature")

# --- テキスト埋め込み ---
navigator = BookmarksNavigator(document)

text_data = {
    "ProjectName": "Smart City Data Platform Phase II",
    "ClientName": "Orient Municipal Group",
    "ReportDate": "September 28, 2026",
    "ProjectPhase": "Phase II - Mid-term Acceptance",
    "Summary": "This phase completed a comprehensive upgrade of the data collection module, "
               "adding 320 sensor access points and increasing daily data throughput from "
               "120 million to 350 million records. The platform has integrated with 5 subsystems "
               "(traffic, environmental, security, water, and municipal), with an average API "
               "response time of 87ms and 99.6% availability. Mid-term acceptance passed 13 of "
               "14 indicators; the remaining 1 (real-time alert latency) is expected to meet "
               "the target by October 15.",
    "RiskLevel": "Medium",
    "RiskDetail": "The Kafka cluster on which the real-time alert module depends experienced "
                  "message accumulation during peak hours, with a peak latency of 4.2 seconds, "
                  "exceeding the 2-second SLA limit. The ops team has been coordinated to expand "
                  "to 6 nodes, expected to complete by October 10.",
}
for name, value in text_data.items():
    navigator.MoveToBookmark(name)
    navigator.ReplaceBookmarkContent(value, False)

# --- マイルストーン表挿入 ---
table = Table(document, True)
table.ResetCells(6, 4)
data = [
    ["Milestone", "Planned Date", "Actual Date", "Status"],
    ["Requirements", "2026-07-15", "2026-07-14", "Completed"],
    ["Architecture", "2026-07-30", "2026-07-30", "Completed"],
    ["Core Development", "2026-08-31", "2026-09-02", "Completed (2-day delay)"],
    ["Integration Test", "2026-09-20", "—", "In Progress (85%)"],
    ["Mid-term Review", "2026-09-28", "2026-09-28", "Passed"],
]
for i in range(6):
    for j in range(4):
        cell = table.Rows[i].Cells[j]
        para = cell.AddParagraph()
        txtRange = para.AppendText(data[i][j])
        if i == 0:
            txtRange.CharacterFormat.Bold = True

navigator.MoveToBookmark("MilestoneTable")
part = TextBodyPart(document)
part.BodyItems.Add(table)
navigator.ReplaceBookmarkContent(part)

# --- 印鑑画像挿入 ---
bn = BookmarksNavigator(document)
bn.MoveToBookmark("Signature", True, True)
section0 = document.AddSection()
paragraph = section0.AddParagraph()
paragraph.Format.HorizontalAlignment = HorizontalAlignment.Center
picture = paragraph.AppendPicture("signature.png")
picture.Width = 150.0
picture.Height = 150.0
bn.InsertParagraph(paragraph)
document.Sections.Remove(section0)

# --- 保存 ---
document.SaveToFile("final_report.docx", FileFormat.Docx)
document.Close()

実行結果:

Final report generated: final_report.docx
Bookmark count: 9
Bookmarks: ['ProjectName', 'ClientName', 'ReportDate', 'ProjectPhase',
            'Summary', 'MilestoneTable', 'RiskLevel', 'RiskDetail', 'Signature']

最終報告書には 9 個のしおりが含まれる(総合テンプレートでは外側の RiskSection を作成せず、内側の RiskLevel と RiskDetail のみ保持)。しおりは置換後も文書内に存続し、後続の二次埋め込みや内容抽出に使用できる。しおりを含まない清書を出力する場合は、保存前に document.Bookmarks を巡回して Remove すればよい。

最終報告書効果


7. 主要クラスとメソッド

クラス / メソッド 説明
Paragraph.AppendBookmarkStart(name) 段落にしおり開始マークを追加
Paragraph.AppendBookmarkEnd(name) 段落にしおり終了マークを追加
Document.Bookmarks しおりコレクション、インデックス [0] と名称 ["name"] でアクセス可能
Document.Bookmarks.Remove(bookmark) しおりマークを削除、内容は保持
BookmarksNavigator(doc) しおりナビゲータ、しおり内容の定位と操作に使用
BookmarksNavigator.MoveToBookmark(name) 指定しおりに定位
BookmarksNavigator.GetBookmarkContent() しおり覆盖の内容オブジェクトを取得
BookmarksNavigator.ReplaceBookmarkContent(text, isKeepFormat) しおり内容をテキストで置換
BookmarksNavigator.ReplaceBookmarkContent(textBodyPart) しおり内容を構造化コンテンツ(表など)で置換
BookmarksNavigator.InsertParagraph(paragraph) しおり位置に段落を挿入
TextBodyPart(doc) コンテンツコンテナ、表などの構造化コンテンツをラップ

8. 適用シーン

しおり方式は「テンプレート固定、データ変化」の文書生成タスクに適する。テンプレートは業務担当者が設計しおりを挿入し、プログラムが埋め込みを担当する。两者が分離し、テンプレートの書式変更が埋め込みロジックに影響しない。

典型的なシーンには、プロジェクト報告書の定期生成(週報、月報、段階検収報告書)、契約書テンプレートの埋め込み(甲乙方情報、金額、期限)、製品マニュアルの組み立て(設定に基づき章節を選択し主文書に挿入)、批量通知書の生成(受取人、事項、金額が各々異なる)がある。埋め込み元がデータベースや Excel の場合、クエリ結果を辞書に整理し、ReplaceBookmarkContent を巡回呼び出しすれば批量生成できる。

差し込み印刷と比較すると、しおり方式の利点は表や画像などの構造化コンテンツを挿入できること、テンプレートに差し込みフィールドを事前挿入する必要がないこと。欠点は批量生成時にテンプレートファイルを毎回再読み込みする必要があること(ReplaceBookmarkContent がしおりを消費する)。一方、差し込み印刷の Execute メソッドは複数フィールドを一回で埋め込める。両者は要件に応じて使い分ける。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?