Skip to main content

template_replace/
template_replace.rs

1//! Template-based placeholder replacement workflow.
2//!
3//! This example demonstrates the typical template workflow:
4//! 1. Create a template document with placeholders
5//! 2. Open the template
6//! 3. Replace placeholders in body text, tables, and across formatting runs
7//! 4. Insert additional content at specific positions
8//! 5. Save the final document
9//!
10//! Run with: cargo run --example template_replace
11
12use std::collections::HashMap;
13use std::path::Path;
14
15use rdocx::{Alignment, BorderStyle, Document, Length};
16
17fn main() {
18    let samples_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
19        .parent()
20        .unwrap()
21        .parent()
22        .unwrap()
23        .join("samples");
24    std::fs::create_dir_all(&samples_dir).unwrap();
25
26    // Step 1: Create a template document with placeholders
27    let template_path = samples_dir.join("_template.docx");
28    create_template(&template_path);
29    println!("  Created template: _template.docx");
30
31    // Step 2: Open the template and replace placeholders
32    let output_path = samples_dir.join("from_template.docx");
33    fill_template(&template_path, &output_path);
34    println!("  Created filled document: from_template.docx");
35
36    // Clean up the intermediate template
37    std::fs::remove_file(&template_path).ok();
38    println!("\nDone!");
39}
40
41/// Create a template document with placeholders in body text, tables, and
42/// cross-run formatting.
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
160
161/// Open the template, replace all placeholders, insert content, and save.
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}