Файлы и CSV
Когда значения уже лежат в файле — справочник, который ведёт команда, выгрузка,
таблица, сохранённая как CSV — читайте их генератором
file, а не вставляйте в конфиг вручную. Это
руководство проходит типичный сценарий работы с CSV от начала до конца: простой
список, одна колонка, правильный разделитель и сохранение записи целиком. Каждый
атрибут разобран во всех подробностях на странице
Генератор file; здесь же в центре внимания сама задача.
Примеры вывода иллюстративны — точные значения случайны и могут отличаться от версии к версии ядра. Важна форма каждого шага и количество строк.
- Aисходный файл: четыре строки, три колонки
- Bбез row= каждое поле выбирает свою строку, и запись собирается из кусков, которые никогда не были вместе (серые ячейки)
- Cс row= все три поля читают одну строку, поэтому каждая запись — настоящая строка файла
Подготовка: папка с данными
Все примеры ниже читают из папки data/ рядом с конфигом, которая передаётся при
запуске через --data-path. В ней лежат два файла — простой список и CSV.
data/cities.txt (одно значение на строку):
Moscow
Kazan
Perm
Omsk
Tver
data/users.csv (строка-заголовок, затем записи):
first_name,last_name,email,city
Anna,Orlova,anna.orlova@example.com,Moscow
Boris,Petrov,boris.petrov@example.com,Kazan
Vera,Sidorova,vera.sidorova@example.com,Perm
Grigori,Ivanov,grigori.ivanov@example.com,Omsk
Любой конфиг запускается одинаково:
./run example.tdc --data-path ./data
Простой список — одна строка, одно значение
Когда пригодится: у вас есть плоский набор значений (города, названия
товаров, теги), который неудобно зашивать в value="…". Направьте
src на файл и не указывайте
column — каждая непустая строка станет одним
значением:
<sequence name="City">
<gen type="file" src="@data/cities.txt"/>
</sequence>
...
<data>${{City}}</data>
Omsk Moscow Perm Perm Kazan
Строки выбираются равномерно случайно, с повторами (Perm выпал дважды). Пустые
строки пропускаются. Чтобы читать строго в порядке файла, а не случайно, добавьте
order="sequential" — тогда строки выдаются ровно в том порядке, в каком идут в
файле (см. Маски и регистр).
Колонка CSV — одно поле из таблицы
Когда пригодится: файл — это таблица, а вам нужно одно поле — например, только
адреса почты. Добавление column как раз и переключает
генератор из режима списка в режим CSV: строка-заголовок отбрасывается, а значения
берутся только из указанной колонки.
По имени
Укажите колонку по имени из строки-заголовка:
<gen type="file" src="@data/users.csv" column="email"/>
anna.orlova@example.com grigori.ivanov@example.com anna.orlova@example.com boris.petrov@example.com anna.orlova@example.com
Заголовок (first_name,last_name,email,city) в вывод никогда не попадает — он
распознаётся как заголовок и используется только для поиска колонки.
По номеру (с единицы)
Ту же колонку можно адресовать по позиции. column="2" — это второй столбец
(last_name); нумерация начинается с единицы, поэтому первый столбец — это
column="1", а не column="0". При адресации по номеру TDC не видит имён из
заголовка, поэтому добавьте header="true", чтобы
отбросить первую строку:
<gen type="file" src="@data/users.csv" column="2" header="true"/>
Sidorova Petrov Petrov Orlova Orlova
Тот же файл с column="3" читает третий столбец, email — те же данные, что и
column="email", просто адресованные по позиции, а не по имени.
Частые ошибки
Пара промахов на единицу и с разделителем падает громко, а не тихо, поэтому их легко заметить:
column="0" -> error[TDC062]: CSV column "0" was not found in the header row column="9" -> error[TDC062]: CSV column "9" ... has no values
column="0"— не номер (нумерация с 1): он читается как буквальное имя0, которого нет в заголовке.- Номер за пределами последней колонки (
column="9"для файла из четырёх столбцов) не имеет ячеек для чтения. - Если разделитель в файле не запятая, задайте его (см. ниже) — иначе вся строка попадёт в одну ячейку и колонка не найдётся.
Разделитель — запятые, точки с запятой, табы
Когда пригодится: выгрузка разделена не запятой. Таблицы часто сохраняются с
точкой с запятой или табом. Если разделитель не задан, TDC режет по запятым, не
находит ячеек и считает всю строку одним полем — так что колонка никогда не
находится. Задайте delimiter под ваш файл. Возьмём тех
же пользователей, сохранённых с точкой с запятой, data/users_semicolon.csv:
first_name;last_name;email;city
Anna;Orlova;anna.orlova@example.com;Moscow
Boris;Petrov;boris.petrov@example.com;Kazan
Vera;Sidorova;vera.sidorova@example.com;Perm
Grigori;Ivanov;grigori.ivanov@example.com;Omsk
Без delimiter предполагается запятая, и колонка email не находится:
error[TDC062]: file generator: CSV column "email" was not found in the header row note: For CSV files, use a header name like column="email" or a 1-based index like column="2".
С delimiter="semicolon" (или delimiter=";") ячейки режутся правильно:
<gen type="file" src="@data/users_semicolon.csv" column="email" delimiter="semicolon"/>
anna.orlova@example.com grigori.ivanov@example.com anna.orlova@example.com boris.petrov@example.com
delimiter принимает один символ или имя-алиас — comma (по умолчанию),
semicolon, pipe, tab. Для файла с табами (TSV) delimiter="tab" и
delimiter="\t" эквивалентны. Полная таблица алиасов — на странице
Генератор file.
Сохранить запись целиком — ключ row
Когда пригодится: несколько полей должны браться из одной и той же строки
CSV. Этот шаг пропускают чаще всего. Независимые генераторы
file каждый выбирает свою строку, и запись
рассыпается: имя из одной строки, фамилия из другой, город из третьей.
Без row — три независимых генератора, и записи не сходятся (Вера с фамилией
Петров, Борис — не в своём городе):
<sequence name="User">
<gen name="First" type="file" src="@data/users.csv" column="first_name"/>
<gen name="Last" type="file" src="@data/users.csv" column="last_name"/>
<gen name="City" type="file" src="@data/users.csv" column="city"/>
</sequence>
...
<data>${{User.First}} ${{User.Last}} — ${{User.City}}</data>
Vera Petrov — Moscow Anna Orlova — Kazan Boris Sidorova — Omsk Boris Ivanov — Kazan Boris Petrov — Omsk
С общим row="user" — каждый генератор с этим же ключом читает одну и ту
же выбранную строку, а разные колонки берут из неё разные ячейки. Записи
согласованы:
<sequence name="User">
<gen name="First" type="file" src="@data/users.csv" column="first_name" row="user"/>
<gen name="Last" type="file" src="@data/users.csv" column="last_name" row="user"/>
<gen name="City" type="file" src="@data/users.csv" column="city" row="user"/>
</sequence>
Vera Sidorova — Perm Grigori Ivanov — Omsk Boris Petrov — Kazan Boris Petrov — Kazan Anna Orlova — Moscow
Теперь first_name, last_name и city всегда берутся из одной строки CSV —
поля не могут разъехаться. Ключ — любая непустая строка (row="user"); генераторы
связываются, когда у них совпадают row, src, delimiter и режим заголовка. Это
работает на любом движке (по умолчанию — потоковый, поэтому память не растёт с
числом строк).
Взвешенные строки — тянем строку по частоте
Когда пригодится: связанную строку нужно выбирать по реальной частоте, а не
равномерно — товар с его настоящей частотой продаж, а его цена и категория
приходят вместе с ним. Добавьте weight="колонка" на
одно из полей группы — и строка выбирается взвешенным жребием по этой колонке
(точно, как разбивка percent), а остальные поля
по-прежнему читают из той же выбранной строки. Пусть data/catalog.csv будет:
name,category,price,sales
Pen,Office,1.10,500
Coffee,Drinks,4.50,1200
Backpack,Bags,45.00,80
<sequence name="Item">
<gen name="Name" type="file" src="@data/catalog.csv" column="name" row="i" weight="sales"/>
<gen name="Price" type="file" src="@data/catalog.csv" column="price" row="i"/>
<gen name="Cat" type="file" src="@data/catalog.csv" column="category" row="i"/>
</sequence>
...
<data>${{Item.Name}} | ${{Item.Cat}} | ${{Item.Price}}</data>
Coffee | Drinks | 4.50 Pen | Office | 1.10 Coffee | Drinks | 4.50
Coffee (продажи 1200) выпадает чаще всего, Backpack (продажи 80) — редко, и цена и категория каждого товара всегда совпадают с его собственной строкой.
Про движок. Без weight связанная группа работает на любом движке. С
weight конфиг всегда считает движок в памяти: потоковый движок не может
взвесить выбор строки, не зная сначала общих итогов по файлу. При явном
--engine 2 TDC честно об этом скажет, а не выдаст молча несвязные колонки. Полную
картину см. в Согласованные и связанные данные.
Ограничения (v1)
rowработает только внутри<sequence>. В блоке вывода генераторов нет, поэтому там вопрос не возникает.rowтребуетcolumn— это возможность для CSV, а не для простого текстового списка.- Один и тот же ключ
rowс разными источниками их не связывает: TDC заводит отдельную группу строк на каждое сочетание источника, разделителя и режима заголовка. Это не ошибка, просто нужно иметь в виду.
Запись CSV обратно
Прочитать CSV — половина дела, записать его безопасно — вторая половина.
Значение с запятой или кавычкой сломало бы строку. Фильтр
csv заключает поле по RFC 4180:
<data>${{Id}},${{Name | csv}},${{Category}}</data>
7,"Набор ножей, 3 шт",Кухня 2,"Кофе ""Арабика"" 250 г",Бакалея
Запятая внутри Набор ножей, 3 шт больше не разбивает строку, а внутренние кавычки
в Кофе "Арабика" 250 г удваиваются. Фильтры экранирования
csv и sql, а также сборка целых
файлов CSV/JSON/SQL, разобраны в Масках и регистре и в
Форматах вывода.
Где ищутся файлы
src="@data/…" ищется в папках, переданных через --data-path; голый
src="names.txt" берётся рядом с конфигом; абсолютные пути тоже работают.
Файлы читаются как UTF-8, а неразрешённый путь останавливает рендер
с ошибкой, а не тихо выдаёт пустоту. Полная таблица разрешения путей — на странице
Генератор file.
Смотрите также
- Генератор file — каждый атрибут во всех подробностях.
- Согласованные и связанные данные — связывание зависимых полей и взвешенные строки.
- Маски и регистр — фильтры экранирования
csv/sqlиorder="sequential". - Форматы вывода — сборка CSV/JSON/SQL вокруг ваших данных.