Skip to content

TEMPLATE-STRUCTURE-01C — Canonical Template Normalization

Status: implemented as a bounded physical repair layer. This slice does not begin SECTION-03E.

A. Problem and evidence

LibreOffice can split one logical expression across several ODF spans. The previous load-time normalizeTemplateDom() removed those nodes and appended a plain text node, which destroyed styles and could change sibling order. Sample 25 demonstrated both failures: styled {{position}} moved after the Company bookmark, and split activity text was flattened.

The 01B projector established that logical recognition and physical repair are different operations. 01C adds the smallest proven physical repair while retaining the read-only projection and diagnostics model.

B. Canonical representation

Canonical does not mean every expression must be an unstyled text node. A canonical expression is one coherent physical fragment where that can be done without guessing, with its local style, native markers, original position, and surrounding ODF structure preserved. Existing plain text and styled-span expressions are both canonical.

C. Normalizer architecture

TemplateStructureNormalizer first invokes the non-mutating TemplateExpressionProjector for evidence and diagnostics. It then plans and applies only same-style, complete-fragment repairs. The projector never mutates. The normalizer owns all physical mutation and returns an immutable TemplateStructureNormalizationResult containing changed(), repairs, skipped candidates, and diagnostics.

No style registry, asset owner, template evaluator, or section service was added to the normalization path.

D. Safe repair rule

The supported repair is a complete logical expression represented by at least two adjacent, same-kind text fragments with the same declared local style, inside one text-flow scope, with no marker or structural node between them.

For example:

<text:span text:style-name="T1">{{pos</text:span>
<text:span text:style-name="T1">ition}}</text:span>

becomes one T1 span containing {{position}}. The first fragment remains at the original sibling position and retains all its attributes. The remaining fragment nodes are removed only after the complete candidate has been validated.

Plain text fragments with no style are treated analogously. A fragment that contains unrelated prefix/suffix text is not merged.

E. Style preservation and sibling order

The first physical node is the repair target; it is never replaced by a newly created unstyled node. The declared text:style-name and other attributes on that node remain intact. The operation changes only the contributing text and removes redundant same-style fragment siblings.

Because the repair inserts nothing before the first fragment and never appends the result to the parent, surrounding text and bookmarks retain their original order. This directly prevents the Sample-25 position relocation regression.

F. Different-style fragmentation

Expressions using multiple declared styles, such as Sample 25's T29, T30, T29 activity expression, are logically recognized but not physically flattened. They produce style-conflict evidence and remain physically unchanged. The normalizer does not select a winning style or invent effective style semantics.

G. Bookmark preservation

Bookmark elements are hard separators for physical repair. Collapsed bookmarks remain collapsed, paired start/end markers remain ordered and named, and no marker is moved, removed, or absorbed into a span. An expression whose logical stream intersects a bookmark remains projected for reading but is skipped for physical normalization with bookmark_intersects_template_expression.

This applies to Sample 25's Activity marker topology. The Company paired bookmark and FromTo collapsed bookmark remain unchanged.

H. Boundaries and atomicity

Repairs are limited to one paragraph, heading, list-item, table-cell, section, or text-box scope. Paragraph/list/cell/section boundaries cannot be crossed. Each candidate is fully checked for complete fragment coverage, matching node kind, matching style, and absence of marker separators before any node is changed. Unsupported, malformed, mixed-style, and boundary-crossing cases are left unchanged.

I. Idempotence

After a successful merge, the expression has one physical fragment and is no longer a repair candidate. A second pass reports no change. Tests also cover canonical expressions and repeated normalization. This prevents progressive DOM restructuring across load/render/save cycles.

J. Legacy fixBrokenVariables() findings

fixBrokenVariables() remains used during render and remains a compatibility concern. It recursively joins direct text nodes and can still be destructive for some nested or fragmented structures. It is not the new canonicalizer and was not removed speculatively. The new load-time path prevents the known safe same-style cases from reaching it as fragmented spans.

K. Legacy normalizeTemplateDom() findings

The old TemplateProcessor::normalizeTemplateDom() was the source of the style-loss/order regression and is no longer used by prepareLoadedTemplate(). Its protected compatibility method remains present but is not the load-time canonicalization path. Existing structured insertion still needs an exact-key compatibility join, so OdtTemplate performs a narrowly targeted join for the requested structured placeholder only; this does not normalize unrelated template expressions.

L. Load-time integration

Preparation now follows the bounded normalizer for content and styles, then continues with the existing default-style setup. Normal rendering, filters, conditions, foreach handling, resource insertion, and addressable operations were otherwise left intact.

M. Normalization result

TemplateStructureNormalizationResult is immutable and reports repairs with expression, repair type, previous/resulting fragment counts, and preserved style. Skipped candidates report the reason and fragment/style context. Projector diagnostics remain available for deferred or unsafe structures. No raw DOM is returned.

N. Sample-25 result

Load/save now preserves the header placeholders' styled containers and order. {{position}} remains before the separator and Company bookmark, with its T25 span. Company, collapsed FromTo, nested ActivityEntry, lists, and the Activity marker topology remain intact. The split {{activity}} expression is still logically recognized with T29/T30 metadata but is intentionally not physically normalized.

O. SECTION-03D regression

The existing clone/identity/local-binding tests remain green. The normalizer does not rewrite values, section identities, bookmarks, lists, styles, or resources. Clone-local processing therefore remains a separate responsibility; any later formatting loss in evaluation must be handled at the processing boundary rather than hidden in normalization.

P. Compatibility and limitations

The compatibility suite still covers legacy fragmented structured placeholders through the exact-key path. The normalizer does not repair different-style fragments, marker-intersecting expressions, control structures, malformed syntax, or expressions crossing structural boundaries. It does not resolve effective inherited styles or perform generic template-language evaluation.

These limitations are deliberate: physical repair would require additional semantic evidence. TEMPLATE-FORMAT-PRESERVATION-01 remains the broader future investigation of preservation during all template-language operations; 01C is only the safe load-time canonicalization boundary.

Q. Tests and validation

Focused tests cover canonical and styled expressions, two/three-fragment same-style repair, style and attribute preservation, sibling order, mixed styles, bookmarks, hard boundaries, malformed/unsupported input, idempotence, DOM stability, and Sample 25 regression evidence. The full suite passes: 191 tests and 1,266 assertions, with one existing PHPUnit deprecation.

PHP lint, Composer validation, git diff --check, and the strict Zensical build also pass. No visual rendering was performed in the agent environment; the change is intended to preserve the existing native layout and should be locally rendered before visual claims are made.

R. Recommendation

The next bounded slice should move template evaluation toward the same logical fragment map, especially for replacement of differently styled or bookmark-intersecting expressions. Do not broaden physical normalization until those operations define explicit style and marker semantics. SECTION-03E should remain deferred until this processing-boundary work is characterized.