Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
dfd259c
First sketching on templates
sirreal Dec 23, 2025
3d85433
DROPME: Collect examples
sirreal Feb 2, 2026
b57bcd6
Introduce test suite based on existing examples
sirreal Feb 2, 2026
ad1f12e
yield + nowdoc
sirreal Feb 2, 2026
b37b72c
lints
sirreal Feb 2, 2026
883c87e
test tweaks
sirreal Feb 2, 2026
8fb6235
Fix numeric array indexes
sirreal Feb 3, 2026
d3370a2
Add test for HTML that could produce a tag after modification
sirreal Feb 3, 2026
379e7c4
Add test to prevent attribute mis-interpretation after replacement
sirreal Feb 3, 2026
9e38ff9
Move serialize_token to tag processor class (for normalization of tex…
sirreal Feb 3, 2026
64835c4
Add attribute behavior testing
sirreal Feb 3, 2026
3fb0c0f
working proof-of-concept
sirreal Feb 3, 2026
3ad02bd
class cleanup and lints
sirreal Feb 4, 2026
9def00a
Add message on disallowed type
sirreal Feb 4, 2026
a954bcf
Cleanup test + lints
sirreal Feb 4, 2026
379678b
Add multiple template tests
sirreal Feb 4, 2026
58cbcf8
Improve test names and specificity
sirreal Feb 4, 2026
0b472dd
Improve test structure and names
sirreal Feb 4, 2026
7a67db2
Refactor WP_HTML_Template to use composition over inheritance
sirreal Feb 4, 2026
dd7d70d
Remove unused preg_attribute_replace_callback method from WP_HTML_Tem…
sirreal Feb 4, 2026
78baca2
Add notes for next steps
sirreal Feb 4, 2026
0dbf70c
Add more tests
sirreal Feb 4, 2026
2e7aad8
lints
sirreal Feb 4, 2026
d323a9a
Add design document for WP_HTML_Template API redesign
sirreal Feb 4, 2026
b15c169
Add v2 design document for WP_HTML_Template API
sirreal Feb 4, 2026
1056bfa
Rework interface
sirreal Feb 4, 2026
2a99525
Update tests for new API
sirreal Feb 4, 2026
763e901
Add placeholder compilation skeleton to WP_HTML_Template
sirreal Feb 4, 2026
644dfb3
Implement text placeholder extraction in compile()
sirreal Feb 4, 2026
cc6ae51
Add attribute placeholder extraction with context promotion
sirreal Feb 4, 2026
e9c8b1b
Add validation warnings to bind()
sirreal Feb 4, 2026
a802982
Refactor render() to use compiled placeholder data
sirreal Feb 4, 2026
67cff97
Fix attribute text escaping and add trailing segment handling
sirreal Feb 4, 2026
5fb3f3d
Revert "Move serialize_token to tag processor class (for normalizatio…
sirreal Feb 5, 2026
ab27234
Switch to use HTML Processor
sirreal Feb 5, 2026
ce87818
Use assertEqualHTML in tests
sirreal Feb 6, 2026
993fe2b
Add newline in PRE tests
sirreal Feb 6, 2026
df9d8c9
Update PRE leading newline test
sirreal Feb 6, 2026
d90142b
Add more test cases
sirreal Feb 6, 2026
ecec350
Remove special handling for PRE, LISTING tags
sirreal Feb 6, 2026
8a2609e
Test tweaks and notes
sirreal Feb 6, 2026
bfdec21
Add design doc for unified edits array in WP_HTML_Template
sirreal Feb 6, 2026
76e6297
HTML API: Add $edits and $placeholder_names properties to WP_HTML_Tem…
sirreal Feb 6, 2026
d1cff79
HTML API: Populate $edits with text normalizations during compile
sirreal Feb 6, 2026
aaae935
HTML API: Populate $edits with text placeholders during compile
sirreal Feb 6, 2026
6415041
HTML API: Pre-compute attribute escapes at compile time
sirreal Feb 6, 2026
dd96aea
HTML API: Populate $edits with attribute placeholders during compile
sirreal Feb 6, 2026
4e42ace
HTML API: Refactor render() to use unified $edits array
sirreal Feb 6, 2026
eea6a51
HTML API: Refactor bind() to use $placeholder_names for validation
sirreal Feb 6, 2026
48dcd2e
HTML API: Remove legacy $text_normalizations and $attr_escapes arrays
sirreal Feb 6, 2026
cbfcd6d
HTML API: Add TODO for future $compiled removal
sirreal Feb 6, 2026
17675db
HTML API: Fix code style issues in WP_HTML_Template
sirreal Feb 6, 2026
09f3524
HTML API: Remove redundant $compiled variable from WP_HTML_Template
sirreal Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/brainstorming/template-compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Template Compile Mode

**Date:** 2026-02-04
**Status:** Brainstorming

---

## Two Modes of Template Usage

### 1. Flush Mode (current)
- One-shot rendering, like `::sprintf`
- Parse and render in a single pass

### 2. Compile Mode (new idea)
- A `compile()` method that:
- Captures placeholder offsets
- Returns a compiled/prepared template
- Accepts an array of replacements at render time
- Can be rendered multiple times efficiently from a single compilation

**Use case:** When you need to render the same template structure repeatedly with different data, avoid re-parsing the template each time.
17 changes: 17 additions & 0 deletions docs/brainstorming/template-html-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Template HTML Context

**Date:** 2026-02-04
**Status:** Brainstorming

---

## HTML Context Constraints

### Problem
The HTML processor cannot normalize certain templates like `<td>` because `<td>` cannot exist outside of a `<table>` element. Context matters for valid HTML.

### Potential Solutions
- Use HTML processor instead of TAG processor
- Add private methods for rendering nested templates
- Processor creates a **fragment parser** at the template replacement location
- Fragment parser handles the child template with proper parent context
125 changes: 125 additions & 0 deletions docs/plans/2026-02-04-template-api-redesign-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# WP_HTML_Template API Redesign v2

**Date:** 2026-02-04
**Status:** Design Complete

---

## Overview

A single-class template API with strict error handling. Parse once, bind values, render on demand.

## Essential Interface

```php
class WP_HTML_Template {
public static function from(string $template): static;
public function bind(array $replacements): static;
public function render(): string|false;
}
```

### Methods

**`from(string $template): static`**

Creates a template from a string.

**`bind(array $replacements): static`**

Returns a new immutable instance with replacements bound.

**`render(): string|false`**

Renders the template to an HTML string. Returns `false` on any error.

## Usage Patterns

### One-shot rendering

```php
$html = WP_HTML_Template::from('<p>Hello, </%name>!</p>')
->bind(['name' => 'World'])
->render();
```

### Nested templates (HTML injection)

A template instance can be used as a replacement value. It will be rendered and injected as HTML (not escaped).

```php
$html = WP_HTML_Template::from('<div></%icon> </%message></div>')
->bind([
'icon' => WP_HTML_Template::from('<span class="dashicon warning"></span>'),
'message' => 'Something went wrong.',
])
->render();
```

### Nested templates with their own placeholders

```php
$html = WP_HTML_Template::from('<div class="alert"></%content></div>')
->bind([
'content' => WP_HTML_Template::from('<a href="</%url>"></%text></a>')
->bind(['url' => '/help', 'text' => 'Learn more']),
])
->render();
```

### Loop rendering

Parse once, bind and render with different values each iteration.

```php
$item_template = WP_HTML_Template::from('<li></%item></li>');

foreach ($items as $item) {
echo $item_template->bind(['item' => $item])->render();
}
```

## Replacement Values

| Replacement Type | Text Context | Attribute Context |
|------------------|--------------|-------------------|
| String | Escaped | Escaped |
| WP_HTML_Template | Rendered (HTML preserved) | `false` (error) |

## Error Handling

All error conditions cause `render()` to return `false`:

| Condition | Result |
|-----------|--------|
| Missing replacement key | `false` |
| Unused replacement key | `false` |
| Template in attribute context | `false` |
| HTML processing/normalization failure | `false` |

Replacements must match placeholders exactly—no more, no less.

## Immutability

`bind()` always returns a new instance. Templates are safe to reuse:

```php
$tpl = WP_HTML_Template::from('<p></%text></p>');

$a = $tpl->bind(['text' => 'Hello']);
$b = $tpl->bind(['text' => 'World']);

// $a and $b are independent
echo $a->render(); // <p>Hello</p>
echo $b->render(); // <p>World</p>
```

## Migration from Current API

| Old | New |
|-----|-----|
| `T::sprintf($tpl, $r)` | `T::from($tpl)->bind($r)->render()` |
| `T::from($tpl, $r)->render()` | `T::from($tpl)->bind($r)->render()` |
| `T::from($tpl)->render($r)` | `T::from($tpl)->bind($r)->render()` |

Breaking changes are acceptable since this is pre-release code (`@since 7.0.0`).
153 changes: 153 additions & 0 deletions docs/plans/2026-02-04-template-api-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# WP_HTML_Template API Redesign

**Date:** 2026-02-04
**Status:** Design Complete

---

## Overview

Redesign the `WP_HTML_Template` API to support efficient repeated rendering. The key insight: parse once, bind many times, render on demand.

## Classes

### WP_HTML_Template

The parsed template. Reusable across multiple bind operations.

```php
class WP_HTML_Template {
public static function render(string $template, array $replacements): string|false;
public static function from(string $template): self;
public function bind(array $replacements): WP_HTML_Bound_Template;
}
```

### WP_HTML_Bound_Template

A template with replacements bound. Ready to render.

```php
class WP_HTML_Bound_Template {
public function render(): string|false;
}
```

## Usage Patterns

### One-shot rendering

For simple cases where you render once:

```php
echo WP_HTML_Template::render('<p></%name></p>', ['name' => 'Alice']);
```

### Reusable templates

For loops or repeated use:

```php
$template = WP_HTML_Template::from('<li></%item></li>');

foreach ($items as $item) {
echo $template->bind(['item' => $item])->render();
}
```

### Nested templates

Templates can contain other bound templates as replacements:

```php
$icon = WP_HTML_Template::from('<span class="icon </%class>"></span>');
$message = WP_HTML_Template::from('<div class="alert"></%icon> </%text></div>');

echo $message->bind([
'icon' => $icon->bind(['class' => 'warning']),
'text' => 'Something went wrong',
])->render();
```

## Replacement Values

Valid replacement types depend on context:

| Context | String | WP_HTML_Bound_Template |
|-----------|--------|------------------------|
| Text | Yes | Yes |
| Attribute | Yes | No (error) |

## Lazy Compilation

`from()` stores the template string but does not parse it immediately. Compilation happens on the first `bind()` call and is cached for subsequent calls.

```php
// Just stores the string
$template = WP_HTML_Template::from('<p></%name></p>');

// First bind() triggers compilation, caches result
$bound1 = $template->bind(['name' => 'Alice']);

// Subsequent bind() reuses cached compilation
$bound2 = $template->bind(['name' => 'Bob']);
```

## Compilation Output

Compilation produces a list of placeholders with their positions and contexts:

```php
[
'template' => '<p class="</%class>"></%content></p>',
'placeholders' => [
'class' => ['start' => 11, 'length' => 10, 'context' => 'attribute'],
'content' => ['start' => 23, 'length' => 12, 'context' => 'text'],
],
]
```

This allows `bind()` to validate replacements and `render()` to efficiently build the output string.

## Error Handling

Validation occurs at `bind()` time using the compiled placeholder information:

### Missing key

```php
$t = WP_HTML_Template::from('<p></%name> </%age></p>');
$t->bind(['name' => 'Alice']);
// Warning: Missing replacement key 'age'
```

Rendering continues with missing placeholders removed.

### Unused key

```php
$t->bind(['name' => 'Alice', 'age' => '30', 'extra' => 'ignored']);
// Warning: Unused replacement key 'extra'
```

Rendering continues with unused keys ignored.

### Wrong type for attribute

```php
$t = WP_HTML_Template::from('<p class="</%class>">text</p>');
$t->bind(['class' => $otherTemplate->bind([...])]);
// Warning: Replacement 'class' must be a string (attribute context)
```

`render()` returns `false`.

## Migration from Current API

| Old | New |
|-----|-----|
| `T::sprintf($tpl, $r)` | `T::render($tpl, $r)` |
| `T::from($tpl, $r)->render()` | `T::from($tpl)->bind($r)->render()` |
| `T::from($tpl)->render($r)` | `T::from($tpl)->bind($r)->render()` |

Breaking changes are acceptable since this is pre-release code (`@since 7.0.0`).
Loading
Loading