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

Условный вывод через 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".

Операторы сравнения

ОператорЗначение
==То же число
!=Не то же число
===Тот же текст, символ в символ
!==Не тот же текст
<Меньше
>Больше
<=Меньше или равно
>=Больше или равно

Каждая колонка, которую производит TDC, — это текст, поэтому у «равно» два прочтения, и у каждого свой оператор. Полный разбор — в Сравнении и истинности.

Операторы порядка <, >, <=, >= всегда приводят оба операнда к числу.

<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: [обычный]

== и != делят строки ровно пополам условия: где одно истинно, там другое ложно.

== — это число, === — это текст

== спрашивает, одно ли это число, и потому читает колонку из цифр как число. === спрашивает, печатаются ли обе стороны одними и теми же символами, и не читает ничего ни как что. Расходятся они ровно там, где число и символы различаются:

<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>
./run strict.tdc
"7": ==7 ===7
"07": ==7 !==7
"7.0": ==7 !==7
"x": !==7
"7": ==7 ===7

07 и 7.0 являются семёркой, поэтому == 7 держится; ни одно из них не печатается как 7, поэтому === 7 — нет.

Зачем/когда: == — повседневный выбор: суммы, возрасты, счётчики, слова-категории. Беритесь за ===, когда важны точные символы: идентификатор фиксированной ширины, код, который обязан сохранить ведущий ноль, колонка-флаг со словом true.

Правило, по которому работает ==

  1. Обе стороны целые числа → сравниваем как целые, точно.
  2. Одна сторона — написанное вами число, другая — текст, читающийся как число → сравниваем как числа.
  3. Иначе → сравниваем как текст.
  • _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>
./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) отклоняется на валидации, до того как будет сгенерирована хоть одна строка: 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>
./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 интуитивно.

Колонка со значением 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>
./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="…" обходится в переоценке почти бесплатно.

См. также