# How OfficeCLI Template Merge Works with {{key}} Placeholders Across DOCX, XLSX, and PPTX Files

> Discover how OfficeCLI's template merge command uses {{key}} placeholders to efficiently update DOCX, XLSX, and PPTX files while preserving formatting. Learn more today.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: how-to-guide
- Published: 2026-08-05

---

**The OfficeCLI `merge` command replaces `{{key}}` placeholders in Word, Excel, and PowerPoint documents using a single-pass regex substitution that preserves formatting and reports unresolved placeholders.**

OfficeCLI's template merge functionality lets you populate Office documents with JSON data by replacing `{{key}}` markers. This deep dive examines the implementation in the iOfficeAI/OfficeCLI repository, showing exactly how the tool handles DOCX, XLSX, and PPTX files differently while maintaining consistent placeholder syntax.

## Command Entry Point and Data Parsing

The merge operation begins in [`CommandBuilder.Import.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.Import.cs), which defines the CLI interface and delegates to [`Core/TemplateMerger.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/Core/TemplateMerger.cs).

### Argument Handling

Lines 284-313 of [`CommandBuilder.Import.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/CommandBuilder.Import.cs) parse the template path, output path, JSON data (`--data`), and optional `--force` flag:

```csharp
var data = Core.TemplateMerger.ParseMergeData(dataArg);
var mergeResult = Core.TemplateMerger.Merge(template, output, data, force);

```

### JSON Flattening Strategy

`TemplateMerger.ParseMergeData` (lines 39-89) accepts either inline JSON or a file path, then builds a flat `Dictionary<string,string>` through two passes:

- **Pass 1**: Stores literal top-level keys directly—`"a.b"` takes precedence over nested `{ "a": { "b": … } }`
- **Pass 2**: Flattens nested objects into dot-notation (`a.b`) and arrays into bracket-notation (`items[0]`)

This flattening enables natural template syntax like `{{user.name}}` and `{{items[0].price}}` regardless of JSON structure.

## Placeholder Detection Regex

The core pattern matching uses a compiled regex defined at line 28 of [`TemplateMerger.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/TemplateMerger.cs):

```csharp
private static readonly Regex PlaceholderPattern =
    new(@"\{\{\s*(\w[\w.\-\[\] ]*?)\s*\}\}", RegexOptions.Compiled);

```

This pattern tolerates:
- Outer whitespace: `{{  key  }}`
- Hyphens and spaces: `{{customer-name}}`, `{{order date}}`
- Dot-paths: `{{invoice.total}}`
- Array indexes: `{{line_items[3]}}`

## Format-Specific Merge Implementation

`TemplateMerger.Merge` (lines 57-89) copies the template and dispatches to format-specific handlers based on file extension.

### DOCX: Word Document Handling

The `MergeDocx` method processes the full document structure:

1. Opens `WordprocessingDocument` and enumerates main document, headers, footers, footnotes, endnotes, and comments
2. For every `<w:t>` (text) element containing `{{`, calls `SinglePassReplace`
3. `SinglePassReplace` runs the regex once, substitutes matches, records used keys, and **does not re-scan output**—preventing nested placeholder expansion
4. `ScanUnresolvedDocx` (second pass) collects any remaining placeholders

Source: lines 91-126, 130-166, 168-210

### XLSX: Excel Spreadsheet Handling

The `MergeXlsx` method focuses on cell-level operations:

1. Opens `SpreadsheetDocument`, iterates all worksheets
2. Extracts cell text handling inline strings, shared strings, and plain strings
3. Applies `SinglePassReplace` to each cell's text
4. Writes results back via `SetCellText` as inline strings
5. Post-merge scan collects unresolved placeholders

Source: lines 28-68, 70-108

### PPTX: PowerPoint Presentation Handling

The `MergePptx` method handles rich text runs carefully:

1. Opens `PresentationDocument`, iterates slides and notes
2. For each `<p:sp>` (shape) with `TextBody`:
   - Concatenates all run texts
   - Runs `SinglePassReplace`
   - **Re-writes runs**: first run receives new text, subsequent runs removed—preserving original formatting of first run
3. Post-merge scan gathers unresolved placeholders

Source: lines 29-73, 75-87, 89-115

## Result Reporting and Metrics

The `Merge` method returns a `MergeResult` containing:

| Property | Description |
|----------|-------------|
| `ReplacedCount` | Total substitutions across document |
| `UnresolvedPlaceholders` | Sorted list of placeholders not found in JSON data |
| `UsedKeys` | Keys that actually appeared in template |

The CLI prints a summary and writes warnings to **stderr** for unresolved placeholders.

## Practical Usage Examples

### Command-Line Merge

```bash

# Basic inline JSON

officecli merge template.docx merged.docx --data '{"name":"Alice","date":"2026-07-29"}'

# JSON file with force overwrite

officecli merge report.xlsx report-out.xlsx --data data.json --force

```

### Template Document Example

```

Dear {{ name }},

Your appointment is scheduled for {{ date }}.

Best regards,
{{ sender.department }}

```

### JSON Data File

```json
{
  "name": "Alice Chen",
  "date": "2026-07-29",
  "sender": {
    "name": "Dr. Smith",
    "department": "Research Division"
  }
}

```

### Programmatic C# Usage

```csharp
var data = OfficeCli.Core.TemplateMerger.ParseMergeData(
    "{\"title\":\"Q1 Report\",\"year\":2024}");
    
var result = OfficeCli.Core.TemplateMerger.Merge(
    "presentation.pptx", 
    "presentation-merged.pptx", 
    data, 
    force: true);

Console.WriteLine($"Replacements: {result.ReplacedCount}");

if (result.UnresolvedPlaceholders.Any())
{
    Console.WriteLine("Unresolved: " + 
        string.Join(", ", result.UnresolvedPlaceholders));
}

```

## Key Implementation Files

- **[`src/officecli/CommandBuilder.Import.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/CommandBuilder.Import.cs)** — CLI command definition and argument parsing
- **[`src/officecli/Core/TemplateMerger.cs`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/TemplateMerger.cs)** — Core merge engine, JSON flattening, format handlers
- **Line 28** — Placeholder regex pattern
- **Lines 130-166** — DOCX replacement logic
- **Lines 71-108** — XLSX replacement logic  
- **Lines 89-115** — PPTX replacement logic

## Summary

- **Single-pass substitution**: `SinglePassReplace` prevents recursive expansion by not re-scanning output text
- **Format-aware handling**: DOCX preserves document parts, XLSX manages cell string types, PPTX collapses runs while keeping first-run formatting
- **Flat key dictionary**: Nested JSON flattens to dot-notation, enabling intuitive template keys
- **Comprehensive reporting**: `MergeResult` tracks replacements, used keys, and unresolved placeholders
- **CLI and programmatic APIs**: Both command-line and C# library usage supported

## Frequently Asked Questions

### What placeholder syntax does OfficeCLI support?

OfficeCLI accepts `{{key}}` syntax with flexible spacing. Keys can include letters, numbers, dots for nesting, brackets for arrays, hyphens, and spaces: `{{ user.name }}`, `{{items[0]}}`, `{{order-date}}`, and `{{customer name}}` are all valid.

### How does OfficeCLI handle nested JSON objects in templates?

The `ParseMergeData` method flattens nested objects into dot-notation keys automatically. A JSON object like `{"user": {"name": "Alice"}}` becomes accessible as `{{user.name}}` without manual flattening.

### Why does PowerPoint merging preserve only the first run's formatting?

The PPTX handler concatenates all text runs in a shape, performs substitution, then assigns the result to the first run and removes subsequent runs. This maintains the formatting of the first run while ensuring clean text replacement—a necessary trade-off since PowerPoint's run-based structure would create fragmented styling if individual runs were modified.

### What happens to unresolved placeholders after merging?

Unresolved placeholders remain as literal text in the output document. The `MergeResult` includes a sorted list of these placeholders, and the CLI writes a warning to stderr. You can use this list to debug missing JSON keys or intentional optional placeholders.