STXT Tree
1. Introducción
Este documento es STXT-TREE-SPEC; las demás especificaciones lo citan con ese nombre. Define la representación JSON canónica del árbol lógico que produce el parseo de un documento STXT válido y las dos operaciones de escritura: la forma canónica de texto de un árbol (sección 11) y el reformateado de un documento (sección 12).
Su propósito es que dos implementaciones conformes puedan mostrar, intercambiar
y comparar el mismo resultado de parseo sin depender de las clases, los getters o
la serialización automática de una plataforma concreta. Es, en particular, el
formato de referencia para los corpus de conformidad y para herramientas como
stxt describe.
Esta especificación representa el árbol lógico, no el fichero fuente. No pretende conservar comentarios, líneas vacías fuera de bloques, el estilo de indentación, la posición exacta de los nodos ni otras decisiones de presentación. Para reescribir un fichero conservando esos elementos hace falta una capa de análisis de fuente distinta del árbol.
La sintaxis de los documentos y el significado de sus nombres, valores, bloques, namespaces e indentación se definen en STXT-SPEC. Los schemas, templates y la resolución de definiciones no cambian este árbol.
1.1 Versión de esta especificación
Esta especificación lleva su propia fecha y su propio estado en los campos Last modif y
Status de su Metadata, independientes de los de las demás especificaciones de STXT y
con el significado que fija STXT-SPEC §1.1. Está en Zenith.
Depende por completo de STXT-SPEC: describe el árbol que produce su sintaxis.
2. Terminología
Las palabras clave "DEBE", "NO DEBE", "DEBERÍA", "NO DEBERÍA", y "PUEDE" deben interpretarse según RFC 2119 y RFC 8174: tienen ese significado únicamente cuando aparecen en mayúsculas, como aquí.
Los términos nodo, INLINE, BLOCK, nombre canónico, namespace efectivo y documento mantienen el significado de STXT-SPEC.
En este documento, una representación de árbol es un valor JSON que cumple las secciones 3 a 6. Un emisor es una implementación que transforma nodos STXT parseados en esa representación.
3. Valor de documento
La representación de un documento STXT DEBE ser un array JSON. Cada elemento del array es un nodo raíz, en el mismo orden en que aparece en el documento.
El array exterior es obligatorio porque STXT permite varios nodos raíz. Un
documento compuesto solamente por comentarios o líneas vacías es válido y se
representa como el array vacío [].
La representación no envuelve el array en un objeto ni incluye un nombre de documento, ruta de fichero, versión de implementación ni resultado de validación. Esos datos pertenecen a la aplicación que invoca el parser, no al documento STXT.
4. Representación de un nodo
Cada nodo DEBE representarse como un objeto JSON con los miembros comunes
name, canonicalName, namespace y form, más value y children si es
INLINE, o lines si es BLOCK.
| Miembro | Tipo JSON | Significado |
|---|---|---|
name |
string | Nombre lógico del nodo, tras el trim y la compactación de espacios de STXT-SPEC sección 4.1. Conserva mayúsculas, diacríticos y separadores del nombre lógico. |
canonicalName |
string | Nombre canónico calculado según STXT-SPEC sección 4.3: NFC, minúsculas Unicode y separadores compactados en -. |
namespace |
string | Namespace efectivo del nodo, ya heredado y normalizado a minúsculas. Es "" si no existe namespace efectivo. |
form |
string | Exactamente "inline" para un nodo : o "block" para un nodo >>. |
children |
array | Sólo en nodos inline: representaciones de sus hijos directos, en orden de aparición. Siempre existe para esa forma, incluso cuando está vacío. |
value |
string | Sólo en nodos inline: su valor inline ya normalizado con trim. Siempre existe para esa forma, incluso si es "". |
lines |
array de strings | Sólo en nodos block: las líneas lógicas del bloque, en orden. Siempre existe para esa forma, incluso si está vacío. |
Un nodo inline DEBE tener exactamente los miembros name, canonicalName,
namespace, form, value y children. Un nodo block DEBE tener exactamente
los miembros name, canonicalName, namespace, form y lines. No se permiten
miembros adicionales en la representación canónica.
Un bloque producido por el parser base no tiene hijos estructurados y, por tanto,
NO DEBE tener el miembro children. Su contenido indentado ya está representado
por lines como texto literal.
5. Valores y líneas de bloque
El miembro value contiene el valor INLINE después del trim izquierdo y derecho de
STXT-SPEC sección 10.1. No se aplican más conversiones: sigue siendo texto, aunque
un schema pueda validarlo como fecha, número u otro tipo.
El miembro lines conserva de forma exacta las líneas lógicas de un bloque según
STXT-SPEC secciones 10.2 y 10.3. Cada elemento es una string, incluida una línea
vacía inicial o intermedia representada por "". Como el parseo descarta las líneas
vacías finales de un bloque (STXT-SPEC §10.3), lines nunca termina en "":
un valor cuyo último elemento de lines fuera "" no es la representación de ningún
documento. No se sustituye el array por una string unida con \n: las líneas son la
unidad lógica del bloque y el array las conserva sin reconstrucción.
Los comentarios y las líneas vacías fuera de un bloque no producen nodos ni líneas.
Dentro de un bloque, una línea que empieza por # sólo aparece en lines si su
indentación la hace contenido literal, tal como define STXT-SPEC sección 9.1.
6. Identidad y datos excluidos
La identidad estructural de un nodo está formada por canonicalName y namespace.
Por ello no se emite un miembro qualifiedName: es un valor derivado, igual a
canonicalName cuando namespace es vacío y a namespace + ":" + canonicalName
en cualquier otro caso.
Tampoco se emite un miembro text: para un INLINE sería igual a value y para un
BLOCK sería la unión derivada de lines con saltos de línea. Ambos casos duplican
información y el segundo pierde distinciones necesarias para la conformidad.
Los números de línea, niveles de indentación, rutas de fichero, estilos de indentación, comentarios y representación original de un namespace NO DEBEN aparecer. Son metadatos de una entrada concreta o información descartada por el parseo, y convertirían dos fuentes semánticamente equivalentes en árboles distintos.
7. JSON y canonicalidad
El formato usa JSON estándar. Un emisor DEBE producir JSON válido y DEBERÍA codificarlo en UTF-8 sin BOM. La canonicalidad se define sobre el valor JSON y sus miembros, no sobre sus bytes: el orden de los miembros de un objeto, la sangría, los espacios y la elección equivalente de escapes JSON no cambian la representación.
Los corpus de conformidad DEBEN comparar el valor JSON tras parsearlo, no una cadena de caracteres. Una aplicación PUEDE elegir una presentación determinista para personas, por ejemplo JSON con dos espacios de sangría y salto de línea final.
Una especificación futura puede definir un perfil de serialización por bytes para firmas, hashes o cachés. Ese perfil no forma parte de STXT-TREE-SPEC.
8. Errores y validación
STXT-TREE-SPEC sólo representa documentos que han superado el parseo sintáctico. No define la serialización de excepciones, diagnósticos, advertencias, errores de discovery ni árboles parciales después de un error. En especial, el modo de recuperar un parser tras varios errores no es parte de este contrato.
Los tests de entradas inválidas siguen comprobando los códigos estables y las líneas definidos por las especificaciones correspondientes. Una representación normalizada de diagnósticos, si se necesita, será una especificación separada.
La validación por schema o template ocurre sobre el árbol ya producido. El resultado de esa validación no modifica la representación de árbol; una herramienta puede mostrarlo por otro canal.
9. Conformidad
Un emisor de árbol STXT es conforme si, para todo documento válido:
- Emite un array de todos los nodos raíz, preservando su orden.
- Emite todos y sólo los miembros exigidos por la sección 4.
- Aplica las normalizaciones y la herencia de namespace de STXT-SPEC antes de emitir.
- Conserva cada línea lógica de un bloque, incluidas las líneas vacías iniciales e intermedias; las finales no llegan al árbol: el parseo las descarta (STXT-SPEC §10.3).
- No emite metadatos de fuente ni valores derivados excluidos por la sección 6.
El directorio conformance/tree/ de la fuente de especificaciones contiene pares
normativos de documento .stxt y representación .json. Toda implementación que
sostenga STXT-TREE-SPEC DEBE producir un valor JSON igual al fichero esperado de
cada par.
10. Ejemplo normativo
Documento STXT de entrada:
# Comentario descartado
Documento (COM.EXAMPLE.DOCS):
Título: Informe
Cuerpo >>
Primera línea
# Esto es texto
Anexo:
Nota: sin namespace heredado lateralmenteSu representación es el siguiente valor JSON. Obsérvese que el comentario inicial
no aparece, el namespace está en minúsculas y Anexo tiene namespace vacío.
[
{
"name": "Documento",
"canonicalName": "documento",
"namespace": "com.example.docs",
"form": "inline",
"value": "",
"children": [
{
"name": "Título",
"canonicalName": "título",
"namespace": "com.example.docs",
"form": "inline",
"value": "Informe",
"children": []
},
{
"name": "Cuerpo",
"canonicalName": "cuerpo",
"namespace": "com.example.docs",
"form": "block",
"lines": ["Primera línea", "", "# Esto es texto"]
}
]
},
{
"name": "Anexo",
"canonicalName": "anexo",
"namespace": "",
"form": "inline",
"value": "",
"children": [
{
"name": "Nota",
"canonicalName": "nota",
"namespace": "",
"form": "inline",
"value": "sin namespace heredado lateralmente",
"children": []
}
]
}
]
11. Forma canónica de texto
Esta sección define la operación inversa a la de las secciones anteriores: del
árbol lógico al texto STXT. Dos implementaciones que escriban el mismo árbol
DEBEN producir exactamente el mismo texto. Es lo que permite que una herramienta
reescriba un fichero sin ruido en los diffs, que stxt export y las bibliotecas
serialicen igual, y que el kit de conformidad compare el texto escrito y no solo el
árbol.
La forma canónica es una función del árbol de la sección 4 y de un estilo de
indentación, que es un parámetro de la operación: TABS (un tabulador por nivel) o
SPACES_4 (cuatro espacios por nivel). Hay, por tanto, dos formas canónicas de cada
árbol, una por estilo; una herramienta DEBE ofrecer las dos y DEBERÍA usar
TABS cuando no se le indica otra cosa, de acuerdo con STXT-SPEC §4.4.
La operación se define para árboles que cumplen los invariantes de las
secciones 4 y 5 — los que produce un parseo: nombres válidos y ya
normalizados, valores inline sin blancos en los extremos, líneas de bloque sin blancos
finales. Para un árbol construido por programa que no los cumpla (un value con blancos
en los extremos, una línea con blancos finales), el texto escrito no reparsearía al mismo
árbol y el resultado no está definido; el caso de un lines que termina en "" tiene
además la regla explícita de la sección 11.1, punto 6.
11.1 Reglas
Para un array de nodos raíz, el texto es la concatenación de la escritura de cada raíz, con una línea vacía entre una raíz y la siguiente. Un array vacío produce la cadena vacía. Cada nodo se escribe así, con nivel 0 para las raíces y el del padre más uno para los hijos:
- La indentación del nivel, en el estilo elegido: nivel tabuladores o nivel grupos de cuatro espacios. El nivel 0 no lleva indentación.
- El nombre (
name) tal cual está en el árbol: es el nombre lógico, ya normalizado según STXT-SPEC §4.1, nunca el canónico. - El namespace entre paréntesis, precedido de un espacio, solo cuando hace
falta declararlo: en una raíz, si su
namespaceno es la cadena vacía; en un hijo, si sunamespacees distinto del de su padre. Un hijo cuyo namespace coincide con el del padre no lo escribe, aunque el fuente original lo repitiera: el árbol no guarda dónde se declaró, solo cuál es el efectivo (sección 6), y al reparsear el texto la herencia produce el mismo resultado. Se escribe en minúsculas, que es como está en el árbol. - La forma. Un nodo
inlineescribe:y, sivalueno es la cadena vacía, un espacio y el valor; sin valor, la línea termina en:sin espacio detrás. Un nodoblockescribe un espacio y>>. - El salto de línea
LF. Toda línea, incluida la última del documento, termina enLF; nunca se escribeCRLF. - El contenido: los hijos de un nodo
inline, en orden, cada uno escrito con esta misma regla un nivel más adentro; o las líneas de un nodoblock, cada una con la indentación de nivel + 1 seguida del texto de la línea yLF. Una línea vacía del bloque ("") se escribe con esa indentación y nada más: así el bloque se lee como una pieza. Si un árbol construido por programa terminaralinesen""—lo que el parseo nunca produce (sección 5)—, el escritor NO DEBE emitir esas líneas vacías finales: no sobrevivirían al reparseo (STXT-SPEC §10.3) y romperían la garantía de ida y vuelta.
No se escribe nada más: ni BOM, ni comentarios, ni líneas vacías fuera de los bloques, ni blancos finales. El texto resultante es un documento STXT válido y, reparseado, DEBE producir un árbol igual al de partida: esa es la garantía de ida y vuelta. Un texto canónico es además un punto fijo: escribir el árbol de un texto canónico devuelve ese mismo texto.
11.2 Ejemplo
El árbol del ejemplo de la sección 10 tiene esta forma canónica con el estilo
TABS. El comentario del fuente ha desaparecido, el namespace sale en minúsculas y
solo en la raíz que lo declara, y la línea vacía del bloque lleva la indentación del
bloque:
Documento (com.example.docs):
Título: Informe
Cuerpo >>
Primera línea
# Esto es texto
Anexo:
Nota: sin namespace heredado lateralmenteCon SPACES_4 el texto es el mismo con cada tabulador sustituido por cuatro espacios.
11.3 Conformidad
Un escritor STXT es conforme si, para todo árbol y en los dos estilos, produce
exactamente el texto de la sección 11.1. El directorio conformance/ de
la fuente de especificaciones contiene casos normativos de árbol y texto esperado; toda
implementación que sostenga esta sección DEBE producir el texto esperado de cada
uno, byte a byte.
12. Reformateado de un documento
La forma canónica pierde lo que el árbol no guarda: comentarios, líneas vacías fuera
de los bloques, el estilo de indentación original. Reformatear un documento es la
otra operación de escritura: reescribirlo línea a línea, sobre el texto original,
de modo que las líneas que el árbol describe queden en forma canónica y todas las
demás se conserven como las escribió su autor. Es lo que hacen stxt format, el
formateador de la extensión de VS Code y el playground, y dos herramientas conformes
DEBEN producir el mismo resultado.
El reformateado toma el documento y un estilo de indentación, y devuelve el documento reformateado junto con los errores de sintaxis encontrados. No repara un documento con errores ni los oculta; si conviene reformatear un documento con errores es decisión de la herramienta que llama.
12.1 Reglas
El resultado tiene las mismas líneas que el fuente, en el mismo orden, con el mismo
terminador de línea (si el fuente usa CRLF en alguna línea, todas salen con CRLF;
si no, con LF) y con salto de línea final solo si el fuente lo tenía. Un BOM inicial
NO DEBE conservarse. Cada línea se transforma según lo que es en el parseo del
documento sin esquema alguno (el formateado no tiene que ver con la validación):
- Una línea que abre un nodo se escribe en forma canónica
(sección 11.1, reglas 1 a 4): la indentación de su nivel en el estilo
pedido, el nombre lógico,
: valorcon exactamente un espacio —o:a secas sin valor—, o>>para un bloque. El namespace se escribe si, y solo si, el fuente lo escribía en esa línea: un hijo que repite el namespace de su padre es redundante pero legal, y quitarlo sería una edición, no un reformateado. Se escribe en minúsculas. - Una línea de texto de un bloque recibe la indentación del bloque (el nivel del
nodo
>>más uno) en el estilo pedido, seguida de su contenido tal cual: la indentación que la línea tuviera más allá de la del bloque es contenido (STXT-SPEC §10.2) y se conserva exactamente. Una línea vacía que precede a más texto del bloque es""en el contenido sea cual sea su aspecto en el fuente (STXT-SPEC §10.3) y se escribe con la indentación del bloque, como en la forma canónica. Las líneas vacías finales del bloque no son contenido (STXT-SPEC §10.3): se conservan según la regla 3, como cualquier línea vacía fuera de un bloque. - Cualquier otra línea —un comentario, una línea vacía fuera de un bloque, o una línea que el árbol no describe por un error de sintaxis— se conserva como está, con dos únicos retoques: se eliminan sus blancos finales, y las unidades enteras de indentación de su inicio se convierten una a una al estilo pedido. Una unidad es un tabulador o cuatro espacios, en cualquiera de los dos estilos; lo que sigue a la última unidad entera, incluido un resto que no llega a unidad, se conserva tal cual. Como STXT-SPEC §9 valida la indentación de un comentario como la de un nodo, en un documento que parsea todo comentario tiene un número entero de unidades y sale completo en el nuevo estilo; el resto solo sobrevive en documentos con errores, que esta conversión ni repara ni esconde.
De estas reglas se siguen tres propiedades que una implementación DEBE cumplir: el resultado es idempotente (reformatearlo de nuevo en el mismo estilo lo deja igual); reformatear al otro estilo y volver devuelve el texto de partida si este ya estaba formateado; y el documento reformateado produce el mismo árbol canónico que el fuente.
12.2 Ejemplo
Este documento, indentado con espacios, con un comentario, una línea vacía entre raíces y blancos sobrantes:
# Proyecto de ejemplo
Documento (COM.EXAMPLE.DOCS):
Título:Informe
Cuerpo >>
con dos espacios de más
última línea
Anexo (com.example.docs):
Nota: aquí no hay nadareformateado con TABS queda así. Se conservan el comentario, la línea vacía —que,
por ser final del bloque, no es contenido y sale sin indentar (regla 3)— y el
namespace redundante de Anexo; se normalizan la separación de Título, el >> de
Cuerpo, los blancos finales y la indentación; y la primera línea del bloque conserva
sus dos espacios de más, que son contenido:
# Proyecto de ejemplo
Documento (com.example.docs):
Título: Informe
Cuerpo >>
con dos espacios de más
última línea
Anexo (com.example.docs):
Nota: aquí no hay nada12.3 Conformidad
Un formateador STXT es conforme si, para todo documento y en los dos estilos,
produce exactamente el texto de la sección 12.1 y los mismos errores de
sintaxis que el parser. Los casos normativos de documento y texto reformateado
esperado viven también en el directorio conformance/ de la fuente de
especificaciones.