Fill Word content controls from the model by their tag#138
Open
ds-o wants to merge 1 commit into
Open
Conversation
A Word content control (structured document tag) whose w:tag is a
placeholder - e.g. a tag of `{{ds.Name}}` or `{{ds.Price}:f(c)}` - now
has its content filled from the bound model, using the same model lookup
and formatters as text placeholders. Content controls inside loops are
filled once per iteration, and content controls in headers/footers are
filled too.
A content control is treated as a named, persistent region:
- the control and its tag are preserved (never removed), so it stays
addressable;
- a tag that cannot be bound - unbound OR resolving to null - leaves the
control unchanged (under SkipBindingAndRemoveContent), so multi-pass
filling is non-destructive even when passes share a model type whose
other members are null;
- the "showing placeholder" flag is cleared on a successful fill;
- under HighlightErrorsInDocument an unbound control is highlighted in
place without discarding its content.
Robustness: a tag that is not a placeholder, is a block directive
(`{{#items}}`), or is a malformed brace directive (`{{@}}`, `{{/switch}}`)
is ignored rather than aborting the render; the target text is scoped to
the control's own content so a nested control is never corrupted; and
collapsing multi-paragraph content leaves no stray empty paragraphs.
Only inline-text formatters (ToUpper, format, expressions) are supported
on a tag; block-producing formatters (html/md/img/template) are not.
Existing templates are unaffected (a tag only participates if it contains
a valid `{{...}}` placeholder).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds support for binding Word content controls (structured document tags,
w:sdt) to the model by their tag. If a content control's Tag is a DocxTemplater placeholder — e.g.{{ds.Name}}or{{ds.Price}:f(c)}— its content is filled with the resolved model value, reusing the existing model lookup and formatter pipeline. The content control itself is preserved, so it stays a named, re-fillable region.Today the only way to drive a content control is to embed a
{{…}}placeholder in its visible content, which is consumed on the first render and can't model a named region that is filled in a later pass. This lets a content control be addressed by its tag instead.Usage
Set a content control's Tag (Developer → Properties → Tag) to a placeholder:
{{ds.Name}}ds.Name.{{ds.Name}:ToUpper()}{{ds.DeliveryDate}:F('d MMM yyyy')}{{(ds.A + ds.B)}}Content controls inside a loop are filled once per iteration (the tag resolves against the loop scope, e.g.
{{ds.Items}}or{{.Name}}); content controls in headers/footers are filled too.Semantics
Unlike a text placeholder, a content control is treated as a named, persistent region:
null— leaves the control unchanged (underSkipBindingAndRemoveContent). This makes multi-pass filling non-destructive, including when passes share a model type whose other members are simplynullrather than absent — a control filled in an earlier pass is never cleared by a later pass.w:showingPlcHdrflag is cleared on a successful fill, so Word shows the value as real text.ThrowExceptionan unbound tag throws; underHighlightErrorsInDocumentthe control's content is highlighted in place without discarding it.Robustness / backward compatibility:
{{#items}}), or is a malformed brace directive ({{@}},{{/switch}},{{}}) is ignored — it never aborts the render. Existing templates are unaffected (a tag only participates if it contains a valid{{…}}placeholder).ToUpper,format, expressions) are supported on a tag. Block-producing formatters (html,md,img,template) are not supported inside a content control (use them as normal body placeholders).Implementation
VariableReplacer.ReplaceVariables(OpenXmlElement, …), the single choke point that fills marked text for both the root element and each cloned loop body — so loops (and headers/footers) are handled for free.SdtElementwhose tag is aVariable/Expressionplaceholder, the value is resolved before the content is touched (so an unbound/null tag leaves the control untouched), then written into a single targetTextvia the existingApplyFormatterpath.RemoveWithEmptyParentin a way that could dissolve it, and target selection excludes text owned by nested controls.Tests
New
ContentControlBindingTest(18 tests) covering: block and inline controls, formatter and expression tags, non-placeholder and malformed tags left untouched, placeholder-in-content, filling inside a loop and in a header, empty controls, multi-paragraph collapse, nested controls,ThrowException/HighlightErrorsInDocument, bound-null, and the deferred multi-pass scenario with both anonymous and shared model types. Every generated document is asserted valid viaDocxTemplate.Validate().Docs
README: new Content Controls section, a feature-list bullet, and notes on the formatter and
GetTemplateSchema()limitations (tag bindings are not part of the rendered text, same as sub-templates).