Skip to content

[Clippy] feat: add RegisterCustomHandler extensibility API to DocumentAssembler#163

Draft
github-actions[bot] wants to merge 2 commits into
masterfrom
clippy/improve-custom-handler-66-20260326-f25d5328ab83f793
Draft

[Clippy] feat: add RegisterCustomHandler extensibility API to DocumentAssembler#163
github-actions[bot] wants to merge 2 commits into
masterfrom
clippy/improve-custom-handler-66-20260326-f25d5328ab83f793

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This PR was created by Clippy, an automated AI assistant.

Closes #66

Summary

Adds a thread-safe, static extensibility API to DocumentAssembler that allows users to register custom content-control directives without forking the library.

New Public API

// Delegate type for custom handlers
public delegate object? CustomAssemblerHandler(XElement directive, XElement data, OpenXmlPart part);

// Register a custom directive handler (with optional XSD schema for validation)
DocumentAssembler.RegisterCustomHandler(string elementName, string? schemaXsd, CustomAssemblerHandler handler);

// Remove a previously registered handler
DocumentAssembler.UnregisterCustomHandler(string elementName);

Usage example — replace a <#(MyGreeting/)#> directive in a template:

DocumentAssembler.RegisterCustomHandler("MyGreeting", schemaXsd: null, (directive, data, part) =>
{
    var name = (string?)data.Element("Name") ?? "World";
    return new XElement(W.r, new XElement(W.t, $"Hello, {name}!"));
});
var result = DocumentAssembler.AssembleDocument(templateWml, dataXml, out var hasErrors);
DocumentAssembler.UnregisterCustomHandler("MyGreeting");
```

The handler receives the directive `XElement`, the current data context `XElement`, and the `OpenXmlPart`. It can return:
- an `XElement` / `XNode` (replaces the directive in the document tree)
- a `string` (wrapped in a `(w:r)(w:t)` run)
- `null` (silently removes the directive)
- throwing an exception maps to a `TemplateError` in the output document

## Design Notes

- **Thread safety**: uses `ConcurrentDictionary` for lock-free reads on the hot path; write operations are also thread-safe.
- **Guard**: `RegisterCustomHandler` throws `ArgumentException` if the element name conflicts with a built-in PA directive (`Content`, `Table`, `Repeat`, `Image`, etc.).
- **XSD validation**: pass an XSD string to validate directive attributes at template-parse time; pass `null` to skip schema validation for that directive.
- **Block-level promotion**: custom directives are automatically included in `ForceBlockLevelAsAppropriate` so block-level substitutions (e.g. replacing with a table or heading) work correctly.
- **Alias recognition**: custom element names are added to `s_aliasList` so content controls with a matching `(w:alias)` are recognized as template directives.

## Tests Added (DA500–DA504)

| Test | What it verifies |
|------|-----------------|
| DA500 | Basic invocation: handler fires and its return value replaces the directive |
| DA501 | Exception in handler maps to a `TemplateError` in the output |
| DA502 | Handler returning `null` silently removes the directive element |
| DA503 | After `UnregisterCustomHandler` the element is treated as an invalid directive |
| DA504 | Custom XSD schema rejects directive with invalid attributes |

## Test Status

```
dotnet test --project Clippit.Tests/
total: 1182  succeeded: 1181  failed: 0  skipped: 1
✅ All tests pass

dotnet csharpier check . → ✅ all 204 files formatted correctly

Generated by Clippy ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f

Adds a thread-safe static API for registering custom content-control
directives in DocumentAssembler. Users can now extend the template
engine without forking the library.

Public API added:
  - CustomAssemblerHandler delegate
  - DocumentAssembler.RegisterCustomHandler(elementNamespace, localName, optionalXsdSchema, handler)
  - DocumentAssembler.UnregisterCustomHandler(elementNamespace, localName)

Closes #66

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Support for custom content controls in DocumentAssembler

1 participant