Перейти к основному содержимому

Условный вывод через 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>
./run age.tdc (6 строк)
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>
./run age.tdc (6 строк)
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 &lt; 18" ломается с ошибкой TDC103. Используйте обычные символы.

Где применяется if

Один и тот же язык выражений работает на трёх тегах, с немного разным эффектом:

ТегЭффект ложного if
<line>Вся строка подавляется — вместе с межстрочным разделителем.
<data>Подавляется только этот кусок текста; другие <data> в той же строке работают.
<gen> внутри <sequence>Делает условную последовательность (ниже). В блоке вывода генераторы не разрешены.

Подавление части строки через <data>

Несколько <data if="…"> на одной строке, каждый со своей меткой, делают подошедшие строки очевидными с одного взгляда:

<block>
<line><data>возраст ${{Age}}:</data><data if="Age >= 18"> совершеннолетний</data></line>
</block>
./run age.tdc (6 строк)
возраст 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>
./run gendered-name.tdc (6 строк)
Женщина: Анна
Мужчина: Дмитрий
Женщина: Софья
Женщина: Ольга
Мужчина: Иван
Мужчина: Егор

Зачем/когда: каждое ${{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

Голый идентификатор (без кавычек) разрешается в два шага:

  1. Сначала TDC ищет последовательность с таким именем и возвращает её значение на этой строке.
  2. Если такой последовательности нет, идентификатор трактуется как строковый литерал, равный собственному имени.

Поэтому можно писать:

<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>
./run age-bands.tdc (6 строк)
возраст 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>
./run roles.tdc (6 строк)
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>
./run strict.tdc (5 строк)
_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>
./run logical.tdc (6 строк)
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>
./run arithmetic.tdc (6 строк)
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>
./run dotted.tdc (6 строк)
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>
./run json-list.tdc (4 строки)
--- 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>
./run combined.tdc (6 строк)
Жанна (Женщина, 36) — не мужчина
Валентин (Мужчина, 10)
Ольга (Женщина, 32) — не мужчина
Виктория (Женщина, 14) — не мужчина
Виктор (Мужчина, 16)
Геннадий (Мужчина, 20) — взрослый мужчина

Метка взрослый мужчина показывается только когда истинны оба условия (Gender == Мужчина и Age >= 18); не мужчина — всякий раз, когда Gender не Мужчина.

Компилируется один раз, дёшево переиспользуется

Каждое выражение компилируется единожды и кэшируется. На тысячах строк одно и то же if="…" обходится в переоценке почти бесплатно.

См. также