Условный вывод через if
Когда это нужно — когда значение должно решать, появится ли часть строки, а не только что в ней написано: одну карточку оставить, другую убрать; одни строки пометить, остальные оставить как есть; после каждой записи, кроме последней, поставить запятую.
Генератор выдаёт значение всегда. Атрибут if — это отдельный выключатель: он
смотрит на текущие значения строки и решает, дойдёт ли до вывода тег, на котором он
стоит. Выражение пересчитывается заново на каждой карточке против её данных.
if принимает небольшой язык выражений — подмножество синтаксиса JavaScript — со
сравнениями, логическими и арифметическими операторами, строковыми и числовыми
литералами и именами последовательностей. Эта страница разбирает язык целиком.
Примеры вывода ниже — иллюстративные: они показывают форму результата и могут
отличаться между версиями ядра. В учебных примерах используется
order="sequential", чтобы значения шли в
фиксированном порядке и эффект каждого условия было легко разглядеть.
До / после
Возьмём поле Age и напечатаем шесть карточек вообще без условия:
<env count="6" seed="demo">
<sequence name="Age"><gen type="text" value="15,17,18,25,40,70" order="sequential"/></sequence>
</env>
<block>
<line><data>${{_count}}. возраст ${{Age}}</data></line>
</block>
1. возраст 15 2. возраст 17 3. возраст 18 4. возраст 25 5. возраст 40 6. возраст 70
Теперь навесим if="Age >= 18" на саму <line>. Когда
условие ложно, TDC убирает строку целиком:
<block>
<line if="Age >= 18"><data>${{_count}}. возраст ${{Age}} — совершеннолетний</data></line>
</block>
3. возраст 18 — совершеннолетний 4. возраст 25 — совершеннолетний 5. возраст 40 — совершеннолетний 6. возраст 70 — совершеннолетний
Первые две карточки (15 и 17) исчезли — на них Age >= 18 было ложно. Обратите
внимание: счётчик по-прежнему читается как 3, 4, 5, …:
_count — это место карточки во всём наборе, оно
определяется до рендеринга, поэтому подавление строки не перенумеровывает остальные.
<, > и & как естьВнутри if операторы пишутся как есть: if="Age < 18",
if="A >= 1 && B <= 9". XML-сущности TDC не раскрывает — if="Age < 18"
ломается с ошибкой TDC103. Используйте обычные символы.
Где применяется if
Один и тот же язык выражений работает на трёх тегах, с немного разным эффектом:
| Тег | Эффект ложного if |
|---|---|
<line> | Вся строка подавляется — вместе с межстрочным разделителем. |
<data> | Подавляется только этот кусок текста; другие <data> в той же строке работают. |
<gen> внутри <sequence> | Делает условную последовательность (ниже). В блоке вывода генераторы не разрешены. |
Подавление части строки через <data>
Несколько <data if="…"> на одной строке, каждый со своей меткой, делают
подошедшие строки очевидными с одного взгляда:
<block>
<line><data>возраст ${{Age}}:</data><data if="Age >= 18"> совершеннолетний</data></line>
</block>
возраст 15: возраст 17: возраст 18: совершеннолетний возраст 25: совершеннолетний возраст 40: совершеннолетний возраст 70: совершеннолетний
Зачем/когда: используйте <data if>, чтобы пометить строку, не убирая её —
префикс возраст N: печатается всегда, а метка совершеннолетний — только когда
условие выполняется.
Условные последовательности с <gen if>
Когда теги <gen> несут if внутри <sequence>, последовательность становится
условной: побеждает первый <gen>, чьё условие истинно, и его значение
становится значением последовательности на этой строке. <gen> без if — запасной вариант
(«else»). Если ничего не подошло — последовательность на этой строке пустая.
Так вся логика «какое значение зависит от чего» остаётся в <env>, а блок вывода
остаётся чистым форматированием:
<sequence name="Gender"><gen type="text" value="Мужчина,Женщина" percent="42,58"/></sequence>
<sequence name="Name">
<gen if="Gender.Мужчина" type="template" value="person.male.firstName"/>
<gen if="Gender.Женщина" type="template" value="person.female.firstName"/>
</sequence>
Женщина: Анна Мужчина: Дмитрий Женщина: Софья Женщина: Ольга Мужчина: Иван Мужчина: Егор
Зачем/когда: каждое ${{Name}} уже правильное имя для своего пола — никаких
ветвлений по строкам в блоке. Эта же идея разбирается в разделах
Связанные и согласованные данные и
Иерархические зависимости.
Обращение к значениям
- Имя последовательности обозначает её значение на текущей строке:
Gender == Мужчина,Age >= 18. - Составное поле через точку:
Person.FirstName,Doctor.last. - Сахар
X.Value— еслиXэто последовательность, аX.Valueне является сам по себе составным полем, выражение читается как проверка равенстваX == "Value". То естьif="Gender.Мужчина"означает «сейчасGenderравенМужчина», аif="!Gender.Мужчина"— «не Мужчина». Это ровно та же точечная нотация, что и вparent="X.Value".
Gender == Мужчина эквивалентно Gender.Мужчина
Gender != Мужчина эквивалентно !Gender.Мужчина
Литералы и голые идентификаторы
| Тип | Пример |
|---|---|
| Число | 5, 3.14, -42 |
| Строка | "admin", 'text' |
| Идентификатор | Name, _count |
Голый идентификатор (без кавычек) разрешается в два шага:
- Сначала TDC ищет последовательность с таким именем и возвращает её значение на этой строке.
- Если такой последовательности нет, идентификатор трактуется как строковый литерал, равный собственному имени.
Поэтому можно писать:
<data if="Role == admin">…</data>
Последовательности с именем admin нет, поэтому admin — это просто слово "admin",
эквивалентное Role == "admin".
Операторы сравнения
| Оператор | Значение |
|---|---|
== | То же число |
!= | Не то же число |
=== | Тот же текст, символ в символ |
!== | Не тот же текст |
< | Меньше |
> | Больше |
<= | Меньше или равно |
>= | Больше или равно |
Каждая колонка, которую производит TDC, — это текст, поэтому у «равно» два прочтения, и у каждого свой оператор. Полный разбор — в Сравнении и истинности.
Операторы порядка <, >, <=, >= всегда приводят оба операнда к числу.
<block>
<line><data>возраст ${{Age}}:</data><data if="Age < 18"> младше18</data><data if="Age >= 18"> взрослый</data><data if="Age > 65"> пенсионер</data></line>
</block>
возраст 15: младше18 возраст 17: младше18 возраст 18: взрослый возраст 25: взрослый возраст 40: взрослый возраст 70: взрослый пенсионер
Age < 18 держится за первые две строки, Age >= 18 берёт остальные (граница 18
попадает во взрослый), а Age > 65 помечает только 70.
Зачем/когда: сравнения порядка — самый повседневный случай: возрастные пороги, границы, отсечки по баллам.
Равенство: == и !=
<sequence name="Role"><gen type="text" value="guest,user,admin,user,admin,guest" order="sequential"/></sequence>
...
<line><data>${{_count}}. ${{Role}}:</data><data if="Role == admin"> [админ]</data><data if="Role != admin"> [обычный]</data></line>
1. guest: [обычный] 2. user: [обычный] 3. admin: [админ] 4. user: [обычный] 5. admin: [админ] 6. guest: [обычный]
== и != делят строки ровно пополам условия: где одно истинно, там другое ложно.
== — это число, === — это текст
== спрашивает, одно ли это число, и потому читает колонку из цифр как число. ===
спрашивает, печатаются ли обе стороны одними и теми же символами, и не читает ничего ни
как что. Расходятся они ровно там, где число и символы различаются:
<tdc>
<env count="5" seed="strict" local="ru">
<sequence name="Order"><gen type="text" value="7,07,7.0,x,7" order="sequential"/></sequence>
</env>
<block>
<line><data>"${{Order}}":</data><data if="Order == 7"> ==7</data><data if="Order === 7"> ===7</data><data if="Order !== 7"> !==7</data></line>
</block>
</tdc>
"7": ==7 ===7 "07": ==7 !==7 "7.0": ==7 !==7 "x": !==7 "7": ==7 ===7
07 и 7.0 являются семёркой, поэтому == 7 держится; ни одно из них не
печатается как 7, поэтому === 7 — нет.
Зачем/когда: == — повседневный выбор: суммы, возрасты, счётчики, слова-категории.
Беритесь за ===, когда важны точные символы: идентификатор фиксированной ширины, код,
который обязан сохранить ведущий ноль, колонка-флаг со словом true.
Правило, по которому работает ==
- Обе стороны целые числа → сравниваем как целые, точно.
- Одна сторона — написанное вами число, другая — текст, читающийся как число → сравниваем как числа.
- Иначе → сравниваем как текст.
_count == 5—_countэто текст5, и шаг 1 делает обе стороны целыми. ✓Age == 18— то же самое. ✓Total == 100, где вTotalлежит100.00— шаг 2. ✓Gender == Мужчина— числа нет нигде, поэтому шаг 3 сравнивает текст. ✓
Сравнение и истинность разбирает углы: что считается числом, что считается истиной и какой оператор отвечает на какой вопрос.
Логические операторы
| Оператор | Значение |
|---|---|
&& | И |
|| | ИЛИ |
! | НЕ |
<line><data>${{_count}}. ${{Role}}/${{Age}}:</data><data if="Role == admin && Age >= 18"> взрослый-админ</data><data if="Age < 18 || Age > 65"> льгота</data></line>
1. guest/15: льгота 2. user/17: льгота 3. admin/18: взрослый-админ 4. user/25: 5. admin/40: взрослый-админ 6. guest/70: льгота
взрослый-админ появляется только там, где Role равен admin и Age >= 18
(строки 3 и 5). льгота появляется там, где Age за границами 18..65 (строки 1,
2, 6). Строка 4 (user/25) не подошла ни под одно условие, поэтому не получает
метки вовсе.
Зачем/когда: комбинируйте условия через && / || и отрицайте через ! — тот
же ! работает в сахаре !Gender.Мужчина выше.
Арифметические операторы
| Оператор | Значение |
|---|---|
+ | Сложение (числовое, если хотя бы один операнд число; иначе конкатенация) |
- | Вычитание (операнды приводятся к числу) |
* | Умножение |
/ | Деление |
Арифметику удобно считать прямо внутри сравнения:
<line><data>${{_count}}/${{_total}} возраст ${{Age}}:</data><data if="_count * 2 > _total"> [вторая-половина]</data><data if="Age + 5 >= 45"> [+5>=45]</data><data if="Age - 18 > 0"> [взрослый]</data><data if="Age / 10 >= 4"> [/10>=4]</data></line>
1/6 возраст 15: 2/6 возраст 17: 3/6 возраст 18: 4/6 возраст 25: [вторая-половина] [взрослый] 5/6 возраст 40: [вторая-половина] [+5>=45] [взрослый] [/10>=4] 6/6 возраст 70: [вторая-половина] [+5>=45] [взрослый] [/10>=4]
_count * 2 > _total помечает вторую половину набора; Age + 5, Age - 18 и
Age / 10 считаются и сравниваются как числа.
Зачем/когда: небольшая математика (половины, смещения, отношения) без отдельной последовательности только ради вычисленного числа.
?? (nullish) отклоняется на валидации, до того как будет сгенерирована хоть одна
строка: error[TDC101]: unsupported operator "??" in if expression, и вывода нет вообще.
В самом сообщении перечислены все операторы и функции, которые доступны.
% больше не среди отвергнутых: это остаток, и он евклидов, поэтому -7 % 3 — это 2.
if="_count % 2 == 0" отбирает каждую вторую строку.
?. (optional chaining) хуже, потому что он падает молча: парсер читает
X?.length как обычное точечное обращение X.length, которое сахар X.Value
превращает в проверку X == "length" — почти всегда ложную. Не используйте ?. в
if.
Сахар X.Value в деле
Role.admin — это короткая запись Role == admin, и она отрицается через
!Role.admin:
<line><data>${{_count}}. ${{Role}}:</data><data if="Role.admin"> [Role.admin]</data><data if="!Role.admin"> [!Role.admin]</data></line>
1. guest: [!Role.admin] 2. user: [!Role.admin] 3. admin: [Role.admin] 4. user: [!Role.admin] 5. admin: [Role.admin] 6. guest: [!Role.admin]
Результат в точности совпадает с примером == / != — это две записи одной и той
же проверки.
Истинность / ложность
В логических операторах и в if в целом голое значение читается как булево по
таким правилам:
| Значение | Читается как |
|---|---|
null, undefined | ложь |
0, NaN | ложь |
"" (пустая строка) | ложь |
"false" (строка) | ложь |
"true" (строка) | истина |
| любая другая строка | истина |
| число ≠ 0 | истина |
Случай "false" — специальный: он существует, чтобы
встроенные последовательности _first / _last, которые
хранятся как буквальные строки "true" / "false", вели себя в if интуитивно.
0 — ИСТИНАСтрока 0 в таблице выше — про ЧИСЛО, которое вы пишете в конфиге. Всё, что TDC выдаёт в
колонку, — это ТЕКСТ, поэтому колонка со значением 0 это односимвольная строка "0", а по
строке выше «любая другая строка» — истина. То есть if="Flag" над колонкой из нулей и
единиц срабатывает на каждой строке, включая нули.
Сравнивайте явно, вместо того чтобы полагаться на истинность: if="Flag == 1" задаёт именно
тот вопрос, который вы имели в виду. Правила сравнения
объясняют, почему "0" здесь не число 0 — это то же решение, из-за которого "01" == 1
истинно.
Встроенные последовательности в if
Четыре встроенных — _count, _first, _last, _total — чаще всего и хочется
проверять. Классический приём: запятая после каждой карточки, кроме последней, плюс
заголовок только на первой карточке.
<block>
<line if="_first"><data>--- HEAD ---</data></line>
<line><data>{"id": ${{_count}}}</data><data if="!_last">,</data></line>
</block>
--- HEAD ---
{"id": 1},
{"id": 2},
{"id": 3},
{"id": 4}Зачем/когда: if="!_last" — стандартный трюк для корректных JSON/CSV-склеек;
if="_first" — для одноразового заголовка; if="_count * 2 > _total" — чтобы
оставить только заднюю половину набора. Полный список см. в
Встроенные последовательности.
Приоритет операторов
Приоритет как в JavaScript:
! → * / → + - → < > <= >= → == != === !== → && → ||
Скобки (…) явно меняют порядок — например if="!(Gender == Мужчина)" отрицает всё
сравнение целиком, а не только Gender.
Всё вместе
Соберём ==, &&, >= и !(…) в одну строку:
<env count="6" seed="demo" local="ru">
<sequence name="Gender"><gen type="text" value="Мужчина,Женщина" percent="50,50"/></sequence>
<sequence name="Age"><gen type="number" value="10..40"/></sequence>
<sequence name="Name">
<gen if="Gender.Мужчина" type="template" value="person.male.firstName"/>
<gen if="Gender.Женщина" type="template" value="person.female.firstName"/>
</sequence>
</env>
<block>
<line><data>${{Name}} (${{Gender}}, ${{Age}})</data><data if="Gender == Мужчина && Age >= 18"> — взрослый мужчина</data><data if="!(Gender == Мужчина)"> — не мужчина</data></line>
</block>
Елена (Женщина, 36) — не мужчина Владимир (Мужчина, 10) Наталья (Женщина, 32) — не мужчина Светлана (Женщина, 14) — не мужчина Сергей (Мужчина, 16) Дмитрий (Мужчина, 20) — взрослый мужчина
Метка взрослый мужчина показывается только когда истинны оба условия
(Gender == Мужчина и Age >= 18); не мужчина — всякий раз, когда Gender не
Мужчина.
Каждое выражение компилируется единожды и кэшируется. На тысячах строк одно и то же
if="…" обходится в переоценке почти бесплатно.
См. также
- Встроенные последовательности —
_count,_first,_last,_total. ifв справочнике атрибутов.- Связанные и согласованные данные и Иерархические зависимости — где условные последовательности особенно хороши.