Salida y formato
Una vez que los datos están declarados como secuencias, el bloque de
salida los convierte en texto, con la forma que usted quiera: CSV, JSON, SQL, una línea
de log, un archivo de configuración. Son tres etiquetas anidadas —<block> → <line> →
<data>— más la interpolación y algunas piezas opcionales (fixtures, filtros,
condiciones).
Las salidas de ejemplo de abajo son ilustrativas: los valores exactos pueden variar según la versión del núcleo y la semilla. Lo que importa es la forma de cada transformación, no los nombres o números concretos.
- Ase imprime una vez, al mero principio
- Bantes de cada tarjeta
- Cuna línea de la tarjeta
- Dentre las líneas de una tarjeta — nunca después de la última línea
- Edespués de cada tarjeta
- Fentre tarjetas — nunca después de la última tarjeta
- Gse imprime una vez, al mero final
- Htodo lo que está dentro del corchete se repite una vez por tarjeta
El bloque de salida: <block> → <line> → <data>
<block>describe el diseño de un registro. El renderizador lo repitecountveces —una por registro— sustituyendo en cada pasada valores frescos de las secuencias.<line>es una línea de salida dentro de un registro. Después de cada<line>va un salto de línea.<data>es un contenedor de texto crudo: todo lo que está entre<data>y</data>se imprime tal cual, salvo la interpolación${{…}}, que se reemplaza por los valores de las secuencias.
Un registro puede ocupar varias líneas; usted las describe una vez y el bloque se repite:
<tdc>
<env count="2" seed="demo">
<sequence name="Person">
<gen name="Name" type="text" value="Ana,Carlos,Elena"/>
<gen name="Age" type="number" value="20..40"/>
</sequence>
</env>
<block>
<line><data>name=${{Person.Name}}</data></line>
<line><data>age=${{Person.Age}}</data></line>
</block>
</tdc>
name=Carlos age=30 name=Ana age=28
El bloque de dos líneas se renderizó dos veces (count="2"): dos registros de dos líneas
cada uno, cada uno con su propio Name/Age.
<block> — el diseño de un registro
<block> es el único hijo obligatorio de <tdc>. No tiene
atributos. Su única tarea es contener las líneas de un solo registro, que el renderizador
repite count veces.
Sus únicos hijos permitidos son <line>. Una etiqueta ajena directamente dentro de
<block> es un error, no un salto silencioso: así se atrapa un
<gen> mal puesto o una etiqueta con errata antes de que
corrompa la salida:
error[TDC013]: <foo> is not allowed directly inside <block>
Si <before_block> /
<after_block> /
<delimiter_block> están declarados en
<env>, envuelven cada registro renderizado — vea
Fixtures más abajo.
<line> — una línea de un registro
<line> describe una sola línea dentro de un registro. Se emite una vez por pasada, con
un salto de línea al final. Contiene cualquier cantidad de hijos <data>, que se imprimen
uno tras otro de izquierda a derecha.
if — mostrar toda la línea solo bajo una condición
Problema. Quiere un separador --- entre registros, pero no colgando después del
último. Proteja toda la línea con if: cuando la expresión es falsa, en esa pasada no se
emite ni la línea ni su delimitador entre líneas.
<tdc>
<env count="3" seed="demo">
<sequence name="Name">
<gen type="text" value="Ana,Carlos,Elena" order="sequential"/>
</sequence>
</env>
<block>
<line><data>name=${{Name}}</data></line>
<line if="!_last"><data>---</data></line>
</block>
</tdc>
name=Ana --- name=Carlos --- name=Elena
La segunda línea se imprime en cada registro salvo el último (_last es la bandera
integrada que dice «este es el registro final»): exactamente el comportamiento «entre,
pero no al final» que también da la fixture
<delimiter_block>. La expresión de if
es el mismo lenguajito que usa <data if>: comparaciones,
!, &&, || y los nombres integrados.
comment — una nota que nunca se renderiza
<line comment="…"> lleva una nota libre para el autor de la configuración. Se ignora al
renderizar y nunca aparece en la salida — es cómoda para anotar un diseño complejo.
Los generadores no viven en <line>
El bloque de salida es solo para formatear. No se puede poner un <gen> ni un <mix>
directamente en un <line>: declare una <sequence> nombrada (o un
<mix>) en <env> y refiéralo con
${{Name}}. Así se mantiene separado qué son los datos de cómo se acomodan.
Un límite relacionado: un generador
advanced_regex usado desde la salida no puede
emplear la forma de selección ponderada (?%{...}), porque los porcentajes exactos
necesitan un contexto de secuencia con un count conocido. Declárelo como una secuencia
nombrada en <env>.
<data> — texto crudo
<data> es un contenedor de texto crudo. Todo lo que está entre el <data> de
apertura y su </data> se imprime literalmente, con una sola excepción: la interpolación
${{…}} se reemplaza por los valores de las secuencias.
<data> es el único lugar donde se puede escribir libremente <, >, {, },
comillas y comas — los caracteres que son especiales para XML. Eso es lo que permite
armar JSON, CSV, SQL o cualquier formato construido con esos caracteres:
<line><data><tag> = "value", {key: 42}</data></line>
<tag> = "value", {key: 42}Dos cosas que hay que saber sobre el texto crudo:
- Las entidades XML no se expanden.
<se queda como el literal<, no se convierte en<. Escriba directamente el carácter que realmente quiere. - En el diseño,
<data>vive dentro de un<line>—las líneas del bloque o las de una fixture— y en ningún otro sitio. Aquí lleva dos atributos opcionales:if(suprimir esta pieza según una condición) ypair(para un</data>literal en el texto). La misma etiqueta tiene un segundo trabajo, sin relación con el primero, dentro de<env>: como hijo de una<sequence>es texto literal pegado a un valor compuesto, y con unnamees un campo constante. Diseño o valor — lo decide la etiqueta que la rodea.
Los espacios también son texto — la indentación sale gratis
«Literal» incluye los espacios. Los iniciales, los finales y cualquier racha interior llegan a la salida exactamente como se escribieron:
<line><data> hello </data><data>|end</data></line>
hello |end
No se recorta ni se colapsa nada, así que la indentación de la salida es la
indentación del texto dentro de <data>. En eso consiste toda la técnica para
producir ficheros estructurados y legibles: un esquema SQL indentado, JSON anidado,
YAML, un árbol.
La diferencia que conviene captar: la indentación de las etiquetas (<line>
desplazado dos espacios) es la disposición de su propio config y no aparece en ninguna
parte. Solo es texto lo que está entre <data> y </data>. En el esquema de abajo,
los dos espacios delante de id están dentro de los datos y sobreviven; los cuatro
delante de <line> están fuera y desaparecen:
<env count="2" seed="indent">
<before>
<line><data>CREATE TABLE customers (</data></line>
<line><data> id INTEGER PRIMARY KEY,</data></line>
<line><data> name TEXT NOT NULL,</data></line>
<line><data> city TEXT NOT NULL</data></line>
<line><data>);</data></line>
</before>
<sequence name="Id"><gen type="increment" value="1"/></sequence>
<sequence name="Name"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="City"><gen type="text" value="Paris,Berlin"/></sequence>
</env>
<block>
<line><data>INSERT INTO customers VALUES (${{Id}}, '${{Name}}', '${{City}}');</data></line>
</block>
CREATE TABLE customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT NOT NULL ); INSERT INTO customers VALUES (1, 'James', 'Berlin'); INSERT INTO customers VALUES (2, 'John', 'Paris');
Los espacios de más tras id y name hacen el trabajo de alineación: nadie los alinea
por usted, los escribe usted. El mismo truco da JSON indentado: { en una línea,
"id": ${{Id}}, en la siguiente, y el anidamiento son simplemente los espacios que
escribió.
El cuerpo de <data> no se recorta nunca; una lista en un atributo sí. En
value="a, b" el segundo elemento es b, no " b": el espacio junto a la coma es
relleno del separador, no dato. Dentro de <data> los espacios son suyos; dentro de
una lista value= se los limpian. La otra cara de «nunca se recorta»: un espacio final
que no ve seguirá estando en el fichero.
Interpolación — ${{Name}}
Dentro de <data>, ${{Name}} se reemplaza por el valor de esa secuencia en la fila
actual.
<block>
<line><data>Nombre: ${{Name}}, edad: ${{Age}}</data></line>
</block>
Nombre: Paulina, edad: 72 Nombre: Cruz, edad: 18 Nombre: Fabiola, edad: 64 Nombre: Santiago, edad: 26
Cómo se resuelve un nombre
Para cada ${{Name}}:
- Una secuencia declarada con valor en esta fila → ese valor.
- Una secuencia declarada sin valor en esta fila (filtrada por un
parenten esta iteración) → la cadena vacía. - Un nombre no declarado (una errata, una declaración olvidada) → error
TDC193, con una sugerencia:
error[TDC193]: "Nmae" is not a declared sequence — it would be printed literally help: did you mean "Name"?
Esta verificación se gana su lugar en la salida tipada: en un CSV quizá alcance a
detectar a simple vista un ${{Nmae}} perdido, pero en un archivo Parquet caería en una
columna tipada de texto y parecería un valor real, imposible de notar. Si de veras quiere
un ${{...}} literal en la salida (al generar una configuración, un archivo de GitHub
Actions, una plantilla de Handlebars), cambie el marcador con
inject y la verificación se apaga.
Nombres integrados
Siempre disponibles sin declarar nada: ${{_count}} (número de registro, empezando en 1),
${{_first}} / ${{_last}} ("true" / "false") y ${{_total}} (el total). Vea
Nombres integrados.
Filtros
Un valor se puede transformar en el lugar con una «tubería» |: una máscara, un cambio de
mayúsculas, un recorte, etcétera. Esto es formateo a la salida: el valor se genera
como siempre y luego el filtro le cambia la forma. Aquí un SSN estadounidense de nueve
dígitos (en crudo) recibe una máscara de presentación:
<data>${{Ssn}} -> ${{Ssn | mask:xxx-xx-xxxx}}</data>
407963258 -> 407-96-3258 539027461 -> 539-02-7461
Los filtros se encadenan de izquierda a derecha (${{Name | mask:w:w | upper}} — primero
la máscara, luego mayúsculas). El conjunto completo —mask,
upper/lower/capitalize/title, slice, replace, trim, group, compact y los
escapes csv/sql— está en Máscaras y mayúsculas.
Dónde no se ejecuta la interpolación
- No en los atributos. La interpolación ocurre solo dentro del texto de
<data>, nunca dentro del valor de un atributo. - Sin anidamiento. La interpolación es de una sola pasada. Si el valor de una
secuencia contiene a su vez
${{Something}}, ese marcador interno no se vuelve a procesar: se inserta tal cual.
Texto condicional con if
<data if="…"> se imprime solo cuando la expresión es verdadera. Si la condición es
falsa, ese <data> en particular se suprime para esa fila, y cualquier otro <data> de
la misma <line> se renderiza con normalidad. El uso clásico es «JSON sin coma colgante»:
un segundo <data if="!_last">, imprime una coma en cada registro menos el último.
<tdc>
<env count="3" seed="demo">
<before><line><data>[</data></line></before>
<after><line><data>]</data></line></after>
<sequence name="Id"><gen type="increment" value="1"/></sequence>
</env>
<block>
<line><data> {"id": ${{Id}}}</data><data if="!_last">,</data></line>
</block>
</tdc>
[
{"id": 1},
{"id": 2},
{"id": 3}
]Los dos <data> de una misma <line> se imprimen pegados: primero el objeto, luego la
coma. En el último registro !_last es falso, la coma se salta y el JSON sigue siendo
válido. El lenguaje de expresiones admite comparaciones (==, !=, <, >), los
booleanos !/&&/|| y los nombres integrados — el mismo que usa
<line if>.
Fixtures — texto alrededor de los registros
Las fixtures imprimen texto fijo en los bordes de la ejecución, para que el <block> por
registro se mantenga limpio:
<before>/<after>se imprimen una vez: antes del primer registro y después del último. Usos típicos: el[de apertura y el]de cierre de un arreglo JSON, una fila de encabezado de CSV, un prefijoINSERT INTO … VALUES, un;final.<before_block>/<after_block>/<delimiter_block>envuelven cada registro; el último de ellos se imprime entre registros pero no después del final.
Todas las fixtures viven dentro de <env> y, como el bloque,
contienen <line> → <data>. El [ y el ] del ejemplo JSON de arriba son <before> y
<after>:
<tdc>
<env count="3" seed="demo">
<before><line><data>[</data></line></before>
<after><line><data>]</data></line></after>
<sequence name="Id"><gen type="increment" value="1"/></sequence>
</env>
<block>
<line><data> ${{Id}}</data></line>
</block>
</tdc>
[ 1 2 3 ]
El [ se imprimió exactamente una vez arriba y el ] exactamente una vez abajo, sin
importar si count es 3 o 3 000.
Dos advertencias, ambas obligadas:
- La interpolación SÍ se ejecuta en las fixtures. Un
${{Name}}lee la fila junto a la que está la fixture:<before>ve la primera,<after>la última, y las de registro y de línea la fila que envuelven. Eso es lo que permite que un registro ponga sus propios campos alrededor de una lista anidada. (Es uno de los pocos comportamientos que hubo que corregir en la implementación de referencia: imprimía las llaves literalmente mientras los cuatro ports las expandían.) - Los generadores tampoco corren en las fixtures. Un
<gen>,<mix>o<switch>dentro de una fixture es el errorTDC131. Antes esto fallaba en silencio: un generadorvalue="500..999"en una fixture siempre emitía500(la cota inferior), lo que parecía un valor real.
Un marcador de interpolación personalizado
inject en <env> cambia los
marcadores de interpolación. El % es donde va el nombre, y necesita una parte de apertura
y otra de cierre a su alrededor — TDC corta el patrón por el % más a la derecha que
deja ambos lados no vacíos, que es lo que permite que el propio % aparezca en la envoltura:
<env inject="${{%}}">…</env> <!-- el valor por omisión -->
<env inject="[%]">…</env> <!-- entonces en <data> se escribe [Name] -->
<env inject="%{%}%">…</env> <!-- corta por el % central: se escribe %{Name}% -->
Con inject="[%]" y <data>Nombre: [Name]</data>:
Nombre: Paulina Nombre: Cruz Nombre: Fabiola Nombre: Santiago
Solo cambia la sintaxis de la sustitución, no los datos: los valores son idénticos a
los de la forma ${{Name}} por omisión. El prefijo y el sufijo se escapan
automáticamente, incluso cuando contienen metacaracteres de expresiones regulares. La
razón principal para cambiar el marcador es emitir un ${{...}} literal en la salida (una
configuración, una plantilla) sin disparar la verificación de nombres no declarados.
Texto crudo y un </data> literal
<data> es crudo, pero un <data> común se cierra en el primer </data> que
encuentra. Para emitir un </data> literal en el texto —por ejemplo, un fragmento de
sintaxis TDC en documentación generada— dele a la etiqueta un marcador
pair; tanto la etiqueta de apertura como la de cierre
deben llevar el mismo marcador. Aquí el ${{...}} también debe aparecer literal, así que
el ejemplo además aparta el marcador de interpolación con
inject:
<env count="1" seed="demo" inject="[[%]]">
<sequence name="Name"><gen type="text" value="a,b"/></sequence>
</env>
<block>
<line><data pair="doc-example">Para imprimir un nombre, escriba: <data>${{Name}}</data></data pair="doc-example"></line>
</block>
Para imprimir un nombre, escriba: <data>${{Name}}</data>
El valor de pair debe ser único dentro del archivo. Ahora el parser sabe que solo
</data pair="doc-example"> cierra el bloque, así que el </data> interno se trata como
texto común. (Sin el inject="[[%]]", ${{Name}} se interpolaría igualmente a un valor —
pair protege las etiquetas, no los marcadores de sustitución.)
Véase también
- Máscaras y mayúsculas — el conjunto completo de filtros y máscaras.
- Determinismo y proporciones —
seed,countypercent. - Dependencias jerárquicas — cómo
parentfiltra una secuencia, lo que da el caso de la cadena vacía de arriba. - Nombres integrados —
_count,_first,_last,_total. - Referencia de etiquetas —
<block>,<line>,<data>y las fixtures de un vistazo.