STXT Tree

Estado:
Zenith
última modificación:
2026-09-07

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 lateralmente

Su 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:

  1. La indentación del nivel, en el estilo elegido: nivel tabuladores o nivel grupos de cuatro espacios. El nivel 0 no lleva indentación.
  2. 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.
  3. El namespace entre paréntesis, precedido de un espacio, solo cuando hace falta declararlo: en una raíz, si su namespace no es la cadena vacía; en un hijo, si su namespace es 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.
  4. La forma. Un nodo inline escribe : y, si value no es la cadena vacía, un espacio y el valor; sin valor, la línea termina en : sin espacio detrás. Un nodo block escribe un espacio y >>.
  5. El salto de línea LF. Toda línea, incluida la última del documento, termina en LF; nunca se escribe CRLF.
  6. 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 nodo block, cada una con la indentación de nivel + 1 seguida del texto de la línea y LF. 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 terminara lines en "" —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 lateralmente

Con 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):

  1. 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, : valor con 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.
  2. 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.
  3. 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 nada

reformateado 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 nada

12.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.