Ориентация главы

xtemplate стандартизует связку «данные + шаблон → текст»: разделение компиляции и рендеринга — xrtTemplateCompile разбирает шаблон в неизменяемое дерево узлов (с подсчётом ссылок, тот же жизненный цикл, что у регулярных выражений главы 30), а xrtTemplateRender рендерит владеющий текст из дерева значений (xvalue главы 31). Три префикса интерполяции закрывают три частые потребности: {$path} прямой вывод, {%path:fmt} форматирование чисел (строка формата — синтаксис главы 26), {&path:fmt} форматирование времени; управляющие конструкции (циклы/условия) и вызовы функций разворачиваются в обзорном образце. Стандартное употребление шаблонов конфигурации, заголовков отчётов и текстов уведомлений: компиляция на старте, высокочастотный рендеринг — стоимость разбора платится один раз. Глава разделяет с регулярными выражениями главы 30 одну ментальную модель двухслойного жизненного цикла: освоив одно, второе берёте бесплатно.

35.1引入

Потребность системы уведомлений (этот модуль есть почти у каждой онлайн-системы): «Уважаемый {имя пользователя}, ваш заказ {номер} отправлен {время}.» — три поля, три типа (строка, число, время), плюс варианты по каналам (короткая версия для SMS, длинная для почты). Ручная сборка через xrtFormat — брать каждое поле, форматировать, сшивать; с ростом числа полей получается лапша; построитель главы 25 лучше, но «шаблон» всё ещё рассыпан по коду — смена одной формулировки означает правку кода, перекомпиляцию, передеплой.

Шаблонный движок выносит «форму» из кода: шаблон — это данные (файл, строка в конфигурации); поменяли формулировку — поменяли шаблон: без релиза и без простоя; рендеринг — чистая функция: один шаблон с одними данными всегда даёт один вывод, тестируемо и кешируемо. Разделение компиляции и рендеринга добавляет звено производительности (в этом его сущностный отрыв от «замены строк»): разбор шаблона (постройка дерева узлов, валидация путей) выполняется один раз при компиляции, а рендеринг делает только «взять + отформатировать + вывести» — стоимость горячего пути (рендеринг уведомления на каждый запрос) практически не зависит от сложности шаблона.

35.2概念

Разделение компиляции и рендеринга (сперва модель, потом функции)

1КомпиляцияxrtTemplateCompile(текст шаблона) -> неизменяемое дерево узлов (счёт ссылок, Retain/Release)
2Данныедерево xvalue (глава 31) - строится ObjectSet/SetNew
3РендерингxrtTemplateRender(скомпилированный объект, дерево значений, ...) -> владеющий вывод
4Переиспользованиеодин скомпилированный объект рендерится с разными деревьями - шаблон один, данные вечно новые

Скомпилированный объект неизменяем и разделяем — многопоточный рендеринг одного шаблона требует лишь собственных деревьев значений у каждого потока (рендеринг читает шаблон, не меняя). Это полностью изоморфно двухслойной модели регулярных выражений главы 30: тяжёлая операция (разбор) один раз, лёгкая (рендеринг) высокочастотно.

Три префикса интерполяции (по одному символу смысла на каждый)

ПрефиксСинтаксисСемантика
Прямой вывод{$user.name}строка как есть; путь навигирует дерево значений по точкам
Форматирование чисел{%count:,d}после двоеточия строка формата главы 26 (тысячи, система счисления, проценты)
Форматирование времени{&updated:%F}значение времени рендерится по строке формата (дата ISO и т.п.)

Синтаксис пути a.b.c навигирует дерево значений (точечный доступ во вложенные объекты — как в большинстве шаблонных языков): при рендеринге значение берётся по пути; промах (путь не существует или тип не тот) обрабатывается по конфигурации рендеринга (ошибка или пустая строка — выбор явный). Типизированное форматирование — водораздел между шаблоном и «заменой строк» (данные хранят тип, показ хранит гибкость: одно и то же числовое поле в SMS-шаблоне компактным форматом, в шаблоне отчёта с разделителями тысяч — решает шаблон): форматирование чисел и времени объявляется в шаблоне, а не препформатируется в строки в коде — данные остаются типизированными, показ остаётся гибким.

Управляющие конструкции и функции (форма подстраивается под данные)

Шаблон — не только интерполяция: циклы (обход массива дерева значений, по переменной цикла на элемент) и условия (ветвление по истинности значения) делают возможным «форма данных определяет структуру вывода» — отчёты построчно, уведомления по наличию вложений. Вызовы функций (форма call обзорного образца) регистрируют переиспользуемую логику форматирования как вызываемые из шаблона единицы — «логика показа» вроде смены регистра и форм множественного числа остаётся на стороне шаблона. Граничная дисциплина конструкций и функций: шаблон принимает решения показа, а не считает бизнес — сложное выражение в шаблоне сигналит, что ему пора обратно в слой подготовки данных.

Позиционирование ошибок (линия обороны компиляции)

Ошибки компиляции несут номер строки (обзорный образец проверяет error-location line=1) — синтаксическая ошибка в шаблоне вскрывается при компиляции, а не выплёвывается покалеченным текстом. Ошибки рендеринга (промах пути) также докладываются структурно — система ошибок главы 4 действует как обычно.

35.3示例

Полная программа: компиляция, дерево значений и три интерполяции

Из образца репозитория examples/template/core/main.c:

examples/template/core/main.c源码 ↗




















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	xvalue* pData = xrtValueObject();   /* 渲染数据:一个对象 */
	xtemplate* pTemplate;
	str sOutput;
	xtime Time;

	



	 */
	if ( (pData == NULL) || !xrtDateTime(
		2026,
		4,
		2,
		12,
		30,
		0,
		0,
		&Time
	) || !xrtValueObjectSetNew(
		pData,
		XRT_STR_LITERAL("name"),
		xrtValueString(XRT_STR_LITERAL("xrt"))
	) || !xrtValueObjectSetNew(
		pData,
		XRT_STR_LITERAL("requests"),
		xrtValueInt(42)
	) || !xrtValueObjectSetNew(
		pData,
		XRT_STR_LITERAL("created"),
		xrtValueTime(Time)
	) ) {
		xrtValueRelease(pData);
		return 1;
	}

	/* 编译:一次解析成不可变对象;模板语法错在此步报错。 */
	pTemplate = xrtTemplateCompile(
		XRT_STR_LITERAL(
			"{$name} handled {%requests:,d} requests on {&created:%F}"
		)
	);
	if ( pTemplate == NULL ) {
		xrtValueRelease(pData);
		return 2;
	}

	/* 渲染:NULL 配置 = 默认预算(防失控输出的上限仍生效)。 */
	sOutput = xrtTemplateRender(pTemplate, pData, NULL);
	if ( sOutput == NULL ) {
		xrtTemplateRelease(pTemplate);
		xrtValueRelease(pData);
		return 3;
	}
	puts(sOutput);

	/* 三个拥有式资源逆序释放。 */
	xrtFree(sOutput);
	xrtTemplateRelease(pTemplate);
	xrtValueRelease(pData);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/template/core/main.c -lws2_32 -liphlpapixrt handled 42 requests on 2026-04-02

Что только что произошло. (1) Сторона данных строится деревом значений главы 31: ObjectNew + SetNew (поля типов String/Int/Time) — шаблон ест деревья значений, так что продукты разбора JSON/XLON (главы 32/33) подключаются напрямую, ноль преобразований. (2) {$name} выводит строку как есть; {%count:,d} прогоняет число через форматирование тысяч главы 26 (42 без разделителей тоже легально); {&day:%F} рендерит время датой ISO — три префикса по одному заходу каждый. (3) Render возвращает владеющий текст, освобождение xrtFree; скомпилированный объект возвращается Release-ом. Подставили другое дерево значений — тот же код выдаёт другое уведомление: разделение шаблона и данных здесь нагляднее всего.

Полная программа: управляющие конструкции и циклы

Из examples/template/control/main.c — цикл по списку и условные ветви:

examples/template/control/main.c源码 ↗






















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	xvalue* pRoot = xrtValueObject();
	xvalue* pUsers = xrtValueArray();
	xtemplate* pTemplate;
	str sOutput;

	



	 */
	if ( (pRoot == NULL) || (pUsers == NULL) ||
		 !xrtValueArrayAppendNew(
			pUsers,
			xrtValueString(XRT_STR_LITERAL("Alice"))
		) || !xrtValueArrayAppendNew(
			pUsers,
			xrtValueString(XRT_STR_LITERAL("Bob"))
		) || !xrtValueObjectSetTake(
			pRoot,
			XRT_STR_LITERAL("users"),
			&pUsers
		) ) {
		xrtValueRelease(pUsers);
		xrtValueRelease(pRoot);
		return 1;
	}

	/* 一条模板串演示全部控制语句(见文件头语法速查)。 */
	pTemplate = xrtTemplateCompile(XRT_STR_LITERAL(
		"{#if:users}Users: {#foreach:users}"
		"{?loop.first::, }{$loop.value}{#end}{#else}No users{#end}; "
		"range: {#for:1:5}"
		"{#if:loop.value = 2}{#continue}{#end}"
		"{#if:loop.value = 4}{#break}{#end}"
		"{%loop.value}{#end}"
	));
	if ( pTemplate == NULL ) {
		xrtValueRelease(pRoot);
		return 2;
	}
	sOutput = xrtTemplateRender(pTemplate, pRoot, NULL);
	if ( sOutput == NULL ) {
		xrtTemplateRelease(pTemplate);
		xrtValueRelease(pRoot);
		return 3;
	}
	puts(sOutput);
	xrtFree(sOutput);
	xrtTemplateRelease(pTemplate);
	xrtValueRelease(pRoot);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/template/control/main.c -lws2_32 -liphlpapiUsers: Alice,Bob; range: 13

Что только что произошло. (1) Цикл обходит массив пользователей в дереве значений — каждый элемент входит в переменную цикла, сегмент шаблона рендерится повторно; вывод через запятую определён фрагментом шаблона внутри тела цикла. (2) Условия и диапазоны: range: 13 показывает вывод условно-агрегатных конструкций — управляющие конструкции позволяют шаблону подстраиваться под форму данных, а не данным гнуться под фиксированный шаблон. (3) Обзорный образец оставлен для продвинутого чтения: он покрывает загрузку шаблона из файла, потоковый вывод рендеринга, вызовы функций и реестр — четыре группы способностей, полная форма шаблонного движка в реальном проекте; четыре строки ok соответствуют им по одной.

35.4契约

  • Двухслойная модель: скомпилированный объект неизменяем, разделяется подсчётом ссылок; рендеринг читает шаблон и идёт высокочастотно параллельно (у каждого потока своё дерево значений).
  • Три префикса: {$} прямой вывод / {%:fmt} строка формата чисел главы 26 / {&:fmt} строка формата времени.
  • Данные — это дерево: вход рендеринга — xvalue; продукты JSON/XLON подключаются без преобразований.
  • Позиционирование ошибок: ошибки компиляции с номером строки; ошибки рендеринга (путь/тип) — структурный отчёт.
  • Граничная дисциплина: шаблон решает вопросы показа, а не бизнеса — сложные выражения переезжают в слой подготовки данных.
  • Владеющий продукт: вывод рендеринга — str, освобождение xrtFree; потоковый рендеринг подключается прямо к sink.

От примеров к инженерии: три хозяина шаблонов

Генерация уведомлений/сообщений (самое частое): глобальные шаблоны компилируются на старте, по приходу события строится дерево значений, рендеринг — и в путь; хорошая форма ловушки 1 и есть её каркас; в многоязычных сценариях компилируется по шаблону на язык, выбор — по языку пользователя. Отчёты и скелеты страниц: строки данных рендерятся циклом, заголовки ветвятся условием — главная площадка управляющих конструкций; большие отчёты идут потоковым рендерингом прямо в sink (форма streamed обзорного образца) — миллион строк не собирает целую страницу в памяти. Конфигурируемые тексты UI: тексты-шаблоны лежат в конфигурации (правка текста без релиза), поля берутся из дерева конфигурации — шаблон и данные становятся горячо-обновляемыми активами. Общий путь эволюции трёх хозяев: шаблон из константы кода → файл конфигурации → горячо-обновляемый актив; каждый шаг опирается на двухслойный дизайн «скомпилированный объект заменяем, рендеринг без состояния».

Карта разделения труда с JSON/XLON/регулярными выражениями

Четыре модуля второй половины тома 4 (плюс дерево значений) часто выходят на одну сцену; карта разделения не даёт перепутать. JSON/XLON (главы 32/33): структурированные данные входят и выходят — разбор в дерево значений; сериализация дерева обратно. Дерево значений (глава 31): промежуточное представление — общая валюта всех модулей. Регулярные выражения (глава 30): извлекают структуру из неструктурированного текста (поля журналов, строки конфигурации) → порождают дерево значений. Шаблоны (эта глава): порождают текст из дерева значений (уведомления, отчёты) → продуцируют строки. Извлечение и генерация разнонаправленны и зеркальны — композиционная глава 36 сцепит «извлечь → дерево значений → сгенерировать шаблоном» в полный конвейер. На карте каждый модуль делает ровно одно дело; композиция идёт через дерево значений как промежуточную валюту.

Взгляд на дизайн: шаблон — тоже интерфейс

Между шаблоном и кодом рендеринга есть неявный интерфейс: множество путей и типов, на которые шаблон ссылается. Интерфейс сменился (поле переименовано, тип из int стал string), а шаблон не поспел — рендеринг упадёт в рантайме. Три привычки делают неявное явным: дымовой тест рендеринга (минимальное дерево + рендер всех префиксов с утверждённым выводом) — в CI; файлы шаблонов — в том же репозитории и ревью, что код; в чек-лист смены полей добавляется графа «затронутые шаблоны». Композиционная глава 36 развернёт эту идею «интерфейса» в контракт данных полного конвейера.

Типичные ошибки

Ловушка 1: перекомпиляция на каждый рендеринг (изоморф ловушке 1 у регулярных выражений):Ловушка 1: перекомпиляция на каждый рендеринг (изоморф ловушке 1 у регулярных выражений)

Симптом: аномальный CPU на высокочастотном пути уведомлений/отчётов; горячая точка в xrtTemplateCompile — полный изоморф ловушки 1 регулярных выражений главы 30.

Причина: компиляция (разбор в дерево узлов) — тяжёлая операция; рендеринг — лёгкая. Тяжёлая попала в цикл.

错误写法
str render(const xvalue* pData, xstrview Tpl)
{
	xtemplate* T = xrtTemplateCompile(Tpl);   /* компиляция на каждый рендеринг */
	str s = xrtTemplateRender(T, pData, NULL, NULL);
	xrtTemplateRelease(T);
	return s;
}
正确写法
/* компиляция один раз на старте; смена шаблона - через "перекомпиляция + атомарная замена" */
static xtemplate* gTpl;
str render(const xvalue* pData)
{
	return xrtTemplateRender(gTpl, pData, NULL, NULL);   /* только лёгкий путь */
}
Ловушка 2: бизнес-вычисления в шаблоне (слой показа переходит границы):Ловушка 2: бизнес-вычисления в шаблоне (слой показа переходит границы)

Симптом: шаблоны растут и всё больше походят на ещё один язык программирования; смена бизнес-правил требует правки шаблонов; тесты шаблонов не покрывают логические ветви.

Причина: шаблонный движок дал выразительность, и команда перенесла в него подготовку данных — граница между «решениями показа» и «бизнес-вычислениями» пала.

错误写法
<!-- 模板里做业务计算:满减规则、运费、税费全在模板表达式里 -->
Total: {%cart.subtotal - discount(cart.promo) + shipping(cart.weight) + tax(...):,.2f}
正确写法
<!-- 模板只展示;计算在代码层完成、结果进值树 -->
Total: {%order.total:,.2f}
(order.total 由代码层按业务规则算好再构造值树)

Практические задания

Базовое: по одному рендеру на каждый префикс

Постройте дерево значений с полями строка/число/время; шаблоном отрендерите каждое своим префиксом; подставьте другой набор данных и перерендерьте, проверяя переиспользование шаблона.

Продвинутое: уведомление с циклом (управляющие конструкции на практике)

Дерево значений с массивом пользователей (имя + число заказов у каждого); шаблон циклом рендерит сводную строку «каждому {name}({count})»; для пустого массива выводится «никто не заказывал» — условие и цикл в связке.

Вызов: отчёт на конфигурации (шаблон и конфигурация — обе горячие)

JSON-конфигурация (глава 32) определяет метаданные отчёта (заголовок, столбцы, пути данных); шаблон по конфигурации динамически рендерит шапку и строки — количество и имена столбцов целиком из конфигурации, без жёсткого кодирования. Критерии приёмки: один шаблон с двумя разными конфигурациями рендерит две разные таблицы; отсутствие столбца даёт ошибку с путём; компиляция происходит ровно один раз (проверка на нескольких рендерах).

Итоги

ТемаКратко
Двухслойная моделькомпиляция неизменяема и разделяема (Retain/Release) / рендеринг только читает, высокочастотно и параллельно
Три префикса{$path} прямой вывод / {%path:fmt} формат чисел / {&path:fmt} формат времени
Данныедерево значений напрямую — продукты JSON/XLON без преобразований; дерево значений — общая валюта модулей
Управлениециклы по массивам / условные ветви / вызовы функций (логика показа — шаблону, бизнес-вычисления — коду)
Ошибкикомпиляция — с номером строки; рендеринг — структурно (путь/тип)
Дисциплина производительностикомпиляция один раз, глобальное разделение; на горячем пути — только рендеринг; та же двухслойная модель, что у регулярных выражений
Границарешения показа — шаблону, бизнес-вычисления — слою кода
Три хозяинагенерация уведомлений / страницы отчётов (потоковый рендеринг) / тексты на конфигурации — эволюция на двухслойном дизайне
Карта разделенияJSON/XLON водят структуру, дерево значений — валюта, регулярные выражения извлекают, шаблоны порождают
Интерфейсный взглядпути и типы, на которые ссылается шаблон, — неявный интерфейс; дымовой тест рендеринга в CI защищает от дрейфа

📖 Справочники API в этой главе:template (51 записей)value (159 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки