Условный вывод через 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".
Операторы сравнения
| Оператор | Значение |
|---|---|
== | Равно (с мягкой числовой промоцией) |
!= | Не равно (симметрично ==) |
=== | Строгое равенство (значение и тип) |
!== | Строгое неравенство |
< | Меньше |
> | Больше |
<= | Меньше или равно |
>= | Больше или равно |
Операторы порядка <, >, <=, >= всегда приводят оба операнда к числу.
<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: [обычный]
== и != делят строки ровно пополам условия: где одно истинно, там другое ложно.
Мягкое == против строгого ===
== сравнивает мягко — если одна сторона число, а другая строка-число, они
сравниваются как числа. === требует ещё и одинакового типа.
_count хранится как строка, поэтому против числа 3
операторы расходятся:
<line><data>_count=${{_count}}:</data><data if="_count == 3"> ==3</data><data if="_count === 3"> ===3</data><data if="_count !== 3"> !==3</data></line>
_count=1: !==3 _count=2: !==3 _count=3: ==3 !==3 _count=4: !==3 _count=5: !==3
На строке 3 расхождение видно: _count == 3 истинно (мягко: "3" равно 3 как
числа), а _count === 3 ложно (строка "3" не равна числу 3 по типу),
из-за чего _count !== 3 истинно на каждой строке, включая третью. Метка
===3 не появляется нигде — строгое равенство строки и числа никогда не
срабатывает.
Зачем/когда: беритесь за === только когда важно различие типов. Для обычных
проверок полей нужно мягкое ==, потому что сгенерированные значения — строки.
Правило мягкой промоции
Когда один операнд число, а другой строка, TDC сначала пытается прочитать строку как число:
_count == 5—_countэто строка"5", но==сравнивает их как числа. ✓Age == 18— строкуAgeпротив числа18сравнивает численно. ✓Gender == Мужчина— обе стороны строки, поэтому сравниваются как строки. ✓
Логические операторы
| Оператор | Значение |
|---|---|
&& | И |
|| | ИЛИ |
! | НЕ |
<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) отклоняются на валидации, до того как будет сгенерирована
хоть одна строка: if="_count % 2 == 0" падает с error[TDC101]: unsupported operator "%" in if expression и не даёт вывода вообще.
?. (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 интуитивно.
Встроенные последовательности в 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в справочнике атрибутов.- Связанные и согласованные данные и Иерархические зависимости — где условные последовательности особенно хороши.